diff --git a/apps/website/screens/common/themes/advanced-theme.json b/apps/website/screens/common/themes/advanced-theme.json index 2ec0b6c056..e489c9afaf 100644 --- a/apps/website/screens/common/themes/advanced-theme.json +++ b/apps/website/screens/common/themes/advanced-theme.json @@ -2,45 +2,43 @@ "accordion": { "backgroundColor": "#ffffff", "hoverBackgroundColor": "#f2eafa", + "focusBackgroundColor": "transparent", + "activeBackgroundColor": "#f2eafa", "arrowColor": "#5f249f", "disabledArrowColor": "#999999", + "subLabelFontFamily": "Open Sans, sans-serif", + "subLabelFontSize": "0.75rem", + "subLabelFontWeight": "400", + "subLabelFontStyle": "normal", + "subLabelFontColor": "#666666", + "disabledSubLabelFontColor": "#999999", "assistiveTextFontFamily": "Open Sans, sans-serif", - "assistiveTextFontSize": "1rem", - "assistiveTextFontWeight": "300", - "assistiveTextFontStyle": "italic", - "assistiveTextLetterSpacing": "0.025em", + "assistiveTextFontSize": "0.75rem", + "assistiveTextFontWeight": "400", + "assistiveTextFontStyle": "normal", "assistiveTextFontColor": "#666666", "disabledAssistiveTextFontColor": "#999999", - "assistiveTextMinWidth": "100px", - "assistiveTextPaddingRight": "1.5rem", - "assistiveTextPaddingLeft": "0rem", "titleLabelFontFamily": "Open Sans, sans-serif", "titleLabelFontSize": "1rem", "titleLabelFontWeight": "400", "titleLabelFontStyle": "normal", - "titleLabelFontColor": "#000000", + "titleLabelFontColor": "#333333", "disabledTitleLabelFontColor": "#999999", - "titleLabelPaddingTop": "0rem", - "titleLabelPaddingBottom": "0rem", - "titleLabelPaddingLeft": "0rem", - "titleLabelPaddingRight": "1rem", "focusBorderColor": "#0095ff", "focusBorderStyle": "solid", "focusBorderThickness": "2px", "borderRadius": "4px", "boxShadowOffsetX": "0px", - "boxShadowOffsetY": "6px", - "boxShadowBlur": "10px", + "boxShadowOffsetY": "12px", + "boxShadowBlur": "12px", + "boxShadowSpread": "0px", "boxShadowColor": "#0000001a", "iconColor": "#5f249f", "disabledIconColor": "#999999", "iconSize": "24px", - "iconMarginLeft": "0rem", - "iconMarginRight": "0.75rem", - "accordionGroupSeparatorBorderColor": "#0000001a", - "accordionGroupSeparatorBorderThickness": "1px", - "accordionGroupSeparatorBorderRadius": "0px", - "accordionGroupSeparatorBorderStyle": "solid" + "accordionSeparatorBorderColor": "#e6e6e6", + "accordionSeparatorBorderThickness": "1px", + "accordionSeparatorBorderStyle": "solid" }, "alert": { "overlayColor": "#000000b3", diff --git a/apps/website/screens/common/themes/opinionated-theme.json b/apps/website/screens/common/themes/opinionated-theme.json index 050215d6ad..70f544101f 100644 --- a/apps/website/screens/common/themes/opinionated-theme.json +++ b/apps/website/screens/common/themes/opinionated-theme.json @@ -1,7 +1,8 @@ { "accordion": { "accentColor": "#5f249f", - "titleFontColor": "#000000", + "titleFontColor": "#333333", + "subLabelFontColor": "#666666", "assistiveTextFontColor": "#666666" }, "button": { diff --git a/apps/website/screens/components/accordion/AccordionPageLayout.tsx b/apps/website/screens/components/accordion/AccordionPageLayout.tsx index 1bfd1ce897..33e4d86743 100644 --- a/apps/website/screens/components/accordion/AccordionPageLayout.tsx +++ b/apps/website/screens/components/accordion/AccordionPageLayout.tsx @@ -17,9 +17,10 @@ const AccordionPageHeading = ({ children }: { children: ReactNode }) => { - Accordions are used to group similar content and hide or show it depending on user needs or preferences. - Accordions give users more granular control over the interface and help digest content in stages, rather - than all at once. + The accordion component is a vertical stack of interactive headers used to group related content into + collapsible sections, allowing users to expand or hide content based on their needs or preferences. It + enhances the user experience by organizing information into smaller, digestible chunks, helping reduce + cognitive load and save screen space. diff --git a/apps/website/screens/components/accordion/code/AccordionCodePage.tsx b/apps/website/screens/components/accordion/code/AccordionCodePage.tsx index 38a210a296..c70438f61b 100644 --- a/apps/website/screens/components/accordion/code/AccordionCodePage.tsx +++ b/apps/website/screens/components/accordion/code/AccordionCodePage.tsx @@ -3,11 +3,12 @@ import QuickNavContainer from "@/common/QuickNavContainer"; import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; import DocFooter from "@/common/DocFooter"; import Example from "@/common/example/Example"; -import controlledAccordion from "./examples/controlledAccordion"; -import uncontrolledAccordion from "./examples/uncontrolledAccordion"; +import basicUsage from "./examples/basicUsage"; +import badgeStatusLight from "./examples/badgeStatusLight"; +import controlled from "./examples/controlled"; +import uncontrolled from "./examples/uncontrolled"; import icons from "./examples/icons"; -import controlledAccordionGroup from "./examples/controlledAccordionGroup"; -import uncontrolledAccordionGroup from "./examples/uncontrolledAccordionGroup"; +import accordions from "./examples/accordions"; import TableCode from "@/common/TableCode"; import StatusBadge from "@/common/StatusBadge"; import Code from "@/common/Code"; @@ -27,96 +28,52 @@ const sections = [ + independent - - - label - - - - string + boolean - The panel label. - - - - - defaultIsExpanded - boolean + When true, limits the user to single-open section at a time. When false, multiple sections can be opened + simultaneously. - Initial state of the panel, only when it is uncontrolled. false - isExpanded + defaultIndexActive - boolean + number | number[] - Represents the state of the panel. When true, the component will be expanded. If undefined, the component - will be uncontrolled and its value will be managed internally by the component. + Initially active accordion, only when it is uncontrolled. If the accordion is not independent, several + accordions can be activated by default. - - icon - - string | {"(React.ReactNode & React.SVGProps )"} - + indexActive - - Material Symbol - {" "} - name or SVG element as the icon that will be placed next to the panel label. When using Material Symbols, - replace spaces with underscores. By default they are outlined if you want it to be filled prefix the - symbol name with "filled_". + number | number[] - - - - - assistiveText - string + The index of the active accordion. If undefined, the component will be uncontrolled and the active + accordion will be managed internally by the component. If null, the component will be controlled and all + accordions will be closed. If the accordion is not independent, several accordions can be activated. - Assistive text to be placed on the right side of the panel. - - disabled - - boolean - - If true, the component will be disabled. - - false - - - - onChange + onActiveChange - {"(isExpanded: boolean) => void"} + {`(index: number | number[]) => void`} - This function will be called when the user clicks the accordion to expand or collapse the panel. The new - state of the panel will be passed as a parameter. + This function will be called when the user clicks on an accordion. The index of the clicked accordion will + be passed as a parameter. - - - - - - children - - - - React.ReactNode - - The expanded panel of the accordion. This area can be used to render custom content. - - - margin @@ -129,24 +86,25 @@ const sections = [ - - tabIndex - number - - - Value of the tabindex attribute. + + + children + - 0 + {`ReactElement[] | ReactElement`} + Contains one or more accordion items. + - ), }, { - title: "Accordion Group", - content: Groups two or more accordions to distribute large volumes of information., + title: "DxcAccordion.AccordionItem", + content: Accordion item, that composes the accordion component., subSections: [ { title: "Props", @@ -162,46 +120,90 @@ const sections = [ - defaultIndexActive - number + + + label + + + + string - Initially active accordion, only when it is uncontrolled. + The panel label. - - indexActive - number + + + subLabel + - The index of the active accordion. If undefined, the component will be uncontrolled and the active - accordion will be managed internally by the component. If null, the component will be controlled and - all accordions will be closed. + string + Additional info label positioned under the label. - - disabled - boolean + + + badge + - If true, the component will be disabled. - false + {"{ position: 'before' | 'after'; element: ReactNode }"} + + Badge component to add extra value to the accordion. + - + + + + + + statusLight + + + + React.ReactNode + Status light component to add extra value to the accordion. + - - onActiveChange + icon - {"(indexActive: number) => void"} + string | {"(React.ReactNode & React.SVGProps )"} - This function will be called when the user clicks on an accordion. The index of the clicked accordion - will be passed as a parameter. + + Material Symbol + {" "} + name or SVG element as the icon that will be placed next to the panel label. When using Material + Symbols, replace spaces with underscores. By default they are outlined if you want it to be filled + prefix the symbol name with "filled_". - + + assistiveText + + string + + Assistive text to be placed on the right side of the panel. + - + + + disabled + + boolean + + If true, the component will be disabled. + + false + + @@ -210,137 +212,56 @@ const sections = [ - - {"React.ReactElement [] | React.ReactElement "} - + React.ReactNode - Contains one or more accordions. + The expanded panel of the accordion. This area can be used to render custom content. - - margin + tabIndex - - 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge' | Margin - + number - Size of the margin to be applied to the component. You can pass an object with 'top', 'bottom', 'left' - and 'right' properties in order to specify different margin sizes. + Value of the tabindex attribute. + + + 0 - - ), }, - { - title: "DxcAccordionGroup.Accordion", - content: Single accordion, part of an accordion group., - subSections: [ - { - title: "Props", - content: ( - - - - Name - Type - Description - Default - - - - - - - - label - - - - string - - The panel label. - - - - - icon - - string | {"React.ReactNode & React.SVGProps "} - - - - Material Symbol - {" "} - name or SVG element as the icon that will be placed next to the panel label. When using Material - Symbols, replace spaces with underscores. By default they are outlined if you want it to be filled - prefix the symbol name with "filled_". - - - - - - assistiveText - - string - - Assistive text to be placed on the right side of the panel. - - - - - disabled - - boolean - - If true, the component will be disabled. - - false - - - - - - - children - - - - React.ReactNode - - The expanded panel of the accordion. This area can be used to render custom content. - - - - - - ), - }, - ], - }, ], }, { title: "Examples", subSections: [ { - title: "Controlled Accordion", - content: , + title: "Basic usage", + content: , }, { - title: "Uncontrolled Accordion", - content: , + title: "Controlled Accordion", + content: , }, { - title: "Controlled Accordion Group", - content: , + title: "Uncontrolled Accordion", + content: , }, { - title: "Uncontrolled Accordion Group", - content: , + title: "Badge and status light", + content: , }, { title: "Icons", content: , }, + { + title: "Group of accordions", + content: , + }, ], }, ]; diff --git a/apps/website/screens/components/accordion/code/examples/accordions.ts b/apps/website/screens/components/accordion/code/examples/accordions.ts new file mode 100644 index 0000000000..1e63a60206 --- /dev/null +++ b/apps/website/screens/components/accordion/code/examples/accordions.ts @@ -0,0 +1,60 @@ +import { DxcAccordion, DxcInset, DxcBadge, DxcStatusLight } from "@dxc-technology/halstack-react"; +import { useState } from "react"; + +const code = `() => { + return ( + + + }} + statusLight={} + > + + Person information + + + }} + defaultIsExpanded + > + + Person creation information + + + }} + statusLight={} + defaultIsExpanded + > + + Interactions information + + + }} + > + + Deletion information + + + + + ); +}`; + +const scope = { + DxcAccordion, + DxcInset, + DxcBadge, + DxcStatusLight, + useState, +}; + +export default { code, scope }; diff --git a/apps/website/screens/components/accordion/code/examples/badgeStatusLight.ts b/apps/website/screens/components/accordion/code/examples/badgeStatusLight.ts new file mode 100644 index 0000000000..7e0668bd36 --- /dev/null +++ b/apps/website/screens/components/accordion/code/examples/badgeStatusLight.ts @@ -0,0 +1,29 @@ +import { DxcInset, DxcAccordion, DxcBadge, DxcStatusLight } from "@dxc-technology/halstack-react"; + +const code = `() => { + return ( + + + }} + statusLight={} + > + + To edit your profile you need to go to Settings and click on Profile. + + + + + ); +}`; + +const scope = { + DxcInset, + DxcAccordion, + DxcBadge, + DxcStatusLight, +}; + +export default { code, scope }; diff --git a/apps/website/screens/components/accordion/code/examples/basicUsage.ts b/apps/website/screens/components/accordion/code/examples/basicUsage.ts new file mode 100644 index 0000000000..d0d34468aa --- /dev/null +++ b/apps/website/screens/components/accordion/code/examples/basicUsage.ts @@ -0,0 +1,23 @@ +import { DxcAccordion, DxcInset } from "@dxc-technology/halstack-react"; + +const code = `() => { + return ( + + + + + To edit your profile you need to go to Settings and click on Profile. + + + + + ); +}`; + +const scope = { + DxcAccordion, + DxcInset, +}; + +export default { code, scope }; diff --git a/apps/website/screens/components/accordion/code/examples/controlled.ts b/apps/website/screens/components/accordion/code/examples/controlled.ts new file mode 100644 index 0000000000..67fffb686b --- /dev/null +++ b/apps/website/screens/components/accordion/code/examples/controlled.ts @@ -0,0 +1,32 @@ +import { DxcAccordion, DxcInset } from "@dxc-technology/halstack-react"; +import { useState } from "react"; + +const code = `() => { + const [indexAccordion, setIndexAccordion] = useState(0); + const onActiveChange = (index) => { + setIndexAccordion((currentIndex) => (currentIndex === index ? -1 : index)); + }; + + return ( + + + + + To edit your profile you need to go to Settings and click on Profile. + + + + + ); +}`; + +const scope = { + DxcAccordion, + DxcInset, + useState, +}; + +export default { code, scope }; diff --git a/apps/website/screens/components/accordion/code/examples/controlledAccordion.ts b/apps/website/screens/components/accordion/code/examples/controlledAccordion.ts deleted file mode 100644 index e1db10e061..0000000000 --- a/apps/website/screens/components/accordion/code/examples/controlledAccordion.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { DxcAccordion, DxcInset } from "@dxc-technology/halstack-react"; -import { useState } from "react"; - -const code = `() => { - const [isExpanded, changeIsExpanded] = useState(true); - const onChange = (newValue) => { - changeIsExpanded(newValue); - }; - - return ( - - - - To edit your profile you need to go to Settings and click on Profile. - - - - ); -}`; - -const scope = { - DxcAccordion, - DxcInset, - useState, -}; - -export default { code, scope }; diff --git a/apps/website/screens/components/accordion/code/examples/controlledAccordionGroup.ts b/apps/website/screens/components/accordion/code/examples/controlledAccordionGroup.ts deleted file mode 100644 index e90adc2482..0000000000 --- a/apps/website/screens/components/accordion/code/examples/controlledAccordionGroup.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { DxcAccordionGroup, DxcInset } from "@dxc-technology/halstack-react"; -import { useState } from "react"; - -const code = `() => { - const [indexAccordion, setIndexAccordion] = useState(0); - const onActiveChange = (index) => { - setIndexAccordion((currentIndex) => (currentIndex === index ? -1 : index)); - }; - - return ( - - - - - To edit your profile you need to go to Settings and click on - Profile. - - - - - To edit your profile you need to go to Settings and click on Log - out. - - - - - ); -}`; - -const scope = { - DxcAccordionGroup, - DxcInset, - useState, -}; - -export default { code, scope }; diff --git a/apps/website/screens/components/accordion/code/examples/icons.ts b/apps/website/screens/components/accordion/code/examples/icons.ts index f32135fb97..75a85109df 100644 --- a/apps/website/screens/components/accordion/code/examples/icons.ts +++ b/apps/website/screens/components/accordion/code/examples/icons.ts @@ -1,38 +1,18 @@ -import { DxcAccordion, DxcAccordionGroup, DxcFlex, DxcInset } from "@dxc-technology/halstack-react"; +import { DxcAccordion, DxcFlex, DxcInset } from "@dxc-technology/halstack-react"; import { useState } from "react"; const code = `() => { - return ( - - - - To edit your profile you need to go to Settings and click on - Profile. - - - - + + To edit your profile you need to go to Settings and click on Profile. - - - - To edit your profile you need to go to Settings and click on Log - out. - - - - + + ); }`; @@ -40,7 +20,6 @@ const code = `() => { const scope = { DxcAccordion, DxcInset, - DxcAccordionGroup, DxcFlex, useState, }; diff --git a/apps/website/screens/components/accordion/code/examples/uncontrolled.ts b/apps/website/screens/components/accordion/code/examples/uncontrolled.ts new file mode 100644 index 0000000000..c9b9fc4928 --- /dev/null +++ b/apps/website/screens/components/accordion/code/examples/uncontrolled.ts @@ -0,0 +1,24 @@ +import { DxcAccordion, DxcInset } from "@dxc-technology/halstack-react"; +import { useState } from "react"; + +const code = `() => { + return ( + + + + + To edit your profile you need to go to Settings and click on Profile. + + + + + ); +}`; + +const scope = { + DxcAccordion, + DxcInset, + useState, +}; + +export default { code, scope }; diff --git a/apps/website/screens/components/accordion/code/examples/uncontrolledAccordion.ts b/apps/website/screens/components/accordion/code/examples/uncontrolledAccordion.ts deleted file mode 100644 index 77ccd09269..0000000000 --- a/apps/website/screens/components/accordion/code/examples/uncontrolledAccordion.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { DxcAccordion, DxcInset } from "@dxc-technology/halstack-react"; -import { useState } from "react"; - -const code = `() => { - return ( - - - - To edit your profile you need to go to Settings and click on Profile. - - - - ); -}`; - -const scope = { - DxcAccordion, - DxcInset, - useState, -}; - -export default { code, scope }; diff --git a/apps/website/screens/components/accordion/code/examples/uncontrolledAccordionGroup.ts b/apps/website/screens/components/accordion/code/examples/uncontrolledAccordionGroup.ts deleted file mode 100644 index 9381beca8b..0000000000 --- a/apps/website/screens/components/accordion/code/examples/uncontrolledAccordionGroup.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { DxcAccordionGroup, DxcInset } from "@dxc-technology/halstack-react"; -import { useState } from "react"; - -const code = `() => { - return ( - - - - - To edit your profile you need to go to Settings and click on - Profile. - - - - - To edit your profile you need to go to Settings and click on Log - out. - - - - - ); -}`; - -const scope = { - DxcAccordionGroup, - DxcInset, - useState, -}; - -export default { code, scope }; diff --git a/apps/website/screens/components/accordion/specs/AccordionSpecsPage.tsx b/apps/website/screens/components/accordion/specs/AccordionSpecsPage.tsx index f22d589a28..3430294597 100644 --- a/apps/website/screens/components/accordion/specs/AccordionSpecsPage.tsx +++ b/apps/website/screens/components/accordion/specs/AccordionSpecsPage.tsx @@ -40,16 +40,17 @@ const sections = [ Header - Custom icon (Optional) + Left secondary element (Optional) Title + Sublabel - Helper text (Optional) + Right secondary element (Optional) Caret icon (Expand/collapse) - Expanded panel + Content area ), @@ -72,23 +73,41 @@ const sections = [ - titleLabelFontColor + accordionSeparatorBorderColor - Title + Separator - color-black + color-grey-200 - #000000 + #e6e6e6 - disabledTitleLabelFontColor + accordionSeparatorBorderThickness - Title:disabled + Separator + - + 1px + + - color-grey-500 + accordionSeparatorBorderStyle - #999999 + Separator + + border-style-solid + + solid + + + + activeBackgroundColor + + Header background:active + + color-purple-100 + + #f2eafa @@ -100,11 +119,31 @@ const sections = [ #5f249f + + + assistiveTextFontColor + + Assistive text + + color-grey-700 + + #666666 + + + + backgroundColor + + Container background + + color-white + + #ffffff + disabledArrowColor - Title:disabled + Caret icon:disabled color-grey-500 @@ -112,13 +151,13 @@ const sections = [ - iconColor + disabledAssistiveTextFontColor - Custom icon + Assistive text:disabled - color-purple-700 + color-grey-500 - #5f249f + #999999 @@ -132,19 +171,19 @@ const sections = [ - assistiveTextFontColor + disabledSubLabelFontColor - Helper text + Sublabel:disabled color-grey-700 - #666666 + #999999 - disabledAssistiveTextFontColor + disabledTitleLabelFontColor - Helper text:disabled + Title:disabled color-grey-500 @@ -152,13 +191,13 @@ const sections = [ - hoverBackgroundColor + focusBackgroundColor - Header background:hover + Header background:focus - color-purple-100 + color-transparent - #f2eafa + transparent @@ -172,28 +211,53 @@ const sections = [ - backgroundColor + hoverBackgroundColor - Container background + Header background:hover - color-white + color-purple-100 - #ffffff + #f2eafa - boxShadowColor + iconColor - Container shadow - - - #0000001a + Custom icon + + color-purple-700 + + #5f249f - accordionGroupSeparatorBorderColor + subLabelFontColor + + Sublabel + + color-grey-500 + + #999999 + + + + titleLabelFontColor + + Title + + color-grey-900 + + #333333 + + + + + boxShadowColor + + Container shadow + + color-grey-200-a - Separator - - #0000001a @@ -215,93 +279,123 @@ const sections = [ - titleLabelFontFamily + assistiveTextFontFamily - Title + Assistive text font-family-sans - 'Open Sans', sans-serif + 'Open Sans', sans-serif; - titleLabelFontSize + assistiveTextFontSize - Title + Assistive text - font-scale-03 + font-scale-01 - 16px + 0.75rem / 12px - titleLabelFontWeight + assistiveTextFontStyle - Title + Assistive text + + font-style-normal + + normal + + + + assistiveTextFontWeight + + Assistive text - font-regular + font-weight-light 400 - titleLabelFontStyle + subLabelFontFamily - Title + Sublabel + + font-family-sans + + 'Open Sans', sans-serif + + + + subLabelFontSize + + Sublabel + + font-scale-01 + + 0.75rem / 12px + + + + subLabelFontStyle + + Sublabel - font-normal + font-style-normal normal - assistiveTextFontFamily + subLabelFontWeight - Helper text + Sublabel - font-sans + font-weight-normal - 'Open Sans', sans-serif; + 400 - assistiveTextFontSize + titleLabelFontFamily - Helper text + Title - font-scale-03 + font-family-sans - 16px + 'Open Sans', sans-serif - assistiveTextFontWeight + titleLabelFontSize - Helper text + Title - font-light + font-scale-03 - 300 + 1rem / 16px - assistiveTextFontStyle + titleLabelFontStyle - Helper text + Title - font-regular + font-style-normal - italic + normal - assistiveTextLetterSpacing + titleLabelFontWeight - Helper text + Title - font-tracking-wide-01 + font-weight-regular - 0.025em + 400 @@ -345,6 +439,16 @@ const sections = [ + + + borderRadius + + Accordion container + + border-radius-medium + + 0.25rem / 4px + focusBorderStyle @@ -365,174 +469,10 @@ const sections = [ 2px - - - borderRadius - - Accordion container - - border-radius-medium - - 0.25rem / 4px - - - - ), - }, - { - title: "Size", - content: ( - - - - Property - Element - Core token - Value - - - - - - height - - Header - - - 48px - - - - min-width - - Accordion container - - - 280px - ), }, - { - title: "Spacing", - content: ( - <> - - - - Component token - Element - Core token - Value - - - - - - titleLabelPaddingRight - - Title - - spacing-16 - - 1rem / 16px - - - - titleLabelPaddingLeft - - Title - - spacing-0 - - 0rem / 0px - - - - titleLabelPaddingTop - - Title - - spacing-0 - - 0rem / 0px - - - - titleLabelPaddingBottom - - Title - - spacing-0 - - 0rem / 0px - - - - - - - Property - Element - Core token - Value - - - - - - padding-left - - Header - - spacing-16 - - 16px - - - - padding-right - - Header - - spacing-16 - - 16px - - - - padding-right - - Helper text - - spacing-24 - - 24px - - - - padding - - Caret icon - - spacing-12 - - 12px - - - - margin-right - - Custom icon - - spacing-12 - - 12px - - - - - ), - }, { title: "Margin", content: ( @@ -553,43 +493,43 @@ const sections = [ xxsmall - 6px + 4px xsmall - 16px + 8px small - 24px + 12px medium - 36px + 16px large - 48px + 24px xlarge - 64px + 32px xxlarge - 100px + 48px diff --git a/apps/website/screens/components/accordion/specs/images/accordion_anatomy.png b/apps/website/screens/components/accordion/specs/images/accordion_anatomy.png index a5ad739b03..62369e4b95 100644 Binary files a/apps/website/screens/components/accordion/specs/images/accordion_anatomy.png and b/apps/website/screens/components/accordion/specs/images/accordion_anatomy.png differ diff --git a/apps/website/screens/components/accordion/specs/images/accordion_specs.png b/apps/website/screens/components/accordion/specs/images/accordion_specs.png index 3ae74d805e..ca2d0df214 100644 Binary files a/apps/website/screens/components/accordion/specs/images/accordion_specs.png and b/apps/website/screens/components/accordion/specs/images/accordion_specs.png differ diff --git a/apps/website/screens/components/accordion/specs/images/accordion_states.png b/apps/website/screens/components/accordion/specs/images/accordion_states.png index 94e24364e0..6f503ddb48 100644 Binary files a/apps/website/screens/components/accordion/specs/images/accordion_states.png and b/apps/website/screens/components/accordion/specs/images/accordion_states.png differ diff --git a/apps/website/screens/components/accordion/usage/AccordionUsagePage.tsx b/apps/website/screens/components/accordion/usage/AccordionUsagePage.tsx index 8b656ddb81..79b4410f13 100644 --- a/apps/website/screens/components/accordion/usage/AccordionUsagePage.tsx +++ b/apps/website/screens/components/accordion/usage/AccordionUsagePage.tsx @@ -4,237 +4,378 @@ import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; import DocFooter from "@/common/DocFooter"; import Figure from "@/common/Figure"; import Image from "@/common/Image"; -import Example from "@/common/example/Example"; -import accordionPlacement from "./images/accordion_placement.png"; -import accordionAlignment from "./images/accordion_alignment.png"; -import accordionTriggers from "./images/accordion_triggers.png"; +import accordionMainParts from "./images/accordion_main_parts.png"; +import accordionElements from "./images/accordion_elements.png"; +import accordionExamples from "./images/accordion_examples.png"; import accordionContent from "./images/accordion_content.png"; -import accordionMultiExpand from "./images/accordion_multi_expand.png"; -import accordionGroup from "./examples/accordionGroup"; -import assistiveText from "./examples/assistiveText"; -import behaviorAndInteraction from "./examples/behaviorAndInteraction"; +import accordionPlacement from "./images/accordion_placement.png"; const sections = [ { title: "Usage", content: ( - The accordion component delivers large amounts of content in a small space through progressive disclosure. + The accordion component is designed to present large amounts of content in a small space by leveraging + progressive disclosure. It helps improve the user experience by breaking down information into manageable + sections, allowing users to focus only on what they need. ), subSections: [ { - title: "Do's", - content: ( - - Displaying and grouping additional information. - - To shorten pages and reduce scrolling when content is not crucial to read in full. - - Adding granular control over the information on a given page. - - ), - }, - { - title: "Don'ts", - content: ( - - - When most of the content on the page is needed to answer user questions. - - - To display a list of clickable options, dropdown should be used instead. - - - Displaying critical system information or a primary action to be taken on the page. (for example: alerts, - confirmation or cancellation buttons). - - - ), - }, - ], - }, - { - title: "Placement and alignment", - subSections: [ - { - title: "Placement", + title: "Main parts", content: ( <> - - Accordions can be placed with main page content or placed inside of a container such as a side panel or - tile. - -
- Accordion placement + Each accordion section consists of two main parts: +
+ Main parts
+ + + Header: Serves as the trigger for expanding or collapsing the section. It acts as a + summary of the content, allowing users to decide if they want to interact with it.{" "} + + + Content area: The expanded section where detailed information or functionality resides.{" "} + + ), + subSections: [ + { + title: "Header layout, primary and secondary elements", + content: ( + <> + + The accordion header is divided into two sections: left and right, each of which can contain different + UI elements. These elements are categorised as primary or secondary, based on their importance and + role within the accordion's functionality. + + Accordion elements + + ), + subSections: [ + { + title: "Primary Elements", + content: ( + <> + + The primary elements are mandatory and provide the basic functionality of the + accordion. These ensure the component is functional and intuitive for users: + + + + Title: A concise and descriptive label that summarises the content of the + accordion section. It helps users understand what type of information they can expect to find + inside. + + + Chevron Icon: A visual indicator of the accordion’s current state (expanded or + collapsed). It provides an affordance for interaction and ensures users can toggle the accordion + intuitively. + + + + Without these elements, the accordion cannot effectively communicate its purpose or provide a + clear interaction model. + + + ), + }, + { + title: "Secondary Elements", + content: ( + <> + + The secondary elements are optional and provide additional context or enhance the + user experience. While not essential for the accordion's functionality, they add useful details or + visual hierarchy. + + + + Left secondary elements: + + + Icon: Adds visual context or aids recognition by representing the content + type or purpose of the section. + + + Badge: Displays supplemental information, such as a notification count or + status, to give users a quick overview of the section. + + + + + Right secondary elements: + + + Helper text: Provides additional context, such as brief instructions or a + summary of the content within the section. + + + Status light: Displays a visual indicator of the section’s status (e.g., + completed, in progress, or error). + + + Badge: Similar to the left-side badge, but placed on the right for better + alignment in specific layouts. + + + + +
+ Accordion examples +
+ + ), + }, + ], + }, + { + title: "Content area", + content: ( + <> + + The content area can contain other components, images, tables, and every custom feature that can be + supported inside the element container. + + Accordion content + + Nesting is allowed and icons can be used as a complement to the header label. + + + ), + }, + ], }, { - title: "Alignment", + title: "Placement and alignment", + subSections: [ + { + title: "Placement", + content: ( + <> + + Accordions can be placed with main page content or placed inside of a container such as a side panel + or tile. + +
+ Accordion placement +
+ + ), + }, + { + title: "Alignment", + content: ( + + By default the chevron icon is placed on the end side of the header. This allows for the title on the + start side to align with other type elements in the layout. + + ), + }, + ], + }, + { + title: "Behavior and interaction", content: ( <> - By default the chevron icon is placed on the end side of the header. This allows for the title on the - start side to align with other type elements in the layout. + The accordion component has two main states: collapsed and expanded. The chevron icon at the end of the + accordion indicates which state the accordion is in. Accordions begin by default in the collapsed state + with all content panels closed. Starting in a collapsed state gives the user a high level overview of the + available information. -
- - Left. Place chevron icon at the end of the accordion header. - - - Right. Don’t place caret icon on the left. - - - } - > - Accordion alignment -
- - ), - }, - ], - }, - { - title: "Behavior and interaction", - content: ( - <> - - - The accordion component has two main states: collapsed and expanded. The chevron icon at the end of the - accordion indicates which state the accordion is in. Accordions begin by default in the collapsed state with - all content panels closed. Starting in a collapsed state gives the user a high level overview of the available - information. - - - - Trigger collapsed and expanded states when clicking on either the header or icon. - - - Use icons and animation to easily reflect collapsed and expanded states. - - - Use a chevron icon to indicate the expand/collapse behavior. - + - When the panel expands, the chevron icon rotates 180 degrees counterclockwise. + Trigger collapsed and expanded states when clicking on either the header or icon. - When the panel collapses, the chevron icon rotates 180 degrees clockwise. + Use icons and animation to easily reflect collapsed and expanded states. + + + Use a chevron icon to indicate the expand/collapse behavior. + + + + When the panel expands, the chevron icon rotates 180 degrees counterclockwise. + + + When the panel collapses, the chevron icon rotates 180 degrees clockwise. + + + - - -
- - Left. Trigger collapsed and expanded states when clicking on either the header or icon. - + + ), + subSections: [ + { + title: "Allowing multiple sections open vs single-open behavior", + content: ( - Right. Leave the header without caret or use a button to trigger the expand/collapse - action. + The accordion component can be configured to allow either multiple sections to be open + simultaneously or limit the user to a single-open section at a time. Each approach has + specific use cases, but it’s important to prioritise user needs and content hierarchy when deciding + which behavior to implement. - - } - > - Accordion trigger usage -
- - ), - subSections: [ + ), + subSections: [ + { + title: "Allow multiple sections open", + content: ( + <> + + This approach gives users full control over the visibility of content, allowing them to open or + collapse multiple sections at the same time. It’s particularly useful when: + + + + Users need to compare or reference information across different sections simultaneously. + + + The content in each section is independent and doesn’t require strict linear navigation. + + + There is enough vertical space to accommodate multiple expanded sections without overwhelming + the layout. + + + + ), + }, + { + title: "Single-open behavior", + content: ( + <> + + Some implementations restrict the accordion to allow only one section to be open + at a time. When a user expands a new section, the previously expanded section collapses + automatically. This pattern is suitable when: + + + + The content is closely related or mutually exclusive, making it logical to view only one section + at a time. + + + Vertical space is limited, and having multiple sections open could cause usability or layout + issues. + + + A simplified and more guided user experience is desired, such as in wizards or step-by-step + processes. + + + + ), + }, + ], + }, + ], + }, { - title: "Mobile", + title: "Best practices", content: ( <> - - In small devices, extremely long pages are detrimental to the user experience. Collapsing information - minimizes excessive scrolling and gives an overview of the structure and content available on the page. - - In mobile use 100% of the available screen width. + + One element per side: Each side of the header (left and{" "} + right) should only include one secondary element to maintain a clean + and organised visual hierarchy. + + + No duplicates: Avoid including multiple instances of the same type of element in the + header (e.g., two badges or two status lights), as this can create visual clutter and confuse users. + + + Semantic colors: If both a badge and a status light{" "} + are included, avoid using semantic colors (e.g., red, green) for the badge to prevent it from competing + visually with the status light. + + + Growth priority: Mandatory and descriptive elements, such as the title, are prioritised + over optional elements to ensure that essential information is always visible and accessible. + ), + subSections: [ + { + title: "Mobile", + content: ( + <> + + In small devices, extremely long pages are detrimental to the user experience. Collapsing information + minimises excessive scrolling and gives an overview of the structure and content available on the + page. + + + In mobile use 100% of the available screen width. + + + ), + }, + { + title: "Do's", + content: ( + <> + Use an accordion when: + + + Displaying and grouping additional information that is related or supplemental to + the primary content. + + + Shortening pages and reducing scrolling, especially for optional or non-critical + content. + + + Providing users with granular control over the visibility of information, helping + them interact with the page in a way that meets their specific needs. + + + Organising FAQs or similar repeated structures, where content can be logically + divided into expandable sections. + + + Enhancing content hierarchy by nesting detailed or secondary content under a more + general overview. + + + + ), + }, + { + title: "Don'ts", + content: ( + <> + Don’t use an accordion if: + + + The majority of the content on the page is crucial for the user to see upfront, as + hiding it may increase friction or confusion. + + + You need to display a list of selectable options (e.g., navigation menus or + filters)—a dropdown or other list pattern is more appropriate. + + + Critical system information or actions (like alerts, confirmations, or primary + buttons) need to be visible—these should remain prominent and accessible without requiring user + interaction. + + + The interaction of expanding and collapsing creates{" "} + unnecessary complexity or extra clicks for the user. + + + + ), + }, + ], }, ], }, - { - title: "Accordion group", - content: ( - <> - - Accordion headers are stacked vertically and different hierarchy levels are allowed. - - - - - The expandable section of an accordion group can contain different types of plain information or clickable - components. - - - -
- Accordion group placement -
-
- - When one accordion panel is expanded, the rest of the group should be collapsed. - - - ), - }, - { - title: "Content", - content: ( - <> - - The accordion component can contain other components, images, tables, and every custom feature that can be - supported inside the element container. - -
- - Left. Nesting is allowed. Use in parent accordion Open Sans Semibold. - - - Right. Icons can be used as a complement to the header label. - - - } - > - Nesting and icon usage examples -
- - ), - }, - { - title: "Assistive text", - content: ( - <> - Assistive text can be shown at the end of the accordion header when needed. - - Icons and images can not be used. - - Only add a assistive text when there is plenty space in the accordion header, in mobile devices is not - displayed. - - - Try always to use a descriptive header so is no necessity to add extra information. - - - Assistive text content will be truncated 48px before reaching the accordion title. Title display has - priority when space is limited. - - - - - ), - }, ]; const AccordionUsagePage = () => { diff --git a/apps/website/screens/components/accordion/usage/examples/accordionGroup.ts b/apps/website/screens/components/accordion/usage/examples/accordionGroup.ts deleted file mode 100644 index c30dfb35b5..0000000000 --- a/apps/website/screens/components/accordion/usage/examples/accordionGroup.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { DxcAccordionGroup, DxcInset, DxcFlex, DxcHeading } from "@dxc-technology/halstack-react"; -import { useState } from "react"; - -const code = `() => { - const [indexAccordion, setIndexAccordion] = useState(0); - const onActiveChange = (index) => { - setIndexAccordion((currentIndex) => (currentIndex === index ? -1 : index)); - }; - - return ( - - - - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. - Suspendisse malesuada lacus ex, sit amet blandit leo lobortis - eget. - - - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse - malesuada lacus ex, sit amet blandit leo lobortis eget. - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse - malesuada lacus ex, sit amet blandit leo lobortis eget. - - - - - ); -}`; - -const scope = { - DxcAccordionGroup, - DxcInset, - DxcFlex, - DxcHeading, - useState, -}; - -export default { code, scope }; diff --git a/apps/website/screens/components/accordion/usage/examples/assistiveText.ts b/apps/website/screens/components/accordion/usage/examples/assistiveText.ts deleted file mode 100644 index 66a2506c1b..0000000000 --- a/apps/website/screens/components/accordion/usage/examples/assistiveText.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { DxcAccordion, DxcInset } from "@dxc-technology/halstack-react"; -import { useState } from "react"; - -const code = `() => { - const [isExpanded, changeIsExpanded] = useState(false); - const onChange = (newValue) => { - changeIsExpanded(newValue); - }; - - return ( - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse - malesuada lacus ex, sit amet blandit leo lobortis eget. - - - - ); -}`; - -const scope = { - DxcAccordion, - DxcInset, - useState, -}; - -export default { code, scope }; diff --git a/apps/website/screens/components/accordion/usage/examples/behaviorAndInteraction.ts b/apps/website/screens/components/accordion/usage/examples/behaviorAndInteraction.ts deleted file mode 100644 index c83d703c04..0000000000 --- a/apps/website/screens/components/accordion/usage/examples/behaviorAndInteraction.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { DxcAccordion, DxcInset, DxcFlex, DxcHeading } from "@dxc-technology/halstack-react"; -import { useState } from "react"; - -const code = `() => { - const [isExpandedCollapsed, changeIsExpandedCollapsed] = useState(false); - const onChangeCollapsed = (newValue) => { - changeIsExpandedCollapsed(newValue); - }; - const [isExpanded, changeIsExpanded] = useState(true); - const onChangeExpanded = (newValue) => { - changeIsExpanded(newValue); - }; - - return ( - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse - malesuada lacus ex, sit amet blandit leo lobortis eget. - - - - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. - Suspendisse malesuada lacus ex, sit amet blandit leo lobortis - eget. - - - - - - - ); -}`; - -const scope = { - DxcAccordion, - DxcInset, - DxcFlex, - DxcHeading, - useState, -}; - -export default { code, scope }; diff --git a/apps/website/screens/components/accordion/usage/images/accordion_alignment.png b/apps/website/screens/components/accordion/usage/images/accordion_alignment.png deleted file mode 100644 index 27c0faee5b..0000000000 Binary files a/apps/website/screens/components/accordion/usage/images/accordion_alignment.png and /dev/null differ diff --git a/apps/website/screens/components/accordion/usage/images/accordion_content.png b/apps/website/screens/components/accordion/usage/images/accordion_content.png index f4c7a14619..bf02e664bd 100644 Binary files a/apps/website/screens/components/accordion/usage/images/accordion_content.png and b/apps/website/screens/components/accordion/usage/images/accordion_content.png differ diff --git a/apps/website/screens/components/accordion/usage/images/accordion_elements.png b/apps/website/screens/components/accordion/usage/images/accordion_elements.png new file mode 100644 index 0000000000..b508c9908b Binary files /dev/null and b/apps/website/screens/components/accordion/usage/images/accordion_elements.png differ diff --git a/apps/website/screens/components/accordion/usage/images/accordion_examples.png b/apps/website/screens/components/accordion/usage/images/accordion_examples.png new file mode 100644 index 0000000000..d4c3afd9e2 Binary files /dev/null and b/apps/website/screens/components/accordion/usage/images/accordion_examples.png differ diff --git a/apps/website/screens/components/accordion/usage/images/accordion_main_parts.png b/apps/website/screens/components/accordion/usage/images/accordion_main_parts.png new file mode 100644 index 0000000000..4aee73941a Binary files /dev/null and b/apps/website/screens/components/accordion/usage/images/accordion_main_parts.png differ diff --git a/apps/website/screens/components/accordion/usage/images/accordion_multi_expand.png b/apps/website/screens/components/accordion/usage/images/accordion_multi_expand.png deleted file mode 100644 index 58d3ce9468..0000000000 Binary files a/apps/website/screens/components/accordion/usage/images/accordion_multi_expand.png and /dev/null differ diff --git a/apps/website/screens/components/accordion/usage/images/accordion_placement.png b/apps/website/screens/components/accordion/usage/images/accordion_placement.png index a117e80c47..d55759d6ec 100644 Binary files a/apps/website/screens/components/accordion/usage/images/accordion_placement.png and b/apps/website/screens/components/accordion/usage/images/accordion_placement.png differ diff --git a/apps/website/screens/components/accordion/usage/images/accordion_triggers.png b/apps/website/screens/components/accordion/usage/images/accordion_triggers.png deleted file mode 100644 index c3c8636eb7..0000000000 Binary files a/apps/website/screens/components/accordion/usage/images/accordion_triggers.png and /dev/null differ diff --git a/apps/website/screens/principles/themes/ThemesPage.tsx b/apps/website/screens/principles/themes/ThemesPage.tsx index ddeba37ef5..65e69dae41 100644 --- a/apps/website/screens/principles/themes/ThemesPage.tsx +++ b/apps/website/screens/principles/themes/ThemesPage.tsx @@ -148,6 +148,13 @@ const sections = [ titleLabelFontColor + + Sublabel font color + +
+ subLabelFontColor + + Assistive text font color diff --git a/apps/website/screens/principles/themes/examples/opinionatedTheme.ts b/apps/website/screens/principles/themes/examples/opinionatedTheme.ts index 3684627ead..42b0147307 100644 --- a/apps/website/screens/principles/themes/examples/opinionatedTheme.ts +++ b/apps/website/screens/principles/themes/examples/opinionatedTheme.ts @@ -2,10 +2,12 @@ import { HalstackProvider, DxcTextInput, DxcInset, DxcAccordion } from "@dxc-tec const code = `() => { const customTheme = { - accordion: { - accentColor: "#1b75bb", - fontColor: "#666666", - }, + "accordion": { + "accentColor": "#fabada", + "titleFontColor": "#333333", + "subLabelFontColor": "#666666", + "assistiveTextFontColor": "#999999" + }, textInput: { fontColor: "#f80808", }, @@ -14,10 +16,12 @@ const code = `() => { return ( - - - - + + + + + + diff --git a/apps/website/screens/theme-generator/components/previews/Accordion.tsx b/apps/website/screens/theme-generator/components/previews/Accordion.tsx index d66d254050..77d1dc2665 100644 --- a/apps/website/screens/theme-generator/components/previews/Accordion.tsx +++ b/apps/website/screens/theme-generator/components/previews/Accordion.tsx @@ -1,7 +1,6 @@ import { useState } from "react"; -import { DxcAccordion, DxcAccordionGroup, DxcInset } from "@dxc-technology/halstack-react"; +import { DxcAccordion, DxcBadge, DxcInset, DxcStatusLight } from "@dxc-technology/halstack-react"; import Mode from "../Mode"; -import facebookIcon from "../../images/FacebookIcon"; import PreviewContainer from "./PreviewContainer"; const Accordion = () => { @@ -14,44 +13,110 @@ const Accordion = () => { return ( - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - + + + To edit your profile you need to go to Settings and click on Profile. + - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - + + + To edit your profile you need to go to Settings and click on Profile. + - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - + + + }} + statusLight={} + > + To edit your profile you need to go to Settings and click on Profile. + - - - - + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet + blandit leo lobortis eget. + + + + }} + statusLight={} + > + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortis eget. - - - + + + + + + }} + statusLight={} + > + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + + + }} + > + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + + + }} + statusLight={} + > + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + + + }} + > + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortis eget. - - + + ); diff --git a/apps/website/screens/theme-generator/themes/schemas/advanced.schema.json b/apps/website/screens/theme-generator/themes/schemas/advanced.schema.json index 58b7ee07bb..41ea7ffcae 100644 --- a/apps/website/screens/theme-generator/themes/schemas/advanced.schema.json +++ b/apps/website/screens/theme-generator/themes/schemas/advanced.schema.json @@ -1,46 +1,44 @@ { "accordion": { "backgroundColor": "color", + "hoverBackgroundColor": "color", + "focusBackgroundColor": "color", + "activeBackgroundColor": "color", "arrowColor": "color", "disabledArrowColor": "color", - "hoverBackgroundColor": "color", + "subLabelFontFamily": "fFamily", + "subLabelFontSize": "length", + "subLabelFontWeight": "fWeight", + "subLabelFontStyle": "fStyle", + "subLabelFontColor": "color", + "disabledSubLabelFontColor": "color", "assistiveTextFontFamily": "fFamily", "assistiveTextFontSize": "length", "assistiveTextFontWeight": "fWeight", "assistiveTextFontStyle": "fStyle", "assistiveTextFontColor": "color", - "assistiveTextLetterSpacing": "length", - "assistiveTextMinWidth": "length", - "assistiveTextPaddingLeft": "length", - "assistiveTextPaddingRight": "length", "disabledAssistiveTextFontColor": "color", "titleLabelFontFamily": "fFamily", "titleLabelFontSize": "length", "titleLabelFontWeight": "fWeight", "titleLabelFontStyle": "fStyle", "titleLabelFontColor": "color", - "titleLabelPaddingLeft": "length", - "titleLabelPaddingRight": "length", - "titleLabelPaddingTop": "length", - "titleLabelPaddingBottom": "length", "disabledTitleLabelFontColor": "color", + "focusBorderColor": "color", "focusBorderStyle": "bStyle", "focusBorderThickness": "bWidth", - "focusBorderColor": "color", "borderRadius": "length", "boxShadowOffsetX": "length", "boxShadowOffsetY": "length", "boxShadowBlur": "length", + "boxShadowSpread": "length", "boxShadowColor": "color", "iconColor": "color", - "iconSize": "length", - "iconMarginLeft": "length", - "iconMarginRight": "length", "disabledIconColor": "color", - "accordionGroupSeparatorBorderColor": "color", - "accordionGroupSeparatorBorderThickness": "bWidth", - "accordionGroupSeparatorBorderRadius": "length", - "accordionGroupSeparatorBorderStyle": "fStyle" + "iconSize": "length", + "accordionSeparatorBorderColor": "color", + "accordionSeparatorBorderThickness": "bWidth", + "accordionSeparatorBorderStyle": "bStyle" }, "alert": { "overlayColor": "color", @@ -74,7 +72,7 @@ "titleFontSize": "length", "modalTitleFontSize": "length", "titleFontStyle": "fStyle", - "titleFontWeight": "bold", + "titleFontWeight": "fWeight", "titlePaddingRight": "length", "messageFontFamily": "fFamily", "messageFontColor": "color", diff --git a/apps/website/screens/theme-generator/themes/schemas/opinionated.schema.json b/apps/website/screens/theme-generator/themes/schemas/opinionated.schema.json index f61d8dd52f..cb7bcd9f89 100644 --- a/apps/website/screens/theme-generator/themes/schemas/opinionated.schema.json +++ b/apps/website/screens/theme-generator/themes/schemas/opinionated.schema.json @@ -2,6 +2,7 @@ "accordion": { "accentColor": "color", "titleFontColor": "color", + "subLabelFontColor": "color", "assistiveTextFontColor": "color" }, "button": { diff --git a/apps/website/screens/utilities/halstack-provider/examples/advancedTheme.ts b/apps/website/screens/utilities/halstack-provider/examples/advancedTheme.ts index 26a92886d5..49a7702ba3 100644 --- a/apps/website/screens/utilities/halstack-provider/examples/advancedTheme.ts +++ b/apps/website/screens/utilities/halstack-provider/examples/advancedTheme.ts @@ -3,24 +3,24 @@ import { HalstackProvider, DxcTextInput, DxcInset, DxcAccordion } from "@dxc-tec const code = `() => { const advancedTheme = { accordion: { - backgroundColor: "#4a90e2", - hoverBackgroundColor: "#b8e986", + backgroundColor: "#e8dee3", + hoverBackgroundColor: "#e4c7d6", arrowColor: "#ffffff", disabledArrowColor: "#999999", - assistiveTextFontSize: "1.5rem", - assistiveTextFontWeight: "400", - assistiveTextLetterSpacing: "-0.0125em", - assistiveTextFontColor: "#f6fa06", + assistiveTextFontSize: "1rem", + assistiveTextFontColor: "#40182d", }, }; return ( - - - - + + + + + + diff --git a/apps/website/screens/utilities/halstack-provider/examples/opinionatedTheme.ts b/apps/website/screens/utilities/halstack-provider/examples/opinionatedTheme.ts index ada18e1fb4..b7ecfba184 100644 --- a/apps/website/screens/utilities/halstack-provider/examples/opinionatedTheme.ts +++ b/apps/website/screens/utilities/halstack-provider/examples/opinionatedTheme.ts @@ -2,22 +2,26 @@ import { HalstackProvider, DxcTextInput, DxcInset, DxcAccordion } from "@dxc-tec const code = `() => { const customTheme = { - accordion: { - accentColor: "#1b75bb", - fontColor: "#666666", + "accordion": { + "accentColor": "#8e1e1e", + "titleFontColor": "#8e1e1e", + "subLabelFontColor": "#cd848d", + "assistiveTextFontColor": "#cd848d" }, textInput: { - fontColor: "#f80808", + fontColor: "#8e1e1e", }, }; return ( - - - - + + + + + + diff --git a/packages/lib/src/HalstackContext.tsx b/packages/lib/src/HalstackContext.tsx index 500f2e7a86..a0995fc35f 100644 --- a/packages/lib/src/HalstackContext.tsx +++ b/packages/lib/src/HalstackContext.tsx @@ -53,6 +53,7 @@ const parseTheme = (theme: DeepPartial): AdvancedTheme => { const accordionTokens = componentTokensCopy.accordion; accordionTokens.assistiveTextFontColor = theme.accordion?.assistiveTextFontColor ?? accordionTokens.assistiveTextFontColor; + accordionTokens.subLabelFontColor = theme.accordion?.subLabelFontColor ?? accordionTokens.subLabelFontColor; accordionTokens.titleLabelFontColor = theme.accordion?.titleFontColor ?? accordionTokens.titleLabelFontColor; accordionTokens.arrowColor = theme.accordion?.accentColor ?? accordionTokens.arrowColor; accordionTokens.iconColor = theme.accordion?.accentColor ?? accordionTokens.iconColor; @@ -142,8 +143,7 @@ const parseTheme = (theme: DeepPartial): AdvancedTheme => { dateTokens.pickerCurrentYearFontColor = theme.dateInput?.baseColor ?? dateTokens.pickerCurrentYearFontColor; dateTokens.pickerHeaderActiveBackgroundColor = subLightness(8, theme.dateInput?.baseColor) ?? dateTokens.pickerHeaderActiveBackgroundColor; - dateTokens.pickerHeaderActiveFontColor = - theme.dateInput?.selectedFontColor ?? dateTokens.pickerHeaderActiveFontColor; + dateTokens.pickerHeaderActiveFontColor = theme.dateInput?.selectedFontColor ?? dateTokens.pickerHeaderActiveFontColor; dateTokens.pickerHoverBackgroundColor = addLightness(52, theme.dateInput?.baseColor) ?? dateTokens.pickerHoverBackgroundColor; dateTokens.pickerCurrentDateBorderColor = @@ -192,8 +192,7 @@ const parseTheme = (theme: DeepPartial): AdvancedTheme => { headerTokens.menuBackgroundColor = theme.header?.menuBaseColor ?? headerTokens.menuBackgroundColor; headerTokens.hamburgerFontColor = theme.header?.fontColor ?? headerTokens.hamburgerFontColor; headerTokens.hamburgerIconColor = theme.header?.hamburgerColor ?? headerTokens.hamburgerIconColor; - headerTokens.hamburgerHoverColor = - addLightness(90, theme.header?.hamburgerColor) ?? headerTokens.hamburgerHoverColor; + headerTokens.hamburgerHoverColor = addLightness(90, theme.header?.hamburgerColor) ?? headerTokens.hamburgerHoverColor; headerTokens.logo = theme.header?.logo ?? headerTokens.logo; headerTokens.logoResponsive = theme.header?.logoResponsive ?? headerTokens.logoResponsive; headerTokens.contentColor = theme.header?.contentColor ?? headerTokens.contentColor; @@ -209,8 +208,7 @@ const parseTheme = (theme: DeepPartial): AdvancedTheme => { navTabsTokens.selectedIconColor = theme.navTabs?.baseColor ?? navTabsTokens.selectedIconColor; navTabsTokens.unselectedIconColor = theme.navTabs?.baseColor ?? navTabsTokens.selectedIconColor; navTabsTokens.selectedUnderlineColor = theme.navTabs?.accentColor ?? navTabsTokens.selectedUnderlineColor; - navTabsTokens.hoverBackgroundColor = - addLightness(55, theme.navTabs?.baseColor) ?? navTabsTokens.hoverBackgroundColor; + navTabsTokens.hoverBackgroundColor = addLightness(55, theme.navTabs?.baseColor) ?? navTabsTokens.hoverBackgroundColor; navTabsTokens.pressedBackgroundColor = addLightness(50, theme.navTabs?.baseColor) ?? navTabsTokens.pressedBackgroundColor; @@ -253,8 +251,7 @@ const parseTheme = (theme: DeepPartial): AdvancedTheme => { selectTokens.collapseIndicatorColor = theme.select?.fontColor ?? selectTokens.collapseIndicatorColor; selectTokens.hoverInputBorderColor = theme.select?.hoverBorderColor ?? selectTokens.hoverInputBorderColor; selectTokens.selectedHoverListOptionBackgroundColor = - subLightness(5, theme.select?.selectedOptionBackgroundColor) ?? - selectTokens.selectedHoverListOptionBackgroundColor; + subLightness(5, theme.select?.selectedOptionBackgroundColor) ?? selectTokens.selectedHoverListOptionBackgroundColor; selectTokens.selectedActiveListOptionBackgroundColor = subLightness(15, theme.select?.selectedOptionBackgroundColor) ?? selectTokens.selectedActiveListOptionBackgroundColor; @@ -287,8 +284,7 @@ const parseTheme = (theme: DeepPartial): AdvancedTheme => { theme.spinner?.overlayFontColor ?? spinnerTokens.overlayProgressValueFontColor; const switchTokens = componentTokensCopy.switch; - switchTokens.checkedTrackBackgroundColor = - theme.switch?.checkedBaseColor ?? switchTokens.checkedTrackBackgroundColor; + switchTokens.checkedTrackBackgroundColor = theme.switch?.checkedBaseColor ?? switchTokens.checkedTrackBackgroundColor; switchTokens.labelFontColor = theme.switch?.fontColor ?? switchTokens.labelFontColor; switchTokens.disabledCheckedTrackBackgroundColor = addLightness(57, theme.switch?.checkedBaseColor) ?? switchTokens.disabledCheckedTrackBackgroundColor; diff --git a/packages/lib/src/accordion-group/AccordionGroup.accessibility.test.tsx b/packages/lib/src/accordion-group/AccordionGroup.accessibility.test.tsx deleted file mode 100644 index bcdac48db7..0000000000 --- a/packages/lib/src/accordion-group/AccordionGroup.accessibility.test.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { render } from "@testing-library/react"; -import DxcAccordionGroup from "./AccordionGroup"; -import { axe } from "../../test/accessibility/axe-helper"; - -const folderIcon = ( - - - - - - - - -); - -describe("Accordion component accessibility tests", () => { - it("Should not have basic accessibility issues", async () => { - const { container } = render( - - -
First accordion
-
- -
Second accordion
-
-
- ); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - it("Should not have basic accessibility issues for disabled mode", async () => { - const { container } = render( - - -
First accordion
-
- -
Second accordion
-
-
- ); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); -}); diff --git a/packages/lib/src/accordion-group/AccordionGroup.stories.tsx b/packages/lib/src/accordion-group/AccordionGroup.stories.tsx deleted file mode 100644 index e6206b0d58..0000000000 --- a/packages/lib/src/accordion-group/AccordionGroup.stories.tsx +++ /dev/null @@ -1,258 +0,0 @@ -import DxcAccordionGroup from "./AccordionGroup"; -import DxcInset from "../inset/Inset"; -import Title from "../../.storybook/components/Title"; -import ExampleContainer from "../../.storybook/components/ExampleContainer"; -import { Meta, StoryObj } from "@storybook/react"; - -export default { - title: "Accordion Group", - component: DxcAccordionGroup, -} as Meta; - -const AccordionGroup = () => ( - <> - - <ExampleContainer> - <Title title="Default" theme="light" level={4} /> - <DxcAccordionGroup> - <DxcAccordionGroup.Accordion label="Accordion1" assistiveText="Assistive text"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - </DxcInset> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion2" assistiveText="Assistive text"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - </DxcInset> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion3" assistiveText="Assistive text"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - </DxcInset> - </DxcAccordionGroup.Accordion> - </DxcAccordionGroup> - </ExampleContainer> - <ExampleContainer> - <Title title="Expanded" theme="light" level={4} /> - <DxcAccordionGroup defaultIndexActive={1}> - <DxcAccordionGroup.Accordion label="Accordion4"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex - ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat - nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum. - </DxcInset> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion5"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - </DxcInset> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion6"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - </DxcInset> - </DxcAccordionGroup.Accordion> - </DxcAccordionGroup> - </ExampleContainer> - <ExampleContainer> - <Title title="Disabled" theme="light" level={4} /> - <DxcAccordionGroup disabled> - <DxcAccordionGroup.Accordion label="Accordion7"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex - ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat - nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum. - </DxcInset> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion8"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - </DxcInset> - </DxcAccordionGroup.Accordion> - </DxcAccordionGroup> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-focus"> - <Title title="Focused" theme="light" level={4} /> - <DxcAccordionGroup defaultIndexActive={2}> - <DxcAccordionGroup.Accordion label="Accordion9"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex - ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat - nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum. - </DxcInset> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion10"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - </DxcInset> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion11"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - </DxcInset> - </DxcAccordionGroup.Accordion> - </DxcAccordionGroup> - </ExampleContainer> - <Title title="Margins" theme="light" level={2} /> - <ExampleContainer> - <Title title="Xxsmall" theme="light" level={4} /> - <DxcAccordionGroup margin="xxsmall"> - <DxcAccordionGroup.Accordion label="Accordion12"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex - ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat - nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum. - </DxcInset> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion13"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - </DxcInset> - </DxcAccordionGroup.Accordion> - </DxcAccordionGroup> - </ExampleContainer> - <ExampleContainer> - <Title title="Xsmall" theme="light" level={4} /> - <DxcAccordionGroup margin="xsmall"> - <DxcAccordionGroup.Accordion label="Accordion14"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex - ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat - nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum. - </DxcInset> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion15"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - </DxcInset> - </DxcAccordionGroup.Accordion> - </DxcAccordionGroup> - </ExampleContainer> - <ExampleContainer> - <Title title="Small" theme="light" level={4} /> - <DxcAccordionGroup margin="small"> - <DxcAccordionGroup.Accordion label="Accordion16"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex - ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat - nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum. - </DxcInset> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion17"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - </DxcInset> - </DxcAccordionGroup.Accordion> - </DxcAccordionGroup> - </ExampleContainer> - <ExampleContainer> - <Title title="Medium" theme="light" level={4} /> - <DxcAccordionGroup margin="medium"> - <DxcAccordionGroup.Accordion label="Accordion18"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex - ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat - nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum. - </DxcInset> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion19"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - </DxcInset> - </DxcAccordionGroup.Accordion> - </DxcAccordionGroup> - </ExampleContainer> - <ExampleContainer> - <Title title="Large" theme="light" level={4} /> - <DxcAccordionGroup margin="large"> - <DxcAccordionGroup.Accordion label="Accordion20"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex - ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat - nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum. - </DxcInset> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion21"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - </DxcInset> - </DxcAccordionGroup.Accordion> - </DxcAccordionGroup> - </ExampleContainer> - <ExampleContainer> - <Title title="Xlarge" theme="light" level={4} /> - <DxcAccordionGroup margin="xlarge"> - <DxcAccordionGroup.Accordion label="Accordion22"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex - ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat - nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum. - </DxcInset> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion23"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - </DxcInset> - </DxcAccordionGroup.Accordion> - </DxcAccordionGroup> - </ExampleContainer> - <ExampleContainer> - <Title title="Xxlarge" theme="light" level={4} /> - <DxcAccordionGroup margin="xxlarge"> - <DxcAccordionGroup.Accordion label="Accordion24"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex - ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat - nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum. - </DxcInset> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion25"> - <DxcInset space="2rem"> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - </DxcInset> - </DxcAccordionGroup.Accordion> - </DxcAccordionGroup> - </ExampleContainer> - </> -); - -type Story = StoryObj<typeof DxcAccordionGroup>; - -export const Chromatic: Story = { - render: AccordionGroup, -}; diff --git a/packages/lib/src/accordion-group/AccordionGroup.test.tsx b/packages/lib/src/accordion-group/AccordionGroup.test.tsx deleted file mode 100644 index 6d4b87ecf3..0000000000 --- a/packages/lib/src/accordion-group/AccordionGroup.test.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { render, fireEvent } from "@testing-library/react"; -import DxcAccordionGroup from "./AccordionGroup"; - -describe("Accordion component tests", () => { - test("Uncontrolled accordion group calls correct function on click", () => { - const onActiveChange = jest.fn(); - const { getByText, getAllByRole } = render( - <DxcAccordionGroup margin="large" onActiveChange={onActiveChange}> - <DxcAccordionGroup.Accordion label="Accordion1"> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - </div> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion2"> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit - leo lobortis eget. - </div> - </DxcAccordionGroup.Accordion> - </DxcAccordionGroup> - ); - expect(getAllByRole("button")[0]?.getAttribute("aria-expanded")).toBe("false"); - expect(getAllByRole("button")[1]?.getAttribute("aria-expanded")).toBe("false"); - fireEvent.click(getByText("Accordion1")); - expect(onActiveChange).toHaveBeenCalled(); - expect(getAllByRole("button")[0]?.getAttribute("aria-expanded")).toBe("true"); - expect(getAllByRole("button")[1]?.getAttribute("aria-expanded")).toBe("false"); - }); - test("Uncontrolled accordion group renders initially with an accordion expanded using defaultIndexActive", () => { - const { queryByText, getByText, getAllByRole } = render( - <DxcAccordionGroup defaultIndexActive={1}> - <DxcAccordionGroup.Accordion label="Accordion1"> - <div>First accordion</div> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion2"> - <div>Second accordion</div> - </DxcAccordionGroup.Accordion> - </DxcAccordionGroup> - ); - expect(getAllByRole("button")[0]?.getAttribute("aria-expanded")).toBe("false"); - expect(getAllByRole("button")[1]?.getAttribute("aria-expanded")).toBe("true"); - expect(getByText("Second accordion")).toBeTruthy(); - expect(queryByText("First accordion")).toBeFalsy(); - }); - test("Controlled accordion with indexActive change", () => { - const onActiveChange = jest.fn(); - const { queryByText, getByText, getAllByRole, rerender } = render( - <DxcAccordionGroup margin="large" indexActive={1} onActiveChange={onActiveChange}> - <DxcAccordionGroup.Accordion label="Accordion1"> - <div>Text1</div> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion2"> - <div>Text2</div> - </DxcAccordionGroup.Accordion> - </DxcAccordionGroup> - ); - expect(queryByText("Text1")).toBeFalsy(); - expect(getByText("Text2")).toBeTruthy(); - fireEvent.click(getByText("Accordion1")); - fireEvent.click(getByText("Accordion2")); - expect(onActiveChange.mock.calls[0][0]).toBe(0); - expect(onActiveChange.mock.calls[1][0]).toBe(1); - expect(getAllByRole("button")[0]?.getAttribute("aria-expanded")).toBe("false"); - expect(getAllByRole("button")[1]?.getAttribute("aria-expanded")).toBe("true"); - rerender( - <DxcAccordionGroup margin="large" indexActive={0} onActiveChange={onActiveChange}> - <DxcAccordionGroup.Accordion label="Accordion1"> - <div>Text1</div> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion2"> - <div>Text2</div> - </DxcAccordionGroup.Accordion> - </DxcAccordionGroup> - ); - expect(getAllByRole("button")[0]?.getAttribute("aria-expanded")).toBe("true"); - expect(getAllByRole("button")[1]?.getAttribute("aria-expanded")).toBe("false"); - }); - test("Disabled uncontrolled accordion group", () => { - const onActiveChange = jest.fn(); - const { getByText } = render( - <DxcAccordionGroup margin="large" onActiveChange={onActiveChange} disabled> - <DxcAccordionGroup.Accordion label="Accordion1"> - <div>Text1</div> - </DxcAccordionGroup.Accordion> - <DxcAccordionGroup.Accordion label="Accordion2"> - <div>Text2</div> - </DxcAccordionGroup.Accordion> - </DxcAccordionGroup> - ); - fireEvent.click(getByText("Accordion1")); - fireEvent.click(getByText("Accordion2")); - expect(onActiveChange).toHaveBeenCalledTimes(0); - }); -}); diff --git a/packages/lib/src/accordion-group/AccordionGroup.tsx b/packages/lib/src/accordion-group/AccordionGroup.tsx deleted file mode 100644 index 89c7def565..0000000000 --- a/packages/lib/src/accordion-group/AccordionGroup.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { Children, useCallback, useContext, useMemo, useState } from "react"; -import styled, { ThemeProvider } from "styled-components"; -import { getMargin } from "../common/utils"; -import { spaces } from "../common/variables"; -import AccordionGroupAccordion from "./AccordionGroupAccordion"; -import AccordionGroupPropsType from "./types"; -import AccordionGroupAccordionContext from "./AccordionGroupContext"; -import HalstackContext from "../HalstackContext"; - -const DxcAccordionGroup = ({ - defaultIndexActive, - indexActive, - disabled = false, - onActiveChange, - margin, - children, -}: AccordionGroupPropsType): JSX.Element => { - const [innerIndexActive, setInnerIndexActive] = useState(defaultIndexActive ?? -1); - const colorsTheme = useContext(HalstackContext); - - const handlerActiveChange = useCallback( - (index: number) => { - indexActive ?? setInnerIndexActive((prev) => (index === prev ? -1 : index)); - !disabled && onActiveChange?.(index); - }, - [disabled, indexActive, onActiveChange] - ); - const contextValue = useMemo( - () => ({ activeIndex: indexActive ?? innerIndexActive, handlerActiveChange, disabled }), - [indexActive, innerIndexActive, handlerActiveChange, disabled] - ); - - return ( - <ThemeProvider theme={colorsTheme.accordion}> - <AccordionGroupContainer margin={margin} disabled={disabled}> - {Children.map(children, (accordion, index) => ( - <AccordionGroupAccordionContext.Provider key={`accordion-${index}`} value={{ index, ...contextValue }}> - {accordion} - </AccordionGroupAccordionContext.Provider> - ))} - </AccordionGroupContainer> - </ThemeProvider> - ); -}; - -DxcAccordionGroup.Accordion = AccordionGroupAccordion; - -const calculateWidth = (margin: AccordionGroupPropsType["margin"]) => - `calc(100% - ${getMargin(margin, "left")} - ${getMargin(margin, "right")})`; - -const AccordionGroupContainer = styled.div<{ - margin: AccordionGroupPropsType["margin"]; - disabled: AccordionGroupPropsType["disabled"]; -}>` - width: ${(props) => calculateWidth(props.margin)}; - 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] : "")}; - cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")}; - - > div:not(:last-of-type):not(:only-of-type) { - border-bottom: ${(props) => - `${props.theme.accordionGroupSeparatorBorderThickness} ${props.theme.accordionGroupSeparatorBorderStyle}`}; - border-color: ${(props) => props.theme.accordionGroupSeparatorBorderColor}; - } - > div:not(:first-of-type):not(:last-of-type):not(:only-of-type) { - border-radius: 0; - & > h3 > button { - border-radius: 0; - } - } - > div:first-of-type:not(:only-of-type) { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - border-top-left-radius: ${(props) => props.theme.borderRadius}; - border-top-right-radius: ${(props) => props.theme.borderRadius}; - - & > h3 > button { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } - } - > div:last-of-type:not(:only-of-type) { - border-top-left-radius: 0; - border-top-right-radius: 0; - border-bottom-left-radius: ${(props) => props.theme.borderRadius}; - border-bottom-right-radius: ${(props) => props.theme.borderRadius}; - - & > h3 > button { - border-top-left-radius: 0; - border-top-right-radius: 0; - } - } -`; - -export default DxcAccordionGroup; diff --git a/packages/lib/src/accordion-group/AccordionGroupAccordion.tsx b/packages/lib/src/accordion-group/AccordionGroupAccordion.tsx deleted file mode 100644 index 8417f78be5..0000000000 --- a/packages/lib/src/accordion-group/AccordionGroupAccordion.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useContext } from "react"; -import DxcAccordion from "../accordion/Accordion"; -import { AccordionPropsType } from "./types"; -import AccordionGroupAccordionContext from "./AccordionGroupContext"; - -const AccordionGroupAccordion = ({ ...childProps }: AccordionPropsType): JSX.Element => { - const { activeIndex, handlerActiveChange, disabled, index } = useContext(AccordionGroupAccordionContext) ?? {}; - - return ( - <DxcAccordion - isExpanded={activeIndex === index} - onChange={() => { - if (index != null) { - handlerActiveChange?.(index); - } - }} - disabled={disabled} - {...childProps} - > - {childProps.children} - </DxcAccordion> - ); -}; - -export default AccordionGroupAccordion; diff --git a/packages/lib/src/accordion-group/AccordionGroupContext.tsx b/packages/lib/src/accordion-group/AccordionGroupContext.tsx deleted file mode 100644 index 55c46ee9b8..0000000000 --- a/packages/lib/src/accordion-group/AccordionGroupContext.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import { createContext } from "react"; -import type { AccordionGroupAccordionContextProps } from "./types"; - -export default createContext<AccordionGroupAccordionContextProps | null>(null); diff --git a/packages/lib/src/accordion-group/types.ts b/packages/lib/src/accordion-group/types.ts deleted file mode 100644 index 9b392a3ad0..0000000000 --- a/packages/lib/src/accordion-group/types.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { ReactElement, ReactNode, SVGProps } from "react"; - -import { Space, Margin, SVG } from "../common/utils"; - -export type AccordionPropsType = { - /** - * The panel label. - */ - label: string; - /** - * Material Symbol name or SVG element used as the icon that will be placed next to panel label. - */ - icon?: string | SVG; - /** - * Assistive text to be placed on the right side of the panel. - */ - assistiveText?: string; - /** - * If true, the component will be disabled. - */ - disabled?: boolean; - /** - * The expanded panel of the accordion. This area can be used to render - * custom content. - */ - children: ReactNode; -}; - -type Props = { - /** - * Initially active accordion, only when it is uncontrolled. - */ - defaultIndexActive?: number; - /** - * The index of the active accordion. If undefined, the component will be uncontrolled and the active accordion will be managed internally by the component. - * If null, the component will be controlled and all accordions will be closed. - */ - indexActive?: number; - /** - * If true, the component will be disabled. - */ - disabled?: boolean; - /** - * This function will be called when the user clicks on an accordion. The index of the clicked accordion will be passed as a parameter. - */ - onActiveChange?: (indexActive: number) => void; - /** - * Size of the margin to be applied to the component ('xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge'). - * You can pass an object with 'top', 'bottom', 'left' and 'right' properties in order to specify different margin sizes. - */ - margin?: Space | Margin; - /** - * Customized accordion(s) that are allowed inside an Accordion Group. - */ - children: ReactElement<AccordionPropsType>[] | ReactElement<AccordionPropsType>; -}; - -export type AccordionGroupAccordionContextProps = { - activeIndex: number; - handlerActiveChange: (index: number) => void; - disabled: boolean; - index: number; -}; - -export default Props; diff --git a/packages/lib/src/accordion/Accordion.accessibility.test.tsx b/packages/lib/src/accordion/Accordion.accessibility.test.tsx index 610eb1b183..16f1a61cf2 100644 --- a/packages/lib/src/accordion/Accordion.accessibility.test.tsx +++ b/packages/lib/src/accordion/Accordion.accessibility.test.tsx @@ -1,6 +1,8 @@ import { render } from "@testing-library/react"; import DxcAccordion from "./Accordion"; import { axe } from "../../test/accessibility/axe-helper"; +import DxcBadge from "../badge/Badge"; +import DxcStatusLight from "../status-light/StatusLight"; const folderIcon = ( <svg @@ -23,17 +25,70 @@ const folderIcon = ( describe("Accordion component accessibility tests", () => { it("Should not have basic accessibility issues", async () => { const { container } = render( - <DxcAccordion label="Accordion" assistiveText="Assistive Text" icon={folderIcon} margin="small" defaultIsExpanded> - <div>test-expanded</div> + <DxcAccordion defaultIndexActive={0} independent={true}> + <DxcAccordion.AccordionItem + label="Assure Claims" + subLabel="Jan, 09 2025" + assistiveText="Ref - 1236532" + icon={folderIcon} + > + <div>test-expanded</div> + </DxcAccordion.AccordionItem> </DxcAccordion> ); const results = await axe(container); expect(results).toHaveNoViolations(); }); + + it("Should not have basic accessibility issues", async () => { + const { container } = render( + <DxcAccordion defaultIndexActive={0} independent={true}> + <DxcAccordion.AccordionItem + label="Assure Claims" + subLabel="Jan, 09 2025" + assistiveText="Ref - 1236532" + badge={{ position: "before", element: <DxcBadge label="Enterprise" icon={folderIcon} /> }} + statusLight={<DxcStatusLight label="Active" />} + > + <div>test-expanded</div> + </DxcAccordion.AccordionItem> + </DxcAccordion> + ); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it("Should not have basic accessibility issues for disabled mode", async () => { + const { container } = render( + <DxcAccordion defaultIndexActive={0} independent={true}> + <DxcAccordion.AccordionItem + label="Assure Claims" + subLabel="Jan, 09 2025" + assistiveText="Ref - 1236532" + icon={folderIcon} + disabled + > + <div>test-expanded</div> + </DxcAccordion.AccordionItem> + </DxcAccordion> + ); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + it("Should not have basic accessibility issues for disabled mode", async () => { const { container } = render( - <DxcAccordion label="Accordion" assistiveText="Assistive Text" icon={folderIcon} margin="small" disabled> - <div>test-expanded</div> + <DxcAccordion defaultIndexActive={0} independent={true}> + <DxcAccordion.AccordionItem + label="Assure Claims" + subLabel="Jan, 09 2025" + assistiveText="Ref - 1236532" + badge={{ position: "before", element: <DxcBadge label="Enterprise" icon={folderIcon} /> }} + statusLight={<DxcStatusLight label="Active" />} + disabled + > + <div>test-expanded</div> + </DxcAccordion.AccordionItem> </DxcAccordion> ); const results = await axe(container); diff --git a/packages/lib/src/accordion/Accordion.stories.tsx b/packages/lib/src/accordion/Accordion.stories.tsx index 4927992ade..cb16d8150d 100644 --- a/packages/lib/src/accordion/Accordion.stories.tsx +++ b/packages/lib/src/accordion/Accordion.stories.tsx @@ -1,8 +1,10 @@ import DxcAccordion from "./Accordion"; import Title from "../../.storybook/components/Title"; import ExampleContainer from "../../.storybook/components/ExampleContainer"; -import { HalstackProvider } from "../HalstackContext"; import { Meta, StoryObj } from "@storybook/react"; +import DxcBadge from "../badge/Badge"; +import DxcStatusLight from "../status-light/StatusLight"; +import DxcInset from "../inset/Inset"; export default { title: "Accordion", @@ -38,204 +40,515 @@ const facebookIcon = ( </svg> ); -const opinionatedTheme = { - accordion: { - accentColor: "#5f249f", - titleFontColor: "#000000", - assistiveTextFontColor: "#666666", - }, -}; - const Accordion = () => ( <> - <Title title="Component anatomy" theme="light" level={2} /> + <Title title="Accordion anatomy" theme="light" level={2} /> <ExampleContainer> - <Title title="With label" theme="light" level={4} /> - <DxcAccordion label="Accordion 1"> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo - lobortis eget. - </div> + <Title title="Label" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem label="Assure Claims"> + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> </DxcAccordion> </ExampleContainer> <ExampleContainer> - <Title title="With assistive text" theme="light" level={4} /> - <DxcAccordion label="Accordion 2" assistiveText="Assistive text"> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo - lobortis eget. - </div> + <Title title="Label and sublabel" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem label="Assure Claims" subLabel="Jan, 09 2025"> + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> </DxcAccordion> </ExampleContainer> <ExampleContainer> - <Title title="With icon" theme="light" level={4} /> - <DxcAccordion label="Accordion 3" assistiveText="Assistive text" icon="folder"> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo - lobortis eget. - </div> + <Title title="Label and assistive text" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem label="Assure Claims" assistiveText="Ref - 1236554546"> + <div> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </div> + </DxcAccordion.AccordionItem> </DxcAccordion> </ExampleContainer> <ExampleContainer> - <Title title="With smaller icon" theme="light" level={4} /> - <DxcAccordion label="Accordion 4" assistiveText="Assistive text" icon={smallIcon}> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo - lobortis eget. - </div> + <Title title="Label, subLabel and assistive text" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem label="Assure Claims" subLabel="Jan, 09 2025" assistiveText="Ref - 1236554546"> + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> </DxcAccordion> </ExampleContainer> <ExampleContainer> - <Title title="With bigger icon (SVG)" theme="light" level={4} /> - <DxcAccordion label="Accordion Test" assistiveText="Assistive text" icon={facebookIcon}> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo - lobortis eget. - </div> + <Title title="Icon and label" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem label="Assure Claims" icon="heart_plus"> + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> </DxcAccordion> </ExampleContainer> - <Title title="States" theme="light" level={2} /> - <ExampleContainer pseudoState="pseudo-focus"> - <Title title="Focused" theme="light" level={4} /> - <DxcAccordion label="Focused"> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo - lobortis eget. - </div> + <ExampleContainer> + <Title title="Icon, label and sublabel" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem label="Assure Claims" subLabel="Jan, 09 2025" icon="heart_plus"> + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> </DxcAccordion> </ExampleContainer> - <ExampleContainer pseudoState="pseudo-hover"> - <Title title="Hovered" theme="light" level={4} /> - <DxcAccordion label="Hovered"> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo - lobortis eget. - </div> + <ExampleContainer> + <Title title="Icon, label, sublabel and assistive text" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem + label="Assure Claims" + subLabel="Jan, 09 2025" + assistiveText="Ref - 1236554546" + icon="heart_plus" + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> </DxcAccordion> </ExampleContainer> - <ExampleContainer pseudoState="pseudo-active"> - <Title title="Active" theme="light" level={4} /> - <DxcAccordion label="Active"> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo - lobortis eget. - </div> + <ExampleContainer> + <Title title="Label, sublabel, assistive text and badge (before)" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem + label="Assure Claims" + subLabel="Jan, 09 2025" + assistiveText="Ref - 1236554546" + badge={{ position: "before", element: <DxcBadge label="Enterprise" icon="home" /> }} + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> </DxcAccordion> </ExampleContainer> <ExampleContainer> - <Title title="Disabled" theme="light" level={4} /> - <DxcAccordion label="Disabled" assistiveText="Assistive text" icon="folder" disabled> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo - lobortis eget. - </div> + <Title title="Label, sublabel and badge (after)" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem + label="Assure Claims" + subLabel="Jan, 09 2025" + badge={{ position: "after", element: <DxcBadge label="Enterprise" /> }} + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> </DxcAccordion> </ExampleContainer> - <Title title="Margins" theme="light" level={2} /> <ExampleContainer> - <Title title="Xxsmall margin" theme="light" level={4} /> - <DxcAccordion label="Xxsmall margin" margin="xxsmall"> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo - lobortis eget. - </div> + <Title title="Icon, label, sublabel and status light" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem + label="Assure Claims" + subLabel="Jan, 09 2025" + icon="heart_plus" + statusLight={<DxcStatusLight label="Active" />} + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> </DxcAccordion> </ExampleContainer> <ExampleContainer> - <Title title="Xsmall margin" theme="light" level={4} /> - <DxcAccordion label="Xsmall margin" margin="xsmall"> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo - lobortis eget. - </div> + <Title title="Label, sublabel, badge (before) and status light" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem + label="Assure Claims" + subLabel="Jan, 09 2025" + badge={{ position: "before", element: <DxcBadge label="Enterprise" /> }} + statusLight={<DxcStatusLight label="Active" />} + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> </DxcAccordion> </ExampleContainer> <ExampleContainer> - <Title title="Small margin" theme="light" level={4} /> - <DxcAccordion label="Small margin" margin="small"> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo - lobortis eget. - </div> + <Title title="Smaller icon" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem label="Assure Claims" assistiveText="Ref - 1236554546" icon={smallIcon}> + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> </DxcAccordion> </ExampleContainer> <ExampleContainer> - <Title title="Medium margin" theme="light" level={4} /> - <DxcAccordion label="Medium margin" margin="medium"> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo - lobortis eget. - </div> + <Title title="Bigger icon (SVG)" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem label="Assure Claims" assistiveText="Ref - 1236554546" icon={facebookIcon}> + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> </DxcAccordion> </ExampleContainer> <ExampleContainer> - <Title title="Large margin" theme="light" level={4} /> - <DxcAccordion label="Large margin" margin="large"> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo - lobortis eget. - </div> + <Title title="Group of accordions (independent false)" theme="light" level={4} /> + <DxcAccordion defaultIndexActive={[0, 2]}> + <DxcAccordion.AccordionItem label="Accordion1" assistiveText="Assistive text"> + <DxcInset space="2rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + <DxcAccordion.AccordionItem label="Accordion2" assistiveText="Assistive text"> + <DxcInset space="2rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + <DxcAccordion.AccordionItem label="Accordion3" assistiveText="Assistive text"> + <DxcInset space="2rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> </DxcAccordion> </ExampleContainer> <ExampleContainer> - <Title title="Xlarge margin" theme="light" level={4} /> - <DxcAccordion label="Xlarge margin" margin="xlarge"> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo - lobortis eget. - </div> + <Title title="Group of accordions (independent true)" theme="light" level={4} /> + <DxcAccordion independent defaultIndexActive={0}> + <DxcAccordion.AccordionItem + label="Find a person" + badge={{ position: "before", element: <DxcBadge label="GET" color="green" /> }} + statusLight={<DxcStatusLight label="Active" mode="success" />} + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + <DxcAccordion.AccordionItem + label="Create a person" + assistiveText="Provide all required info" + badge={{ position: "before", element: <DxcBadge label="POST" color="blue" /> }} + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + <DxcAccordion.AccordionItem + label="Find interactions" + badge={{ position: "before", element: <DxcBadge label="OPTIONS" color="yellow" /> }} + statusLight={<DxcStatusLight label="Active" mode="warning" />} + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + <DxcAccordion.AccordionItem + label="Delete a person" + assistiveText="Deletion will be permanent" + icon="delete" + badge={{ position: "before", element: <DxcBadge label="DELETE" /> }} + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> </DxcAccordion> </ExampleContainer> <ExampleContainer> - <Title title="Xxlarge margin" theme="light" level={4} /> - <DxcAccordion label="Xxlarge margin" margin="xxlarge"> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo - lobortis eget. - </div> + <Title title="Long badge, long label, long sublabel and long assistive text" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem + label="Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure ClaimsAssure Claims" + subLabel="Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025" + assistiveText="Assistive text Assistive text Assistive text Assistive text Assistive text Assistive text Assistive text Assistive text Assistive text Assistive text Assistive text Assistive text Assistive text " + badge={{ + position: "before", + element: ( + <DxcBadge label="Long long long long long long long long long longlong long long long long long long longlong long long long long long long long text" /> + ), + }} + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> </DxcAccordion> </ExampleContainer> - <Title title="Opinionated theme" theme="light" level={2} /> <ExampleContainer> - <Title title="With assistive text and icon" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcAccordion label="Accordion" assistiveText="Assistive text" icon="folder"> - Content - </DxcAccordion> - </HalstackProvider> + <Title title="Long label, long sublabel" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem + label="Assure Claims Assure Claims Assure Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims " + subLabel="Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025" + icon="heart_plus" + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + </DxcAccordion> + </ExampleContainer> + <ExampleContainer> + <Title title="Long label, long sublabel and short assistive text" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem + label="Assure Claims Assure Claims Assure Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims Assure Claims " + subLabel="Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025 Jan, 09 2025" + assistiveText="Assistive text Assistive text" + icon="heart_plus" + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + </DxcAccordion> + </ExampleContainer> + <ExampleContainer> + <Title title="Short label, long sublabel and long assistive text" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem + label="Assure Claim" + subLabel="Jan, 09 2025" + assistiveText="Assistive text Assistive text Assistive text Assistive text Assistive text Assistive text Assistive text Assistive text Assistive text Assistive text Assistive text Assistive text Assistive text Assistive text" + icon="heart_plus" + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + </DxcAccordion> + </ExampleContainer> + <Title title="States" theme="light" level={2} /> + <ExampleContainer pseudoState="pseudo-focus"> + <Title title="Focused" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem label="Assure Claims" subLabel="Jan, 09 2025" assistiveText="Ref - 1236554546"> + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + </DxcAccordion> </ExampleContainer> <ExampleContainer pseudoState="pseudo-hover"> <Title title="Hovered" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcAccordion label="Hovered"> - <div> + <DxcAccordion> + <DxcAccordion.AccordionItem label="Assure Claims" subLabel="Jan, 09 2025" assistiveText="Ref - 1236554546"> + <DxcInset space="1.5rem"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortis eget. - </div> - </DxcAccordion> - </HalstackProvider> + </DxcInset> + </DxcAccordion.AccordionItem> + </DxcAccordion> </ExampleContainer> <ExampleContainer pseudoState="pseudo-active"> <Title title="Active" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcAccordion label="Active"> - <div> + <DxcAccordion> + <DxcAccordion.AccordionItem label="Assure Claims" subLabel="Jan, 09 2025" assistiveText="Ref - 1236554546"> + <DxcInset space="1.5rem"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortis eget. - </div> - </DxcAccordion> - </HalstackProvider> + </DxcInset> + </DxcAccordion.AccordionItem> + </DxcAccordion> </ExampleContainer> <ExampleContainer> <Title title="Disabled" theme="light" level={4} /> - <HalstackProvider theme={opinionatedTheme}> - <DxcAccordion label="Disabled" assistiveText="Assistive text" icon="folder" disabled> - <div> + <DxcAccordion> + <DxcAccordion.AccordionItem + label="Assure Claims" + subLabel="Jan, 09 2025" + assistiveText="Ref - 1236554546" + icon="heart_plus" + disabled + > + <DxcInset space="1.5rem"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortis eget. - </div> - </DxcAccordion> - </HalstackProvider> + </DxcInset> + </DxcAccordion.AccordionItem> + </DxcAccordion> + <DxcAccordion> + <DxcAccordion.AccordionItem + label="Assure Claims" + subLabel="Jan, 09 2025" + disabled + badge={{ position: "before", element: <DxcBadge label="Enterprise" color="green" /> }} + statusLight={<DxcStatusLight label="Active" mode="success" />} + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + </DxcAccordion> + <DxcAccordion> + <DxcAccordion.AccordionItem + label="Assure Claims" + subLabel="Jan, 09 2025" + icon="heart_plus" + disabled + badge={{ position: "after", element: <DxcBadge label="Enterprise" color="green" /> }} + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + </DxcAccordion> + </ExampleContainer> + <ExampleContainer> + <Title title="Group of accordions" theme="light" level={4} /> + <DxcAccordion> + <DxcAccordion.AccordionItem + label="Find a person" + badge={{ position: "before", element: <DxcBadge label="GET" color="green" /> }} + statusLight={<DxcStatusLight label="Active" mode="success" />} + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + <DxcAccordion.AccordionItem + label="Create a person" + assistiveText="Provide all required info" + badge={{ position: "before", element: <DxcBadge label="POST" color="blue" /> }} + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + <DxcAccordion.AccordionItem + label="Find interactions" + badge={{ position: "before", element: <DxcBadge label="OPTIONS" color="yellow" /> }} + statusLight={<DxcStatusLight label="Active" mode="warning" />} + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + <DxcAccordion.AccordionItem + label="Delete a person" + assistiveText="Deletion will be permanent" + icon="delete" + badge={{ position: "before", element: <DxcBadge label="DELETE" /> }} + > + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + </DxcAccordion> + </ExampleContainer> + <Title title="Margins" theme="light" level={2} /> + <ExampleContainer> + <Title title="Xxsmall margin" theme="light" level={4} /> + <DxcAccordion margin="xxsmall"> + <DxcAccordion.AccordionItem label="Assure Claims"> + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + </DxcAccordion> + </ExampleContainer> + <ExampleContainer> + <Title title="Xsmall margin" theme="light" level={4} /> + <DxcAccordion margin="xsmall"> + <DxcAccordion.AccordionItem label="Assure Claims"> + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + </DxcAccordion> + </ExampleContainer> + <ExampleContainer> + <Title title="Small margin" theme="light" level={4} /> + <DxcAccordion margin="small"> + <DxcAccordion.AccordionItem label="Assure Claims"> + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + </DxcAccordion> + </ExampleContainer> + <ExampleContainer> + <Title title="Medium margin" theme="light" level={4} /> + <DxcAccordion margin="medium"> + <DxcAccordion.AccordionItem label="Assure Claims"> + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + </DxcAccordion> + </ExampleContainer> + <ExampleContainer> + <Title title="Large margin" theme="light" level={4} /> + <DxcAccordion margin="large"> + <DxcAccordion.AccordionItem label="Assure Claims"> + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + </DxcAccordion> + </ExampleContainer> + <ExampleContainer> + <Title title="Xlarge margin" theme="light" level={4} /> + <DxcAccordion margin="xlarge"> + <DxcAccordion.AccordionItem label="Assure Claims"> + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + </DxcAccordion> + </ExampleContainer> + <ExampleContainer> + <Title title="Xxlarge margin" theme="light" level={4} /> + <DxcAccordion margin="xxlarge"> + <DxcAccordion.AccordionItem label="Assure Claims"> + <DxcInset space="1.5rem"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit + leo lobortis eget. + </DxcInset> + </DxcAccordion.AccordionItem> + </DxcAccordion> </ExampleContainer> </> ); diff --git a/packages/lib/src/accordion/Accordion.test.tsx b/packages/lib/src/accordion/Accordion.test.tsx index b2b0b5b263..762f1ffa64 100644 --- a/packages/lib/src/accordion/Accordion.test.tsx +++ b/packages/lib/src/accordion/Accordion.test.tsx @@ -4,8 +4,10 @@ import DxcAccordion from "./Accordion"; describe("Accordion component tests", () => { test("Renders with correct aria accessibility attributes", () => { const { getByRole } = render( - <DxcAccordion label="Accordion" defaultIsExpanded> - <div>test-expanded</div> + <DxcAccordion defaultIndexActive={0} independent={true}> + <DxcAccordion.AccordionItem label="Accordion"> + <div>test-expanded</div> + </DxcAccordion.AccordionItem> </DxcAccordion> ); const accordion = getByRole("button"); @@ -14,19 +16,33 @@ describe("Accordion component tests", () => { expect(panel.getAttribute("aria-labelledby")).toBe(accordion.id); }); test("Renders expanded by default when it is uncontrolled", () => { - const { getByRole } = render( - <DxcAccordion label="Accordion" defaultIsExpanded> - <div>test-expanded</div> + const { getAllByRole } = render( + <DxcAccordion defaultIndexActive={[0, 2]}> + <DxcAccordion.AccordionItem label="Accordion 1"> + <div>test-expanded-1</div> + </DxcAccordion.AccordionItem> + <DxcAccordion.AccordionItem label="Accordion 2"> + <div>test-expanded-2</div> + </DxcAccordion.AccordionItem> + <DxcAccordion.AccordionItem label="Accordion 3"> + <div>test-expanded-3</div> + </DxcAccordion.AccordionItem> </DxcAccordion> ); - const accordion = getByRole("button"); - expect(accordion.getAttribute("aria-expanded")).toBe("true"); + const accordion1 = getAllByRole("button")[0]; + expect(accordion1?.getAttribute("aria-expanded")).toBe("true"); + const accordion2 = getAllByRole("button")[1]; + expect(accordion2?.getAttribute("aria-expanded")).toBe("false"); + const accordion3 = getAllByRole("button")[2]; + expect(accordion3?.getAttribute("aria-expanded")).toBe("true"); }); test("Calls correct function on click", () => { const onChange = jest.fn(); const { getByText } = render( - <DxcAccordion label="Accordion" onChange={onChange}> - <div>test-expanded</div> + <DxcAccordion onActiveChange={onChange} independent={true}> + <DxcAccordion.AccordionItem label="Accordion"> + <div>test-expanded</div> + </DxcAccordion.AccordionItem> </DxcAccordion> ); fireEvent.click(getByText("Accordion")); @@ -34,18 +50,85 @@ describe("Accordion component tests", () => { }); test("Controlled accordion", () => { const onChange = jest.fn(); - const { getByText, getByRole } = render( - <DxcAccordion label="Accordion" onChange={onChange} isExpanded> - <div>test-expanded</div> + const { getByText, getByRole, rerender } = render( + <DxcAccordion onActiveChange={onChange} indexActive={0} independent={true}> + <DxcAccordion.AccordionItem label="Accordion"> + <div>test-expanded</div> + </DxcAccordion.AccordionItem> </DxcAccordion> ); expect(getByRole("button").getAttribute("aria-expanded")).toBe("true"); fireEvent.click(getByText("Accordion")); + rerender( + <DxcAccordion onActiveChange={onChange} indexActive={-1} independent={true}> + <DxcAccordion.AccordionItem label="Accordion"> + <div>test-expanded</div> + </DxcAccordion.AccordionItem> + </DxcAccordion> + ); + expect(getByRole("button").getAttribute("aria-expanded")).toBe("false"); + fireEvent.click(getByText("Accordion")); + rerender( + <DxcAccordion onActiveChange={onChange} indexActive={0} independent={true}> + <DxcAccordion.AccordionItem label="Accordion"> + <div>test-expanded</div> + </DxcAccordion.AccordionItem> + </DxcAccordion> + ); + expect(getByRole("button").getAttribute("aria-expanded")).toBe("true"); + expect(onChange).toHaveBeenCalledTimes(2); + }); + test("Independent accordion items behave independently", () => { + const { getAllByRole, getByText } = render( + <DxcAccordion independent={true} defaultIndexActive={0}> + <DxcAccordion.AccordionItem label="Accordion 1"> + <div>test-expanded-1</div> + </DxcAccordion.AccordionItem> + <DxcAccordion.AccordionItem label="Accordion 2"> + <div>test-expanded-2</div> + </DxcAccordion.AccordionItem> + <DxcAccordion.AccordionItem label="Accordion 3"> + <div>test-expanded-3</div> + </DxcAccordion.AccordionItem> + </DxcAccordion> + ); + + const accordion1 = getAllByRole("button")[0]; + const accordion2 = getAllByRole("button")[1]; + const accordion3 = getAllByRole("button")[2]; + + expect(accordion1?.getAttribute("aria-expanded")).toBe("true"); + expect(accordion2?.getAttribute("aria-expanded")).toBe("false"); + expect(accordion3?.getAttribute("aria-expanded")).toBe("false"); + + fireEvent.click(getByText("Accordion 2")); + expect(accordion1?.getAttribute("aria-expanded")).toBe("false"); + expect(accordion2?.getAttribute("aria-expanded")).toBe("true"); + expect(accordion3?.getAttribute("aria-expanded")).toBe("false"); + }); + test("Accordion item is disabled", () => { + const { getByText, getByRole } = render( + <DxcAccordion defaultIndexActive={0} independent={true}> + <DxcAccordion.AccordionItem label="Accordion" disabled> + <div>test-expanded</div> + </DxcAccordion.AccordionItem> + </DxcAccordion> + ); + const accordion = getByRole("button"); + expect(accordion.getAttribute("aria-expanded")).toBe("true"); fireEvent.click(getByText("Accordion")); + expect(accordion.getAttribute("aria-expanded")).toBe("true"); + }); + test("Does not call onActiveChange when disabled", () => { + const onChange = jest.fn(); + const { getByText } = render( + <DxcAccordion onActiveChange={onChange}> + <DxcAccordion.AccordionItem label="Accordion" disabled> + <div>test-expanded</div> + </DxcAccordion.AccordionItem> + </DxcAccordion> + ); fireEvent.click(getByText("Accordion")); - expect(onChange).toHaveBeenCalledTimes(3); - expect(onChange.mock.calls[0][0]).toBe(false); - expect(onChange.mock.calls[1][0]).toBe(false); - expect(onChange.mock.calls[2][0]).toBe(false); + expect(onChange).not.toHaveBeenCalled(); }); }); diff --git a/packages/lib/src/accordion/Accordion.tsx b/packages/lib/src/accordion/Accordion.tsx index cbdea4f20b..180777ce19 100644 --- a/packages/lib/src/accordion/Accordion.tsx +++ b/packages/lib/src/accordion/Accordion.tsx @@ -1,194 +1,146 @@ -import { useContext, useId, useState } from "react"; +import { Children, useCallback, useContext, useMemo, useState } from "react"; import styled, { ThemeProvider } from "styled-components"; import { getMargin } from "../common/utils"; import { spaces } from "../common/variables"; -import HalstackContext from "../HalstackContext"; import AccordionPropsType from "./types"; -import DxcIcon from "../icon/Icon"; +import AccordionContext from "./AccordionContext"; +import HalstackContext from "../HalstackContext"; +import AccordionItem from "./AccordionItem"; + +const DxcAccordion = (props: AccordionPropsType): JSX.Element => { + const { children, margin, onActiveChange } = props; + const colorsTheme = useContext(HalstackContext); + + const [innerIndexActive, setInnerIndexActive] = useState( + props.independent + ? (props.defaultIndexActive ?? -1) + : Array.isArray(props.defaultIndexActive) + ? props.defaultIndexActive.filter((i) => i !== undefined) + : [] + ); + + const handlerActiveChange = useCallback( + (index: number | number[]) => { + if (props.indexActive == null) { + setInnerIndexActive((prev) => { + if (props.independent) return typeof index === "number" ? (index === prev ? -1 : index) : prev; + else { + const prevArray = Array.isArray(prev) ? prev : []; + return Array.isArray(index) + ? index + : prevArray.includes(index) + ? prevArray.filter((i) => i !== index) + : [...prevArray, index]; + } + }); + } + onActiveChange?.(index as number & number[]); + }, + [props.indexActive, props.independent, onActiveChange, innerIndexActive] + ); + + const contextValue = useMemo( + () => ({ + activeIndex: props.indexActive ?? innerIndexActive, + handlerActiveChange, + independent: props.independent, + }), + [props.indexActive, innerIndexActive, handlerActiveChange, props.independent] + ); + + return ( + <ThemeProvider theme={colorsTheme.accordion}> + <AccordionContainer margin={margin}> + {Children.map(children, (accordion, index) => ( + <AccordionContext.Provider key={`accordion-${index}`} value={{ index, ...contextValue }}> + {accordion} + </AccordionContext.Provider> + ))} + </AccordionContainer> + </ThemeProvider> + ); +}; + +DxcAccordion.AccordionItem = AccordionItem; const calculateWidth = (margin: AccordionPropsType["margin"]) => `calc(100% - ${getMargin(margin, "left")} - ${getMargin(margin, "right")})`; const AccordionContainer = styled.div<{ - isExpanded: AccordionPropsType["isExpanded"]; margin: AccordionPropsType["margin"]; }>` - display: flex; - flex-direction: column; - background-color: ${(props) => props.theme.backgroundColor}; - border-radius: ${(props) => props.theme.borderRadius}; - ${(props) => props.isExpanded && `border-bottom-left-radius: 0; border-bottom-right-radius: 0;`} - box-shadow: ${(props) => - `${props.theme.boxShadowOffsetX} ${props.theme.boxShadowOffsetY} ${props.theme.boxShadowBlur} ${props.theme.boxShadowColor}`}; - min-width: 280px; - 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] : ""}; width: ${(props) => calculateWidth(props.margin)}; -`; - -const AccordionHeader = styled.h3` - display: flex; - flex-direction: column; - min-height: 48px; - margin: 0; -`; - -const AccordionTrigger = styled.button<{ - isExpanded: AccordionPropsType["isExpanded"]; -}>` - display: flex; - justify-content: space-between; - align-items: center; - gap: 24px; - width: 100%; - background-color: transparent; - border: none; - border-radius: ${(props) => props.theme.borderRadius}; - ${(props) => props.isExpanded && `border-bottom-left-radius: 0; border-bottom-right-radius: 0;`} - padding: 12px 16px; - cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")}; - - :focus { - outline: ${(props) => - `${props.theme.focusBorderColor} ${props.theme.focusBorderStyle} ${props.theme.focusBorderThickness}`}; - } - :hover:enabled { - background-color: ${(props) => `${props.theme.hoverBackgroundColor}`}; - } - :active:enabled { - background-color: ${(props) => `${props.theme.hoverBackgroundColor}`}; + 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] : "")}; + cursor: "pointer"; + + // first and middle accordions (separator) + > div:not(:last-of-type):not(:only-of-type) { + border-bottom: ${(props) => + `${props.theme.accordionSeparatorBorderThickness} ${props.theme.accordionSeparatorBorderStyle}`}; + border-color: ${(props) => props.theme.accordionSeparatorBorderColor}; } -`; -const AccordionInfo = styled.span` - display: inline-flex; - justify-content: space-between; - width: 100%; -`; + // first accordion + > div:first-of-type:not(:only-of-type) { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-top-left-radius: ${(props) => props.theme.borderRadius}; + border-top-right-radius: ${(props) => props.theme.borderRadius}; + } -const AccordionLabel = styled.span<{ disabled: AccordionPropsType["disabled"] }>` - display: flex; - padding-top: ${(props) => props.theme.titleLabelPaddingTop}; - padding-bottom: ${(props) => props.theme.titleLabelPaddingBottom}; - padding-right: ${(props) => props.theme.titleLabelPaddingRight}; - padding-left: ${(props) => props.theme.titleLabelPaddingLeft}; - color: ${(props) => (props.disabled ? props.theme.disabledTitleLabelFontColor : props.theme.titleLabelFontColor)}; - font-family: ${(props) => props.theme.titleLabelFontFamily}; - font-size: ${(props) => props.theme.titleLabelFontSize}; - font-style: ${(props) => props.theme.titleLabelFontStyle}; - font-weight: ${(props) => props.theme.titleLabelFontWeight}; - line-height: 1.5em; -`; + // first accordion: hover, focus and active + > div:first-of-type:not(:only-of-type) button:hover, + div:first-of-type:not(:only-of-type) button:focus, + div:first-of-type:not(:only-of-type) button:active { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } -const IconContainer = styled.span<{ disabled: AccordionPropsType["disabled"] }>` - display: flex; - margin-left: ${(props) => props.theme.iconMarginLeft}; - margin-right: ${(props) => props.theme.iconMarginRight}; - color: ${(props) => (props.disabled ? props.theme.disabledIconColor : props.theme.iconColor)}; - font-size: ${(props) => props.theme.iconSize}; + // middle accordions + > div:not(:first-of-type):not(:last-of-type):not(:only-of-type) { + border-radius: 0; + } - svg { - height: ${(props) => props.theme.iconSize}; - width: ${(props) => props.theme.iconSize}; + // middle accordions: hover, focus and active + > div:not(:first-of-type):not(:last-of-type):not(:only-of-type) button:hover, + div:not(:first-of-type):not(:last-of-type):not(:only-of-type) button:focus, + div:not(:first-of-type):not(:last-of-type):not(:only-of-type) button:active { + border-radius: 0; } -`; -const AccordionAssistiveText = styled.span<{ disabled: AccordionPropsType["disabled"] }>` - min-width: ${(props) => props.theme.assistiveTextMinWidth}; - padding-left: ${(props) => props.theme.assistiveTextPaddingLeft}; - padding-right: ${(props) => props.theme.assistiveTextPaddingRight}; - color: ${(props) => - props.disabled ? props.theme.disabledAssistiveTextFontColor : props.theme.assistiveTextFontColor}; - font-family: ${(props) => props.theme.assistiveTextFontFamily}; - font-size: ${(props) => props.theme.assistiveTextFontSize}; - font-style: ${(props) => props.theme.assistiveTextFontStyle}; - font-weight: ${(props) => props.theme.assistiveTextFontWeight}; - letter-spacing: ${(props) => props.theme.assistiveTextLetterSpacing}; - line-height: 1.5em; -`; + // last accordion + > div:last-of-type:not(:only-of-type) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: ${(props) => props.theme.borderRadius}; + border-bottom-right-radius: ${(props) => props.theme.borderRadius}; + } -const CollapseIndicator = styled.span<{ - disabled: AccordionPropsType["disabled"]; -}>` - display: flex; - flex-wrap: wrap; - align-content: center; - font-size: 24px; - color: ${(props) => (props.disabled ? props.theme.disabledArrowColor : props.theme.arrowColor)}; -`; + // last accordion: hover, focus and active + > div:last-of-type:not(:only-of-type) button:hover, + div:last-of-type:not(:only-of-type) button:focus, + div:last-of-type:not(:only-of-type) button:active { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: ${(props) => props.theme.borderRadius}; + border-bottom-right-radius: ${(props) => props.theme.borderRadius}; + } -const AccordionPanel = styled.div` - border-bottom-left-radius: ${(props) => props.theme.borderRadius}; - border-bottom-right-radius: ${(props) => props.theme.borderRadius}; + // last expanded accordion + > div:last-of-type:not(:only-of-type) > button[aria-expanded="true"] { + border-radius: 0; + } + // last expanded accordion: hover, focus and active + > div:last-of-type:not(:only-of-type) > button[aria-expanded="true"]:hover, + div:last-of-type:not(:only-of-type) > button[aria-expanded="true"]:focus, + div:last-of-type:not(:only-of-type) > button[aria-expanded="true"]:active { + border-radius: 0; + } `; -const DxcAccordion = ({ - label = "", - defaultIsExpanded = false, - isExpanded, - icon, - assistiveText = "", - disabled = false, - onChange, - children, - margin, - tabIndex = 0, -}: AccordionPropsType): JSX.Element => { - const id = useId(); - const [innerIsExpanded, setInnerIsExpanded] = useState(defaultIsExpanded); - const colorsTheme = useContext(HalstackContext); - - const handleAccordionState = () => { - if (isExpanded == null) { - setInnerIsExpanded((innerIsCurrentlyExpanded) => !innerIsCurrentlyExpanded); - } - onChange?.(isExpanded != null ? !isExpanded : !innerIsExpanded); - }; - - return ( - <ThemeProvider theme={colorsTheme.accordion}> - <AccordionContainer isExpanded={isExpanded ?? innerIsExpanded} margin={margin}> - <AccordionHeader> - <AccordionTrigger - id={`accordion-${id}`} - onClick={disabled ? undefined : handleAccordionState} - disabled={disabled} - tabIndex={disabled ? -1 : tabIndex} - aria-expanded={isExpanded ?? innerIsExpanded} - aria-controls={`accordion-panel-${id}`} - isExpanded={isExpanded ?? innerIsExpanded} - > - <AccordionInfo> - <AccordionLabel disabled={disabled}> - {icon && ( - <IconContainer disabled={disabled}> - {typeof icon === "string" ? <DxcIcon icon={icon} /> : icon} - </IconContainer> - )} - {label} - </AccordionLabel> - {assistiveText && <AccordionAssistiveText disabled={disabled}>{assistiveText}</AccordionAssistiveText>} - </AccordionInfo> - <CollapseIndicator disabled={disabled}> - <DxcIcon icon={(isExpanded ?? innerIsExpanded) ? "expand_less" : "expand_more"} /> - </CollapseIndicator> - </AccordionTrigger> - </AccordionHeader> - {(isExpanded ?? innerIsExpanded) && ( - <AccordionPanel id={`accordion-panel-${id}`} role="region" aria-labelledby={`accordion-${id}`}> - {children} - </AccordionPanel> - )} - </AccordionContainer> - </ThemeProvider> - ); -}; - export default DxcAccordion; diff --git a/packages/lib/src/accordion/AccordionContext.tsx b/packages/lib/src/accordion/AccordionContext.tsx new file mode 100644 index 0000000000..972b8a8ce0 --- /dev/null +++ b/packages/lib/src/accordion/AccordionContext.tsx @@ -0,0 +1,4 @@ +import { createContext } from "react"; +import type { AccordionContextProps } from "./types"; + +export default createContext<AccordionContextProps | null>(null); diff --git a/packages/lib/src/accordion/AccordionItem.tsx b/packages/lib/src/accordion/AccordionItem.tsx new file mode 100644 index 0000000000..c2ce6a448b --- /dev/null +++ b/packages/lib/src/accordion/AccordionItem.tsx @@ -0,0 +1,243 @@ +import { ReactElement, useContext, useId, cloneElement, useMemo } from "react"; +import styled, { ThemeProvider } from "styled-components"; +import HalstackContext from "../HalstackContext"; +import { AccordionItemProps } from "./types"; +import DxcIcon from "../icon/Icon"; +import DxcFlex from "../flex/Flex"; +import DxcContainer from "../container/Container"; +import React from "react"; +import AccordionContext from "./AccordionContext"; + +const AccordionItem = ({ + label = "", + subLabel = "", + badge, + statusLight, + icon, + assistiveText = "", + disabled = false, + children, + tabIndex = 0, +}: AccordionItemProps): JSX.Element => { + const id = useId(); + const colorsTheme = useContext(HalstackContext); + const { activeIndex, handlerActiveChange, index, independent } = useContext(AccordionContext) ?? {}; + const isItemExpanded = useMemo(() => { + return independent + ? activeIndex === index + : Array.isArray(activeIndex) && index !== undefined && activeIndex.includes(index); + }, [independent, activeIndex, index]); + + const handleAccordionState = () => { + if (index !== undefined) handlerActiveChange?.(index); + }; + + return ( + <ThemeProvider theme={colorsTheme.accordion}> + <AccordionContainer> + <AccordionTrigger + id={`accordion-${id}`} + onClick={disabled ? undefined : handleAccordionState} + disabled={disabled} + tabIndex={disabled ? -1 : tabIndex} + aria-expanded={isItemExpanded} + aria-controls={`accordion-panel-${id}`} + > + <DxcContainer width="100%" height="100%"> + <DxcFlex gap="1.5rem"> + <LeftSideContainer> + <DxcFlex gap="0.75rem"> + {(icon || badge?.position === "before") && ( + <OptionalElement> + {icon ? ( + <IconContainer disabled={disabled}> + {typeof icon === "string" ? <DxcIcon icon={icon} /> : icon} + </IconContainer> + ) : ( + <StatusContainer subLabel={subLabel}> + {disabled ? cloneElement(badge?.element as ReactElement, { color: "grey" }) : badge?.element} + </StatusContainer> + )} + </OptionalElement> + )} + <LabelsContainer> + <AccordionLabel disabled={disabled}>{label}</AccordionLabel> + {subLabel && <SubLabel disabled={disabled}>{subLabel}</SubLabel>} + </LabelsContainer> + </DxcFlex> + </LeftSideContainer> + <RightSideContainer> + {assistiveText && ( + <AssistiveText disabled={disabled} subLabel={subLabel}> + {assistiveText} + </AssistiveText> + )} + {badge && badge?.position === "after" && !assistiveText && ( + <StatusContainer subLabel={subLabel}> + {disabled ? React.cloneElement(badge.element as ReactElement, { color: "grey" }) : badge.element} + </StatusContainer> + )} + {badge?.position !== "after" && statusLight && !assistiveText && ( + <StatusContainer subLabel={subLabel}> + {disabled ? React.cloneElement(statusLight as ReactElement, { mode: "default" }) : statusLight} + </StatusContainer> + )} + <CollapseIndicator disabled={disabled}> + <DxcIcon icon={isItemExpanded ? "expand_less" : "expand_more"} /> + </CollapseIndicator> + </RightSideContainer> + </DxcFlex> + </DxcContainer> + </AccordionTrigger> + {isItemExpanded && ( + <AccordionPanel id={`accordion-panel-${id}`} role="region" aria-labelledby={`accordion-${id}`}> + {children} + </AccordionPanel> + )} + </AccordionContainer> + </ThemeProvider> + ); +}; + +const AccordionContainer = styled.div` + display: flex; + flex-direction: column; + background-color: ${(props) => props.theme.backgroundColor}; + border-radius: ${(props) => props.theme.borderRadius}; + box-shadow: ${(props) => + `${props.theme.boxShadowOffsetX} ${props.theme.boxShadowOffsetY} ${props.theme.boxShadowBlur} ${props.theme.boxShadowSpread} ${props.theme.boxShadowColor}`}; + min-width: 280px; + width: 100%; +`; + +const AccordionTrigger = styled.button` + display: flex; + justify-content: space-between; + gap: 1.5rem; + width: 100%; + background-color: transparent; + border: none; + border-radius: ${(props) => props.theme.borderRadius}; + padding: 8px 16px; + cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")}; + :focus { + background-color: ${(props) => `${props.theme.focusBackgroundColor}`}; + box-shadow: inset 0 0 0 ${(props) => props.theme.focusBorderThickness} ${(props) => props.theme.focusBorderColor}; + } + :focus-visible { + background-color: ${(props) => `${props.theme.focusBackgroundColor}`}; + box-shadow: inset 0 0 0 ${(props) => props.theme.focusBorderThickness} ${(props) => props.theme.focusBorderColor}; + outline: none; + } + :active:enabled { + background-color: ${(props) => `${props.theme.activeBackgroundColor}`}; + box-shadow: inset 0 0 0 ${(props) => props.theme.focusBorderThickness} ${(props) => props.theme.focusBorderColor}; + } + :hover:enabled { + background-color: ${(props) => `${props.theme.hoverBackgroundColor}`}; + } +`; +const LeftSideContainer = styled.div` + flex: 1; + overflow: hidden; +`; + +const RightSideContainer = styled.div` + display: flex; + overflow: hidden; + justify-content: flex-end; + gap: 0.5rem; + max-width: 30%; + flex-shrink: 0; +`; + +const OptionalElement = styled.div` + max-width: 30%; + overflow: hidden; +`; + +const LabelsContainer = styled.div` + display: flex; + flex-direction: column; + overflow: hidden; +`; + +const StatusContainer = styled.div<{ subLabel: AccordionItemProps["subLabel"] }>` + display: flex; + align-items: ${(props) => (props.subLabel ? "flex-start" : "center")}; + margin-top: ${(props) => props.subLabel && "4px"}; +`; + +const IconContainer = styled.span<{ disabled: AccordionItemProps["disabled"] }>` + display: flex; + color: ${(props) => (props.disabled ? props.theme.disabledIconColor : props.theme.iconColor)}; + font-size: ${(props) => props.theme.iconSize}; + svg { + height: ${(props) => props.theme.iconSize}; + width: ${(props) => props.theme.iconSize}; + } +`; + +const AccordionLabel = styled.span<{ disabled: AccordionItemProps["disabled"] }>` + color: ${(props) => (props.disabled ? props.theme.disabledTitleLabelFontColor : props.theme.titleLabelFontColor)}; + font-family: ${(props) => props.theme.titleLabelFontFamily}; + font-size: ${(props) => props.theme.titleLabelFontSize}; + font-style: ${(props) => props.theme.titleLabelFontStyle}; + font-weight: ${(props) => props.theme.titleLabelFontWeight}; + line-height: 1.5em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +const SubLabel = styled.span<{ disabled: AccordionItemProps["disabled"] }>` + height: 20px; + color: ${(props) => (props.disabled ? props.theme.disabledSubLabelFontColor : props.theme.subLabelFontColor)}; + font-family: ${(props) => props.theme.subLabelFontFamily}; + font-size: ${(props) => props.theme.subLabelFontSize}; + font-style: ${(props) => props.theme.subLabelFontStyle}; + font-weight: ${(props) => props.theme.subLabelFontWeight}; + line-height: 1.5em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +`; + +const AssistiveText = styled.span<{ + disabled: AccordionItemProps["disabled"]; + subLabel: AccordionItemProps["subLabel"]; +}>` + color: ${(props) => + props.disabled ? props.theme.disabledAssistiveTextFontColor : props.theme.assistiveTextFontColor}; + font-family: ${(props) => props.theme.assistiveTextFontFamily}; + font-size: ${(props) => props.theme.assistiveTextFontSize}; + font-style: ${(props) => props.theme.assistiveTextFontStyle}; + font-weight: ${(props) => props.theme.assistiveTextFontWeight}; + line-height: 1.5em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + align-content: ${(props) => !props.subLabel && "center"}; + margin-top: ${(props) => props.subLabel && "4px"}; +`; + +const CollapseIndicator = styled.span<{ + disabled: AccordionItemProps["disabled"]; +}>` + display: flex; + flex-wrap: wrap; + font-size: 24px; + color: ${(props) => (props.disabled ? props.theme.disabledArrowColor : props.theme.arrowColor)}; + svg { + height: ${(props) => props.theme.iconSize}; + width: ${(props) => props.theme.iconSize}; + } +`; + +const AccordionPanel = styled.div` + border-bottom-left-radius: ${(props) => props.theme.borderRadius}; + border-bottom-right-radius: ${(props) => props.theme.borderRadius}; +`; + +export default AccordionItem; diff --git a/packages/lib/src/accordion/types.ts b/packages/lib/src/accordion/types.ts index cfa1c191dd..8f44b489ad 100644 --- a/packages/lib/src/accordion/types.ts +++ b/packages/lib/src/accordion/types.ts @@ -1,23 +1,31 @@ -import { ReactNode } from "react"; +import { ReactNode, ReactElement } from "react"; import { Margin, SVG, Space } from "../common/utils"; -type Props = { +export type AccordionItemProps = { /** * The panel label. */ label: string; /** - * Initial state of the panel, only when it is uncontrolled. + * Additional info label positioned under the label. */ - defaultIsExpanded?: boolean; + subLabel?: string; /** - * Represents the state of the panel. When true, the component will be - * expanded. If undefined, the component will be uncontrolled and its - * value will be managed internally by the component. + * Badge component to add extra value to the accordion. */ - isExpanded?: boolean; + badge?: { + position: "before" | "after"; + element: ReactNode; + }; /** - * Material Symbol name or SVG element used as the icon that will be placed next to panel label. + * Status light component to add extra value to the accordion. + */ + statusLight?: ReactNode; + /** + * Material Symbol name or SVG element used as the icon that will be + * placed next to panel label. When using Material Symbols, replace spaces with underscores. + * By default they are outlined if you want it to be filled + * prefix the symbol name with "filled_". */ icon?: string | SVG; /** @@ -28,25 +36,80 @@ type Props = { * If true, the component will be disabled. */ disabled?: boolean; - /** - * This function will be called when the user clicks the accordion to expand or collapse - * the panel. The new state of the panel will be passed as a parameter. - */ - onChange?: (isExpanded: boolean) => void; /** * The expanded panel of the accordion. This area can be used to render * custom content. */ children: ReactNode; + /** + * Value of the tabindex attribute. + */ + tabIndex?: number; +}; + +type CommonProps = { /** * Size of the margin to be applied to the component ('xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge'). * You can pass an object with 'top', 'bottom', 'left' and 'right' properties in order to specify different margin sizes. */ margin?: Space | Margin; /** - * Value of the tabindex attribute. + * Contains one or more accordion items. */ - tabIndex?: number; + children: ReactElement<AccordionItemProps>[] | ReactElement<AccordionItemProps>; +}; + +type IndependentProps = CommonProps & { + /** + * When true, limits the user to single-open section at a time. When false, multiple sections can be opened simultaneously. + */ + independent: true; + /** + * Initially active accordion, only when it is uncontrolled.If the accordion is not independent, + * several accordions can be activated by default. + */ + defaultIndexActive?: number; + /** + * The index of the active accordion. If undefined, the component will be uncontrolled and the active + * accordion will be managed internally by the component. If null, the component will be controlled and all + * accordions will be closed. If the accordion is not independent, several accordions can be activated. + */ + indexActive?: number; + /** + * This function will be called when the user clicks on an accordion. The index of the clicked accordion will be passed as a parameter. + */ + onActiveChange?: (index: number) => void; +}; + +type NonIndependentProps = CommonProps & { + /** + * When true, limits the user to single-open section at a time. When false, multiple sections can be opened simultaneously. + */ + independent?: false; + /** + * Initially active accordion, only when it is uncontrolled. If the accordion is not independent, + * several accordions can be activated by default. + */ + defaultIndexActive?: number[]; + /** + * The index of the active accordion. If undefined, the component will be uncontrolled and the active + * accordion will be managed internally by the component. If null, the component will be controlled and all + * accordions will be closed. If the accordion is not independent, several accordions can be activated. + */ + indexActive?: number[]; + /** + * This function will be called when the user clicks on an accordion. The index of the clicked accordion will be passed as a parameter. + */ + onActiveChange?: (index: number[]) => void; +}; + +type Props = IndependentProps | NonIndependentProps; + +export type AccordionContextProps = { + index: number; + activeIndex?: number | number[]; + handlerActiveChange?: (index: number | number[]) => void; + independent?: boolean; }; export default Props; diff --git a/packages/lib/src/badge/types.tsx b/packages/lib/src/badge/types.ts similarity index 100% rename from packages/lib/src/badge/types.tsx rename to packages/lib/src/badge/types.ts diff --git a/packages/lib/src/common/variables.ts b/packages/lib/src/common/variables.ts index a7349d1833..feb9a657c5 100644 --- a/packages/lib/src/common/variables.ts +++ b/packages/lib/src/common/variables.ts @@ -4,45 +4,43 @@ export const componentTokens = { accordion: { backgroundColor: CoreTokens.color_white, hoverBackgroundColor: CoreTokens.color_purple_100, + focusBackgroundColor: CoreTokens.color_transparent, + activeBackgroundColor: CoreTokens.color_purple_100, arrowColor: CoreTokens.color_purple_700, disabledArrowColor: CoreTokens.color_grey_500, + subLabelFontFamily: CoreTokens.type_sans, + subLabelFontSize: CoreTokens.type_scale_01, + subLabelFontWeight: CoreTokens.type_regular, + subLabelFontStyle: CoreTokens.type_normal, + subLabelFontColor: CoreTokens.color_grey_700, + disabledSubLabelFontColor: CoreTokens.color_grey_500, assistiveTextFontFamily: CoreTokens.type_sans, - assistiveTextFontSize: CoreTokens.type_scale_03, - assistiveTextFontWeight: CoreTokens.type_light, - assistiveTextFontStyle: CoreTokens.type_italic, - assistiveTextLetterSpacing: CoreTokens.type_spacing_wide_01, + assistiveTextFontSize: CoreTokens.type_scale_01, + assistiveTextFontWeight: CoreTokens.type_regular, + assistiveTextFontStyle: CoreTokens.type_normal, assistiveTextFontColor: CoreTokens.color_grey_700, disabledAssistiveTextFontColor: CoreTokens.color_grey_500, - assistiveTextMinWidth: "100px", - assistiveTextPaddingRight: CoreTokens.spacing_24, - assistiveTextPaddingLeft: CoreTokens.spacing_0, titleLabelFontFamily: CoreTokens.type_sans, titleLabelFontSize: CoreTokens.type_scale_03, titleLabelFontWeight: CoreTokens.type_regular, titleLabelFontStyle: CoreTokens.type_normal, - titleLabelFontColor: CoreTokens.color_black, + titleLabelFontColor: CoreTokens.color_grey_900, disabledTitleLabelFontColor: CoreTokens.color_grey_500, - titleLabelPaddingTop: CoreTokens.spacing_0, - titleLabelPaddingBottom: CoreTokens.spacing_0, - titleLabelPaddingLeft: CoreTokens.spacing_0, - titleLabelPaddingRight: CoreTokens.spacing_16, focusBorderColor: CoreTokens.color_blue_600, focusBorderStyle: CoreTokens.border_solid, focusBorderThickness: "2px", borderRadius: "4px", boxShadowOffsetX: "0px", - boxShadowOffsetY: "6px", - boxShadowBlur: "10px", + boxShadowOffsetY: "12px", + boxShadowBlur: "12px", + boxShadowSpread: "0px", boxShadowColor: CoreTokens.color_grey_200_a, iconColor: CoreTokens.color_purple_700, disabledIconColor: CoreTokens.color_grey_500, iconSize: "24px", - iconMarginLeft: CoreTokens.spacing_0, - iconMarginRight: CoreTokens.spacing_12, - accordionGroupSeparatorBorderColor: CoreTokens.color_grey_200_a, - accordionGroupSeparatorBorderThickness: "1px", - accordionGroupSeparatorBorderRadius: "0px", - accordionGroupSeparatorBorderStyle: CoreTokens.border_solid, + accordionSeparatorBorderColor: CoreTokens.color_grey_200, + accordionSeparatorBorderThickness: "1px", + accordionSeparatorBorderStyle: CoreTokens.border_solid, }, alert: { errorBackgroundColor: CoreTokens.color_red_100, @@ -63,7 +61,7 @@ export const componentTokens = { successIconColor: CoreTokens.color_green_700, warningBackgroundColor: CoreTokens.color_yellow_100, warningIconColor: CoreTokens.color_yellow_700, - }, + }, bulletedList: { fontColor: CoreTokens.color_black, bulletIconHeight: "1.5rem", @@ -1285,6 +1283,7 @@ export type OpinionatedTheme = { accordion: { accentColor: string; titleFontColor: string; + subLabelFontColor: string; assistiveTextFontColor: string; }; alert: { diff --git a/packages/lib/src/index.ts b/packages/lib/src/index.ts index fd0fc86ca5..b973d43510 100644 --- a/packages/lib/src/index.ts +++ b/packages/lib/src/index.ts @@ -2,7 +2,6 @@ import "./styles/fonts.css"; // import "./styles/variables.css"; export { default as DxcAccordion } from "./accordion/Accordion"; -export { default as DxcAccordionGroup } from "./accordion-group/AccordionGroup"; export { default as DxcAlert } from "./alert/Alert"; export { default as DxcApplicationLayout } from "./layout/ApplicationLayout"; export { default as DxcBadge } from "./badge/Badge";