Chatbot: Typing

No notes defined.

<div class="sds-chatbot__container sds-stackXl">

    <!-- Chatbot -->
    <div class="sds-chatbot -hasValue" 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="Est-ce que ma fille de 12 ans peut ouvrir un compte à la Spuerkeess ?" placeholder="Posez-moi votre question" autocomplete="off" />

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

                <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="80" aria-valuemin="0" aria-valuemax="100" aria-valuetext="80 % 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" />

                    <circle class="sds-circularProgress__shape" cx="10" cy="10" r="9" fill="none" stroke-dasharray="45.2389336 56.548667" transform="rotate(-90 10 10)" />

                </svg>
            </div>

        </div>

    </div>

</div>
<div class="{{ namespace }}chatbot__container {{ namespace }}stackXl">

	<!-- Chatbot -->
	<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 %}
			/>
			{% if showClear %}
				{% render "@icon-btn-secondary--ghost", {
					classes: [namespace + "chatbot__clear"],
					icon: "icon-cross",
					action: clearActionLabel,
					attrs: {
						"data-chatbot-clear": "true"
					}
				}, true %}
			{% endif %}
			{% 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>
			<ul class="{{ namespace }}chatbot__suggestions list-unstyled" data-chatbot-suggestions>
				{% 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>
		{% endif %}
	</div>

	{% if isLoading or answerText or noResultText %}
	<!-- Réponse -->
	<div class="{{ namespace }}chatbot__response {{ namespace }}stackXl">

		{% if showResponseHeading %}
			<h3 class="h3">Réponse</h3>
		{% endif %}

		{% if isLoading %}
			<p class="sr-only" role="status">{{ loadingLabel }}</p>
			<section aria-labelledby="{{ responseHeadingId }}">
				<h4 class="sr-only" id="{{ responseHeadingId }}">{{ responseHeading }}</h4>
				<div class="{{ namespace }}box -boxInsetLg {{ namespace }}chatbot__responseBox">
					<div class="{{ namespace }}stackSm" aria-hidden="true">
						{% for skeletonLine in ["100%", "100%", "100%", "100%", "40%"] %}
							{% render "@skeleton-unit--small", { inlineCSS: { "width": skeletonLine } }, true %}
						{% endfor %}
					</div>
				</div>
			</section>
		{% elseif answerText %}
			<section aria-labelledby="{{ responseHeadingId }}" aria-live="polite">
				<h4 class="sr-only" id="{{ responseHeadingId }}">{{ responseHeading }}</h4>
				{% render "@chat-message--chatbot-without-indicator", {
					classes: [namespace + "chatbot__responseBox"],
					text: answerText,
					rate: true,
					ratingActive: false
				}, true %}
			</section>
		{% elseif noResultText %}
			<section aria-labelledby="{{ responseHeadingId }}" aria-live="polite">
				<h4 class="sr-only" id="{{ responseHeadingId }}">{{ responseHeading }}</h4>
				<div class="{{ namespace }}box -boxInsetLg {{ namespace }}chatbot__responseBox">
					<div class="{{ namespace }}wysiwyg">
						<p>{{ noResultText }}</p>
					</div>
				</div>
			</section>
		{% endif %}
	</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 suggestionsHintId = input.getAttribute("aria-describedby");
    
    		// 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("-hasValue", 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);
    		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("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();
    			});
    		});
    
    		// 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: 2.5 KB
  • Content:
    .#{$namespace}chatbot {
    
    	$self: &;
    
    	display: flex;
    	flex-direction: column;
    	gap: var(--ui-space-stack-static-500);
    	width: 100%;
    	padding: var(--ui-space-inset-vertical-static-500) var(--ui-space-inset-horizontal-static-1000);
    	border-radius: var(--sys-border-radius-1000);
    	@include custom-prop-fallback("background-color", "comp-box-background-color");
    	box-shadow: map-deep-get($token-shadow-map, "active");
    
    	&__bar {
    
    		display: flex;
    		align-items: center;
    		gap: var(--ui-space-inline-static-1000);
    
    	}
    
    	&__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;
    
    	}
    
    	&__suggestions {
    
    		padding-block-start: var(--ui-space-inset-vertical-static-500);
    
    	}
    
    	&__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");
    
    			}
    
    		}
    
    	}
    
    	&__input,
    	&__suggestion {
    
    		&:focus-visible {
    			outline-width: map-deep-get($design-tokens, "comp-search-focused-border-width");
    			outline-color: map-deep-get($design-tokens, "comp-search-focused-border-color");
    			outline-style: solid;
    			outline-offset: 0;
    
    		}
    
    	}
    
    	&__responseBox {
    		&#{$self}__responseBox#{$self}__responseBox {
    			width: 100%;
    			padding-bottom: var(--comp-box-large-inset-v) !important;
    			box-shadow: map-deep-get($token-shadow-map, "active");
    		}
    	}
    
    	&__container {
    		display: flex;
    		flex-direction: column;
    
    		width: 100%;
    	}
    
    	&.-hasValue {
    
    		#{$self}__suggestions {
    
    			display: none;
    
    		}
    
    	}
    
    }
    
  • URL: /components/raw/chatbot/_chatbot.scss
  • Filesystem Path: components/token-based/actions-and-inputs/chatbot/_chatbot.scss
  • Size: 2.1 KB