From 320a796827107c57c9f466c190e939d11f121e62 Mon Sep 17 00:00:00 2001 From: Pelayo Felgueroso Date: Tue, 19 Aug 2025 10:33:24 +0200 Subject: [PATCH 1/6] Add optional prop to the File Input component --- packages/lib/src/file-input/FileInput.stories.tsx | 6 ++++++ packages/lib/src/file-input/FileInput.tsx | 15 ++++++++++----- packages/lib/src/file-input/types.ts | 7 +++++++ packages/lib/src/file-input/utils.ts | 2 +- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/packages/lib/src/file-input/FileInput.stories.tsx b/packages/lib/src/file-input/FileInput.stories.tsx index 14b7c254ad..e6bff8e8c4 100644 --- a/packages/lib/src/file-input/FileInput.stories.tsx +++ b/packages/lib/src/file-input/FileInput.stories.tsx @@ -81,6 +81,12 @@ const FileInput = () => ( <DxcFileInput label="File input" value={[]} callbackFile={() => {}} /> </ExampleContainer> + + <ExampleContainer> + <Title title="Optional" theme="light" level={4} /> + <DxcFileInput label="File input" value={[]} callbackFile={() => {}} optional /> + </ExampleContainer> + <ExampleContainer> <Title title="With label and helper text" theme="light" level={4} /> <DxcFileInput label="File input" helperText="Please select files" value={[]} callbackFile={() => {}} /> diff --git a/packages/lib/src/file-input/FileInput.tsx b/packages/lib/src/file-input/FileInput.tsx index 9109460d4c..08aab3317b 100644 --- a/packages/lib/src/file-input/FileInput.tsx +++ b/packages/lib/src/file-input/FileInput.tsx @@ -105,7 +105,7 @@ const DxcFileInput = forwardRef<RefType, FileInputPropsType>( ( { mode = "file", - label = "", + label, buttonLabel, dropAreaLabel, helperText = "", @@ -119,6 +119,7 @@ const DxcFileInput = forwardRef<RefType, FileInputPropsType>( value, margin, tabIndex = 0, + optional = false, }, ref ): JSX.Element => { @@ -226,10 +227,12 @@ const DxcFileInput = forwardRef<RefType, FileInputPropsType>( return ( <FileInputContainer margin={margin} ref={ref}> - <Label htmlFor={fileInputId} disabled={disabled}> - {label} - </Label> - <HelperText disabled={disabled}>{helperText}</HelperText> + {label && ( + <Label disabled={disabled} hasMargin={!helperText} htmlFor={fileInputId}> + {label} {optional && <span>{translatedLabels.formFields.optionalLabel}</span>} + </Label> + )} + {helperText && <HelperText disabled={disabled}>{helperText}</HelperText>} {mode === "file" ? ( <FileContainer singleFileMode={!multiple && files.length === 1}> <ValueInput @@ -240,6 +243,7 @@ const DxcFileInput = forwardRef<RefType, FileInputPropsType>( onChange={selectFiles} disabled={disabled} readOnly + required={!optional} /> <DxcButton mode="secondary" @@ -282,6 +286,7 @@ const DxcFileInput = forwardRef<RefType, FileInputPropsType>( onChange={selectFiles} disabled={disabled} readOnly + required={!optional} /> <DragDropArea isDragging={isDragging} diff --git a/packages/lib/src/file-input/types.ts b/packages/lib/src/file-input/types.ts index ab0c39a2e5..ebe3b6cb2b 100644 --- a/packages/lib/src/file-input/types.ts +++ b/packages/lib/src/file-input/types.ts @@ -71,6 +71,13 @@ type CommonProps = { * Value of the tabindex attribute. */ tabIndex?: number; + /** + * If true, the input will be optional, showing '(Optional)' + * next to the label. Otherwise, the field will be considered required and an error will be + * passed as a parameter to the OnBlur and onChange functions when it has + * not been filled. + */ + optional?: boolean; }; type DropModeProps = CommonProps & { /** diff --git a/packages/lib/src/file-input/utils.ts b/packages/lib/src/file-input/utils.ts index 7b76761e73..cf0f4e9f40 100644 --- a/packages/lib/src/file-input/utils.ts +++ b/packages/lib/src/file-input/utils.ts @@ -24,4 +24,4 @@ export const isFileIncluded = (file: FileData, fileList: FileData[]) => { lastModified === file.file.lastModified && webkitRelativePath === file.file.webkitRelativePath ); -}; \ No newline at end of file +}; From f3d17cd98794b84d9d88ed3c245cc115cf94e170 Mon Sep 17 00:00:00 2001 From: Pelayo Felgueroso <pelayo.felgueroso@dxc.com> Date: Tue, 19 Aug 2025 14:30:43 +0200 Subject: [PATCH 2/6] Sort props on types.ts and add documentation for optional props --- .../lib/src/file-input/FileInput.stories.tsx | 1 - packages/lib/src/file-input/FileInput.tsx | 18 +++- packages/lib/src/file-input/types.ts | 84 +++++++++---------- packages/lib/src/file-input/utils.ts | 2 + 4 files changed, 61 insertions(+), 44 deletions(-) diff --git a/packages/lib/src/file-input/FileInput.stories.tsx b/packages/lib/src/file-input/FileInput.stories.tsx index e6bff8e8c4..c90a014a50 100644 --- a/packages/lib/src/file-input/FileInput.stories.tsx +++ b/packages/lib/src/file-input/FileInput.stories.tsx @@ -2,7 +2,6 @@ import { Meta, StoryObj } from "@storybook/react"; import ExampleContainer from "../../.storybook/components/ExampleContainer"; import Title from "../../.storybook/components/Title"; import DxcFileInput from "./FileInput"; -import { userEvent, within } from "@storybook/test"; export default { title: "File Input", diff --git a/packages/lib/src/file-input/FileInput.tsx b/packages/lib/src/file-input/FileInput.tsx index 08aab3317b..0baa5cbd51 100644 --- a/packages/lib/src/file-input/FileInput.tsx +++ b/packages/lib/src/file-input/FileInput.tsx @@ -5,9 +5,10 @@ import { spaces } from "../common/variables"; import FileItem from "./FileItem"; import FileInputPropsType, { FileData, RefType } from "./types"; import { HalstackLanguageContext } from "../HalstackContext"; -import { getFilePreview, isFileIncluded } from "./utils"; +import { getFilePreview, isFileIncluded, isRequired } from "./utils"; import HelperText from "../styles/forms/HelperText"; import Label from "../styles/forms/Label"; +import ErrorMessage from "../styles/forms/ErrorMessage"; const FileInputContainer = styled.div<{ margin: FileInputPropsType["margin"] }>` display: flex; @@ -124,8 +125,10 @@ const DxcFileInput = forwardRef<RefType, FileInputPropsType>( ref ): JSX.Element => { const [isDragging, setIsDragging] = useState(false); + const [inputError, setInputError] = useState<string | undefined>(undefined); const [files, setFiles] = useState<FileData[]>([]); const fileInputId = `file-input-${useId()}`; + const errorId = `error-${fileInputId}`; const translatedLabels = useContext(HalstackLanguageContext); const checkFileSize = (file: File) => { @@ -173,6 +176,7 @@ const DxcFileInput = forwardRef<RefType, FileInputPropsType>( const fileIndex = filesCopy.indexOf(fileToRemove); filesCopy.splice(fileIndex, 1); callbackFile?.(filesCopy); + validateIsRequired(filesCopy); } }, [files, callbackFile] @@ -207,6 +211,14 @@ const DxcFileInput = forwardRef<RefType, FileInputPropsType>( } }; + const validateIsRequired = (files: FileData[]) => { + if (isRequired(files, optional)) { + setInputError(translatedLabels.formFields.requiredValueErrorMessage); + } else { + setInputError(undefined); + } + }; + useEffect(() => { const getFiles = async () => { if (value) { @@ -247,6 +259,7 @@ const DxcFileInput = forwardRef<RefType, FileInputPropsType>( /> <DxcButton mode="secondary" + semantic={inputError ? "error" : "default"} label={ buttonLabel ?? (multiple @@ -299,11 +312,13 @@ const DxcFileInput = forwardRef<RefType, FileInputPropsType>( > <DxcButton mode="secondary" + semantic={inputError ? "error" : "default"} label={buttonLabel ?? translatedLabels.fileInput.dropAreaButtonLabelDefault} onClick={handleClick} disabled={disabled} size={{ width: "fitContent", height: "medium" }} /> + {mode === "dropzone" ? ( <DropzoneLabel disabled={disabled}> {dropAreaLabel ?? @@ -339,6 +354,7 @@ const DxcFileInput = forwardRef<RefType, FileInputPropsType>( )} </Container> )} + {!disabled && inputError && <ErrorMessage error={inputError} id={errorId} />} </FileInputContainer> ); } diff --git a/packages/lib/src/file-input/types.ts b/packages/lib/src/file-input/types.ts index ebe3b6cb2b..5c891e1148 100644 --- a/packages/lib/src/file-input/types.ts +++ b/packages/lib/src/file-input/types.ts @@ -1,14 +1,14 @@ import { Margin, Space } from "../common/utils"; export type FileData = { - /** - * Selected file. - */ - file: File; /** * Error of the file. If it is defined, it will be shown and the file item will be mark as invalid. */ error?: string; + /** + * Selected file. + */ + file: File; /** * Preview of the file. */ @@ -17,69 +17,73 @@ export type FileData = { type CommonProps = { /** - * Text to be placed above the component. + * The file types that the component accepts. Its value must be one of all the possible values of the HTML file input's accept attribute. */ - label?: string; + accept?: string; /** * Text to be placed inside the button. */ buttonLabel?: string; /** - * Helper text to be placed above the component. + * This function will be called when the user selects or drops a file. The list of files will be sent as a parameter. + * In this function, the files can be updated or returned as they come. These files must be passed to the value in order to be shown. */ - helperText?: string; + callbackFile: (files: FileData[]) => void; /** - * The file types that the component accepts. Its value must be one of all the possible values of the HTML file input's accept attribute. + * If true, the component will be disabled. */ - accept?: string; + disabled?: boolean; /** - * An array of files representing the selected files. + * Helper text to be placed above the component. */ - value: FileData[]; + helperText?: string; /** - * The minimum file size (in bytes) allowed. If the size of the file does not comply the minSize, the file will have an error. + * Text to be placed above the component. */ - minSize?: number; + label?: string; + /** + * Size of the margin to be applied to the component ('xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge'). + * You can pass an object with 'top', 'bottom', 'left' and 'right' properties in order to specify different margin sizes. + */ + margin?: Space | Margin; /** * The maximum file size (in bytes) allowed. If the size of the file does not comply the maxSize, the file will have an error. */ maxSize?: number; /** - * If true, if the file is an image, a preview of it will be shown. If not, an icon refering to the file type will be shown. + * The minimum file size (in bytes) allowed. If the size of the file does not comply the minSize, the file will have an error. */ - showPreview?: boolean; + minSize?: number; /** * If true, the component allows multiple file items and will show all of them. If false, only one file will be shown, and if there is already one * file selected and a new one is chosen, it will be replaced by the new selected one. */ multiple?: boolean; /** - * If true, the component will be disabled. - */ - disabled?: boolean; - /** - * This function will be called when the user selects or drops a file. The list of files will be sent as a parameter. - * In this function, the files can be updated or returned as they come. These files must be passed to the value in order to be shown. + * If true, the input will be optional, showing '(Optional)' + * next to the label. Otherwise, the field will be considered required and an error will be + * passed as a parameter to the OnBlur and onChange functions when it has + * not been filled. */ - callbackFile: (files: FileData[]) => void; + optional?: boolean; /** - * Size of the margin to be applied to the component ('xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge'). - * You can pass an object with 'top', 'bottom', 'left' and 'right' properties in order to specify different margin sizes. + * If true, if the file is an image, a preview of it will be shown. If not, an icon refering to the file type will be shown. */ - margin?: Space | Margin; + showPreview?: boolean; /** * Value of the tabindex attribute. */ tabIndex?: number; /** - * If true, the input will be optional, showing '(Optional)' - * next to the label. Otherwise, the field will be considered required and an error will be - * passed as a parameter to the OnBlur and onChange functions when it has - * not been filled. + * An array of files representing the selected files. */ - optional?: boolean; + value: FileData[]; }; type DropModeProps = CommonProps & { + /** + * Text to be placed inside the drag and drop zone alongside the button. + */ + dropAreaLabel?: string; /** * Uses one of the available file input modes: * 'file': Files are selected by clicking the button and selecting it through the file explorer. @@ -88,12 +92,12 @@ type DropModeProps = CommonProps & { * The drag and drop area of this mode is bigger than the one of the filedrop mode. */ mode: "filedrop" | "dropzone"; +}; +type FileModeProps = CommonProps & { /** * Text to be placed inside the drag and drop zone alongside the button. */ - dropAreaLabel?: string; -}; -type FileModeProps = CommonProps & { + dropAreaLabel?: never; /** * Uses one of the available file input modes: * 'file': Files are selected by clicking the button and selecting it through the file explorer. @@ -102,10 +106,6 @@ type FileModeProps = CommonProps & { * The drag and drop area of this mode is bigger than the one of the filedrop mode. */ mode?: "file"; - /** - * Text to be placed inside the drag and drop zone alongside the button. - */ - dropAreaLabel?: never; }; /** @@ -119,14 +119,14 @@ type Props = DropModeProps | FileModeProps; * Single file item preview. */ export type FileItemProps = { - fileName?: string; error?: string; + fileName?: string; + onDelete: (fileName: string) => void; + preview: string; showPreview: boolean; singleFileMode: boolean; - preview: string; - type: string; - onDelete: (fileName: string) => void; tabIndex: number; + type: string; }; export default Props; diff --git a/packages/lib/src/file-input/utils.ts b/packages/lib/src/file-input/utils.ts index cf0f4e9f40..ab3aeae9b7 100644 --- a/packages/lib/src/file-input/utils.ts +++ b/packages/lib/src/file-input/utils.ts @@ -25,3 +25,5 @@ export const isFileIncluded = (file: FileData, fileList: FileData[]) => { webkitRelativePath === file.file.webkitRelativePath ); }; + +export const isRequired = (value: FileData[], optional: boolean) => value.length === 0 && !optional; \ No newline at end of file From dd2914b54f44f014c048a3d26b111c06871815ae Mon Sep 17 00:00:00 2001 From: Pelayo Felgueroso <pelayo.felgueroso@dxc.com> Date: Wed, 20 Aug 2025 08:10:33 +0200 Subject: [PATCH 3/6] Add updated documentation page --- .../file-input/code/FileInputCodePage.tsx | 150 ++++++++++-------- 1 file changed, 82 insertions(+), 68 deletions(-) diff --git a/apps/website/screens/components/file-input/code/FileInputCodePage.tsx b/apps/website/screens/components/file-input/code/FileInputCodePage.tsx index 0be192ba0d..a55128bb7c 100644 --- a/apps/website/screens/components/file-input/code/FileInputCodePage.tsx +++ b/apps/website/screens/components/file-input/code/FileInputCodePage.tsx @@ -24,31 +24,52 @@ const sections = [ </thead> <tbody> <tr> - <td>mode</td> + <td>accept</td> <td> - <TableCode>'file' | 'filedrop' | 'dropzone'</TableCode> + <TableCode>string</TableCode> </td> - <td>Available modes of the component.</td> <td> - <TableCode>'file'</TableCode> + The file types that the component accepts. Its value must be one of all the possible values of the HTML + file input's <Code>accept</Code> attribute. Please check the documentation{" "} + <DxcLink href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept">here</DxcLink>. </td> + <td>-</td> </tr> <tr> - <td>label</td> + <td>buttonLabel</td> <td> <TableCode>string</TableCode> </td> - <td>Text to be placed above the component.</td> + <td>Text to be placed inside the button.</td> <td>-</td> </tr> <tr> - <td>buttonLabel</td> <td> - <TableCode>string</TableCode> + <DxcFlex direction="column" gap="var(--spacing-gap-xs)" alignItems="baseline"> + <StatusBadge status="required" /> + callbackFile + </DxcFlex> + </td> + <td> + <TableCode>{"(files: { file: File, error?: string, preview?: string }[]) => void"}</TableCode> + </td> + <td> + This function will be called when the user adds or deletes a file. That is, when the file input's inner + value is modified. The list of files will be sent as a parameter. In this function, the files can be + updated or returned as they come. These files must be passed to the value in order to be shown. </td> - <td>Text to be placed inside the button.</td> <td>-</td> </tr> + <tr> + <td>disabled</td> + <td> + <TableCode>boolean</TableCode> + </td> + <td>If true, the component will be disabled.</td> + <td> + <TableCode>false</TableCode> + </td> + </tr> <tr> <td>dropAreaLabel</td> <td> @@ -66,25 +87,21 @@ const sections = [ <td>-</td> </tr> <tr> - <td>accept</td> + <td>label</td> <td> <TableCode>string</TableCode> </td> - <td> - The file types that the component accepts. Its value must be one of all the possible values of the HTML - file input's <Code>accept</Code> attribute. Please check the documentation{" "} - <DxcLink href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept">here</DxcLink>. - </td> + <td>Text to be placed above the component.</td> <td>-</td> </tr> <tr> - <td>minSize</td> + <td>margin</td> <td> - <TableCode>number</TableCode> + <TableCode>'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge' | Margin</TableCode> </td> <td> - The minimum file size (in bytes) allowed. If the size of the file does not comply this value, the file - will have an error. + Size of the margin to be applied to the component. You can pass an object with 'top', 'bottom', 'left' and + 'right' properties in order to specify different margin sizes. </td> <td>-</td> </tr> @@ -100,43 +117,24 @@ const sections = [ <td>-</td> </tr> <tr> + <td>minSize</td> <td> - <DxcFlex direction="column" gap="var(--spacing-gap-xs)" alignItems="baseline"> - <StatusBadge status="required" /> - value - </DxcFlex> - </td> - <td> - <TableCode>{"{ file: File, error?: string, preview?: string }[]"}</TableCode> + <TableCode>number</TableCode> </td> <td> - An array of files representing the selected files. Each file has the following properties: - <ul> - <li> - <b>file</b>: Selected file. - </li> - <li> - <b>error</b>: Error of the file. If it is defined, it will be shown and the file item will be mark as - invalid. - </li> - <li> - <b>preview</b>: Preview of the file. - </li> - </ul> + The minimum file size (in bytes) allowed. If the size of the file does not comply this value, the file + will have an error. </td> <td>-</td> </tr> <tr> - <td>showPreview</td> - <td> - <TableCode>boolean</TableCode> - </td> + <td>mode</td> <td> - If true, if the file is an image, a preview of it will be shown. If not, an icon referring to the file - type will be shown. + <TableCode>'file' | 'filedrop' | 'dropzone'</TableCode> </td> + <td>Available modes of the component.</td> <td> - <TableCode>false</TableCode> + <TableCode>'file'</TableCode> </td> </tr> <tr> @@ -154,42 +152,39 @@ const sections = [ </td> </tr> <tr> - <td>disabled</td> + <td>optional</td> <td> <TableCode>boolean</TableCode> </td> - <td>If true, the component will be disabled.</td> + <td> + If true, the input will be optional, showing '(Optional)' next to the label. Otherwise, the field will be + considered required and an error will be passed as a parameter to the <TableCode>onBlur</TableCode> and{" "} + <TableCode>onChange</TableCode> functions when it has not been filled. + </td> <td> <TableCode>false</TableCode> </td> </tr> <tr> + <td>ref</td> <td> - <DxcFlex direction="column" gap="var(--spacing-gap-xs)" alignItems="baseline"> - <StatusBadge status="required" /> - callbackFile - </DxcFlex> - </td> - <td> - <TableCode>{"(files: { file: File, error?: string, preview?: string }[]) => void"}</TableCode> - </td> - <td> - This function will be called when the user adds or deletes a file. That is, when the file input's inner - value is modified. The list of files will be sent as a parameter. In this function, the files can be - updated or returned as they come. These files must be passed to the value in order to be shown. + <TableCode>{"React.Ref<HTMLDivElement>"}</TableCode> </td> + <td>Reference to the component.</td> <td>-</td> </tr> <tr> - <td>margin</td> + <td>showPreview</td> <td> - <TableCode>'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge' | Margin</TableCode> + <TableCode>boolean</TableCode> </td> <td> - Size of the margin to be applied to the component. You can pass an object with 'top', 'bottom', 'left' and - 'right' properties in order to specify different margin sizes. + If true, if the file is an image, a preview of it will be shown. If not, an icon referring to the file + type will be shown. + </td> + <td> + <TableCode>false</TableCode> </td> - <td>-</td> </tr> <tr> <td>tabIndex</td> @@ -204,11 +199,30 @@ const sections = [ </td> </tr> <tr> - <td>ref</td> <td> - <TableCode>{"React.Ref<HTMLDivElement>"}</TableCode> + <DxcFlex direction="column" gap="var(--spacing-gap-xs)" alignItems="baseline"> + <StatusBadge status="required" /> + value + </DxcFlex> + </td> + <td> + <TableCode>{"{ file: File, error?: string, preview?: string }[]"}</TableCode> + </td> + <td> + An array of files representing the selected files. Each file has the following properties: + <ul> + <li> + <b>file</b>: Selected file. + </li> + <li> + <b>error</b>: Error of the file. If it is defined, it will be shown and the file item will be mark as + invalid. + </li> + <li> + <b>preview</b>: Preview of the file. + </li> + </ul> </td> - <td>Reference to the component.</td> <td>-</td> </tr> </tbody> From 40f87d5b89565bdbc3b00458e6c008b6e5ccec7d Mon Sep 17 00:00:00 2001 From: Pelayo Felgueroso <pfelguerosogalguera@gmail.com> Date: Thu, 21 Aug 2025 09:11:53 +0200 Subject: [PATCH 4/6] Remove input error from the component --- .../file-input/code/FileInputCodePage.tsx | 10 ++++------ .../lib/src/file-input/FileInput.stories.tsx | 2 -- packages/lib/src/file-input/FileInput.tsx | 16 ---------------- 3 files changed, 4 insertions(+), 24 deletions(-) diff --git a/apps/website/screens/components/file-input/code/FileInputCodePage.tsx b/apps/website/screens/components/file-input/code/FileInputCodePage.tsx index a55128bb7c..4825913bb8 100644 --- a/apps/website/screens/components/file-input/code/FileInputCodePage.tsx +++ b/apps/website/screens/components/file-input/code/FileInputCodePage.tsx @@ -51,7 +51,9 @@ const sections = [ </DxcFlex> </td> <td> - <TableCode>{"(files: { file: File, error?: string, preview?: string }[]) => void"}</TableCode> + <TableCode> + {"(val: {files: { file: File, error?: string, preview?: string }[], error?: string}) => void"} + </TableCode> </td> <td> This function will be called when the user adds or deletes a file. That is, when the file input's inner @@ -156,11 +158,7 @@ const sections = [ <td> <TableCode>boolean</TableCode> </td> - <td> - If true, the input will be optional, showing '(Optional)' next to the label. Otherwise, the field will be - considered required and an error will be passed as a parameter to the <TableCode>onBlur</TableCode> and{" "} - <TableCode>onChange</TableCode> functions when it has not been filled. - </td> + <td>If true, the input will be optional, showing '(Optional)' next to the label.</td> <td> <TableCode>false</TableCode> </td> diff --git a/packages/lib/src/file-input/FileInput.stories.tsx b/packages/lib/src/file-input/FileInput.stories.tsx index c90a014a50..05d1cd72db 100644 --- a/packages/lib/src/file-input/FileInput.stories.tsx +++ b/packages/lib/src/file-input/FileInput.stories.tsx @@ -80,12 +80,10 @@ const FileInput = () => ( <Title title="With label" theme="light" level={4} /> <DxcFileInput label="File input" value={[]} callbackFile={() => {}} /> </ExampleContainer> - <ExampleContainer> <Title title="Optional" theme="light" level={4} /> <DxcFileInput label="File input" value={[]} callbackFile={() => {}} optional /> </ExampleContainer> - <ExampleContainer> <Title title="With label and helper text" theme="light" level={4} /> <DxcFileInput label="File input" helperText="Please select files" value={[]} callbackFile={() => {}} /> diff --git a/packages/lib/src/file-input/FileInput.tsx b/packages/lib/src/file-input/FileInput.tsx index 0baa5cbd51..ce2c12bf07 100644 --- a/packages/lib/src/file-input/FileInput.tsx +++ b/packages/lib/src/file-input/FileInput.tsx @@ -8,7 +8,6 @@ import { HalstackLanguageContext } from "../HalstackContext"; import { getFilePreview, isFileIncluded, isRequired } from "./utils"; import HelperText from "../styles/forms/HelperText"; import Label from "../styles/forms/Label"; -import ErrorMessage from "../styles/forms/ErrorMessage"; const FileInputContainer = styled.div<{ margin: FileInputPropsType["margin"] }>` display: flex; @@ -125,10 +124,8 @@ const DxcFileInput = forwardRef<RefType, FileInputPropsType>( ref ): JSX.Element => { const [isDragging, setIsDragging] = useState(false); - const [inputError, setInputError] = useState<string | undefined>(undefined); const [files, setFiles] = useState<FileData[]>([]); const fileInputId = `file-input-${useId()}`; - const errorId = `error-${fileInputId}`; const translatedLabels = useContext(HalstackLanguageContext); const checkFileSize = (file: File) => { @@ -176,7 +173,6 @@ const DxcFileInput = forwardRef<RefType, FileInputPropsType>( const fileIndex = filesCopy.indexOf(fileToRemove); filesCopy.splice(fileIndex, 1); callbackFile?.(filesCopy); - validateIsRequired(filesCopy); } }, [files, callbackFile] @@ -211,14 +207,6 @@ const DxcFileInput = forwardRef<RefType, FileInputPropsType>( } }; - const validateIsRequired = (files: FileData[]) => { - if (isRequired(files, optional)) { - setInputError(translatedLabels.formFields.requiredValueErrorMessage); - } else { - setInputError(undefined); - } - }; - useEffect(() => { const getFiles = async () => { if (value) { @@ -259,7 +247,6 @@ const DxcFileInput = forwardRef<RefType, FileInputPropsType>( /> <DxcButton mode="secondary" - semantic={inputError ? "error" : "default"} label={ buttonLabel ?? (multiple @@ -312,13 +299,11 @@ const DxcFileInput = forwardRef<RefType, FileInputPropsType>( > <DxcButton mode="secondary" - semantic={inputError ? "error" : "default"} label={buttonLabel ?? translatedLabels.fileInput.dropAreaButtonLabelDefault} onClick={handleClick} disabled={disabled} size={{ width: "fitContent", height: "medium" }} /> - {mode === "dropzone" ? ( <DropzoneLabel disabled={disabled}> {dropAreaLabel ?? @@ -354,7 +339,6 @@ const DxcFileInput = forwardRef<RefType, FileInputPropsType>( )} </Container> )} - {!disabled && inputError && <ErrorMessage error={inputError} id={errorId} />} </FileInputContainer> ); } From d91358d3ecd2bba39cbb4beb1370040a3a5b4ba0 Mon Sep 17 00:00:00 2001 From: Pelayo Felgueroso <pfelguerosogalguera@gmail.com> Date: Thu, 21 Aug 2025 11:20:39 +0200 Subject: [PATCH 5/6] Remove unnecesary function from utils.ts and its import on the component --- packages/lib/src/file-input/FileInput.tsx | 2 +- packages/lib/src/file-input/utils.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/lib/src/file-input/FileInput.tsx b/packages/lib/src/file-input/FileInput.tsx index ce2c12bf07..08aab3317b 100644 --- a/packages/lib/src/file-input/FileInput.tsx +++ b/packages/lib/src/file-input/FileInput.tsx @@ -5,7 +5,7 @@ import { spaces } from "../common/variables"; import FileItem from "./FileItem"; import FileInputPropsType, { FileData, RefType } from "./types"; import { HalstackLanguageContext } from "../HalstackContext"; -import { getFilePreview, isFileIncluded, isRequired } from "./utils"; +import { getFilePreview, isFileIncluded } from "./utils"; import HelperText from "../styles/forms/HelperText"; import Label from "../styles/forms/Label"; diff --git a/packages/lib/src/file-input/utils.ts b/packages/lib/src/file-input/utils.ts index ab3aeae9b7..cf0f4e9f40 100644 --- a/packages/lib/src/file-input/utils.ts +++ b/packages/lib/src/file-input/utils.ts @@ -25,5 +25,3 @@ export const isFileIncluded = (file: FileData, fileList: FileData[]) => { webkitRelativePath === file.file.webkitRelativePath ); }; - -export const isRequired = (value: FileData[], optional: boolean) => value.length === 0 && !optional; \ No newline at end of file From e169555a19cb3d122809367233ee083ad97902df Mon Sep 17 00:00:00 2001 From: Pelayo Felgueroso <pfelguerosogalguera@gmail.com> Date: Mon, 25 Aug 2025 13:11:34 +0200 Subject: [PATCH 6/6] Tokens foundations doc added --- apps/website/pages/foundations/tokens.tsx | 13 + apps/website/screens/common/pagesList.ts | 1 + .../screens/foundations/tokens/TokensPage.tsx | 321 ++++++++++++++++++ .../tokens/images/component_tokens.gif | Bin 0 -> 53377 bytes 4 files changed, 335 insertions(+) create mode 100644 apps/website/pages/foundations/tokens.tsx create mode 100644 apps/website/screens/foundations/tokens/TokensPage.tsx create mode 100644 apps/website/screens/foundations/tokens/images/component_tokens.gif diff --git a/apps/website/pages/foundations/tokens.tsx b/apps/website/pages/foundations/tokens.tsx new file mode 100644 index 0000000000..94653d7124 --- /dev/null +++ b/apps/website/pages/foundations/tokens.tsx @@ -0,0 +1,13 @@ +import Head from "next/head"; +import TokensPage from "screens/foundations/tokens/TokensPage"; + +const Tokens = () => ( + <> + <Head> + <title>Tokens — Halstack Design System + + + +); + +export default Tokens; \ No newline at end of file diff --git a/apps/website/screens/common/pagesList.ts b/apps/website/screens/common/pagesList.ts index c6d771d1b4..c22de2dcdc 100644 --- a/apps/website/screens/common/pagesList.ts +++ b/apps/website/screens/common/pagesList.ts @@ -45,6 +45,7 @@ const foundationsLinks: LinkDetails[] = [ { label: "Iconography", path: "/foundations/iconography" }, { label: "Spacing", path: "/foundations/spacing" }, { label: "Typography", path: "/foundations/typography" }, + { label: "Tokens", path: "/foundations/tokens" }, ]; const componentsLinks = componentsList as LinkDetails[]; diff --git a/apps/website/screens/foundations/tokens/TokensPage.tsx b/apps/website/screens/foundations/tokens/TokensPage.tsx new file mode 100644 index 0000000000..bbae0bef13 --- /dev/null +++ b/apps/website/screens/foundations/tokens/TokensPage.tsx @@ -0,0 +1,321 @@ +import Code from "@/common/Code"; +import DocFooter from "@/common/DocFooter"; +import Image from "@/common/Image"; +import PageHeading from "@/common/PageHeading"; +import DxcQuickNavContainer from "@/common/QuickNavContainer"; +import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; +import { DxcAlert, DxcBulletedList, DxcFlex, DxcHeading, DxcLink, DxcParagraph } from "@dxc-technology/halstack-react"; +import Link from "next/link"; +import componentTokens from "./images/component_tokens.gif"; + +const sections = [ + { + title: "Introduction", + content: ( + <> + + Design tokens are the single source of truth for all design decisions in the Halstack Design + System. They create a shared language between design and development, ensuring consistency across products + while maintaining flexibility where customization is needed. + + + In Halstack, tokens define core attributes like colors, typography, spacing, and more—serving + as the foundation for scalable, themeable, and reusable component styling. + + + Tokens allow product teams to build faster, with greater consistency and confidence. + + + ), + }, + { + title: "From 2-layer to 3-layer token architecture", + content: ( + <> + + + When Halstack was first established, its token structure followed a two-tier model:{" "} + primitive tokens (the raw foundational values) and component tokens (styles + applied at the component level.) The intermediate alias layer—commonly used to connect + foundations to components—was missing. + + + Over time, this gap led to inconsistencies. Each component seemed to "speak its own language," with naming + conventions, color assignments, and styling logic varying wildly between them. Without clear documentation or + a shared taxonomy, this structure is hard to maintain and harder to scale. + + + ), + subSections: [ + { + title: "Why change", + content: ( + <> + + The absence of a middle layer and a consistent naming strategy cause several challenges: + + + + Inconsistent styles: Components with similar behaviors (e.g., hover states) often used + unrelated token values. + + + Difficult maintenance:Without a systematic link between primitives and components, + updating or theming became unpredictable. + + + Limited reusability: Repeated values were not abstracted into tokens, forcing + duplication and increasing the chance of drift. + + + + ), + }, + { + title: "Where we're going", + content: ( + <> + + The refactor aims to create a three-tier token architecture: + + + + Core: The raw values. + + + Alias: Contextual tokens that translate foundations into semantic meanings. + + + Component: Tokens applied to specific UI components, referencing aliases rather than + hardcoded values. + + + + At this stage, the Core and Alias layers are already in place and + actively used in both design and development workflows. These two layers give us a solid foundation of + structured, semantic tokens that ensure consistency and scalability. + + + The next step is to extend this work into the Component layer, where tokens will be + defined specifically for UI components by referencing existing Alias tokens. This will allow us to + standardize pattern across all Halstack components and give product teams even greater flexibility when + theming. We expect to begin working on this layer in the coming months. + + + ), + }, + ], + }, + { + title: "Structure", + subSections: [ + { + title: "Core", + content: ( + <> + + The Core layer contains the fundamental, non-contextual design values that form the base + of the system. They are not tied to any component or theme; instead, they describe pure design attributes + such as specific colors or type sizes. + + Foundations in Halstack include: + + + + Color + + - Defines the base color palette, organized and measured using the OKLCH color space for accuracy and + accessibility. + + + + Typography + {" "} + - Sets font families, weights, sizes, and line-heights that serve as the system's typographic + foundation. + + + + Spacing + {" "} + - A consistent set of spacing values to control layout and component padding/margins. + + + + Border + {" "} + - Defines border widths, radius, and styles for consistent corner and edge treatment. + + + + Shadow + + - Standardized elevation styles for depth and hierarchy in UI elements. + + + + Height + {" "} + - Predefined vertical dimensions for components and layouts. + + + + ), + }, + { + title: "Alias", + content: ( + <> + + The Alias layer maps core values to semantic meanings that align with the design language + and user context. + + + For example: color/bg/primary/strong → maps to a core token like{" "} + color/primary/700 (#6F4B97) + + + By separating raw values from their semantic role, we not only enable easy theming and quick adjustments + without touching the foundational values, but also establish clear usage expectations: + whether a token should be applied as a background (bg), foreground (fg), or{" "} + border element. This reduces ambiguity and ensures that tokens are applied consistently + across components and interfaces. + + + ), + }, + { + title: "Component", + content: ( + <> + + Component tokens define the styling for each component in Halstack, referencing{" "} + Alias tokens rather than hardcoded values. + + + Example: A button's hover background might use, which itself maps to an alias token, which then points to + a core color. + + This indirection: + + Keeps component styles consistent across the system. + Simplifies global updates. + + Allows products to theme components without rewriting every style rule. + + + Component tokens + + ), + }, + ], + }, + { + title: "Halstack's theming strategy and token layers", + content: ( + <> + + Until now, Halstack has supported two theming strategies—an{" "} + opinionated approach with limited, safe customization, and an{" "} + advanced approach with broader freedom. Several products have already benefited from these + strategies. + + + With the new token architecture, our goal is to continue supporting both levels of theming,{" "} + even though the underlying structure that makes them possible will change. This ensures that product teams can + rely on the same flexibility they know today, while also benefiting from a more scalable and consistent + system. + + + By aligning this strategy with our token architecture: + + + With the Core layer, we can already enable opinionated theming, exposing + tokens like colors, spacing, or typography in a way that is safe and predictable. + + + To unlock Advanced theming, we will rely on the upcoming{" "} + Component layer, which will allow deeper overrides at the component-token level. This + work is still in progress and will be rolled out in the coming months. + + + + + This structured approach ensures that theming in Halstack is flexible but safe—giving teams + the ability to adapt components to their needs while still protecting those{" "} + design decisions that are critical for accessibility such as maintaining proper{" "} + contrast ratios, minimum font sizes, and spacing values. In this way, we preserve both{" "} + brand flexibility and usability standards across all products. + + + ), + }, + { + title: "How tokens move from design to code", + content: ( + <> + + Design tokens in Halstack are created in{" "} + + Figma + + , where they serve as the foundation for all design decisions. From there, they evolve into development-ready + assets, ensuring a seamless bridge between design and code. + + + ), + subSections: [ + { + title: "Tokens in Design", + content: ( + <> + + In Halstack, tokens are defined and maintained as Variables in Figma. These variables + represent values like colors, spacing, typography, and more, and are applied directly in the design of{" "} + + components + {" "} + and interface flows. + + + ), + }, + { + title: "Tokens in Development", + content: ( + <> + + Once validated in Figma, tokens can be exported as structured JSON files, which makes + them consumable by development teams. ..... + + + ), + }, + ], + }, +]; + +export default function TokensPage() { + return ( + <> + + + + + + + + + + + + + ); +} diff --git a/apps/website/screens/foundations/tokens/images/component_tokens.gif b/apps/website/screens/foundations/tokens/images/component_tokens.gif new file mode 100644 index 0000000000000000000000000000000000000000..ecab8b7c854a5e60ffbad5f759b10d37fa256177 GIT binary patch literal 53377 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