From 6bff88ed10287ae8385b4e36f2c64df71582a5e6 Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso Date: Fri, 3 Jul 2026 11:42:30 +0200 Subject: [PATCH 01/22] first approach to message input component using prompt input from assure360 --- packages/lib/src/index.ts | 1 + .../message-input/MessageInput.stories.tsx | 28 ++ .../lib/src/message-input/MessageInput.tsx | 319 ++++++++++++++++++ packages/lib/src/message-input/types.ts | 200 +++++++++++ packages/lib/src/message-input/utils.ts | 95 ++++++ 5 files changed, 643 insertions(+) create mode 100644 packages/lib/src/message-input/MessageInput.stories.tsx create mode 100644 packages/lib/src/message-input/MessageInput.tsx create mode 100644 packages/lib/src/message-input/types.ts create mode 100644 packages/lib/src/message-input/utils.ts diff --git a/packages/lib/src/index.ts b/packages/lib/src/index.ts index ed05c0296..e776fe13c 100644 --- a/packages/lib/src/index.ts +++ b/packages/lib/src/index.ts @@ -27,6 +27,7 @@ export { default as DxcHeading } from "./heading/Heading"; export { default as DxcImage } from "./image/Image"; export { default as DxcInset } from "./inset/Inset"; export { default as DxcLink } from "./link/Link"; +export { default as DxcMessageInput } from "./message-input/MessageInput"; export { default as DxcNavTabs } from "./nav-tabs/NavTabs"; export { default as DxcNumberInput } from "./number-input/NumberInput"; export { default as DxcPaginator } from "./paginator/Paginator"; diff --git a/packages/lib/src/message-input/MessageInput.stories.tsx b/packages/lib/src/message-input/MessageInput.stories.tsx new file mode 100644 index 000000000..13b653bf0 --- /dev/null +++ b/packages/lib/src/message-input/MessageInput.stories.tsx @@ -0,0 +1,28 @@ +import { Meta, StoryObj } from "@storybook/react-vite"; +import DxcMessageInput from "./MessageInput"; +import Title from "../../.storybook/components/Title"; +import ExampleContainer from "../../.storybook/components/ExampleContainer"; + +export default { + title: "Message Input", + component: DxcMessageInput, +} satisfies Meta; + +type Story = StoryObj; + +const MessageInput = () => ( + <> + + <ExampleContainer> + <DxcMessageInput placeholder="Ask me anything..." /> + </ExampleContainer> + <Title title="Files" level={4} /> + <ExampleContainer> + <DxcMessageInput placeholder="Ask me anything..." allowFileUploads /> + </ExampleContainer> + </> +); + +export const Chromatic: Story = { + render: MessageInput, +}; diff --git a/packages/lib/src/message-input/MessageInput.tsx b/packages/lib/src/message-input/MessageInput.tsx new file mode 100644 index 000000000..36a03ed1c --- /dev/null +++ b/packages/lib/src/message-input/MessageInput.tsx @@ -0,0 +1,319 @@ +import { ChangeEvent, FocusEvent, KeyboardEvent, MouseEvent, useContext, useId, useRef, useState } from "react"; +import styled from "@emotion/styled"; +import PromptInputPropsType, { Item } from "./types"; +import { inputStylesByStatePromptInput, isLengthIncorrect } from "./utils"; +import DxcButton from "../button/Button"; +import DxcChip from "../chip/Chip"; +import DxcContainer from "../container/Container"; +import DxcDropdown from "../dropdown/Dropdown"; +import DxcFlex from "../flex/Flex"; +import { HalstackLanguageContext } from "../HalstackContext"; +import ErrorMessage from "../styles/forms/ErrorMessage"; + +const sizes = { + small: "240px", + medium: "360px", + large: "480px", + fillParent: "100%", +} as const; + +const MessageInputContainer = styled.div<{ size: PromptInputPropsType["size"] }>` + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: var(--spacing-gap-xs); + max-height: 320px; + box-shadow: 0 -24px 10px 4px rgba(255, 255, 255, 0.6); + width: ${({ size = "medium" }) => sizes[size]}; +`; + +const MessageInput = styled.div<{ + disabled: Required<PromptInputPropsType>["disabled"]; + error: boolean; + focused: boolean; +}>` + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + gap: var(--spacing-gap-s); + height: 100%; + padding: var(--spacing-padding-m) var(--spacing-padding-xs); + ${({ disabled, error, focused }) => inputStylesByStatePromptInput(disabled, error, focused)} + overflow: hidden; +`; + +const Input = styled.textarea` + max-width: 100%; + background: none; + border: none; + outline: none; + padding: var(--spacing-padding-none) var(--spacing-padding-xs); + resize: none; + flex-grow: 1; + color: ${({ disabled }) => (disabled ? "var(--color-fg-neutral-medium)" : "var(--color-fg-neutral-dark)")}; + font-family: var(--typography-font-family); + font-size: var(--typography-label-m); + font-weight: var(--typography-label-regular); + white-space: pre-wrap; + word-break: break-word; + overflow-y: auto; + + ::placeholder { + color: ${({ disabled }) => (disabled ? "var(--color-fg-neutral-medium)" : "var(--color-fg-neutral-strong)")}; + } + ${({ disabled }) => disabled && "cursor: not-allowed;"} +`; + +const HelperText = styled.span<{ disabled: boolean; hasMargin?: boolean }>` + color: ${({ disabled }) => (disabled ? "var(--color-fg-neutral-medium)" : "var(--color-fg-neutral-stronger)")}; + font-family: var(--typography-font-family); + font-size: var(--typography-helper-text-s); + font-weight: var(--typography-helper-text-regular); + ${({ hasMargin }) => hasMargin && "margin-bottom: var(--spacing-padding-xxs);"} +`; + +const DxcMessageInput = ({ + allowFileUploads = false, + defaultValue = "", + disabled = false, + error, + isLoading, + helperText, + maxLength, + minLength, + placeholder = "", + onBlur, + onChange, + value, + onSubmit, + size = "medium", + topItems, + onTopItemsChange, + bottomActions = [], + stop, +}: PromptInputPropsType) => { + const inputId = `input-${useId()}`; + const errorId = `error-${inputId}`; + const translatedLabels = useContext(HalstackLanguageContext); + const inputRef = useRef<HTMLTextAreaElement | null>(null); + const inputContainerRef = useRef<HTMLDivElement | null>(null); + const [innerValue, setInnerValue] = useState(defaultValue); + const [innerTopItems, setInnerTopItems] = useState<Item[]>([]); + const fileInputRef = useRef<HTMLInputElement | null>(null); + const [isInputFocused, setIsInputFocused] = useState(false); + + const changeValue = (newValue: string) => { + if (value == null) { + setInnerValue(newValue); + } + if (isLengthIncorrect(newValue, minLength, maxLength)) { + onChange?.({ + value: newValue, + error: translatedLabels.formFields.lengthErrorMessage?.(minLength, maxLength), + }); + } else { + onChange?.({ value: newValue }); + } + }; + + const handleInputContainerOnClick = () => { + inputRef.current?.focus(); + }; + const handleInputContainerOnMouseDown = (event: MouseEvent<HTMLDivElement>) => { + // Avoid input to lose the focus when the container is pressed + if (document.activeElement === inputRef.current) { + event.preventDefault(); + } + }; + + const handleInputOnChange = (event: ChangeEvent<HTMLTextAreaElement>) => { + const el = event.target; + + el.style.height = "auto"; + + el.style.height = `${el.scrollHeight}px`; + + changeValue(el.value); + }; + + const handleInputOnFocus = () => { + setIsInputFocused(true); + }; + const handleInputOnBlur = (event: FocusEvent<HTMLTextAreaElement>) => { + setIsInputFocused(false); + if (isLengthIncorrect(event.target.value, minLength, maxLength)) { + onBlur?.({ + value: event.target.value, + error: translatedLabels.formFields.lengthErrorMessage?.(minLength, maxLength), + }); + } else { + onBlur?.({ value: event.target.value }); + } + }; + const handleInputOnKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => { + if (event.key === "Enter" && !event.shiftKey) { + event.preventDefault(); + void handleSubmit(); + } + }; + + const handleFileSelect = () => { + fileInputRef.current?.click(); + }; + + const addTopItems = (items: Item[]) => { + const currentItems = topItems ?? innerTopItems; + const newItems = [...currentItems, ...items]; + if (topItems == null) { + setInnerTopItems(newItems); + } + onTopItemsChange?.(newItems); + }; + + const removeItem = (label: string) => { + const currentItems = topItems ?? innerTopItems; + const filteredItems = currentItems.filter((item) => item.label !== label); + if (topItems == null) { + setInnerTopItems(filteredItems); + } + onTopItemsChange?.(filteredItems); + }; + + const handleFilesChosen = (e: React.ChangeEvent<HTMLInputElement>) => { + if (!e.target.files) return; + const formData = new FormData(); + Array.from(e.target.files).forEach((file) => formData.append("files", file)); + + addTopItems( + Array.from(e.target.files).map((file) => ({ + id: crypto.randomUUID(), + label: file.name, + prefixIcon: "description", + disabled: disabled, + })) + ); + + // TBD + + // const res = await fetch("/api/context-files", { + // method: "POST", + // body: formData, + // }); + + // const uploadedFiles = await res.json(); + + e.target.value = ""; + }; + + const handleSubmit = async () => { + if (disabled || isLoading) return; + + const controller = new AbortController(); + + try { + const result = onSubmit?.(controller.signal); + if (result instanceof Promise) { + await result; + } + } catch (e) { + if ((e as DOMException).name !== "AbortError") { + throw e; + } + } + }; + + return ( + <MessageInputContainer size={size}> + {helperText && ( + <HelperText disabled={disabled} hasMargin> + {helperText} + </HelperText> + )} + <MessageInput + disabled={disabled} + error={!!error} + focused={isInputFocused} + onClick={handleInputContainerOnClick} + onMouseDown={handleInputContainerOnMouseDown} + ref={inputContainerRef} + > + {allowFileUploads && ( + <DxcFlex alignItems="center" gap="var(--spacing-gap-xs)"> + <DxcDropdown + icon="add" + options={[{ label: "Attach documents", value: "fileorphoto" }]} + onSelectOption={handleFileSelect} + /> + <input ref={fileInputRef} type="file" hidden multiple onChange={handleFilesChosen} /> + <DxcContainer overflow={{ x: "auto" }} width="100%"> + <DxcFlex gap="var(--spacing-gap-xs)"> + {(topItems ?? innerTopItems)?.map((item) => ( + <DxcChip + label={item.label} + mode="dismissible" + prefix={item.prefixIcon} + onClick={() => removeItem(item.label)} + key={item.id} + /> + ))} + </DxcFlex> + </DxcContainer> + </DxcFlex> + )} + <Input + aria-errormessage={error ? errorId : undefined} + aria-invalid={!!error} + disabled={disabled} + id={inputId} + onBlur={handleInputOnBlur} + onFocus={handleInputOnFocus} + onChange={handleInputOnChange} + onKeyDown={handleInputOnKeyDown} + onMouseDown={(event) => { + event.stopPropagation(); + }} + placeholder={placeholder} + ref={inputRef} + maxLength={maxLength} + minLength={minLength} + value={value ?? innerValue} + /> + {/* TODO: Replace with space-between if there is dropdown */} + <DxcFlex justifyContent="flex-end" alignItems="center"> + {/* <DxcDropdown + label="Select a model" + options={[{ label: "AA 2.0", value: "aa2.0" }]} + onSelectOption={() => {}} + /> */} + {/* <DxcSelect options={[{ label: "AA 2.0", value: "aa2.0" }]} defaultValue="aa2.0" size="small" /> */} + {/* TODO: Add model selection dropdown */} + <DxcFlex gap="var(--spacing-gap-xs)"> + {bottomActions.map((action) => ( + <DxcButton + icon={action.icon} + size={{ height: "medium" }} + disabled={disabled} + onClick={action.onClick} + title={!disabled ? (action.title ?? undefined) : undefined} + mode="tertiary" + key={action.id} + /> + ))} + <DxcButton + icon={!isLoading ? "send" : "filled_stop"} + size={{ height: "medium" }} + disabled={disabled} + onClick={!isLoading ? handleSubmit : stop} + title={!isLoading ? "Submit" : "Stop"} + mode="primary" + /> + </DxcFlex> + </DxcFlex> + </MessageInput> + {!disabled && typeof error === "string" && <ErrorMessage error={error} id={errorId} />} + </MessageInputContainer> + ); +}; + +export default DxcMessageInput; diff --git a/packages/lib/src/message-input/types.ts b/packages/lib/src/message-input/types.ts new file mode 100644 index 000000000..717851bf9 --- /dev/null +++ b/packages/lib/src/message-input/types.ts @@ -0,0 +1,200 @@ +import { CSSProperties, ReactNode, SVGProps } from "react"; + +type SVG = ReactNode & SVGProps<SVGSVGElement>; + +type Action = { + /** + * Unique identifier of the action. + */ + id: string; + /** + * This function will be called when the user clicks on the button. + */ + onClick: () => void; + /** + * Icon to be placed in the action. + */ + icon: string | SVG; + /** + * Text representing advisory information related + * to the button's action. Under the hood, this prop also serves + * as an accessible label for the component. + */ + title?: string; +}; + +export type Item = { + /** + * Unique identifier of the chip. + */ + id: string; + /** + * Text representing advisory information related + * to the button's action. Under the hood, this prop also serves + * as an accessible label for the component. + */ + title?: string; + /** + * The chip label. + */ + label: string; + /** + * Icon to be placed in the suffix of the Chip. + */ + suffixIcon?: string | SVG; + /** + * Icon to be placed in the prefix of the Chip. + */ + prefixIcon?: string | SVG; + // /** + // * This function will be called when the user clicks on the suffix. + // */ + // onClickSuffix?: () => void; + // /** + // * This function will be called when the user clicks on the prefix. + // */ + // onClickPrefix?: () => void; + /** + * If true, the chip will be disabled. + */ + disabled?: boolean; +}; + +type Props = { + /** + * Initial value of the input, only when it is uncontrolled. + */ + defaultValue?: string; + /** + * Value of the input. If undefined, the component will be uncontrolled and the value will be managed internally by the component. + */ + value?: string; + /** + * Helper text to be placed above the input. + */ + helperText?: string; + /** + * If true, the component will be disabled. + */ + disabled?: boolean; + /** + * If it is a defined value and also a truthy string, the component will + * change its appearance, showing the error below the input component. If + * the defined value is an empty string, it will reserve a space below + * the component for a future error, but it would not change its look. In + * case of being undefined or null, both the appearance and the space for + * the error message would not be modified. + */ + error?: string; + /** + * Text to be put as placeholder of the input. + */ + placeholder?: string; + /** + * Specifies the size of the component. The size will affect the width of the input. + */ + size?: "small" | "medium" | "large" | "fillParent"; + /** + * Items to be shown at the top. + */ + topItems?: Item[]; + /** + * This function will be called when the selection of top items changes. + */ + onTopItemsChange?: (items: Item[]) => void; + /** + * Actions to be shown at the bottom, next to the submit button. + */ + bottomActions?: Action[]; + /** + * Specifies the minimum length allowed by the input. + * This will be checked both when the input element loses the + * focus and while typing within it. If the string entered does not + * comply the minimum length, the onBlur and onChange functions will be called + * with the current value and an internal error informing that the value + * length does not comply the specified range. If a valid length is + * reached, the error parameter of both events will not be defined. + */ + minLength?: number; + /** + * Specifies the maximum length allowed by the input. + * This will be checked both when the input element loses the + * focus and while typing within it. If the string entered does not + * comply the maximum length, the onBlur and onChange functions will be called + * with the current value and an internal error informing that the value + * length does not comply the specified range. If a valid length is + * reached, the error parameter of both events will not be defined. + */ + maxLength?: number; + /** + * This function will be called when the user types within the input + * element of the component. An object including the current value and + * the error (if the value entered is not valid) will be passed to this + * function. If there is no error, error will not be defined. + */ + onChange?: (val: { value: string; error?: string }) => void; + /** + * This function will be called when the input element loses the focus. + * An object including the input value and the error (if the value + * entered is not valid) will be passed to this function. If there is no error, + * error will not be defined. + */ + onBlur?: (val: { value: string; error?: string }) => void; + /** + * This function will be called when the user clicks on the button or presses enter. + */ + onSubmit?: (signal?: AbortSignal) => void | Promise<void>; + /** + * If true, the file upload button will be shown. + */ + allowFileUploads?: boolean; + /** + * If true, it indicates that a request is being processed after the user submits a query. + */ + isLoading?: boolean; + /** + * This function will be called to stop the current action. + */ + stop?: () => void; +}; + +/** + * List of suggestions of a Text Input component. + */ +export type SuggestionsProps = { + id: string; + value: string; + suggestions: string[]; + visualFocusIndex: number; + highlightedSuggestions: boolean; + searchHasErrors: boolean; + isSearching: boolean; + suggestionOnClick: (suggestion: string) => void; + styles: CSSProperties; +}; + +/** + * Reference to the component. + */ +export type RefType = HTMLDivElement; + +/** + * Single suggestion of a Text Input component. + */ +export type SuggestionProps = { + id: string; + value: string; + onClick: (suggestion: string) => void; + suggestion: string; + isLast: boolean; + visuallyFocused: boolean; + highlighted: boolean; +}; + +export type AutosuggestWrapperProps = { + condition: boolean; + wrapper: (children: ReactNode) => JSX.Element; + children: ReactNode; +}; + +export default Props; diff --git a/packages/lib/src/message-input/utils.ts b/packages/lib/src/message-input/utils.ts new file mode 100644 index 000000000..672cbe34a --- /dev/null +++ b/packages/lib/src/message-input/utils.ts @@ -0,0 +1,95 @@ +import TextInputPropsType from "./types"; + +import { css } from "@emotion/react"; + +export const makeCancelable = (promise: Promise<string[]>) => { + let hasCanceled_ = false; + const wrappedPromise = new Promise<string[]>((resolve, reject) => { + promise.then( + (val) => { + if (hasCanceled_) { + reject(new Error("Is canceled")); + } else { + resolve(val); + } + }, + (promiseError) => { + if (hasCanceled_) { + reject(new Error("Is canceled")); + } else if (promiseError instanceof Error) { + reject(promiseError); + } else { + reject(new Error(String(promiseError))); + } + } + ); + }); + return { + promise: wrappedPromise, + cancel() { + hasCanceled_ = true; + }, + }; +}; + +export const isLengthIncorrect = ( + value: string, + minLength: TextInputPropsType["minLength"], + maxLength: TextInputPropsType["maxLength"] +) => + value != null && ((minLength != null && value.length < minLength) || (maxLength != null && value.length > maxLength)); + +export const isNumberIncorrect = ( + value: number, + minNumber: TextInputPropsType["minLength"], + maxNumber: TextInputPropsType["maxLength"] +) => (minNumber != null && value < minNumber) || (maxNumber != null && value > maxNumber); + +export const transformSpecialChars = (str: string) => { + const specialCharsRegex = /[\\*()[\]{}+?/]/; + let value = str; + if (specialCharsRegex.test(value)) { + const regexAsString = specialCharsRegex.toString().split(""); + const uniqueSpecialChars = regexAsString.filter((item, index) => regexAsString.indexOf(item) === index); + uniqueSpecialChars.forEach((specialChar) => { + if (str.includes(specialChar)) { + value = value.replace(specialChar, `\\${specialChar}`); + } + }); + } + return value; +}; + +export const inputStylesByStatePromptInput = (disabled: boolean, error: boolean, focused: boolean) => css` + background-color: ${disabled ? `var(--color-bg-neutral-lightest)` : `transparent`}; + + border-radius: var(--border-radius-l); + + border: ${!disabled && error ? "var(--border-width-m)" : "var(--border-width-s)"} var(--border-style-default) + ${(() => { + if (disabled) return "var(--border-color-neutral-lighter)"; + else if (error) return "var(--border-color-error-medium)"; + else return "var(--border-color-neutral-lighter)"; + })()}; + + ${!disabled + ? ` + &:hover { + border-color: ${ + error ? "var(--border-color-error-strong)" : !focused ? "var(--border-color-primary-strong)" : "transparent" + }; + } + + ${ + focused + ? ` + border-color: transparent; + outline-offset: -2px; + outline: var(--border-width-m) var(--border-style-default) + var(--border-color-secondary-medium); + ` + : "" + } + ` + : "cursor: not-allowed;"}; +`; From a6b1130db5dcb7ad0d455a677d00754a4f289d46 Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Mon, 13 Jul 2026 13:46:27 +0200 Subject: [PATCH 02/22] Add custom Select --- .../lib/src/message-input/MessageInput.tsx | 94 +++++------- .../lib/src/message-input/ModelSelect.tsx | 138 ++++++++++++++++++ packages/lib/src/message-input/types.ts | 70 +++------ packages/lib/src/message-input/utils.ts | 38 ++--- 4 files changed, 218 insertions(+), 122 deletions(-) create mode 100644 packages/lib/src/message-input/ModelSelect.tsx diff --git a/packages/lib/src/message-input/MessageInput.tsx b/packages/lib/src/message-input/MessageInput.tsx index 36a03ed1c..c1b189959 100644 --- a/packages/lib/src/message-input/MessageInput.tsx +++ b/packages/lib/src/message-input/MessageInput.tsx @@ -9,6 +9,7 @@ import DxcDropdown from "../dropdown/Dropdown"; import DxcFlex from "../flex/Flex"; import { HalstackLanguageContext } from "../HalstackContext"; import ErrorMessage from "../styles/forms/ErrorMessage"; +import ModelSelect from "./ModelSelect"; const sizes = { small: "240px", @@ -30,7 +31,7 @@ const MessageInputContainer = styled.div<{ size: PromptInputPropsType["size"] }> const MessageInput = styled.div<{ disabled: Required<PromptInputPropsType>["disabled"]; error: boolean; - focused: boolean; + focus: boolean; }>` position: relative; display: flex; @@ -39,7 +40,7 @@ const MessageInput = styled.div<{ gap: var(--spacing-gap-s); height: 100%; padding: var(--spacing-padding-m) var(--spacing-padding-xs); - ${({ disabled, error, focused }) => inputStylesByStatePromptInput(disabled, error, focused)} + ${({ disabled, error, focus }) => inputStylesByStatePromptInput(disabled, error, focus)} overflow: hidden; `; @@ -75,6 +76,7 @@ const HelperText = styled.span<{ disabled: boolean; hasMargin?: boolean }>` const DxcMessageInput = ({ allowFileUploads = false, + bottomOptions, defaultValue = "", disabled = false, error, @@ -89,8 +91,7 @@ const DxcMessageInput = ({ onSubmit, size = "medium", topItems, - onTopItemsChange, - bottomActions = [], + callbackItems, stop, }: PromptInputPropsType) => { const inputId = `input-${useId()}`; @@ -100,8 +101,8 @@ const DxcMessageInput = ({ const inputContainerRef = useRef<HTMLDivElement | null>(null); const [innerValue, setInnerValue] = useState(defaultValue); const [innerTopItems, setInnerTopItems] = useState<Item[]>([]); + const [isFocused, setIsFocused] = useState(false); const fileInputRef = useRef<HTMLInputElement | null>(null); - const [isInputFocused, setIsInputFocused] = useState(false); const changeValue = (newValue: string) => { if (value == null) { @@ -138,10 +139,11 @@ const DxcMessageInput = ({ }; const handleInputOnFocus = () => { - setIsInputFocused(true); + setIsFocused(true); }; + const handleInputOnBlur = (event: FocusEvent<HTMLTextAreaElement>) => { - setIsInputFocused(false); + setIsFocused(false); if (isLengthIncorrect(event.target.value, minLength, maxLength)) { onBlur?.({ value: event.target.value, @@ -168,7 +170,7 @@ const DxcMessageInput = ({ if (topItems == null) { setInnerTopItems(newItems); } - onTopItemsChange?.(newItems); + callbackItems?.(newItems); }; const removeItem = (label: string) => { @@ -177,7 +179,7 @@ const DxcMessageInput = ({ if (topItems == null) { setInnerTopItems(filteredItems); } - onTopItemsChange?.(filteredItems); + callbackItems?.(filteredItems); }; const handleFilesChosen = (e: React.ChangeEvent<HTMLInputElement>) => { @@ -233,42 +235,42 @@ const DxcMessageInput = ({ <MessageInput disabled={disabled} error={!!error} - focused={isInputFocused} + focus={isFocused} onClick={handleInputContainerOnClick} onMouseDown={handleInputContainerOnMouseDown} ref={inputContainerRef} > {allowFileUploads && ( - <DxcFlex alignItems="center" gap="var(--spacing-gap-xs)"> - <DxcDropdown - icon="add" - options={[{ label: "Attach documents", value: "fileorphoto" }]} - onSelectOption={handleFileSelect} - /> - <input ref={fileInputRef} type="file" hidden multiple onChange={handleFilesChosen} /> - <DxcContainer overflow={{ x: "auto" }} width="100%"> - <DxcFlex gap="var(--spacing-gap-xs)"> - {(topItems ?? innerTopItems)?.map((item) => ( - <DxcChip - label={item.label} - mode="dismissible" - prefix={item.prefixIcon} - onClick={() => removeItem(item.label)} - key={item.id} - /> - ))} - </DxcFlex> - </DxcContainer> - </DxcFlex> + <DxcContainer overflow={{ x: "auto" }} width="100%"> + <DxcFlex alignItems="center" gap="var(--spacing-gap-xs)"> + <DxcDropdown + options={[{ label: "Attach documents", value: "fileorphoto" }]} + onSelectOption={handleFileSelect} + icon="add" + disabled={disabled} + caretHidden + /> + <input ref={fileInputRef} type="file" hidden multiple onChange={handleFilesChosen} /> + {(topItems ?? innerTopItems)?.map((item) => ( + <DxcChip + label={item.label} + mode="dismissible" + prefix="file" + onClick={() => removeItem(item.label)} + key={item.id} + /> + ))} + </DxcFlex> + </DxcContainer> )} <Input aria-errormessage={error ? errorId : undefined} aria-invalid={!!error} - disabled={disabled} + disabled={isLoading === true || disabled} id={inputId} onBlur={handleInputOnBlur} - onFocus={handleInputOnFocus} onChange={handleInputOnChange} + onFocus={handleInputOnFocus} onKeyDown={handleInputOnKeyDown} onMouseDown={(event) => { event.stopPropagation(); @@ -279,34 +281,18 @@ const DxcMessageInput = ({ minLength={minLength} value={value ?? innerValue} /> - {/* TODO: Replace with space-between if there is dropdown */} - <DxcFlex justifyContent="flex-end" alignItems="center"> - {/* <DxcDropdown - label="Select a model" - options={[{ label: "AA 2.0", value: "aa2.0" }]} - onSelectOption={() => {}} - /> */} - {/* <DxcSelect options={[{ label: "AA 2.0", value: "aa2.0" }]} defaultValue="aa2.0" size="small" /> */} - {/* TODO: Add model selection dropdown */} + <DxcFlex justifyContent={bottomOptions ? "space-between" : "flex-end"} alignItems="center"> + {bottomOptions && <ModelSelect options={bottomOptions} disabled={disabled} />} + <DxcFlex gap="var(--spacing-gap-xs)"> - {bottomActions.map((action) => ( - <DxcButton - icon={action.icon} - size={{ height: "medium" }} - disabled={disabled} - onClick={action.onClick} - title={!disabled ? (action.title ?? undefined) : undefined} - mode="tertiary" - key={action.id} - /> - ))} + <DxcButton icon="mic" size={{ height: "medium" }} mode="tertiary" disabled={disabled} /> + <DxcButton icon={!isLoading ? "send" : "filled_stop"} size={{ height: "medium" }} disabled={disabled} onClick={!isLoading ? handleSubmit : stop} title={!isLoading ? "Submit" : "Stop"} - mode="primary" /> </DxcFlex> </DxcFlex> diff --git a/packages/lib/src/message-input/ModelSelect.tsx b/packages/lib/src/message-input/ModelSelect.tsx new file mode 100644 index 000000000..36eed94c9 --- /dev/null +++ b/packages/lib/src/message-input/ModelSelect.tsx @@ -0,0 +1,138 @@ +import * as Popover from "@radix-ui/react-popover"; +import DxcIcon from "../icon/Icon"; +import { BottomSelectProps } from "./types"; +import styled from "@emotion/styled"; +import { useEffect, useId, useRef, useState } from "react"; +import Listbox from "../select/Listbox"; +import { ListOptionType } from "../select/types"; + +const SelectContainer = styled.div<{ disabled: boolean }>` + position: relative; + display: inline-flex; + height: var(--height-m); + align-items: center; + gap: var(--spacing-gap-s); + cursor: ${({ disabled }) => (disabled ? "not-allowed" : "pointer")}; + opacity: ${({ disabled }) => (disabled ? 0.5 : 1)}; + padding: 0 var(--spacing-padding-xs); + border: var(--border-width-s) var(--border-style-default) transparent; + border-radius: var(--border-radius-s); + + &:focus { + outline: var(--border-width-m) var(--border-style-default) var(--border-color-secondary-medium); + outline-offset: -2px; + } +`; + +const Select = styled.div` + display: inline-flex; + align-items: center; + gap: var(--spacing-gap-s); +`; + +const SelectedValue = styled.div` + display: flex; + align-items: center; + gap: var(--spacing-gap-xs); + font-family: var(--typography-font-family); + font-size: var(--typography-label-m); + font-weight: var(--typography-label-regular); + color: var(--color-fg-neutral-dark); +`; + +const ModelSelect = ({ options, disabled = false }: BottomSelectProps) => { + const id = `select-${useId()}`; + const listboxId = `${id}-listbox`; + const labelId = `${id}-label`; + const [selectedValue, setSelectedValue] = useState<string>(options[0]?.value ?? ""); + const [isOpen, setIsOpen] = useState(false); + const containerRef = useRef<HTMLDivElement>(null); + const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null); + + useEffect(() => { + setPortalContainer(document?.getElementById(`${id}-portal`)); + }, [id]); + + const toggleDropdown = () => { + if (!disabled) { + setIsOpen(!isOpen); + } + }; + + const handleOptionClick = (option: ListOptionType) => { + const selectedOption = options.find((opt) => opt.value === option.value); + if (selectedOption) { + setSelectedValue(option.value); + setIsOpen(false); + selectedOption.onSelect(); + } + }; + + // Convert BottomSelectProps options to ListOptionType format + const listboxOptions: ListOptionType[] = options.map((opt) => ({ + label: opt.label ?? opt.value, + value: opt.value, + icon: typeof opt.icon === "string" ? opt.icon : undefined, + })); + + const selectedOption = options.find((opt) => opt.value === selectedValue) ?? options[0]; + + return ( + <> + <SelectContainer + disabled={disabled} + onClick={toggleDropdown} + ref={containerRef} + role="combobox" + aria-expanded={isOpen} + aria-haspopup="listbox" + aria-disabled={disabled} + tabIndex={disabled ? -1 : 0} + > + <Popover.Root open={isOpen} onOpenChange={setIsOpen}> + <Popover.Trigger asChild type={undefined}> + <Select> + <SelectedValue> + {typeof selectedOption?.icon === "string" ? ( + <DxcIcon icon={selectedOption.icon} /> + ) : ( + selectedOption?.icon + )} + {selectedOption?.label ?? selectedOption?.value} + </SelectedValue> + <DxcIcon icon={isOpen ? "keyboard_arrow_up" : "keyboard_arrow_down"} /> + </Select> + </Popover.Trigger> + {portalContainer && ( + <Popover.Portal container={portalContainer}> + <Popover.Content side="bottom" sideOffset={4} align="start"> + <Listbox + ariaLabelledBy={labelId} + currentValue={selectedValue} + enableSelectAll={false} + handleOptionOnClick={handleOptionClick} + handleGroupOnClick={() => {}} + handleSelectAllOnClick={() => {}} + id={listboxId} + lastOptionIndex={listboxOptions.length - 1} + multiple={false} + optional={false} + optionalItem={{ label: "", value: "" }} + options={listboxOptions} + searchable={false} + selectionType="unchecked" + styles={{ width: "240px" }} + visualFocusIndex={-1} + /> + </Popover.Content> + </Popover.Portal> + )} + </Popover.Root> + </SelectContainer> + + <div id={`${id}-portal`} style={{ position: "absolute" }} /> + </> + ); +}; + +export default ModelSelect; diff --git a/packages/lib/src/message-input/types.ts b/packages/lib/src/message-input/types.ts index 717851bf9..dfeb9b714 100644 --- a/packages/lib/src/message-input/types.ts +++ b/packages/lib/src/message-input/types.ts @@ -1,27 +1,4 @@ -import { CSSProperties, ReactNode, SVGProps } from "react"; - -type SVG = ReactNode & SVGProps<SVGSVGElement>; - -type Action = { - /** - * Unique identifier of the action. - */ - id: string; - /** - * This function will be called when the user clicks on the button. - */ - onClick: () => void; - /** - * Icon to be placed in the action. - */ - icon: string | SVG; - /** - * Text representing advisory information related - * to the button's action. Under the hood, this prop also serves - * as an accessible label for the component. - */ - title?: string; -}; +import { CSSProperties, ReactNode } from "react"; export type Item = { /** @@ -38,29 +15,18 @@ export type Item = { * The chip label. */ label: string; - /** - * Icon to be placed in the suffix of the Chip. - */ - suffixIcon?: string | SVG; - /** - * Icon to be placed in the prefix of the Chip. - */ - prefixIcon?: string | SVG; - // /** - // * This function will be called when the user clicks on the suffix. - // */ - // onClickSuffix?: () => void; - // /** - // * This function will be called when the user clicks on the prefix. - // */ - // onClickPrefix?: () => void; - /** - * If true, the chip will be disabled. - */ - disabled?: boolean; }; type Props = { + /** + * Options to be shown on the dropdown under the input. + */ + bottomOptions?: { + label?: string; + icon?: string | (React.ReactNode & React.SVGProps<SVGSVGElement>); + value: string; + onSelect: () => void; + }[]; /** * Initial value of the input, only when it is uncontrolled. */ @@ -101,11 +67,7 @@ type Props = { /** * This function will be called when the selection of top items changes. */ - onTopItemsChange?: (items: Item[]) => void; - /** - * Actions to be shown at the bottom, next to the submit button. - */ - bottomActions?: Action[]; + callbackItems?: (items: Item[]) => void; /** * Specifies the minimum length allowed by the input. * This will be checked both when the input element loses the @@ -197,4 +159,14 @@ export type AutosuggestWrapperProps = { children: ReactNode; }; +export type BottomSelectProps = { + options: { + label?: string; + icon?: string | (React.ReactNode & React.SVGProps<SVGSVGElement>); + value: string; + onSelect: () => void; + }[]; + disabled?: boolean; +}; + export default Props; diff --git a/packages/lib/src/message-input/utils.ts b/packages/lib/src/message-input/utils.ts index 672cbe34a..fbc23976a 100644 --- a/packages/lib/src/message-input/utils.ts +++ b/packages/lib/src/message-input/utils.ts @@ -1,5 +1,4 @@ -import TextInputPropsType from "./types"; - +import MessageInputPropsType from "./types"; import { css } from "@emotion/react"; export const makeCancelable = (promise: Promise<string[]>) => { @@ -34,15 +33,15 @@ export const makeCancelable = (promise: Promise<string[]>) => { export const isLengthIncorrect = ( value: string, - minLength: TextInputPropsType["minLength"], - maxLength: TextInputPropsType["maxLength"] + minLength: MessageInputPropsType["minLength"], + maxLength: MessageInputPropsType["maxLength"] ) => value != null && ((minLength != null && value.length < minLength) || (maxLength != null && value.length > maxLength)); export const isNumberIncorrect = ( value: number, - minNumber: TextInputPropsType["minLength"], - maxNumber: TextInputPropsType["maxLength"] + minNumber: MessageInputPropsType["minLength"], + maxNumber: MessageInputPropsType["maxLength"] ) => (minNumber != null && value < minNumber) || (maxNumber != null && value > maxNumber); export const transformSpecialChars = (str: string) => { @@ -60,7 +59,7 @@ export const transformSpecialChars = (str: string) => { return value; }; -export const inputStylesByStatePromptInput = (disabled: boolean, error: boolean, focused: boolean) => css` +export const inputStylesByStatePromptInput = (disabled: boolean, error: boolean, focus?: boolean) => css` background-color: ${disabled ? `var(--color-bg-neutral-lightest)` : `transparent`}; border-radius: var(--border-radius-l); @@ -75,21 +74,22 @@ export const inputStylesByStatePromptInput = (disabled: boolean, error: boolean, ${!disabled ? ` &:hover { - border-color: ${ - error ? "var(--border-color-error-strong)" : !focused ? "var(--border-color-primary-strong)" : "transparent" - }; + border-color: ${error ? "var(--border-color-error-strong)" : "var(--border-color-primary-strong)"}; } - ${ - focused - ? ` - border-color: transparent; - outline-offset: -2px; - outline: var(--border-width-m) var(--border-style-default) - var(--border-color-secondary-medium); - ` - : "" + &:focus { + outline-offset: -2px; + outline: var(--border-width-m) var(--border-style-default) var(--border-color-secondary-medium); + border-color: transparent; } ` : "cursor: not-allowed;"}; + + ${focus && !disabled + ? ` + outline-offset: -2px; + outline: var(--border-width-m) var(--border-style-default) var(--border-color-secondary-medium); + border-color: transparent; + ` + : ""}; `; From d094ca45dbde5b7e851121fb91e14fdd53cb93bd Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Mon, 13 Jul 2026 13:46:45 +0200 Subject: [PATCH 03/22] Add stories and tests --- .../message-input/MessageInput.stories.tsx | 61 +++- .../src/message-input/MessageInput.test.tsx | 335 ++++++++++++++++++ 2 files changed, 392 insertions(+), 4 deletions(-) create mode 100644 packages/lib/src/message-input/MessageInput.test.tsx diff --git a/packages/lib/src/message-input/MessageInput.stories.tsx b/packages/lib/src/message-input/MessageInput.stories.tsx index 13b653bf0..d564312d2 100644 --- a/packages/lib/src/message-input/MessageInput.stories.tsx +++ b/packages/lib/src/message-input/MessageInput.stories.tsx @@ -12,14 +12,67 @@ type Story = StoryObj<typeof DxcMessageInput>; const MessageInput = () => ( <> - <Title title="Default" level={4} /> + <Title title="States" theme="light" level={2} /> <ExampleContainer> - <DxcMessageInput placeholder="Ask me anything..." /> + <Title title="Default" level={4} /> + <DxcMessageInput placeholder="Ask me anything..." allowFileUploads /> </ExampleContainer> - <Title title="Files" level={4} /> - <ExampleContainer> + <ExampleContainer pseudoState="pseudo-hover"> + <Title title="Hover" level={4} /> + <DxcMessageInput placeholder="Ask me anything..." allowFileUploads /> + </ExampleContainer> + <ExampleContainer pseudoState="pseudo-focus"> + <Title title="Focused" level={4} /> + <DxcMessageInput placeholder="Ask me anything..." allowFileUploads /> + </ExampleContainer> + <ExampleContainer pseudoState={["pseudo-focus", "pseudo-hover"]}> + <Title title="Active" level={4} /> <DxcMessageInput placeholder="Ask me anything..." allowFileUploads /> </ExampleContainer> + <ExampleContainer> + <Title title="Disabled" level={4} /> + <DxcMessageInput placeholder="Ask me anything..." disabled allowFileUploads /> + </ExampleContainer> + <ExampleContainer> + <Title title="isLoading" level={4} /> + <DxcMessageInput placeholder="Ask me anything..." isLoading allowFileUploads /> + </ExampleContainer> + <ExampleContainer> + <Title title="Error" level={4} /> + <DxcMessageInput placeholder="Ask me anything..." allowFileUploads error="Error Message" /> + </ExampleContainer> + <ExampleContainer pseudoState="pseudo-hover"> + <Title title="Hover error" level={4} /> + <DxcMessageInput placeholder="Ask me anything..." allowFileUploads error="Error Message" /> + </ExampleContainer> + + <ExampleContainer> + <Title title="Min Length" level={4} /> + <DxcMessageInput placeholder="Ask me anything..." allowFileUploads minLength={15} defaultValue="Hello" /> + </ExampleContainer> + <ExampleContainer> + <Title title="Max Length" level={4} /> + <DxcMessageInput + placeholder="Ask me anything..." + allowFileUploads + maxLength={15} + defaultValue="Hello this is a test of the max length property" + /> + </ExampleContainer> + + <ExampleContainer> + <Title title="Bottom Select" level={4} /> + <DxcMessageInput + placeholder="Ask me anything..." + bottomOptions={[ + { label: "Option 1", value: "option1", onSelect: () => {} }, + { label: "Option 2", value: "option2", onSelect: () => {} }, + { label: "Option 3", value: "option3", onSelect: () => {} }, + { label: "Option 4", value: "option4", onSelect: () => {} }, + ]} + allowFileUploads + /> + </ExampleContainer> </> ); diff --git a/packages/lib/src/message-input/MessageInput.test.tsx b/packages/lib/src/message-input/MessageInput.test.tsx new file mode 100644 index 000000000..d7b6b02eb --- /dev/null +++ b/packages/lib/src/message-input/MessageInput.test.tsx @@ -0,0 +1,335 @@ +import "@testing-library/jest-dom"; +import { render, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import DxcMessageInput from "./MessageInput"; + +// Mock ResizeObserver +global.ResizeObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), +})); + +describe("Message Input component tests", () => { + test("Message Input renders correctly", () => { + const { getByPlaceholderText } = render(<DxcMessageInput placeholder="Ask me anything..." />); + const input = getByPlaceholderText("Ask me anything..."); + expect(input).toBeInTheDocument(); + }); + + test("renders with default value", () => { + const { getByDisplayValue } = render(<DxcMessageInput defaultValue="Hello World" />); + expect(getByDisplayValue("Hello World")).toBeInTheDocument(); + }); + + test("renders with helper text", () => { + const { getByText } = render(<DxcMessageInput helperText="This is a helper text" />); + expect(getByText("This is a helper text")).toBeInTheDocument(); + }); + + test("renders with error message", () => { + const { getByText } = render(<DxcMessageInput error="This is an error" />); + expect(getByText("This is an error")).toBeInTheDocument(); + }); + + test("calls onChange when user types", () => { + const onChange = jest.fn(); + const { getByRole } = render(<DxcMessageInput onChange={onChange} />); + const input = getByRole("textbox"); + + userEvent.type(input, "Hello"); + + expect(onChange).toHaveBeenCalledTimes(5); + expect(onChange).toHaveBeenLastCalledWith({ value: "Hello" }); + }); + + test("calls onBlur when input loses focus", () => { + const onBlur = jest.fn(); + const { getByRole } = render(<DxcMessageInput onBlur={onBlur} />); + const input = getByRole("textbox"); + + userEvent.click(input); + userEvent.type(input, "Test"); + userEvent.tab(); + + expect(onBlur).toHaveBeenCalledWith({ value: "Test" }); + }); + + test("calls onSubmit when Enter key is pressed", () => { + const onSubmit = jest.fn(); + const { getByRole } = render(<DxcMessageInput onSubmit={onSubmit} />); + const input = getByRole("textbox"); + + userEvent.click(input); + userEvent.type(input, "Test message{Enter}"); + + expect(onSubmit).toHaveBeenCalledTimes(1); + }); + + test("does not call onSubmit when Shift+Enter is pressed", () => { + const onSubmit = jest.fn(); + const { getByRole } = render(<DxcMessageInput onSubmit={onSubmit} />); + const input = getByRole("textbox"); + + userEvent.click(input); + userEvent.type(input, "Line 1{Shift>}{Enter}{/Shift}Line 2"); + + expect(onSubmit).not.toHaveBeenCalled(); + }); + + test("calls onSubmit when submit button is clicked", () => { + const onSubmit = jest.fn(); + const { getByLabelText } = render(<DxcMessageInput onSubmit={onSubmit} />); + const submitButton = getByLabelText("Submit"); + + userEvent.click(submitButton); + + expect(onSubmit).toHaveBeenCalledTimes(1); + }); + + test("disables input when disabled prop is true", () => { + const { getByRole } = render(<DxcMessageInput disabled />); + const input = getByRole("textbox"); + + expect(input).toBeDisabled(); + }); + + test("disables input when isLoading is true", () => { + const { getByRole } = render(<DxcMessageInput isLoading />); + const input = getByRole("textbox"); + + expect(input).toBeDisabled(); + }); + + test("shows stop button when isLoading is true", () => { + const stop = jest.fn(); + const { getByLabelText } = render(<DxcMessageInput isLoading stop={stop} />); + const stopButton = getByLabelText("Stop"); + + expect(stopButton).toBeInTheDocument(); + }); + + test("calls stop when stop button is clicked", () => { + const stop = jest.fn(); + const { getByLabelText } = render(<DxcMessageInput isLoading stop={stop} />); + const stopButton = getByLabelText("Stop"); + + userEvent.click(stopButton); + + expect(stop).toHaveBeenCalledTimes(1); + }); + + test("validates minLength and calls onChange with error", () => { + const onChange = jest.fn(); + const { getByRole } = render(<DxcMessageInput minLength={5} onChange={onChange} />); + const input = getByRole("textbox"); + + userEvent.type(input, "Hi"); + + // Verify error is present in the call + expect(onChange).toHaveBeenCalled(); + const calls = onChange.mock.calls; + const lastCall = calls[calls.length - 1] as [{ value: string; error?: string }]; + expect(lastCall[0].value).toBe("Hi"); + expect(lastCall[0].error).toBeDefined(); + }); + + test("validates maxLength attribute is set", () => { + const { getByRole } = render(<DxcMessageInput maxLength={10} />); + const input = getByRole("textbox"); + + expect(input).toHaveAttribute("maxLength", "10"); + }); + + test("validates minLength on blur", () => { + const onBlur = jest.fn(); + const { getByRole } = render(<DxcMessageInput minLength={5} onBlur={onBlur} />); + const input = getByRole("textbox"); + + userEvent.click(input); + userEvent.type(input, "Hi"); + userEvent.tab(); + + // Verify error is present in the call + expect(onBlur).toHaveBeenCalled(); + const calls = onBlur.mock.calls; + const lastCall = calls[calls.length - 1] as [{ value: string; error?: string }]; + expect(lastCall[0].value).toBe("Hi"); + expect(lastCall[0].error).toBeDefined(); + }); + + test("works as controlled component", () => { + const onChange = jest.fn(); + const { getByRole, rerender } = render(<DxcMessageInput value="Initial" onChange={onChange} />); + const input = getByRole("textbox"); + + expect(input).toHaveValue("Initial"); + + userEvent.type(input, " text"); + + rerender(<DxcMessageInput value="Initial text" onChange={onChange} />); + expect(input).toHaveValue("Initial text"); + }); + + test("renders file upload dropdown when allowFileUploads is true", () => { + const { getByRole } = render(<DxcMessageInput allowFileUploads />); + const dropdown = getByRole("button", { name: "Show options" }); + + expect(dropdown).toBeInTheDocument(); + }); + + test("renders bottom select when bottomOptions is provided", () => { + const bottomOptions = [ + { label: "Option 1", value: "option1", onSelect: jest.fn() }, + { label: "Option 2", value: "option2", onSelect: jest.fn() }, + ]; + const { getByRole } = render(<DxcMessageInput bottomOptions={bottomOptions} />); + const select = getByRole("combobox"); + + expect(select).toBeInTheDocument(); + }); + + test("calls onSelect when a bottom option is selected", async () => { + const onSelect = jest.fn(); + const bottomOptions = [ + { label: "Option 1", value: "option1", onSelect }, + { label: "Option 2", value: "option2", onSelect: jest.fn() }, + ]; + const { getByRole } = render(<DxcMessageInput bottomOptions={bottomOptions} />); + const select = getByRole("combobox"); + + userEvent.click(select); + const option1 = await waitFor(() => getByRole("option", { name: "Option 1" })); + userEvent.click(option1); + + expect(onSelect).toHaveBeenCalledTimes(1); + }); + + test("does not show error when disabled", () => { + const { queryByText } = render(<DxcMessageInput disabled error="This is an error" />); + expect(queryByText("This is an error")).not.toBeInTheDocument(); + }); + + test("async onSubmit is handled correctly", async () => { + const onSubmit = jest.fn().mockResolvedValue(undefined); + const { getByLabelText } = render(<DxcMessageInput onSubmit={onSubmit} />); + const submitButton = getByLabelText("Submit"); + + userEvent.click(submitButton); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledTimes(1); + }); + }); + + test("does not call onSubmit when disabled", () => { + const onSubmit = jest.fn(); + const { getByLabelText } = render(<DxcMessageInput disabled onSubmit={onSubmit} />); + const submitButton = getByLabelText("Submit"); + + userEvent.click(submitButton); + + expect(onSubmit).not.toHaveBeenCalled(); + }); + + test("does not call onSubmit when isLoading", () => { + const onSubmit = jest.fn(); + const { getByLabelText } = render(<DxcMessageInput isLoading onSubmit={onSubmit} />); + + // The button should be the stop button when isLoading + const button = getByLabelText("Stop"); + expect(button).toBeInTheDocument(); + expect(onSubmit).not.toHaveBeenCalled(); + }); + + test("handles file selection", () => { + const { getByRole } = render(<DxcMessageInput allowFileUploads />); + const dropdown = getByRole("button", { name: "Show options" }); + + // Simulate selecting the option + userEvent.click(dropdown); + + expect(dropdown).toBeInTheDocument(); + }); + + test("adds files when files are selected", () => { + const callbackItems = jest.fn(); + const { container } = render(<DxcMessageInput allowFileUploads callbackItems={callbackItems} />); + + const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement; + expect(fileInput).toBeInTheDocument(); + + const file = new File(["content"], "test.txt", { type: "text/plain" }); + Object.defineProperty(fileInput, "files", { + value: [file], + writable: false, + }); + + fileInput.dispatchEvent(new Event("change", { bubbles: true })); + + expect(callbackItems).toHaveBeenCalled(); + }); + + test("removes item when chip is clicked", () => { + const callbackItems = jest.fn(); + const topItems = [ + { id: "1", label: "File 1" }, + { id: "2", label: "File 2" }, + ]; + const { getByText } = render( + <DxcMessageInput allowFileUploads topItems={topItems} callbackItems={callbackItems} /> + ); + + // Verify chips are rendered + expect(getByText("File 1")).toBeInTheDocument(); + expect(getByText("File 2")).toBeInTheDocument(); + + // Note: Testing the actual click on chip requires finding the dismissible button + // which is handled internally by DxcChip component + expect(callbackItems).not.toHaveBeenCalled(); + }); + + test("handles AbortError in onSubmit without throwing", async () => { + const abortError = new DOMException("Aborted", "AbortError"); + const onSubmit = jest.fn().mockRejectedValue(abortError); + const { getByLabelText } = render(<DxcMessageInput onSubmit={onSubmit} />); + const submitButton = getByLabelText("Submit"); + + userEvent.click(submitButton); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalled(); + }); + + // AbortError should be caught and not throw + expect(onSubmit).toHaveBeenCalledTimes(1); + }); + + test("validates minLength correctly when value is exactly minLength", () => { + const onChange = jest.fn(); + const { getByRole } = render(<DxcMessageInput minLength={5} onChange={onChange} />); + const input = getByRole("textbox"); + + userEvent.type(input, "Hello"); + + // When exactly at minLength, should not have error + expect(onChange).toHaveBeenLastCalledWith({ value: "Hello" }); + }); + + test("works with uncontrolled topItems", () => { + const callbackItems = jest.fn(); + const { container } = render(<DxcMessageInput allowFileUploads callbackItems={callbackItems} />); + + const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement; + const file = new File(["content"], "test.txt", { type: "text/plain" }); + + Object.defineProperty(fileInput, "files", { + value: [file], + writable: false, + }); + + fileInput.dispatchEvent(new Event("change", { bubbles: true })); + + expect(callbackItems).toHaveBeenCalled(); + }); +}); From 7d5601735bb7b4180e4245629a50e97a8221b262 Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Tue, 14 Jul 2026 14:31:51 +0200 Subject: [PATCH 04/22] add voiceTranscription hook --- .../message-input/MessageInput.stories.tsx | 67 +++++++++ .../lib/src/message-input/MessageInput.tsx | 72 +++++++++- packages/lib/src/message-input/types.ts | 21 ++- .../message-input/useVoiceTranscription.tsx | 129 ++++++++++++++++++ 4 files changed, 284 insertions(+), 5 deletions(-) create mode 100644 packages/lib/src/message-input/useVoiceTranscription.tsx diff --git a/packages/lib/src/message-input/MessageInput.stories.tsx b/packages/lib/src/message-input/MessageInput.stories.tsx index d564312d2..85dbc480e 100644 --- a/packages/lib/src/message-input/MessageInput.stories.tsx +++ b/packages/lib/src/message-input/MessageInput.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; import DxcMessageInput from "./MessageInput"; import Title from "../../.storybook/components/Title"; import ExampleContainer from "../../.storybook/components/ExampleContainer"; @@ -73,9 +74,75 @@ const MessageInput = () => ( allowFileUploads /> </ExampleContainer> + + <ExampleContainer> + <Title title="With Voice Input" level={4} /> + <DxcMessageInput placeholder="Ask me anything with voice..." allowVoiceInput allowFileUploads /> + </ExampleContainer> + + <ExampleContainer> + <Title title="Without Voice Input" level={4} /> + <DxcMessageInput placeholder="No voice button here..." allowFileUploads /> + </ExampleContainer> </> ); +const ControlledVoiceExample = () => { + const [isRecording, setIsRecording] = useState(false); + const [transcript, setTranscript] = useState(""); + const [inputValue, setInputValue] = useState(""); + const [baseText, setBaseText] = useState(""); + + const handleTranscript = (newTranscript: string) => { + setTranscript(newTranscript); + // Actualizar el input en tiempo real: texto base + transcripción actual + const combined = baseText ? `${baseText} ${newTranscript}` : newTranscript; + setInputValue(combined); + }; + + const handleRecordingChange = (recording: boolean) => { + setIsRecording(recording); + if (recording) { + // Cuando inicia la grabación, guardar el texto base actual + setBaseText(inputValue); + setTranscript(""); + } else if (transcript) { + // Cuando termina la grabación, consolidar el texto + const combined = baseText ? `${baseText} ${transcript}` : transcript; + setInputValue(combined); + setBaseText(combined); + setTranscript(""); + } + }; + + const handleChange = (val: { value: string; error?: string }) => { + setInputValue(val.value); + setBaseText(val.value); + }; + + return ( + <> + <Title title="Controlled Voice Recording" theme="light" level={2} /> + <ExampleContainer> + <DxcMessageInput + placeholder="Controla la grabación desde el componente padre..." + allowFileUploads + allowVoiceInput + value={inputValue} + onChange={handleChange} + isRecording={isRecording} + onRecordingChange={handleRecordingChange} + onTranscript={handleTranscript} + /> + </ExampleContainer> + </> + ); +}; + export const Chromatic: Story = { render: MessageInput, }; + +export const ControlledVoice: Story = { + render: ControlledVoiceExample, +}; diff --git a/packages/lib/src/message-input/MessageInput.tsx b/packages/lib/src/message-input/MessageInput.tsx index c1b189959..43379298c 100644 --- a/packages/lib/src/message-input/MessageInput.tsx +++ b/packages/lib/src/message-input/MessageInput.tsx @@ -1,4 +1,14 @@ -import { ChangeEvent, FocusEvent, KeyboardEvent, MouseEvent, useContext, useId, useRef, useState } from "react"; +import { + ChangeEvent, + FocusEvent, + KeyboardEvent, + MouseEvent, + useContext, + useEffect, + useId, + useRef, + useState, +} from "react"; import styled from "@emotion/styled"; import PromptInputPropsType, { Item } from "./types"; import { inputStylesByStatePromptInput, isLengthIncorrect } from "./utils"; @@ -10,6 +20,7 @@ import DxcFlex from "../flex/Flex"; import { HalstackLanguageContext } from "../HalstackContext"; import ErrorMessage from "../styles/forms/ErrorMessage"; import ModelSelect from "./ModelSelect"; +import { useVoiceTranscription } from "./useVoiceTranscription"; const sizes = { small: "240px", @@ -76,6 +87,7 @@ const HelperText = styled.span<{ disabled: boolean; hasMargin?: boolean }>` const DxcMessageInput = ({ allowFileUploads = false, + allowVoiceInput = false, bottomOptions, defaultValue = "", disabled = false, @@ -92,7 +104,10 @@ const DxcMessageInput = ({ size = "medium", topItems, callbackItems, - stop, + onStop, + isRecording: isRecordingProp, + onRecordingChange, + onTranscript, }: PromptInputPropsType) => { const inputId = `input-${useId()}`; const errorId = `error-${inputId}`; @@ -103,6 +118,9 @@ const DxcMessageInput = ({ const [innerTopItems, setInnerTopItems] = useState<Item[]>([]); const [isFocused, setIsFocused] = useState(false); const fileInputRef = useRef<HTMLInputElement | null>(null); + const { transcript, isRecording, startRecording, stopRecording } = useVoiceTranscription(); + + const displayedRecording: boolean = typeof isRecordingProp === "boolean" ? isRecordingProp : isRecording; const changeValue = (newValue: string) => { if (value == null) { @@ -208,6 +226,42 @@ const DxcMessageInput = ({ e.target.value = ""; }; + const baseTextRef = useRef(""); + + const toggleVoiceRecognition = () => { + if (disabled) return; + + if (isRecording) { + stopRecording(); + } else { + baseTextRef.current = value ?? innerValue; + startRecording(); + } + }; + + useEffect(() => { + if (!transcript) return; + + if (onTranscript) { + onTranscript(transcript); + return; + } + + const combined = baseTextRef.current ? `${baseTextRef.current} ${transcript}` : transcript; + changeValue(combined); + + if (inputRef.current) { + inputRef.current.style.height = "auto"; + inputRef.current.style.height = `${inputRef.current.scrollHeight}px`; + } + }, [transcript, onTranscript]); + + useEffect(() => { + if (onRecordingChange) { + onRecordingChange(isRecording); + } + }, [isRecording, onRecordingChange]); + const handleSubmit = async () => { if (disabled || isLoading) return; @@ -285,13 +339,23 @@ const DxcMessageInput = ({ {bottomOptions && <ModelSelect options={bottomOptions} disabled={disabled} />} <DxcFlex gap="var(--spacing-gap-xs)"> - <DxcButton icon="mic" size={{ height: "medium" }} mode="tertiary" disabled={disabled} /> + {allowVoiceInput && ( + <DxcButton + icon={displayedRecording ? "filled_pause" : "mic"} + size={{ height: "medium" }} + mode="tertiary" + disabled={disabled} + onClick={toggleVoiceRecognition} + title={displayedRecording ? "Stop recording" : "Start voice input"} + aria-label={displayedRecording ? "Stop recording" : "Start voice input"} + /> + )} <DxcButton icon={!isLoading ? "send" : "filled_stop"} size={{ height: "medium" }} disabled={disabled} - onClick={!isLoading ? handleSubmit : stop} + onClick={!isLoading ? handleSubmit : onStop} title={!isLoading ? "Submit" : "Stop"} /> </DxcFlex> diff --git a/packages/lib/src/message-input/types.ts b/packages/lib/src/message-input/types.ts index dfeb9b714..91da7d62c 100644 --- a/packages/lib/src/message-input/types.ts +++ b/packages/lib/src/message-input/types.ts @@ -110,6 +110,10 @@ type Props = { * If true, the file upload button will be shown. */ allowFileUploads?: boolean; + /** + * If true, the voice recording button will be shown. + */ + allowVoiceInput?: boolean; /** * If true, it indicates that a request is being processed after the user submits a query. */ @@ -117,7 +121,22 @@ type Props = { /** * This function will be called to stop the current action. */ - stop?: () => void; + onStop?: () => void; + /** + * If true, the voice recording is active. Use this prop to control the recording state externally. + * If undefined, the component will manage the recording state internally. + */ + isRecording?: boolean; + /** + * This function will be called when the recording state changes (starts or stops). + * Only used when isRecording prop is controlled. + */ + onRecordingChange?: (isRecording: boolean) => void; + /** + * This function will be called with the transcript text as the user speaks. + * If provided, the transcript won't be automatically added to the input value. + */ + onTranscript?: (transcript: string) => void; }; /** diff --git a/packages/lib/src/message-input/useVoiceTranscription.tsx b/packages/lib/src/message-input/useVoiceTranscription.tsx new file mode 100644 index 000000000..bf8ca2d4b --- /dev/null +++ b/packages/lib/src/message-input/useVoiceTranscription.tsx @@ -0,0 +1,129 @@ +import { useCallback, useRef, useState } from "react"; + +interface SpeechRecognitionResult { + readonly transcript: string; + readonly confidence: number; +} + +interface SpeechRecognitionResultItem { + readonly [index: number]: SpeechRecognitionResult; + readonly length: number; +} + +interface SpeechRecognitionResultList { + readonly [index: number]: SpeechRecognitionResultItem; + readonly length: number; +} + +interface SpeechRecognitionEvent extends Event { + readonly results: SpeechRecognitionResultList; +} + +interface SpeechRecognitionErrorEvent extends Event { + readonly error: string; +} + +interface ISpeechRecognition extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + onresult: ((event: SpeechRecognitionEvent) => void) | null; + onerror: ((event: SpeechRecognitionErrorEvent) => void) | null; + onend: (() => void) | null; + start(): void; + stop(): void; +} + +interface ISpeechRecognitionConstructor { + new (): ISpeechRecognition; +} + +interface WindowWithSpeechRecognition extends Window { + SpeechRecognition?: ISpeechRecognitionConstructor; + webkitSpeechRecognition?: ISpeechRecognitionConstructor; +} + +interface UseVoiceTranscriptionOptions { + lang?: string; + continuous?: boolean; + interimResults?: boolean; +} + +interface UseVoiceTranscriptionReturn { + transcript: string; + isRecording: boolean; + isSupported: boolean; + startRecording: () => void; + stopRecording: () => void; + resetTranscript: () => void; +} + +export const useVoiceTranscription = ({ + lang = "es-ES", + continuous = true, + interimResults = true, +}: UseVoiceTranscriptionOptions = {}): UseVoiceTranscriptionReturn => { + const [transcript, setTranscript] = useState(""); + const [isRecording, setIsRecording] = useState(false); + const recognitionRef = useRef<ISpeechRecognition | null>(null); + + const windowWithSpeech = + typeof window !== "undefined" ? (window as unknown as WindowWithSpeechRecognition) : undefined; + + const isSupported = !!(windowWithSpeech?.SpeechRecognition ?? windowWithSpeech?.webkitSpeechRecognition); + + const startRecording = useCallback(() => { + if (!isSupported || recognitionRef.current || !windowWithSpeech) return; + + const SpeechRecognitionConstructor = windowWithSpeech.SpeechRecognition ?? windowWithSpeech.webkitSpeechRecognition; + + if (!SpeechRecognitionConstructor) return; + + const recognition = new SpeechRecognitionConstructor(); + + recognition.lang = lang; + recognition.continuous = continuous; + recognition.interimResults = interimResults; + + recognition.onresult = (event: SpeechRecognitionEvent) => { + const results: SpeechRecognitionResultItem[] = []; + for (let i = 0; i < event.results.length; i++) { + const result = event.results[i]; + if (result) { + results.push(result); + } + } + + const fullTranscript = results + .map((result) => result[0]?.transcript ?? "") + .join(" ") + .trim(); + setTranscript(fullTranscript); + }; + + recognition.onerror = (event: SpeechRecognitionErrorEvent) => { + console.error("Speech recognition error:", event.error); + setIsRecording(false); + recognitionRef.current = null; + }; + + recognition.onend = () => { + setIsRecording(false); + recognitionRef.current = null; + }; + + recognition.start(); + recognitionRef.current = recognition; + setIsRecording(true); + }, [isSupported, lang, continuous, interimResults, windowWithSpeech]); + + const stopRecording = useCallback(() => { + if (recognitionRef.current) { + recognitionRef.current.stop(); + } + }, []); + + const resetTranscript = useCallback(() => setTranscript(""), []); + + return { transcript, isRecording, isSupported, startRecording, stopRecording, resetTranscript }; +}; From 7644f63c15179187e4b3009bfa980eda0b417487 Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Wed, 15 Jul 2026 12:22:37 +0200 Subject: [PATCH 05/22] Changes based on the new API --- .../message-input/MessageInput.stories.tsx | 35 ++--- .../lib/src/message-input/MessageInput.tsx | 91 ++++++------ .../lib/src/message-input/ModelSelect.tsx | 138 ------------------ packages/lib/src/message-input/types.ts | 76 +--------- packages/lib/src/select/utils.ts | 2 +- 5 files changed, 67 insertions(+), 275 deletions(-) delete mode 100644 packages/lib/src/message-input/ModelSelect.tsx diff --git a/packages/lib/src/message-input/MessageInput.stories.tsx b/packages/lib/src/message-input/MessageInput.stories.tsx index 85dbc480e..ea080ec6d 100644 --- a/packages/lib/src/message-input/MessageInput.stories.tsx +++ b/packages/lib/src/message-input/MessageInput.stories.tsx @@ -16,46 +16,46 @@ const MessageInput = () => ( <Title title="States" theme="light" level={2} /> <ExampleContainer> <Title title="Default" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." allowFileUploads /> + <DxcMessageInput placeholder="Ask me anything..." files={[]} /> </ExampleContainer> <ExampleContainer pseudoState="pseudo-hover"> <Title title="Hover" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." allowFileUploads /> + <DxcMessageInput placeholder="Ask me anything..." files={[]} /> </ExampleContainer> <ExampleContainer pseudoState="pseudo-focus"> <Title title="Focused" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." allowFileUploads /> + <DxcMessageInput placeholder="Ask me anything..." files={[]} /> </ExampleContainer> <ExampleContainer pseudoState={["pseudo-focus", "pseudo-hover"]}> <Title title="Active" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." allowFileUploads /> + <DxcMessageInput placeholder="Ask me anything..." files={[]} /> </ExampleContainer> <ExampleContainer> <Title title="Disabled" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." disabled allowFileUploads /> + <DxcMessageInput placeholder="Ask me anything..." disabled files={[]} /> </ExampleContainer> <ExampleContainer> - <Title title="isLoading" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." isLoading allowFileUploads /> + <Title title="isGenerating" level={4} /> + <DxcMessageInput placeholder="Ask me anything..." isGenerating files={[]} /> </ExampleContainer> <ExampleContainer> <Title title="Error" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." allowFileUploads error="Error Message" /> + <DxcMessageInput placeholder="Ask me anything..." files={[]} error="Error Message" /> </ExampleContainer> <ExampleContainer pseudoState="pseudo-hover"> <Title title="Hover error" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." allowFileUploads error="Error Message" /> + <DxcMessageInput placeholder="Ask me anything..." files={[]} error="Error Message" /> </ExampleContainer> <ExampleContainer> <Title title="Min Length" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." allowFileUploads minLength={15} defaultValue="Hello" /> + <DxcMessageInput placeholder="Ask me anything..." files={[]} minLength={15} defaultValue="Hello" /> </ExampleContainer> <ExampleContainer> <Title title="Max Length" level={4} /> <DxcMessageInput placeholder="Ask me anything..." - allowFileUploads + files={[]} maxLength={15} defaultValue="Hello this is a test of the max length property" /> @@ -67,28 +67,27 @@ const MessageInput = () => ( placeholder="Ask me anything..." bottomOptions={[ { label: "Option 1", value: "option1", onSelect: () => {} }, - { label: "Option 2", value: "option2", onSelect: () => {} }, + { label: "Option 29872357857274590", value: "option2", onSelect: () => {} }, { label: "Option 3", value: "option3", onSelect: () => {} }, { label: "Option 4", value: "option4", onSelect: () => {} }, ]} - allowFileUploads + files={[]} /> </ExampleContainer> <ExampleContainer> <Title title="With Voice Input" level={4} /> - <DxcMessageInput placeholder="Ask me anything with voice..." allowVoiceInput allowFileUploads /> + <DxcMessageInput placeholder="Ask me anything with voice..." allowVoiceInput files={[]} /> </ExampleContainer> <ExampleContainer> <Title title="Without Voice Input" level={4} /> - <DxcMessageInput placeholder="No voice button here..." allowFileUploads /> + <DxcMessageInput placeholder="No voice button here..." files={[]} /> </ExampleContainer> </> ); const ControlledVoiceExample = () => { - const [isRecording, setIsRecording] = useState(false); const [transcript, setTranscript] = useState(""); const [inputValue, setInputValue] = useState(""); const [baseText, setBaseText] = useState(""); @@ -101,7 +100,6 @@ const ControlledVoiceExample = () => { }; const handleRecordingChange = (recording: boolean) => { - setIsRecording(recording); if (recording) { // Cuando inicia la grabación, guardar el texto base actual setBaseText(inputValue); @@ -126,11 +124,10 @@ const ControlledVoiceExample = () => { <ExampleContainer> <DxcMessageInput placeholder="Controla la grabación desde el componente padre..." - allowFileUploads + files={[]} allowVoiceInput value={inputValue} onChange={handleChange} - isRecording={isRecording} onRecordingChange={handleRecordingChange} onTranscript={handleTranscript} /> diff --git a/packages/lib/src/message-input/MessageInput.tsx b/packages/lib/src/message-input/MessageInput.tsx index 43379298c..b7acb3bde 100644 --- a/packages/lib/src/message-input/MessageInput.tsx +++ b/packages/lib/src/message-input/MessageInput.tsx @@ -10,7 +10,7 @@ import { useState, } from "react"; import styled from "@emotion/styled"; -import PromptInputPropsType, { Item } from "./types"; +import PromptInputPropsType, { FileData } from "./types"; import { inputStylesByStatePromptInput, isLengthIncorrect } from "./utils"; import DxcButton from "../button/Button"; import DxcChip from "../chip/Chip"; @@ -19,8 +19,8 @@ import DxcDropdown from "../dropdown/Dropdown"; import DxcFlex from "../flex/Flex"; import { HalstackLanguageContext } from "../HalstackContext"; import ErrorMessage from "../styles/forms/ErrorMessage"; -import ModelSelect from "./ModelSelect"; import { useVoiceTranscription } from "./useVoiceTranscription"; +import DxcSelect from "../select/Select"; const sizes = { small: "240px", @@ -77,23 +77,13 @@ const Input = styled.textarea` ${({ disabled }) => disabled && "cursor: not-allowed;"} `; -const HelperText = styled.span<{ disabled: boolean; hasMargin?: boolean }>` - color: ${({ disabled }) => (disabled ? "var(--color-fg-neutral-medium)" : "var(--color-fg-neutral-stronger)")}; - font-family: var(--typography-font-family); - font-size: var(--typography-helper-text-s); - font-weight: var(--typography-helper-text-regular); - ${({ hasMargin }) => hasMargin && "margin-bottom: var(--spacing-padding-xxs);"} -`; - const DxcMessageInput = ({ - allowFileUploads = false, allowVoiceInput = false, bottomOptions, defaultValue = "", disabled = false, error, - isLoading, - helperText, + isGenerating = false, maxLength, minLength, placeholder = "", @@ -102,10 +92,9 @@ const DxcMessageInput = ({ value, onSubmit, size = "medium", - topItems, - callbackItems, + files, + callbackFiles, onStop, - isRecording: isRecordingProp, onRecordingChange, onTranscript, }: PromptInputPropsType) => { @@ -115,13 +104,11 @@ const DxcMessageInput = ({ const inputRef = useRef<HTMLTextAreaElement | null>(null); const inputContainerRef = useRef<HTMLDivElement | null>(null); const [innerValue, setInnerValue] = useState(defaultValue); - const [innerTopItems, setInnerTopItems] = useState<Item[]>([]); + const [innerFiles, setInnerFiles] = useState<FileData[]>([]); const [isFocused, setIsFocused] = useState(false); const fileInputRef = useRef<HTMLInputElement | null>(null); const { transcript, isRecording, startRecording, stopRecording } = useVoiceTranscription(); - const displayedRecording: boolean = typeof isRecordingProp === "boolean" ? isRecordingProp : isRecording; - const changeValue = (newValue: string) => { if (value == null) { setInnerValue(newValue); @@ -182,22 +169,22 @@ const DxcMessageInput = ({ fileInputRef.current?.click(); }; - const addTopItems = (items: Item[]) => { - const currentItems = topItems ?? innerTopItems; + const addTopItems = (items: FileData[]) => { + const currentItems = files ?? innerFiles; const newItems = [...currentItems, ...items]; - if (topItems == null) { - setInnerTopItems(newItems); + if (files == null) { + setInnerFiles(newItems); } - callbackItems?.(newItems); + callbackFiles?.(newItems); }; const removeItem = (label: string) => { - const currentItems = topItems ?? innerTopItems; + const currentItems = files ?? innerFiles; const filteredItems = currentItems.filter((item) => item.label !== label); - if (topItems == null) { - setInnerTopItems(filteredItems); + if (files == null) { + setInnerFiles(filteredItems); } - callbackItems?.(filteredItems); + callbackFiles?.(filteredItems); }; const handleFilesChosen = (e: React.ChangeEvent<HTMLInputElement>) => { @@ -207,9 +194,8 @@ const DxcMessageInput = ({ addTopItems( Array.from(e.target.files).map((file) => ({ - id: crypto.randomUUID(), label: file.name, - prefixIcon: "description", + icon: "description", disabled: disabled, })) ); @@ -263,7 +249,7 @@ const DxcMessageInput = ({ }, [isRecording, onRecordingChange]); const handleSubmit = async () => { - if (disabled || isLoading) return; + if (disabled || isGenerating) return; const controller = new AbortController(); @@ -281,11 +267,6 @@ const DxcMessageInput = ({ return ( <MessageInputContainer size={size}> - {helperText && ( - <HelperText disabled={disabled} hasMargin> - {helperText} - </HelperText> - )} <MessageInput disabled={disabled} error={!!error} @@ -294,24 +275,24 @@ const DxcMessageInput = ({ onMouseDown={handleInputContainerOnMouseDown} ref={inputContainerRef} > - {allowFileUploads && ( + {files && ( <DxcContainer overflow={{ x: "auto" }} width="100%"> <DxcFlex alignItems="center" gap="var(--spacing-gap-xs)"> <DxcDropdown options={[{ label: "Attach documents", value: "fileorphoto" }]} onSelectOption={handleFileSelect} icon="add" - disabled={disabled} + disabled={isGenerating || disabled} caretHidden /> <input ref={fileInputRef} type="file" hidden multiple onChange={handleFilesChosen} /> - {(topItems ?? innerTopItems)?.map((item) => ( + {(files ?? innerFiles)?.map((item, index) => ( <DxcChip + key={index} label={item.label} mode="dismissible" prefix="file" onClick={() => removeItem(item.label)} - key={item.id} /> ))} </DxcFlex> @@ -320,7 +301,7 @@ const DxcMessageInput = ({ <Input aria-errormessage={error ? errorId : undefined} aria-invalid={!!error} - disabled={isLoading === true || disabled} + disabled={isGenerating || disabled} id={inputId} onBlur={handleInputOnBlur} onChange={handleInputOnChange} @@ -336,27 +317,39 @@ const DxcMessageInput = ({ value={value ?? innerValue} /> <DxcFlex justifyContent={bottomOptions ? "space-between" : "flex-end"} alignItems="center"> - {bottomOptions && <ModelSelect options={bottomOptions} disabled={disabled} />} + {bottomOptions && ( + <DxcSelect + options={bottomOptions.map((option) => ({ label: option.label ?? option.value, value: option.value }))} + disabled={isGenerating || disabled} + placeholder="Select" + defaultValue={bottomOptions[0]?.value ?? ""} + onChange={(val) => { + const selectedOption = bottomOptions.find((option) => option.value === val.value); + selectedOption?.onSelect(); + }} + size="small" + /> + )} <DxcFlex gap="var(--spacing-gap-xs)"> {allowVoiceInput && ( <DxcButton - icon={displayedRecording ? "filled_pause" : "mic"} + icon={isRecording ? "filled_pause" : "mic"} size={{ height: "medium" }} mode="tertiary" - disabled={disabled} + disabled={isGenerating || disabled} onClick={toggleVoiceRecognition} - title={displayedRecording ? "Stop recording" : "Start voice input"} - aria-label={displayedRecording ? "Stop recording" : "Start voice input"} + title={isRecording ? "Stop recording" : "Start voice input"} + aria-label={isRecording ? "Stop recording" : "Start voice input"} /> )} <DxcButton - icon={!isLoading ? "send" : "filled_stop"} + icon={!isGenerating ? "send" : "filled_stop"} size={{ height: "medium" }} disabled={disabled} - onClick={!isLoading ? handleSubmit : onStop} - title={!isLoading ? "Submit" : "Stop"} + onClick={!isGenerating ? handleSubmit : onStop} + title={!isGenerating ? "Submit" : "Stop"} /> </DxcFlex> </DxcFlex> diff --git a/packages/lib/src/message-input/ModelSelect.tsx b/packages/lib/src/message-input/ModelSelect.tsx deleted file mode 100644 index 36eed94c9..000000000 --- a/packages/lib/src/message-input/ModelSelect.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import * as Popover from "@radix-ui/react-popover"; -import DxcIcon from "../icon/Icon"; -import { BottomSelectProps } from "./types"; -import styled from "@emotion/styled"; -import { useEffect, useId, useRef, useState } from "react"; -import Listbox from "../select/Listbox"; -import { ListOptionType } from "../select/types"; - -const SelectContainer = styled.div<{ disabled: boolean }>` - position: relative; - display: inline-flex; - height: var(--height-m); - align-items: center; - gap: var(--spacing-gap-s); - cursor: ${({ disabled }) => (disabled ? "not-allowed" : "pointer")}; - opacity: ${({ disabled }) => (disabled ? 0.5 : 1)}; - padding: 0 var(--spacing-padding-xs); - border: var(--border-width-s) var(--border-style-default) transparent; - border-radius: var(--border-radius-s); - - &:focus { - outline: var(--border-width-m) var(--border-style-default) var(--border-color-secondary-medium); - outline-offset: -2px; - } -`; - -const Select = styled.div` - display: inline-flex; - align-items: center; - gap: var(--spacing-gap-s); -`; - -const SelectedValue = styled.div` - display: flex; - align-items: center; - gap: var(--spacing-gap-xs); - font-family: var(--typography-font-family); - font-size: var(--typography-label-m); - font-weight: var(--typography-label-regular); - color: var(--color-fg-neutral-dark); -`; - -const ModelSelect = ({ options, disabled = false }: BottomSelectProps) => { - const id = `select-${useId()}`; - const listboxId = `${id}-listbox`; - const labelId = `${id}-label`; - const [selectedValue, setSelectedValue] = useState<string>(options[0]?.value ?? ""); - const [isOpen, setIsOpen] = useState(false); - const containerRef = useRef<HTMLDivElement>(null); - const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null); - - useEffect(() => { - setPortalContainer(document?.getElementById(`${id}-portal`)); - }, [id]); - - const toggleDropdown = () => { - if (!disabled) { - setIsOpen(!isOpen); - } - }; - - const handleOptionClick = (option: ListOptionType) => { - const selectedOption = options.find((opt) => opt.value === option.value); - if (selectedOption) { - setSelectedValue(option.value); - setIsOpen(false); - selectedOption.onSelect(); - } - }; - - // Convert BottomSelectProps options to ListOptionType format - const listboxOptions: ListOptionType[] = options.map((opt) => ({ - label: opt.label ?? opt.value, - value: opt.value, - icon: typeof opt.icon === "string" ? opt.icon : undefined, - })); - - const selectedOption = options.find((opt) => opt.value === selectedValue) ?? options[0]; - - return ( - <> - <SelectContainer - disabled={disabled} - onClick={toggleDropdown} - ref={containerRef} - role="combobox" - aria-expanded={isOpen} - aria-haspopup="listbox" - aria-disabled={disabled} - tabIndex={disabled ? -1 : 0} - > - <Popover.Root open={isOpen} onOpenChange={setIsOpen}> - <Popover.Trigger asChild type={undefined}> - <Select> - <SelectedValue> - {typeof selectedOption?.icon === "string" ? ( - <DxcIcon icon={selectedOption.icon} /> - ) : ( - selectedOption?.icon - )} - {selectedOption?.label ?? selectedOption?.value} - </SelectedValue> - <DxcIcon icon={isOpen ? "keyboard_arrow_up" : "keyboard_arrow_down"} /> - </Select> - </Popover.Trigger> - {portalContainer && ( - <Popover.Portal container={portalContainer}> - <Popover.Content side="bottom" sideOffset={4} align="start"> - <Listbox - ariaLabelledBy={labelId} - currentValue={selectedValue} - enableSelectAll={false} - handleOptionOnClick={handleOptionClick} - handleGroupOnClick={() => {}} - handleSelectAllOnClick={() => {}} - id={listboxId} - lastOptionIndex={listboxOptions.length - 1} - multiple={false} - optional={false} - optionalItem={{ label: "", value: "" }} - options={listboxOptions} - searchable={false} - selectionType="unchecked" - styles={{ width: "240px" }} - visualFocusIndex={-1} - /> - </Popover.Content> - </Popover.Portal> - )} - </Popover.Root> - </SelectContainer> - - <div id={`${id}-portal`} style={{ position: "absolute" }} /> - </> - ); -}; - -export default ModelSelect; diff --git a/packages/lib/src/message-input/types.ts b/packages/lib/src/message-input/types.ts index 91da7d62c..bed927785 100644 --- a/packages/lib/src/message-input/types.ts +++ b/packages/lib/src/message-input/types.ts @@ -1,20 +1,12 @@ -import { CSSProperties, ReactNode } from "react"; - -export type Item = { - /** - * Unique identifier of the chip. - */ - id: string; - /** - * Text representing advisory information related - * to the button's action. Under the hood, this prop also serves - * as an accessible label for the component. - */ - title?: string; +export type FileData = { /** * The chip label. */ label: string; + /* * + * The chip icon. It can be a string representing the icon name or a ReactNode representing a custom icon. + */ + icon: string | (React.ReactNode & React.SVGProps<SVGSVGElement>); }; type Props = { @@ -35,10 +27,6 @@ type Props = { * Value of the input. If undefined, the component will be uncontrolled and the value will be managed internally by the component. */ value?: string; - /** - * Helper text to be placed above the input. - */ - helperText?: string; /** * If true, the component will be disabled. */ @@ -63,11 +51,11 @@ type Props = { /** * Items to be shown at the top. */ - topItems?: Item[]; + files?: FileData[] | []; /** * This function will be called when the selection of top items changes. */ - callbackItems?: (items: Item[]) => void; + callbackFiles?: (files: FileData[]) => void; /** * Specifies the minimum length allowed by the input. * This will be checked both when the input element loses the @@ -106,10 +94,6 @@ type Props = { * This function will be called when the user clicks on the button or presses enter. */ onSubmit?: (signal?: AbortSignal) => void | Promise<void>; - /** - * If true, the file upload button will be shown. - */ - allowFileUploads?: boolean; /** * If true, the voice recording button will be shown. */ @@ -117,16 +101,11 @@ type Props = { /** * If true, it indicates that a request is being processed after the user submits a query. */ - isLoading?: boolean; + isGenerating?: boolean; /** * This function will be called to stop the current action. */ onStop?: () => void; - /** - * If true, the voice recording is active. Use this prop to control the recording state externally. - * If undefined, the component will manage the recording state internally. - */ - isRecording?: boolean; /** * This function will be called when the recording state changes (starts or stops). * Only used when isRecording prop is controlled. @@ -139,45 +118,6 @@ type Props = { onTranscript?: (transcript: string) => void; }; -/** - * List of suggestions of a Text Input component. - */ -export type SuggestionsProps = { - id: string; - value: string; - suggestions: string[]; - visualFocusIndex: number; - highlightedSuggestions: boolean; - searchHasErrors: boolean; - isSearching: boolean; - suggestionOnClick: (suggestion: string) => void; - styles: CSSProperties; -}; - -/** - * Reference to the component. - */ -export type RefType = HTMLDivElement; - -/** - * Single suggestion of a Text Input component. - */ -export type SuggestionProps = { - id: string; - value: string; - onClick: (suggestion: string) => void; - suggestion: string; - isLast: boolean; - visuallyFocused: boolean; - highlighted: boolean; -}; - -export type AutosuggestWrapperProps = { - condition: boolean; - wrapper: (children: ReactNode) => JSX.Element; - children: ReactNode; -}; - export type BottomSelectProps = { options: { label?: string; diff --git a/packages/lib/src/select/utils.ts b/packages/lib/src/select/utils.ts index 215c1b0d1..e45b91283 100644 --- a/packages/lib/src/select/utils.ts +++ b/packages/lib/src/select/utils.ts @@ -6,7 +6,7 @@ const sizes = { small: "240px", medium: "360px", large: "480px", - fillParent: "100%", + fillParent: "fit-content", }; export const calculateWidth = (margin: SelectPropsType["margin"], size: SelectPropsType["size"]) => From 529bdb984d61e52dde75092138ed0dfefbc7d953 Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Wed, 15 Jul 2026 14:11:46 +0200 Subject: [PATCH 06/22] Add chip icon to types --- .../message-input/MessageInput.stories.tsx | 29 +++++++++ .../lib/src/message-input/MessageInput.tsx | 64 +++++-------------- 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/packages/lib/src/message-input/MessageInput.stories.tsx b/packages/lib/src/message-input/MessageInput.stories.tsx index ea080ec6d..010e50104 100644 --- a/packages/lib/src/message-input/MessageInput.stories.tsx +++ b/packages/lib/src/message-input/MessageInput.stories.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import DxcMessageInput from "./MessageInput"; import Title from "../../.storybook/components/Title"; import ExampleContainer from "../../.storybook/components/ExampleContainer"; +import type { FileData } from "./types"; export default { title: "Message Input", @@ -87,6 +88,30 @@ const MessageInput = () => ( </> ); +const ControlledFileExample = () => { + const [files, setFiles] = useState<FileData[]>([]); + + const handleSubmit = (_signal?: AbortSignal) => { + console.log(files.map((file: FileData) => file.label)); + }; + + const callbackFiles = (updatedFiles: FileData[]) => { + setFiles(updatedFiles); + }; + + return ( + <ExampleContainer> + <Title title="Default" level={4} /> + <DxcMessageInput + placeholder="Ask me anything..." + files={files} + callbackFiles={callbackFiles} + onSubmit={handleSubmit} + /> + </ExampleContainer> + ); +}; + const ControlledVoiceExample = () => { const [transcript, setTranscript] = useState(""); const [inputValue, setInputValue] = useState(""); @@ -140,6 +165,10 @@ export const Chromatic: Story = { render: MessageInput, }; +export const ControlledFile: Story = { + render: ControlledFileExample, +}; + export const ControlledVoice: Story = { render: ControlledVoiceExample, }; diff --git a/packages/lib/src/message-input/MessageInput.tsx b/packages/lib/src/message-input/MessageInput.tsx index b7acb3bde..e9afd755e 100644 --- a/packages/lib/src/message-input/MessageInput.tsx +++ b/packages/lib/src/message-input/MessageInput.tsx @@ -10,7 +10,7 @@ import { useState, } from "react"; import styled from "@emotion/styled"; -import PromptInputPropsType, { FileData } from "./types"; +import PromptInputPropsType from "./types"; import { inputStylesByStatePromptInput, isLengthIncorrect } from "./utils"; import DxcButton from "../button/Button"; import DxcChip from "../chip/Chip"; @@ -104,7 +104,6 @@ const DxcMessageInput = ({ const inputRef = useRef<HTMLTextAreaElement | null>(null); const inputContainerRef = useRef<HTMLDivElement | null>(null); const [innerValue, setInnerValue] = useState(defaultValue); - const [innerFiles, setInnerFiles] = useState<FileData[]>([]); const [isFocused, setIsFocused] = useState(false); const fileInputRef = useRef<HTMLInputElement | null>(null); const { transcript, isRecording, startRecording, stopRecording } = useVoiceTranscription(); @@ -169,47 +168,19 @@ const DxcMessageInput = ({ fileInputRef.current?.click(); }; - const addTopItems = (items: FileData[]) => { - const currentItems = files ?? innerFiles; - const newItems = [...currentItems, ...items]; - if (files == null) { - setInnerFiles(newItems); - } - callbackFiles?.(newItems); + const handleFileInputOnChange = (event: ChangeEvent<HTMLInputElement>) => { + const selectedFiles = Array.from(event.target.files ?? []).map((file) => ({ + label: file.name, + icon: "insert_drive_file", + })); + const nextFiles = [...(files ?? []), ...selectedFiles]; + callbackFiles?.(nextFiles); + event.target.value = ""; }; - const removeItem = (label: string) => { - const currentItems = files ?? innerFiles; - const filteredItems = currentItems.filter((item) => item.label !== label); - if (files == null) { - setInnerFiles(filteredItems); - } - callbackFiles?.(filteredItems); - }; - - const handleFilesChosen = (e: React.ChangeEvent<HTMLInputElement>) => { - if (!e.target.files) return; - const formData = new FormData(); - Array.from(e.target.files).forEach((file) => formData.append("files", file)); - - addTopItems( - Array.from(e.target.files).map((file) => ({ - label: file.name, - icon: "description", - disabled: disabled, - })) - ); - - // TBD - - // const res = await fetch("/api/context-files", { - // method: "POST", - // body: formData, - // }); - - // const uploadedFiles = await res.json(); - - e.target.value = ""; + const removeItem = (itemIndex: number) => { + const nextFiles = (files ?? []).filter((_, index) => index !== itemIndex); + callbackFiles?.(nextFiles); }; const baseTextRef = useRef(""); @@ -285,14 +256,14 @@ const DxcMessageInput = ({ disabled={isGenerating || disabled} caretHidden /> - <input ref={fileInputRef} type="file" hidden multiple onChange={handleFilesChosen} /> - {(files ?? innerFiles)?.map((item, index) => ( + <input ref={fileInputRef} type="file" hidden multiple onChange={handleFileInputOnChange} /> + {(files ?? []).map((item, index) => ( <DxcChip key={index} label={item.label} mode="dismissible" - prefix="file" - onClick={() => removeItem(item.label)} + prefix={item.icon ?? "description"} + onClick={() => removeItem(index)} /> ))} </DxcFlex> @@ -319,15 +290,14 @@ const DxcMessageInput = ({ <DxcFlex justifyContent={bottomOptions ? "space-between" : "flex-end"} alignItems="center"> {bottomOptions && ( <DxcSelect + // size="fitContent" options={bottomOptions.map((option) => ({ label: option.label ?? option.value, value: option.value }))} disabled={isGenerating || disabled} - placeholder="Select" defaultValue={bottomOptions[0]?.value ?? ""} onChange={(val) => { const selectedOption = bottomOptions.find((option) => option.value === val.value); selectedOption?.onSelect(); }} - size="small" /> )} From baa29c455e691e058a30e3ce4c1de74a1c7208da Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Thu, 16 Jul 2026 14:11:51 +0200 Subject: [PATCH 07/22] Fix API based on team decissions and document it --- .../pages/components/message-input/code.tsx | 17 ++ .../pages/components/message-input/index.tsx | 15 + .../screens/common/componentsList.json | 8 +- .../message-input/MessageInputPageLayout.tsx | 31 +++ .../code/MessageInputCodePage.tsx | 260 ++++++++++++++++++ .../overview/MessageInputOverviewPage.tsx | 16 ++ .../message-input/MessageInput.stories.tsx | 41 +-- .../lib/src/message-input/MessageInput.tsx | 48 ++-- packages/lib/src/message-input/types.ts | 99 +++---- 9 files changed, 423 insertions(+), 112 deletions(-) create mode 100644 apps/website/pages/components/message-input/code.tsx create mode 100644 apps/website/pages/components/message-input/index.tsx create mode 100644 apps/website/screens/components/message-input/MessageInputPageLayout.tsx create mode 100644 apps/website/screens/components/message-input/code/MessageInputCodePage.tsx create mode 100644 apps/website/screens/components/message-input/overview/MessageInputOverviewPage.tsx diff --git a/apps/website/pages/components/message-input/code.tsx b/apps/website/pages/components/message-input/code.tsx new file mode 100644 index 000000000..c5934bfb7 --- /dev/null +++ b/apps/website/pages/components/message-input/code.tsx @@ -0,0 +1,17 @@ +import Head from "next/head"; +import type { ReactElement } from "react"; +import MessageInputCodePage from "screens/components/message-input/code/MessageInputCodePage"; +import MessageInputPageLayout from "screens/components/message-input/MessageInputPageLayout"; + +const Code = () => ( + <> + <Head> + <title>Message Input code — Halstack Design System + + + +); + +Code.getLayout = (page: ReactElement) => {page}; + +export default Code; diff --git a/apps/website/pages/components/message-input/index.tsx b/apps/website/pages/components/message-input/index.tsx new file mode 100644 index 000000000..1c370a31e --- /dev/null +++ b/apps/website/pages/components/message-input/index.tsx @@ -0,0 +1,15 @@ +import Head from "next/head"; +import type { ReactElement } from "react"; +import MessageInputPageLayout from "screens/components/message-input/MessageInputPageLayout"; + +const Index = () => ( + <> + + Message Input — Halstack Design System + + +); + +Index.getLayout = (page: ReactElement) => {page}; + +export default Index; diff --git a/apps/website/screens/common/componentsList.json b/apps/website/screens/common/componentsList.json index 5969856e1..818d91ffb 100644 --- a/apps/website/screens/common/componentsList.json +++ b/apps/website/screens/common/componentsList.json @@ -141,6 +141,12 @@ "status": "stable", "icon": "filled_file_upload" }, + { + "label": "Message input", + "path": "/components/message-input", + "status": "experimental", + "icon": "chat_bubble" + }, { "label": "Number input", "path": "/components/number-input", @@ -271,7 +277,7 @@ "label": "Popover", "path": "/components/popover", "status": "experimental", - "icon": "chat_bubble" + "icon": "chat_bubble" } ] }, diff --git a/apps/website/screens/components/message-input/MessageInputPageLayout.tsx b/apps/website/screens/components/message-input/MessageInputPageLayout.tsx new file mode 100644 index 000000000..caf7c67e0 --- /dev/null +++ b/apps/website/screens/components/message-input/MessageInputPageLayout.tsx @@ -0,0 +1,31 @@ +import { DxcParagraph, DxcFlex } from "@dxc-technology/halstack-react"; +import PageHeading from "@/common/PageHeading"; +import TabsPageHeading from "@/common/TabsPageLayout"; +import ComponentHeading from "@/common/ComponentHeading"; +import { ReactNode } from "react"; + +const MessageInputPageHeading = ({ children }: { children: ReactNode }) => { + const tabs = [ + { label: "Overview", path: "/components/message-input" }, + { label: "Code", path: "/components/message-input/code" }, + ]; + + return ( + + + + + + Message inputs allow users to enter and send messages within an application. They typically include a text + area for composing messages and may offer additional features such as attachments, emojis, or formatting + options. + + + + + {children} + + ); +}; + +export default MessageInputPageHeading; diff --git a/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx b/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx new file mode 100644 index 000000000..1f26ed7d4 --- /dev/null +++ b/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx @@ -0,0 +1,260 @@ +import { DxcTable, DxcFlex } from "@dxc-technology/halstack-react"; +import DocFooter from "@/common/DocFooter"; +import QuickNavContainer from "@/common/QuickNavContainer"; +import Code, { TableCode } from "@/common/Code"; + +const sections = [ + { + title: "Props", + content: ( + + + + Name + Type + Description + Default + + + + + allowVoiceInput + + boolean + + If true, the voice recording button will be shown. + + false + + + + callbackFile + + {"(files: FileData[]) => void"} + + This function will be called when the selection of top items changes. + - + + + defaultValue + + string + + Initial value of the input, only when it is uncontrolled. + - + + + disabled + + boolean + + If true, the component will be disabled. + + false + + + + error + + string + + + If it is a defined value and also a truthy string, the component will change its appearance, showing the + error below the input component. If the defined value is an empty string, it will reserve a space below + the component for a future error, but it would not change its look. In case of being undefined or null, + both the appearance and the space for the error message would not be modified. + + - + + + files + + {"FileData[] | []"} + + Items to be shown at the top. + - + + + isGenerating + + boolean + + If true, it indicates that a request is being processed after the user submits a query. + + false + + + + maxLength + + number + + + Specifies the maximum length allowed by the input. This will be checked both when the input element loses + the focus and while typing within it. If the string entered does not comply the maximum length, the onBlur + and onChange functions will be called with the current value and an internal error informing that the + value length does not comply the specified range. If a valid length is reached, the error parameter of + both events will not be defined. + + - + + + minLength + + number + + + Specifies the minimum length allowed by the input. This will be checked both when the input element loses + the focus and while typing within it. If the string entered does not comply the minimum length, the onBlur + and onChange functions will be called with the current value and an internal error informing that the + value length does not comply the specified range. If a valid length is reached, the error parameter of + both events will not be defined. + + - + + + modelList + + + { + "{ label?: string; icon?: string | (React.ReactNode & React.SVGProps); value: string; onSelect: () => void; }[]" + } + + + Options to be shown on the dropdown under the input. + - + + + onBlur + + {"(val: { value: string; error?: string }) => void"} + + + This function will be called when the input element loses the focus. An object including the input value + and the error (if the value entered is not valid) will be passed to this function. If there is no error, + error will not be defined. + + - + + + onButtonClick + + {"(type: 'submit' | 'stop', signal?: AbortSignal) => void | Promise"} + + + This function will be called when the user clicks on the button (submit or stop) or presses enter. The + type parameter indicates whether it's a "submit" or "stop" event. For submit events, an AbortSignal is + provided to allow cancellation. + + - + + + onChange + + {"(val: { value: string; error?: string }) => void"} + + + This function will be called when the user types within the input element of the component. An object + including the current value and the error (if the value entered is not valid) will be passed to this + function. If there is no error, error will not be defined. + + - + + + onRecordingChange + + {"(isRecording: boolean) => void"} + + + This function will be called when the recording state changes (starts or stops). Only used when + isRecording prop is controlled. + + - + + + placeholder + + string + + Text to be put as placeholder of the input. + - + + + size + + {"'small' | 'medium' | 'large' | 'fillParent'"} + + Specifies the size of the component. The size will affect the width of the input. + + 'medium' + + + + tabIndex + + number + + + Value of the tabindex attribute. + + - + + + value + + string + + + Value of the input. If undefined, the component will be uncontrolled and the value will be managed + internally by the component. + + - + + + + ), + }, + { + title: "FileData", + content: ( + + + + Name + Type + Description + + + + + label + + string + + The chip label. + + + icon + + {"string | (React.ReactNode & React.SVGProps)"} + + + The chip icon. It can be a string representing the icon name or a ReactNode representing a custom icon. + + + + + ), + }, +]; + +const MessageInputCodePage = () => { + return ( + + + + + ); +}; + +export default MessageInputCodePage; diff --git a/apps/website/screens/components/message-input/overview/MessageInputOverviewPage.tsx b/apps/website/screens/components/message-input/overview/MessageInputOverviewPage.tsx new file mode 100644 index 000000000..acc497639 --- /dev/null +++ b/apps/website/screens/components/message-input/overview/MessageInputOverviewPage.tsx @@ -0,0 +1,16 @@ +//import QuickNavContainer from "@/common/QuickNavContainer"; +import DxcFlex from "../../../../../../packages/lib/src/flex/Flex"; +import DocFooter from "@/common/DocFooter"; + +// const sections = []; + +const MessageInputOverviewPage = () => { + return ( + + {/* */} + + + ); +}; + +export default MessageInputOverviewPage; diff --git a/packages/lib/src/message-input/MessageInput.stories.tsx b/packages/lib/src/message-input/MessageInput.stories.tsx index 010e50104..a5c5b14f0 100644 --- a/packages/lib/src/message-input/MessageInput.stories.tsx +++ b/packages/lib/src/message-input/MessageInput.stories.tsx @@ -66,7 +66,7 @@ const MessageInput = () => ( <DxcMessageInput placeholder="Ask me anything..." - bottomOptions={[ + modelList={[ { label: "Option 1", value: "option1", onSelect: () => {} }, { label: "Option 29872357857274590", value: "option2", onSelect: () => {} }, { label: "Option 3", value: "option3", onSelect: () => {} }, @@ -91,8 +91,12 @@ const MessageInput = () => ( const ControlledFileExample = () => { const [files, setFiles] = useState<FileData[]>([]); - const handleSubmit = (_signal?: AbortSignal) => { - console.log(files.map((file: FileData) => file.label)); + const handleButtonClick = (type: "submit" | "stop", _signal?: AbortSignal) => { + if (type === "submit") { + console.log(files.map((file: FileData) => file.label)); + } else { + console.log("Stop action triggered"); + } }; const callbackFiles = (updatedFiles: FileData[]) => { @@ -105,42 +109,19 @@ const ControlledFileExample = () => { <DxcMessageInput placeholder="Ask me anything..." files={files} - callbackFiles={callbackFiles} - onSubmit={handleSubmit} + callbackFile={callbackFiles} + onButtonClick={handleButtonClick} /> </ExampleContainer> ); }; const ControlledVoiceExample = () => { - const [transcript, setTranscript] = useState(""); const [inputValue, setInputValue] = useState(""); - const [baseText, setBaseText] = useState(""); - - const handleTranscript = (newTranscript: string) => { - setTranscript(newTranscript); - // Actualizar el input en tiempo real: texto base + transcripción actual - const combined = baseText ? `${baseText} ${newTranscript}` : newTranscript; - setInputValue(combined); - }; - - const handleRecordingChange = (recording: boolean) => { - if (recording) { - // Cuando inicia la grabación, guardar el texto base actual - setBaseText(inputValue); - setTranscript(""); - } else if (transcript) { - // Cuando termina la grabación, consolidar el texto - const combined = baseText ? `${baseText} ${transcript}` : transcript; - setInputValue(combined); - setBaseText(combined); - setTranscript(""); - } - }; const handleChange = (val: { value: string; error?: string }) => { setInputValue(val.value); - setBaseText(val.value); + console.log(val); }; return ( @@ -153,8 +134,6 @@ const ControlledVoiceExample = () => { allowVoiceInput value={inputValue} onChange={handleChange} - onRecordingChange={handleRecordingChange} - onTranscript={handleTranscript} /> </ExampleContainer> </> diff --git a/packages/lib/src/message-input/MessageInput.tsx b/packages/lib/src/message-input/MessageInput.tsx index e9afd755e..45cae91ea 100644 --- a/packages/lib/src/message-input/MessageInput.tsx +++ b/packages/lib/src/message-input/MessageInput.tsx @@ -79,24 +79,23 @@ const Input = styled.textarea` const DxcMessageInput = ({ allowVoiceInput = false, - bottomOptions, + callbackFile, defaultValue = "", disabled = false, error, + files, isGenerating = false, maxLength, minLength, - placeholder = "", + modelList, onBlur, + onButtonClick, onChange, - value, - onSubmit, - size = "medium", - files, - callbackFiles, - onStop, onRecordingChange, - onTranscript, + placeholder = "", + size = "medium", + tabIndex, + value, }: PromptInputPropsType) => { const inputId = `input-${useId()}`; const errorId = `error-${inputId}`; @@ -174,13 +173,13 @@ const DxcMessageInput = ({ icon: "insert_drive_file", })); const nextFiles = [...(files ?? []), ...selectedFiles]; - callbackFiles?.(nextFiles); + callbackFile?.(nextFiles); event.target.value = ""; }; const removeItem = (itemIndex: number) => { const nextFiles = (files ?? []).filter((_, index) => index !== itemIndex); - callbackFiles?.(nextFiles); + callbackFile?.(nextFiles); }; const baseTextRef = useRef(""); @@ -199,11 +198,6 @@ const DxcMessageInput = ({ useEffect(() => { if (!transcript) return; - if (onTranscript) { - onTranscript(transcript); - return; - } - const combined = baseTextRef.current ? `${baseTextRef.current} ${transcript}` : transcript; changeValue(combined); @@ -211,7 +205,7 @@ const DxcMessageInput = ({ inputRef.current.style.height = "auto"; inputRef.current.style.height = `${inputRef.current.scrollHeight}px`; } - }, [transcript, onTranscript]); + }, [transcript, changeValue]); useEffect(() => { if (onRecordingChange) { @@ -225,7 +219,7 @@ const DxcMessageInput = ({ const controller = new AbortController(); try { - const result = onSubmit?.(controller.signal); + const result = onButtonClick?.("submit", controller.signal); if (result instanceof Promise) { await result; } @@ -236,6 +230,11 @@ const DxcMessageInput = ({ } }; + const handleStop = () => { + if (disabled) return; + void onButtonClick?.("stop"); + }; + return ( <MessageInputContainer size={size}> <MessageInput @@ -283,19 +282,20 @@ const DxcMessageInput = ({ }} placeholder={placeholder} ref={inputRef} + tabIndex={tabIndex} maxLength={maxLength} minLength={minLength} value={value ?? innerValue} /> - <DxcFlex justifyContent={bottomOptions ? "space-between" : "flex-end"} alignItems="center"> - {bottomOptions && ( + <DxcFlex justifyContent={modelList ? "space-between" : "flex-end"} alignItems="center"> + {modelList && ( <DxcSelect // size="fitContent" - options={bottomOptions.map((option) => ({ label: option.label ?? option.value, value: option.value }))} + options={modelList.map((option) => ({ label: option.label ?? option.value, value: option.value }))} disabled={isGenerating || disabled} - defaultValue={bottomOptions[0]?.value ?? ""} + defaultValue={modelList[0]?.value ?? ""} onChange={(val) => { - const selectedOption = bottomOptions.find((option) => option.value === val.value); + const selectedOption = modelList.find((option) => option.value === val.value); selectedOption?.onSelect(); }} /> @@ -318,7 +318,7 @@ const DxcMessageInput = ({ icon={!isGenerating ? "send" : "filled_stop"} size={{ height: "medium" }} disabled={disabled} - onClick={!isGenerating ? handleSubmit : onStop} + onClick={!isGenerating ? handleSubmit : handleStop} title={!isGenerating ? "Submit" : "Stop"} /> </DxcFlex> diff --git a/packages/lib/src/message-input/types.ts b/packages/lib/src/message-input/types.ts index bed927785..2f5b878bf 100644 --- a/packages/lib/src/message-input/types.ts +++ b/packages/lib/src/message-input/types.ts @@ -11,22 +11,17 @@ export type FileData = { type Props = { /** - * Options to be shown on the dropdown under the input. + * If true, the voice recording button will be shown. */ - bottomOptions?: { - label?: string; - icon?: string | (React.ReactNode & React.SVGProps<SVGSVGElement>); - value: string; - onSelect: () => void; - }[]; + allowVoiceInput?: boolean; /** - * Initial value of the input, only when it is uncontrolled. + * This function will be called when the selection of top items changes. */ - defaultValue?: string; + callbackFile?: (files: FileData[]) => void; /** - * Value of the input. If undefined, the component will be uncontrolled and the value will be managed internally by the component. + * Initial value of the input, only when it is uncontrolled. */ - value?: string; + defaultValue?: string; /** * If true, the component will be disabled. */ @@ -40,49 +35,43 @@ type Props = { * the error message would not be modified. */ error?: string; - /** - * Text to be put as placeholder of the input. - */ - placeholder?: string; - /** - * Specifies the size of the component. The size will affect the width of the input. - */ - size?: "small" | "medium" | "large" | "fillParent"; /** * Items to be shown at the top. */ files?: FileData[] | []; /** - * This function will be called when the selection of top items changes. + * If true, it indicates that a request is being processed after the user submits a query. */ - callbackFiles?: (files: FileData[]) => void; + isGenerating?: boolean; /** - * Specifies the minimum length allowed by the input. + * Specifies the maximum length allowed by the input. * This will be checked both when the input element loses the * focus and while typing within it. If the string entered does not - * comply the minimum length, the onBlur and onChange functions will be called + * comply the maximum length, the onBlur and onChange functions will be called * with the current value and an internal error informing that the value * length does not comply the specified range. If a valid length is * reached, the error parameter of both events will not be defined. */ - minLength?: number; + maxLength?: number; /** - * Specifies the maximum length allowed by the input. + * Specifies the minimum length allowed by the input. * This will be checked both when the input element loses the * focus and while typing within it. If the string entered does not - * comply the maximum length, the onBlur and onChange functions will be called + * comply the minimum length, the onBlur and onChange functions will be called * with the current value and an internal error informing that the value * length does not comply the specified range. If a valid length is * reached, the error parameter of both events will not be defined. */ - maxLength?: number; + minLength?: number; /** - * This function will be called when the user types within the input - * element of the component. An object including the current value and - * the error (if the value entered is not valid) will be passed to this - * function. If there is no error, error will not be defined. + * Options to be shown on the dropdown under the input. */ - onChange?: (val: { value: string; error?: string }) => void; + modelList?: { + label?: string; + icon?: string | (React.ReactNode & React.SVGProps<SVGSVGElement>); + value: string; + onSelect: () => void; + }[]; /** * This function will be called when the input element loses the focus. * An object including the input value and the error (if the value @@ -91,41 +80,39 @@ type Props = { */ onBlur?: (val: { value: string; error?: string }) => void; /** - * This function will be called when the user clicks on the button or presses enter. + * This function will be called when the user types within the input + * element of the component. An object including the current value and + * the error (if the value entered is not valid) will be passed to this + * function. If there is no error, error will not be defined. */ - onSubmit?: (signal?: AbortSignal) => void | Promise<void>; + onChange?: (val: { value: string; error?: string }) => void; /** - * If true, the voice recording button will be shown. + * This function will be called when the user clicks on the button (submit or stop) or presses enter. + * The type parameter indicates whether it's a "submit" or "stop" event. + * For submit events, an AbortSignal is provided to allow cancellation. */ - allowVoiceInput?: boolean; + onButtonClick?: (type: "submit" | "stop", signal?: AbortSignal) => void | Promise<void>; /** - * If true, it indicates that a request is being processed after the user submits a query. + * This function will be called when the recording state changes (starts or stops). + * Only used when isRecording prop is controlled. */ - isGenerating?: boolean; + onRecordingChange?: (isRecording: boolean) => void; /** - * This function will be called to stop the current action. + * Text to be put as placeholder of the input. */ - onStop?: () => void; + placeholder?: string; /** - * This function will be called when the recording state changes (starts or stops). - * Only used when isRecording prop is controlled. + * Specifies the size of the component. The size will affect the width of the input. */ - onRecordingChange?: (isRecording: boolean) => void; + size?: "small" | "medium" | "large" | "fillParent"; /** - * This function will be called with the transcript text as the user speaks. - * If provided, the transcript won't be automatically added to the input value. + * Value of the tabindex attribute. */ - onTranscript?: (transcript: string) => void; -}; - -export type BottomSelectProps = { - options: { - label?: string; - icon?: string | (React.ReactNode & React.SVGProps<SVGSVGElement>); - value: string; - onSelect: () => void; - }[]; - disabled?: boolean; + tabIndex?: number; + /** + * Value of the input. If undefined, the component will be uncontrolled and the value will be managed internally by the component. + */ + value?: string; }; export default Props; From 7a554d36fd8076421089e2bc7218724ea35369eb Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Fri, 17 Jul 2026 10:27:03 +0200 Subject: [PATCH 08/22] add overview doc --- .../message-input/MessageInputPageLayout.tsx | 5 +- .../overview/MessageInputOverviewPage.tsx | 313 +++++++++++++++++- .../overview/images/generating-state.png | Bin 0 -> 15620 bytes .../overview/images/message-input-example.png | Bin 0 -> 170602 bytes 4 files changed, 312 insertions(+), 6 deletions(-) create mode 100644 apps/website/screens/components/message-input/overview/images/generating-state.png create mode 100644 apps/website/screens/components/message-input/overview/images/message-input-example.png diff --git a/apps/website/screens/components/message-input/MessageInputPageLayout.tsx b/apps/website/screens/components/message-input/MessageInputPageLayout.tsx index caf7c67e0..bbf4a11c6 100644 --- a/apps/website/screens/components/message-input/MessageInputPageLayout.tsx +++ b/apps/website/screens/components/message-input/MessageInputPageLayout.tsx @@ -16,9 +16,8 @@ const MessageInputPageHeading = ({ children }: { children: ReactNode }) => { <DxcFlex direction="column" gap="var(--spacing-gap-xl)"> <ComponentHeading name="Message Input" /> <DxcParagraph> - Message inputs allow users to enter and send messages within an application. They typically include a text - area for composing messages and may offer additional features such as attachments, emojis, or formatting - options. + Message inputs are composition components specifically designed to capture and send user messages in{" "} + <strong>conversational interfaces</strong>, both in human-to-human messaging and AI-assisted applications. </DxcParagraph> <TabsPageHeading tabs={tabs} /> </DxcFlex> diff --git a/apps/website/screens/components/message-input/overview/MessageInputOverviewPage.tsx b/apps/website/screens/components/message-input/overview/MessageInputOverviewPage.tsx index acc497639..9c2e24f5b 100644 --- a/apps/website/screens/components/message-input/overview/MessageInputOverviewPage.tsx +++ b/apps/website/screens/components/message-input/overview/MessageInputOverviewPage.tsx @@ -1,13 +1,320 @@ -//import QuickNavContainer from "@/common/QuickNavContainer"; +import QuickNavContainer from "@/common/QuickNavContainer"; import DxcFlex from "../../../../../../packages/lib/src/flex/Flex"; import DocFooter from "@/common/DocFooter"; +import DxcParagraph from "../../../../../../packages/lib/src/paragraph/Paragraph"; +import { DxcBulletedList } from "@dxc-technology/halstack-react"; +import Figure from "@/common/Figure"; +import Image from "@/common/Image"; +import messageInputExample from "./images/message-input-example.png"; +import generatingState from "./images/generating-state.png"; -// const sections = []; +const sections = [ + { + title: "Overview", + content: ( + <> + <DxcParagraph> + The <strong>Message input</strong> provides a structured composition area for conversational interfaces. It + combines a <strong>text input field</strong>, an optional <strong>context zone</strong> for attachments and + selections, and a <strong>persistent action bar</strong> with submission controls. Common use cases include + chat applications, customer support tools, AI assistants, and any interface where users compose and send + messages or queries. + </DxcParagraph> + <DxcParagraph> + Unlike our text area, which is a general-purpose multi-line text field for form contexts, the{" "} + <strong>message input is built around the interaction model of sending a message</strong>. It exposes + configurable zones for attached context, a left action slot for secondary selections, and a voice input + toggle, and it includes a dedicated generating state for AI interfaces that need to communicate when the + system is processing a response. + </DxcParagraph> + <DxcParagraph> + Proper use of placeholder text, the available states, and the optional zones can significantly improve the + usability and clarity of any conversational interface. + </DxcParagraph> + </> + ), + }, + { + title: "Anatomy", + content: ( + <> + {/* <Image src={linkAnatomy} alt="Message input anatomy" /> */} + <DxcBulletedList type="number"> + <DxcBulletedList.Item> + <strong>Dropdown</strong> <em>(optional)</em>: allows the user to attach files, either as context for the AI + or as files to send in a conversation or message. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Attachments </strong> <em>(optional)</em>: A chip or set of chips that reflects the selection made + by the dropdown immediately to its left. It represents the context attachments the user can add for the + agent, or any file they want to include with the message. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Send button</strong>: the primary submission control, anchored to the bottom right of the action + bar. Transitions to a stop or cancel affordance in the generating state. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Container</strong>: the visual wrapper of the component, defining its boundary, background, and + border. Border color changes to reflect the current interaction state. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Model Selector</strong> <em>(optional)</em>: a dropdown anchored to the bottom-left corner of the + action bar. Intended for selections that shape how the message is processed, such as a model selector or a + conversation mode picker. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Text input area</strong>: the main composition field. Displays placeholder text when empty and grows + vertically as the user types. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Dictation button</strong> <em>(optional)</em>: a voice input trigger positioned to the left of the + send button that lets user dictate their query instead of typing. It should only be visible when voice input + is functional in the target context. + </DxcBulletedList.Item> + </DxcBulletedList> + </> + ), + }, + { + title: "Conversational inputs", + content: ( + <> + <DxcParagraph> + The message input is designed for <strong>conversational UI patterns</strong>, where the primary user goal is + to compose and submit a message or query. Unlike traditional form inputs, it is not intended for data + collection in structured forms. + </DxcParagraph> + <DxcParagraph> + A message input always requires a send action to complete the interaction. It may optionally include a + contextual zone for attachments and a left action slot for secondary selections that affect how the message is + processed. + </DxcParagraph> + </> + ), + subSections: [ + { + title: "Shared input characteristics", + content: ( + <> + <DxcParagraph> + Although the Message input differs from standard form inputs, it shares some common configurable features: + </DxcParagraph> + <DxcBulletedList> + <DxcBulletedList.Item> + <strong>Placeholder text:</strong> a short hint displayed inside the text input area that describes its + purpose or expected content. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Helper text:</strong> additional guidance displayed below the component to help the user + understand constraints or formatting requirements. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Optional elements:</strong> the top actions zone, left action slot, and voice button are all + optional and can be toggled independently to fit the needs of each interface. + </DxcBulletedList.Item> + </DxcBulletedList> + </> + ), + }, + { + title: "Common input states", + content: ( + <> + <DxcParagraph> + The message input supports the following standard interactive and informative states: + </DxcParagraph> + <DxcBulletedList> + <DxcBulletedList.Item> + <strong>Disabled:</strong> prevents user interaction. Use when the input is not applicable or editable + under certain conditions, such as when permissions are insufficient or the interface is in a read-only + mode. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Error:</strong> applied when the input fails validation or a submission results in a system + error. The border switches to the error color and the error message zone below the container becomes + visible. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Read-only:</strong> the field is visible and focusable but not editable. Suitable for displaying + a message that cannot be modified. + </DxcBulletedList.Item> + </DxcBulletedList> + </> + ), + }, + ], + }, + { + title: "Using message inputs", + content: ( + <> + <DxcParagraph> + The Message input is highly configurable, allowing teams to adapt it to both simple messaging interfaces and + complex AI-assisted applications. This section highlights the key behaviors and zones of the component. + </DxcParagraph> + <Figure caption="An example of our message input inside a conversational interface."> + <Image src={messageInputExample} alt="An example of our message input inside a conversational interface." /> + </Figure> + </> + ), + subSections: [ + { + title: "Top actions zone", + content: ( + <> + <DxcParagraph> + The top actions zone provides space for contextual chips and a dropdown. When enabled, it appears as a + horizontally scrollable strip above the text input. Chips in this zone represent items attached to or + scoping the current message, such as uploaded files or active filters. + </DxcParagraph> + <DxcParagraph> + Users can dismiss individual chips using the close action on each one. When the number of chips exceeds + the available width, the strip scrolls horizontally. + </DxcParagraph> + </> + ), + }, + { + title: "Left action slot", + content: ( + <> + <DxcParagraph> + The left action slot hosts an optional selector at the bottom left of the action bar. Its purpose is to + provide a secondary selection that affects how the message is processed, not to trigger an action. + Examples include model selectors, conversation mode pickers, and language selectors. + </DxcParagraph> + </> + ), + }, + { + title: "Action buttons", + content: ( + <> + <DxcParagraph> + The action bar's right side contains the <strong>send button</strong>, and optionally the{" "} + <strong>voice button</strong>. + </DxcParagraph> + <DxcBulletedList> + <DxcBulletedList.Item> + The <strong>send button</strong> is always visible and submits the composed message. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + The <strong>voice button</strong> provides a voice input trigger. It should only be shown when dictation + is supported and operational in the target context. Displaying it without functional voice capability + creates a broken affordance. + </DxcBulletedList.Item> + </DxcBulletedList> + </> + ), + }, + { + title: "Generating state", + content: ( + <> + <DxcParagraph> + The generating state is specific to AI-assisted interfaces. It locks the send button to prevent duplicate + submissions while the system processes a response, and transitions the button to a stop or cancel + affordance. The text input area remains accessible so users can draft their next message while waiting. + </DxcParagraph> + + <Figure caption="Generating state of the message input"> + <Image src={generatingState} alt="Generating state of the message input" /> + </Figure> + </> + ), + }, + { + title: "Best practices", + subSections: [ + { + title: "General", + content: ( + <> + <DxcBulletedList> + <DxcBulletedList.Item> + <strong>Use this component for message composition only:</strong> The message input carries + conversational connotations by design. Using it in standard form contexts, such as notes fields, + description inputs, or feedback forms, creates mismatched expectations. Use our text area component + for those cases. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Write placeholder text that sets scope:</strong> avoid generic strings like "Type here" or + "Write something...". Prefer specific strings that reflect the capability of the interface, such as + "Ask about your policy documents". Keep it short enough to be absorbed at a glance. + </DxcBulletedList.Item> + </DxcBulletedList> + </> + ), + }, + { + title: "Top actions", + content: ( + <> + <DxcBulletedList> + <DxcBulletedList.Item> + <strong>Keep chips focused on the current message:</strong> chips should represent items directly + tied to the message being composed, such as attached files or active filters. Do not use this zone + for persistent session state or navigation elements unrelated to the current input. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Avoid overloading the context selector:</strong> the context selector dropdown in the top + zone should offer a focused set of options. If the number of options grows large, consider using a + separate selection pattern before the user enters the composition area. + </DxcBulletedList.Item> + </DxcBulletedList> + </> + ), + }, + { + title: "Model selector", + content: ( + <> + <DxcBulletedList> + <DxcBulletedList.Item> + <strong>Reserve this slot for context-setting selections:</strong> the left action slot is intended + for selections that shape the message, not for buttons or navigation elements. Model selectors, mode + pickers, and language selectors are appropriate. Overloading this slot with primary controls creates + ambiguity about the message flow. + </DxcBulletedList.Item> + </DxcBulletedList> + </> + ), + }, + { + title: "AI contexts", + content: ( + <> + <DxcBulletedList> + <DxcBulletedList.Item> + <strong>Use the generating state instead of disabling the component:</strong> disabling the field + during AI processing removes the ability to draft the next message while waiting for a response. The + generating state locks submission while keeping the composition area accessible. Reserve disabled + for genuinely unavailable contexts. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Write actionable error messages:</strong> the error string should tell the user what to do, + not just what went wrong. "Maximum 2,000 characters" is more useful than "Input too long". If the + error originates from a system failure, acknowledge it clearly and offer a next step. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Show the voice button only when voice input is functional:</strong> if voice capability is + conditional, perform capability detection before enabling the voice button property. A microphone + button that does nothing erodes trust and usability. + </DxcBulletedList.Item> + </DxcBulletedList> + </> + ), + }, + ], + }, + ], + }, +]; const MessageInputOverviewPage = () => { return ( <DxcFlex direction="column" gap="4rem"> - {/* <QuickNavContainer sections={sections} startHeadingLevel={2} /> */} + <QuickNavContainer sections={sections} startHeadingLevel={2} /> <DocFooter githubLink="https://github.com/dxc-technology/halstack-react/blob/master/apps/website/screens/components/message-input/overview/MessageInputOverviewPage.tsx" /> </DxcFlex> ); diff --git a/apps/website/screens/components/message-input/overview/images/generating-state.png b/apps/website/screens/components/message-input/overview/images/generating-state.png new file mode 100644 index 0000000000000000000000000000000000000000..97e88fa3cdeca3fc147a1cf77656996d6a92f145 GIT binary patch literal 15620 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 literal 0 HcmV?d00001 diff --git a/apps/website/screens/components/message-input/overview/images/message-input-example.png b/apps/website/screens/components/message-input/overview/images/message-input-example.png new file mode 100644 index 0000000000000000000000000000000000000000..21109fa454df624fc4699c19703eedfaebfdc27d GIT binary patch literal 170602 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�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 XaEg-<7jrrtk@mp8!=Li^BwhM1dFzpn literal 0 HcmV?d00001 From 2f69a676578205b16bb2a9e6d5bc804dac8ce0e6 Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Mon, 20 Jul 2026 11:59:40 +0200 Subject: [PATCH 09/22] Add documentation --- .../pages/components/message-input/index.tsx | 2 + .../code/MessageInputCodePage.tsx | 87 +++++++------ .../message-input/code/examples/advanced.tsx | 118 ++++++++++++++++++ .../code/examples/controlled.tsx | 43 +++++++ .../code/examples/uncontrolled.tsx | 23 ++++ packages/lib/src/message-input/types.ts | 7 +- 6 files changed, 232 insertions(+), 48 deletions(-) create mode 100644 apps/website/screens/components/message-input/code/examples/advanced.tsx create mode 100644 apps/website/screens/components/message-input/code/examples/controlled.tsx create mode 100644 apps/website/screens/components/message-input/code/examples/uncontrolled.tsx diff --git a/apps/website/pages/components/message-input/index.tsx b/apps/website/pages/components/message-input/index.tsx index 1c370a31e..5a5c8b355 100644 --- a/apps/website/pages/components/message-input/index.tsx +++ b/apps/website/pages/components/message-input/index.tsx @@ -1,12 +1,14 @@ import Head from "next/head"; import type { ReactElement } from "react"; import MessageInputPageLayout from "screens/components/message-input/MessageInputPageLayout"; +import MessageInputOverviewPage from "screens/components/message-input/overview/MessageInputOverviewPage"; const Index = () => ( <> <Head> <title>Message Input — Halstack Design System + ); diff --git a/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx b/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx index 1f26ed7d4..9b7bc57a3 100644 --- a/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx +++ b/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx @@ -1,7 +1,24 @@ import { DxcTable, DxcFlex } from "@dxc-technology/halstack-react"; import DocFooter from "@/common/DocFooter"; import QuickNavContainer from "@/common/QuickNavContainer"; -import Code, { TableCode } from "@/common/Code"; +import Code, { ExtendedTableCode, TableCode } from "@/common/Code"; +import Example from "@/common/example/Example"; +import controlled from "./examples/controlled"; +import uncontrolled from "./examples/uncontrolled"; +import advanced from "./examples/advanced"; + +const fileDataTypeString = `{ + label: string; + icon?: string | SVG; +};`; +const callbackFileTypeString = "(files: FileData[]) => void"; +const filesTypeString = `FileData[] | []`; +const modelListTypeString = `{ + label?: string; + icon?: string | SVG; + value: string; + onSelect: () => void; +}[]`; const sections = [ { @@ -30,7 +47,11 @@ const sections = [ callbackFile - {"(files: FileData[]) => void"} + {callbackFileTypeString} +

+ being FileData an object with the following properties: +

+ {fileDataTypeString} This function will be called when the selection of top items changes. - @@ -69,7 +90,7 @@ const sections = [ files - {"FileData[] | []"} + {filesTypeString} Items to be shown at the top. - @@ -115,13 +136,9 @@ const sections = [ modelList - - { - "{ label?: string; icon?: string | (React.ReactNode & React.SVGProps); value: string; onSelect: () => void; }[]" - } - + {modelListTypeString} - Options to be shown on the dropdown under the input. + Options to be shown on the dropdown under the input. The first option will be selected by default. - @@ -139,7 +156,7 @@ const sections = [ onButtonClick - {"(type: 'submit' | 'stop', signal?: AbortSignal) => void | Promise"} + {"(type: 'submit' | 'stop') => void"} This function will be called when the user clicks on the button (submit or stop) or presses enter. The @@ -165,10 +182,7 @@ const sections = [ {"(isRecording: boolean) => void"} - - This function will be called when the recording state changes (starts or stops). Only used when - isRecording prop is controlled. - + This function will be called when the recording state changes (starts or stops). - @@ -215,36 +229,21 @@ const sections = [ ), }, { - title: "FileData", - content: ( - - - - Name - Type - Description - - - - - label - - string - - The chip label. - - - icon - - {"string | (React.ReactNode & React.SVGProps)"} - - - The chip icon. It can be a string representing the icon name or a ReactNode representing a custom icon. - - - - - ), + title: "Examples", + subSections: [ + { + title: "Uncontrolled", + content: , + }, + { + title: "Controlled", + content: , + }, + { + title: "Advanced", + content: , + }, + ], }, ]; diff --git a/apps/website/screens/components/message-input/code/examples/advanced.tsx b/apps/website/screens/components/message-input/code/examples/advanced.tsx new file mode 100644 index 000000000..bd17a4394 --- /dev/null +++ b/apps/website/screens/components/message-input/code/examples/advanced.tsx @@ -0,0 +1,118 @@ +import { DxcMessageInput, DxcInset, DxcFlex, DxcButton } from "@dxc-technology/halstack-react"; +import { useState } from "react"; + +const code = `() => { + const [value, setValue] = useState(""); + const [files, setFiles] = useState([ + { label: "document.pdf", icon: "insert_drive_file" }, + { label: "image.png", icon: "image" } + ]); + const [selectedModel, setSelectedModel] = useState("GPT-4"); + const [isGenerating, setIsGenerating] = useState(false); + const [error, setError] = useState(""); + + const onChange = ({ value, error }) => { + setValue(value); + setError(error || ""); + }; + + const onBlur = ({ value, error }) => { + console.log("Input blurred with value:", value); + if (error) { + setError(error); + } + }; + + const onButtonClick = async (type, signal) => { + if (type === "submit") { + setIsGenerating(true); + console.log("Submitting message:", value); + console.log("Attached files:", files); + console.log("Selected model:", selectedModel); + + // Simulate async operation + await new Promise((resolve) => setTimeout(resolve, 2000)); + + setIsGenerating(false); + setValue(""); + setFiles([]); + } else if (type === "stop") { + console.log("Stopping generation"); + setIsGenerating(false); + } + }; + + const callbackFile = (updatedFiles) => { + setFiles(updatedFiles); + }; + + const onRecordingChange = (isRecording) => { + console.log("Recording state changed:", isRecording); + }; + + const modelList = [ + { + label: "GPT-4", + icon: "psychology", + value: "gpt4", + onSelect: () => setSelectedModel("GPT-4") + }, + { + label: "GPT-3.5", + icon: "smart_toy", + value: "gpt3.5", + onSelect: () => setSelectedModel("GPT-3.5") + }, + { + label: "Claude", + icon: "lightbulb", + value: "claude", + onSelect: () => setSelectedModel("Claude") + } + ]; + + const toggleSize = () => { + // This would toggle between sizes in a real implementation + }; + + return ( + + + +
+ Selected model: {selectedModel} +
+ Character count: {value.length} / 200 +
+ Attached files: {files.length} +
+
+
+ ); +}`; + +const scope = { + DxcMessageInput, + DxcInset, + DxcFlex, + DxcButton, + useState, +}; + +export default { code, scope }; diff --git a/apps/website/screens/components/message-input/code/examples/controlled.tsx b/apps/website/screens/components/message-input/code/examples/controlled.tsx new file mode 100644 index 000000000..a2ee51edd --- /dev/null +++ b/apps/website/screens/components/message-input/code/examples/controlled.tsx @@ -0,0 +1,43 @@ +import { DxcMessageInput, DxcInset } from "@dxc-technology/halstack-react"; +import { useState } from "react"; + +const code = `() => { + const [value, setValue] = useState(""); + + const onChange = ({ value }) => { + setValue(value); + }; + + const onBlur = ({ value }) => { + console.log("Input blurred with value:", value); + }; + + const onButtonClick = async (type, signal) => { + if (type === "submit") { + console.log("Submitted message:", value); + // Simulate async operation + await new Promise((resolve) => setTimeout(resolve, 1000)); + setValue(""); // Clear input after submit + } + }; + + return ( + + + + ); +}`; + +const scope = { + DxcMessageInput, + DxcInset, + useState, +}; + +export default { code, scope }; diff --git a/apps/website/screens/components/message-input/code/examples/uncontrolled.tsx b/apps/website/screens/components/message-input/code/examples/uncontrolled.tsx new file mode 100644 index 000000000..a78fd5896 --- /dev/null +++ b/apps/website/screens/components/message-input/code/examples/uncontrolled.tsx @@ -0,0 +1,23 @@ +import { DxcMessageInput, DxcButton, DxcFlex, DxcInset } from "@dxc-technology/halstack-react"; +import { useRef } from "react"; + +const code = `() => { + return ( + + + + ); +}`; + +const scope = { + DxcMessageInput, + DxcButton, + DxcFlex, + DxcInset, + useRef, +}; + +export default { code, scope }; diff --git a/packages/lib/src/message-input/types.ts b/packages/lib/src/message-input/types.ts index 2f5b878bf..a4da3d616 100644 --- a/packages/lib/src/message-input/types.ts +++ b/packages/lib/src/message-input/types.ts @@ -13,7 +13,7 @@ type Props = { /** * If true, the voice recording button will be shown. */ - allowVoiceInput?: boolean; + allowRecording?: boolean; /** * This function will be called when the selection of top items changes. */ @@ -64,7 +64,7 @@ type Props = { */ minLength?: number; /** - * Options to be shown on the dropdown under the input. + * Options to be shown on the dropdown under the input. The first option will be selected by default. */ modelList?: { label?: string; @@ -91,10 +91,9 @@ type Props = { * The type parameter indicates whether it's a "submit" or "stop" event. * For submit events, an AbortSignal is provided to allow cancellation. */ - onButtonClick?: (type: "submit" | "stop", signal?: AbortSignal) => void | Promise; + onButtonClick?: (type: "submit" | "stop") => void; /** * This function will be called when the recording state changes (starts or stops). - * Only used when isRecording prop is controlled. */ onRecordingChange?: (isRecording: boolean) => void; /** From 3541c3ff53f4ac223724e76f709e65f2563d9acc Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso Date: Mon, 20 Jul 2026 12:00:33 +0200 Subject: [PATCH 10/22] Add stories and tests --- .../message-input/MessageInput.stories.tsx | 113 +++++--- .../src/message-input/MessageInput.test.tsx | 152 +++++----- .../lib/src/message-input/MessageInput.tsx | 274 +++++++++++------- .../message-input/useVoiceTranscription.tsx | 3 +- 4 files changed, 327 insertions(+), 215 deletions(-) diff --git a/packages/lib/src/message-input/MessageInput.stories.tsx b/packages/lib/src/message-input/MessageInput.stories.tsx index a5c5b14f0..773135d45 100644 --- a/packages/lib/src/message-input/MessageInput.stories.tsx +++ b/packages/lib/src/message-input/MessageInput.stories.tsx @@ -12,8 +12,47 @@ export default { type Story = StoryObj; +const modelListOptions = [ + { label: "GPT-4", value: "gpt4", onSelect: () => {} }, + { label: "Claude Sonnet", value: "claude", onSelect: () => {} }, + { label: "Gemini Pro", value: "gemini", onSelect: () => {} }, + { label: "LLaMA 3", value: "llama", onSelect: () => {} }, +]; + const MessageInput = () => ( <> + + <ExampleContainer> + <Title title="Small" level={4} /> + <DxcMessageInput modelList={modelListOptions} size="small" placeholder="Ask me anything..." files={[]} /> + </ExampleContainer> + <ExampleContainer> + <Title title="Medium (default)" level={4} /> + <DxcMessageInput modelList={modelListOptions} size="medium" placeholder="Ask me anything..." files={[]} /> + </ExampleContainer> + <ExampleContainer> + <Title title="Large" level={4} /> + <DxcMessageInput modelList={modelListOptions} size="large" placeholder="Ask me anything..." files={[]} /> + </ExampleContainer> + <ExampleContainer> + <Title title="Fill Parent" level={4} /> + <DxcMessageInput modelList={modelListOptions} size="fillParent" placeholder="Ask me anything..." files={[]} /> + </ExampleContainer> + + <ExampleContainer> + <Title title="With Voice Input" level={2} /> + <DxcMessageInput placeholder="Ask me anything with voice..." allowRecording files={[]} /> + </ExampleContainer> + <ExampleContainer> + <Title title="With Model List" level={2} /> + <DxcMessageInput placeholder="Select a model and ask..." modelList={modelListOptions} files={[]} /> + </ExampleContainer> + <ExampleContainer> + <Title title="With All Features" level={2} /> + <DxcMessageInput placeholder="Full featured input..." allowRecording modelList={modelListOptions} files={[]} /> + </ExampleContainer> + + {/* ================== STATES ================== */} <Title title="States" theme="light" level={2} /> <ExampleContainer> <Title title="Default" level={4} /> @@ -33,58 +72,35 @@ const MessageInput = () => ( </ExampleContainer> <ExampleContainer> <Title title="Disabled" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." disabled files={[]} /> + <DxcMessageInput placeholder="This input is disabled..." disabled files={[]} /> </ExampleContainer> <ExampleContainer> - <Title title="isGenerating" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." isGenerating files={[]} /> + <Title title="Is Generating" level={4} /> + <DxcMessageInput placeholder="AI is generating response..." isGenerating files={[]} /> </ExampleContainer> <ExampleContainer> <Title title="Error" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." files={[]} error="Error Message" /> + <DxcMessageInput placeholder="Ask me anything..." files={[]} error="Please enter a valid message" /> </ExampleContainer> <ExampleContainer pseudoState="pseudo-hover"> <Title title="Hover error" level={4} /> <DxcMessageInput placeholder="Ask me anything..." files={[]} error="Error Message" /> </ExampleContainer> + <Title title="Validation" theme="light" level={2} /> <ExampleContainer> - <Title title="Min Length" level={4} /> + <Title title="Min Length (15 characters)" level={4} /> <DxcMessageInput placeholder="Ask me anything..." files={[]} minLength={15} defaultValue="Hello" /> </ExampleContainer> <ExampleContainer> - <Title title="Max Length" level={4} /> + <Title title="Max Length (50 characters)" level={4} /> <DxcMessageInput placeholder="Ask me anything..." files={[]} - maxLength={15} - defaultValue="Hello this is a test of the max length property" + maxLength={50} + defaultValue="This is a test message to demonstrate the maximum length validation feature" /> </ExampleContainer> - - <ExampleContainer> - <Title title="Bottom Select" level={4} /> - <DxcMessageInput - placeholder="Ask me anything..." - modelList={[ - { label: "Option 1", value: "option1", onSelect: () => {} }, - { label: "Option 29872357857274590", value: "option2", onSelect: () => {} }, - { label: "Option 3", value: "option3", onSelect: () => {} }, - { label: "Option 4", value: "option4", onSelect: () => {} }, - ]} - files={[]} - /> - </ExampleContainer> - - <ExampleContainer> - <Title title="With Voice Input" level={4} /> - <DxcMessageInput placeholder="Ask me anything with voice..." allowVoiceInput files={[]} /> - </ExampleContainer> - - <ExampleContainer> - <Title title="Without Voice Input" level={4} /> - <DxcMessageInput placeholder="No voice button here..." files={[]} /> - </ExampleContainer> </> ); @@ -93,7 +109,10 @@ const ControlledFileExample = () => { const handleButtonClick = (type: "submit" | "stop", _signal?: AbortSignal) => { if (type === "submit") { - console.log(files.map((file: FileData) => file.label)); + console.log( + "Submitted files:", + files.map((file: FileData) => file.label) + ); } else { console.log("Stop action triggered"); } @@ -104,15 +123,17 @@ const ControlledFileExample = () => { }; return ( - <ExampleContainer> - <Title title="Default" level={4} /> - <DxcMessageInput - placeholder="Ask me anything..." - files={files} - callbackFile={callbackFiles} - onButtonClick={handleButtonClick} - /> - </ExampleContainer> + <> + <Title title="Controlled Files" theme="light" level={2} /> + <ExampleContainer> + <DxcMessageInput + placeholder="Attach files and submit..." + files={files} + callbackFile={callbackFiles} + onButtonClick={handleButtonClick} + /> + </ExampleContainer> + </> ); }; @@ -121,7 +142,7 @@ const ControlledVoiceExample = () => { const handleChange = (val: { value: string; error?: string }) => { setInputValue(val.value); - console.log(val); + console.log("Value changed:", val); }; return ( @@ -129,9 +150,9 @@ const ControlledVoiceExample = () => { <Title title="Controlled Voice Recording" theme="light" level={2} /> <ExampleContainer> <DxcMessageInput - placeholder="Controla la grabación desde el componente padre..." + placeholder="Use voice input..." files={[]} - allowVoiceInput + allowRecording value={inputValue} onChange={handleChange} /> @@ -144,7 +165,7 @@ export const Chromatic: Story = { render: MessageInput, }; -export const ControlledFile: Story = { +export const ControlledFiles: Story = { render: ControlledFileExample, }; diff --git a/packages/lib/src/message-input/MessageInput.test.tsx b/packages/lib/src/message-input/MessageInput.test.tsx index d7b6b02eb..848478407 100644 --- a/packages/lib/src/message-input/MessageInput.test.tsx +++ b/packages/lib/src/message-input/MessageInput.test.tsx @@ -22,11 +22,6 @@ describe("Message Input component tests", () => { expect(getByDisplayValue("Hello World")).toBeInTheDocument(); }); - test("renders with helper text", () => { - const { getByText } = render(<DxcMessageInput helperText="This is a helper text" />); - expect(getByText("This is a helper text")).toBeInTheDocument(); - }); - test("renders with error message", () => { const { getByText } = render(<DxcMessageInput error="This is an error" />); expect(getByText("This is an error")).toBeInTheDocument(); @@ -55,36 +50,58 @@ describe("Message Input component tests", () => { expect(onBlur).toHaveBeenCalledWith({ value: "Test" }); }); - test("calls onSubmit when Enter key is pressed", () => { - const onSubmit = jest.fn(); - const { getByRole } = render(<DxcMessageInput onSubmit={onSubmit} />); + test("calls onButtonClick when Enter key is pressed", () => { + const onButtonClick = jest.fn(); + const { getByRole } = render(<DxcMessageInput onButtonClick={onButtonClick} />); const input = getByRole("textbox"); userEvent.click(input); userEvent.type(input, "Test message{Enter}"); - expect(onSubmit).toHaveBeenCalledTimes(1); + expect(onButtonClick).toHaveBeenCalledWith("submit"); + expect(onButtonClick).toHaveBeenCalledTimes(1); }); - test("does not call onSubmit when Shift+Enter is pressed", () => { - const onSubmit = jest.fn(); - const { getByRole } = render(<DxcMessageInput onSubmit={onSubmit} />); + test("does not call onButtonClick when Shift+Enter is pressed", () => { + const onButtonClick = jest.fn(); + const { getByRole } = render(<DxcMessageInput onButtonClick={onButtonClick} />); const input = getByRole("textbox"); userEvent.click(input); userEvent.type(input, "Line 1{Shift>}{Enter}{/Shift}Line 2"); - expect(onSubmit).not.toHaveBeenCalled(); + expect(onButtonClick).not.toHaveBeenCalled(); + }); + + test("does not call onButtonClick when Enter is pressed and component is disabled", () => { + const onButtonClick = jest.fn(); + const { getByRole } = render(<DxcMessageInput disabled onButtonClick={onButtonClick} />); + const input = getByRole("textbox"); + + userEvent.type(input, "Test message{Enter}"); + + expect(onButtonClick).not.toHaveBeenCalled(); + }); + + test("does not call onButtonClick when Enter is pressed and isGenerating is true", () => { + const onButtonClick = jest.fn(); + const { getByRole } = render(<DxcMessageInput isGenerating onButtonClick={onButtonClick} />); + const input = getByRole("textbox"); + + userEvent.type(input, "Test message{Enter}"); + + expect(onButtonClick).not.toHaveBeenCalled(); }); - test("calls onSubmit when submit button is clicked", () => { - const onSubmit = jest.fn(); - const { getByLabelText } = render(<DxcMessageInput onSubmit={onSubmit} />); + test("calls onButtonClick when submit button is clicked", () => { + const onButtonClick = jest.fn(); + const { getByLabelText } = render(<DxcMessageInput onButtonClick={onButtonClick} />); const submitButton = getByLabelText("Submit"); userEvent.click(submitButton); - expect(onSubmit).toHaveBeenCalledTimes(1); + expect(onButtonClick).toHaveBeenCalledWith("submit"); + expect(onButtonClick).toHaveBeenCalledTimes(1); }); test("disables input when disabled prop is true", () => { @@ -94,29 +111,30 @@ describe("Message Input component tests", () => { expect(input).toBeDisabled(); }); - test("disables input when isLoading is true", () => { - const { getByRole } = render(<DxcMessageInput isLoading />); + test("disables input when isGenerating is true", () => { + const { getByRole } = render(<DxcMessageInput isGenerating />); const input = getByRole("textbox"); expect(input).toBeDisabled(); }); - test("shows stop button when isLoading is true", () => { - const stop = jest.fn(); - const { getByLabelText } = render(<DxcMessageInput isLoading stop={stop} />); + test("shows stop button when isGenerating is true", () => { + const onButtonClick = jest.fn(); + const { getByLabelText } = render(<DxcMessageInput isGenerating onButtonClick={onButtonClick} />); const stopButton = getByLabelText("Stop"); expect(stopButton).toBeInTheDocument(); }); - test("calls stop when stop button is clicked", () => { - const stop = jest.fn(); - const { getByLabelText } = render(<DxcMessageInput isLoading stop={stop} />); + test("calls onButtonClick with 'stop' when stop button is clicked", () => { + const onButtonClick = jest.fn(); + const { getByLabelText } = render(<DxcMessageInput isGenerating onButtonClick={onButtonClick} />); const stopButton = getByLabelText("Stop"); userEvent.click(stopButton); - expect(stop).toHaveBeenCalledTimes(1); + expect(onButtonClick).toHaveBeenCalledWith("stop"); + expect(onButtonClick).toHaveBeenCalledTimes(1); }); test("validates minLength and calls onChange with error", () => { @@ -171,19 +189,19 @@ describe("Message Input component tests", () => { expect(input).toHaveValue("Initial text"); }); - test("renders file upload dropdown when allowFileUploads is true", () => { - const { getByRole } = render(<DxcMessageInput allowFileUploads />); + test("renders file upload dropdown when files prop is provided", () => { + const { getByRole } = render(<DxcMessageInput files={[]} />); const dropdown = getByRole("button", { name: "Show options" }); expect(dropdown).toBeInTheDocument(); }); - test("renders bottom select when bottomOptions is provided", () => { - const bottomOptions = [ + test("renders bottom select when modelList is provided", () => { + const modelList = [ { label: "Option 1", value: "option1", onSelect: jest.fn() }, { label: "Option 2", value: "option2", onSelect: jest.fn() }, ]; - const { getByRole } = render(<DxcMessageInput bottomOptions={bottomOptions} />); + const { getByRole } = render(<DxcMessageInput modelList={modelList} />); const select = getByRole("combobox"); expect(select).toBeInTheDocument(); @@ -191,11 +209,11 @@ describe("Message Input component tests", () => { test("calls onSelect when a bottom option is selected", async () => { const onSelect = jest.fn(); - const bottomOptions = [ + const modelList = [ { label: "Option 1", value: "option1", onSelect }, { label: "Option 2", value: "option2", onSelect: jest.fn() }, ]; - const { getByRole } = render(<DxcMessageInput bottomOptions={bottomOptions} />); + const { getByRole } = render(<DxcMessageInput modelList={modelList} />); const select = getByRole("combobox"); userEvent.click(select); @@ -210,40 +228,40 @@ describe("Message Input component tests", () => { expect(queryByText("This is an error")).not.toBeInTheDocument(); }); - test("async onSubmit is handled correctly", async () => { - const onSubmit = jest.fn().mockResolvedValue(undefined); - const { getByLabelText } = render(<DxcMessageInput onSubmit={onSubmit} />); + test("async onButtonClick is handled correctly", async () => { + const onButtonClick = jest.fn(); + const { getByLabelText } = render(<DxcMessageInput onButtonClick={onButtonClick} />); const submitButton = getByLabelText("Submit"); userEvent.click(submitButton); await waitFor(() => { - expect(onSubmit).toHaveBeenCalledTimes(1); + expect(onButtonClick).toHaveBeenCalledWith("submit"); + expect(onButtonClick).toHaveBeenCalledTimes(1); }); }); - test("does not call onSubmit when disabled", () => { - const onSubmit = jest.fn(); - const { getByLabelText } = render(<DxcMessageInput disabled onSubmit={onSubmit} />); + test("does not call onButtonClick when disabled", () => { + const onButtonClick = jest.fn(); + const { getByLabelText } = render(<DxcMessageInput disabled onButtonClick={onButtonClick} />); const submitButton = getByLabelText("Submit"); userEvent.click(submitButton); - expect(onSubmit).not.toHaveBeenCalled(); + expect(onButtonClick).not.toHaveBeenCalled(); }); - test("does not call onSubmit when isLoading", () => { - const onSubmit = jest.fn(); - const { getByLabelText } = render(<DxcMessageInput isLoading onSubmit={onSubmit} />); + test("shows stop button instead of submit when isGenerating", () => { + const onButtonClick = jest.fn(); + const { getByLabelText } = render(<DxcMessageInput isGenerating onButtonClick={onButtonClick} />); - // The button should be the stop button when isLoading + // The button should be the stop button when isGenerating const button = getByLabelText("Stop"); expect(button).toBeInTheDocument(); - expect(onSubmit).not.toHaveBeenCalled(); }); test("handles file selection", () => { - const { getByRole } = render(<DxcMessageInput allowFileUploads />); + const { getByRole } = render(<DxcMessageInput files={[]} />); const dropdown = getByRole("button", { name: "Show options" }); // Simulate selecting the option @@ -253,8 +271,8 @@ describe("Message Input component tests", () => { }); test("adds files when files are selected", () => { - const callbackItems = jest.fn(); - const { container } = render(<DxcMessageInput allowFileUploads callbackItems={callbackItems} />); + const callbackFile = jest.fn(); + const { container } = render(<DxcMessageInput files={[]} callbackFile={callbackFile} />); const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement; expect(fileInput).toBeInTheDocument(); @@ -267,18 +285,16 @@ describe("Message Input component tests", () => { fileInput.dispatchEvent(new Event("change", { bubbles: true })); - expect(callbackItems).toHaveBeenCalled(); + expect(callbackFile).toHaveBeenCalled(); }); test("removes item when chip is clicked", () => { - const callbackItems = jest.fn(); - const topItems = [ - { id: "1", label: "File 1" }, - { id: "2", label: "File 2" }, + const callbackFile = jest.fn(); + const files = [ + { label: "File 1", icon: "insert_drive_file" }, + { label: "File 2", icon: "insert_drive_file" }, ]; - const { getByText } = render( - <DxcMessageInput allowFileUploads topItems={topItems} callbackItems={callbackItems} /> - ); + const { getByText } = render(<DxcMessageInput files={files} callbackFile={callbackFile} />); // Verify chips are rendered expect(getByText("File 1")).toBeInTheDocument(); @@ -286,23 +302,21 @@ describe("Message Input component tests", () => { // Note: Testing the actual click on chip requires finding the dismissible button // which is handled internally by DxcChip component - expect(callbackItems).not.toHaveBeenCalled(); + expect(callbackFile).not.toHaveBeenCalled(); }); - test("handles AbortError in onSubmit without throwing", async () => { - const abortError = new DOMException("Aborted", "AbortError"); - const onSubmit = jest.fn().mockRejectedValue(abortError); - const { getByLabelText } = render(<DxcMessageInput onSubmit={onSubmit} />); + test("calls onButtonClick with submit when submit button is clicked", async () => { + const onButtonClick = jest.fn(); + const { getByLabelText } = render(<DxcMessageInput onButtonClick={onButtonClick} />); const submitButton = getByLabelText("Submit"); userEvent.click(submitButton); await waitFor(() => { - expect(onSubmit).toHaveBeenCalled(); + expect(onButtonClick).toHaveBeenCalledWith("submit"); }); - // AbortError should be caught and not throw - expect(onSubmit).toHaveBeenCalledTimes(1); + expect(onButtonClick).toHaveBeenCalledTimes(1); }); test("validates minLength correctly when value is exactly minLength", () => { @@ -316,9 +330,9 @@ describe("Message Input component tests", () => { expect(onChange).toHaveBeenLastCalledWith({ value: "Hello" }); }); - test("works with uncontrolled topItems", () => { - const callbackItems = jest.fn(); - const { container } = render(<DxcMessageInput allowFileUploads callbackItems={callbackItems} />); + test("works with file uploads", () => { + const callbackFile = jest.fn(); + const { container } = render(<DxcMessageInput files={[]} callbackFile={callbackFile} />); const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement; const file = new File(["content"], "test.txt", { type: "text/plain" }); @@ -330,6 +344,6 @@ describe("Message Input component tests", () => { fileInput.dispatchEvent(new Event("change", { bubbles: true })); - expect(callbackItems).toHaveBeenCalled(); + expect(callbackFile).toHaveBeenCalled(); }); }); diff --git a/packages/lib/src/message-input/MessageInput.tsx b/packages/lib/src/message-input/MessageInput.tsx index 45cae91ea..60f95e33e 100644 --- a/packages/lib/src/message-input/MessageInput.tsx +++ b/packages/lib/src/message-input/MessageInput.tsx @@ -10,6 +10,7 @@ import { useState, } from "react"; import styled from "@emotion/styled"; +import scrollbarStyles from "../styles/scroll"; import PromptInputPropsType from "./types"; import { inputStylesByStatePromptInput, isLengthIncorrect } from "./utils"; import DxcButton from "../button/Button"; @@ -35,6 +36,7 @@ const MessageInputContainer = styled.div<{ size: PromptInputPropsType["size"] }> flex-direction: column; gap: var(--spacing-gap-xs); max-height: 320px; + background-color: var(--color-bg-neutral-lightest); box-shadow: 0 -24px 10px 4px rgba(255, 255, 255, 0.6); width: ${({ size = "medium" }) => sizes[size]}; `; @@ -55,7 +57,24 @@ const MessageInput = styled.div<{ overflow: hidden; `; -const Input = styled.textarea` +const FilesContainer = styled.div` + min-height: 32px; + width: 100%; + display: flex; + align-items: center; + gap: var(--spacing-gap-xs); + overflow-x: auto; + overflow-y: hidden; + padding-bottom: 4px; + + ${scrollbarStyles}; + &::-webkit-scrollbar { + height: 4px; + } +`; + +const Input = styled.textarea<{ isRecording?: boolean }>` + min-height: 20px; max-width: 100%; background: none; border: none; @@ -63,7 +82,8 @@ const Input = styled.textarea` padding: var(--spacing-padding-none) var(--spacing-padding-xs); resize: none; flex-grow: 1; - color: ${({ disabled }) => (disabled ? "var(--color-fg-neutral-medium)" : "var(--color-fg-neutral-dark)")}; + color: ${({ disabled, isRecording }) => + isRecording ? "transparent" : disabled ? "var(--color-fg-neutral-medium)" : "var(--color-fg-neutral-dark)"}; font-family: var(--typography-font-family); font-size: var(--typography-label-m); font-weight: var(--typography-label-regular); @@ -75,10 +95,44 @@ const Input = styled.textarea` color: ${({ disabled }) => (disabled ? "var(--color-fg-neutral-medium)" : "var(--color-fg-neutral-strong)")}; } ${({ disabled }) => disabled && "cursor: not-allowed;"} + + ${scrollbarStyles}; + &::-webkit-scrollbar { + width: 4px; + } +`; + +const InputWrapper = styled.div` + max-height: 150px; + position: relative; + flex-grow: 1; + display: flex; + flex-direction: column; +`; + +const TranscriptOverlay = styled.div` + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + padding: var(--spacing-padding-none) var(--spacing-padding-xs); + pointer-events: none; + color: var(--color-fg-neutral-dark); + font-family: var(--typography-font-family); + font-size: var(--typography-label-m); + font-weight: var(--typography-label-regular); + white-space: pre-wrap; + word-break: break-word; + overflow-y: auto; +`; + +const HighlightedText = styled.span` + color: var(--color-fg-neutral-strong); `; const DxcMessageInput = ({ - allowVoiceInput = false, + allowRecording = false, callbackFile, defaultValue = "", disabled = false, @@ -98,27 +152,25 @@ const DxcMessageInput = ({ value, }: PromptInputPropsType) => { const inputId = `input-${useId()}`; - const errorId = `error-${inputId}`; const translatedLabels = useContext(HalstackLanguageContext); const inputRef = useRef<HTMLTextAreaElement | null>(null); - const inputContainerRef = useRef<HTMLDivElement | null>(null); + const overlayRef = useRef<HTMLDivElement | null>(null); const [innerValue, setInnerValue] = useState(defaultValue); const [isFocused, setIsFocused] = useState(false); const fileInputRef = useRef<HTMLInputElement | null>(null); - const { transcript, isRecording, startRecording, stopRecording } = useVoiceTranscription(); + const { transcript, isRecording, startRecording, stopRecording, resetTranscript } = useVoiceTranscription(); const changeValue = (newValue: string) => { if (value == null) { setInnerValue(newValue); } - if (isLengthIncorrect(newValue, minLength, maxLength)) { - onChange?.({ - value: newValue, - error: translatedLabels.formFields.lengthErrorMessage?.(minLength, maxLength), - }); - } else { - onChange?.({ value: newValue }); - } + const lengthError = isLengthIncorrect(newValue, minLength, maxLength) + ? translatedLabels.formFields.lengthErrorMessage?.(minLength, maxLength) + : undefined; + onChange?.({ + value: newValue, + ...(lengthError && { error: lengthError }), + }); }; const handleInputContainerOnClick = () => { @@ -133,7 +185,6 @@ const DxcMessageInput = ({ const handleInputOnChange = (event: ChangeEvent<HTMLTextAreaElement>) => { const el = event.target; - el.style.height = "auto"; el.style.height = `${el.scrollHeight}px`; @@ -147,20 +198,13 @@ const DxcMessageInput = ({ const handleInputOnBlur = (event: FocusEvent<HTMLTextAreaElement>) => { setIsFocused(false); - if (isLengthIncorrect(event.target.value, minLength, maxLength)) { - onBlur?.({ - value: event.target.value, - error: translatedLabels.formFields.lengthErrorMessage?.(minLength, maxLength), - }); - } else { - onBlur?.({ value: event.target.value }); - } - }; - const handleInputOnKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => { - if (event.key === "Enter" && !event.shiftKey) { - event.preventDefault(); - void handleSubmit(); - } + const lengthError = isLengthIncorrect(event.target.value, minLength, maxLength) + ? translatedLabels.formFields.lengthErrorMessage?.(minLength, maxLength) + : undefined; + onBlur?.({ + value: event.target.value, + ...(lengthError && { error: lengthError }), + }); }; const handleFileSelect = () => { @@ -189,6 +233,7 @@ const DxcMessageInput = ({ if (isRecording) { stopRecording(); + resetTranscript(); } else { baseTextRef.current = value ?? innerValue; startRecording(); @@ -204,35 +249,48 @@ const DxcMessageInput = ({ if (inputRef.current) { inputRef.current.style.height = "auto"; inputRef.current.style.height = `${inputRef.current.scrollHeight}px`; + + if (overlayRef.current) { + overlayRef.current.scrollTop = inputRef.current.scrollTop; + } } }, [transcript, changeValue]); useEffect(() => { - if (onRecordingChange) { - onRecordingChange(isRecording); - } + onRecordingChange?.(isRecording); }, [isRecording, onRecordingChange]); - const handleSubmit = async () => { - if (disabled || isGenerating) return; + useEffect(() => { + const textarea = inputRef.current; + const overlay = overlayRef.current; - const controller = new AbortController(); + if (!textarea || !overlay || !isRecording) return; - try { - const result = onButtonClick?.("submit", controller.signal); - if (result instanceof Promise) { - await result; - } - } catch (e) { - if ((e as DOMException).name !== "AbortError") { - throw e; - } - } + overlay.scrollTop = textarea.scrollTop; + + const handleScroll = () => { + overlay.scrollTop = textarea.scrollTop; + }; + + textarea.addEventListener("scroll", handleScroll); + return () => textarea.removeEventListener("scroll", handleScroll); + }, [isRecording]); + + const handleSubmit = () => { + if (disabled || isGenerating) return; + onButtonClick?.("submit"); }; const handleStop = () => { if (disabled) return; - void onButtonClick?.("stop"); + onButtonClick?.("stop"); + }; + + const handleInputOnKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => { + if (!disabled && !isGenerating && event.key === "Enter" && !event.shiftKey) { + event.preventDefault(); + handleSubmit(); + } }; return ( @@ -243,66 +301,86 @@ const DxcMessageInput = ({ focus={isFocused} onClick={handleInputContainerOnClick} onMouseDown={handleInputContainerOnMouseDown} - ref={inputContainerRef} > {files && ( - <DxcContainer overflow={{ x: "auto" }} width="100%"> - <DxcFlex alignItems="center" gap="var(--spacing-gap-xs)"> - <DxcDropdown - options={[{ label: "Attach documents", value: "fileorphoto" }]} - onSelectOption={handleFileSelect} - icon="add" - disabled={isGenerating || disabled} - caretHidden + <FilesContainer> + <DxcDropdown + options={[{ label: "Attach documents", value: "fileorphoto" }]} + onSelectOption={handleFileSelect} + icon="add" + disabled={isGenerating || disabled} + caretHidden + /> + <input ref={fileInputRef} type="file" hidden multiple onChange={handleFileInputOnChange} /> + {(files ?? []).map((item, index) => ( + <DxcChip + key={index} + label={item.label} + mode="dismissible" + prefix={item.icon ?? "description"} + onClick={() => removeItem(index)} /> - <input ref={fileInputRef} type="file" hidden multiple onChange={handleFileInputOnChange} /> - {(files ?? []).map((item, index) => ( - <DxcChip - key={index} - label={item.label} - mode="dismissible" - prefix={item.icon ?? "description"} - onClick={() => removeItem(index)} - /> - ))} - </DxcFlex> - </DxcContainer> + ))} + </FilesContainer> )} - <Input - aria-errormessage={error ? errorId : undefined} - aria-invalid={!!error} - disabled={isGenerating || disabled} - id={inputId} - onBlur={handleInputOnBlur} - onChange={handleInputOnChange} - onFocus={handleInputOnFocus} - onKeyDown={handleInputOnKeyDown} - onMouseDown={(event) => { - event.stopPropagation(); - }} - placeholder={placeholder} - ref={inputRef} - tabIndex={tabIndex} - maxLength={maxLength} - minLength={minLength} - value={value ?? innerValue} - /> + <InputWrapper> + <Input + aria-errormessage={error ? `error-${inputId}` : undefined} + aria-invalid={!!error} + disabled={isGenerating || disabled} + id={inputId} + isRecording={isRecording} + onBlur={handleInputOnBlur} + onChange={handleInputOnChange} + onFocus={handleInputOnFocus} + onKeyDown={handleInputOnKeyDown} + onMouseDown={(event) => { + event.stopPropagation(); + }} + placeholder={placeholder} + ref={inputRef} + tabIndex={tabIndex} + maxLength={maxLength} + minLength={minLength} + value={value ?? innerValue} + /> + {isRecording && ( + <TranscriptOverlay ref={overlayRef}> + {baseTextRef.current && <span>{baseTextRef.current}</span>} + {transcript && ( + <> + {baseTextRef.current && <span> </span>} + {transcript.length > 2 ? ( + <> + <span>{transcript.slice(0, -2)}</span> + <HighlightedText>{transcript.slice(-2)}...</HighlightedText> + </> + ) : ( + <HighlightedText>{transcript}</HighlightedText> + )} + </> + )} + </TranscriptOverlay> + )} + </InputWrapper> <DxcFlex justifyContent={modelList ? "space-between" : "flex-end"} alignItems="center"> {modelList && ( - <DxcSelect - // size="fitContent" - options={modelList.map((option) => ({ label: option.label ?? option.value, value: option.value }))} - disabled={isGenerating || disabled} - defaultValue={modelList[0]?.value ?? ""} - onChange={(val) => { - const selectedOption = modelList.find((option) => option.value === val.value); - selectedOption?.onSelect(); - }} - /> + <DxcContainer width="35%" maxWidth="240px"> + <DxcSelect + size="fillParent" + options={modelList.map((option) => ({ label: option.label ?? option.value, value: option.value }))} + disabled={isGenerating || disabled} + defaultValue={modelList[0]?.value} + onChange={(val) => { + const selectedOption = modelList.find((option) => option.value === val.value); + selectedOption?.onSelect(); + }} + /> + </DxcContainer> )} <DxcFlex gap="var(--spacing-gap-xs)"> - {allowVoiceInput && ( + {allowRecording && ( <DxcButton icon={isRecording ? "filled_pause" : "mic"} size={{ height: "medium" }} @@ -324,7 +402,7 @@ const DxcMessageInput = ({ </DxcFlex> </DxcFlex> </MessageInput> - {!disabled && typeof error === "string" && <ErrorMessage error={error} id={errorId} />} + {!disabled && typeof error === "string" && <ErrorMessage error={error} id={`error-${inputId}`} />} </MessageInputContainer> ); }; diff --git a/packages/lib/src/message-input/useVoiceTranscription.tsx b/packages/lib/src/message-input/useVoiceTranscription.tsx index bf8ca2d4b..1ecc5381e 100644 --- a/packages/lib/src/message-input/useVoiceTranscription.tsx +++ b/packages/lib/src/message-input/useVoiceTranscription.tsx @@ -59,7 +59,7 @@ interface UseVoiceTranscriptionReturn { } export const useVoiceTranscription = ({ - lang = "es-ES", + lang = "en-US", continuous = true, interimResults = true, }: UseVoiceTranscriptionOptions = {}): UseVoiceTranscriptionReturn => { @@ -80,7 +80,6 @@ export const useVoiceTranscription = ({ if (!SpeechRecognitionConstructor) return; const recognition = new SpeechRecognitionConstructor(); - recognition.lang = lang; recognition.continuous = continuous; recognition.interimResults = interimResults; From 030371c9e7d0649841a58d70cac87bf6cdf0119b Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Mon, 20 Jul 2026 12:29:45 +0200 Subject: [PATCH 11/22] Add localization --- packages/lib/src/common/variables.ts | 8 +++++ .../lib/src/message-input/MessageInput.tsx | 32 ++++++++++++++----- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/packages/lib/src/common/variables.ts b/packages/lib/src/common/variables.ts index f5a58b378..f1bc14d30 100644 --- a/packages/lib/src/common/variables.ts +++ b/packages/lib/src/common/variables.ts @@ -82,6 +82,14 @@ export const defaultTranslatedComponentLabels = { closeIcon: "Close menu", hamburgerTitle: "Menu", }, + messageInput: { + inputAriaLabel: "Message input", + sendButtonTitle: "Send message", + stopButtonTitle: "Stop request", + attachFileButtonTitle: "Attach file", + recordAudioButtonTitle: "Record audio", + stopRecordingButtonTitle: "Stop recording", + }, numberInput: { valueGreaterThanOrEqualToErrorMessage: (value: number) => `Value must be greater than or equal to ${value}.`, valueLessThanOrEqualToErrorMessage: (value: number) => `Value must be less than or equal to ${value}.`, diff --git a/packages/lib/src/message-input/MessageInput.tsx b/packages/lib/src/message-input/MessageInput.tsx index 60f95e33e..867ffb76b 100644 --- a/packages/lib/src/message-input/MessageInput.tsx +++ b/packages/lib/src/message-input/MessageInput.tsx @@ -151,21 +151,24 @@ const DxcMessageInput = ({ tabIndex, value, }: PromptInputPropsType) => { + const languageContext = useContext(HalstackLanguageContext); + const locale = languageContext.locale ? languageContext.locale : undefined; const inputId = `input-${useId()}`; - const translatedLabels = useContext(HalstackLanguageContext); const inputRef = useRef<HTMLTextAreaElement | null>(null); const overlayRef = useRef<HTMLDivElement | null>(null); const [innerValue, setInnerValue] = useState(defaultValue); const [isFocused, setIsFocused] = useState(false); const fileInputRef = useRef<HTMLInputElement | null>(null); - const { transcript, isRecording, startRecording, stopRecording, resetTranscript } = useVoiceTranscription(); + const { transcript, isRecording, startRecording, stopRecording, resetTranscript } = useVoiceTranscription({ + lang: locale ?? "en-US", + }); const changeValue = (newValue: string) => { if (value == null) { setInnerValue(newValue); } const lengthError = isLengthIncorrect(newValue, minLength, maxLength) - ? translatedLabels.formFields.lengthErrorMessage?.(minLength, maxLength) + ? languageContext.labels.formFields.lengthErrorMessage?.(minLength, maxLength) : undefined; onChange?.({ value: newValue, @@ -199,7 +202,7 @@ const DxcMessageInput = ({ const handleInputOnBlur = (event: FocusEvent<HTMLTextAreaElement>) => { setIsFocused(false); const lengthError = isLengthIncorrect(event.target.value, minLength, maxLength) - ? translatedLabels.formFields.lengthErrorMessage?.(minLength, maxLength) + ? languageContext.labels.formFields.lengthErrorMessage?.(minLength, maxLength) : undefined; onBlur?.({ value: event.target.value, @@ -305,7 +308,7 @@ const DxcMessageInput = ({ {files && ( <FilesContainer> <DxcDropdown - options={[{ label: "Attach documents", value: "fileorphoto" }]} + options={[{ label: languageContext.labels.messageInput.attachFileButtonTitle, value: "fileorphoto" }]} onSelectOption={handleFileSelect} icon="add" disabled={isGenerating || disabled} @@ -325,6 +328,7 @@ const DxcMessageInput = ({ )} <InputWrapper> <Input + aria-label={languageContext.labels.messageInput.inputAriaLabel} aria-errormessage={error ? `error-${inputId}` : undefined} aria-invalid={!!error} disabled={isGenerating || disabled} @@ -387,8 +391,16 @@ const DxcMessageInput = ({ mode="tertiary" disabled={isGenerating || disabled} onClick={toggleVoiceRecognition} - title={isRecording ? "Stop recording" : "Start voice input"} - aria-label={isRecording ? "Stop recording" : "Start voice input"} + title={ + isRecording + ? languageContext.labels.messageInput.stopRecordingButtonTitle + : languageContext.labels.messageInput.recordAudioButtonTitle + } + aria-label={ + isRecording + ? languageContext.labels.messageInput.stopRecordingButtonTitle + : languageContext.labels.messageInput.recordAudioButtonTitle + } /> )} @@ -397,7 +409,11 @@ const DxcMessageInput = ({ size={{ height: "medium" }} disabled={disabled} onClick={!isGenerating ? handleSubmit : handleStop} - title={!isGenerating ? "Submit" : "Stop"} + title={ + !isGenerating + ? languageContext.labels.messageInput.sendButtonTitle + : languageContext.labels.messageInput.stopButtonTitle + } /> </DxcFlex> </DxcFlex> From 188cc983ac89bd289bc2b948c8795aa2bba4d07b Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Mon, 20 Jul 2026 12:41:54 +0200 Subject: [PATCH 12/22] fix tests based on label changes --- .../src/message-input/MessageInput.test.tsx | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/lib/src/message-input/MessageInput.test.tsx b/packages/lib/src/message-input/MessageInput.test.tsx index 848478407..80bc1c0dc 100644 --- a/packages/lib/src/message-input/MessageInput.test.tsx +++ b/packages/lib/src/message-input/MessageInput.test.tsx @@ -95,8 +95,8 @@ describe("Message Input component tests", () => { test("calls onButtonClick when submit button is clicked", () => { const onButtonClick = jest.fn(); - const { getByLabelText } = render(<DxcMessageInput onButtonClick={onButtonClick} />); - const submitButton = getByLabelText("Submit"); + const { getByTitle } = render(<DxcMessageInput onButtonClick={onButtonClick} />); + const submitButton = getByTitle("Send message"); userEvent.click(submitButton); @@ -120,16 +120,16 @@ describe("Message Input component tests", () => { test("shows stop button when isGenerating is true", () => { const onButtonClick = jest.fn(); - const { getByLabelText } = render(<DxcMessageInput isGenerating onButtonClick={onButtonClick} />); - const stopButton = getByLabelText("Stop"); + const { getByTitle } = render(<DxcMessageInput isGenerating onButtonClick={onButtonClick} />); + const stopButton = getByTitle("Stop request"); expect(stopButton).toBeInTheDocument(); }); test("calls onButtonClick with 'stop' when stop button is clicked", () => { const onButtonClick = jest.fn(); - const { getByLabelText } = render(<DxcMessageInput isGenerating onButtonClick={onButtonClick} />); - const stopButton = getByLabelText("Stop"); + const { getByTitle } = render(<DxcMessageInput isGenerating onButtonClick={onButtonClick} />); + const stopButton = getByTitle("Stop request"); userEvent.click(stopButton); @@ -230,8 +230,8 @@ describe("Message Input component tests", () => { test("async onButtonClick is handled correctly", async () => { const onButtonClick = jest.fn(); - const { getByLabelText } = render(<DxcMessageInput onButtonClick={onButtonClick} />); - const submitButton = getByLabelText("Submit"); + const { getByTitle } = render(<DxcMessageInput onButtonClick={onButtonClick} />); + const submitButton = getByTitle("Send message"); userEvent.click(submitButton); @@ -243,8 +243,8 @@ describe("Message Input component tests", () => { test("does not call onButtonClick when disabled", () => { const onButtonClick = jest.fn(); - const { getByLabelText } = render(<DxcMessageInput disabled onButtonClick={onButtonClick} />); - const submitButton = getByLabelText("Submit"); + const { getByTitle } = render(<DxcMessageInput disabled onButtonClick={onButtonClick} />); + const submitButton = getByTitle("Send message"); userEvent.click(submitButton); @@ -253,10 +253,10 @@ describe("Message Input component tests", () => { test("shows stop button instead of submit when isGenerating", () => { const onButtonClick = jest.fn(); - const { getByLabelText } = render(<DxcMessageInput isGenerating onButtonClick={onButtonClick} />); + const { getByTitle } = render(<DxcMessageInput isGenerating onButtonClick={onButtonClick} />); // The button should be the stop button when isGenerating - const button = getByLabelText("Stop"); + const button = getByTitle("Stop request"); expect(button).toBeInTheDocument(); }); @@ -307,8 +307,8 @@ describe("Message Input component tests", () => { test("calls onButtonClick with submit when submit button is clicked", async () => { const onButtonClick = jest.fn(); - const { getByLabelText } = render(<DxcMessageInput onButtonClick={onButtonClick} />); - const submitButton = getByLabelText("Submit"); + const { getByTitle } = render(<DxcMessageInput onButtonClick={onButtonClick} />); + const submitButton = getByTitle("Send message"); userEvent.click(submitButton); From e24fa2628bd1fa00d7b8c550edc74d27a57ae157 Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Mon, 20 Jul 2026 13:05:01 +0200 Subject: [PATCH 13/22] Fix tests and improve code quality --- .../src/message-input/MessageInput.test.tsx | 28 +++---- .../lib/src/message-input/MessageInput.tsx | 83 +++++++------------ 2 files changed, 42 insertions(+), 69 deletions(-) diff --git a/packages/lib/src/message-input/MessageInput.test.tsx b/packages/lib/src/message-input/MessageInput.test.tsx index 80bc1c0dc..66613e4de 100644 --- a/packages/lib/src/message-input/MessageInput.test.tsx +++ b/packages/lib/src/message-input/MessageInput.test.tsx @@ -95,8 +95,8 @@ describe("Message Input component tests", () => { test("calls onButtonClick when submit button is clicked", () => { const onButtonClick = jest.fn(); - const { getByTitle } = render(<DxcMessageInput onButtonClick={onButtonClick} />); - const submitButton = getByTitle("Send message"); + const { getByLabelText } = render(<DxcMessageInput onButtonClick={onButtonClick} />); + const submitButton = getByLabelText("Send message"); userEvent.click(submitButton); @@ -120,16 +120,16 @@ describe("Message Input component tests", () => { test("shows stop button when isGenerating is true", () => { const onButtonClick = jest.fn(); - const { getByTitle } = render(<DxcMessageInput isGenerating onButtonClick={onButtonClick} />); - const stopButton = getByTitle("Stop request"); + const { getByLabelText } = render(<DxcMessageInput isGenerating onButtonClick={onButtonClick} />); + const stopButton = getByLabelText("Stop request"); expect(stopButton).toBeInTheDocument(); }); test("calls onButtonClick with 'stop' when stop button is clicked", () => { const onButtonClick = jest.fn(); - const { getByTitle } = render(<DxcMessageInput isGenerating onButtonClick={onButtonClick} />); - const stopButton = getByTitle("Stop request"); + const { getByLabelText } = render(<DxcMessageInput isGenerating onButtonClick={onButtonClick} />); + const stopButton = getByLabelText("Stop request"); userEvent.click(stopButton); @@ -230,8 +230,8 @@ describe("Message Input component tests", () => { test("async onButtonClick is handled correctly", async () => { const onButtonClick = jest.fn(); - const { getByTitle } = render(<DxcMessageInput onButtonClick={onButtonClick} />); - const submitButton = getByTitle("Send message"); + const { getByLabelText } = render(<DxcMessageInput onButtonClick={onButtonClick} />); + const submitButton = getByLabelText("Send message"); userEvent.click(submitButton); @@ -243,8 +243,8 @@ describe("Message Input component tests", () => { test("does not call onButtonClick when disabled", () => { const onButtonClick = jest.fn(); - const { getByTitle } = render(<DxcMessageInput disabled onButtonClick={onButtonClick} />); - const submitButton = getByTitle("Send message"); + const { getByLabelText } = render(<DxcMessageInput disabled onButtonClick={onButtonClick} />); + const submitButton = getByLabelText("Send message"); userEvent.click(submitButton); @@ -253,10 +253,10 @@ describe("Message Input component tests", () => { test("shows stop button instead of submit when isGenerating", () => { const onButtonClick = jest.fn(); - const { getByTitle } = render(<DxcMessageInput isGenerating onButtonClick={onButtonClick} />); + const { getByLabelText } = render(<DxcMessageInput isGenerating onButtonClick={onButtonClick} />); // The button should be the stop button when isGenerating - const button = getByTitle("Stop request"); + const button = getByLabelText("Stop request"); expect(button).toBeInTheDocument(); }); @@ -307,8 +307,8 @@ describe("Message Input component tests", () => { test("calls onButtonClick with submit when submit button is clicked", async () => { const onButtonClick = jest.fn(); - const { getByTitle } = render(<DxcMessageInput onButtonClick={onButtonClick} />); - const submitButton = getByTitle("Send message"); + const { getByLabelText } = render(<DxcMessageInput onButtonClick={onButtonClick} />); + const submitButton = getByLabelText("Send message"); userEvent.click(submitButton); diff --git a/packages/lib/src/message-input/MessageInput.tsx b/packages/lib/src/message-input/MessageInput.tsx index 867ffb76b..804765db2 100644 --- a/packages/lib/src/message-input/MessageInput.tsx +++ b/packages/lib/src/message-input/MessageInput.tsx @@ -163,27 +163,21 @@ const DxcMessageInput = ({ lang: locale ?? "en-US", }); - const changeValue = (newValue: string) => { - if (value == null) { - setInnerValue(newValue); - } - const lengthError = isLengthIncorrect(newValue, minLength, maxLength) + const getValidationError = (val: string) => + isLengthIncorrect(val, minLength, maxLength) ? languageContext.labels.formFields.lengthErrorMessage?.(minLength, maxLength) : undefined; - onChange?.({ - value: newValue, - ...(lengthError && { error: lengthError }), - }); - }; - const handleInputContainerOnClick = () => { - inputRef.current?.focus(); + const changeValue = (newValue: string) => { + if (value == null) setInnerValue(newValue); + const lengthError = getValidationError(newValue); + onChange?.({ value: newValue, ...(lengthError && { error: lengthError }) }); }; + + const handleInputContainerOnClick = () => inputRef.current?.focus(); + const handleInputContainerOnMouseDown = (event: MouseEvent<HTMLDivElement>) => { - // Avoid input to lose the focus when the container is pressed - if (document.activeElement === inputRef.current) { - event.preventDefault(); - } + if (document.activeElement === inputRef.current) event.preventDefault(); }; const handleInputOnChange = (event: ChangeEvent<HTMLTextAreaElement>) => { @@ -195,39 +189,26 @@ const DxcMessageInput = ({ changeValue(el.value); }; - const handleInputOnFocus = () => { - setIsFocused(true); - }; + const handleInputOnFocus = () => setIsFocused(true); const handleInputOnBlur = (event: FocusEvent<HTMLTextAreaElement>) => { setIsFocused(false); - const lengthError = isLengthIncorrect(event.target.value, minLength, maxLength) - ? languageContext.labels.formFields.lengthErrorMessage?.(minLength, maxLength) - : undefined; - onBlur?.({ - value: event.target.value, - ...(lengthError && { error: lengthError }), - }); + const lengthError = getValidationError(event.target.value); + onBlur?.({ value: event.target.value, ...(lengthError && { error: lengthError }) }); }; - const handleFileSelect = () => { - fileInputRef.current?.click(); - }; + const handleFileSelect = () => fileInputRef.current?.click(); const handleFileInputOnChange = (event: ChangeEvent<HTMLInputElement>) => { const selectedFiles = Array.from(event.target.files ?? []).map((file) => ({ label: file.name, icon: "insert_drive_file", })); - const nextFiles = [...(files ?? []), ...selectedFiles]; - callbackFile?.(nextFiles); + callbackFile?.([...(files ?? []), ...selectedFiles]); event.target.value = ""; }; - const removeItem = (itemIndex: number) => { - const nextFiles = (files ?? []).filter((_, index) => index !== itemIndex); - callbackFile?.(nextFiles); - }; + const removeItem = (itemIndex: number) => callbackFile?.((files ?? []).filter((_, index) => index !== itemIndex)); const baseTextRef = useRef(""); @@ -243,6 +224,7 @@ const DxcMessageInput = ({ } }; + // Handle transcript updates and scroll synchronization useEffect(() => { if (!transcript) return; @@ -252,42 +234,28 @@ const DxcMessageInput = ({ if (inputRef.current) { inputRef.current.style.height = "auto"; inputRef.current.style.height = `${inputRef.current.scrollHeight}px`; - - if (overlayRef.current) { - overlayRef.current.scrollTop = inputRef.current.scrollTop; - } + if (overlayRef.current) overlayRef.current.scrollTop = inputRef.current.scrollTop; } }, [transcript, changeValue]); + // Handle recording state changes and scroll sync useEffect(() => { onRecordingChange?.(isRecording); - }, [isRecording, onRecordingChange]); - useEffect(() => { const textarea = inputRef.current; const overlay = overlayRef.current; - if (!textarea || !overlay || !isRecording) return; overlay.scrollTop = textarea.scrollTop; - - const handleScroll = () => { - overlay.scrollTop = textarea.scrollTop; - }; + const handleScroll = () => (overlay.scrollTop = textarea.scrollTop); textarea.addEventListener("scroll", handleScroll); return () => textarea.removeEventListener("scroll", handleScroll); - }, [isRecording]); + }, [isRecording, onRecordingChange]); - const handleSubmit = () => { - if (disabled || isGenerating) return; - onButtonClick?.("submit"); - }; + const handleSubmit = () => !disabled && !isGenerating && onButtonClick?.("submit"); - const handleStop = () => { - if (disabled) return; - onButtonClick?.("stop"); - }; + const handleStop = () => !disabled && onButtonClick?.("stop"); const handleInputOnKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => { if (!disabled && !isGenerating && event.key === "Enter" && !event.shiftKey) { @@ -414,6 +382,11 @@ const DxcMessageInput = ({ ? languageContext.labels.messageInput.sendButtonTitle : languageContext.labels.messageInput.stopButtonTitle } + aria-label={ + !isGenerating + ? languageContext.labels.messageInput.sendButtonTitle + : languageContext.labels.messageInput.stopButtonTitle + } /> </DxcFlex> </DxcFlex> From 0063e89fa55c896ba16b7c5d4dd74a3f985384bf Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Mon, 20 Jul 2026 13:13:53 +0200 Subject: [PATCH 14/22] change allowVoiceInput in Code Page --- .../components/message-input/code/MessageInputCodePage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx b/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx index 9b7bc57a3..dc37013ea 100644 --- a/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx +++ b/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx @@ -35,7 +35,7 @@ const sections = [ </thead> <tbody> <tr> - <td>allowVoiceInput</td> + <td>allowRecording</td> <td> <TableCode>boolean</TableCode> </td> @@ -125,7 +125,7 @@ const sections = [ <TableCode>number</TableCode> </td> <td> - Specifies the minimum length allowed by the input. This will be checked both when the input element loses + Specifies the minimum length allowed by the input. This will be checked aboth when the input element loses the focus and while typing within it. If the string entered does not comply the minimum length, the onBlur and onChange functions will be called with the current value and an internal error informing that the value length does not comply the specified range. If a valid length is reached, the error parameter of From d33d9a3560b60653617a40b77d1c52032eae3e16 Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Mon, 20 Jul 2026 13:16:30 +0200 Subject: [PATCH 15/22] update advanced example --- .../message-input/code/examples/advanced.tsx | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/apps/website/screens/components/message-input/code/examples/advanced.tsx b/apps/website/screens/components/message-input/code/examples/advanced.tsx index bd17a4394..fc5d414ba 100644 --- a/apps/website/screens/components/message-input/code/examples/advanced.tsx +++ b/apps/website/screens/components/message-input/code/examples/advanced.tsx @@ -17,7 +17,6 @@ const code = `() => { }; const onBlur = ({ value, error }) => { - console.log("Input blurred with value:", value); if (error) { setError(error); } @@ -46,10 +45,6 @@ const code = `() => { setFiles(updatedFiles); }; - const onRecordingChange = (isRecording) => { - console.log("Recording state changed:", isRecording); - }; - const modelList = [ { label: "GPT-4", @@ -70,14 +65,9 @@ const code = `() => { onSelect: () => setSelectedModel("Claude") } ]; - - const toggleSize = () => { - // This would toggle between sizes in a real implementation - }; return ( <DxcInset space="var(--spacing-padding-xl)"> - <DxcFlex direction="column" gap="var(--spacing-gap-l)"> <DxcMessageInput placeholder="Ask me anything... (min 10, max 200 characters)" value={value} @@ -88,21 +78,12 @@ const code = `() => { callbackFile={callbackFile} modelList={modelList} allowVoiceInput - onRecordingChange={onRecordingChange} isGenerating={isGenerating} error={error} minLength={10} maxLength={200} size="large" /> - <div style={{ fontSize: "14px", color: "#666" }}> - <strong>Selected model:</strong> {selectedModel} - <br /> - <strong>Character count:</strong> {value.length} / 200 - <br /> - <strong>Attached files:</strong> {files.length} - </div> - </DxcFlex> </DxcInset> ); }`; From 372d468b5489d0d243aabe5da2ac47d582ca23c5 Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Tue, 21 Jul 2026 10:33:16 +0200 Subject: [PATCH 16/22] improve tests and types, and correct manage of min and maxLength errors. --- .../code/MessageInputCodePage.tsx | 11 +---- .../message-input/code/examples/advanced.tsx | 23 +++++----- .../message-input/MessageInput.stories.tsx | 39 ++++++++++++++-- .../src/message-input/MessageInput.test.tsx | 45 +++---------------- .../lib/src/message-input/MessageInput.tsx | 33 +++++++------- packages/lib/src/message-input/types.ts | 9 ++-- 6 files changed, 77 insertions(+), 83 deletions(-) diff --git a/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx b/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx index dc37013ea..a2df7009e 100644 --- a/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx +++ b/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx @@ -18,6 +18,7 @@ const modelListTypeString = `{ icon?: string | SVG; value: string; onSelect: () => void; + selected?: boolean; }[]`; const sections = [ @@ -138,7 +139,7 @@ const sections = [ <td> <ExtendedTableCode>{modelListTypeString}</ExtendedTableCode> </td> - <td>Options to be shown on the dropdown under the input. The first option will be selected by default.</td> + <td>Options to be shown on the dropdown under the input.</td> <td>-</td> </tr> <tr> @@ -177,14 +178,6 @@ const sections = [ </td> <td>-</td> </tr> - <tr> - <td>onRecordingChange</td> - <td> - <TableCode>{"(isRecording: boolean) => void"}</TableCode> - </td> - <td>This function will be called when the recording state changes (starts or stops).</td> - <td>-</td> - </tr> <tr> <td>placeholder</td> <td> diff --git a/apps/website/screens/components/message-input/code/examples/advanced.tsx b/apps/website/screens/components/message-input/code/examples/advanced.tsx index fc5d414ba..ff35ced8e 100644 --- a/apps/website/screens/components/message-input/code/examples/advanced.tsx +++ b/apps/website/screens/components/message-input/code/examples/advanced.tsx @@ -47,22 +47,25 @@ const code = `() => { const modelList = [ { - label: "GPT-4", + label: "MODEL-1.0", icon: "psychology", - value: "gpt4", - onSelect: () => setSelectedModel("GPT-4") + value: "model-1.0", + onSelect: () => setSelectedModel("model-1.0"), + selected: selectedModel === "model-1.0" }, { - label: "GPT-3.5", + label: "MODEL-3.5", icon: "smart_toy", - value: "gpt3.5", - onSelect: () => setSelectedModel("GPT-3.5") + value: "model-3.5", + onSelect: () => setSelectedModel("model-3.5"), + selected: selectedModel === "model-3.5" }, { - label: "Claude", + label: "MODEL-4+", icon: "lightbulb", - value: "claude", - onSelect: () => setSelectedModel("Claude") + value: "model-4+", + onSelect: () => setSelectedModel("model-4+"), + selected: selectedModel === "model-4+" } ]; @@ -77,7 +80,7 @@ const code = `() => { files={files} callbackFile={callbackFile} modelList={modelList} - allowVoiceInput + allowRecording isGenerating={isGenerating} error={error} minLength={10} diff --git a/packages/lib/src/message-input/MessageInput.stories.tsx b/packages/lib/src/message-input/MessageInput.stories.tsx index 773135d45..596fe147f 100644 --- a/packages/lib/src/message-input/MessageInput.stories.tsx +++ b/packages/lib/src/message-input/MessageInput.stories.tsx @@ -14,7 +14,7 @@ type Story = StoryObj<typeof DxcMessageInput>; const modelListOptions = [ { label: "GPT-4", value: "gpt4", onSelect: () => {} }, - { label: "Claude Sonnet", value: "claude", onSelect: () => {} }, + { label: "Claude Sonnet", value: "claude", onSelect: () => {}, selected: true }, { label: "Gemini Pro", value: "gemini", onSelect: () => {} }, { label: "LLaMA 3", value: "llama", onSelect: () => {} }, ]; @@ -161,14 +161,47 @@ const ControlledVoiceExample = () => { ); }; +const ControlledSelectedModelExample = () => { + const [model, setModel] = useState("claude"); + + return ( + <> + <Title title="Controlled Voice Recording" theme="light" level={2} /> + <ExampleContainer> + <DxcMessageInput + placeholder="Change model..." + modelList={[ + { + label: "Claude", + value: "claude", + onSelect: (value: string) => setModel(value), + selected: model === "claude", + }, + { + label: "GPT-3", + value: "gpt-3", + onSelect: (value: string) => setModel(value), + selected: model === "gpt-3", + }, + ]} + /> + </ExampleContainer> + </> + ); +}; + export const Chromatic: Story = { render: MessageInput, }; -export const ControlledFiles: Story = { +export const MessageInputWithFiles: Story = { render: ControlledFileExample, }; -export const ControlledVoice: Story = { +export const MessageInputWithVoiceRecording: Story = { render: ControlledVoiceExample, }; + +export const MessageInputWithSelectedModel: Story = { + render: ControlledSelectedModelExample, +}; diff --git a/packages/lib/src/message-input/MessageInput.test.tsx b/packages/lib/src/message-input/MessageInput.test.tsx index 66613e4de..97a73d774 100644 --- a/packages/lib/src/message-input/MessageInput.test.tsx +++ b/packages/lib/src/message-input/MessageInput.test.tsx @@ -1,5 +1,5 @@ import "@testing-library/jest-dom"; -import { render, waitFor } from "@testing-library/react"; +import { render } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import DxcMessageInput from "./MessageInput"; @@ -207,7 +207,7 @@ describe("Message Input component tests", () => { expect(select).toBeInTheDocument(); }); - test("calls onSelect when a bottom option is selected", async () => { + test("calls onSelect when a bottom option is selected", () => { const onSelect = jest.fn(); const modelList = [ { label: "Option 1", value: "option1", onSelect }, @@ -217,10 +217,11 @@ describe("Message Input component tests", () => { const select = getByRole("combobox"); userEvent.click(select); - const option1 = await waitFor(() => getByRole("option", { name: "Option 1" })); + const option1 = getByRole("option", { name: "Option 1" }); userEvent.click(option1); expect(onSelect).toHaveBeenCalledTimes(1); + expect(onSelect).toHaveBeenCalledWith("option1"); }); test("does not show error when disabled", () => { @@ -228,19 +229,6 @@ describe("Message Input component tests", () => { expect(queryByText("This is an error")).not.toBeInTheDocument(); }); - test("async onButtonClick is handled correctly", async () => { - const onButtonClick = jest.fn(); - const { getByLabelText } = render(<DxcMessageInput onButtonClick={onButtonClick} />); - const submitButton = getByLabelText("Send message"); - - userEvent.click(submitButton); - - await waitFor(() => { - expect(onButtonClick).toHaveBeenCalledWith("submit"); - expect(onButtonClick).toHaveBeenCalledTimes(1); - }); - }); - test("does not call onButtonClick when disabled", () => { const onButtonClick = jest.fn(); const { getByLabelText } = render(<DxcMessageInput disabled onButtonClick={onButtonClick} />); @@ -288,34 +276,13 @@ describe("Message Input component tests", () => { expect(callbackFile).toHaveBeenCalled(); }); - test("removes item when chip is clicked", () => { - const callbackFile = jest.fn(); - const files = [ - { label: "File 1", icon: "insert_drive_file" }, - { label: "File 2", icon: "insert_drive_file" }, - ]; - const { getByText } = render(<DxcMessageInput files={files} callbackFile={callbackFile} />); - - // Verify chips are rendered - expect(getByText("File 1")).toBeInTheDocument(); - expect(getByText("File 2")).toBeInTheDocument(); - - // Note: Testing the actual click on chip requires finding the dismissible button - // which is handled internally by DxcChip component - expect(callbackFile).not.toHaveBeenCalled(); - }); - - test("calls onButtonClick with submit when submit button is clicked", async () => { + test("calls onButtonClick with submit when submit button is clicked", () => { const onButtonClick = jest.fn(); const { getByLabelText } = render(<DxcMessageInput onButtonClick={onButtonClick} />); const submitButton = getByLabelText("Send message"); userEvent.click(submitButton); - - await waitFor(() => { - expect(onButtonClick).toHaveBeenCalledWith("submit"); - }); - + expect(onButtonClick).toHaveBeenCalledWith("submit"); expect(onButtonClick).toHaveBeenCalledTimes(1); }); diff --git a/packages/lib/src/message-input/MessageInput.tsx b/packages/lib/src/message-input/MessageInput.tsx index 804765db2..1625e18ef 100644 --- a/packages/lib/src/message-input/MessageInput.tsx +++ b/packages/lib/src/message-input/MessageInput.tsx @@ -145,7 +145,6 @@ const DxcMessageInput = ({ onBlur, onButtonClick, onChange, - onRecordingChange, placeholder = "", size = "medium", tabIndex, @@ -163,15 +162,15 @@ const DxcMessageInput = ({ lang: locale ?? "en-US", }); - const getValidationError = (val: string) => - isLengthIncorrect(val, minLength, maxLength) - ? languageContext.labels.formFields.lengthErrorMessage?.(minLength, maxLength) - : undefined; - const changeValue = (newValue: string) => { if (value == null) setInnerValue(newValue); - const lengthError = getValidationError(newValue); - onChange?.({ value: newValue, ...(lengthError && { error: lengthError }) }); + + if (isLengthIncorrect(newValue, minLength, maxLength)) { + onChange?.({ + value: newValue, + error: languageContext.labels.formFields.lengthErrorMessage?.(minLength, maxLength), + }); + } else onChange?.({ value: newValue }); }; const handleInputContainerOnClick = () => inputRef.current?.focus(); @@ -193,8 +192,13 @@ const DxcMessageInput = ({ const handleInputOnBlur = (event: FocusEvent<HTMLTextAreaElement>) => { setIsFocused(false); - const lengthError = getValidationError(event.target.value); - onBlur?.({ value: event.target.value, ...(lengthError && { error: lengthError }) }); + + if (isLengthIncorrect(event.target.value, minLength, maxLength)) { + onBlur?.({ + value: event.target.value, + error: languageContext.labels.formFields.lengthErrorMessage?.(minLength, maxLength), + }); + } else onBlur?.({ value: event.target.value }); }; const handleFileSelect = () => fileInputRef.current?.click(); @@ -238,10 +242,7 @@ const DxcMessageInput = ({ } }, [transcript, changeValue]); - // Handle recording state changes and scroll sync useEffect(() => { - onRecordingChange?.(isRecording); - const textarea = inputRef.current; const overlay = overlayRef.current; if (!textarea || !overlay || !isRecording) return; @@ -251,7 +252,7 @@ const DxcMessageInput = ({ textarea.addEventListener("scroll", handleScroll); return () => textarea.removeEventListener("scroll", handleScroll); - }, [isRecording, onRecordingChange]); + }, [isRecording]); const handleSubmit = () => !disabled && !isGenerating && onButtonClick?.("submit"); @@ -342,10 +343,10 @@ const DxcMessageInput = ({ size="fillParent" options={modelList.map((option) => ({ label: option.label ?? option.value, value: option.value }))} disabled={isGenerating || disabled} - defaultValue={modelList[0]?.value} + value={modelList.find((option) => option.selected)?.value || modelList[0]?.value} onChange={(val) => { const selectedOption = modelList.find((option) => option.value === val.value); - selectedOption?.onSelect(); + selectedOption?.onSelect(val.value); }} /> </DxcContainer> diff --git a/packages/lib/src/message-input/types.ts b/packages/lib/src/message-input/types.ts index a4da3d616..105a13514 100644 --- a/packages/lib/src/message-input/types.ts +++ b/packages/lib/src/message-input/types.ts @@ -64,13 +64,14 @@ type Props = { */ minLength?: number; /** - * Options to be shown on the dropdown under the input. The first option will be selected by default. + * Options to be shown on the dropdown under the input. */ modelList?: { label?: string; icon?: string | (React.ReactNode & React.SVGProps<SVGSVGElement>); value: string; - onSelect: () => void; + onSelect: (value: string) => void; + selected?: boolean; }[]; /** * This function will be called when the input element loses the focus. @@ -92,10 +93,6 @@ type Props = { * For submit events, an AbortSignal is provided to allow cancellation. */ onButtonClick?: (type: "submit" | "stop") => void; - /** - * This function will be called when the recording state changes (starts or stops). - */ - onRecordingChange?: (isRecording: boolean) => void; /** * Text to be put as placeholder of the input. */ From 2c85af6b3a1dcc7410d23052143f86db8e54d07f Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Tue, 21 Jul 2026 10:34:13 +0200 Subject: [PATCH 17/22] Remove unused functions in utils --- packages/lib/src/message-input/utils.ts | 51 ------------------------- 1 file changed, 51 deletions(-) diff --git a/packages/lib/src/message-input/utils.ts b/packages/lib/src/message-input/utils.ts index fbc23976a..c869ade61 100644 --- a/packages/lib/src/message-input/utils.ts +++ b/packages/lib/src/message-input/utils.ts @@ -1,36 +1,6 @@ import MessageInputPropsType from "./types"; import { css } from "@emotion/react"; -export const makeCancelable = (promise: Promise<string[]>) => { - let hasCanceled_ = false; - const wrappedPromise = new Promise<string[]>((resolve, reject) => { - promise.then( - (val) => { - if (hasCanceled_) { - reject(new Error("Is canceled")); - } else { - resolve(val); - } - }, - (promiseError) => { - if (hasCanceled_) { - reject(new Error("Is canceled")); - } else if (promiseError instanceof Error) { - reject(promiseError); - } else { - reject(new Error(String(promiseError))); - } - } - ); - }); - return { - promise: wrappedPromise, - cancel() { - hasCanceled_ = true; - }, - }; -}; - export const isLengthIncorrect = ( value: string, minLength: MessageInputPropsType["minLength"], @@ -38,27 +8,6 @@ export const isLengthIncorrect = ( ) => value != null && ((minLength != null && value.length < minLength) || (maxLength != null && value.length > maxLength)); -export const isNumberIncorrect = ( - value: number, - minNumber: MessageInputPropsType["minLength"], - maxNumber: MessageInputPropsType["maxLength"] -) => (minNumber != null && value < minNumber) || (maxNumber != null && value > maxNumber); - -export const transformSpecialChars = (str: string) => { - const specialCharsRegex = /[\\*()[\]{}+?/]/; - let value = str; - if (specialCharsRegex.test(value)) { - const regexAsString = specialCharsRegex.toString().split(""); - const uniqueSpecialChars = regexAsString.filter((item, index) => regexAsString.indexOf(item) === index); - uniqueSpecialChars.forEach((specialChar) => { - if (str.includes(specialChar)) { - value = value.replace(specialChar, `\\${specialChar}`); - } - }); - } - return value; -}; - export const inputStylesByStatePromptInput = (disabled: boolean, error: boolean, focus?: boolean) => css` background-color: ${disabled ? `var(--color-bg-neutral-lightest)` : `transparent`}; From 7c46ed7eb77e233b3cf4b1436ab882c0ad2e6dab Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Tue, 21 Jul 2026 11:57:55 +0200 Subject: [PATCH 18/22] update minLength and maxLenght error behaviour within current formFields error message --- .../lib/src/message-input/MessageInput.test.tsx | 14 +++++++++----- packages/lib/src/message-input/MessageInput.tsx | 10 +++++++--- packages/lib/src/message-input/utils.ts | 8 -------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/lib/src/message-input/MessageInput.test.tsx b/packages/lib/src/message-input/MessageInput.test.tsx index 97a73d774..6368867ca 100644 --- a/packages/lib/src/message-input/MessageInput.test.tsx +++ b/packages/lib/src/message-input/MessageInput.test.tsx @@ -139,7 +139,8 @@ describe("Message Input component tests", () => { test("validates minLength and calls onChange with error", () => { const onChange = jest.fn(); - const { getByRole } = render(<DxcMessageInput minLength={5} onChange={onChange} />); + // TO BE DONE: Remove maxLength when formFields message errors is updated to support minLength and maxLength separately. + const { getByRole } = render(<DxcMessageInput minLength={5} maxLength={200} onChange={onChange} />); const input = getByRole("textbox"); userEvent.type(input, "Hi"); @@ -161,7 +162,9 @@ describe("Message Input component tests", () => { test("validates minLength on blur", () => { const onBlur = jest.fn(); - const { getByRole } = render(<DxcMessageInput minLength={5} onBlur={onBlur} />); + // TO BE DONE: Remove maxLength when formFields message errors is updated to support minLength and maxLength separately. + + const { getByRole } = render(<DxcMessageInput minLength={5} maxLength={200} onBlur={onBlur} />); const input = getByRole("textbox"); userEvent.click(input); @@ -288,12 +291,13 @@ describe("Message Input component tests", () => { test("validates minLength correctly when value is exactly minLength", () => { const onChange = jest.fn(); - const { getByRole } = render(<DxcMessageInput minLength={5} onChange={onChange} />); + const { getByRole } = render(<DxcMessageInput minLength={5} maxLength={100} onChange={onChange} />); const input = getByRole("textbox"); - userEvent.type(input, "Hello"); + userEvent.type(input, "Hell"); + expect(onChange).toHaveBeenLastCalledWith({ value: "Hell", error: "Min length 5, max length 100." }); - // When exactly at minLength, should not have error + userEvent.type(input, "o"); expect(onChange).toHaveBeenLastCalledWith({ value: "Hello" }); }); diff --git a/packages/lib/src/message-input/MessageInput.tsx b/packages/lib/src/message-input/MessageInput.tsx index 1625e18ef..092ec7db7 100644 --- a/packages/lib/src/message-input/MessageInput.tsx +++ b/packages/lib/src/message-input/MessageInput.tsx @@ -12,7 +12,7 @@ import { import styled from "@emotion/styled"; import scrollbarStyles from "../styles/scroll"; import PromptInputPropsType from "./types"; -import { inputStylesByStatePromptInput, isLengthIncorrect } from "./utils"; +import { inputStylesByStatePromptInput } from "./utils"; import DxcButton from "../button/Button"; import DxcChip from "../chip/Chip"; import DxcContainer from "../container/Container"; @@ -162,10 +162,14 @@ const DxcMessageInput = ({ lang: locale ?? "en-US", }); + // TO BE DONE: IMPLEMENT SEPARATELY minLenght & maxLength + const isLengthOutOfRange = (value: string) => + value !== "" && minLength && maxLength && (value.length < minLength || value.length > maxLength); + const changeValue = (newValue: string) => { if (value == null) setInnerValue(newValue); - if (isLengthIncorrect(newValue, minLength, maxLength)) { + if (isLengthOutOfRange(newValue)) { onChange?.({ value: newValue, error: languageContext.labels.formFields.lengthErrorMessage?.(minLength, maxLength), @@ -193,7 +197,7 @@ const DxcMessageInput = ({ const handleInputOnBlur = (event: FocusEvent<HTMLTextAreaElement>) => { setIsFocused(false); - if (isLengthIncorrect(event.target.value, minLength, maxLength)) { + if (isLengthOutOfRange(event.target.value)) { onBlur?.({ value: event.target.value, error: languageContext.labels.formFields.lengthErrorMessage?.(minLength, maxLength), diff --git a/packages/lib/src/message-input/utils.ts b/packages/lib/src/message-input/utils.ts index c869ade61..c8fea892c 100644 --- a/packages/lib/src/message-input/utils.ts +++ b/packages/lib/src/message-input/utils.ts @@ -1,13 +1,5 @@ -import MessageInputPropsType from "./types"; import { css } from "@emotion/react"; -export const isLengthIncorrect = ( - value: string, - minLength: MessageInputPropsType["minLength"], - maxLength: MessageInputPropsType["maxLength"] -) => - value != null && ((minLength != null && value.length < minLength) || (maxLength != null && value.length > maxLength)); - export const inputStylesByStatePromptInput = (disabled: boolean, error: boolean, focus?: boolean) => css` background-color: ${disabled ? `var(--color-bg-neutral-lightest)` : `transparent`}; From ab69ce8666c89912ee0ed04692d95113d1c2541d Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Thu, 23 Jul 2026 08:45:13 +0200 Subject: [PATCH 19/22] fix based on comments --- .../code/MessageInputCodePage.tsx | 6 +- .../message-input/MessageInput.stories.tsx | 150 ++++----------- .../src/message-input/MessageInput.test.tsx | 175 +++++++++++++++++- .../lib/src/message-input/MessageInput.tsx | 119 ++++++------ packages/lib/src/message-input/types.ts | 24 +-- .../message-input/useVoiceTranscription.tsx | 73 +++++--- packages/lib/src/message-input/utils.ts | 4 + packages/lib/src/select/utils.ts | 2 +- 8 files changed, 321 insertions(+), 232 deletions(-) diff --git a/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx b/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx index a2df7009e..e7246d6de 100644 --- a/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx +++ b/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx @@ -13,7 +13,7 @@ const fileDataTypeString = `{ };`; const callbackFileTypeString = "(files: FileData[]) => void"; const filesTypeString = `FileData[] | []`; -const modelListTypeString = `{ +const selectOptionsTypeString = `{ label?: string; icon?: string | SVG; value: string; @@ -135,9 +135,9 @@ const sections = [ <td>-</td> </tr> <tr> - <td>modelList</td> + <td>selectOptions</td> <td> - <ExtendedTableCode>{modelListTypeString}</ExtendedTableCode> + <ExtendedTableCode>{selectOptionsTypeString}</ExtendedTableCode> </td> <td>Options to be shown on the dropdown under the input.</td> <td>-</td> diff --git a/packages/lib/src/message-input/MessageInput.stories.tsx b/packages/lib/src/message-input/MessageInput.stories.tsx index 596fe147f..a6570b9bd 100644 --- a/packages/lib/src/message-input/MessageInput.stories.tsx +++ b/packages/lib/src/message-input/MessageInput.stories.tsx @@ -1,9 +1,7 @@ import { Meta, StoryObj } from "@storybook/react-vite"; -import { useState } from "react"; import DxcMessageInput from "./MessageInput"; import Title from "../../.storybook/components/Title"; import ExampleContainer from "../../.storybook/components/ExampleContainer"; -import type { FileData } from "./types"; export default { title: "Message Input", @@ -12,91 +10,103 @@ export default { type Story = StoryObj<typeof DxcMessageInput>; -const modelListOptions = [ +const selectOptionsOptions = [ { label: "GPT-4", value: "gpt4", onSelect: () => {} }, { label: "Claude Sonnet", value: "claude", onSelect: () => {}, selected: true }, { label: "Gemini Pro", value: "gemini", onSelect: () => {} }, { label: "LLaMA 3", value: "llama", onSelect: () => {} }, ]; +const files = [ + { label: "document.pdf" }, + { label: "document.pdf" }, + { label: "document.pdf" }, + { label: "document.pdf" }, +]; + const MessageInput = () => ( <> <Title title="Size" theme="light" level={2} /> <ExampleContainer> <Title title="Small" level={4} /> - <DxcMessageInput modelList={modelListOptions} size="small" placeholder="Ask me anything..." files={[]} /> + <DxcMessageInput selectOptions={selectOptionsOptions} size="small" placeholder="Ask me anything..." /> </ExampleContainer> <ExampleContainer> <Title title="Medium (default)" level={4} /> - <DxcMessageInput modelList={modelListOptions} size="medium" placeholder="Ask me anything..." files={[]} /> + <DxcMessageInput selectOptions={selectOptionsOptions} size="medium" placeholder="Ask me anything..." /> </ExampleContainer> <ExampleContainer> <Title title="Large" level={4} /> - <DxcMessageInput modelList={modelListOptions} size="large" placeholder="Ask me anything..." files={[]} /> + <DxcMessageInput selectOptions={selectOptionsOptions} size="large" placeholder="Ask me anything..." /> </ExampleContainer> <ExampleContainer> <Title title="Fill Parent" level={4} /> - <DxcMessageInput modelList={modelListOptions} size="fillParent" placeholder="Ask me anything..." files={[]} /> + <DxcMessageInput selectOptions={selectOptionsOptions} size="fillParent" placeholder="Ask me anything..." /> </ExampleContainer> <ExampleContainer> <Title title="With Voice Input" level={2} /> - <DxcMessageInput placeholder="Ask me anything with voice..." allowRecording files={[]} /> + <DxcMessageInput placeholder="Ask me anything with voice..." allowRecording /> </ExampleContainer> <ExampleContainer> <Title title="With Model List" level={2} /> - <DxcMessageInput placeholder="Select a model and ask..." modelList={modelListOptions} files={[]} /> + <DxcMessageInput placeholder="Select a model and ask..." selectOptions={selectOptionsOptions} /> </ExampleContainer> <ExampleContainer> - <Title title="With All Features" level={2} /> - <DxcMessageInput placeholder="Full featured input..." allowRecording modelList={modelListOptions} files={[]} /> + <Title title="With Files" level={2} /> + <DxcMessageInput + placeholder="Full featured input..." + allowRecording + selectOptions={selectOptionsOptions} + files={files} + callbackFile={() => console.log("added file")} + /> </ExampleContainer> {/* ================== STATES ================== */} <Title title="States" theme="light" level={2} /> <ExampleContainer> <Title title="Default" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." files={[]} /> + <DxcMessageInput placeholder="Ask me anything..." /> </ExampleContainer> <ExampleContainer pseudoState="pseudo-hover"> <Title title="Hover" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." files={[]} /> + <DxcMessageInput placeholder="Ask me anything..." /> </ExampleContainer> <ExampleContainer pseudoState="pseudo-focus"> <Title title="Focused" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." files={[]} /> + <DxcMessageInput placeholder="Ask me anything..." /> </ExampleContainer> <ExampleContainer pseudoState={["pseudo-focus", "pseudo-hover"]}> <Title title="Active" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." files={[]} /> + <DxcMessageInput placeholder="Ask me anything..." /> </ExampleContainer> <ExampleContainer> <Title title="Disabled" level={4} /> - <DxcMessageInput placeholder="This input is disabled..." disabled files={[]} /> + <DxcMessageInput placeholder="This input is disabled..." disabled /> </ExampleContainer> <ExampleContainer> <Title title="Is Generating" level={4} /> - <DxcMessageInput placeholder="AI is generating response..." isGenerating files={[]} /> + <DxcMessageInput placeholder="AI is generating response..." isGenerating /> </ExampleContainer> <ExampleContainer> <Title title="Error" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." files={[]} error="Please enter a valid message" /> + <DxcMessageInput placeholder="Ask me anything..." error="Please enter a valid message" /> </ExampleContainer> <ExampleContainer pseudoState="pseudo-hover"> <Title title="Hover error" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." files={[]} error="Error Message" /> + <DxcMessageInput placeholder="Ask me anything..." error="Error Message" /> </ExampleContainer> <Title title="Validation" theme="light" level={2} /> <ExampleContainer> <Title title="Min Length (15 characters)" level={4} /> - <DxcMessageInput placeholder="Ask me anything..." files={[]} minLength={15} defaultValue="Hello" /> + <DxcMessageInput placeholder="Ask me anything..." minLength={15} defaultValue="Hello" /> </ExampleContainer> <ExampleContainer> <Title title="Max Length (50 characters)" level={4} /> <DxcMessageInput placeholder="Ask me anything..." - files={[]} maxLength={50} defaultValue="This is a test message to demonstrate the maximum length validation feature" /> @@ -104,104 +114,6 @@ const MessageInput = () => ( </> ); -const ControlledFileExample = () => { - const [files, setFiles] = useState<FileData[]>([]); - - const handleButtonClick = (type: "submit" | "stop", _signal?: AbortSignal) => { - if (type === "submit") { - console.log( - "Submitted files:", - files.map((file: FileData) => file.label) - ); - } else { - console.log("Stop action triggered"); - } - }; - - const callbackFiles = (updatedFiles: FileData[]) => { - setFiles(updatedFiles); - }; - - return ( - <> - <Title title="Controlled Files" theme="light" level={2} /> - <ExampleContainer> - <DxcMessageInput - placeholder="Attach files and submit..." - files={files} - callbackFile={callbackFiles} - onButtonClick={handleButtonClick} - /> - </ExampleContainer> - </> - ); -}; - -const ControlledVoiceExample = () => { - const [inputValue, setInputValue] = useState(""); - - const handleChange = (val: { value: string; error?: string }) => { - setInputValue(val.value); - console.log("Value changed:", val); - }; - - return ( - <> - <Title title="Controlled Voice Recording" theme="light" level={2} /> - <ExampleContainer> - <DxcMessageInput - placeholder="Use voice input..." - files={[]} - allowRecording - value={inputValue} - onChange={handleChange} - /> - </ExampleContainer> - </> - ); -}; - -const ControlledSelectedModelExample = () => { - const [model, setModel] = useState("claude"); - - return ( - <> - <Title title="Controlled Voice Recording" theme="light" level={2} /> - <ExampleContainer> - <DxcMessageInput - placeholder="Change model..." - modelList={[ - { - label: "Claude", - value: "claude", - onSelect: (value: string) => setModel(value), - selected: model === "claude", - }, - { - label: "GPT-3", - value: "gpt-3", - onSelect: (value: string) => setModel(value), - selected: model === "gpt-3", - }, - ]} - /> - </ExampleContainer> - </> - ); -}; - export const Chromatic: Story = { render: MessageInput, }; - -export const MessageInputWithFiles: Story = { - render: ControlledFileExample, -}; - -export const MessageInputWithVoiceRecording: Story = { - render: ControlledVoiceExample, -}; - -export const MessageInputWithSelectedModel: Story = { - render: ControlledSelectedModelExample, -}; diff --git a/packages/lib/src/message-input/MessageInput.test.tsx b/packages/lib/src/message-input/MessageInput.test.tsx index 6368867ca..7be905446 100644 --- a/packages/lib/src/message-input/MessageInput.test.tsx +++ b/packages/lib/src/message-input/MessageInput.test.tsx @@ -1,7 +1,17 @@ import "@testing-library/jest-dom"; -import { render } from "@testing-library/react"; +import { act, render, renderHook } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import DxcMessageInput from "./MessageInput"; +import { + ISpeechRecognition, + SpeechRecognitionAlternative, + SpeechRecognitionErrorEvent, + SpeechRecognitionEvent, + SpeechRecognitionResultItem, + SpeechRecognitionResultList, + useVoiceTranscription, + WindowWithSpeechRecognition, +} from "./useVoiceTranscription"; // Mock ResizeObserver global.ResizeObserver = jest.fn().mockImplementation(() => ({ @@ -193,18 +203,18 @@ describe("Message Input component tests", () => { }); test("renders file upload dropdown when files prop is provided", () => { - const { getByRole } = render(<DxcMessageInput files={[]} />); + const { getByRole } = render(<DxcMessageInput files={[]} callbackFile={() => console.log("")} />); const dropdown = getByRole("button", { name: "Show options" }); expect(dropdown).toBeInTheDocument(); }); - test("renders bottom select when modelList is provided", () => { - const modelList = [ + test("renders bottom select when selectOptions is provided", () => { + const selectOptions = [ { label: "Option 1", value: "option1", onSelect: jest.fn() }, { label: "Option 2", value: "option2", onSelect: jest.fn() }, ]; - const { getByRole } = render(<DxcMessageInput modelList={modelList} />); + const { getByRole } = render(<DxcMessageInput selectOptions={selectOptions} />); const select = getByRole("combobox"); expect(select).toBeInTheDocument(); @@ -212,11 +222,11 @@ describe("Message Input component tests", () => { test("calls onSelect when a bottom option is selected", () => { const onSelect = jest.fn(); - const modelList = [ + const selectOptions = [ { label: "Option 1", value: "option1", onSelect }, { label: "Option 2", value: "option2", onSelect: jest.fn() }, ]; - const { getByRole } = render(<DxcMessageInput modelList={modelList} />); + const { getByRole } = render(<DxcMessageInput selectOptions={selectOptions} />); const select = getByRole("combobox"); userEvent.click(select); @@ -252,7 +262,7 @@ describe("Message Input component tests", () => { }); test("handles file selection", () => { - const { getByRole } = render(<DxcMessageInput files={[]} />); + const { getByRole } = render(<DxcMessageInput files={[]} callbackFile={() => console.log("")} />); const dropdown = getByRole("button", { name: "Show options" }); // Simulate selecting the option @@ -318,3 +328,152 @@ describe("Message Input component tests", () => { expect(callbackFile).toHaveBeenCalled(); }); }); + +// Mock Speech Recognition +class MockSpeechRecognition extends EventTarget implements ISpeechRecognition { + lang = ""; + continuous = false; + interimResults = false; + onresult: ((event: SpeechRecognitionEvent) => void) | null = null; + onerror: ((event: SpeechRecognitionErrorEvent) => void) | null = null; + onend: (() => void) | null = null; + + start = jest.fn(); + stop = jest.fn(() => { + this.onend?.(); + }); + + emitResult(transcript: string, confidence: number) { + const alternative: SpeechRecognitionAlternative = { transcript, confidence }; + const resultItem: SpeechRecognitionResultItem = { length: 1, 0: alternative }; + const results: SpeechRecognitionResultList = { length: 1, 0: resultItem }; + + this.onresult?.(Object.assign(new Event("result"), { results })); + } + + emitEnd() { + this.onend?.(); + } + + emitError(error: string) { + this.onerror?.(Object.assign(new Event("error"), { error })); + } +} + +describe("useVoiceTranscription", () => { + let mockInstance: MockSpeechRecognition; + + beforeEach(() => { + mockInstance = new MockSpeechRecognition(); + (window as WindowWithSpeechRecognition).SpeechRecognition = jest.fn(() => mockInstance); + }); + + afterEach(() => { + delete (window as WindowWithSpeechRecognition).SpeechRecognition; + jest.clearAllMocks(); + }); + + test("detects support when SpeechRecognition exists", () => { + const { result } = renderHook(() => useVoiceTranscription({ lang: "es-ES" })); + expect(result.current.isSupported).toBe(true); + }); + + test("starts recording and sets isRecording to true", () => { + const { result } = renderHook(() => useVoiceTranscription({ lang: "es-ES" })); + + act(() => { + result.current.startRecording(); + }); + + expect(mockInstance.start).toHaveBeenCalled(); + expect(result.current.isRecording).toBe(true); + }); + + test("updates transcript when confidence is high enough", () => { + const { result } = renderHook(() => useVoiceTranscription({ lang: "es-ES" })); + + act(() => { + result.current.startRecording(); + }); + + act(() => { + mockInstance.emitResult("hola mundo", 0.8); + }); + + expect(result.current.transcript).toBe("hola mundo"); + }); + + test("ignores transcript when confidence is below 0.5", () => { + const { result } = renderHook(() => useVoiceTranscription({ lang: "es-ES" })); + + act(() => { + result.current.startRecording(); + }); + + act(() => { + mockInstance.emitResult("texto poco fiable", 0.2); + }); + + expect(result.current.transcript).toBe(""); + }); + + test("resets transcript and isRecording when recognition ends automatically", () => { + const { result } = renderHook(() => useVoiceTranscription({ lang: "es-ES" })); + + act(() => { + result.current.startRecording(); + }); + + act(() => { + mockInstance.emitResult("hola", 0.9); + }); + + expect(result.current.transcript).toBe("hola"); + + act(() => { + mockInstance.emitEnd(); + }); + + expect(result.current.isRecording).toBe(false); + expect(result.current.transcript).toBe(""); + }); + + test("resets transcript on error", () => { + const { result } = renderHook(() => useVoiceTranscription({ lang: "es-ES" })); + + act(() => { + result.current.startRecording(); + }); + + act(() => { + mockInstance.emitResult("hola", 0.9); + }); + + act(() => { + mockInstance.emitError("no-speech"); + }); + + expect(result.current.isRecording).toBe(false); + expect(result.current.transcript).toBe(""); + }); + + test("stopRecording calls recognition.stop", () => { + const { result } = renderHook(() => useVoiceTranscription({ lang: "es-ES" })); + + act(() => { + result.current.startRecording(); + }); + + act(() => { + result.current.stopRecording(); + }); + + expect(mockInstance.stop).toHaveBeenCalled(); + }); + + test("isSupported is false when SpeechRecognition is not available", () => { + delete (window as WindowWithSpeechRecognition).SpeechRecognition; + const { result } = renderHook(() => useVoiceTranscription({ lang: "es-ES" })); + expect(result.current.isSupported).toBe(false); + }); +}); diff --git a/packages/lib/src/message-input/MessageInput.tsx b/packages/lib/src/message-input/MessageInput.tsx index 092ec7db7..2c568c4b1 100644 --- a/packages/lib/src/message-input/MessageInput.tsx +++ b/packages/lib/src/message-input/MessageInput.tsx @@ -12,7 +12,7 @@ import { import styled from "@emotion/styled"; import scrollbarStyles from "../styles/scroll"; import PromptInputPropsType from "./types"; -import { inputStylesByStatePromptInput } from "./utils"; +import { inputStylesByStatePromptInput, isLengthOutOfRange } from "./utils"; import DxcButton from "../button/Button"; import DxcChip from "../chip/Chip"; import DxcContainer from "../container/Container"; @@ -31,28 +31,29 @@ const sizes = { } as const; const MessageInputContainer = styled.div<{ size: PromptInputPropsType["size"] }>` - box-sizing: border-box; + width: ${({ size = "medium" }) => sizes[size]}; display: flex; flex-direction: column; gap: var(--spacing-gap-xs); - max-height: 320px; - background-color: var(--color-bg-neutral-lightest); - box-shadow: 0 -24px 10px 4px rgba(255, 255, 255, 0.6); - width: ${({ size = "medium" }) => sizes[size]}; `; const MessageInput = styled.div<{ disabled: Required<PromptInputPropsType>["disabled"]; error: boolean; focus: boolean; + hasFiles?: boolean; }>` + width: 100%; + max-height: 320px; + box-sizing: border-box; position: relative; - display: flex; - flex-direction: column; - justify-content: center; + display: grid; + grid-template-rows: ${({ hasFiles }) => + hasFiles ? "minmax(36px, 40px) minmax(0, 1fr) 34px" : "minmax(0, 1fr) 34px"}; gap: var(--spacing-gap-s); - height: 100%; padding: var(--spacing-padding-m) var(--spacing-padding-xs); + background-color: var(--color-bg-neutral-lightest); + box-shadow: 0 -24px 10px 4px rgba(255, 255, 255, 0.6); ${({ disabled, error, focus }) => inputStylesByStatePromptInput(disabled, error, focus)} overflow: hidden; `; @@ -73,15 +74,15 @@ const FilesContainer = styled.div` } `; -const Input = styled.textarea<{ isRecording?: boolean }>` +const MessageArea = styled.textarea<{ isRecording?: boolean }>` min-height: 20px; - max-width: 100%; + width: 100%; background: none; border: none; outline: none; padding: var(--spacing-padding-none) var(--spacing-padding-xs); + field-sizing: content; resize: none; - flex-grow: 1; color: ${({ disabled, isRecording }) => isRecording ? "transparent" : disabled ? "var(--color-fg-neutral-medium)" : "var(--color-fg-neutral-dark)"}; font-family: var(--typography-font-family); @@ -90,6 +91,7 @@ const Input = styled.textarea<{ isRecording?: boolean }>` white-space: pre-wrap; word-break: break-word; overflow-y: auto; + box-sizing: border-box; ::placeholder { color: ${({ disabled }) => (disabled ? "var(--color-fg-neutral-medium)" : "var(--color-fg-neutral-strong)")}; @@ -103,11 +105,8 @@ const Input = styled.textarea<{ isRecording?: boolean }>` `; const InputWrapper = styled.div` - max-height: 150px; position: relative; - flex-grow: 1; display: flex; - flex-direction: column; `; const TranscriptOverlay = styled.div` @@ -116,7 +115,6 @@ const TranscriptOverlay = styled.div` left: 0; right: 0; bottom: 0; - padding: var(--spacing-padding-none) var(--spacing-padding-xs); pointer-events: none; color: var(--color-fg-neutral-dark); font-family: var(--typography-font-family); @@ -125,6 +123,12 @@ const TranscriptOverlay = styled.div` white-space: pre-wrap; word-break: break-word; overflow-y: auto; + padding: var(--spacing-padding-none) var(--spacing-padding-xs); + + ${scrollbarStyles}; + &::-webkit-scrollbar { + width: 4px; + } `; const HighlightedText = styled.span` @@ -141,7 +145,7 @@ const DxcMessageInput = ({ isGenerating = false, maxLength, minLength, - modelList, + selectOptions, onBlur, onButtonClick, onChange, @@ -151,30 +155,35 @@ const DxcMessageInput = ({ value, }: PromptInputPropsType) => { const languageContext = useContext(HalstackLanguageContext); - const locale = languageContext.locale ? languageContext.locale : undefined; + const locale = languageContext.locale ?? "en-US"; const inputId = `input-${useId()}`; const inputRef = useRef<HTMLTextAreaElement | null>(null); const overlayRef = useRef<HTMLDivElement | null>(null); const [innerValue, setInnerValue] = useState(defaultValue); const [isFocused, setIsFocused] = useState(false); const fileInputRef = useRef<HTMLInputElement | null>(null); - const { transcript, isRecording, startRecording, stopRecording, resetTranscript } = useVoiceTranscription({ - lang: locale ?? "en-US", + const { + transcript, + isRecording, + startRecording, + stopRecording, + resetTranscript, + error: transcriptError, + } = useVoiceTranscription({ + lang: locale, }); + const dropdownOptions = [{ label: languageContext.labels.messageInput.attachFileButtonTitle, value: "fileorphoto" }]; - // TO BE DONE: IMPLEMENT SEPARATELY minLenght & maxLength - const isLengthOutOfRange = (value: string) => - value !== "" && minLength && maxLength && (value.length < minLength || value.length > maxLength); + const getLengthError = (val: string) => + isLengthOutOfRange(val, minLength, maxLength) + ? languageContext.labels.formFields.lengthErrorMessage?.(minLength, maxLength) + : undefined; + + const getError = (val: string) => transcriptError ?? getLengthError(val); const changeValue = (newValue: string) => { if (value == null) setInnerValue(newValue); - - if (isLengthOutOfRange(newValue)) { - onChange?.({ - value: newValue, - error: languageContext.labels.formFields.lengthErrorMessage?.(minLength, maxLength), - }); - } else onChange?.({ value: newValue }); + onChange?.({ value: newValue, error: getError(newValue) }); }; const handleInputContainerOnClick = () => inputRef.current?.focus(); @@ -184,12 +193,7 @@ const DxcMessageInput = ({ }; const handleInputOnChange = (event: ChangeEvent<HTMLTextAreaElement>) => { - const el = event.target; - el.style.height = "auto"; - - el.style.height = `${el.scrollHeight}px`; - - changeValue(el.value); + changeValue(event.target.value); }; const handleInputOnFocus = () => setIsFocused(true); @@ -197,12 +201,7 @@ const DxcMessageInput = ({ const handleInputOnBlur = (event: FocusEvent<HTMLTextAreaElement>) => { setIsFocused(false); - if (isLengthOutOfRange(event.target.value)) { - onBlur?.({ - value: event.target.value, - error: languageContext.labels.formFields.lengthErrorMessage?.(minLength, maxLength), - }); - } else onBlur?.({ value: event.target.value }); + onBlur?.({ value: event.target.value, error: getError(event.target.value) }); }; const handleFileSelect = () => fileInputRef.current?.click(); @@ -258,9 +257,13 @@ const DxcMessageInput = ({ return () => textarea.removeEventListener("scroll", handleScroll); }, [isRecording]); - const handleSubmit = () => !disabled && !isGenerating && onButtonClick?.("submit"); + const handleSubmit = () => { + if (!disabled && !isGenerating) onButtonClick?.("submit"); + }; - const handleStop = () => !disabled && onButtonClick?.("stop"); + const handleStop = () => { + if (!disabled) onButtonClick?.("stop"); + }; const handleInputOnKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => { if (!disabled && !isGenerating && event.key === "Enter" && !event.shiftKey) { @@ -275,16 +278,17 @@ const DxcMessageInput = ({ disabled={disabled} error={!!error} focus={isFocused} + hasFiles={files && typeof callbackFile === "function"} onClick={handleInputContainerOnClick} onMouseDown={handleInputContainerOnMouseDown} > - {files && ( + {files && typeof callbackFile === "function" && ( <FilesContainer> <DxcDropdown - options={[{ label: languageContext.labels.messageInput.attachFileButtonTitle, value: "fileorphoto" }]} + options={dropdownOptions} onSelectOption={handleFileSelect} icon="add" - disabled={isGenerating || disabled} + disabled={isGenerating || disabled || isRecording} caretHidden /> <input ref={fileInputRef} type="file" hidden multiple onChange={handleFileInputOnChange} /> @@ -300,7 +304,7 @@ const DxcMessageInput = ({ </FilesContainer> )} <InputWrapper> - <Input + <MessageArea aria-label={languageContext.labels.messageInput.inputAriaLabel} aria-errormessage={error ? `error-${inputId}` : undefined} aria-invalid={!!error} @@ -326,11 +330,10 @@ const DxcMessageInput = ({ {baseTextRef.current && <span>{baseTextRef.current}</span>} {transcript && ( <> - {baseTextRef.current && <span> </span>} {transcript.length > 2 ? ( <> <span>{transcript.slice(0, -2)}</span> - <HighlightedText>{transcript.slice(-2)}...</HighlightedText> + <HighlightedText>{transcript.slice(-2)} ...</HighlightedText> </> ) : ( <HighlightedText>{transcript}</HighlightedText> @@ -340,16 +343,16 @@ const DxcMessageInput = ({ </TranscriptOverlay> )} </InputWrapper> - <DxcFlex justifyContent={modelList ? "space-between" : "flex-end"} alignItems="center"> - {modelList && ( + <DxcFlex justifyContent={selectOptions ? "space-between" : "flex-end"} alignItems="center"> + {selectOptions && ( <DxcContainer width="35%" maxWidth="240px"> <DxcSelect size="fillParent" - options={modelList.map((option) => ({ label: option.label ?? option.value, value: option.value }))} - disabled={isGenerating || disabled} - value={modelList.find((option) => option.selected)?.value || modelList[0]?.value} + options={selectOptions.map((option) => ({ label: option.label ?? option.value, value: option.value }))} + disabled={isGenerating || disabled || isRecording} + value={selectOptions.find((option) => option.selected)?.value || selectOptions[0]?.value} onChange={(val) => { - const selectedOption = modelList.find((option) => option.value === val.value); + const selectedOption = selectOptions.find((option) => option.value === val.value); selectedOption?.onSelect(val.value); }} /> @@ -380,7 +383,7 @@ const DxcMessageInput = ({ <DxcButton icon={!isGenerating ? "send" : "filled_stop"} size={{ height: "medium" }} - disabled={disabled} + disabled={disabled || isRecording} onClick={!isGenerating ? handleSubmit : handleStop} title={ !isGenerating diff --git a/packages/lib/src/message-input/types.ts b/packages/lib/src/message-input/types.ts index 105a13514..90b21967f 100644 --- a/packages/lib/src/message-input/types.ts +++ b/packages/lib/src/message-input/types.ts @@ -1,23 +1,21 @@ +import { SVG } from "../common/utils"; + export type FileData = { /** * The chip label. */ label: string; - /* * + /** * The chip icon. It can be a string representing the icon name or a ReactNode representing a custom icon. */ - icon: string | (React.ReactNode & React.SVGProps<SVGSVGElement>); + icon?: string | SVG; }; -type Props = { +type CommonProps = { /** * If true, the voice recording button will be shown. */ allowRecording?: boolean; - /** - * This function will be called when the selection of top items changes. - */ - callbackFile?: (files: FileData[]) => void; /** * Initial value of the input, only when it is uncontrolled. */ @@ -35,10 +33,6 @@ type Props = { * the error message would not be modified. */ error?: string; - /** - * Items to be shown at the top. - */ - files?: FileData[] | []; /** * If true, it indicates that a request is being processed after the user submits a query. */ @@ -66,9 +60,9 @@ type Props = { /** * Options to be shown on the dropdown under the input. */ - modelList?: { + selectOptions?: { label?: string; - icon?: string | (React.ReactNode & React.SVGProps<SVGSVGElement>); + icon?: string | SVG; value: string; onSelect: (value: string) => void; selected?: boolean; @@ -90,7 +84,6 @@ type Props = { /** * This function will be called when the user clicks on the button (submit or stop) or presses enter. * The type parameter indicates whether it's a "submit" or "stop" event. - * For submit events, an AbortSignal is provided to allow cancellation. */ onButtonClick?: (type: "submit" | "stop") => void; /** @@ -111,4 +104,7 @@ type Props = { value?: string; }; +type Props = CommonProps & + ({ files: FileData[]; callbackFile: (files: FileData[]) => void } | { files?: undefined; callbackFile?: undefined }); + export default Props; diff --git a/packages/lib/src/message-input/useVoiceTranscription.tsx b/packages/lib/src/message-input/useVoiceTranscription.tsx index 1ecc5381e..32329e778 100644 --- a/packages/lib/src/message-input/useVoiceTranscription.tsx +++ b/packages/lib/src/message-input/useVoiceTranscription.tsx @@ -1,29 +1,34 @@ import { useCallback, useRef, useState } from "react"; -interface SpeechRecognitionResult { +export type SpeechRecognitionAlternative = { readonly transcript: string; readonly confidence: number; -} +}; -interface SpeechRecognitionResultItem { - readonly [index: number]: SpeechRecognitionResult; +export type SpeechRecognitionResult = { readonly length: number; -} + [index: number]: SpeechRecognitionAlternative; +}; + +export type SpeechRecognitionResultItem = { + readonly [index: number]: SpeechRecognitionAlternative; + readonly length: number; +}; -interface SpeechRecognitionResultList { +export type SpeechRecognitionResultList = { readonly [index: number]: SpeechRecognitionResultItem; readonly length: number; -} +}; -interface SpeechRecognitionEvent extends Event { +export type SpeechRecognitionEvent = Event & { readonly results: SpeechRecognitionResultList; -} +}; -interface SpeechRecognitionErrorEvent extends Event { +export type SpeechRecognitionErrorEvent = Event & { readonly error: string; -} +}; -interface ISpeechRecognition extends EventTarget { +export type ISpeechRecognition = EventTarget & { lang: string; continuous: boolean; interimResults: boolean; @@ -32,40 +37,45 @@ interface ISpeechRecognition extends EventTarget { onend: (() => void) | null; start(): void; stop(): void; -} +}; -interface ISpeechRecognitionConstructor { +type ISpeechRecognitionConstructor = { new (): ISpeechRecognition; -} +}; -interface WindowWithSpeechRecognition extends Window { +export type WindowWithSpeechRecognition = Window & { SpeechRecognition?: ISpeechRecognitionConstructor; webkitSpeechRecognition?: ISpeechRecognitionConstructor; -} +}; -interface UseVoiceTranscriptionOptions { - lang?: string; +type UseVoiceTranscriptionProps = { + lang: string; continuous?: boolean; interimResults?: boolean; -} + onError?: (error: string) => void; +}; -interface UseVoiceTranscriptionReturn { +type UseVoiceTranscriptionReturn = { transcript: string; isRecording: boolean; isSupported: boolean; startRecording: () => void; stopRecording: () => void; resetTranscript: () => void; -} + error?: string; +}; export const useVoiceTranscription = ({ - lang = "en-US", + lang, continuous = true, interimResults = true, -}: UseVoiceTranscriptionOptions = {}): UseVoiceTranscriptionReturn => { + onError, +}: UseVoiceTranscriptionProps): UseVoiceTranscriptionReturn => { const [transcript, setTranscript] = useState(""); const [isRecording, setIsRecording] = useState(false); + const [error, setError] = useState<string>(); const recognitionRef = useRef<ISpeechRecognition | null>(null); + const resetTranscript = useCallback(() => setTranscript(""), []); const windowWithSpeech = typeof window !== "undefined" ? (window as unknown as WindowWithSpeechRecognition) : undefined; @@ -94,20 +104,27 @@ export const useVoiceTranscription = ({ } const fullTranscript = results - .map((result) => result[0]?.transcript ?? "") + .map((result) => { + const best = result[0]; + return best && best.confidence >= 0.5 ? best.transcript : ""; + }) .join(" ") .trim(); setTranscript(fullTranscript); }; recognition.onerror = (event: SpeechRecognitionErrorEvent) => { - console.error("Speech recognition error:", event.error); + setError(event.error); + onError?.(event.error); + + resetTranscript(); setIsRecording(false); recognitionRef.current = null; }; recognition.onend = () => { setIsRecording(false); + resetTranscript(); recognitionRef.current = null; }; @@ -122,7 +139,5 @@ export const useVoiceTranscription = ({ } }, []); - const resetTranscript = useCallback(() => setTranscript(""), []); - - return { transcript, isRecording, isSupported, startRecording, stopRecording, resetTranscript }; + return { transcript, isRecording, isSupported, error, startRecording, stopRecording, resetTranscript }; }; diff --git a/packages/lib/src/message-input/utils.ts b/packages/lib/src/message-input/utils.ts index c8fea892c..20af6e48e 100644 --- a/packages/lib/src/message-input/utils.ts +++ b/packages/lib/src/message-input/utils.ts @@ -1,5 +1,9 @@ import { css } from "@emotion/react"; +// TO BE DONE: IMPLEMENT SEPARATELY minLenght & maxLength +export const isLengthOutOfRange = (value: string, minLength?: number, maxLength?: number) => + value !== "" && minLength && maxLength && (value.length < minLength || value.length > maxLength); + export const inputStylesByStatePromptInput = (disabled: boolean, error: boolean, focus?: boolean) => css` background-color: ${disabled ? `var(--color-bg-neutral-lightest)` : `transparent`}; diff --git a/packages/lib/src/select/utils.ts b/packages/lib/src/select/utils.ts index e45b91283..215c1b0d1 100644 --- a/packages/lib/src/select/utils.ts +++ b/packages/lib/src/select/utils.ts @@ -6,7 +6,7 @@ const sizes = { small: "240px", medium: "360px", large: "480px", - fillParent: "fit-content", + fillParent: "100%", }; export const calculateWidth = (margin: SelectPropsType["margin"], size: SelectPropsType["size"]) => From a8bafe00cf4dad5d8fefc6270a2315b7345a1c3c Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Thu, 23 Jul 2026 09:03:17 +0200 Subject: [PATCH 20/22] pass all the relevant information onSubmit action --- .../lib/src/message-input/MessageInput.tsx | 14 +++++++---- packages/lib/src/message-input/types.ts | 23 ++++++++++++------- packages/lib/src/message-input/utils.ts | 4 ++++ 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/packages/lib/src/message-input/MessageInput.tsx b/packages/lib/src/message-input/MessageInput.tsx index 2c568c4b1..91665a38a 100644 --- a/packages/lib/src/message-input/MessageInput.tsx +++ b/packages/lib/src/message-input/MessageInput.tsx @@ -12,7 +12,7 @@ import { import styled from "@emotion/styled"; import scrollbarStyles from "../styles/scroll"; import PromptInputPropsType from "./types"; -import { inputStylesByStatePromptInput, isLengthOutOfRange } from "./utils"; +import { getSelectedOption, inputStylesByStatePromptInput, isLengthOutOfRange } from "./utils"; import DxcButton from "../button/Button"; import DxcChip from "../chip/Chip"; import DxcContainer from "../container/Container"; @@ -258,11 +258,17 @@ const DxcMessageInput = ({ }, [isRecording]); const handleSubmit = () => { - if (!disabled && !isGenerating) onButtonClick?.("submit"); + if (!disabled && !isGenerating) + onButtonClick?.({ + type: "submit", + value: value ?? innerValue, + files: files, + selectedOption: selectOptions && getSelectedOption(selectOptions), + }); }; const handleStop = () => { - if (!disabled) onButtonClick?.("stop"); + if (!disabled) onButtonClick?.({ type: "stop" }); }; const handleInputOnKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => { @@ -352,7 +358,7 @@ const DxcMessageInput = ({ disabled={isGenerating || disabled || isRecording} value={selectOptions.find((option) => option.selected)?.value || selectOptions[0]?.value} onChange={(val) => { - const selectedOption = selectOptions.find((option) => option.value === val.value); + const selectedOption = getSelectedOption(selectOptions); selectedOption?.onSelect(val.value); }} /> diff --git a/packages/lib/src/message-input/types.ts b/packages/lib/src/message-input/types.ts index 90b21967f..907718ea5 100644 --- a/packages/lib/src/message-input/types.ts +++ b/packages/lib/src/message-input/types.ts @@ -11,6 +11,14 @@ export type FileData = { icon?: string | SVG; }; +export type SelectOption = { + label?: string; + icon?: string | SVG; + value: string; + onSelect: (value: string) => void; + selected?: boolean; +}; + type CommonProps = { /** * If true, the voice recording button will be shown. @@ -60,13 +68,7 @@ type CommonProps = { /** * Options to be shown on the dropdown under the input. */ - selectOptions?: { - label?: string; - icon?: string | SVG; - value: string; - onSelect: (value: string) => void; - selected?: boolean; - }[]; + selectOptions?: SelectOption[]; /** * This function will be called when the input element loses the focus. * An object including the input value and the error (if the value @@ -85,7 +87,12 @@ type CommonProps = { * This function will be called when the user clicks on the button (submit or stop) or presses enter. * The type parameter indicates whether it's a "submit" or "stop" event. */ - onButtonClick?: (type: "submit" | "stop") => void; + onButtonClick?: (val: { + type: "submit" | "stop"; + value?: string; + files?: FileData[]; + selectedOption?: SelectOption; + }) => void; /** * Text to be put as placeholder of the input. */ diff --git a/packages/lib/src/message-input/utils.ts b/packages/lib/src/message-input/utils.ts index 20af6e48e..c1f6d62d0 100644 --- a/packages/lib/src/message-input/utils.ts +++ b/packages/lib/src/message-input/utils.ts @@ -1,9 +1,13 @@ import { css } from "@emotion/react"; +import { SelectOption } from "./types"; // TO BE DONE: IMPLEMENT SEPARATELY minLenght & maxLength export const isLengthOutOfRange = (value: string, minLength?: number, maxLength?: number) => value !== "" && minLength && maxLength && (value.length < minLength || value.length > maxLength); +export const getSelectedOption = (selectOptions: SelectOption[]) => + selectOptions.find((option) => option.selected === true); + export const inputStylesByStatePromptInput = (disabled: boolean, error: boolean, focus?: boolean) => css` background-color: ${disabled ? `var(--color-bg-neutral-lightest)` : `transparent`}; From 90a68af047c237d5f5ed98b0e9a1eb75b1ea8d3b Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Thu, 23 Jul 2026 09:37:52 +0200 Subject: [PATCH 21/22] fix tests and update examples at code page --- .../code/MessageInputCodePage.tsx | 13 +++++-- .../message-input/code/examples/advanced.tsx | 22 ++++++------ .../code/examples/controlled.tsx | 10 ++++-- .../code/examples/uncontrolled.tsx | 17 ++++++++++ .../src/message-input/MessageInput.test.tsx | 34 +++++++++---------- .../lib/src/message-input/MessageInput.tsx | 4 +-- packages/lib/src/message-input/types.ts | 2 +- 7 files changed, 65 insertions(+), 37 deletions(-) diff --git a/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx b/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx index e7246d6de..d72dc6d2d 100644 --- a/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx +++ b/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx @@ -20,6 +20,13 @@ const selectOptionsTypeString = `{ onSelect: () => void; selected?: boolean; }[]`; +const onButtonClickTypeString = `(val: { + type: "submit" | "stop"; + value?: string; + files?: FileData[]; + selectedOption?: SelectOption; + }) => void; +`; const sections = [ { @@ -157,12 +164,12 @@ const sections = [ <tr> <td>onButtonClick</td> <td> - <ExtendedTableCode>{"(type: 'submit' | 'stop') => void"}</ExtendedTableCode> + <ExtendedTableCode>{onButtonClickTypeString}</ExtendedTableCode> </td> <td> This function will be called when the user clicks on the button (submit or stop) or presses enter. The - type parameter indicates whether it's a "submit" or "stop" event. For submit events, an AbortSignal is - provided to allow cancellation. + type parameter indicates whether it's a <Code>'submit'</Code> or <Code>'stop'</Code> event. For submit + events, <Code>'value'</Code>, <Code>'files'</Code>and <Code>'selectedOption'</Code> are provided. </td> <td>-</td> </tr> diff --git a/apps/website/screens/components/message-input/code/examples/advanced.tsx b/apps/website/screens/components/message-input/code/examples/advanced.tsx index ff35ced8e..37a3292d9 100644 --- a/apps/website/screens/components/message-input/code/examples/advanced.tsx +++ b/apps/website/screens/components/message-input/code/examples/advanced.tsx @@ -7,7 +7,7 @@ const code = `() => { { label: "document.pdf", icon: "insert_drive_file" }, { label: "image.png", icon: "image" } ]); - const [selectedModel, setSelectedModel] = useState("GPT-4"); + const [selectedOption, setSelectedOptions] = useState("model-1.0"); const [isGenerating, setIsGenerating] = useState(false); const [error, setError] = useState(""); @@ -22,12 +22,12 @@ const code = `() => { } }; - const onButtonClick = async (type, signal) => { + const onButtonClick = async ({type}) => { if (type === "submit") { setIsGenerating(true); console.log("Submitting message:", value); console.log("Attached files:", files); - console.log("Selected model:", selectedModel); + console.log("Selected model:", selectedOption); // Simulate async operation await new Promise((resolve) => setTimeout(resolve, 2000)); @@ -45,27 +45,27 @@ const code = `() => { setFiles(updatedFiles); }; - const modelList = [ + const selectOptions = [ { label: "MODEL-1.0", icon: "psychology", value: "model-1.0", - onSelect: () => setSelectedModel("model-1.0"), - selected: selectedModel === "model-1.0" + onSelect: () => setSelectedOptions("model-1.0"), + selected: selectedOption === "model-1.0" }, { label: "MODEL-3.5", icon: "smart_toy", value: "model-3.5", - onSelect: () => setSelectedModel("model-3.5"), - selected: selectedModel === "model-3.5" + onSelect: () => setSelectedOptions("model-3.5"), + selected: selectedOption === "model-3.5" }, { label: "MODEL-4+", icon: "lightbulb", value: "model-4+", - onSelect: () => setSelectedModel("model-4+"), - selected: selectedModel === "model-4+" + onSelect: () => setSelectedOptions("model-4+"), + selected: selectedOption === "model-4+" } ]; @@ -79,7 +79,7 @@ const code = `() => { onButtonClick={onButtonClick} files={files} callbackFile={callbackFile} - modelList={modelList} + selectOptions={selectOptions} allowRecording isGenerating={isGenerating} error={error} diff --git a/apps/website/screens/components/message-input/code/examples/controlled.tsx b/apps/website/screens/components/message-input/code/examples/controlled.tsx index a2ee51edd..c19837a57 100644 --- a/apps/website/screens/components/message-input/code/examples/controlled.tsx +++ b/apps/website/screens/components/message-input/code/examples/controlled.tsx @@ -3,16 +3,17 @@ import { useState } from "react"; const code = `() => { const [value, setValue] = useState(""); + const [error, setError] = useState(); const onChange = ({ value }) => { setValue(value); }; - const onBlur = ({ value }) => { - console.log("Input blurred with value:", value); + const onBlur = ({ value, error }) => { + setError(error) }; - const onButtonClick = async (type, signal) => { + const onButtonClick = async ({type}) => { if (type === "submit") { console.log("Submitted message:", value); // Simulate async operation @@ -29,6 +30,9 @@ const code = `() => { onChange={onChange} onBlur={onBlur} onButtonClick={onButtonClick} + error={error} + minLength={5} + maxLength={15} /> </DxcInset> ); diff --git a/apps/website/screens/components/message-input/code/examples/uncontrolled.tsx b/apps/website/screens/components/message-input/code/examples/uncontrolled.tsx index a78fd5896..a3abac1ad 100644 --- a/apps/website/screens/components/message-input/code/examples/uncontrolled.tsx +++ b/apps/website/screens/components/message-input/code/examples/uncontrolled.tsx @@ -2,6 +2,23 @@ import { DxcMessageInput, DxcButton, DxcFlex, DxcInset } from "@dxc-technology/h import { useRef } from "react"; const code = `() => { + const onChange = ({ value }) => { + setValue(value); + }; + + const onBlur = ({ value }) => { + console.log("Input blurred with value:", value); + }; + + const onButtonClick = async ({type, value}) => { + if (type === "submit") { + console.log("Submitted message:", value); + // Simulate async operation + await new Promise((resolve) => setTimeout(resolve, 1000)); + setValue(""); // Clear input after submit + } + }; + return ( <DxcInset space="var(--spacing-padding-xl)"> <DxcMessageInput diff --git a/packages/lib/src/message-input/MessageInput.test.tsx b/packages/lib/src/message-input/MessageInput.test.tsx index 7be905446..c3b890aa3 100644 --- a/packages/lib/src/message-input/MessageInput.test.tsx +++ b/packages/lib/src/message-input/MessageInput.test.tsx @@ -68,7 +68,7 @@ describe("Message Input component tests", () => { userEvent.click(input); userEvent.type(input, "Test message{Enter}"); - expect(onButtonClick).toHaveBeenCalledWith("submit"); + expect(onButtonClick).toHaveBeenCalledWith({ type: "submit", value: "Test message" }); expect(onButtonClick).toHaveBeenCalledTimes(1); }); @@ -110,7 +110,7 @@ describe("Message Input component tests", () => { userEvent.click(submitButton); - expect(onButtonClick).toHaveBeenCalledWith("submit"); + expect(onButtonClick).toHaveBeenCalledWith({ type: "submit", value: "" }); expect(onButtonClick).toHaveBeenCalledTimes(1); }); @@ -143,7 +143,7 @@ describe("Message Input component tests", () => { userEvent.click(stopButton); - expect(onButtonClick).toHaveBeenCalledWith("stop"); + expect(onButtonClick).toHaveBeenCalledWith({ type: "stop" }); expect(onButtonClick).toHaveBeenCalledTimes(1); }); @@ -220,7 +220,7 @@ describe("Message Input component tests", () => { expect(select).toBeInTheDocument(); }); - test("calls onSelect when a bottom option is selected", () => { + test("calls onSelect when a select option is selected", () => { const onSelect = jest.fn(); const selectOptions = [ { label: "Option 1", value: "option1", onSelect }, @@ -295,7 +295,7 @@ describe("Message Input component tests", () => { const submitButton = getByLabelText("Send message"); userEvent.click(submitButton); - expect(onButtonClick).toHaveBeenCalledWith("submit"); + expect(onButtonClick).toHaveBeenCalledWith({ type: "submit", value: "" }); expect(onButtonClick).toHaveBeenCalledTimes(1); }); @@ -390,45 +390,45 @@ describe("useVoiceTranscription", () => { }); test("updates transcript when confidence is high enough", () => { - const { result } = renderHook(() => useVoiceTranscription({ lang: "es-ES" })); + const { result } = renderHook(() => useVoiceTranscription({ lang: "en-US" })); act(() => { result.current.startRecording(); }); act(() => { - mockInstance.emitResult("hola mundo", 0.8); + mockInstance.emitResult("hello world", 0.8); }); - expect(result.current.transcript).toBe("hola mundo"); + expect(result.current.transcript).toBe("hello world"); }); test("ignores transcript when confidence is below 0.5", () => { - const { result } = renderHook(() => useVoiceTranscription({ lang: "es-ES" })); + const { result } = renderHook(() => useVoiceTranscription({ lang: "en-US" })); act(() => { result.current.startRecording(); }); act(() => { - mockInstance.emitResult("texto poco fiable", 0.2); + mockInstance.emitResult("text with low confidence", 0.2); }); expect(result.current.transcript).toBe(""); }); test("resets transcript and isRecording when recognition ends automatically", () => { - const { result } = renderHook(() => useVoiceTranscription({ lang: "es-ES" })); + const { result } = renderHook(() => useVoiceTranscription({ lang: "eb-US" })); act(() => { result.current.startRecording(); }); act(() => { - mockInstance.emitResult("hola", 0.9); + mockInstance.emitResult("hello", 0.9); }); - expect(result.current.transcript).toBe("hola"); + expect(result.current.transcript).toBe("hello"); act(() => { mockInstance.emitEnd(); @@ -439,14 +439,14 @@ describe("useVoiceTranscription", () => { }); test("resets transcript on error", () => { - const { result } = renderHook(() => useVoiceTranscription({ lang: "es-ES" })); + const { result } = renderHook(() => useVoiceTranscription({ lang: "en-US" })); act(() => { result.current.startRecording(); }); act(() => { - mockInstance.emitResult("hola", 0.9); + mockInstance.emitResult("hello", 0.9); }); act(() => { @@ -458,7 +458,7 @@ describe("useVoiceTranscription", () => { }); test("stopRecording calls recognition.stop", () => { - const { result } = renderHook(() => useVoiceTranscription({ lang: "es-ES" })); + const { result } = renderHook(() => useVoiceTranscription({ lang: "en-US" })); act(() => { result.current.startRecording(); @@ -473,7 +473,7 @@ describe("useVoiceTranscription", () => { test("isSupported is false when SpeechRecognition is not available", () => { delete (window as WindowWithSpeechRecognition).SpeechRecognition; - const { result } = renderHook(() => useVoiceTranscription({ lang: "es-ES" })); + const { result } = renderHook(() => useVoiceTranscription({ lang: "en-US" })); expect(result.current.isSupported).toBe(false); }); }); diff --git a/packages/lib/src/message-input/MessageInput.tsx b/packages/lib/src/message-input/MessageInput.tsx index 91665a38a..d64637e90 100644 --- a/packages/lib/src/message-input/MessageInput.tsx +++ b/packages/lib/src/message-input/MessageInput.tsx @@ -358,8 +358,8 @@ const DxcMessageInput = ({ disabled={isGenerating || disabled || isRecording} value={selectOptions.find((option) => option.selected)?.value || selectOptions[0]?.value} onChange={(val) => { - const selectedOption = getSelectedOption(selectOptions); - selectedOption?.onSelect(val.value); + const newOption = selectOptions.find((option) => option.value === val.value); + newOption?.onSelect(val.value); }} /> </DxcContainer> diff --git a/packages/lib/src/message-input/types.ts b/packages/lib/src/message-input/types.ts index 907718ea5..81b4ce747 100644 --- a/packages/lib/src/message-input/types.ts +++ b/packages/lib/src/message-input/types.ts @@ -85,7 +85,7 @@ type CommonProps = { onChange?: (val: { value: string; error?: string }) => void; /** * This function will be called when the user clicks on the button (submit or stop) or presses enter. - * The type parameter indicates whether it's a "submit" or "stop" event. + * The type parameter indicates whether it's a "submit" or "stop" event. For submit events, "value", "files" and "selectedOption" are provided. */ onButtonClick?: (val: { type: "submit" | "stop"; From a002c0c7084435b9c5bc0a038f0f45975a119a17 Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Fri, 24 Jul 2026 09:19:09 +0200 Subject: [PATCH 22/22] change images and add getFilePreview --- .../code/MessageInputCodePage.tsx | 2 +- .../overview/MessageInputOverviewPage.tsx | 3 ++- .../overview/images/generating-state.png | Bin 15620 -> 15902 bytes .../overview/images/message-input-anatomy.png | Bin 0 -> 27951 bytes .../lib/src/message-input/MessageInput.tsx | 8 +++++--- packages/lib/src/message-input/types.ts | 8 ++++++-- packages/lib/src/message-input/utils.ts | 7 +++++++ 7 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 apps/website/screens/components/message-input/overview/images/message-input-anatomy.png diff --git a/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx b/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx index d72dc6d2d..561e2d67b 100644 --- a/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx +++ b/apps/website/screens/components/message-input/code/MessageInputCodePage.tsx @@ -9,7 +9,7 @@ import advanced from "./examples/advanced"; const fileDataTypeString = `{ label: string; - icon?: string | SVG; + file: File; };`; const callbackFileTypeString = "(files: FileData[]) => void"; const filesTypeString = `FileData[] | []`; diff --git a/apps/website/screens/components/message-input/overview/MessageInputOverviewPage.tsx b/apps/website/screens/components/message-input/overview/MessageInputOverviewPage.tsx index 9c2e24f5b..ab27256fe 100644 --- a/apps/website/screens/components/message-input/overview/MessageInputOverviewPage.tsx +++ b/apps/website/screens/components/message-input/overview/MessageInputOverviewPage.tsx @@ -5,6 +5,7 @@ import DxcParagraph from "../../../../../../packages/lib/src/paragraph/Paragraph import { DxcBulletedList } from "@dxc-technology/halstack-react"; import Figure from "@/common/Figure"; import Image from "@/common/Image"; +import messageInputAnatomy from "./images/message-input-anatomy.png"; import messageInputExample from "./images/message-input-example.png"; import generatingState from "./images/generating-state.png"; @@ -38,7 +39,7 @@ const sections = [ title: "Anatomy", content: ( <> - {/* <Image src={linkAnatomy} alt="Message input anatomy" /> */} + <Image src={messageInputAnatomy} alt="Message input anatomy" /> <DxcBulletedList type="number"> <DxcBulletedList.Item> <strong>Dropdown</strong> <em>(optional)</em>: allows the user to attach files, either as context for the AI diff --git a/apps/website/screens/components/message-input/overview/images/generating-state.png b/apps/website/screens/components/message-input/overview/images/generating-state.png index 97e88fa3cdeca3fc147a1cf77656996d6a92f145..15d7082627f0f03fdd0fba1ba298e80498d0fb9d 100644 GIT binary patch literal 15902 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 literal 15620 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 diff --git a/apps/website/screens/components/message-input/overview/images/message-input-anatomy.png b/apps/website/screens/components/message-input/overview/images/message-input-anatomy.png new file mode 100644 index 0000000000000000000000000000000000000000..260b326e5a01c0260c5f8178627d5bc1040e16ab GIT binary patch literal 27951 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 SbeKer_6c46qgjV-LjMOLBw=U( literal 0 HcmV?d00001 diff --git a/packages/lib/src/message-input/MessageInput.tsx b/packages/lib/src/message-input/MessageInput.tsx index d64637e90..adc43d06a 100644 --- a/packages/lib/src/message-input/MessageInput.tsx +++ b/packages/lib/src/message-input/MessageInput.tsx @@ -12,7 +12,7 @@ import { import styled from "@emotion/styled"; import scrollbarStyles from "../styles/scroll"; import PromptInputPropsType from "./types"; -import { getSelectedOption, inputStylesByStatePromptInput, isLengthOutOfRange } from "./utils"; +import { getFilePreview, getSelectedOption, inputStylesByStatePromptInput, isLengthOutOfRange } from "./utils"; import DxcButton from "../button/Button"; import DxcChip from "../chip/Chip"; import DxcContainer from "../container/Container"; @@ -207,9 +207,11 @@ const DxcMessageInput = ({ const handleFileSelect = () => fileInputRef.current?.click(); const handleFileInputOnChange = (event: ChangeEvent<HTMLInputElement>) => { - const selectedFiles = Array.from(event.target.files ?? []).map((file) => ({ + const filesArray = Array.from(event.target.files ?? []); + const selectedFiles = filesArray.map((file) => ({ label: file.name, - icon: "insert_drive_file", + icon: getFilePreview(file), + file: file, })); callbackFile?.([...(files ?? []), ...selectedFiles]); event.target.value = ""; diff --git a/packages/lib/src/message-input/types.ts b/packages/lib/src/message-input/types.ts index 81b4ce747..73003907f 100644 --- a/packages/lib/src/message-input/types.ts +++ b/packages/lib/src/message-input/types.ts @@ -6,9 +6,13 @@ export type FileData = { */ label: string; /** - * The chip icon. It can be a string representing the icon name or a ReactNode representing a custom icon. + * The chip icon. It can be a string representing the icon name. */ - icon?: string | SVG; + icon?: string; + /** + * The file object. + */ + file: File; }; export type SelectOption = { diff --git a/packages/lib/src/message-input/utils.ts b/packages/lib/src/message-input/utils.ts index c1f6d62d0..b2eda26b3 100644 --- a/packages/lib/src/message-input/utils.ts +++ b/packages/lib/src/message-input/utils.ts @@ -8,6 +8,13 @@ export const isLengthOutOfRange = (value: string, minLength?: number, maxLength? export const getSelectedOption = (selectOptions: SelectOption[]) => selectOptions.find((option) => option.selected === true); +export const getFilePreview = (file: File): string => { + if (file.type.includes("video")) return "filled_movie"; + else if (file.type.includes("audio")) return "music_video"; + else if (file.type.includes("image")) return "image"; + else return "description"; +}; + export const inputStylesByStatePromptInput = (disabled: boolean, error: boolean, focus?: boolean) => css` background-color: ${disabled ? `var(--color-bg-neutral-lightest)` : `transparent`};