From 2e00044b14b10d1f060973e62349d2c6c9dab7ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20G=C3=B3mez=20Pinta?= <44321109+GomezIvann@users.noreply.github.com> Date: Tue, 25 Mar 2025 12:56:14 +0100 Subject: [PATCH 1/9] Text input redesign --- packages/lib/src/select/Select.tsx | 118 ++-- .../lib/src/styles/forms/ErrorMessage.tsx | 26 + packages/lib/src/styles/forms/HelperText.tsx | 11 + packages/lib/src/styles/forms/Label.tsx | 20 + .../src/styles/forms/inputStylesByState.tsx | 29 + packages/lib/src/text-input/Suggestion.tsx | 63 +-- packages/lib/src/text-input/Suggestions.tsx | 80 ++- .../lib/src/text-input/TextInput.stories.tsx | 409 +++++--------- packages/lib/src/text-input/TextInput.tsx | 505 +++++++----------- 9 files changed, 519 insertions(+), 742 deletions(-) create mode 100644 packages/lib/src/styles/forms/ErrorMessage.tsx create mode 100644 packages/lib/src/styles/forms/HelperText.tsx create mode 100644 packages/lib/src/styles/forms/Label.tsx create mode 100644 packages/lib/src/styles/forms/inputStylesByState.tsx diff --git a/packages/lib/src/select/Select.tsx b/packages/lib/src/select/Select.tsx index 40693d4220..6ea0e22a1a 100644 --- a/packages/lib/src/select/Select.tsx +++ b/packages/lib/src/select/Select.tsx @@ -33,6 +33,10 @@ import { import SelectPropsType, { ListOptionType, RefType } from "./types"; import DxcActionIcon from "../action-icon/ActionIcon"; import DxcFlex from "../flex/Flex"; +import ErrorMessage from "../styles/forms/ErrorMessage"; +import HelperText from "../styles/forms/HelperText"; +import Label from "../styles/forms/Label"; +import { inputStylesByState } from "../styles/forms/inputStylesByState"; const SelectContainer = styled.div<{ margin: SelectPropsType["margin"]; @@ -54,34 +58,9 @@ const SelectContainer = styled.div<{ props.margin && typeof props.margin === "object" && props.margin.left ? spaces[props.margin.left] : ""}; `; -const Label = styled.label<{ - disabled: SelectPropsType["disabled"]; - helperText: SelectPropsType["helperText"]; -}>` - 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-semibold); - ${({ helperText }) => !helperText && "margin-bottom: var(--spacing-gap-xs);"} - - /* Optional text */ - > span { - color: ${({ disabled }) => (disabled ? "var(--color-fg-neutral-medium)" : "var(--color-fg-neutral-stronger)")}; - font-weight: var(--typography-label-regular); - } -`; - -const HelperText = styled.span<{ disabled: SelectPropsType["disabled"] }>` - margin-bottom: var(--spacing-gap-xs); - 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); -`; - const Select = styled.div<{ - disabled: SelectPropsType["disabled"]; - error: SelectPropsType["error"]; + disabled: Required["disabled"]; + error: boolean; }>` position: relative; display: flex; @@ -89,24 +68,7 @@ const Select = styled.div<{ gap: var(--spacing-gap-s); height: var(--height-m); padding: var(--spacing-padding-none) var(--spacing-padding-xs); - border-radius: var(--border-radius-s); - border: var(--border-width-s) var(--border-style-default) var(--border-color-neutral-dark); - ${({ disabled, error }) => - error && !disabled && "border: var(--border-width-m) var(--border-style-default) var(--border-color-error-medium);"} - - ${({ disabled, error }) => - !disabled - ? ` - cursor: pointer; - &:hover { - border-color: ${error ? "var(--border-color-error-strong)" : "var(--border-color-primary-strong)"}; - } - &:focus-within { - outline-offset: -2px; - outline: var(--border-width-m) var(--border-style-default) var(--border-color-secondary-medium); - } - ` - : "background: var(--color-bg-neutral-lighter); border-color: var(--border-color-neutral-medium); cursor: not-allowed;"}; + ${({ disabled, error }) => inputStylesByState(disabled, error, false)} /* Collapse indicator */ > span[role="img"] { @@ -204,21 +166,6 @@ const SearchInput = styled.input` font-weight: var(--typography-label-regular); `; -const Error = styled.span` - display: flex; - align-items: center; - gap: var(--spacing-gap-xs); - color: var(--color-fg-error-medium); - font-size: var(--typography-helper-text-s); - font-weight: var(--typography-helper-text-regular, 400); - margin-top: var(--spacing-gap-xs); - - /* Error icon */ - > span[role="img"] { - font-size: var(--height-xxs); - } -`; - const DxcSelect = forwardRef( ( { @@ -483,12 +430,12 @@ const DxcSelect = forwardRef( {label && ( @@ -508,7 +455,7 @@ const DxcSelect = forwardRef( aria-labelledby={label ? labelId : undefined} aria-required={!disabled && !optional} disabled={disabled} - error={error} + error={!!error} id={selectInputId} onBlur={handleOnBlur} onClick={handleOnClick} @@ -523,14 +470,14 @@ const DxcSelect = forwardRef( {selectedOption.length} { // Avoid input to lose focus when pressed event.preventDefault(); }} - onClick={handleClearOptionsActionOnClick} tabIndex={-1} - aria-label={translatedLabels.select.actionClearSelectionTitle} > @@ -540,9 +487,9 @@ const DxcSelect = forwardRef( ( /> {searchable && ( )} {(!searchable || searchValue === "") && ( {getSelectedOptionLabel(placeholder, selectedOption)} @@ -592,40 +539,35 @@ const DxcSelect = forwardRef( { - // Avoid select to lose focus when the list is opened - event.preventDefault(); - }} onCloseAutoFocus={(event) => { // Avoid select to lose focus when the list is closed event.preventDefault(); }} + onOpenAutoFocus={(event) => { + // Avoid select to lose focus when the list is opened + event.preventDefault(); + }} + sideOffset={4} + style={{ zIndex: "2147483647" }} > - {!disabled && typeof error === "string" && ( - - {error && } - {error} - - )} + {!disabled && typeof error === "string" && } ); } diff --git a/packages/lib/src/styles/forms/ErrorMessage.tsx b/packages/lib/src/styles/forms/ErrorMessage.tsx new file mode 100644 index 0000000000..60f4c0cee2 --- /dev/null +++ b/packages/lib/src/styles/forms/ErrorMessage.tsx @@ -0,0 +1,26 @@ +import styled from "styled-components"; +import DxcIcon from "../../icon/Icon"; + +const ErrorMessageContainer = styled.span` + display: flex; + align-items: center; + gap: var(--spacing-gap-xs); + color: var(--color-fg-error-medium); + font-size: var(--typography-helper-text-s); + font-weight: var(--typography-helper-text-regular, 400); + margin-top: var(--spacing-gap-xs); + + /* Error icon */ + > span[role="img"] { + font-size: var(--height-xxs); + } +`; + +const ErrorMessage = ({ error, id }: { error: string; id: string }) => ( + + {error && } + {error} + +); + +export default ErrorMessage; diff --git a/packages/lib/src/styles/forms/HelperText.tsx b/packages/lib/src/styles/forms/HelperText.tsx new file mode 100644 index 0000000000..d1fed8a048 --- /dev/null +++ b/packages/lib/src/styles/forms/HelperText.tsx @@ -0,0 +1,11 @@ +import styled from "styled-components"; + +const HelperText = styled.span<{ disabled: 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); + margin-bottom: var(--spacing-gap-xs); +`; + +export default HelperText; \ No newline at end of file diff --git a/packages/lib/src/styles/forms/Label.tsx b/packages/lib/src/styles/forms/Label.tsx new file mode 100644 index 0000000000..445ea02084 --- /dev/null +++ b/packages/lib/src/styles/forms/Label.tsx @@ -0,0 +1,20 @@ +import styled from "styled-components"; + +const Label = styled.label<{ + disabled: boolean; + hasHelperText: boolean; +}>` + 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-semibold); + ${({ hasHelperText }) => !hasHelperText && "margin-bottom: var(--spacing-gap-xs);"} + + /* Optional text */ + > span { + color: ${({ disabled }) => (disabled ? "var(--color-fg-neutral-medium)" : "var(--color-fg-neutral-stronger)")}; + font-weight: var(--typography-label-regular); + } +`; + +export default Label; diff --git a/packages/lib/src/styles/forms/inputStylesByState.tsx b/packages/lib/src/styles/forms/inputStylesByState.tsx new file mode 100644 index 0000000000..b31a02f0ac --- /dev/null +++ b/packages/lib/src/styles/forms/inputStylesByState.tsx @@ -0,0 +1,29 @@ +import { css } from "styled-components"; + +export const inputStylesByState = (disabled: boolean, error: boolean, readOnly: boolean) => css` + border-radius: var(--border-radius-s); + border: ${!disabled && error ? "var(--border-width-m)" : "var(--border-width-s)"} var(--border-style-default) + ${(() => { + if (disabled) return "var(--border-color-neutral-strong)"; + else if (error) return "var(--border-color-error-medium)"; + else if (readOnly) return "var(--border-color-neutral-strong)"; + else return "var(--border-color-neutral-dark)"; + })()}; + cursor: pointer; + ${!disabled + ? `&:hover { + border-color: ${ + error + ? "var(--border-color-error-strong)" + : readOnly + ? "var(--border-color-neutral-stronger)" + : "var(--border-color-primary-strong)" + }; + } + &:focus, &:focus-within { + border-color: transparent; + outline-offset: -2px; + outline: var(--border-width-m) var(--border-style-default) var(--border-color-secondary-medium); + }` + : "cursor: not-allowed;"}; +`; diff --git a/packages/lib/src/text-input/Suggestion.tsx b/packages/lib/src/text-input/Suggestion.tsx index f5fcd8c00c..721936f7fb 100644 --- a/packages/lib/src/text-input/Suggestion.tsx +++ b/packages/lib/src/text-input/Suggestion.tsx @@ -2,49 +2,33 @@ import { memo, useMemo } from "react"; import styled from "styled-components"; import { SuggestionProps } from "./types"; import { transformSpecialChars } from "./utils"; +import DxcDivider from "../divider/Divider"; +import DxcFlex from "../flex/Flex"; const SuggestionContainer = styled.li<{ visuallyFocused: SuggestionProps["visuallyFocused"]; }>` display: flex; - padding: 0 0.5rem; - line-height: 1.715em; + flex-direction: column; + height: var(--height-m); + padding: var(--spacing-padding-none) var(--spacing-padding-xs); cursor: pointer; - box-shadow: inset 0 0 0 2px - ${(props) => (props.visuallyFocused ? props.theme.focusListOptionBorderColor : "transparent")}; - - &:hover { - background-color: ${(props) => props.theme.hoverListOptionBackgroundColor}; - } + &:hover, &:active { - background-color: ${(props) => props.theme.activeListOptionBackgroundColor}; + background-color: var(--color-bg-neutral-light); } + ${({ visuallyFocused }) => + visuallyFocused && + "outline: var(--border-width-m) var(--border-style-default) var(--border-color-secondary-medium); outline-offset: -2px;"} `; -const StyledSuggestion = styled.span<{ - visuallyFocused: SuggestionProps["visuallyFocused"]; - isLast: SuggestionProps["isLast"]; -}>` - width: 100%; +const StyledSuggestion = styled.span` overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - padding: 0.25rem 0.5rem 0.188rem 0.5rem; - ${(props) => - props.isLast || props.visuallyFocused - ? `border-bottom: 1px solid transparent` - : `border-bottom: 1px solid ${props.theme.listOptionDividerColor}`}; `; -const Suggestion = ({ - id, - value, - onClick, - suggestion, - isLast, - visuallyFocused, - highlighted, -}: SuggestionProps): JSX.Element => { +const Suggestion = ({ highlighted, id, isLast, onClick, suggestion, value, visuallyFocused }: SuggestionProps) => { const matchedSuggestion = useMemo(() => { const regEx = new RegExp(transformSpecialChars(value), "i"); return { matchedWords: suggestion.match(regEx), noMatchedWords: suggestion.replace(regEx, "") }; @@ -60,16 +44,19 @@ const Suggestion = ({ role="option" aria-selected={visuallyFocused ? true : undefined} > - - {highlighted ? ( - <> - {matchedSuggestion.matchedWords} - {matchedSuggestion.noMatchedWords} - - ) : ( - suggestion - )} - + + + {highlighted ? ( + <> + {matchedSuggestion.matchedWords} + {matchedSuggestion.noMatchedWords} + + ) : ( + suggestion + )} + + + {!isLast && } ); }; diff --git a/packages/lib/src/text-input/Suggestions.tsx b/packages/lib/src/text-input/Suggestions.tsx index d78912df20..00c07b8320 100644 --- a/packages/lib/src/text-input/Suggestions.tsx +++ b/packages/lib/src/text-input/Suggestions.tsx @@ -4,51 +4,45 @@ import { HalstackLanguageContext } from "../HalstackContext"; import Suggestion from "./Suggestion"; import { SuggestionsProps } from "./types"; import DxcIcon from "../icon/Icon"; +import { scrollbarStyles } from "../styles/scroll"; const SuggestionsContainer = styled.ul<{ error: boolean }>` box-sizing: border-box; - max-height: 304px; - overflow-y: auto; margin: 0; - padding: 0.25rem 0; - background-color: ${(props) => - props.error ? props.theme.errorListDialogBackgroundColor : props.theme.listDialogBackgroundColor}; - border: 1px solid - ${(props) => (props.error ? props.theme.errorListDialogBorderColor : props.theme.listDialogBorderColor)}; - - border-radius: 0.25rem; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); - color: ${(props) => props.theme.listOptionFontColor}; - font-family: ${(props) => props.theme.fontFamily}; - font-size: ${(props) => props.theme.listOptionFontSize}; - font-style: ${(props) => props.theme.listOptionFontStyle}; - font-weight: ${(props) => props.theme.listOptionFontWeight}; + max-height: 304px; + padding: var(--spacing-padding-xxs) var(--spacing-padding-none); + background-color: var(--color-bg-neutral-lightest); + border: var(--border-width-s) var(--border-style-default) var(--border-color-neutral-medium); + border-radius: var(--border-radius-s); + box-shadow: var(--shadow-mid-x-position) var(--shadow-mid-y-position) var(--shadow-mid-blur) var(--shadow-mid-spread) + var(--shadow-light); + color: var(--color-fg-neutral-dark); + font-family: var(--typography-font-family); + font-size: var(--typography-label-m); + font-weight: var(--typography-label-regular); + overflow: auto; + ${scrollbarStyles} `; const SuggestionsSystemMessage = styled.span` display: flex; - padding: 0.25rem 1rem; - color: ${(props) => props.theme.systemMessageFontColor}; - line-height: 1.715em; -`; - -const SuggestionsErrorIcon = styled.span` - display: flex; - flex-wrap: wrap; - align-content: center; - margin-right: 0.5rem; - height: 18px; - width: 18px; - font-size: 18px; - color: ${(props) => props.theme.errorIconColor}; + align-items: center; + color: var(--color-fg-neutral-strong); + height: var(--height-m); + padding: var(--spacing-padding-none) var(--spacing-padding-xs); `; -const SuggestionsError = styled.span` +const SuggestionsErrorMessage = styled.span` display: flex; - padding: 0.25rem 1rem; align-items: center; - line-height: 1.715em; - color: ${(props) => props.theme.errorListDialogFontColor}; + gap: var(--spacing-gap-s); + color: var(--color-fg-error-medium); + height: var(--height-m); + padding: var(--spacing-padding-none) var(--spacing-padding-xs); + /* Error icon */ + > span[role="img"] { + font-size: var(--height-xxs); + } `; const Suggestions = ({ @@ -61,7 +55,7 @@ const Suggestions = ({ isSearching, suggestionOnClick, styles, -}: SuggestionsProps): JSX.Element => { +}: SuggestionsProps) => { const translatedLabels = useContext(HalstackLanguageContext); const listboxRef = useRef(null); @@ -101,17 +95,19 @@ const Suggestions = ({ /> ))} {isSearching && ( - {translatedLabels.textInput.searchingMessage} +
  • + + {translatedLabels.textInput.searchingMessage} + +
  • )} {searchHasErrors && ( - - - - - +
  • + + {translatedLabels.textInput.fetchingDataErrorMessage} - - + +
  • )} ); diff --git a/packages/lib/src/text-input/TextInput.stories.tsx b/packages/lib/src/text-input/TextInput.stories.tsx index 140b97dcd7..5d7a87f1f6 100644 --- a/packages/lib/src/text-input/TextInput.stories.tsx +++ b/packages/lib/src/text-input/TextInput.stories.tsx @@ -35,15 +35,10 @@ const actionLargeIconSVG = { title: "Clock", }; -const actionLargeIconURL = { - onClick: () => {}, - icon: "search", - title: "Search", -}; - const country = ["Afghanistan"]; const countries = [ + "A very long country name just to test the ellipsis when text overflows in a suggestion", "Afghanistan", "Albania", "Algeria", @@ -68,75 +63,40 @@ const countries = [ "Djibouti", ]; -const opinionatedTheme = { - textInput: { - fontColor: "#000000", - hoverBorderColor: "#a46ede", - }, -}; - const TextInput = () => ( <> - - - <DxcTextInput label="Text input" /> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-focus-within"> - <Title title="Focused input" theme="light" level={4} /> - <DxcTextInput label="Text input" /> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-hover"> - <Title title="Hovered action" theme="light" level={4} /> - <DxcTextInput label="Text input" defaultValue="Text" clearable /> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-active"> - <Title title="Actived action" theme="light" level={4} /> - <DxcTextInput label="Text input" action={action} clearable /> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-focus"> - <Title title="Focused action" theme="light" level={4} /> - <DxcTextInput label="Text input" action={action} clearable /> - </ExampleContainer> + <Title title="States" theme="light" level={2} /> <ExampleContainer> - <Title title="Without label" theme="light" level={4} /> + <Title title="Default" theme="light" level={4} /> <DxcTextInput /> </ExampleContainer> - <ExampleContainer> - <Title title="With label and placeholder" theme="light" level={4} /> - <DxcTextInput label="Text input" placeholder="Placeholder" /> + <ExampleContainer pseudoState="pseudo-hover"> + <Title title="Hovered" theme="light" level={4} /> + <DxcTextInput /> </ExampleContainer> - <ExampleContainer> - <Title title="Helper text, optional, and clearable" theme="light" level={4} /> - <DxcTextInput label="Text input" clearable defaultValue="Text" helperText="Help message" optional /> + <ExampleContainer pseudoState="pseudo-focus-within"> + <Title title="Focused" theme="light" level={4} /> + <DxcTextInput /> </ExampleContainer> <ExampleContainer> - <Title title="Clearable and large icon action (SVG)" theme="light" level={4} /> - <DxcTextInput - label="Text input" - defaultValue="Text text text text text text text text text text" - clearable - action={actionLargeIconSVG} - /> + <Title title="Disabled" theme="light" level={4} /> + <DxcTextInput disabled placeholder="Name" /> </ExampleContainer> <ExampleContainer> - <Title title="Clearable and large icon action (URL)" theme="light" level={4} /> + <Title title="Disabled - Complete example" theme="light" level={4} /> <DxcTextInput - label="Text input" - defaultValue="Text text text text text text text text text text" - clearable - action={actionLargeIconURL} + label="Disabled" + helperText="Help text" + disabled + defaultValue="John Doe" + action={action} + optional + prefix="+34" + suffix="USD" /> </ExampleContainer> <ExampleContainer> - <Title title="Prefix" theme="light" level={4} /> - <DxcTextInput label="With prefix" prefix="+34" /> - </ExampleContainer> - <ExampleContainer> - <Title title="Suffix and action" theme="light" level={4} /> - <DxcTextInput label="With suffix" suffix="USD" action={action} /> - </ExampleContainer> - <ExampleContainer> - <Title title="Invalid" theme="light" level={4} /> + <Title title="Error" theme="light" level={4} /> <DxcTextInput label="Error text input" helperText="Help message" @@ -148,7 +108,7 @@ const TextInput = () => ( /> </ExampleContainer> <ExampleContainer pseudoState="pseudo-hover"> - <Title title="Invalid and hovered" theme="light" level={4} /> + <Title title="Hovered error" theme="light" level={4} /> <DxcTextInput label="Error text input" helperText="Help message" @@ -156,34 +116,6 @@ const TextInput = () => ( error="Error message." /> </ExampleContainer> - <ExampleContainer> - <Title title="Disabled and placeholder" theme="light" level={4} /> - <DxcTextInput label="Disabled text input" disabled placeholder="Placeholder" /> - </ExampleContainer> - <ExampleContainer> - <Title title="Disabled, helper text, optional, value and action" theme="light" level={4} /> - <DxcTextInput - label="Disabled text input" - helperText="Help message" - disabled - optional - defaultValue="Text" - action={action} - /> - </ExampleContainer> - <ExampleContainer> - <Title title="Disabled with prefix and suffix" theme="light" level={4} /> - <DxcTextInput - label="Disabled text input" - helperText="Help message" - disabled - optional - prefix="+34" - suffix="USD" - defaultValue="Text" - action={action} - /> - </ExampleContainer> <ExampleContainer> <Title title="Read only" theme="light" level={4} /> <DxcTextInput @@ -210,17 +142,28 @@ const TextInput = () => ( action={action} /> </ExampleContainer> - <ExampleContainer pseudoState="pseudo-active"> - <Title title="Active read only" theme="light" level={4} /> + <Title title="Anatomy" theme="light" level={2} />{" "} + <ExampleContainer> + <Title title="Complete example" theme="light" level={4} /> <DxcTextInput - label="Example label" - helperText="Help message" - clearable - readOnly + label="Insert your phone number" + helperText="Help text" + defaultValue="983 023 123" + action={action} optional prefix="+34" - defaultValue="Text" - action={action} + suffix="USD" + clearable + /> + </ExampleContainer> + <ExampleContainer> + <Title title="Text ellipsis and large icon action (SVG)" theme="light" level={4} /> + <DxcTextInput + label="Text input" + defaultValue="Text text text text text text text text text text" + clearable + action={actionLargeIconSVG} + suffix="SUFFIX" /> </ExampleContainer> <Title title="Margins" theme="light" level={2} /> @@ -277,197 +220,133 @@ const TextInput = () => ( <DxcTextInput label="Text input" size="large" /> </DxcFlex> </ExampleContainer> - <Title title="Opinionated theme" theme="light" level={2} /> - <ExampleContainer> - <HalstackProvider theme={opinionatedTheme}> - <ExampleContainer pseudoState="pseudo-hover"> - <Title title="Hovered input" theme="light" level={4} /> - <DxcTextInput label="Text input" helperText="Help message" /> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-focus-within"> - <Title title="Focused input" theme="light" level={4} /> - <DxcTextInput label="Text input" helperText="Help message" /> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-hover"> - <Title title="Hovered action" theme="light" level={4} /> - <DxcTextInput label="Text input" helperText="Help message" defaultValue="Text" clearable /> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-active"> - <Title title="Actived action" theme="light" level={4} /> - <DxcTextInput label="Text input" helperText="Help message" action={action} clearable /> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-focus"> - <Title title="Focused action" theme="light" level={4} /> - <DxcTextInput label="Text input" helperText="Help message" action={action} clearable /> - </ExampleContainer> - <ExampleContainer> - <Title title="Prefix" theme="light" level={4} /> - <DxcTextInput label="With prefix" prefix="+34" helperText="Help message" /> - </ExampleContainer> - <ExampleContainer> - <Title title="Suffix and action" theme="light" level={4} /> - <DxcTextInput label="With suffix" helperText="Help message" suffix="USD" action={action} /> - </ExampleContainer> - <ExampleContainer> - <Title title="Invalid" theme="light" level={4} /> - <DxcTextInput - label="Error text input" - helperText="Help message" - error="Error message." - defaultValue="Text" - clearable - optional - action={action} - /> - </ExampleContainer> - <ExampleContainer> - <Title title="Disabled and placeholder" theme="light" level={4} /> - <DxcTextInput label="Disabled text input" disabled placeholder="Placeholder" prefix="+34" suffix="USD" /> - </ExampleContainer> - <ExampleContainer> - <Title title="Disabled, helper text, optional, value and action" theme="light" level={4} /> - <DxcTextInput - label="Disabled text input" - helperText="Help message" - disabled - optional - defaultValue="Text" - action={action} - /> - </ExampleContainer> - </HalstackProvider> - </ExampleContainer> </> ); -const AutosuggestListbox = () => { - const colorsTheme: any = useContext(HalstackContext); - - return ( - <ThemeProvider theme={colorsTheme.textInput}> +const AutosuggestListbox = () => ( + <> + <ExampleContainer> + <Title title="Autosuggest listbox" theme="light" level={2} /> <ExampleContainer> - <Title title="Autosuggest listbox" theme="light" level={2} /> - <ExampleContainer> - <Title - title="List dialog uses a Radix Popover to appear over elements with a certain z-index" - theme="light" - level={3} - /> - <div - style={{ - display: "flex", - flexDirection: "column", - gap: "20px", - height: "150px", - width: "500px", - marginBottom: "250px", - padding: "20px", - border: "1px solid black", - borderRadius: "4px", - overflow: "auto", - zIndex: "1300", - position: "relative", - }} - > - <DxcTextInput - label="Label" - suggestions={countries} - optional - placeholder="Choose an option" - size="fillParent" - /> - <button style={{ zIndex: "1", width: "100px" }}>Submit</button> - </div> - </ExampleContainer> - <Title title="Listbox suggestion states" theme="light" level={3} /> - <ExampleContainer pseudoState="pseudo-hover"> - <Title title="Hovered suggestion" theme="light" level={4} /> - <Suggestions - id="x1" - value="" - suggestions={country} - visualFocusIndex={-1} - highlightedSuggestions={false} - searchHasErrors={false} - isSearching={false} - suggestionOnClick={() => {}} - styles={{ width: 350 }} - /> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-active"> - <Title title="Active suggestion" theme="light" level={4} /> - <Suggestions - id="x2" - value="" - suggestions={country} - visualFocusIndex={-1} - highlightedSuggestions={false} - searchHasErrors={false} - isSearching={false} - suggestionOnClick={(suggestion) => {}} - styles={{ width: 350 }} - /> - </ExampleContainer> - <ExampleContainer> - <Title title="Focused suggestion" theme="light" level={4} /> - <Suggestions - id="x3" - value="" - suggestions={country} - visualFocusIndex={0} - highlightedSuggestions={false} - searchHasErrors={false} - isSearching={false} - suggestionOnClick={(suggestion) => {}} - styles={{ width: 350 }} - /> - </ExampleContainer> - <ExampleContainer> - <Title title="Highlighted suggestion" theme="light" level={4} /> - <Suggestions - id="x4" - value="Afgh" - suggestions={country} - visualFocusIndex={-1} - highlightedSuggestions={true} - searchHasErrors={false} - isSearching={false} - suggestionOnClick={(suggestion) => {}} - styles={{ width: 350 }} + <Title + title="List dialog uses a Radix Popover to appear over elements with a certain z-index" + theme="light" + level={3} + /> + <div + style={{ + display: "flex", + flexDirection: "column", + gap: "20px", + height: "150px", + width: "500px", + marginBottom: "250px", + padding: "20px", + border: "1px solid black", + borderRadius: "4px", + overflow: "auto", + zIndex: "1300", + position: "relative", + }} + > + <DxcTextInput + label="Label" + suggestions={countries} + optional + placeholder="Choose an option" + size="fillParent" /> - </ExampleContainer> + <button style={{ zIndex: "1", width: "100px" }}>Submit</button> + </div> </ExampleContainer> - <ExampleContainer> - <Title title="Autosuggest Error" theme="light" level={3} /> + <Title title="Listbox suggestion states" theme="light" level={3} /> + <ExampleContainer pseudoState="pseudo-hover"> + <Title title="Hovered suggestion" theme="light" level={4} /> <Suggestions - id="x5" + id="x1" value="" suggestions={country} visualFocusIndex={-1} highlightedSuggestions={false} - searchHasErrors={true} + searchHasErrors={false} isSearching={false} - suggestionOnClick={(suggestion) => {}} + suggestionOnClick={() => {}} styles={{ width: 350 }} /> </ExampleContainer> - <ExampleContainer> - <Title title="Autosuggest Searching message" theme="light" level={3} /> + <ExampleContainer pseudoState="pseudo-active"> + <Title title="Active suggestion" theme="light" level={4} /> <Suggestions - id="x6" + id="x2" value="" suggestions={country} visualFocusIndex={-1} highlightedSuggestions={false} searchHasErrors={false} - isSearching={true} - suggestionOnClick={(suggestion) => {}} + isSearching={false} + suggestionOnClick={() => {}} styles={{ width: 350 }} /> </ExampleContainer> - </ThemeProvider> - ); -}; + <ExampleContainer> + <Title title="Focused suggestion" theme="light" level={4} /> + <Suggestions + id="x3" + value="" + suggestions={country} + visualFocusIndex={0} + highlightedSuggestions={false} + searchHasErrors={false} + isSearching={false} + suggestionOnClick={() => {}} + styles={{ width: 350 }} + /> + </ExampleContainer> + <ExampleContainer> + <Title title="Highlighted suggestion" theme="light" level={4} /> + <Suggestions + id="x4" + value="Afgh" + suggestions={country} + visualFocusIndex={-1} + highlightedSuggestions={true} + searchHasErrors={false} + isSearching={false} + suggestionOnClick={() => {}} + styles={{ width: 350 }} + /> + </ExampleContainer> + </ExampleContainer> + <ExampleContainer> + <Title title="Autosuggest Error" theme="light" level={3} /> + <Suggestions + id="x5" + value="" + suggestions={country} + visualFocusIndex={-1} + highlightedSuggestions={false} + searchHasErrors={true} + isSearching={false} + suggestionOnClick={() => {}} + styles={{ width: 350 }} + /> + </ExampleContainer> + <ExampleContainer> + <Title title="Autosuggest Searching message" theme="light" level={3} /> + <Suggestions + id="x6" + value="" + suggestions={country} + visualFocusIndex={-1} + highlightedSuggestions={false} + searchHasErrors={false} + isSearching={true} + suggestionOnClick={() => {}} + styles={{ width: 350 }} + /> + </ExampleContainer> + </> +); type Story = StoryObj<typeof DxcTextInput>; diff --git a/packages/lib/src/text-input/TextInput.tsx b/packages/lib/src/text-input/TextInput.tsx index a28111da97..0ca40e11a0 100644 --- a/packages/lib/src/text-input/TextInput.tsx +++ b/packages/lib/src/text-input/TextInput.tsx @@ -12,13 +12,12 @@ import { useState, WheelEvent, } from "react"; -import styled, { ThemeProvider } from "styled-components"; +import styled from "styled-components"; import DxcActionIcon from "../action-icon/ActionIcon"; import { spaces } from "../common/variables"; import DxcFlex from "../flex/Flex"; -import DxcIcon from "../icon/Icon"; import NumberInputContext from "../number-input/NumberInputContext"; -import HalstackContext, { HalstackLanguageContext } from "../HalstackContext"; +import { HalstackLanguageContext } from "../HalstackContext"; import useWidth from "../utils/useWidth"; import Suggestions from "./Suggestions"; import TextInputPropsType, { AutosuggestWrapperProps, RefType } from "./types"; @@ -31,6 +30,10 @@ import { makeCancelable, patternMismatch, } from "./utils"; +import HelperText from "../styles/forms/HelperText"; +import Label from "../styles/forms/Label"; +import ErrorMessage from "../styles/forms/ErrorMessage"; +import { inputStylesByState } from "../styles/forms/inputStylesByState"; const TextInputContainer = styled.div<{ margin: TextInputPropsType["margin"]; @@ -39,162 +42,62 @@ const TextInputContainer = styled.div<{ box-sizing: border-box; display: flex; flex-direction: column; - width: ${(props) => calculateWidth(props.margin, props.size)}; - ${(props) => props.size !== "fillParent" && `min-width:${calculateWidth(props.margin, props.size)}`}; - margin: ${(props) => (props.margin && typeof props.margin !== "object" ? spaces[props.margin] : "0px")}; - margin-top: ${(props) => - props.margin && typeof props.margin === "object" && props.margin.top ? spaces[props.margin.top] : ""}; - margin-right: ${(props) => - props.margin && typeof props.margin === "object" && props.margin.right ? spaces[props.margin.right] : ""}; - margin-bottom: ${(props) => - props.margin && typeof props.margin === "object" && props.margin.bottom ? spaces[props.margin.bottom] : ""}; - margin-left: ${(props) => - props.margin && typeof props.margin === "object" && props.margin.left ? spaces[props.margin.left] : ""}; - font-family: ${(props) => props.theme.fontFamily}; + width: ${({ margin, size }) => calculateWidth(margin, size)}; + ${({ margin, size }) => size !== "fillParent" && `min-width:${calculateWidth(margin, size)}`}; + margin: ${({ margin }) => (margin && typeof margin !== "object" ? spaces[margin] : "0px")}; + margin-top: ${({ margin }) => (margin && typeof margin === "object" && margin.top ? spaces[margin.top] : "")}; + margin-right: ${({ margin }) => (margin && typeof margin === "object" && margin.right ? spaces[margin.right] : "")}; + margin-bottom: ${({ margin }) => + margin && typeof margin === "object" && margin.bottom ? spaces[margin.bottom] : ""}; + margin-left: ${({ margin }) => (margin && typeof margin === "object" && margin.left ? spaces[margin.left] : "")}; `; -const Label = styled.label<{ - disabled: TextInputPropsType["disabled"]; - hasHelperText: boolean; -}>` - color: ${(props) => (props.disabled ? props.theme.disabledLabelFontColor : props.theme.labelFontColor)}; - font-size: ${(props) => props.theme.labelFontSize}; - font-style: ${(props) => props.theme.labelFontStyle}; - font-weight: ${(props) => props.theme.labelFontWeight}; - line-height: ${(props) => props.theme.labelLineHeight}; - ${(props) => !props.hasHelperText && `margin-bottom: 0.25rem`} -`; - -const OptionalLabel = styled.span` - font-weight: ${(props) => props.theme.optionalLabelFontWeight}; -`; - -const HelperText = styled.span<{ disabled: TextInputPropsType["disabled"] }>` - color: ${(props) => (props.disabled ? props.theme.disabledHelperTextFontColor : props.theme.helperTextFontColor)}; - font-size: ${(props) => props.theme.helperTextFontSize}; - font-style: ${(props) => props.theme.helperTextFontStyle}; - font-weight: ${(props) => props.theme.helperTextFontWeight}; - line-height: ${(props) => props.theme.helperTextLineHeight}; - margin-bottom: 0.25rem; -`; - -const InputContainer = styled.div<{ - disabled: TextInputPropsType["disabled"]; - readOnly: TextInputPropsType["readOnly"]; +const TextInput = styled.div<{ + disabled: Required<TextInputPropsType>["disabled"]; error: boolean; + readOnly: Required<TextInputPropsType>["readOnly"]; }>` position: relative; display: flex; align-items: center; - height: calc(2.5rem - 2px); - padding: 0 0.5rem; - - ${(props) => props.disabled && `background-color: ${props.theme.disabledContainerFillColor};`} - box-shadow: 0 0 0 2px transparent; - border-radius: 4px; - border: 1px solid - ${(props) => - props.disabled - ? props.theme.disabledBorderColor - : props.readOnly - ? props.theme.readOnlyBorderColor - : props.theme.enabledBorderColor}; - ${(props) => - props.error && - !props.disabled && - `border-color: transparent; - box-shadow: 0 0 0 2px ${props.theme.errorBorderColor}; - `} - ${(props) => - !props.disabled - ? ` - &:hover { - border-color: ${ - props.error - ? "transparent" - : props.readOnly - ? props.theme.hoverReadOnlyBorderColor - : props.theme.hoverBorderColor - }; - ${props.error ? `box-shadow: 0 0 0 2px ${props.theme.hoverErrorBorderColor};` : ""} - } - &:focus-within { - border-color: transparent; - box-shadow: 0 0 0 2px ${props.theme.focusBorderColor}; - } - ` - : "cursor: not-allowed;"}; + gap: var(--spacing-gap-s); + height: var(--height-m); + padding: var(--spacing-padding-none) var(--spacing-padding-xs); + ${({ disabled, error, readOnly }) => inputStylesByState(disabled, error, readOnly)} `; const Input = styled.input` - height: calc(2.5rem - 2px); - width: 100%; background: none; border: none; outline: none; - padding: 0 0.5rem; + padding: 0; + 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); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - color: ${(props) => (props.disabled ? props.theme.disabledValueFontColor : props.theme.valueFontColor)}; - font-family: ${(props) => props.theme.fontFamily}; - font-size: ${(props) => props.theme.valueFontSize}; - font-style: ${(props) => props.theme.valueFontStyle}; - font-weight: ${(props) => props.theme.valueFontWeight}; - line-height: 1.5em; - ${(props) => props.disabled && `cursor: not-allowed;`} ::placeholder { - color: ${(props) => (props.disabled ? props.theme.disabledPlaceholderFontColor : props.theme.placeholderFontColor)}; + color: ${({ disabled }) => (disabled ? "var(--color-fg-neutral-medium)" : "var(--color-fg-neutral-strong)")}; } + ${({ disabled }) => disabled && "cursor: not-allowed;"} `; -const Prefix = styled.span<{ disabled: TextInputPropsType["disabled"] }>` - height: 1.5rem; - margin-left: 0.25rem; - padding-right: ${(props) => props.theme.prefixDividerPaddingRight}; - ${(props) => { - const color = props.disabled ? props.theme.disabledPrefixColor : props.theme.prefixColor; - return `color: ${color}; border-right: ${props.theme.prefixDividerBorderWidth} ${props.theme.prefixDividerBorderStyle} ${color};`; - }}; - font-size: 1rem; - line-height: 1.5rem; - pointer-events: none; -`; - -const Suffix = styled.span<{ disabled: TextInputPropsType["disabled"] }>` - height: 1.5rem; - margin: 0 0.25rem; - padding-left: ${(props) => props.theme.suffixDividerPaddingLeft}; - ${(props) => { - const color = props.disabled ? props.theme.disabledSuffixColor : props.theme.suffixColor; - return `color: ${color}; border-left: ${props.theme.suffixDividerBorderWidth} ${props.theme.suffixDividerBorderStyle} ${color};`; +const Addon = styled.span<{ disabled: TextInputPropsType["disabled"]; type: "prefix" | "suffix" }>` + ${({ disabled, type }) => { + const color = disabled ? "var(--color-fg-neutral-medium)" : "var(--color-fg-neutral-stronger)"; + return `color: ${color}; border-${type === "prefix" ? "right" : "left"}: var(--border-width-s) var(--border-style-default) ${color};`; }}; - font-size: 1rem; - line-height: 1.5rem; + font-family: var(--typography-font-family); + font-size: var(--typography-label-m); + font-weight: var(--typography-label-regular); + ${({ type }) => `padding-${type === "prefix" ? "right" : "left"}: var(--spacing-padding-xs);`} pointer-events: none; `; -const ErrorIcon = styled.span` - display: flex; - flex-wrap: wrap; - align-content: center; - padding: 3px; - height: 18px; - width: 18px; - font-size: 18px; - color: ${(props) => props.theme.errorIconColor}; -`; - -const ErrorMessageContainer = styled.span` - min-height: 1.5em; - color: ${(props) => props.theme.errorMessageColor}; - font-size: 0.75rem; - font-weight: 400; - line-height: 1.5em; - margin-top: 0.25rem; -`; - const AutosuggestWrapper = ({ condition, wrapper, children }: AutosuggestWrapperProps): JSX.Element => ( <>{condition ? wrapper(children) : children}</> ); @@ -202,53 +105,48 @@ const AutosuggestWrapper = ({ condition, wrapper, children }: AutosuggestWrapper const DxcTextInput = forwardRef<RefType, TextInputPropsType>( ( { - label, - name = "", - defaultValue = "", - value, - helperText, - placeholder = "", action, + ariaLabel = "Text input", + autocomplete = "off", clearable = false, + defaultValue = "", disabled = false, - readOnly = false, + error, + helperText, + label, + margin, + maxLength, + minLength, + name = "", optional = false, + placeholder = "", prefix = "", + readOnly = false, suffix = "", - onChange, onBlur, - error, - suggestions, + onChange, pattern, - minLength, - maxLength, - autocomplete = "off", - margin, size = "medium", + suggestions, tabIndex = 0, - ariaLabel = "Text input", + value, }, ref - ): JSX.Element => { + ) => { const inputId = `input-${useId()}`; const autosuggestId = `suggestions-${inputId}`; const errorId = `error-${inputId}`; - - const colorsTheme = useContext(HalstackContext); const translatedLabels = useContext(HalstackLanguageContext); const numberInputContext = useContext(NumberInputContext); - const inputRef = useRef<HTMLInputElement | null>(null); const inputContainerRef = useRef<HTMLDivElement | null>(null); const actionRef = useRef<HTMLButtonElement | null>(null); - const [innerValue, setInnerValue] = useState(defaultValue); const [isOpen, changeIsOpen] = useState(false); const [isSearching, changeIsSearching] = useState(false); const [isAutosuggestError, changeIsAutosuggestError] = useState(false); const [filteredSuggestions, changeFilteredSuggestions] = useState<string[]>([]); const [visualFocusIndex, changeVisualFocusIndex] = useState(-1); - const width = useWidth(inputContainerRef.current); const getNumberErrorMessage = (checkedValue: number) => @@ -489,18 +387,10 @@ const DxcTextInput = forwardRef<RefType, TextInputPropsType>( }; const setNumberProps = (type?: string, min?: number, max?: number, step?: number) => { - if (min != null) { - inputRef.current?.setAttribute("min", min.toString()); - } - if (max != null) { - inputRef.current?.setAttribute("max", max.toString()); - } - if (step != null) { - inputRef.current?.setAttribute("step", step.toString()); - } - if (type != null) { - inputRef.current?.setAttribute("type", type); - } + if (min != null) inputRef.current?.setAttribute("min", min.toString()); + if (max != null) inputRef.current?.setAttribute("max", max.toString()); + if (step != null) inputRef.current?.setAttribute("step", step.toString()); + if (type != null) inputRef.current?.setAttribute("type", type); }; useEffect(() => { @@ -538,167 +428,164 @@ const DxcTextInput = forwardRef<RefType, TextInputPropsType>( numberInputContext.typeNumber, numberInputContext.minNumber, numberInputContext.maxNumber, - numberInputContext.stepNumber, + numberInputContext.stepNumber ); } return undefined; }, [value, innerValue, suggestions, numberInputContext]); return ( - <ThemeProvider theme={colorsTheme.textInput}> - <TextInputContainer margin={margin} size={size} ref={ref}> - {label && ( - <Label htmlFor={inputId} disabled={disabled} hasHelperText={!!helperText}> - {label} {optional && <OptionalLabel>{translatedLabels.formFields.optionalLabel}</OptionalLabel>} - </Label> - )} - {helperText && <HelperText disabled={disabled}>{helperText}</HelperText>} - <AutosuggestWrapper - condition={hasSuggestions(suggestions)} - wrapper={(children) => ( - <Popover.Root open={isOpen && (filteredSuggestions.length > 0 || isSearching || isAutosuggestError)}> - <Popover.Trigger - asChild - type={undefined} - aria-controls={undefined} - aria-haspopup={undefined} - aria-expanded={undefined} + <TextInputContainer margin={margin} ref={ref} size={size}> + {label && ( + <Label disabled={disabled} hasHelperText={!!helperText} htmlFor={inputId}> + {label} {optional && <span>{translatedLabels.formFields.optionalLabel}</span>} + </Label> + )} + {helperText && <HelperText disabled={disabled}>{helperText}</HelperText>} + <AutosuggestWrapper + condition={hasSuggestions(suggestions)} + wrapper={(children) => ( + <Popover.Root open={isOpen && (filteredSuggestions.length > 0 || isSearching || isAutosuggestError)}> + <Popover.Trigger + aria-controls={undefined} + aria-expanded={undefined} + aria-haspopup={undefined} + asChild + type={undefined} + > + {children} + </Popover.Trigger> + <Popover.Portal> + <Popover.Content + onCloseAutoFocus={(event) => { + // Avoid select to lose focus when the list is closed + event.preventDefault(); + }} + onOpenAutoFocus={(event) => { + // Avoid select to lose focus when the list is opened + event.preventDefault(); + }} + sideOffset={4} + style={{ zIndex: "2147483647" }} > - {children} - </Popover.Trigger> - <Popover.Portal> - <Popover.Content - sideOffset={5} - style={{ zIndex: "2147483647" }} - onOpenAutoFocus={(event) => { - // Avoid select to lose focus when the list is opened - event.preventDefault(); - }} - onCloseAutoFocus={(event) => { - // Avoid select to lose focus when the list is closed - event.preventDefault(); + <Suggestions + highlightedSuggestions={typeof suggestions !== "function"} + id={autosuggestId} + isSearching={isSearching} + searchHasErrors={isAutosuggestError} + suggestionOnClick={(suggestion) => { + changeValue(suggestion); + closeSuggestions(); }} - > - <Suggestions - id={autosuggestId} - value={value ?? innerValue} - suggestions={filteredSuggestions} - visualFocusIndex={visualFocusIndex} - highlightedSuggestions={typeof suggestions !== "function"} - searchHasErrors={isAutosuggestError} - isSearching={isSearching} - suggestionOnClick={(suggestion) => { - changeValue(suggestion); - closeSuggestions(); - }} - styles={{ width }} - /> - </Popover.Content> - </Popover.Portal> - </Popover.Root> - )} + suggestions={filteredSuggestions} + styles={{ width }} + value={value ?? innerValue} + visualFocusIndex={visualFocusIndex} + /> + </Popover.Content> + </Popover.Portal> + </Popover.Root> + )} + > + <TextInput + disabled={disabled} + error={!!error} + onClick={handleInputContainerOnClick} + onMouseDown={handleInputContainerOnMouseDown} + readOnly={readOnly} + ref={inputContainerRef} > - <InputContainer - error={!!error} + {prefix && ( + <Addon disabled={disabled} type="prefix"> + {prefix} + </Addon> + )} + <Input + aria-activedescendant={ + hasSuggestions(suggestions) && isOpen && visualFocusIndex !== -1 + ? `suggestion-${visualFocusIndex}` + : undefined + } + aria-autocomplete={hasSuggestions(suggestions) ? "list" : undefined} + aria-controls={hasSuggestions(suggestions) ? autosuggestId : undefined} + aria-errormessage={error ? errorId : undefined} + aria-expanded={hasSuggestions(suggestions) ? isOpen : undefined} + aria-haspopup={hasSuggestions(suggestions) ? "listbox" : undefined} + aria-invalid={!!error} + aria-label={label ? undefined : ariaLabel} + aria-required={!disabled && !optional} + autoComplete={autocomplete === "off" ? "nope" : autocomplete} disabled={disabled} + id={inputId} + name={name} + onBlur={handleInputOnBlur} + onChange={handleInputOnChange} + onFocus={!readOnly ? openSuggestions : undefined} + onKeyDown={!readOnly ? handleInputOnKeyDown : undefined} + onMouseDown={(event) => { + event.stopPropagation(); + }} + onWheel={numberInputContext?.typeNumber === "number" ? handleNumberInputWheel : undefined} + placeholder={placeholder} + pattern={pattern} readOnly={readOnly} - onClick={handleInputContainerOnClick} - onMouseDown={handleInputContainerOnMouseDown} - ref={inputContainerRef} - > - {prefix && <Prefix disabled={disabled}>{prefix}</Prefix>} - <DxcFlex gap="0.25rem" alignItems="center" grow={1}> - <Input - id={inputId} - name={name} - value={value ?? innerValue} - placeholder={placeholder} - onBlur={handleInputOnBlur} - onChange={handleInputOnChange} - onFocus={!readOnly ? openSuggestions : undefined} - onKeyDown={!readOnly ? handleInputOnKeyDown : undefined} - onMouseDown={(event) => { - event.stopPropagation(); - }} - onWheel={numberInputContext?.typeNumber === "number" ? handleNumberInputWheel : undefined} - disabled={disabled} - readOnly={readOnly} - ref={inputRef} - pattern={pattern} - minLength={minLength} - maxLength={maxLength} - autoComplete={autocomplete === "off" ? "nope" : autocomplete} + ref={inputRef} + role={hasSuggestions(suggestions) ? "combobox" : undefined} + maxLength={maxLength} + minLength={minLength} + tabIndex={tabIndex} + type="text" + value={value ?? innerValue} + /> + <DxcFlex> + {!disabled && !readOnly && clearable && (value ?? innerValue).length > 0 && ( + <DxcActionIcon + icon="close" + onClick={handleClearActionOnClick} tabIndex={tabIndex} - type="text" - role={hasSuggestions(suggestions) ? "combobox" : undefined} - aria-autocomplete={hasSuggestions(suggestions) ? "list" : undefined} - aria-controls={hasSuggestions(suggestions) ? autosuggestId : undefined} - aria-expanded={hasSuggestions(suggestions) ? isOpen : undefined} - aria-haspopup={hasSuggestions(suggestions) ? "listbox" : undefined} - aria-activedescendant={ - hasSuggestions(suggestions) && isOpen && visualFocusIndex !== -1 - ? `suggestion-${visualFocusIndex}` - : undefined - } - aria-invalid={!!error} - aria-errormessage={error ? errorId : undefined} - aria-required={!disabled && !optional} - aria-label={label ? undefined : ariaLabel} + title={translatedLabels.textInput.clearFieldActionTitle} /> - {!disabled && error && ( - <ErrorIcon aria-hidden="true"> - <DxcIcon icon="filled_error" /> - </ErrorIcon> - )} - {!disabled && !readOnly && clearable && (value ?? innerValue).length > 0 && ( + )} + {numberInputContext?.typeNumber === "number" && numberInputContext?.showControls && ( + <> <DxcActionIcon - onClick={handleClearActionOnClick} - icon="close" + disabled={disabled} + icon="remove" + onClick={!readOnly ? handleDecrementActionOnClick : undefined} + ref={actionRef} tabIndex={tabIndex} - title={translatedLabels.textInput.clearFieldActionTitle} + title={translatedLabels.numberInput.decrementValueTitle} /> - )} - {numberInputContext?.typeNumber === "number" && numberInputContext?.showControls && ( - <> - <DxcActionIcon - onClick={!readOnly ? handleDecrementActionOnClick : undefined} - icon="remove" - tabIndex={tabIndex} - ref={actionRef} - title={translatedLabels.numberInput.decrementValueTitle} - disabled={disabled} - /> - <DxcActionIcon - onClick={!readOnly ? handleIncrementActionOnClick : undefined} - icon="add" - tabIndex={tabIndex} - ref={actionRef} - title={translatedLabels.numberInput.incrementValueTitle} - disabled={disabled} - /> - </> - )} - {action && ( <DxcActionIcon - onClick={!readOnly ? action.onClick : undefined} - icon={action.icon} - tabIndex={tabIndex} - ref={actionRef} - title={action.title ?? ""} disabled={disabled} + icon="add" + onClick={!readOnly ? handleIncrementActionOnClick : undefined} + ref={actionRef} + tabIndex={tabIndex} + title={translatedLabels.numberInput.incrementValueTitle} /> - )} - </DxcFlex> - {suffix && <Suffix disabled={disabled}>{suffix}</Suffix>} - </InputContainer> - </AutosuggestWrapper> - {!disabled && typeof error === "string" && ( - <ErrorMessageContainer id={errorId} role="alert" aria-live={error ? "assertive" : "off"}> - {error} - </ErrorMessageContainer> - )} - </TextInputContainer> - </ThemeProvider> + </> + )} + {action && ( + <DxcActionIcon + disabled={disabled} + icon={action.icon} + onClick={!readOnly ? action.onClick : undefined} + ref={actionRef} + tabIndex={tabIndex} + title={action.title ?? ""} + /> + )} + </DxcFlex> + {suffix && ( + <Addon disabled={disabled} type="suffix"> + {suffix} + </Addon> + )} + </TextInput> + </AutosuggestWrapper> + {!disabled && typeof error === "string" && <ErrorMessage error={error} id={errorId} />} + </TextInputContainer> ); } ); From 203b7a8e58e1db3f7362df9251ef5d0e6b6338cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20G=C3=B3mez=20Pinta?= <44321109+GomezIvann@users.noreply.github.com> Date: Tue, 25 Mar 2025 13:53:44 +0100 Subject: [PATCH 2/9] Accessibility updates to the Autosuggest structure --- .../lib/src/styles/forms/ErrorMessage.tsx | 2 +- packages/lib/src/text-input/Suggestion.tsx | 4 +- packages/lib/src/text-input/Suggestions.tsx | 95 +++++++++---------- .../lib/src/text-input/TextInput.stories.tsx | 64 ++++++------- .../lib/src/text-input/TextInput.test.tsx | 13 +-- 5 files changed, 85 insertions(+), 93 deletions(-) diff --git a/packages/lib/src/styles/forms/ErrorMessage.tsx b/packages/lib/src/styles/forms/ErrorMessage.tsx index 60f4c0cee2..95095a3e3c 100644 --- a/packages/lib/src/styles/forms/ErrorMessage.tsx +++ b/packages/lib/src/styles/forms/ErrorMessage.tsx @@ -1,7 +1,7 @@ import styled from "styled-components"; import DxcIcon from "../../icon/Icon"; -const ErrorMessageContainer = styled.span` +const ErrorMessageContainer = styled.div` display: flex; align-items: center; gap: var(--spacing-gap-xs); diff --git a/packages/lib/src/text-input/Suggestion.tsx b/packages/lib/src/text-input/Suggestion.tsx index 721936f7fb..97336517f0 100644 --- a/packages/lib/src/text-input/Suggestion.tsx +++ b/packages/lib/src/text-input/Suggestion.tsx @@ -36,13 +36,13 @@ const Suggestion = ({ highlighted, id, isLast, onClick, suggestion, value, visua return ( <SuggestionContainer + aria-selected={visuallyFocused ? true : undefined} id={id} onClick={() => { onClick(suggestion); }} - visuallyFocused={visuallyFocused} role="option" - aria-selected={visuallyFocused ? true : undefined} + visuallyFocused={visuallyFocused} > <DxcFlex alignItems="center" grow={1}> <StyledSuggestion> diff --git a/packages/lib/src/text-input/Suggestions.tsx b/packages/lib/src/text-input/Suggestions.tsx index 00c07b8320..a4b4eacd66 100644 --- a/packages/lib/src/text-input/Suggestions.tsx +++ b/packages/lib/src/text-input/Suggestions.tsx @@ -6,16 +6,15 @@ import { SuggestionsProps } from "./types"; import DxcIcon from "../icon/Icon"; import { scrollbarStyles } from "../styles/scroll"; -const SuggestionsContainer = styled.ul<{ error: boolean }>` - box-sizing: border-box; - margin: 0; - max-height: 304px; - padding: var(--spacing-padding-xxs) var(--spacing-padding-none); +const SuggestionsContainer = styled.div` background-color: var(--color-bg-neutral-lightest); border: var(--border-width-s) var(--border-style-default) var(--border-color-neutral-medium); border-radius: var(--border-radius-s); box-shadow: var(--shadow-mid-x-position) var(--shadow-mid-y-position) var(--shadow-mid-blur) var(--shadow-mid-spread) var(--shadow-light); + box-sizing: border-box; + max-height: 304px; + padding: var(--spacing-padding-xxs) var(--spacing-padding-none); color: var(--color-fg-neutral-dark); font-family: var(--typography-font-family); font-size: var(--typography-label-m); @@ -32,7 +31,7 @@ const SuggestionsSystemMessage = styled.span` padding: var(--spacing-padding-none) var(--spacing-padding-xs); `; -const SuggestionsErrorMessage = styled.span` +const SuggestionsErrorMessage = styled.div` display: flex; align-items: center; gap: var(--spacing-gap-s); @@ -46,15 +45,15 @@ const SuggestionsErrorMessage = styled.span` `; const Suggestions = ({ - id, - value, - suggestions, - visualFocusIndex, highlightedSuggestions, - searchHasErrors, + id, isSearching, - suggestionOnClick, + searchHasErrors, styles, + suggestionOnClick, + suggestions, + value, + visualFocusIndex, }: SuggestionsProps) => { const translatedLabels = useContext(HalstackLanguageContext); const listboxRef = useRef<HTMLUListElement | null>(null); @@ -68,46 +67,38 @@ const Suggestions = ({ }, [visualFocusIndex]); return ( - <SuggestionsContainer - id={id} - error={!!searchHasErrors} - onMouseDown={(event) => { - event.preventDefault(); - }} - ref={listboxRef} - role="listbox" - style={styles} - aria-label="Suggestions" - > - {!isSearching && - !searchHasErrors && - suggestions.length > 0 && - suggestions.map((suggestion, index) => ( - <Suggestion - key={`${id}-suggestion-${index}`} - id={`${id}-suggestion-${index}`} - value={value} - onClick={suggestionOnClick} - suggestion={suggestion} - isLast={index === suggestions.length - 1} - visuallyFocused={visualFocusIndex === index} - highlighted={highlightedSuggestions} - /> - ))} - {isSearching && ( - <li role="option"> - <SuggestionsSystemMessage> - {translatedLabels.textInput.searchingMessage} - </SuggestionsSystemMessage> - </li> - )} - {searchHasErrors && ( - <li role="option"> - <SuggestionsErrorMessage role="alert" aria-live="assertive"> - <DxcIcon icon="filled_error" /> - {translatedLabels.textInput.fetchingDataErrorMessage} - </SuggestionsErrorMessage> - </li> + <SuggestionsContainer style={styles}> + {isSearching ? ( + <SuggestionsSystemMessage aria-live="polite">{translatedLabels.textInput.searchingMessage}</SuggestionsSystemMessage> + ) : searchHasErrors ? ( + <SuggestionsErrorMessage aria-live="assertive" role="alert"> + <DxcIcon icon="filled_error" /> + {translatedLabels.textInput.fetchingDataErrorMessage} + </SuggestionsErrorMessage> + ) : ( + <ul + aria-label="Suggestions" + id={id} + onMouseDown={(event) => { + event.preventDefault(); + }} + ref={listboxRef} + role="listbox" + style={{ margin: 0, padding: 0 }} + > + {suggestions.map((suggestion, index) => ( + <Suggestion + highlighted={highlightedSuggestions} + id={`${id}-suggestion-${index}`} + isLast={index === suggestions.length - 1} + key={`${id}-suggestion-${index}`} + onClick={suggestionOnClick} + suggestion={suggestion} + value={value} + visuallyFocused={visualFocusIndex === index} + /> + ))} + </ul> )} </SuggestionsContainer> ); diff --git a/packages/lib/src/text-input/TextInput.stories.tsx b/packages/lib/src/text-input/TextInput.stories.tsx index 5d7a87f1f6..6e4b9ec975 100644 --- a/packages/lib/src/text-input/TextInput.stories.tsx +++ b/packages/lib/src/text-input/TextInput.stories.tsx @@ -261,7 +261,7 @@ const AutosuggestListbox = () => ( </ExampleContainer> <Title title="Listbox suggestion states" theme="light" level={3} /> <ExampleContainer pseudoState="pseudo-hover"> - <Title title="Hovered suggestion" theme="light" level={4} /> + <Title title="Hovered" theme="light" level={4} /> <Suggestions id="x1" value="" @@ -275,7 +275,7 @@ const AutosuggestListbox = () => ( /> </ExampleContainer> <ExampleContainer pseudoState="pseudo-active"> - <Title title="Active suggestion" theme="light" level={4} /> + <Title title="Active" theme="light" level={4} /> <Suggestions id="x2" value="" @@ -289,7 +289,7 @@ const AutosuggestListbox = () => ( /> </ExampleContainer> <ExampleContainer> - <Title title="Focused suggestion" theme="light" level={4} /> + <Title title="Focused" theme="light" level={4} /> <Suggestions id="x3" value="" @@ -303,7 +303,7 @@ const AutosuggestListbox = () => ( /> </ExampleContainer> <ExampleContainer> - <Title title="Highlighted suggestion" theme="light" level={4} /> + <Title title="Highlighted" theme="light" level={4} /> <Suggestions id="x4" value="Afgh" @@ -316,34 +316,34 @@ const AutosuggestListbox = () => ( styles={{ width: 350 }} /> </ExampleContainer> - </ExampleContainer> - <ExampleContainer> - <Title title="Autosuggest Error" theme="light" level={3} /> - <Suggestions - id="x5" - value="" - suggestions={country} - visualFocusIndex={-1} - highlightedSuggestions={false} - searchHasErrors={true} - isSearching={false} - suggestionOnClick={() => {}} - styles={{ width: 350 }} - /> - </ExampleContainer> - <ExampleContainer> - <Title title="Autosuggest Searching message" theme="light" level={3} /> - <Suggestions - id="x6" - value="" - suggestions={country} - visualFocusIndex={-1} - highlightedSuggestions={false} - searchHasErrors={false} - isSearching={true} - suggestionOnClick={() => {}} - styles={{ width: 350 }} - /> + <ExampleContainer> + <Title title="Error" theme="light" level={4} /> + <Suggestions + id="x5" + value="" + suggestions={country} + visualFocusIndex={-1} + highlightedSuggestions={false} + searchHasErrors={true} + isSearching={false} + suggestionOnClick={() => {}} + styles={{ width: 350 }} + /> + </ExampleContainer> + <ExampleContainer> + <Title title="Searching" theme="light" level={4} /> + <Suggestions + id="x6" + value="" + suggestions={country} + visualFocusIndex={-1} + highlightedSuggestions={false} + searchHasErrors={false} + isSearching={true} + suggestionOnClick={() => {}} + styles={{ width: 350 }} + /> + </ExampleContainer> </ExampleContainer> </> ); diff --git a/packages/lib/src/text-input/TextInput.test.tsx b/packages/lib/src/text-input/TextInput.test.tsx index 22be2c6ff8..aab2912912 100644 --- a/packages/lib/src/text-input/TextInput.test.tsx +++ b/packages/lib/src/text-input/TextInput.test.tsx @@ -816,8 +816,10 @@ describe("TextInput component asynchronous autosuggest tests", () => { ); const input = getByRole("combobox") as HTMLInputElement; fireEvent.focus(input); - expect(getByRole("listbox")).toBeTruthy(); + expect(getByText("Searching...")).toBeTruthy(); + expect(getByText("Searching...").getAttribute("aria-live")).toBe("polite"); await waitForElementToBeRemoved(() => getByText("Searching...")); + expect(getByRole("listbox")).toBeTruthy(); expect(getByText("Afghanistan")).toBeTruthy(); expect(getByText("Albania")).toBeTruthy(); expect(getByText("Algeria")).toBeTruthy(); @@ -844,12 +846,12 @@ describe("TextInput component asynchronous autosuggest tests", () => { return result; }); const onChange = jest.fn(); - const { getByRole, queryByText, queryByRole } = render( + const { getByRole, getByText, queryByText, queryByRole } = render( <DxcTextInput label="Autosuggest Countries" suggestions={callbackFunc} onChange={onChange} /> ); const input = getByRole("combobox") as HTMLInputElement; fireEvent.focus(input); - expect(getByRole("listbox")).toBeTruthy(); + expect(getByText("Searching...")).toBeTruthy(); userEvent.type(input, "Ab"); fireEvent.keyDown(input, { key: "Esc", code: "Esc", keyCode: 27, charCode: 27 }); expect(queryByRole("listbox")).toBeFalsy(); @@ -874,16 +876,15 @@ describe("TextInput component asynchronous autosuggest tests", () => { ); const input = getByRole("combobox") as HTMLInputElement; fireEvent.focus(input); - const list = getByRole("listbox"); - expect(list).toBeTruthy(); + expect(getByText("Searching...")).toBeTruthy(); userEvent.type(input, "Ab"); fireEvent.keyDown(input, { key: "Esc", code: "Esc", keyCode: 27, charCode: 27 }); expect(queryByRole("listbox")).toBeFalsy(); expect(queryByText("Searching...")).toBeFalsy(); expect(input.value).toBe(""); fireEvent.keyDown(input, { key: "ArrowDown", code: "ArrowDown", keyCode: 40, charCode: 40 }); - expect(list).toBeTruthy(); await waitForElementToBeRemoved(() => getByText("Searching...")); + expect(getByRole("listbox")).toBeTruthy(); expect(getByText("Afghanistan")).toBeTruthy(); expect(getByText("Albania")).toBeTruthy(); expect(getByText("Algeria")).toBeTruthy(); From 174db8a0edeb07f87cf0e068248115c883bdd640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20G=C3=B3mez=20Pinta?= <44321109+GomezIvann@users.noreply.github.com> Date: Wed, 26 Mar 2025 17:06:44 +0100 Subject: [PATCH 3/9] Label and HelperText updates --- packages/lib/src/select/Select.tsx | 12 +++++--- packages/lib/src/slider/Slider.tsx | 30 ++++++++------------ packages/lib/src/styles/forms/HelperText.tsx | 4 +-- packages/lib/src/styles/forms/Label.tsx | 4 +-- packages/lib/src/text-input/TextInput.tsx | 4 +-- 5 files changed, 26 insertions(+), 28 deletions(-) diff --git a/packages/lib/src/select/Select.tsx b/packages/lib/src/select/Select.tsx index 6ea0e22a1a..c2f8aab155 100644 --- a/packages/lib/src/select/Select.tsx +++ b/packages/lib/src/select/Select.tsx @@ -423,15 +423,15 @@ const DxcSelect = forwardRef<RefType, SelectPropsType>( } setSearchValue(""); }, - [handleOnChangeValue, closeListbox, multiple] + [closeListbox, handleOnChangeValue, multiple] ); return ( - <SelectContainer margin={margin} size={size} ref={ref}> + <SelectContainer margin={margin} ref={ref} size={size}> {label && ( <Label disabled={disabled} - hasHelperText={!!helperText} + hasMargin={!helperText} id={labelId} onClick={() => { selectRef?.current?.focus(); @@ -440,7 +440,11 @@ const DxcSelect = forwardRef<RefType, SelectPropsType>( {label} {optional && <span>{translatedLabels.formFields.optionalLabel}</span>} </Label> )} - {helperText && <HelperText disabled={disabled}>{helperText}</HelperText>} + {helperText && ( + <HelperText disabled={disabled} hasMargin> + {helperText} + </HelperText> + )} <Popover.Root open={isOpen}> <Popover.Trigger asChild type={undefined}> <Select diff --git a/packages/lib/src/slider/Slider.tsx b/packages/lib/src/slider/Slider.tsx index 34782b2e02..ce493ef363 100644 --- a/packages/lib/src/slider/Slider.tsx +++ b/packages/lib/src/slider/Slider.tsx @@ -4,6 +4,8 @@ import { spaces } from "../common/variables"; import SliderPropsType, { RefType } from "./types"; import { calculateWidth, roundUp, stepPrecision } from "./utils"; import DxcNumberInput from "../number-input/NumberInput"; +import HelperText from "../styles/forms/HelperText"; +import Label from "../styles/forms/Label"; const SliderContainer = styled.div<{ margin: SliderPropsType["margin"]; @@ -23,20 +25,6 @@ const SliderContainer = styled.div<{ width: ${(props) => calculateWidth(props.margin, props.size)}; `; -const Label = styled.label<{ disabled: SliderPropsType["disabled"] }>` - 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-semibold); -`; - -const HelperText = styled.span<{ disabled: SliderPropsType["disabled"] }>` - 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); -`; - const MainContainer = styled.div<{ showInput: SliderPropsType["showInput"] }>` display: grid; gap: var(--spacing-gap-l); @@ -135,7 +123,7 @@ const SliderInput = styled.input<{ } `; -const TicksContainer = styled.div` +const TicksContainer = styled.datalist` position: absolute; display: flex; align-items: center; @@ -144,10 +132,11 @@ const TicksContainer = styled.div` pointer-events: none; `; -const Tick = styled.span<{ +const Tick = styled.option<{ disabled: SliderPropsType["disabled"]; currentTick: boolean; }>` + all: unset; background-color: ${({ disabled }) => disabled ? "var(--color-fg-neutral-medium)" : "var(--color-fg-secondary-medium)"}; border-radius: 50%; @@ -161,7 +150,7 @@ const DxcSlider = forwardRef<RefType, SliderPropsType>( { ariaLabel = "Slider", defaultValue = 0, - disabled, + disabled = false, helperText, label, labelFormatCallback, @@ -224,7 +213,11 @@ const DxcSlider = forwardRef<RefType, SliderPropsType>( {label} </Label> )} - {helperText && <HelperText disabled={disabled}>{helperText}</HelperText>} + {helperText && ( + <HelperText disabled={disabled}> + {helperText} + </HelperText> + )} <MainContainer showInput={showInput}> <LimitsValueGrid showLimitsValues={showLimitsValues}> {showLimitsValues && <LimitLabel disabled={disabled}>{minLabel}</LimitLabel>} @@ -255,6 +248,7 @@ const DxcSlider = forwardRef<RefType, SliderPropsType>( currentTick={roundedUpValue === stepPrecision(tick, step)} disabled={disabled} key={`tickmark-${index}`} + value={tick.toString()} /> ); })} diff --git a/packages/lib/src/styles/forms/HelperText.tsx b/packages/lib/src/styles/forms/HelperText.tsx index d1fed8a048..d35a3592c6 100644 --- a/packages/lib/src/styles/forms/HelperText.tsx +++ b/packages/lib/src/styles/forms/HelperText.tsx @@ -1,11 +1,11 @@ import styled from "styled-components"; -const HelperText = styled.span<{ disabled: boolean }>` +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); - margin-bottom: var(--spacing-gap-xs); + ${({ hasMargin = false }) => hasMargin && "margin-bottom: var(--spacing-gap-xs);"} `; export default HelperText; \ No newline at end of file diff --git a/packages/lib/src/styles/forms/Label.tsx b/packages/lib/src/styles/forms/Label.tsx index 445ea02084..4f8fcd879a 100644 --- a/packages/lib/src/styles/forms/Label.tsx +++ b/packages/lib/src/styles/forms/Label.tsx @@ -2,13 +2,13 @@ import styled from "styled-components"; const Label = styled.label<{ disabled: boolean; - hasHelperText: boolean; + hasMargin?: boolean; }>` 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-semibold); - ${({ hasHelperText }) => !hasHelperText && "margin-bottom: var(--spacing-gap-xs);"} + ${({ hasMargin = false }) => hasMargin && "margin-bottom: var(--spacing-gap-xs);"} /* Optional text */ > span { diff --git a/packages/lib/src/text-input/TextInput.tsx b/packages/lib/src/text-input/TextInput.tsx index 0ca40e11a0..5cdf8d88df 100644 --- a/packages/lib/src/text-input/TextInput.tsx +++ b/packages/lib/src/text-input/TextInput.tsx @@ -437,11 +437,11 @@ const DxcTextInput = forwardRef<RefType, TextInputPropsType>( return ( <TextInputContainer margin={margin} ref={ref} size={size}> {label && ( - <Label disabled={disabled} hasHelperText={!!helperText} htmlFor={inputId}> + <Label disabled={disabled} hasMargin={!helperText} htmlFor={inputId}> {label} {optional && <span>{translatedLabels.formFields.optionalLabel}</span>} </Label> )} - {helperText && <HelperText disabled={disabled}>{helperText}</HelperText>} + {helperText && <HelperText disabled={disabled} hasMargin={false}>{helperText}</HelperText>} <AutosuggestWrapper condition={hasSuggestions(suggestions)} wrapper={(children) => ( From f7c58b34480aaca3a755f985ec55a401098c9743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20G=C3=B3mez=20Pinta?= <44321109+GomezIvann@users.noreply.github.com> Date: Thu, 27 Mar 2025 13:02:47 +0100 Subject: [PATCH 4/9] Radio group redesign --- apps/website/pages/_app.tsx | 17 +- .../pages/components/radio-group/code.tsx | 17 + .../pages/components/radio-group/index.tsx | 24 +- .../components/radio-group/specifications.tsx | 21 - .../pages/components/radio-group/usage.tsx | 21 - apps/website/screens/common/pagesList.ts | 4 +- .../dialog/overview/DialogOverviewPage.tsx | 2 +- .../radio-group/RadioGroupPageLayout.tsx | 9 +- .../radio-group/code/RadioGroupCodePage.tsx | 170 ++-- .../RadioGroupOverviewPage.tsx} | 64 +- .../{usage => overview}/examples/stacking.ts | 0 .../radio-group/specs/RadioGroupSpecsPage.tsx | 733 ------------------ .../specs/images/radio_group_anatomy.png | Bin 21446 -> 0 bytes .../specs/images/radio_group_base_states.png | Bin 19903 -> 0 bytes .../specs/images/radio_group_specs.png | Bin 44604 -> 0 bytes .../specs/images/radio_group_states.png | Bin 38186 -> 0 bytes packages/lib/src/radio-group/Radio.tsx | 221 ++---- .../src/radio-group/RadioGroup.stories.tsx | 104 +-- packages/lib/src/radio-group/RadioGroup.tsx | 259 +++---- packages/lib/src/radio-group/types.ts | 80 +- packages/lib/src/radio-group/utils.tsx | 52 ++ 21 files changed, 431 insertions(+), 1367 deletions(-) create mode 100644 apps/website/pages/components/radio-group/code.tsx delete mode 100644 apps/website/pages/components/radio-group/specifications.tsx delete mode 100644 apps/website/pages/components/radio-group/usage.tsx rename apps/website/screens/components/radio-group/{usage/RadioGroupUsagePage.tsx => overview/RadioGroupOverviewPage.tsx} (67%) rename apps/website/screens/components/radio-group/{usage => overview}/examples/stacking.ts (100%) delete mode 100644 apps/website/screens/components/radio-group/specs/RadioGroupSpecsPage.tsx delete mode 100644 apps/website/screens/components/radio-group/specs/images/radio_group_anatomy.png delete mode 100644 apps/website/screens/components/radio-group/specs/images/radio_group_base_states.png delete mode 100644 apps/website/screens/components/radio-group/specs/images/radio_group_specs.png delete mode 100644 apps/website/screens/components/radio-group/specs/images/radio_group_states.png create mode 100644 packages/lib/src/radio-group/utils.tsx diff --git a/apps/website/pages/_app.tsx b/apps/website/pages/_app.tsx index 2f3ff87952..83cd4ed682 100644 --- a/apps/website/pages/_app.tsx +++ b/apps/website/pages/_app.tsx @@ -30,10 +30,8 @@ const ApplicationLayoutWrapper = ({ condition, wrapper, children }: ApplicationL const App = ({ Component, pageProps }: AppPropsWithLayout) => { const getLayout = Component.getLayout || ((page) => page); const componentWithLayout = getLayout(<Component {...pageProps} />); - const [filter, setFilter] = useState(""); const { asPath: currentPath } = useRouter(); - const filteredLinks = useMemo(() => { const filtered: LinksSectionDetails[] = []; LinksSections.map((section) => { @@ -47,17 +45,10 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => { return filtered; }, [filter]); - const onFilterInputChange = ({ value }: { value: string }) => { - setFilter(value); - }; - const matchPaths = (linkPath: string) => { + const desiredPaths = [linkPath, `${linkPath}/code`]; const pathToBeMatched = currentPath?.split("#")[0]?.slice(0, -1); - const desiredPaths = [linkPath, `${linkPath}/specifications`, `${linkPath}/usage`]; - if (pathToBeMatched) { - return desiredPaths.includes(pathToBeMatched); - } - return false; + return pathToBeMatched ? desiredPaths.includes(pathToBeMatched) : false; }; return ( @@ -77,7 +68,9 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => { <DxcTextInput placeholder="Search docs" value={filter} - onChange={onFilterInputChange} + onChange={({ value }: { value: string }) => { + setFilter(value); + }} size="fillParent" clearable margin={{ diff --git a/apps/website/pages/components/radio-group/code.tsx b/apps/website/pages/components/radio-group/code.tsx new file mode 100644 index 0000000000..7500e86777 --- /dev/null +++ b/apps/website/pages/components/radio-group/code.tsx @@ -0,0 +1,17 @@ +import Head from "next/head"; +import type { ReactElement } from "react"; +import RadioGroupPageLayout from "screens/components/radio-group/RadioGroupPageLayout"; +import RadioGroupCodePage from "screens/components/radio-group/code/RadioGroupCodePage"; + +const Code = () => ( + <> + <Head> + <title>Radio group code — Halstack Design System + + + +); + +Code.getLayout = (page: ReactElement) => {page}; + +export default Code; diff --git a/apps/website/pages/components/radio-group/index.tsx b/apps/website/pages/components/radio-group/index.tsx index 766ab409f2..4de6d90268 100644 --- a/apps/website/pages/components/radio-group/index.tsx +++ b/apps/website/pages/components/radio-group/index.tsx @@ -1,21 +1,17 @@ import Head from "next/head"; import type { ReactElement } from "react"; import RadioGroupPageLayout from "screens/components/radio-group/RadioGroupPageLayout"; -import RadioGroupCodePage from "screens/components/radio-group/code/RadioGroupCodePage"; +import RadioGroupOverviewPage from "screens/components/radio-group/overview/RadioGroupOverviewPage"; -const Index = () => { - return ( - <> - - Radio Group — Halstack Design System - - - - ); -}; +const Index = () => ( + <> + + Radio group — Halstack Design System + + + +); -Index.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; +Index.getLayout = (page: ReactElement) => {page}; export default Index; diff --git a/apps/website/pages/components/radio-group/specifications.tsx b/apps/website/pages/components/radio-group/specifications.tsx deleted file mode 100644 index 7738a750f5..0000000000 --- a/apps/website/pages/components/radio-group/specifications.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Head from "next/head"; -import type { ReactElement } from "react"; -import RadioGroupPageLayout from "screens/components/radio-group/RadioGroupPageLayout"; -import RadioGroupSpecsPage from "screens/components/radio-group/specs/RadioGroupSpecsPage"; - -const Specifications = () => { - return ( - <> - - Radio Group Specs — Halstack Design System - - - - ); -}; - -Specifications.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default Specifications; diff --git a/apps/website/pages/components/radio-group/usage.tsx b/apps/website/pages/components/radio-group/usage.tsx deleted file mode 100644 index 4cb0df574e..0000000000 --- a/apps/website/pages/components/radio-group/usage.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Head from "next/head"; -import type { ReactElement } from "react"; -import RadioGroupPageLayout from "screens/components/radio-group/RadioGroupPageLayout"; -import RadioGroupUsagePage from "screens/components/radio-group/usage/RadioGroupUsagePage"; - -const Usage = () => { - return ( - <> - - Radio Group Usage — Halstack Design System - - - - ); -}; - -Usage.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default Usage; diff --git a/apps/website/screens/common/pagesList.ts b/apps/website/screens/common/pagesList.ts index 546dad4589..dbb8e5a0b1 100644 --- a/apps/website/screens/common/pagesList.ts +++ b/apps/website/screens/common/pagesList.ts @@ -68,9 +68,7 @@ const getCurrentLinkIndex = (links: LinkDetails[], currentPath: string) => { export const getNavigationLinks = (currentPath: string): NavigationLinks => { const links = LinksSections.flatMap((section) => section.links); const currentLinkIndex = getCurrentLinkIndex(links, currentPath); - if (currentLinkIndex === -1) { - return {}; - } + if (currentLinkIndex === -1) return {}; return { previousLink: currentLinkIndex + 1 < links.length ? links[currentLinkIndex + 1] : undefined, nextLink: currentLinkIndex - 1 >= 0 ? links[currentLinkIndex - 1] : undefined, diff --git a/apps/website/screens/components/dialog/overview/DialogOverviewPage.tsx b/apps/website/screens/components/dialog/overview/DialogOverviewPage.tsx index ec25f5966e..7301f2cb25 100644 --- a/apps/website/screens/components/dialog/overview/DialogOverviewPage.tsx +++ b/apps/website/screens/components/dialog/overview/DialogOverviewPage.tsx @@ -44,7 +44,7 @@ const sections = [ Actions: a set of buttons at the bottom of the dialog that guide users toward completing or - canceling the intended action. + cancelling the intended action. diff --git a/apps/website/screens/components/radio-group/RadioGroupPageLayout.tsx b/apps/website/screens/components/radio-group/RadioGroupPageLayout.tsx index 961904f34e..103ccc05a1 100644 --- a/apps/website/screens/components/radio-group/RadioGroupPageLayout.tsx +++ b/apps/website/screens/components/radio-group/RadioGroupPageLayout.tsx @@ -6,20 +6,19 @@ import { ReactNode } from "react"; const RadioGroupPageHeading = ({ children }: { children: ReactNode }) => { const tabs = [ - { label: "Code", path: "/components/radio-group" }, - { label: "Usage", path: "/components/radio-group/usage" }, - { label: "Specifications", path: "/components/radio-group/specifications" }, + { label: "Overview", path: "/components/radio-group" }, + { label: "Code", path: "/components/radio-group/code" }, ]; return ( - + A radio group let the user make a mutually exclusive selection from a group of options. - + {children} diff --git a/apps/website/screens/components/radio-group/code/RadioGroupCodePage.tsx b/apps/website/screens/components/radio-group/code/RadioGroupCodePage.tsx index 20541fdb37..b16048b3c0 100644 --- a/apps/website/screens/components/radio-group/code/RadioGroupCodePage.tsx +++ b/apps/website/screens/components/radio-group/code/RadioGroupCodePage.tsx @@ -24,6 +24,16 @@ const sections = [ + + ariaLabel + + string + + Specifies a string to be used as the name for the radio group when no `label` is provided. + + 'Radio group' + + defaultValue @@ -33,14 +43,34 @@ const sections = [ - - value + disabled + + boolean + + If true, the component will be disabled. + + false + + + + error string - Value of the radio group. If undefined, the component will be uncontrolled and the value will be managed - internally by the component. + If it is a defined value and also a truthy string, the component will change its appearance, showing the + error below the radio group. 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. + + - + + + helperText + + string + Helper text to be placed above the radio group. - @@ -63,49 +93,26 @@ const sections = [ - - helperText - - string - - Helper text to be placed above the radio group. - - - - - - - - options - - + onBlur - {"{ value: string; label: string; disabled?: boolean; }[]"} + {"(val: { value?: string; error?: string }) => void"} - An array of objects representing the selectable options. Each object Option has the following properties: -
      -
    • - label: Label of the option placed next to the radio input. -
    • -
    • - value: Value of the option. It should be unique and not an empty string, which is reserved to - the optional item added by the optional prop. -
    • -
    • - disabled: disables the option. -
    • -
    + This function will be called when the radio group loses the focus. An object including the value and the + error will be passed to this function. If there is no error, error will not be defined. - - disabled + onChange - boolean + {"(value: string) => void"} - If true, the component will be disabled. - false + This function will be called when the user chooses an option. The new value will be passed to this + function. + - optional @@ -132,59 +139,59 @@ const sections = [ - readOnly - - boolean - - If true, the component will not be mutable, meaning the user can not edit the control. - false + + + options + - - - stacking - 'row' | 'column' + {"{ disabled?: boolean; label: string; value: string; }[]"} - Sets the orientation of the options within the radio group. - 'column' + An array of objects representing the selectable options. Each object Option has the following properties: +
      +
    • + label: Label of the option placed next to the radio input. +
    • +
    • + value: Value of the option. It should be unique and not an empty string, which is reserved to + the optional item added by the optional prop. +
    • +
    • + disabled: disables the option. +
    • +
    + - - onChange + readOnly - {"(value: string) => void"} + boolean + If true, the component will not be mutable, meaning the user can not edit the control. - This function will be called when the user chooses an option. The new value will be passed to this - function. + false - - - onBlur - - {"(val: { value?: string; error?: string }) => void"} - + ref - This function will be called when the radio group loses the focus. An object including the value and the - error will be passed to this function. If there is no error, error will not be defined. + {"React.Ref"} + Reference to the component. - - error + stacking - string + 'row' | 'column' + Sets the orientation of the options within the radio group. - If it is a defined value and also a truthy string, the component will change its appearance, showing the - error below the radio group. 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. + 'column' - - tabIndex @@ -199,20 +206,15 @@ const sections = [ - ref + value - {"React.Ref"} + string - Reference to the component. - - - - - ariaLabel - string + Value of the radio group. If undefined, the component will be uncontrolled and the value will be managed + internally by the component. - Specifies a string to be used as the name for the radio group when no `label` is provided. - 'Radio group' + - @@ -247,15 +249,13 @@ const sections = [ }, ]; -const RadioGroupCodePage = () => { - return ( - - - - - - - ); -}; +const RadioGroupCodePage = () => ( + + + + + + +); export default RadioGroupCodePage; diff --git a/apps/website/screens/components/radio-group/usage/RadioGroupUsagePage.tsx b/apps/website/screens/components/radio-group/overview/RadioGroupOverviewPage.tsx similarity index 67% rename from apps/website/screens/components/radio-group/usage/RadioGroupUsagePage.tsx rename to apps/website/screens/components/radio-group/overview/RadioGroupOverviewPage.tsx index 86b36de0d7..065609552e 100644 --- a/apps/website/screens/components/radio-group/usage/RadioGroupUsagePage.tsx +++ b/apps/website/screens/components/radio-group/overview/RadioGroupOverviewPage.tsx @@ -8,21 +8,13 @@ import HeaderDescriptionCell from "@/common/HeaderDescriptionCell"; const sections = [ { - title: "Usage", + title: "Introduction", content: ( - - - Labelling should be concise and clearly differentiated from other options. - - - One option of the radio group can be pre-selected. Select the safest or convenient option. - - Single radio button should not be used. - - If the question that the user needs to respond is as easier as yes/no, it is recommended to use a checkbox - instead of radio group. - - + + Radio buttons are used when there is a list of two or more options that are mutually exclusive and the user must + select exactly one choice. In a group of radio buttons, only one radio button at a time can be selected. Radio + buttons are always used in groups, and each option is represented by one radio button + ), }, { @@ -60,23 +52,41 @@ const sections = [ - *In any case, in the specification it is specified the ideal distance between component with label in the same - horizontal edge to avoid the problem of pairing and scalability. + + * In any case, in the specification it is specified the ideal distance between component with label in the + same horizontal edge to avoid the problem of pairing and scalability. + ), }, + { + title: "Best practices", + content: ( + + + Labelling should be concise and clearly differentiated from other options. + + + One option of the radio group can be pre-selected. Select the safest or convenient option. + + Single radio button should not be used. + + If the question that the user needs to respond is as easier as yes/no, it is recommended to use a checkbox + instead of radio group. + + + ), + }, ]; -const RadioGroupUsagePage = () => { - return ( - - - - - - - ); -}; +const RadioGroupOverviewPage = () => ( + + + + + + +); -export default RadioGroupUsagePage; +export default RadioGroupOverviewPage; diff --git a/apps/website/screens/components/radio-group/usage/examples/stacking.ts b/apps/website/screens/components/radio-group/overview/examples/stacking.ts similarity index 100% rename from apps/website/screens/components/radio-group/usage/examples/stacking.ts rename to apps/website/screens/components/radio-group/overview/examples/stacking.ts diff --git a/apps/website/screens/components/radio-group/specs/RadioGroupSpecsPage.tsx b/apps/website/screens/components/radio-group/specs/RadioGroupSpecsPage.tsx deleted file mode 100644 index 2bd61eaeb6..0000000000 --- a/apps/website/screens/components/radio-group/specs/RadioGroupSpecsPage.tsx +++ /dev/null @@ -1,733 +0,0 @@ -import { DxcBulletedList, DxcParagraph, DxcFlex, DxcTable, DxcLink } from "@dxc-technology/halstack-react"; -import DocFooter from "@/common/DocFooter"; -import Figure from "@/common/Figure"; -import QuickNavContainer from "@/common/QuickNavContainer"; -import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; -import Image from "@/common/Image"; -import Code from "@/common/Code"; -import radioGroupBaseStates from "./images/radio_group_base_states.png"; -import radioGroupStates from "./images/radio_group_states.png"; -import radioGroupAnatomy from "./images/radio_group_anatomy.png"; -import radioGroupSpecs from "./images/radio_group_specs.png"; - -const sections = [ - { - title: "Specifications", - content: ( -
    - Radio group design specifications -
    - ), - }, - { - title: "States", - content: ( - <> - - The following states are defined in the life cycle of the component: unselected enabled,{" "} - unselected hover, unselected focus, unselected disabled,{" "} - selected enabled, selected hover, selected focus and{" "} - selected disabled. - -
    - Radio button states -
    - - The following states are defined in the life cycle of the component: enabled,{" "} - error, readOnly and disabled. - -
    - Radio group states -
    - - ), - }, - { - title: "Anatomy", - content: ( - <> - Radio group anatomy - - Label - Helper text - Radio input - Radio input label - Error message - - - ), - }, - { - title: "Design tokens", - subSections: [ - { - title: "Color", - content: ( - - - - Component token - Element - Core token - Value - - - - - - radioInputColor - - Radio input - - color-blue-700 - - #0086e6 - - - - hoverRadioInputColor - - Radio input:hover - - color-blue-800 - - #0067b3 - - - - focusBorderColor - - Radio input:focus - - color-blue-600 - - #0095ff - - - - activeRadioInputColor - - Radio input:active - - color-blue-900 - - #003c66 - - - - errorRadioInputColor - - Radio input:error - - color-red-700 - - #d0011b - - - - hoverErrorRadioInputColor - - Radio input:error:hover - - color-red-800 - - #980115 - - - - activeErrorRadioInputColor - - Radio input:error:active - - color-red-900 - - #65010e - - - - disabledRadioInputColor - - Radio input:disabled - - color-grey-500 - - #999999 - - - - readOnlyRadioInputColor - - Radio input:readonly - - color-grey-500 - - #999999 - - - - hoverReadOnlyRadioInputColor - - Radio input:readonly:hover - - color-grey-600 - - #808080 - - - - activeReadOnlyRadioInputColor - - Radio input:readonly:active - - color-grey-700 - - #666666 - - - - labelFontColor - - Label - - color-black - - #000000 - - - - disabledLabelFontColor - - Label:disabled - - color-grey-500 - - #999999 - - - - helperTextFontColor - - Helper text - - color-black - - #000000 - - - - disabledHelperTextFontColor - - Helper text:disabled - - color-grey-500 - - #999999 - - - - radioInputLabelFontColor - - Input label - - color-black - - #000000 - - - - disabledRadioInputLabelFontColor - - Input label:disabled - - color-grey-500 - - #999999 - - - - errorMessageColor - - Error message - - color-red-700 - - #d0011b - - - - ), - }, - { - title: "Typography", - content: ( - <> - - - - Component token - Element - Core token - Value - - - - - - fontFamily - - Label - - font-family-sans - - 'Open Sans', sans-serif - - - - labelFontSize - - Label - - font-scale-02 - - 0.875rem / 14px - - - - labelFontWeight - - Label - - font-weight-semibold - - 600 - - - - labelLineHeight - - Label - - font-leading-loose-01 - - 1.715em - - - - labelFontStyle - - Label - - font-style-normal - - normal - - - - helperTextFontSize - - Helper text - - font-scale-01 - - 0.75rem / 12px - - - - helperTextFontWeight - - Helper text - - font-weight-regular - - 400 - - - - helperTextFontStyle - - Helper text - - font-style-normal - - normal - - - - helperTextLineHeight - - Helper text - - font-leading-normal - - 1.5em - - - - radioInputLabelFontSize - - Input label - - font-scale-02 - - 0.875rem / 14px - - - - radioInputLabelFontWeight - - Input label - - font-weight-regular - - 400 - - - - radioInputLabelFontStyle - - Input label - - font-style-normal - - normal - - - - radioInputLabelLineHeight - - Helper text - - font-leading-loose-01 - - 1.715em - - - - - - - Property - Element - Core token - Value - - - - - - font-size - - Error message - - font-scale-01 - - 0.75rem / 12px - - - - font-weight - - Error message - - font-weight-regular - - 400 - - - - line-height - - Error message - - font-leading-normal - - 1.5em - - - - - ), - }, - { - title: "Border", - content: ( - - - - Property - Element - Core token - Value - - - - - - border-width - - Radio input - - border-width-2 - - 2px - - - - border-style - - Radio input - - border-style-solid - - solid - - - - border-width - - Focus border - - border-width-2 - - 2px - - - - border-style - - Focus border - - border-style-solid - - solid - - - - ), - }, - { - title: "Spacing", - content: ( - <> - - - - Component token - Element - Core token - Value - - - - - - groupLabelMargin - - Label/Helper text - - spacing-8 - - 0.5rem / 8px - - - - radioInputLabelMargin - - Input Label - - spacing-8 - - 0.5rem / 8px - - - - groupVerticalGutter - - Radio item* - - spacing-4 - - 0.25rem / 4px - - - - groupHorizontalGutter - - Radio item - - spacing-32 - - 2rem / 32px - - - - (*) Radio item = Radio input + Radio label - - ), - }, - { - title: "Size", - content: ( - - - - Property - Element - Value - - - - - - width - - Radio input - 18px - - - - width - - focus outline - 24px - - - - height - - Radio input - 18px - - - - height - - focus outline - 24px - - - - ), - }, - { - title: "Margin", - content: ( - <> - - Margin can be set independently for top, right, bottom, and{" "} - left. - - - - - Margin - Value - - - - - - xxsmall - - 6px - - - - xsmall - - 16px - - - - small - - 24px - - - - medium - - 36px - - - - large - - 48px - - - - xlarge - - 64px - - - - xxlarge - - 100px - - - - - ), - }, - ], - }, - { - title: "Accessibility", - subSections: [ - { - title: "WCAG 2.2", - content: ( - - - Understanding WCAG 2.2 -{" "} - - SC 1.3.1: Info and Relationships - - - - Understanding WCAG 2.2 -{" "} - - SC 3.3.2: Labels or Instructions - - - - Understanding WCAG 2.2 -{" "} - - SC 2.4.6: Headings and Labels - - - - ), - }, - { - title: "WAI-ARIA 1.2", - content: ( - - - WAI-ARIA Authoring Practices 1.2 -{" "} - - 3.12 Radio group - - - - ), - }, - ], - }, -]; - -const RadioGroupSpecsPage = () => { - return ( - - - - - - - ); -}; - -export default RadioGroupSpecsPage; diff --git a/apps/website/screens/components/radio-group/specs/images/radio_group_anatomy.png b/apps/website/screens/components/radio-group/specs/images/radio_group_anatomy.png deleted file mode 100644 index 418fc6265bd67c2afa87a0b795c44b7c59c54222..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21446 zcmeIaWmuG58#O$HC?bNOB8>rvl+q1~(xHTOC^2+*4x%6;AvFw*5+X6Azz~BXAky6- z4MTVH?LoaC@8^5}zsGwVk3Vo`uHlMv?|q(Yt#fUH9xF)`pQkzxfk235Wgeh>GfC~#9>O3X1j zrJW~y&-2bD{qPe0m3&x=40#%hikd_jtn*3^4-v!r6qzTa`J~9G8g;ynuhp_r32v~z zToqq&Q@(JoQ850c9pAyo?9Xz3!J;DWWUaQ^FhpVt89BM5q5!D`1PA}GAK#~Nwa(Qb zyk5@J;htlFLH_vxyTkx-y!rPh;F!Pz7-aGFvy8}peHDU>Z@B!gvx19ApCg6Q-fMOu z`}YADq*?T@t6;A;ehvpW|7HL}>BhegkiyE({&lC=8>hK00de_uw@dc#M*s(K@9zBP z&QD*#ZsMP-3B6`26!h-{5|A0X{|bTKO9)Q?rCJ5%OaDG_u4WkTzn(LclmR0AJa+!p zKVkZHcAQwxe}`3~<`M{s^abw3hyOkRf=cqgp#E#1{}nsJCCN__Mu*CsH4I`Q>( zmoz>pDRK4W)1?^q_%OHhjuIO}%8rGrFY-0CFF*M6FQDjb$Mw(S)U;4AK*kR5rJOm(VCRqDdGinz|G7+B(e#CFyTB9nKi>;~f{Q=? zHeyET97A|2?tZgvVh}YOr>9chI~0wOSAU`ic|z)k?{$AFqTl8f21q87x#T(w7Iw}l z8Lc-H=vn$Rx>RvlCG%zTl1lB+M3m&8XCz&{2a0SM@wt}!5WixgxkcObj%ReNciwRZ zHhb`>S(NGfsm<&mMPmAJE2Q|JU+rgQIe-06mab-&fNWRYOML|HOv;aPj3yi^K7x48 zZY}xdhb4&yJYAeNa=Y{jT%|2{W8sr3-NzevaWQM3auSI|H`^uG2cXG%eXnQ)9ln=4 z&MpqK4~G7Z<3$h`;X{IygLB4Gex_Zuj8;2{%N0+vWMI0XA(8Lh9ltYIt*i<`_)(1L z9BNHw?n1r3%(L`fkDns-*;lG|^bc2#4iD4xDqIlND^8@pfd&^6f+MgI$}TEdQ-FKL z;6fx`CZ|WwiO2K{V|&gOJiCvU<(n}-T|LxG6N$_r`lU9B31Z$tjW%!TM71x{i?JQ; zZ$~Rd6)^ps=l>7cu;F+RXHS)ff*-sj;P1KQ5 zQnI9`rgE8g-YTCA6`nxaw~6k2KiV1N^z-xM@;P*4(a1|+GpHVJjVh-7eV<-3082^w z-&ZA-VDQF2Jn)@&e)JSC|7k)WX3)E6ys=N)!zz|woY%7Vdnm0hT2c_&nxOLW@pVU= zv1)IVx;IxMcrBkcCX1tsEqdhiefCTQJvKi^v8YN}^yaV#yDsx^*Nb{=hS!Hs%by${ znangJm>9;~XA`-gtu%t3+c}4uUCJt{56-CCZY}iGjg1-j94zrWDjjSurF-ry(+$c0 z1Ket;!QA1Oi@FcN(IFh3gV0Pqf)!uUBn#<*BiADZMY#iS5R zYTz#nIg5`UORAG5b7=$7dJE2`r4q+$c~$&F_D|H)T%4$YMx$2bBQxw zEZ(0B$|&RD+Id*0P~e{1eOVKti%UD9ja$`oT|Q-ta@>7mrY_99lB0PqOC|kHjsaRs43aGF ztG~TCU}Opx{LgB-3|3PnlQURN%IDmQ2xBTzpTZ@C&B=43l5lz%+lBDF_SQ`rBN&^+ zaaUhox@p|~@yQyRgoFfMv5b;uQcAe3EPmv4xY88E&RQ2t0S}?(uRm)@JZRuOJz|;V zH4*UP(r+wjh>fOoz?*;@Fjy(>Xe9wl2~Ix?^8R`;VN4IiYrd0_<#7tE*k)wV>P31( zwf8}q!}O2$NZmMbUoo8iA?gH&-zN&U0AczxFK`J5-%H)_Br90O+$mP0rAP>`($eWB zi_TrRhVxK)IKjAj_R~gIWF^@cu{W_wsm(}p8<=NFAL6ylcbB>{!?ff?tp|Aqip?Xv zHd^>QQ|{y8<|o=-`@KwIiU3ISQQnRNK)M$n)i%blQ5&Wy+$cHE;Vk!kn1i>oM9CnR zElV-qVe0$!j*gD3i#7XHw}{aw&rJk3^dVdbz1b<>nJy1^BN#ao$Jk!&q0!Fq3%@UN zg3NnztrHJq-Ukv;KTG9)1}JA;MzC^q!a(zZZ{pp9cn(7rH8KhcsLLxCw934cLD>F3 zFufE4a5oMPK3DE())tt}bebQVpyN%~GwX253YPmbxH$MWp%9$inKDOpeA4d>e(v!k z@?mF$JDo}d!=A$Poc+7h&NAQplkLt8d*R@&w$S5ggHQN1C7>^O@pber<~Akq9&&AX z`k#Hh@aGQvE|4)mprkx-kW}MYQ`+#uj-HqHQ5($%Xxv;We+BiiUCy_6#W3L;Y2lGux4dK`Q< z!Z`zlEzb0&(noULb(#a)Xmo4<_{h=3@u;r&<6aX>yq?zc^t-s{%J4T2Ool&=aM>2Q z3b(uF>O7Dto}eEra|bc-dkf|^G>xzCcg6ElU`Ywn-aIr6+zaGo4Lq(LT5J`ucvkne z5KJryZ7|&C9h17h8J1loBz{To_$mZc602KaxH=3w)P5gZW%t`WhhC;zL-CGL>1g{y za1p97NP=KPwEpkfQA2#S9qNNo7&detaCB(toNoVFyKGGIf?C8zEewL&dKDA^&%=39 zPy_8G@j)WFwT0<}bD8`Y4&TqZjAKALC+1tBA>2e^w{%G3y!Pgs-o~)Sh@X!QoGOV( z1s9tz7}P=8R4cG21Lp`YKV#AJiE(4L4X$Hb#asOWtj0(Zv0VwV<6Wb$MD)(6=QK8R zgPiEe7T=329Yz~=!$ImqXbplqrM5oZ#Eh(5f5T(h%clQ)eX5ZQ zya);oj>N$u_+I0O*8<(IINaN4TIk7s1WiGo9D8QzmOC7}3*F4MzO9*`GSw2Vag{^Y zOl+*e6}h?4Bkyyxr9mD#+l(-ZF%myH+)(+X#xf6bOP1uxj={-3Tu>UXcsuK15Si)r z{yS;9f-f>1-{KkDtvt$JE{PeA0Bp?6DkTab^1a7vaNI${oc zj!l|c6)w4i4dhG;;YjqccA;s@9mm<$Xj);H*mat7Ze$;KpU2V&cz384V8oJl+!Xhw z=aRX8Hlde~rb64tB?h<-mE@gOuVJTjjWDPXu1ZnZ^-9B-f};sl!^6XyJfNkdG{-GC zpd623*ZBb|eVWsJXNE!~lVbA)tDaBl$atJy$nYn1RzaV`vfaHsL?OTqQJ@&f1F-N2 zewTtnSCh_X_u0k(5oaX>uSIwGXq9I?q|$58p<}wSkv0f#6=dl&n-Nr@&yfdtOk0AW z(cov>F|m!J&*16D;QnVx;`qv-QMm-;joI7Zzj3Nu{5q!7G?B13f9SKd&irWAfw&g*a9oxnNqXy&o&h)helKJ8a<}i-p3RxsiPazItK@i`-}5-qlmc zhVjQ6o|*o^Gml9S=<3hvuadtsuOnaa5 zS9gwo625)1T1zrB@beqa?tEr4pWk+OmMYV(g+PxX7@h1^8i_yhw)Ok2z zdRwCCMcncSV3?ERm6K*cls`{}1PHqbyXfX7RT{)sTub=lr~y(`yBvd(*Lz*Q;K6CP zt})9iV@^rgiY1ULpeQ*8o@#Vyvg2Q05iuDrT(DFLZNnW)7oOsA~sgyF1y@WCJ`sRoW+5n_|>JQ)Y-Z0IH#wvcz#p(ndoRnZZ)SAy{V1(aVhpbMh1Mu`Pjel=1PtPZ_c-fRHyyY2dx>+60-q~VVrOs2 z#Ao-N(~=5Yh|w4nzeu{^>7Jg*Z~AP`U%KJbnbg1&&7lsF7KhHLqS`p`AU9`cZ@wx3(wg%bQDm)RoS0 z`ejlXfQOnB{r+&^wEKpmGqs&YUqX4h8MtOU7CK98?BW*2jZeWr8{o%nQcp5zMmHZi_+#&#@}>l&^%^wq#qYBsbS07r=?>~_ko@(X#7L=8N20bv z)AfTF%K(?#f-2x`DPl$pmIpSg^y^CaudZ7+==t7YTzH;iB}`-OahZ(f_Wklg*`e_F zdE$@GRrAq9;{edu22VQVIBdrI`&WNIGwczVtdH)#`49H{bz*>GtlI29^!&QcKLNUW z=3LFyFSq~D*WX7C0b9uzZ3ub#UuV$-*VMuPAn;$86a^>Qrs3BP`4h-rr#E~8u6gP0 z1UMr5+h>^8+$zIbu?4WD~7f|A*?%TmrQ$*}sZM5Tu(zu{+%G&q%*&|MxPkik}Y z{j#4xr_I_@9c=dEF2~1|S`)E|c}DD>ncgVjXs0_J6?{^=GN5(;^q<$yM4#>jGKBWr?sFNPCb+wD8h_@;I0O3x1alUM~k;X7N7jsVG7G_4Fo4TO6cRf%XIf1pAVxko*(vHE|=a^R>9f*a+W^o zrgW(0Cr;j1U96B~QO~C~=p&bbDobd>>{iEPpckezHa#_6HCbIE%>PK{w8o&L-L#@wN2$m1` zH?uO6$P%CRf@(#}Wmp3V0`L&f%%I-BVsq6EtqF+NOfx0%pbI_!`JD3c2noXnXrP) zs0W69Z+9}x1kmja18w;BbRfM?ICwa@o#l~fP(#4w3ctT1qL^B)Sc$-{1C zULVy zNECLB0hp%Au}zd20N|piJL(JcIAvNzrdaKPQqUpoaC^wM6Yc9u&Lr$KXSm;6W^c5% zu@RXn87P-77d_QqV5CyvV!a2x)#CU~8%cNO{N=lT1*(}!T!5XE`!FlSGW`7cbA9aO zSOb7di?0O%mc;3d@-b0@jx(|8{T{h#F%q6#6ji1G85nLaEPX`81}<>A~# zU)9@_tq~IT8Ht6*=3PwW-92iO`2n6w{@bOpkxVo6GyqcG1fVyjKKARc^*MY2ON1ZK z#$l6^A`mVin2lO>C99%tBUT(u{}Di=CNcDURau&Zenz}z!yXDG^XpZ*bD=)Pf!1?zj| z0M+*CK>>B^1j-O5#v0Q`#pUdxO+vg(CFabhj%Ocym0dwVdH1kDj6AofO!kbS?f(0# z`-@JamF|;6nK+tyo2X^UAs(Ov?bhU>lX)#;=E$r>4e;`hja~?a0o>SGrg93j!CU~f zvwGlW%@#JN(qCQOVr-{U38NS1SgG7ls(jBet5;$Xjpz^sfO8lCQYKQ2LVhb5hV3KW z{)uxh@6S#>;=-1Aet!8dasor*my!V&?j@Ip0rn9jHkRG=YRvb<+kHMA-rQ_AaWe~| zTk!l5cTv4m@VPFQV+(2|lG;^u45i(j+*T|k&Y8A_B2{*3LSQnJ|<@qOu> zHRaWvcNlpY81RtTa$f{VCu^-1eHT*ErdMPdg80%+she1kt5uYtHIZF}07Ponxvzb? zvc#eXm1m=8Dr)lMGJ?mv)7bZ>9>fs@ zIwJ~q>{Awdvf+r99!h;yoPJRmyt5a1iar8SIB8&2mcOBsI6p#&EC2&Cylgz5wF0bH zF_nxSaD6`RmUr*onfm9v4CF}eb=*Q2?xeQ0Ei@O|I7N3qLF$Wg4<*-hG7S`( z$a6t-=7TdKBHsH>TKR^4W_GUh`xiztt zya4?orTajmKn%!@kbbfr`mBG(P%k1UUbH;fvj>vH9m`q0`+fkS1>dSZTyJ{+Qw8c+ zYCC3wo5!K3bPZ-?WYm(AFP5CmT_7Z%({@n*?VAi3&MCn69Wc42aVZC1i8y0nALY}W zvx*3$tZM^^sTH(DH}{OaTQ!rLdbCCX&_)pFQ}LJ?K3E2t;qw>EKnGBKtPjYo)bfi-Lhi14Z@ALxHO_5c$#(%fI4>2Y1V!fTNKZp zapIq?D$!|*7QYxzs_V6#W(<8{v`ruNK{W8kUcO>Pl~aDy*n0W&$cGVpp{DG%5sUV2 z+5qPb7UDcYIGd+7Zti_mSIugdr8q(aYG@6lm=?>|o9zf4;yNM6Y!j#Lr~ECQ>5V~{ zUZ`2J*Sc(xc}%s9Y%S8}S~~X!k3&#IOwU?aBLn)SLZ}*n9@zpFip*~MSVBTIwdE|0 z`li{|r$IE233BRU%@qb(dq(blb7A$T+qZAa+)?{z_@eOh^(-hXce~Cyp_|O?NP+)q z7c_LsyUF)>FVkJGGaFfLv9Ac({&FH?j@6=Ius(o+ky*E2g9#y^F|P8B^F8+$su#|J zPLKO8lJ$QspE{Ri@tDK*K>a-&8U4{+z8Q;*(_@+=5>kCz+%l%y?LKnn3uVU*O2U^H zW;Cc2B84_-tL^RWnNlnm6zAJ_#x1<}4=q);=E*FH7ngTPD=?jpvwwC}1s5Oe?CT<1 z#hCSltabIcn>_SYk{SD?SiRS#vXB}orQd|jJcYvAe&NVfb&xoap%<30a)Xxw&^yC6 zuRV{E%ygP7oPDk;o$fKy*ML%V{H>-kkOLUb-C@{y1&k`gO?)S#9HM9O(}(+t@40I| z<`irtCOsl5p6KYHO_2Q}CVa{RneX!%LGMNNkVGRE!PjI z8D)vdeMDBp<{6wYT(35_Tko-v8|MZAPNqCh1t9r6dpMmhi*u>DeOO#*GGR59^V866 znme;GNLl-j>z}Q!*FlxZ&?#Q?qG{cMur|U~(Mdw zYwB+&&;)PfVivZbyuHI}gUL##cPkW4Q+{+=a788*^fl&rQ*Q@2^()Ktkg1V&wjU zSoBE|_9b#V5StXfrQq)`RYSr!XR>k>#UevouQ$G@yImQmw-ndmAmysK_ zAKGw|Y4|5mP#96jHPVyAm19$qc+#AvRA(lEk_G@J0VZONU{X1@mD98_n``Y`&H zEkXXmaZdE%jg{(yJcT5YJt_SF6t`wg*N(!(jMFDtHhL$tB9-C*!`PBo%Z>s;7{zIE z9R|a?dORW|Iv>BmN8zsJNK2+art>uy^HzOB z_q&tZ=w#aHEaSiIcQvuOaCTisjGr6LTwi^Mxn5CuJ?iUC0=b{>{2xMJ?DX1L$4J7p zirUxfHL3Zm!Zq~WAN3*)IDvM9m22g~=tPZcU^sM3=-mk-_vP~0hyHfjVE{SHffin; z8W5bkZUQ|p0-z>Q#Vxx+TSB+}B-ws#Z{+S-kB0hZ+N)9ndSY}KW4nji=QrdU_MH@<#ZwC&q4V;t`AbrzpY#WwE`q-GhT*f{p}1M>!y&iaw3h$JqsS z$Khb9v}t*yLR6nS9UvB-wY|m0QJ=Q5<0{48i=R00#0Revd3Hxs6P0>is8KpdS{t~Z z@$7Ul0H>XU=KN*Q`1mCUC$6iQmOfry^l@k{rS{0_n zTh#xtc(G(TSrylgGukC^`lWnkm0uS`_x(e{J)oAQ0byP?$pO$cKx31=wt6%oI1R+y z7qT)Zpn>FP$IHqD_IA)G>_k`DNOLQId=0uQmm3p>OS2Auy1(8j&)EnN$n8K9dcNxi zU4nGXbq=hXz_# znZ7)|={gDn1s4K=^!@6iZ5=I?QQ~Ue5jEgv-fQ)gQCR zZ-$B7Xb7W+gLdW*_R`_?Fx7ZoOC~TQBH!JV{sE*`rlW%${`-&ppHDzUToyI8${o@P zL-yGU2)+U_8gb7kAWK7XT$i1Oa^mujEX8k?gq4og5Rk;U2ux-t}J1n0!O_hZE{ zPtZQzT`^!1#P7?+u$eDZQ%YawJQ1#F2N|)p{7ufSGT8l2l(aa7l zSRKovP)t3CEvPjoAQujw2NQc9zmm9yaeX?R?7m>Se)(Z%#S_XsFVD0K262Z)wKlhz zkLq}Mfl^J8=kQ#|3*vHOH7#`jRNWSZ=^XzkKZVy_n&wU8f z@B0SGO6Ssj^{tUIb3RDv0F4 za)q+k$)Te;;E{*Cyb$zSJ+&#HfbA&ySk`pnTM*Z8LJ0;x2B<*wQC*UFcECr_V#Oyt zh%e&ZHr0YqI%8* zdpXM1EbEJKhc!9f!XrX^cE8adWOJ7rgmdLn$e-qMXH$_m2e=#`?ycA0k;H~)Hu;eg zJkYbrXY|+HG5gVK*rp0l z4onFKysbtn2a4;ethAHWd&if>u&RF7Xc`dj)snX#)uIUctp*C~`m%7`zuNBq+AL%v0&Z%ox% zGWbVE6?BSK1Cms1N;1Z6PK|pyyefJo)cas z5Pli~^>5xN5%SM&iYKQhl>E@Ty1<=>!g;Eax&rzV5#qi++>s0VHe9&z-*8h^$RJAo= zfNQt!?dT4Q?SkAWho8&fj>3M(d0wnSp9GR>QpjyEZ~4gkeHY%VQ6+kXTR*;zc4vlG z>ki!7cgTtQ4p)7;B*1~Ei)0y?72;1f>YI~#zq0AnX`e628KhtE6ql+elFL$@N3YM& z>-nNn`Q^3XJwd1$Z{LCM*?RO!UzLtiwKRSBc)4$zjK^e#Fs`dYo{sc^L|D(ERT?@Du78U$u6a~Id% zyB`B>Or3#y%N^9jmc4yMYs`Z48wyU2eWm9|Rdo9Z4whbtgMK66jZec;=oe+4vkO4d zik}|bKg{>ZSmEq#$%{B&leADo2J6YX?@wTAstbL;<$(fZ*A62by~P0WbUf>!Q4g?} zN(sT0Js>KIJ#_PhykMZ#~qoqh420J759Xzk^Af72rx+V1&O$7J)hLo zDfSRR{RXOPY+c@Y6@n|dBiea~_Wdu%CyJO6i1D=0*rT(jy1tlIq%kKg8&I=tT*5v( zU>Plw$byxvp<#-hdLB7V@3-1lvzCRJgRT zCH5NT{nQQvjlhvbr9@T@jtyzhpFxZQy(&gIOALsCn9ju15+uqVZgVT6XohOB#K40T zmOC7j)a0=tI$0irkr&wVtp7wl4LGM?_jyv7DrHSkg%%fR{}v#7CyZ}L|e&+ z?CO&~^CGm3NaB1Cwq*p&j~^}15?T%EI=oclee{3e%Us;GS)xw8o1l0yL6}|V$V`cg zwV8f0J&}#>qtqkL-A~9N==z3*)OA(2gnmL%Z?%(4omT{wju%1O^Zm-T-m!|YBFwRZ zFA`^zw!iA0OqQ5!tForl(&iOe)FgL92>H0@O#OfgmPXoP2>`!bjK?L3@eCri;MY;1 zjy`g57q;ams)OZx`S$Cn++Uq$M&NUBtX?n2rzsopobuUIa~LTco->6B=xz&65Q#e@ZTy=(xZEu+BF z<1t{lGQmPf>AyB$s!KPS2XHlsixTyX3oEV89=qYoB}@BQR=twpIk zZ#7=y)Vb5hQMV|58_dRcL}S)`*UNNw9=`Rpg$G< z5UFYT)po!+xbl;aZ&9G)x!&n^A#$sR$q!*-5x_|Rj10lbJig#RhlOsI`Hl~P%Ef_F zzlOt58nAL&=@33_CzSG53bj71Sw|zACkJ|Z!x-vA&M*C6Br#W~GzNG%y`>D81G04x3f=TCh(ulY{Ug*tbwXIwWh+@~_tFETxHvJxNTg?JIni*AC zkE&$gMMhz~1Qml~14g;1yUvP7ZMCa;~N& zjyLjo-J3>WLYW2)l*r?wgZR4xsgh^ANmSYu`Vja(wpwp93mwzgX#tEuqAyl zS}~62-pTmm3QQPKU6DYLU-o z_?p29)~uGZ0PtW-&H@%E0UJ=daa+<6iqFhVuhOl6&~170^N>xXG@bjmm;EofYjjF& zc%Q8Rm!E09Rv3Ua2$L3IWsd@FEP2omXlVl`uMQSKW-*YpgocJnEH#QA3jIuNyN$WvU}u+4Si203E_tg>BkIA^2(e1%Bt&N7 zxUG(VS!*i8w%btOw4l)=fvtxN+?@+~$>xyWySiyI?=Do|ET{QZQO(JL3BDz)2n*5_ z*vCh|%Oy{;Q&e|$T83};nBUZBOOo%g4Cm=zFjWb{GePQCHCyGiu4)t3EZ?URuuDNa z@&*mK<^W}sV2lkcy8GQ-U8GK_67W|V;TGGRkKWa+@+d)=hrWHwLv73`;m_s(voF!oIg?pfi@h`B44j44kQ13>rd?N2%3!sel8~#{GZDYnb*6-`)wXW-Ph8# zw=*D-!kvuu9@W*_G6u?N@%rW?z{kL2-2;u;Qh#*+{(bD$6!NGNVp&l%V?2os_2y`|n|iir8w%}T)`Q$>J zR_{i{K3h4T_Ixn2vUgPkigEP*P?x*cFY_1-2?&6#d)dxa8$2qN>aqe^)_h1y8(+z{JrpDkG;Wv?XE1COdN zayVoGbzJHr#p7@J3yy)W{@?AQfvR;nD5pQz!1`p5w8#DRK!0#Lj27q%E;PJ@|Jhpy zZX1AlEx>9JZ^{4j`=_t|c6edWzN+PSl*#eq%~FHO-sed&=bvvL(uDF^6aWAEbnv!q zzX0q4K>%NEf|hiQ&o5vClL$C(wn|%SQ3`0-w``2d>COq%D~84BtFOQ%{Wifi_{^}I zCBH)o^FI^w=%HL2iHi)_kRg8_cnupDTYJEclPB`CPfkrG49=h@&|PkS_0N+U;?G!% zv58(?l!>fQkj|FxLwzy-rhz%(0EZFQ{`b&Qn$5%ODb5>jQ&Z~g! zXd^sn*;Z}dTf0>nQ+7LFRK^=Q$OIP|gG8OwYzDRxza6>jhl&F0-l0R4@md>T2Ck7W z@K6l~tXUppd{dx1PqsDM;IOoy9(24Y-}tohb$KshfT*SISpAO7J700u^5t{9nRSV1 z2I+&as0~atIXb0M#cChTm@=O@Q{+gG3|B>>Sqr3ZFiJ=3r0q5nMu?V`>F&=%1;rc+ zbw|sGx(%l4ptr`CE2b9aZwq!hzSiAegRZfvJU+iUf9HOfGmrCp^X}Rc)v3pvr(BhinJzT;(~kZg4`H94gZNWST_31+2awYO-b|3-}@&bp9jW>xd(c!*FMoA z_32k>pdN`Z=q1iEnx_E`V=!&pn860l)*uyZ{666GKqzRsB@dfoc#gJNNpA^$(ztix z3;jt-X!cm=O(jqBOWDRa_2aS~`yf#tl{arG+1#~x_|U?<)-k$=_JNn2^nWvFbn|;88?1sUGlPBA}UJ`H6_w-+&is6xwqY;s+rl2*5mu`q|m~QqAp8gt; ztKu-aiGDHLjc_nop;vL5*vK14NrrBF>))|Dk;~Joyyb~0jwyVW{MuktqQfw8S$`|41FIg&5bJ&E08~IDgH$JlcQNpS*6~-4PkKm<`&2M>E zj(?~68q--N1BC+BF%olzrc&BSaouU`hU1ol_fy3fUr}1H>A28j*Wkph_S6L(UJVxd z=2jXXSUU`==gc76P07lKxEry@QCy>z33krXf70RB2|zx{R23S;G^-kI5)az})gwly^U zD8AYJthP(cb5$vs?M~k+dFN!-;uzgs)YL~!B_CveJN{%Pt#&+WxB&!C!ms~ltw@c@4eJNs=4W&|_Hatj*Kx;r7y9$E{zgDpFM#%+!j#E_zYgm_RM>O;r0~f|@AdGhPV2r%O&0(Lwj2 z5Y|)NKXWfDe-eYvv!9PReEKPr@LL~;K}_PS2f9Vwed0xnN$+}By$<(+9Nfp~O|u-5 zOo+MbEV-RDhWNx6AK$B52`t`E&|1M-PQM- zAb+r==RynF+&3OZe}^Am2wT!RKiW?1OwL9|6a8vfnFWq&Ie`X`&EJl2Nw7DtCXn7& z`>-k6L9|HtyVYaoxu4Z4c4o=D;R&NSyW=;NgM1q&RE!TFa|_eDZYLPGkTdk@wr#wz zyqU`;=ot5|-9e~}dKF1PMsbvBd^E;on>bMAu~>GQPE>Qdw^~}RQuMyVDBbfU?$NR0 z4^d-f!}#Nl^z86CUQGHo%p{}y#XdH-7mPjHfi`987HbL{Yz3_^4T8&`4}Jm5y>bAb z)jkXpLmZ)4yApAzjEwmhT+sL;pYZ7nN@EV}G1!d(bGNkKzZW}s#jYEU;(1L(88^=O zkl5wzrZ>o*=}R+(@#@kobSvkTnc5iX$%o*rSTCOsnS6Mx0rfaL&MF<+c&SfH2VS}o zl|v<_ms;_aBwG=3RJ%2tC&ur!B$wkZC9s>Awf0P4Im(jRpf5mEt8h;?s9g1yPQeWa zmkusL!JowBx8GWEt%dQpYG`Z|y;H-hZbFU>QL&-2RQ9Q&xCy?pj8W@S6-TBG0KfA7B7 zb1UB9srlC6%w_7Ymf@YY2_FfuKm-`tXOd3UQLw25^+YXZaX{C%aI76u#@dI`t8f}v z)J-%PVH85A8I42^hXm0rY4h0n{pX(A0?l#)5O+R zl9`V;2v4~=DZE)4240#d_SiX}=KLi4>)6cuH}Qk`IbDA3%>J%-B%>+MV8Wx%PAn)y zkpciI1iXa&h`EU2Q$6xoS$%nfej}%L`L9AC?}iMf1_!J zNTNCJFGlG8rJj0Cxm0TXkjmMsNqa3*J7-3K6L*XZsM@`5?Np}^4w|*)FoK_8>vF~M z!_!^JE&_v_w{HnNe&Jve`T6=IJ>NH^Ue;o*JYd7}A`5_fvt=C?zkl^~{(v9BAOgXL zm$92p{zCMB+1LJcfDMo%_7_iR{`F;vnj~-%&rs^zf8zM-RQcFlALpd(|2{Lgq$fDZ zlAuj7)qkDkFOd7!Z~X7Z{}=K9O_jeE#NXc1|1HjclGgvS%K!5C{|eCGCH4Q|%DH-S a^G*?${fw#^_`{qK*+)tb^Bx$!`2PS{m12wl diff --git a/apps/website/screens/components/radio-group/specs/images/radio_group_base_states.png b/apps/website/screens/components/radio-group/specs/images/radio_group_base_states.png deleted file mode 100644 index 093b4d1568c0ca6777af56ce2076941c4c0bacf3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19903 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 Mn;h-#*t$pm4X>6>Gynhq diff --git a/apps/website/screens/components/radio-group/specs/images/radio_group_states.png b/apps/website/screens/components/radio-group/specs/images/radio_group_states.png deleted file mode 100644 index f0fb9ec00360b31e297ead18ea17ad425db1d4a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38186 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 F{{YdZdlCQu diff --git a/packages/lib/src/radio-group/Radio.tsx b/packages/lib/src/radio-group/Radio.tsx index 3b8580601e..ef42de1fec 100644 --- a/packages/lib/src/radio-group/Radio.tsx +++ b/packages/lib/src/radio-group/Radio.tsx @@ -1,171 +1,94 @@ -import { memo, useContext, useEffect, useId, useRef, useState } from "react"; -import styled, { ThemeProvider } from "styled-components"; -import { AdvancedTheme } from "../common/variables"; -import DxcFlex from "../flex/Flex"; -import HalstackContext from "../HalstackContext"; +import { memo, useEffect, useId, useRef, useState } from "react"; +import styled from "styled-components"; import { RadioProps } from "./types"; +import { icons, getRadioInputStyles } from "./utils"; -const DxcRadio = ({ - label, - checked, - onClick, - error, - disabled, - focused, - readOnly, - tabIndex, -}: RadioProps): JSX.Element => { - const radioLabelId = `radio-${useId()}`; - const ref = useRef(null); - const colorsTheme = useContext(HalstackContext); - - const handleOnClick = () => { - onClick(); - document.activeElement !== ref.current && ref.current?.focus(); - }; - - const [firstUpdate, setFirstUpdate] = useState(true); - useEffect(() => { - // Don't apply in the first render - if (firstUpdate) { - setFirstUpdate(false); - return; - } - focused && ref.current?.focus(); - }, [focused]); - - return ( - - - - - - {checked && } - - - - - - - ); -}; +const Label = styled.span<{ disabled: RadioProps["disabled"] }>` + 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); +`; type CommonStylingProps = { - error: RadioProps["error"]; disabled: RadioProps["disabled"]; + error: boolean; readOnly: RadioProps["readOnly"]; }; -const getRadioInputStateColor = ( - props: CommonStylingProps & { theme: AdvancedTheme["radioGroup"] }, - state: "enabled" | "hover" | "active" -) => { - switch (state) { - case "enabled": - return props.disabled - ? props.theme.disabledRadioInputColor - : props.error - ? props.theme.errorRadioInputColor - : props.readOnly - ? props.theme.readOnlyRadioInputColor - : props.theme.radioInputColor; - case "hover": - return props.error - ? props.theme.hoverErrorRadioInputColor - : props.readOnly - ? props.theme.hoverReadOnlyRadioInputColor - : props.theme.hoverRadioInputColor; - case "active": - return props.error - ? props.theme.activeErrorRadioInputColor - : props.readOnly - ? props.theme.activeReadOnlyRadioInputColor - : props.theme.activeRadioInputColor; - } -}; -const RadioInputContainer = styled.span` - display: flex; - align-items: center; - justify-content: center; - height: 24px; +const RadioButton = styled.span` + display: grid; + place-items: center; + height: var(--height-s); width: 24px; -`; - -const RadioInput = styled.span` - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - width: 18px; - height: 18px; - border: 2px solid ${(props) => getRadioInputStateColor(props, "enabled")}; border-radius: 50%; + ${({ disabled }) => disabled && "pointer-events: none;"} &:focus { - outline: 2px solid ${(props) => props.theme.focusBorderColor}; - outline-offset: 1px; + outline: var(--border-width-m) var(--border-style-default) var(--border-color-secondary-medium); + outline-offset: -2px; } - ${(props) => props.disabled && "pointer-events: none;"} `; -const Dot = styled.span` - height: 10px; - width: 10px; - border-radius: 50%; - background-color: ${(props) => getRadioInputStateColor(props, "enabled")}; -`; - -const Label = styled.span<{ disabled: RadioProps["disabled"] }>` - margin-left: ${(props) => props.theme.radioInputLabelMargin}; - font-family: ${(props) => props.theme.fontFamily}; - font-size: ${(props) => props.theme.radioInputLabelFontSize}; - font-style: ${(props) => props.theme.radioInputLabelFontStyle}; - font-weight: ${(props) => props.theme.radioInputLabelFontWeight}; - line-height: ${(props) => props.theme.radioInputLabelLineHeight}; - ${(props) => - props.disabled - ? `color: ${props.theme.disabledRadioInputLabelFontColor};` - : `color: ${props.theme.radioInputLabelFontColor}`} -`; - -const RadioContainer = styled.span` +const RadioInputContainer = styled.span` display: inline-flex; align-items: center; - cursor: ${(props) => (props.disabled ? "not-allowed" : props.readOnly ? "default" : "pointer")}; + gap: var(--spacing-gap-s); + color: ${({ disabled, error, readOnly }) => getRadioInputStyles(disabled, error, readOnly, "default")}; + cursor: ${({ disabled, readOnly }) => (disabled ? "not-allowed" : readOnly ? "default" : "pointer")}; - &:hover { - ${RadioInput} { - border-color: ${(props) => !props.disabled && getRadioInputStateColor(props, "hover")}; - } - ${Dot} { - background-color: ${(props) => !props.disabled && getRadioInputStateColor(props, "hover")}; - } + &:hover ${RadioButton} { + color: ${({ disabled, error, readOnly }) => getRadioInputStyles(disabled, error, readOnly, "hover")}; } - &:active { - ${RadioInput} { - border-color: ${(props) => !props.disabled && getRadioInputStateColor(props, "active")}; - } - ${Dot} { - background-color: ${(props) => !props.disabled && getRadioInputStateColor(props, "active")}; - } + &:active ${RadioButton} { + color: ${({ disabled, error, readOnly }) => getRadioInputStyles(disabled, error, readOnly, "active")}; } `; -export default memo(DxcRadio); +const RadioInput = ({ checked, disabled, error, focused, label, onClick, readOnly, tabIndex }: RadioProps) => { + const radioLabelId = `radio-${useId()}`; + const ref = useRef(null); + const [firstUpdate, setFirstUpdate] = useState(true); + + useEffect(() => { + // Don't apply in the first render + if (firstUpdate) { + setFirstUpdate(false); + return; + } + focused && ref.current?.focus(); + }, [focused]); + + const handleOnClick = () => { + onClick(); + document.activeElement !== ref.current && ref.current?.focus(); + }; + + return ( + + + {checked ? icons.checked : icons.unchecked} + + + + ); +}; + +export default memo(RadioInput); diff --git a/packages/lib/src/radio-group/RadioGroup.stories.tsx b/packages/lib/src/radio-group/RadioGroup.stories.tsx index 82d7356921..ab8a93d340 100644 --- a/packages/lib/src/radio-group/RadioGroup.stories.tsx +++ b/packages/lib/src/radio-group/RadioGroup.stories.tsx @@ -1,7 +1,6 @@ import { Meta, StoryObj } from "@storybook/react"; import ExampleContainer from "../../.storybook/components/ExampleContainer"; import Title from "../../.storybook/components/Title"; -import { HalstackProvider } from "../HalstackContext"; import DxcRadioGroup from "./RadioGroup"; export default { @@ -20,18 +19,11 @@ const options = [ const single_disabled_options = [{ label: "Option A", value: "A", disabled: true }]; -const opinionatedTheme = { - radioGroup: { - baseColor: "#0086e6", - fontColor: "#000000", - }, -}; - const RadioGroup = () => ( <> - + <Title title="Enabled" theme="light" level={2} /> <ExampleContainer> - <Title title="Enabled" theme="light" level={4} /> + <Title title="Default" theme="light" level={4} /> <DxcRadioGroup label="Label" helperText="Helper text" defaultValue="A" options={single_option} /> </ExampleContainer> <ExampleContainer pseudoState="pseudo-hover"> @@ -46,13 +38,14 @@ const RadioGroup = () => ( <Title title="Focused" theme="light" level={4} /> <DxcRadioGroup label="Label" helperText="Helper text" defaultValue="A" options={single_option} /> </ExampleContainer> + <Title title="Disabled" theme="light" level={2} /> <ExampleContainer> <Title title="Disabled" theme="light" level={4} /> <DxcRadioGroup label="Label" helperText="Helper text" options={single_disabled_options} defaultValue="A" /> </ExampleContainer> - <Title title="Readonly radio input sub-states" theme="light" level={3} /> + <Title title="Readonly" theme="light" level={2} /> <ExampleContainer> - <Title title="Enabled" theme="light" level={4} /> + <Title title="Default" theme="light" level={4} /> <DxcRadioGroup label="Label" helperText="Helper text" options={single_option} defaultValue="A" readOnly /> </ExampleContainer> <ExampleContainer pseudoState="pseudo-hover"> @@ -63,7 +56,7 @@ const RadioGroup = () => ( <Title title="Active" theme="light" level={4} /> <DxcRadioGroup label="Label" helperText="Helper text" options={single_option} defaultValue="A" readOnly /> </ExampleContainer> - <Title title="Error radio input sub-states" theme="light" level={3} /> + <Title title="Error" theme="light" level={2} /> <ExampleContainer> <Title title="Enabled" theme="light" level={4} /> <DxcRadioGroup @@ -125,91 +118,6 @@ const RadioGroup = () => ( <Title title="Error" theme="light" level={4} /> <DxcRadioGroup label="Label" error="Error message" helperText="Helper text" options={options} /> </ExampleContainer> - <Title title="Opinionated theme" theme="light" level={2} /> - <ExampleContainer> - <Title title="Enabled" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcRadioGroup label="Label" helperText="Helper text" defaultValue="A" options={single_option} /> - </HalstackProvider> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-hover"> - <Title title="Hovered" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcRadioGroup label="Label" helperText="Helper text" defaultValue="A" options={single_option} /> - </HalstackProvider> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-active"> - <Title title="Active" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcRadioGroup label="Label" helperText="Helper text" defaultValue="A" options={single_option} /> - </HalstackProvider> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-focus"> - <Title title="Focused" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcRadioGroup label="Label" helperText="Helper text" defaultValue="A" options={single_option} /> - </HalstackProvider> - </ExampleContainer> - <ExampleContainer> - <Title title="Disabled" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcRadioGroup label="Label" helperText="Helper text" options={single_disabled_options} defaultValue="A" /> - </HalstackProvider> - </ExampleContainer> - <ExampleContainer> - <Title title="Readonly enabled" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcRadioGroup label="Label" options={single_option} defaultValue="A" readOnly /> - </HalstackProvider> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-hover"> - <Title title="Readonly hovered" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcRadioGroup label="Label" options={single_option} defaultValue="A" readOnly /> - </HalstackProvider> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-active"> - <Title title="Readonly active" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcRadioGroup label="Label" options={single_option} defaultValue="A" readOnly /> - </HalstackProvider> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-focus"> - <Title title="Readonly focused" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcRadioGroup label="Label" options={single_option} defaultValue="A" readOnly /> - </HalstackProvider> - </ExampleContainer> - <ExampleContainer> - <Title title="Enabled" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcRadioGroup label="Label" options={single_option} defaultValue="A" error="Error message" /> - </HalstackProvider> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-hover"> - <Title title="Hovered" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcRadioGroup label="Label" options={single_option} defaultValue="A" error="Error message" /> - </HalstackProvider> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-active"> - <Title title="Active" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcRadioGroup label="Label" options={single_option} defaultValue="A" error="Error message" /> - </HalstackProvider> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-focus"> - <Title title="Focused" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcRadioGroup label="Label" options={single_option} defaultValue="A" error="Error message" /> - </HalstackProvider> - </ExampleContainer> - <ExampleContainer> - <Title title="Disabled" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcRadioGroup label="Label" helperText="Helper text" options={options} disabled defaultValue="A" /> - </HalstackProvider> - </ExampleContainer> </> ); diff --git a/packages/lib/src/radio-group/RadioGroup.tsx b/packages/lib/src/radio-group/RadioGroup.tsx index dce962d848..238591afe8 100644 --- a/packages/lib/src/radio-group/RadioGroup.tsx +++ b/packages/lib/src/radio-group/RadioGroup.tsx @@ -1,46 +1,52 @@ import { FocusEvent, forwardRef, KeyboardEvent, useCallback, useContext, useId, useMemo, useState } from "react"; -import styled, { ThemeProvider } from "styled-components"; -import HalstackContext, { HalstackLanguageContext } from "../HalstackContext"; -import DxcRadio from "./Radio"; -import RadioGroupPropsType, { RadioOption, RefType } from "./types"; +import styled from "styled-components"; +import { HalstackLanguageContext } from "../HalstackContext"; +import RadioInput from "./Radio"; +import RadioGroupPropsType, { RefType } from "./types"; +import Label from "../styles/forms/Label"; +import HelperText from "../styles/forms/HelperText"; +import ErrorMessage from "../styles/forms/ErrorMessage"; -const getInitialFocusIndex = (innerOptions: RadioOption[], value?: string) => { - const initialSelectedOptionIndex = innerOptions.findIndex((option) => option.value === value); - return initialSelectedOptionIndex !== -1 ? initialSelectedOptionIndex : 0; -}; +const RadioGroupContainer = styled.div` + box-sizing: border-box; + display: inline-flex; + flex-direction: column; +`; + +const RadioGroup = styled.div<{ stacking: RadioGroupPropsType["stacking"] }>` + display: flex; + flex-wrap: wrap; + flex-direction: ${({ stacking }) => stacking}; + column-gap: var(--spacing-gap-l); + row-gap: var(--spacing-gap-xs); +`; const DxcRadioGroup = forwardRef<RefType, RadioGroupPropsType>( ( { + ariaLabel = "Radio group", + defaultValue, + disabled = false, + error, + helperText, label, name, - helperText, - options, - disabled = false, + onBlur, + onChange, optional = false, optionalItemLabel, + options, readOnly = false, stacking = "column", - defaultValue, - value, - onChange, - onBlur, - error, tabIndex = 0, - ariaLabel = "Radio group", + value, }, ref - ): JSX.Element => { - const radioGroupId = `radio-group-${useId()}`; - const radioGroupLabelId = `label-${radioGroupId}`; - const errorId = `error-${radioGroupId}`; - - const [innerValue, setInnerValue] = useState(defaultValue); - const [firstTimeFocus, setFirstTimeFocus] = useState(true); - - const colorsTheme = useContext(HalstackContext); + ) => { + const id = `radio-group-${useId()}`; + const labelId = `label-${id}`; + const errorId = `error-${id}`; const translatedLabels = useContext(HalstackLanguageContext); - const innerOptions = useMemo( () => optional @@ -53,42 +59,40 @@ const DxcRadioGroup = forwardRef<RefType, RadioGroupPropsType>( }, ] : options, - [optional, options, optionalItemLabel, translatedLabels] + [optional, optionalItemLabel, options, translatedLabels] ); - - const [currentFocusIndex, setCurrentFocusIndex] = useState(getInitialFocusIndex(innerOptions, value ?? innerValue)); + const [innerValue, setInnerValue] = useState(defaultValue); + const [currentFocusIndex, setCurrentFocusIndex] = useState(() => { + const initialSelectedOptionIndex = innerOptions.findIndex((option) => option.value === (value ?? innerValue)); + return initialSelectedOptionIndex !== -1 ? initialSelectedOptionIndex : 0; + }); + const [firstTimeFocus, setFirstTimeFocus] = useState(true); const handleOnChange = useCallback( (newValue: string) => { const currentValue = value ?? innerValue; if (newValue !== currentValue && !readOnly) { - if (value == null) { - setInnerValue(newValue); - } + if (value == null) setInnerValue(newValue); onChange?.(newValue); } }, - [value, innerValue, onChange] + [innerValue, onChange, value] ); + const handleOnBlur = (event: FocusEvent<HTMLDivElement>) => { // If the radio group loses the focus to an element not contained inside it... if (!event.currentTarget.contains(event.relatedTarget as Node)) { setFirstTimeFocus(true); const currentValue = value ?? innerValue; - if (!optional && !currentValue) { - onBlur?.({ - value: currentValue, - error: translatedLabels.formFields.requiredSelectionErrorMessage, - }); - } else { - onBlur?.({ value: currentValue }); - } + onBlur?.({ + value: currentValue, + error: !optional && !currentValue ? translatedLabels.formFields.requiredSelectionErrorMessage : undefined, + }); } }; + const handleOnFocus = () => { - if (firstTimeFocus) { - setFirstTimeFocus(false); - } + if (firstTimeFocus) setFirstTimeFocus(false); }; const setPreviousRadioChecked = () => { @@ -98,12 +102,11 @@ const DxcRadioGroup = forwardRef<RefType, RadioGroupPropsType>( index = index === 0 ? innerOptions.length - 1 : index - 1; } const option = innerOptions[index]; - if (option != null) { - handleOnChange(option.value); - } + if (option != null) handleOnChange(option.value); return index; }); }; + const setNextRadioChecked = () => { setCurrentFocusIndex((currentFocusIndexValue) => { let index = currentFocusIndexValue === innerOptions.length - 1 ? 0 : currentFocusIndexValue + 1; @@ -111,12 +114,11 @@ const DxcRadioGroup = forwardRef<RefType, RadioGroupPropsType>( index = index === innerOptions.length - 1 ? 0 : index + 1; } const option = innerOptions[index]; - if (option != null) { - handleOnChange(option.value); - } + if (option != null) handleOnChange(option.value); return index; }); }; + const handleOnKeyDown = (event: KeyboardEvent<HTMLDivElement>) => { switch (event.key) { case "Left": @@ -135,9 +137,7 @@ const DxcRadioGroup = forwardRef<RefType, RadioGroupPropsType>( break; case " ": event.preventDefault(); - if (innerOptions[currentFocusIndex] != null) { - handleOnChange(innerOptions[currentFocusIndex].value); - } + if (innerOptions[currentFocusIndex] != null) handleOnChange(innerOptions[currentFocusIndex].value); break; default: break; @@ -145,112 +145,55 @@ const DxcRadioGroup = forwardRef<RefType, RadioGroupPropsType>( }; return ( - <ThemeProvider theme={colorsTheme.radioGroup}> - <RadioGroupContainer ref={ref}> - {label && ( - <Label id={radioGroupLabelId} helperText={helperText} disabled={disabled}> - {label} - {optional && <OptionalLabel>{` ${translatedLabels.formFields.optionalLabel}`}</OptionalLabel>} - </Label> - )} - {helperText && <HelperText disabled={disabled}>{helperText}</HelperText>} - <RadioGroup - onBlur={handleOnBlur} - onFocus={handleOnFocus} - onKeyDown={handleOnKeyDown} - stacking={stacking} - role="radiogroup" - aria-disabled={disabled} - aria-labelledby={label ? radioGroupLabelId : undefined} - aria-invalid={!!error} - aria-errormessage={error ? errorId : undefined} - aria-required={!disabled && !readOnly && !optional} - aria-readonly={readOnly} - aria-orientation={stacking === "column" ? "vertical" : "horizontal"} - aria-label={label ? undefined : ariaLabel} - > - <ValueInput name={name} disabled={disabled} value={value ?? innerValue ?? ""} readOnly /> - {innerOptions.map((option, index) => ( - <DxcRadio - key={`radio-${index}`} - label={option.label ?? ""} - checked={(value ?? innerValue) === option.value} - onClick={() => { - handleOnChange(option.value); - setCurrentFocusIndex(index); - }} - error={error} - disabled={option.disabled || disabled} - focused={currentFocusIndex === index} - readOnly={readOnly} - tabIndex={tabIndex} - /> - ))} - </RadioGroup> - {!disabled && typeof error === "string" && ( - <ErrorMessageContainer id={errorId} role="alert" aria-live={error ? "assertive" : "off"}> - {error} - </ErrorMessageContainer> - )} - </RadioGroupContainer> - </ThemeProvider> + <RadioGroupContainer ref={ref}> + {label && ( + <Label disabled={disabled} hasMargin={!helperText} id={labelId}> + {label} + {optional && <span>{` ${translatedLabels.formFields.optionalLabel}`}</span>} + </Label> + )} + {helperText && ( + <HelperText disabled={disabled} hasMargin> + {helperText} + </HelperText> + )} + <RadioGroup + aria-disabled={disabled} + aria-errormessage={error ? errorId : undefined} + aria-invalid={!!error} + aria-labelledby={label ? labelId : undefined} + aria-orientation={stacking === "column" ? "vertical" : "horizontal"} + aria-readonly={readOnly} + aria-required={!disabled && !readOnly && !optional} + aria-label={label ? undefined : ariaLabel} + onBlur={handleOnBlur} + onFocus={handleOnFocus} + onKeyDown={handleOnKeyDown} + role="radiogroup" + stacking={stacking} + > + <input disabled={disabled} name={name} readOnly type="hidden" value={value ?? innerValue ?? ""} /> + {innerOptions.map((option, index) => ( + <RadioInput + checked={(value ?? innerValue) === option.value} + disabled={option.disabled || disabled} + error={error} + focused={currentFocusIndex === index} + key={`radio-${index}`} + label={option.label ?? ""} + onClick={() => { + handleOnChange(option.value); + setCurrentFocusIndex(index); + }} + readOnly={readOnly} + tabIndex={tabIndex} + /> + ))} + </RadioGroup> + {!disabled && typeof error === "string" && <ErrorMessage error={error} id={errorId} />} + </RadioGroupContainer> ); } ); -const RadioGroupContainer = styled.div` - box-sizing: border-box; - display: inline-flex; - flex-direction: column; -`; - -const Label = styled.span<{ - helperText: RadioGroupPropsType["helperText"]; - disabled: RadioGroupPropsType["disabled"]; -}>` - color: ${(props) => (props.disabled ? props.theme.disabledLabelFontColor : props.theme.labelFontColor)}; - font-family: ${(props) => props.theme.fontFamily}; - font-size: ${(props) => props.theme.labelFontSize}; - font-style: ${(props) => props.theme.labelFontStyle}; - font-weight: ${(props) => props.theme.labelFontWeight}; - line-height: ${(props) => props.theme.labelLineHeight}; - ${(props) => !props.helperText && `margin-bottom: ${props.theme.groupLabelMargin}`} -`; - -const OptionalLabel = styled.span` - font-weight: ${(props) => props.theme.optionalLabelFontWeight}; -`; - -const HelperText = styled.span<{ disabled: RadioGroupPropsType["disabled"] }>` - color: ${(props) => (props.disabled ? props.theme.disabledHelperTextFontColor : props.theme.helperTextFontColor)}; - font-family: ${(props) => props.theme.fontFamily}; - font-size: ${(props) => props.theme.helperTextFontSize}; - font-style: ${(props) => props.theme.helperTextFontStyle}; - font-weight: ${(props) => props.theme.helperTextFontWeight}; - line-height: ${(props) => props.theme.helperTextLineHeight}; - margin-bottom: ${(props) => props.theme.groupLabelMargin}; -`; - -const RadioGroup = styled.div<{ stacking: RadioGroupPropsType["stacking"] }>` - display: flex; - flex-wrap: wrap; - flex-direction: ${(props) => props.stacking}; - row-gap: ${(props) => props.theme.groupVerticalGutter}; - column-gap: ${(props) => props.theme.groupHorizontalGutter}; -`; - -const ValueInput = styled.input` - display: none; -`; - -const ErrorMessageContainer = styled.span` - min-height: 1.5em; - color: ${(props) => props.theme.errorMessageColor}; - font-family: ${(props) => props.theme.fontFamily}; - font-size: 0.75rem; - font-weight: 400; - line-height: 1.5em; - margin-top: 0.5rem; -`; - export default DxcRadioGroup; diff --git a/packages/lib/src/radio-group/types.ts b/packages/lib/src/radio-group/types.ts index 0926a4838e..1d11a868ba 100644 --- a/packages/lib/src/radio-group/types.ts +++ b/packages/lib/src/radio-group/types.ts @@ -1,4 +1,4 @@ -export type RadioOption = { +type Option = { /** * Label of the option placed next to the radio input. */ @@ -17,26 +17,39 @@ export type RadioOption = { type RadioGroupProps = { /** - * Text to be placed above the radio group. + * Specifies a string to be used as the name for the radio group when no `label` is provided. */ - label?: string; + ariaLabel?: string; /** - * Name attribute of the input element. This attribute will allow users - * to find the component's value during the submit event. + * Initial value of the radio group, only when it is uncontrolled. */ - name?: string; + defaultValue?: 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 radio group. 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; /** * Helper text to be placed above the radio group. */ helperText?: string; /** - * An array of objects representing the selectable options. + * Text to be placed above the radio group. */ - options: RadioOption[]; + label?: string; /** - * If true, the component will be disabled. + * Name attribute of the input element. This attribute will allow users + * to find the component's value during the submit event. */ - disabled?: boolean; + name?: string; /** * If true, the radio group will be optional, showing * (Optional) next to the label and adding a default last @@ -50,51 +63,38 @@ type RadioGroupProps = { */ optionalItemLabel?: string; /** - * If true, the component will not be mutable, meaning the user can not edit the control. - */ - readOnly?: boolean; - /** - * Sets the orientation of the options within the radio group. - */ - stacking?: "row" | "column"; - /** - * Initial value of the radio group, only when it is uncontrolled. + * An array of objects representing the selectable options. */ - defaultValue?: string; + options: Option[]; /** - * Value of the radio group. If undefined, the component will be - * uncontrolled and the value will be managed internally by the - * component. + * This function will be called when the radio group loses the focus. An + * object including the value and the error will be passed to this + * function. If there is no error, error will not be defined. */ - value?: string; + onBlur?: (val: { value?: string; error?: string }) => void; /** * This function will be called when the user chooses an option. The new * value will be passed to this function. */ onChange?: (value: string) => void; /** - * This function will be called when the radio group loses the focus. An - * object including the value and the error will be passed to this - * function. If there is no error, error will not be defined. + * If true, the component will not be mutable, meaning the user can not edit the control. */ - onBlur?: (val: { value?: string; error?: string }) => void; + readOnly?: boolean; /** - * If it is a defined value and also a truthy string, the component will - * change its appearance, showing the error below the radio group. 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. + * Sets the orientation of the options within the radio group. */ - error?: string; + stacking?: "row" | "column"; /** * Value of the tabindex attribute. */ tabIndex?: number; /** - * Specifies a string to be used as the name for the radio group when no `label` is provided. + * Value of the radio group. If undefined, the component will be + * uncontrolled and the value will be managed internally by the + * component. */ - ariaLabel?: string; + value?: string; }; /** @@ -106,12 +106,12 @@ export type RefType = HTMLDivElement; * Single radio prop types. */ export type RadioProps = { - label: string; checked: boolean; - onClick: () => void; - error?: string; disabled: boolean; + error?: string; focused: boolean; + label: string; + onClick: () => void; readOnly: boolean; tabIndex: number; }; diff --git a/packages/lib/src/radio-group/utils.tsx b/packages/lib/src/radio-group/utils.tsx new file mode 100644 index 0000000000..a6904886eb --- /dev/null +++ b/packages/lib/src/radio-group/utils.tsx @@ -0,0 +1,52 @@ +export function getRadioInputStyles( + disabled: boolean, + error: boolean, + readOnly: boolean, + status: "default" | "hover" | "active" +) { + switch (true) { + case disabled: + return "var(--color-fg-neutral-medium)"; + case error: + return status === "default" + ? "var(--color-fg-error-medium)" + : status === "hover" + ? "var(--color-fg-error-strong)" + : "var(--color-fg-error-stronger)"; + case readOnly: + return status === "default" + ? "var(--color-fg-neutral-medium)" + : status === "hover" + ? "var(--color-fg-neutral-strong)" + : "var(--color-fg-neutral-stronger)"; + default: + return status === "default" + ? "var(--color-fg-secondary-medium)" + : status === "hover" + ? "var(--color-fg-secondary-strong)" + : "var(--color-fg-secondary-stronger)"; + } +} + +export const icons = { + checked: ( + <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> + <path + d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12ZM5.00194 12C5.00194 15.8649 8.13508 18.9981 12 18.9981C15.8649 18.9981 18.9981 15.8649 18.9981 12C18.9981 8.13508 15.8649 5.00194 12 5.00194C8.13508 5.00194 5.00194 8.13508 5.00194 12Z" + fill="currentColor" + /> + <path + d="M17 12C17 14.7614 14.7614 17 12 17C9.23858 17 7 14.7614 7 12C7 9.23858 9.23858 7 12 7C14.7614 7 17 9.23858 17 12Z" + fill="currentColor" + /> + </svg> + ), + unchecked: ( + <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> + <path + d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12ZM5.00194 12C5.00194 15.8649 8.13508 18.9981 12 18.9981C15.8649 18.9981 18.9981 15.8649 18.9981 12C18.9981 8.13508 15.8649 5.00194 12 5.00194C8.13508 5.00194 5.00194 8.13508 5.00194 12Z" + fill="currentColor" + /> + </svg> + ), +}; From aeeeb1e85d23e52446ba17ca1e2b2e9ad4f3ee16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20G=C3=B3mez=20Pinta?= <44321109+GomezIvann@users.noreply.github.com> Date: Thu, 27 Mar 2025 13:51:51 +0100 Subject: [PATCH 5/9] Radio group updates --- packages/lib/src/radio-group/RadioGroup.tsx | 2 +- .../lib/src/radio-group/{Radio.tsx => RadioInput.tsx} | 10 +++++----- packages/lib/src/radio-group/types.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) rename packages/lib/src/radio-group/{Radio.tsx => RadioInput.tsx} (91%) diff --git a/packages/lib/src/radio-group/RadioGroup.tsx b/packages/lib/src/radio-group/RadioGroup.tsx index 238591afe8..80b732dbf3 100644 --- a/packages/lib/src/radio-group/RadioGroup.tsx +++ b/packages/lib/src/radio-group/RadioGroup.tsx @@ -1,7 +1,7 @@ import { FocusEvent, forwardRef, KeyboardEvent, useCallback, useContext, useId, useMemo, useState } from "react"; import styled from "styled-components"; import { HalstackLanguageContext } from "../HalstackContext"; -import RadioInput from "./Radio"; +import RadioInput from "./RadioInput"; import RadioGroupPropsType, { RefType } from "./types"; import Label from "../styles/forms/Label"; import HelperText from "../styles/forms/HelperText"; diff --git a/packages/lib/src/radio-group/Radio.tsx b/packages/lib/src/radio-group/RadioInput.tsx similarity index 91% rename from packages/lib/src/radio-group/Radio.tsx rename to packages/lib/src/radio-group/RadioInput.tsx index ef42de1fec..d3244dd449 100644 --- a/packages/lib/src/radio-group/Radio.tsx +++ b/packages/lib/src/radio-group/RadioInput.tsx @@ -1,9 +1,9 @@ import { memo, useEffect, useId, useRef, useState } from "react"; import styled from "styled-components"; -import { RadioProps } from "./types"; +import { RadioInputProps } from "./types"; import { icons, getRadioInputStyles } from "./utils"; -const Label = styled.span<{ disabled: RadioProps["disabled"] }>` +const Label = styled.span<{ disabled: RadioInputProps["disabled"] }>` 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); @@ -11,9 +11,9 @@ const Label = styled.span<{ disabled: RadioProps["disabled"] }>` `; type CommonStylingProps = { - disabled: RadioProps["disabled"]; + disabled: RadioInputProps["disabled"]; error: boolean; - readOnly: RadioProps["readOnly"]; + readOnly: RadioInputProps["readOnly"]; }; const RadioButton = styled.span<CommonStylingProps>` @@ -45,7 +45,7 @@ const RadioInputContainer = styled.span<CommonStylingProps>` } `; -const RadioInput = ({ checked, disabled, error, focused, label, onClick, readOnly, tabIndex }: RadioProps) => { +const RadioInput = ({ checked, disabled, error, focused, label, onClick, readOnly, tabIndex }: RadioInputProps) => { const radioLabelId = `radio-${useId()}`; const ref = useRef<HTMLSpanElement>(null); const [firstUpdate, setFirstUpdate] = useState(true); diff --git a/packages/lib/src/radio-group/types.ts b/packages/lib/src/radio-group/types.ts index 1d11a868ba..81c86de584 100644 --- a/packages/lib/src/radio-group/types.ts +++ b/packages/lib/src/radio-group/types.ts @@ -103,9 +103,9 @@ type RadioGroupProps = { export type RefType = HTMLDivElement; /** - * Single radio prop types. + * Radio input prop types. */ -export type RadioProps = { +export type RadioInputProps = { checked: boolean; disabled: boolean; error?: string; From abbdfd65b49f3dba5e8c27b4091c5ca783416811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20G=C3=B3mez=20Pinta?= <44321109+GomezIvann@users.noreply.github.com> Date: Thu, 3 Apr 2025 11:42:56 +0200 Subject: [PATCH 6/9] Updates based on feedback --- packages/lib/src/styles/forms/HelperText.tsx | 2 +- packages/lib/src/styles/forms/Label.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/lib/src/styles/forms/HelperText.tsx b/packages/lib/src/styles/forms/HelperText.tsx index d35a3592c6..800dae63f1 100644 --- a/packages/lib/src/styles/forms/HelperText.tsx +++ b/packages/lib/src/styles/forms/HelperText.tsx @@ -5,7 +5,7 @@ const HelperText = styled.span<{ disabled: boolean, hasMargin?: boolean }>` font-family: var(--typography-font-family); font-size: var(--typography-helper-text-s); font-weight: var(--typography-helper-text-regular); - ${({ hasMargin = false }) => hasMargin && "margin-bottom: var(--spacing-gap-xs);"} + ${({ hasMargin = false }) => hasMargin && "margin-bottom: var(--spacing-padding-xxs);"} `; export default HelperText; \ No newline at end of file diff --git a/packages/lib/src/styles/forms/Label.tsx b/packages/lib/src/styles/forms/Label.tsx index 4f8fcd879a..37f30d3c5a 100644 --- a/packages/lib/src/styles/forms/Label.tsx +++ b/packages/lib/src/styles/forms/Label.tsx @@ -8,7 +8,7 @@ const Label = styled.label<{ font-family: var(--typography-font-family); font-size: var(--typography-label-m); font-weight: var(--typography-label-semibold); - ${({ hasMargin = false }) => hasMargin && "margin-bottom: var(--spacing-gap-xs);"} + ${({ hasMargin = false }) => hasMargin && "margin-bottom: var(--spacing-padding-xxs);"} /* Optional text */ > span { From 256b09e3c2911249a0658250814c2d1d14727f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20G=C3=B3mez=20Pinta?= <44321109+GomezIvann@users.noreply.github.com> Date: Tue, 8 Apr 2025 13:17:17 +0200 Subject: [PATCH 7/9] Radio group documentation update --- .../radio-group/RadioGroupPageLayout.tsx | 2 +- .../overview/RadioGroupOverviewPage.tsx | 211 +++++++++++++++--- .../overview/images/radio_group_anatomy.png | Bin 0 -> 47309 bytes 3 files changed, 176 insertions(+), 37 deletions(-) create mode 100644 apps/website/screens/components/radio-group/overview/images/radio_group_anatomy.png diff --git a/apps/website/screens/components/radio-group/RadioGroupPageLayout.tsx b/apps/website/screens/components/radio-group/RadioGroupPageLayout.tsx index 103ccc05a1..046ef4d367 100644 --- a/apps/website/screens/components/radio-group/RadioGroupPageLayout.tsx +++ b/apps/website/screens/components/radio-group/RadioGroupPageLayout.tsx @@ -16,7 +16,7 @@ const RadioGroupPageHeading = ({ children }: { children: ReactNode }) => { <DxcFlex direction="column" gap="2rem"> <ComponentHeading name="Radio group" /> <DxcParagraph> - A radio group let the user make a mutually exclusive selection from a group of options. + A radio group allows users to select one option from a set of related, mutually exclusive choices. </DxcParagraph> <TabsPageHeading tabs={tabs} /> </DxcFlex> diff --git a/apps/website/screens/components/radio-group/overview/RadioGroupOverviewPage.tsx b/apps/website/screens/components/radio-group/overview/RadioGroupOverviewPage.tsx index 065609552e..7a5a574ed1 100644 --- a/apps/website/screens/components/radio-group/overview/RadioGroupOverviewPage.tsx +++ b/apps/website/screens/components/radio-group/overview/RadioGroupOverviewPage.tsx @@ -1,82 +1,221 @@ -import { DxcBulletedList, DxcParagraph, DxcFlex, DxcTable } from "@dxc-technology/halstack-react"; +import { DxcBulletedList, DxcParagraph, DxcFlex, DxcTable, DxcLink } from "@dxc-technology/halstack-react"; import QuickNavContainer from "@/common/QuickNavContainer"; import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; import DocFooter from "@/common/DocFooter"; import Example from "@/common/example/Example"; import stacking from "./examples/stacking"; import HeaderDescriptionCell from "@/common/HeaderDescriptionCell"; +import Image from "@/common/Image"; +import anatomy from "./images/radio_group_anatomy.png"; +import Link from "next/link"; const sections = [ { title: "Introduction", content: ( <DxcParagraph> - Radio buttons are used when there is a list of two or more options that are mutually exclusive and the user must - select exactly one choice. In a group of radio buttons, only one radio button at a time can be selected. Radio - buttons are always used in groups, and each option is represented by one radio button + The radio group component allows users to <strong>select a single option</strong> from a predefined set of + choices. Each option is represented by a radio button, ensuring clear and mutually exclusive selection within + the group. This component is commonly used in forms, surveys, and settings where users need to make a definitive + choice. By organizing options in a structured manner, the radio group enhances usability and readability, + guiding users toward a straightforward decision-making process. </DxcParagraph> ), }, { - title: "Stacking", + title: "Anatomy", content: ( <> - <Example example={stacking} /> + <Image src={anatomy} alt="Radio group's anatomy" /> + <DxcBulletedList type="number"> + <DxcBulletedList.Item> + <strong>Label</strong> <em>(Optional)</em>: the main heading for the radio group. It clearly communicates + what decision the user is being asked to make. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Helper text</strong> <em>(Optional)</em>: optional supporting text that provides additional context + or instructions to guide the user's choice. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Radio input:</strong> the circular selection control representing each option. Only one radio input + can be selected within the group at a time. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Radio input label:</strong> the descriptive text placed next to each radio input. It communicates + the meaning of the option and is always visible for clarity. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Error message:</strong> displayed below the group when validation fails. It explains what went wrong + and helps users correct their input. + </DxcBulletedList.Item> + </DxcBulletedList> + </> + ), + }, + { + title: "Stacking radio buttons", + content: ( + <DxcParagraph> + Radio buttons can be arranged vertically or horizontally depending on the context, layout constraints, and user + experience considerations. + </DxcParagraph> + ), + subSections: [ + { + title: "Vertical stacking", + content: ( + <DxcParagraph> + Vertical stacking is the preferred layout for radio groups, especially in forms or when presenting more than + two options. It <strong>enhances readability</strong> by allowing users to scan and compare choices with + minimal cognitive load. This format supports accessibility and scales well with longer option labels. + </DxcParagraph> + ), + }, + { + title: "Horizontal stacking", + content: ( + <> + <DxcParagraph> + Horizontal stacking is suitable when screen space is limited or when options are short and easily + distinguishable. This layout <strong>can reduce vertical scroll</strong> but should only be used when it + doesn't compromise readability. Always maintain visual grouping and alignment, and ensure that labels + remain clear and unambiguous. + </DxcParagraph> + <Example example={stacking} defaultIsVisible={false} /> + </> + ), + }, + ], + }, + { + title: "Radio group vs. checkbox vs. switch", + content: ( + <> + <DxcParagraph> + Although radio groups,{" "} + <Link href="/components/checkbox" passHref legacyBehavior> + <DxcLink>checkboxes</DxcLink> + </Link> + , and{" "} + <Link href="/components/switch" passHref legacyBehavior> + <DxcLink>switches</DxcLink> + </Link>{" "} + may appear as selection controls, they serve distinct purposes in a user interface: + </DxcParagraph> <DxcTable> <thead> <tr> - <th>Name</th> - <HeaderDescriptionCell>Description</HeaderDescriptionCell> + <th>Component</th> + <HeaderDescriptionCell>Use case</HeaderDescriptionCell> </tr> </thead> <tbody> <tr> <td> - <strong>Vertical</strong> + <strong>Radio group</strong> + </td> + <td> + Use when the user must select <strong>only one option</strong> from a list of predefined, mutually + exclusive choices. Ideal for short, static lists where all options should be visible at once to support + decision-making. + </td> + </tr> + <tr> + <td> + <strong>Checkbox</strong> </td> <td> - Short lists of radio buttons should be stacked vertically below a descriptive label to better associate - the group. Options that are listed vertically are easier to read. + Use when users can select <strong>multiple options</strong> independently. Each checkbox represents an + on/off decision, making them suitable for filters, preference settings, or multi-select tasks. A group + may allow none, some, or all options to be selected. </td> </tr> <tr> <td> - <strong>Horizontal</strong> + <strong>Switch</strong> </td> <td> - Multiple radio buttons may be displayed horizontally across the page while keeping them aligned within - their respective columns. Here, it is needed to have in consideration that the linear radio buttons - represent some challenge, because it's difficult to scan and localize. + Use for a <strong>single, immediate toggle</strong> between two states, like on/off or enabled/disabled. + Switches should act instantly and are best for system or UI-level settings. </td> </tr> </tbody> </DxcTable> - <DxcParagraph> - <em> - * In any case, in the specification it is specified the ideal distance between component with label in the - same horizontal edge to avoid the problem of pairing and scalability. - </em> - </DxcParagraph> </> ), }, { title: "Best practices", - content: ( - <DxcBulletedList> - <DxcBulletedList.Item> - Labelling should be concise and clearly differentiated from other options. - </DxcBulletedList.Item> - <DxcBulletedList.Item> - One option of the radio group can be pre-selected. Select the safest or convenient option. - </DxcBulletedList.Item> - <DxcBulletedList.Item>Single radio button should not be used.</DxcBulletedList.Item> - <DxcBulletedList.Item> - If the question that the user needs to respond is as easier as yes/no, it is recommended to use a checkbox - instead of radio group. - </DxcBulletedList.Item> - </DxcBulletedList> - ), + subSections: [ + { + title: "Use for mutually exclusive choices", + content: ( + <DxcBulletedList> + <DxcBulletedList.Item> + Radio Groups are best suited when users need to select <strong>only one option</strong> from a predefined + list. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + Avoid using them for multiple selections — checkboxes are more appropriate in that case. + </DxcBulletedList.Item> + </DxcBulletedList> + ), + }, + { + title: "Keep option labels short and clear", + content: ( + <DxcBulletedList> + <DxcBulletedList.Item> + Use concise, descriptive labels so users can quickly understand each choice. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + Avoid using long sentences or technical jargon, which can overwhelm the layout and slow down + decision-making. + </DxcBulletedList.Item> + </DxcBulletedList> + ), + }, + { + title: "Default to vertical layout for clarity", + content: ( + <DxcBulletedList> + <DxcBulletedList.Item> + Stacking options vertically improves readability and makes scanning easier, especially when there are more + than two options. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + Use horizontal layout only when space is limited or options are very short and simple. + </DxcBulletedList.Item> + </DxcBulletedList> + ), + }, + { + title: "Group related options together", + content: ( + <DxcBulletedList> + <DxcBulletedList.Item> + Ensure all radio buttons in a group are logically related and fall under the same question or decision + point. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + Never separate radio buttons from their group label or helper text — they should feel like a cohesive + unit. + </DxcBulletedList.Item> + </DxcBulletedList> + ), + }, + { + title: "Handle errors gracefully", + content: ( + <DxcBulletedList> + <DxcBulletedList.Item> + Use validation to prevent submission without a selection if required, and display clear, specific error + messages. + </DxcBulletedList.Item> + </DxcBulletedList> + ), + }, + ], }, ]; diff --git a/apps/website/screens/components/radio-group/overview/images/radio_group_anatomy.png b/apps/website/screens/components/radio-group/overview/images/radio_group_anatomy.png new file mode 100644 index 0000000000000000000000000000000000000000..e2f09b8c0dba0c2718e60775112e64533a6d0cd9 GIT binary patch literal 47309 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 DK$^^y literal 0 HcmV?d00001 From 3a262590cca1513d0a27c590bdfad8acda2dfb8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20G=C3=B3mez=20Pinta?= <44321109+GomezIvann@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:08:25 +0200 Subject: [PATCH 8/9] Radio group documentation updates --- .../overview/RadioGroupOverviewPage.tsx | 124 ++++++------------ 1 file changed, 41 insertions(+), 83 deletions(-) diff --git a/apps/website/screens/components/radio-group/overview/RadioGroupOverviewPage.tsx b/apps/website/screens/components/radio-group/overview/RadioGroupOverviewPage.tsx index 7a5a574ed1..365833a937 100644 --- a/apps/website/screens/components/radio-group/overview/RadioGroupOverviewPage.tsx +++ b/apps/website/screens/components/radio-group/overview/RadioGroupOverviewPage.tsx @@ -55,10 +55,13 @@ const sections = [ { title: "Stacking radio buttons", content: ( - <DxcParagraph> - Radio buttons can be arranged vertically or horizontally depending on the context, layout constraints, and user - experience considerations. - </DxcParagraph> + <> + <DxcParagraph> + Radio buttons can be arranged vertically or horizontally depending on the context, layout constraints, and + user experience considerations. + </DxcParagraph> + <Example example={stacking} defaultIsVisible={false} /> + </> ), subSections: [ { @@ -74,15 +77,12 @@ const sections = [ { title: "Horizontal stacking", content: ( - <> - <DxcParagraph> - Horizontal stacking is suitable when screen space is limited or when options are short and easily - distinguishable. This layout <strong>can reduce vertical scroll</strong> but should only be used when it - doesn't compromise readability. Always maintain visual grouping and alignment, and ensure that labels - remain clear and unambiguous. - </DxcParagraph> - <Example example={stacking} defaultIsVisible={false} /> - </> + <DxcParagraph> + Horizontal stacking is suitable when screen space is limited or when options are short and easily + distinguishable. This layout <strong>can reduce vertical scroll</strong> but should only be used when it + doesn't compromise readability. Always maintain visual grouping and alignment, and ensure that labels remain + clear and unambiguous. + </DxcParagraph> ), }, ], @@ -146,76 +146,34 @@ const sections = [ }, { title: "Best practices", - subSections: [ - { - title: "Use for mutually exclusive choices", - content: ( - <DxcBulletedList> - <DxcBulletedList.Item> - Radio Groups are best suited when users need to select <strong>only one option</strong> from a predefined - list. - </DxcBulletedList.Item> - <DxcBulletedList.Item> - Avoid using them for multiple selections — checkboxes are more appropriate in that case. - </DxcBulletedList.Item> - </DxcBulletedList> - ), - }, - { - title: "Keep option labels short and clear", - content: ( - <DxcBulletedList> - <DxcBulletedList.Item> - Use concise, descriptive labels so users can quickly understand each choice. - </DxcBulletedList.Item> - <DxcBulletedList.Item> - Avoid using long sentences or technical jargon, which can overwhelm the layout and slow down - decision-making. - </DxcBulletedList.Item> - </DxcBulletedList> - ), - }, - { - title: "Default to vertical layout for clarity", - content: ( - <DxcBulletedList> - <DxcBulletedList.Item> - Stacking options vertically improves readability and makes scanning easier, especially when there are more - than two options. - </DxcBulletedList.Item> - <DxcBulletedList.Item> - Use horizontal layout only when space is limited or options are very short and simple. - </DxcBulletedList.Item> - </DxcBulletedList> - ), - }, - { - title: "Group related options together", - content: ( - <DxcBulletedList> - <DxcBulletedList.Item> - Ensure all radio buttons in a group are logically related and fall under the same question or decision - point. - </DxcBulletedList.Item> - <DxcBulletedList.Item> - Never separate radio buttons from their group label or helper text — they should feel like a cohesive - unit. - </DxcBulletedList.Item> - </DxcBulletedList> - ), - }, - { - title: "Handle errors gracefully", - content: ( - <DxcBulletedList> - <DxcBulletedList.Item> - Use validation to prevent submission without a selection if required, and display clear, specific error - messages. - </DxcBulletedList.Item> - </DxcBulletedList> - ), - }, - ], + content: ( + <DxcBulletedList> + <DxcBulletedList.Item> + <strong>Use for mutually exclusive choices:</strong> radio groups are best suited when users need to select{" "} + <strong>only one option</strong> from a predefined list. Avoid using them for multiple selections — checkboxes + are more appropriate in that case. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Keep option labels short and clear:</strong> use concise, descriptive labels so users can quickly + understand each choice. Avoid using long sentences or technical jargon, which can overwhelm the layout and + slow down decision-making. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Default to vertical layout for clarity:</strong> stacking options vertically improves readability and + makes scanning easier, especially when there are more than two options. Use horizontal layout only when space + is limited or options are very short and simple. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Group related options together:</strong> ensure all radio buttons in a group are logically related and + fall under the same question or decision point. Never separate radio buttons from their group label or helper + text — they should feel like a cohesive unit. + </DxcBulletedList.Item> + <DxcBulletedList.Item> + <strong>Handle errors gracefully:</strong> use validation to prevent submission without a selection if + required, and display clear, specific error messages. + </DxcBulletedList.Item> + </DxcBulletedList> + ), }, ]; From 327bdc959e0bc48949ef02177b310ee2062793cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20G=C3=B3mez=20Pinta?= <44321109+GomezIvann@users.noreply.github.com> Date: Wed, 9 Apr 2025 13:26:14 +0200 Subject: [PATCH 9/9] Feedback update --- packages/lib/src/styles/forms/ErrorMessage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lib/src/styles/forms/ErrorMessage.tsx b/packages/lib/src/styles/forms/ErrorMessage.tsx index 95095a3e3c..9dbe45fb29 100644 --- a/packages/lib/src/styles/forms/ErrorMessage.tsx +++ b/packages/lib/src/styles/forms/ErrorMessage.tsx @@ -7,7 +7,7 @@ const ErrorMessageContainer = styled.div` gap: var(--spacing-gap-xs); color: var(--color-fg-error-medium); font-size: var(--typography-helper-text-s); - font-weight: var(--typography-helper-text-regular, 400); + font-weight: var(--typography-helper-text-regular); margin-top: var(--spacing-gap-xs); /* Error icon */