Chatbot: Default

No notes defined.

<div class="sds-chatbot -chatbotSuggestionsShown" data-js-chatbot>
    <label class="sr-only" for="chatbot-question">Posez votre question</label>
    <div class="sds-chatbot__bar">
        <input id="chatbot-question" class="sds-chatbot__input form-control" data-chatbot-input type="text" name="question" value="" placeholder="Posez-moi votre question" autocomplete="off" aria-describedby="chatbot-suggestions-hint" />
        <button type="button" class="sds-btn -iconBtn -btnSecondary -ghost sds-chatbot__clear" data-chatbot-clear="true">

            <span aria-hidden="true" class="sds-icon sds-icon-cross"></span>

            <span class="sr-only">Effacer la question</span>

        </button>
        <button type="button" class="sds-btn -btnPrimary sds-chatbot__submit" data-chatbot-submit="true" disabled>

            <span class="sds-btn__text">
                Envoyer

            </span>

        </button>

        <div class="sds-circularProgress -circularProgressTokenUsage sds-chatbot__tokenUsage" role="meter" aria-label="Utilisation des crédits IA" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" aria-valuetext="0 % des crédits IA utilisés">
            <svg class="sds-circularProgress__svg" viewBox="0 0 20 20" aria-hidden="true" focusable="false">

                <circle class="sds-circularProgress__bgShape" cx="10" cy="10" r="9" fill="none" />

            </svg>
        </div>

    </div>

    <p class="sr-only" id="chatbot-suggestions-hint">Questions fréquentes. Appuyez sur Flèche bas pour accéder aux suggestions, puis utilisez les flèches haut et bas pour les parcourir.</p>
    <div class="sds-chatbot__suggestions list-unstyled" data-chatbot-suggestions>
        <ul class="list-unstyled">

            <li class="sds-chatbot__suggestionItem">
                <button class="sds-chatbot__suggestion" type="button" data-chatbot-suggestion value="Comment puis-je consulter le solde et les mouvements de mon compte via l’application Spuerkeess ?">
                    <span class="sds-icon sds-icon-search" aria-hidden="true"></span>
                    <span>Comment puis-je consulter le solde et les mouvements de mon compte via l’application Spuerkeess ?</span>
                </button>
            </li>

            <li class="sds-chatbot__suggestionItem">
                <button class="sds-chatbot__suggestion" type="button" data-chatbot-suggestion value="Quels sont les frais et les conditions pour ouvrir un compte bancaire chez Spuerkeess ?">
                    <span class="sds-icon sds-icon-search" aria-hidden="true"></span>
                    <span>Quels sont les frais et les conditions pour ouvrir un compte bancaire chez Spuerkeess ?</span>
                </button>
            </li>

            <li class="sds-chatbot__suggestionItem">
                <button class="sds-chatbot__suggestion" type="button" data-chatbot-suggestion value="Que dois-je faire si j’ai perdu ma carte bancaire ou si je remarque une opération suspecte sur mon compte ?">
                    <span class="sds-icon sds-icon-search" aria-hidden="true"></span>
                    <span>Que dois-je faire si j’ai perdu ma carte bancaire ou si je remarque une opération suspecte sur mon compte ?</span>
                </button>
            </li>

        </ul>
    </div>

</div>
<div class="{{ namespace }}chatbot{% for mod in modifiers %} {{ mod }}{% endfor %}{% for componentClass in classes %} {{ componentClass }}{% endfor %}"
     data-js-chatbot
	 {% for attrKey, attr in attrs %} {{ attrKey }}="{{ attr }}"{% endfor %}
	{% if isLoading %} aria-busy="true"{% endif %}>
	<label class="sr-only" for="{{ id }}">{{ label }}</label>
	<div class="{{ namespace }}chatbot__bar">
		<input
			id="{{ id }}"
			class="{{ namespace }}chatbot__input form-control"
			data-chatbot-input
			type="text"
			name="{{ inputName }}"
			value="{{ value }}"
			placeholder="{{ placeholder }}"
			autocomplete="off"
			{% if isLoading %} readonly{% endif %}
			{% if suggestions.length and not value and not isLoading %} aria-describedby="{{ suggestionsHintId }}"{% endif %}
		/>
		{% render "@icon-btn-secondary--ghost", {
			classes: [namespace + "chatbot__clear"],
			icon: "icon-cross",
			action: clearActionLabel,
			attrs: {
				"data-chatbot-clear": "true"
			}
		}, true %}
		{% render "@btn-primary", {
			classes: [namespace + "chatbot__submit"],
			text: submitLabel,
			disabled: submitDisabled,
			attrs: {
				"data-chatbot-submit": "true"
			}
		}, true %}
		{% if tokenUsage %}
			{% render "@circular-progress--token-usage", {
				classes: [namespace + "chatbot__tokenUsage"],
				attrs: tokenUsage.attrs,
				progress: tokenUsage.progress,
				tokenUsageDanger: tokenUsage.danger,
				a11yLabel: tokenUsage.a11yLabel,
				a11yValueText: tokenUsage.a11yValueText
			}, true %}
		{% endif %}
	</div>

	{% if suggestions.length and not value and not isLoading %}
		<p class="sr-only" id="{{ suggestionsHintId }}">{{ suggestionsHint }}</p>
		<div class="{{ namespace }}chatbot__suggestions list-unstyled" data-chatbot-suggestions>
			<ul class="list-unstyled">
				{% for suggestion in suggestions %}
					<li class="{{ namespace }}chatbot__suggestionItem">
						<button class="{{ namespace }}chatbot__suggestion" type="button" data-chatbot-suggestion value="{{ suggestion }}">
							<span class="{{ namespace }}icon {{ namespace }}icon-search" aria-hidden="true"></span>
							<span>{{ suggestion }}</span>
						</button>
					</li>
				{% endfor %}
			</ul>
		</div>
	{% endif %}
</div>
  • Content:
    export default class Chatbot {
    
    	// Enhances the server-rendered component with input state and suggestion navigation.
    	constructor() {
    
    		this.chatbots = document.querySelectorAll("[data-js-chatbot]");
    		this.chatbots.forEach((chatbot) => this.init(chatbot));
    
    	}
    
    	init(chatbot) {
    
    		const input = chatbot.querySelector("[data-chatbot-input]");
    
    		if (!input) return;
    
    		const submitButton = chatbot.querySelector("[data-chatbot-submit]");
    		const clearButton = chatbot.querySelector("[data-chatbot-clear]");
    		const suggestions = Array.from(chatbot.querySelectorAll("[data-chatbot-suggestion]"));
    		const suggestionsContainer = chatbot.querySelector("[data-chatbot-suggestions]");
    		const suggestionsHintId = input.getAttribute("aria-describedby");
    		let isPointerOverSuggestions = false;
    
    		const isSuggestion = (element) => suggestions.some((suggestion) => suggestion.contains(element));
    		const setFocusedState = (isFocused) => chatbot.classList.toggle("-chatbotFocused", isFocused);
    
    		chatbot.addEventListener("keydown", (event) => {
    			if (event.key === "Escape") {
    				setFocusedState(false);
    			}
    		});
    
    		input.addEventListener("focus", () => setFocusedState(input.value.length < 1));
    		input.addEventListener("blur", (event) => {
    			if (!isPointerOverSuggestions && !isSuggestion(event.relatedTarget)) {
    				setFocusedState(false);
    			}
    		});
    
    		suggestionsContainer?.addEventListener("pointerenter", () => {
    			isPointerOverSuggestions = true;
    		});
    		suggestionsContainer?.addEventListener("pointerleave", () => {
    			isPointerOverSuggestions = false;
    
    			if (document.activeElement !== input && !isSuggestion(document.activeElement)) {
    				setFocusedState(false);
    			}
    		});
    
    		// Keep the Figma variant's initial button state, then derive it from user input.
    		const updateValueState = ({ preserveSubmitState = false } = {}) => {
    			const hasValue = input.value.trim() !== "";
    
    			chatbot.classList.toggle("-chatbotHasValue", hasValue);
    
    			if (clearButton) {
    				clearButton.hidden = !hasValue;
    			}
    
    			if (submitButton && !preserveSubmitState && !chatbot.hasAttribute("aria-busy")) {
    				submitButton.disabled = !hasValue;
    			}
    
    			if (suggestionsHintId) {
    				input.toggleAttribute("aria-describedby", !hasValue);
    				if (!hasValue) input.setAttribute("aria-describedby", suggestionsHintId);
    			}
    		};
    
    		input.addEventListener("input", () => {
    			updateValueState();
    			setFocusedState(input.value.length < 1);
    		});
    		input.addEventListener("keydown", (event) => {
    			// Suggestions are regular buttons, so move real focus into the list.
    			if (event.key === "ArrowDown" && suggestions.length && input.value.trim() === "") {
    				event.preventDefault();
    				suggestions[0].focus();
    			}
    		});
    
    		clearButton?.addEventListener("click", () => {
    			input.value = "";
    			updateValueState();
    			input.focus();
    		});
    
    		suggestions.forEach((suggestion, index) => {
    			suggestion.addEventListener("focus", () => setFocusedState(true));
    			suggestion.addEventListener("blur", (event) => {
    				if (!isPointerOverSuggestions && event.relatedTarget !== input && !isSuggestion(event.relatedTarget)) {
    					setFocusedState(false);
    				}
    			});
    
    			suggestion.addEventListener("keydown", (event) => {
    				// Keep keyboard focus inside the suggestion list, with input as its first item.
    				if (event.key === "ArrowDown" && index < suggestions.length - 1) {
    					event.preventDefault();
    					suggestions[index + 1].focus();
    				}
    
    				if (event.key === "ArrowUp") {
    					event.preventDefault();
    					(index === 0 ? input : suggestions[index - 1]).focus();
    				}
    
    				if (event.key === "Escape") {
    					event.preventDefault();
    					input.focus();
    				}
    			});
    
    			suggestion.addEventListener("click", () => {
    				input.value = suggestion.value;
    				updateValueState();
    				input.focus();
    				setFocusedState(false);
    			});
    		});
    
    		// Do not overwrite the disabled state supplied by the current Fractal/backend variant.
    		updateValueState({ preserveSubmitState: true });
    
    	}
    }
    
  • URL: /components/raw/chatbot/Chatbot.js
  • Filesystem Path: components/token-based/actions-and-inputs/chatbot/Chatbot.js
  • Size: 4 KB
  • Content:
    .#{$namespace}chatbot {
    
    	$self: &;
    
    	position: relative;
    	box-shadow: map-deep-get($token-shadow-map, "active");
    	border-radius: var(--sys-border-radius-1000);
    
    	&__bar {
    
    		position: relative;
    		z-index: z("low");
    		display: flex;
    		align-items: center;
    		gap: var(--ui-space-inline-static-1000);
    		padding: var(--ui-space-inset-vertical-static-500) var(--ui-space-inset-horizontal-static-1000);
    		@include custom-prop-fallback("background-color", "comp-box-background-color");
    		border-radius: inherit;
    	}
    
    	&__input {
    
    		flex: 1 1 auto;
    		padding-inline: var(--comp-input-inset-h);
    		border: 0;
    		background-color: transparent !important;
    		@include custom-prop-fallback("color", "comp-search-filled-text-color");
    
    		&::placeholder {
    			@include custom-prop-fallback("color", "comp-search-enabled-text-color");
    		}
    
    	}
    
    	&__clear,
    	&__submit {
    
    		flex: 0 0 auto;
    
    	}
    
    	&__clear {
    
    		display: none;
    
    	}
    
    	&__suggestions {
    
    		padding: var(--ui-space-inset-vertical-static-500) var(--ui-space-inset-horizontal-static-1000);
    		position: absolute;
    		top: 100%;
    		left: 0;
    		right: 0;
    		@include custom-prop-fallback("background-color", "comp-box-background-color");
    		border-radius: inherit;
    		border-start-start-radius: 0;
    		border-start-end-radius: 0;
    		box-shadow: map-deep-get($token-shadow-map, "active");
    		display: none;
    
    	}
    
    	&__suggestion {
    
    		display: flex;
    		align-items: flex-start;
    		gap: var(--ui-space-inline-static-500);
    		width: 100%;
    		padding: var(--ui-space-inset-vertical-static-375) var(--ui-space-inset-horizontal-static-250);
    		@include custom-prop-fallback("color", "comp-input-text-color");
    		text-align: left;
    
    		&:hover {
    
    			@media (hover: hover) {
    
    				@include custom-prop-fallback("background-color", "comp-box-sunken-background-color");
    
    			}
    
    		}
    
    	}
    
    	&__suggestion {
    
    		&:focus-visible {
    			@include box-outline();
    			@include custom-prop-fallback("outline-color","comp-input-active-border-color","true","true")
    		}
    
    	}
    
    	&.-chatbotHasValue {
    
    		#{$self}__clear {
    
    			display: inline-flex;
    
    		}
    
    		#{$self}__suggestions {
    
    			display: none;
    
    		}
    
    	}
    
    	&.-chatbotFocused {
    		#{$self}__suggestions {
    
    			display: block;
    
    		}
    		#{$self}__bar {
    			border-end-start-radius: 0;
    			border-end-end-radius: 0;
    			&:after {
    				content: "";
    				position: absolute;
    				bottom: 0;
    				left: var(--ui-space-inset-horizontal-static-1000);
    				right: var(--ui-space-inset-horizontal-static-1000);
    				border-top: var(--sys-border-width-thin) solid var(--comp-radio-border-color);
    			}
    		}
    	}
    
    	&:has(INPUT:focus-visible) {
    
    		#{$self}__bar {
    			@include box-outline("comp-search-focused-border-color");
    			@include custom-prop-fallback("outline-color","comp-input-active-border-color","true","true");
    
    			&::after {
    				content: none;
    			}
    		}
    
    		INPUT {
    			outline: none !important;
    		}
    
    	}
    
    }
    
  • URL: /components/raw/chatbot/_chatbot.scss
  • Filesystem Path: components/token-based/actions-and-inputs/chatbot/_chatbot.scss
  • Size: 2.8 KB