diff --git a/apps/website/pages/components/wizard/code.tsx b/apps/website/pages/components/wizard/code.tsx new file mode 100644 index 0000000000..f3430b8f11 --- /dev/null +++ b/apps/website/pages/components/wizard/code.tsx @@ -0,0 +1,17 @@ +import Head from "next/head"; +import type { ReactElement } from "react"; +import WizardPageLayout from "screens/components/wizard/WizardPageLayout"; +import WizardCodePage from "screens/components/wizard/code/WizardCodePage"; + +const Code = () => ( + <> + + Wizard code — Halstack Design System + + + +); + +Code.getLayout = (page: ReactElement) => {page}; + +export default Code; diff --git a/apps/website/pages/components/wizard/index.tsx b/apps/website/pages/components/wizard/index.tsx index f8f803289e..d4e5c6f311 100644 --- a/apps/website/pages/components/wizard/index.tsx +++ b/apps/website/pages/components/wizard/index.tsx @@ -1,21 +1,17 @@ import Head from "next/head"; import type { ReactElement } from "react"; import WizardPageLayout from "screens/components/wizard/WizardPageLayout"; -import WizardCodePage from "screens/components/wizard/code/WizardCodePage"; +import WizardOverviewPage from "screens/components/wizard/overview/WizardOverviewPage"; -const Index = () => { - return ( - <> - - Wizard — Halstack Design System - - - - ); -}; +const Index = () => ( + <> + + Wizard — Halstack Design System + + + +); -Index.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; +Index.getLayout = (page: ReactElement) => {page}; export default Index; diff --git a/apps/website/pages/components/wizard/specifications.tsx b/apps/website/pages/components/wizard/specifications.tsx deleted file mode 100644 index 310adc86f0..0000000000 --- a/apps/website/pages/components/wizard/specifications.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Head from "next/head"; -import type { ReactElement } from "react"; -import WizardPageLayout from "screens/components/wizard/WizardPageLayout"; -import WizardSpecsPage from "screens/components/wizard/specs/WizardSpecsPage"; - -const Specifications = () => { - return ( - <> - - Wizard Specs — Halstack Design System - - - - ); -}; - -Specifications.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default Specifications; diff --git a/apps/website/pages/components/wizard/usage.tsx b/apps/website/pages/components/wizard/usage.tsx deleted file mode 100644 index 551fd09e34..0000000000 --- a/apps/website/pages/components/wizard/usage.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Head from "next/head"; -import type { ReactElement } from "react"; -import WizardPageLayout from "screens/components/wizard/WizardPageLayout"; -import WizardUsagePage from "screens/components/wizard/usage/WizardUsagePage"; - -const Usage = () => { - return ( - <> - - Wizard Usage — Halstack Design System - - - - ); -}; - -Usage.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default Usage; diff --git a/apps/website/screens/components/wizard/WizardPageLayout.tsx b/apps/website/screens/components/wizard/WizardPageLayout.tsx index fef897a481..b296958789 100644 --- a/apps/website/screens/components/wizard/WizardPageLayout.tsx +++ b/apps/website/screens/components/wizard/WizardPageLayout.tsx @@ -6,9 +6,8 @@ import { ReactNode } from "react"; const WizardPageHeading = ({ children }: { children: ReactNode }) => { const tabs = [ - { label: "Code", path: "/components/wizard" }, - { label: "Usage", path: "/components/wizard/usage" }, - { label: "Specifications", path: "/components/wizard/specifications" }, + { label: "Overview", path: "/components/wizard" }, + { label: "Code", path: "/components/wizard/code" }, ]; return ( @@ -21,7 +20,7 @@ const WizardPageHeading = ({ children }: { children: ReactNode }) => { with several bullet points where the user need to interact with the content of each step during the workflow. - + {children} diff --git a/apps/website/screens/components/wizard/code/WizardCodePage.tsx b/apps/website/screens/components/wizard/code/WizardCodePage.tsx index acb4d9f9bb..e06612bbba 100644 --- a/apps/website/screens/components/wizard/code/WizardCodePage.tsx +++ b/apps/website/screens/components/wizard/code/WizardCodePage.tsx @@ -7,9 +7,19 @@ import Example from "@/common/example/Example"; import controlled from "./examples/controlled"; import uncontrolled from "./examples/uncontrolled"; import icons from "./examples/icons"; -import TableCode from "@/common/TableCode"; +import TableCode, { ExtendedTableCode } from "@/common/TableCode"; import StatusBadge from "@/common/StatusBadge"; +const stepsType = `{ + label: string; + description?: string; + disabled?: boolean; + icon?: string | + (React.ReactNode & + React.SVGProps); + valid?: boolean; +}[]`; + const sections = [ { title: "Props", @@ -24,6 +34,17 @@ const sections = [ + + currentStep + + number + + + Defines which step is marked as the current. The numeration starts at 0. If undefined, the component will + be uncontrolled and the step will be managed internally by the component. + + - + defaultCurrentStep @@ -35,13 +56,13 @@ const sections = [ - currentStep + margin - number + 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge' | Margin - Defines which step is marked as the current. The numeration starts at 0. If undefined, the component will - be uncontrolled and the step will be managed internally by the component. + 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. - @@ -55,6 +76,16 @@ const sections = [ 'horizontal' + + onStepClick + + {"(currentStep: number) => void"} + + + This function will be called when the user clicks a step. The step number will be passed as a parameter. + + - + @@ -63,11 +94,7 @@ const sections = [ - - { - "{ label: string; description?: string; icon?: string | (React.ReactNode & React.SVGProps ); disabled?: boolean; valid?: boolean; }[]" - } - + {stepsType} An array of objects representing the steps. Each of them has the following properties: @@ -95,27 +122,6 @@ const sections = [ - - - onStepClick - - {"(currentStep: number) => void"} - - - This function will be called when the user clicks a step. The step number will be passed as a parameter. - - - - - - margin - - 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge' | Margin - - - 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. - - - - tabIndex @@ -151,15 +157,13 @@ const sections = [ }, ]; -const WizardCodePage = () => { - return ( - - - - - - - ); -}; +const WizardCodePage = () => ( + + + + + + +); export default WizardCodePage; diff --git a/apps/website/screens/components/wizard/code/examples/controlled.ts b/apps/website/screens/components/wizard/code/examples/controlled.ts index f8cbbba02c..8a129f92f3 100644 --- a/apps/website/screens/components/wizard/code/examples/controlled.ts +++ b/apps/website/screens/components/wizard/code/examples/controlled.ts @@ -19,7 +19,7 @@ const code = `() => { }, { label: "Policy", - valid: true, + valid: false, }, { label: "Payment", diff --git a/apps/website/screens/components/wizard/overview/WizardOverviewPage.tsx b/apps/website/screens/components/wizard/overview/WizardOverviewPage.tsx new file mode 100644 index 0000000000..1e7fc62694 --- /dev/null +++ b/apps/website/screens/components/wizard/overview/WizardOverviewPage.tsx @@ -0,0 +1,198 @@ +import { DxcParagraph, DxcBulletedList, DxcFlex } from "@dxc-technology/halstack-react"; +import QuickNavContainer from "@/common/QuickNavContainer"; +import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; +import DocFooter from "@/common/DocFooter"; +import Example from "@/common/example/Example"; +import description from "./examples/description"; +import anatomy from "./images/wizard_anatomy.png"; +import Image from "@/common/Image"; +import vertical from "./examples/vertical"; +import horizontal from "./examples/horizontal"; +import stepNavigation from "./examples/stepNavigation"; +import linearNonLinear from "./examples/linearNonLinear"; +import validation from "./examples/validation"; + +const sections = [ + { + title: "Introduction", + content: ( + + The wizard component helps users navigate through multi-step processes in a structured and guided manner. It + breaks down complex workflows into manageable steps, improving clarity and reducing cognitive load.{" "} + Each step represents a key stage of the process, allowing users to track progress and move + forward or backward as needed. Wizards are commonly used in forms, onboarding flows, and setup configurations + where sequential input is required. + + ), + }, + { + title: "Anatomy", + content: ( + <> + Wizard's anatomy + + + Step: represents an individual stage in the wizard navigation. Each step corresponds to a + specific section of a multi-step process, guiding the user progressively through a flow. + + + Label: a descriptive title for the step that provides users with a clear understanding of + the content or action required at that stage. + + + Separator: a visual connector between steps that indicates the sequence and relationship + between them, reinforcing the step-by-step progression. + + + Step validator: a status indicator (checkmark or error icon) that provides feedback on + whether the step has been successfully completed or requires attention. + + + Description: an optional text displayed alongside a step to offer additional guidance, + clarifications, or instructions, ensuring users understand the requirements before proceeding. + + + + ), + }, + { + title: "Key interactions and features", + content: ( + + To better understand the wizard component and its possible use cases when working with a form, it's important to + review its key characteristics and interactions. + + ), + subSections: [ + { + title: "Step navigation", + content: ( + <> + + Users progress through the wizard by clicking on steps, using navigation buttons, or interacting with the + interface if direct access to steps is enabled. Navigation can be controlled based on the form's + requirements, preventing users from skipping essential steps when necessary. + + + + ), + }, + { + title: "Linear vs. Non-Linear Flow", + content: ( + <> + + Wizards can follow a linear approach, where users must complete each step in sequence + before proceeding, or a non-linear approach, which allows free movement between steps. + The choice depends on the complexity of the process and whether dependencies exist between steps. + + + + ), + }, + { + title: "Step validation", + content: ( + <> + + There may be scenarios where the content of a step needs to be validated while the user is filling in + fields or performing relevant actions within each step of the wizard. In such cases, a validation mark can + be displayed on each step once the user progresses to the next one. This mark will indicate the validation + status of the content, showing either a success or error mark on the step. + + + + ), + }, + { + title: "Optional description and icons", + content: ( + <> + + Each step can include description or validation icons to provide context and feedback. + + + + ), + }, + ], + }, + { + title: "Variants", + content: ( + + The wizard component has two variants depending on its orientation: horizontal and vertical. + + ), + subSections: [ + { + title: "Horizontal", + content: ( + <> + + The horizontal variant of the wizard is suitable for workflows where space is available + horizontally, making it ideal for tasks like multi-step forms and progress tracking. For example, setting + up a user account with several steps. + + + + ), + }, + { + title: "Vertical", + content: ( + <> + + The vertical variant works well when horizontal space is limited, often used in scenarios + like long forms or detailed steps in a process. An example could be an onboarding process with in-depth + configuration options. + + + + ), + }, + ], + }, + { + title: "Best practices", + content: ( + + + Keep steps clear and logical: ensure that each step represents a meaningful part of the + process, following a natural order that users can easily follow. + + + Use concise and descriptive labels: step labels should clearly indicate the purpose of each + step to help users understand where they are in the process. + + + Provide real-time validation: if validation is required before moving forward, display clear + and immediate error messages to help users correct mistakes. + + + Indicate progress visually: use clear indicators to show the user's current step, completed + steps, and upcoming steps to enhance orientation and usability. + + + Confirm before final submission: if the wizard leads to an irreversible action (e.g., + submitting an application), provide a review step where users can check and edit their inputs. + + + Choose the right layout: use the horizontal variant for processes with a small number of + steps and the vertical variant for complex flows with long step descriptions. + + + ), + }, +]; + +const WizardOverviewPage = () => ( + + + + + + +); + +export default WizardOverviewPage; diff --git a/apps/website/screens/components/wizard/overview/examples/description.ts b/apps/website/screens/components/wizard/overview/examples/description.ts new file mode 100644 index 0000000000..b20c64bbef --- /dev/null +++ b/apps/website/screens/components/wizard/overview/examples/description.ts @@ -0,0 +1,34 @@ +import { DxcWizard, DxcInset } from "@dxc-technology/halstack-react"; + +const code = `() => { + return ( + + + + ); +}`; + +const scope = { + DxcWizard, + DxcInset, +}; + +export default { code, scope }; diff --git a/apps/website/screens/components/wizard/overview/examples/horizontal.ts b/apps/website/screens/components/wizard/overview/examples/horizontal.ts new file mode 100644 index 0000000000..acf2d8e007 --- /dev/null +++ b/apps/website/screens/components/wizard/overview/examples/horizontal.ts @@ -0,0 +1,23 @@ +import { DxcWizard, DxcInset } from "@dxc-technology/halstack-react"; + +const code = `() => { + return ( + + + + ); +}`; + +const scope = { + DxcWizard, + DxcInset, +}; + +export default { code, scope }; diff --git a/apps/website/screens/components/wizard/overview/examples/linearNonLinear.ts b/apps/website/screens/components/wizard/overview/examples/linearNonLinear.ts new file mode 100644 index 0000000000..3af9696e8b --- /dev/null +++ b/apps/website/screens/components/wizard/overview/examples/linearNonLinear.ts @@ -0,0 +1,23 @@ +import { DxcWizard, DxcInset } from "@dxc-technology/halstack-react"; + +const code = `() => { + return ( + + + + ); +}`; + +const scope = { + DxcWizard, + DxcInset, +}; + +export default { code, scope }; diff --git a/apps/website/screens/components/wizard/overview/examples/stepNavigation.ts b/apps/website/screens/components/wizard/overview/examples/stepNavigation.ts new file mode 100644 index 0000000000..7359d726d8 --- /dev/null +++ b/apps/website/screens/components/wizard/overview/examples/stepNavigation.ts @@ -0,0 +1,28 @@ +import { DxcWizard, DxcInset } from "@dxc-technology/halstack-react"; + +const code = `() => { + return ( + + + + ); +}`; + +const scope = { + DxcWizard, + DxcInset, +}; + +export default { code, scope }; diff --git a/apps/website/screens/components/wizard/usage/examples/description.ts b/apps/website/screens/components/wizard/overview/examples/validation.ts similarity index 51% rename from apps/website/screens/components/wizard/usage/examples/description.ts rename to apps/website/screens/components/wizard/overview/examples/validation.ts index 6ca9a1946d..a3c83a6dae 100644 --- a/apps/website/screens/components/wizard/usage/examples/description.ts +++ b/apps/website/screens/components/wizard/overview/examples/validation.ts @@ -4,23 +4,21 @@ const code = `() => { return ( + /> ); }`; diff --git a/apps/website/screens/components/wizard/overview/examples/vertical.ts b/apps/website/screens/components/wizard/overview/examples/vertical.ts new file mode 100644 index 0000000000..006cec61bb --- /dev/null +++ b/apps/website/screens/components/wizard/overview/examples/vertical.ts @@ -0,0 +1,24 @@ +import { DxcWizard, DxcInset } from "@dxc-technology/halstack-react"; + +const code = `() => { + return ( + + + + ); +}`; + +const scope = { + DxcWizard, + DxcInset, +}; + +export default { code, scope }; diff --git a/apps/website/screens/components/wizard/overview/images/wizard_anatomy.png b/apps/website/screens/components/wizard/overview/images/wizard_anatomy.png new file mode 100644 index 0000000000..aca827a69c Binary files /dev/null and b/apps/website/screens/components/wizard/overview/images/wizard_anatomy.png differ diff --git a/apps/website/screens/components/wizard/specs/WizardSpecsPage.tsx b/apps/website/screens/components/wizard/specs/WizardSpecsPage.tsx deleted file mode 100644 index 8ca0081969..0000000000 --- a/apps/website/screens/components/wizard/specs/WizardSpecsPage.tsx +++ /dev/null @@ -1,761 +0,0 @@ -import { DxcTable, DxcParagraph, DxcBulletedList, DxcFlex, DxcLink } from "@dxc-technology/halstack-react"; -import Image from "@/common/Image"; -import QuickNavContainer from "@/common/QuickNavContainer"; -import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; -import Figure from "@/common/Figure"; -import Code from "@/common/Code"; -import DocFooter from "@/common/DocFooter"; -import spacingImage from "./images/wizard_specs_spacing.png"; -import stepperImage from "./images/wizard_specs_stepper.png"; -import statesImage from "./images/wizard_states.png"; -import anatomyImage from "./images/wizard_anatomy.png"; - -const sections = [ - { - title: "Specifications", - content: ( - <> -
- Wizard step design specifications -
-
- Wizard variants design specifications -
- - ), - }, - { - title: "States", - content: ( - <> - - States: enabled, focus, selected,{" "} - unvisited and disabled. - -
- Wizard states -
- - ), - }, - { - title: "Anatomy", - content: ( - <> - Wizard anatomy - - Step - Label - Separator - - Helper text (Optional) - - - Step validator (Optional) - - - - ), - }, - { - title: "Design tokens", - subSections: [ - { - title: "Color", - content: ( - - - - Component token - Element - Core token - Value - - - - - - visitedStepFontColor - - Step:visited text - - color-black - - #000000 - - - - selectedStepFontColor - - Step:selected text - - color-white - - #ffffff - - - - unvisitedStepFontColor - - Step:unvisited text - - color-grey-700 - - #666666 - - - - disabledStepFontColor - - Step:disabled text - - color-grey-500 - - #999999 - - - - visitedStepBackgroundColor - - Step:visited - - color-white - - #ffffff - - - - selectedStepBackgroundColor - - Step:selected background - - color-purple-700 - - #5f249f - - - - unvisitedStepBackgroundColor - - Step:unvisited background - - color-transparent - - transparent - - - - disabledStepBackgroundColor - - Step:disabled background - - color-grey-100 - - #f2f2f2 - - - - visitedStepBorderColor - - Step:visited border - - color-black - - #000000 - - - - selectedStepBorderColor - - Step:selected border - - color-purple-700 - - #5f249f - - - - unvisitedStepBorderColor - - Step:unvisited border - - color-grey-700 - - #666666 - - - - disabledStepBorderColor - - Step:disabled border - - color-grey-100 - - #f2f2f2 - - - - visitedLabelFontColor - - Label:visited - - color-black - - #000000 - - - - selectedLabelFontColor - - Label:selected - - color-black - - #000000 - - - - disabledLabelFontColor - - Label:disabled - - color-grey-500 - - #999999 - - - - visitedHelperTextFontColor - - Helper text:visited - - color-black - - #000000 - - - - selectedHelperTextFontColor - - Helper text:selected - - color-black - - #000000 - - - - separatorColor - - Separator - - color-grey-700 - - #666666 - - - - focusColor - - Focus outline - - color-blue-600 - - #0095ff - - - - ), - }, - { - title: "Margin", - content: ( - <> - - Different values can be applied to each side of the component: - top, bottom, left, right. - - - - - Margin - Value - - - - - - xxsmall - - 6px - - - - xsmall - - 16px - - - - small - - 24px - - - - medium - - 36px - - - - large - - 48px - - - - xlarge - - 64px - - - - xxlarge - - 100px - - - - - ), - }, - { - title: "Typography", - content: ( - - - - Component token - Element - Core token - Value - - - - - - stepFontSize - - Step text - - font-scale-03 - - 1rem / 16px - - - - stepFontFamily - - Step text - - font-family-sans - - Open Sans - - - - stepFontStyle - - Step text - - font-style-normal - - normal - - - - stepFontWeight - - Step text - - font-style-regular - - 400 - - - - stepFontTracking - - Step text - - font-tracking-wide-02 - - 0.05em - - - - labelFontSize - - Label - - font-scale-03 - - 1rem / 16px - - - - labelFontFamily - - Label - - font-family-sans - - Open Sans - - - - labelFontStyle - - Label - - font-style-normal - - normal - - - - labelFontWeight - - Label - - font-style-regular - - 400 - - - - labelFontTracking - - Label - - font-tracking-normal - - 0em - - - - helperTextFontSize - - Helper text - - font-scale-02 - - 0.875rem / 14px - - - - helperTextFontFamily - - Helper text - - font-family-sans - - Open Sans - - - - helperTextFontStyle - - Helper text - - font-style-normal - - normal - - - - helperTextFontWeight - - Helper text - - font-style-regular - - 400 - - - - helperTextFontTracking - - Helper text - - font-tracking-normal - - 0em - - - - ), - }, - { - title: "Border", - content: ( - - - - Component token - Element - Core token - Value - - - - - - stepBorderStyle - - Step border - - border-style-solid - - solid - - - - stepBorderWidth - - Step border - - border-width-2 - - 2px - - - - selectedStepBorderWidth - - Step border:selected - - border-width-2 - - 2px - - - - disabledStepBorderWidth - - Step border:disabled - - border-width-2 - - 2px - - - - stepBorderWidth - - Step border-radius - - border-radius-full - - 9999px - - - - separatorBorderWidth - - Separator - - border-width-1 - - 1px - - - - separatorBorderStyle - - Separator - - border-style-solid - - solid - - - - ), - }, - { - title: "Size", - content: ( - - - - Property - Element - Core token - Value - - - - - - width - - Step - - - 32px - - - - height - - Step - - - 32px - - - - ), - }, - { - title: "Spacing", - content: ( - - - - Property - Element - Core token - Value - - - - - - margin-left - - Label - - spacing-12 - - 0.75rem / 12px - - - - margin-left - - Step container - - spacing-24 - - 1.5rem / 24px - - - - margin-right - - Step container - - spacing-24 - - 1.5rem / 24px - - - - ), - }, - { - title: "Iconography", - content: ( - - - - Property - Element - Value - - - - - - height/ width - - Custom icon - 20/20px - - - - height/ width - - Validation icon - 18/18px - - - - ), - }, - ], - }, - { - title: "Accessibility", - subSections: [ - { - title: "WCAG 2.2", - content: ( - - - Understanding WCAG 2.2 -{" "} - - SC 1.3.1: Info and Relationships - - - - Understanding WCAG 2.2 -{" "} - - SC 2.2.1: Timing Adjustable - - - - Understanding WCAG 2.2 -{" "} - - SC 2.2.2: Pause, Stop, Hide - - - - Understanding WCAG 2.2 -{" "} - - SC 3.3.2: Labels or Instructions - - - - ), - }, - { - title: "WAI-ARIA", - content: ( - - - Web accessibility tutorials -{" "} - - Multi-page forms - - - - ), - }, - ], - }, -]; - -const WizardSpecsPage = () => { - return ( - - - - - - - ); -}; - -export default WizardSpecsPage; diff --git a/apps/website/screens/components/wizard/specs/images/wizard_anatomy.png b/apps/website/screens/components/wizard/specs/images/wizard_anatomy.png deleted file mode 100644 index 6534dfc97e..0000000000 Binary files a/apps/website/screens/components/wizard/specs/images/wizard_anatomy.png and /dev/null differ diff --git a/apps/website/screens/components/wizard/specs/images/wizard_specs_spacing.png b/apps/website/screens/components/wizard/specs/images/wizard_specs_spacing.png deleted file mode 100644 index 956567e9e9..0000000000 Binary files a/apps/website/screens/components/wizard/specs/images/wizard_specs_spacing.png and /dev/null differ diff --git a/apps/website/screens/components/wizard/specs/images/wizard_specs_stepper.png b/apps/website/screens/components/wizard/specs/images/wizard_specs_stepper.png deleted file mode 100644 index 659e964e5a..0000000000 Binary files a/apps/website/screens/components/wizard/specs/images/wizard_specs_stepper.png and /dev/null differ diff --git a/apps/website/screens/components/wizard/specs/images/wizard_states.png b/apps/website/screens/components/wizard/specs/images/wizard_states.png deleted file mode 100644 index 48d2993438..0000000000 Binary files a/apps/website/screens/components/wizard/specs/images/wizard_states.png and /dev/null differ diff --git a/apps/website/screens/components/wizard/usage/WizardUsagePage.tsx b/apps/website/screens/components/wizard/usage/WizardUsagePage.tsx deleted file mode 100644 index 3cb16d962e..0000000000 --- a/apps/website/screens/components/wizard/usage/WizardUsagePage.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { DxcParagraph, DxcBulletedList, DxcFlex } from "@dxc-technology/halstack-react"; -import QuickNavContainer from "@/common/QuickNavContainer"; -import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; -import DocFooter from "@/common/DocFooter"; -import Example from "@/common/example/Example"; -import variants from "./examples/variants"; -import validation from "./examples/validation"; -import content from "./examples/content"; -import description from "./examples/description"; - -const sections = [ - { - title: "Usage", - content: ( - - - The horizontal/vertical line should not extend to the left of the first circle or to the right of the last - circle. - - - Do not overwhelmed the component with too many steps, it won't be a good idea for the user workflow neither - for the display of the information. - - - ), - }, - { - title: "Variants", - content: ( - <> - - The wizard component has two variants: horizontal and vertical. - - - - ), - }, - { - title: "Validation", - content: ( - <> - - There could be some scenarios in which the content of a step wants to be validated while the user is filling - the fields with information or doing relevant actions in every step of the wizard. For that case, a validation - mark can be represented in every step once the user navigate to the next step in the linear progression. This - will represent the status of the validation respecting the content, with a success mark or an error mark - visible on the step mark. - - - - ), - }, - { - title: "Content", - content: ( - <> - Different variations of the content can be performed in the step: - - Only numbers - Only icons - Numbers/icons with step label - - - - ), - }, - { - title: "Description", - content: ( - <> - Description can be added to the wizard component step: - - - ), - }, -]; - -const WizardUsagePage = () => { - return ( - - - - - - - ); -}; - -export default WizardUsagePage; diff --git a/apps/website/screens/components/wizard/usage/examples/content.ts b/apps/website/screens/components/wizard/usage/examples/content.ts deleted file mode 100644 index 5d16984bda..0000000000 --- a/apps/website/screens/components/wizard/usage/examples/content.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { DxcWizard, DxcInset } from "@dxc-technology/halstack-react"; - -const code = `() => { - const userIcon = ( - - - - - ); - - return ( - - - - ); -}`; - -const scope = { - DxcWizard, - DxcInset, -}; - -export default { code, scope }; diff --git a/apps/website/screens/components/wizard/usage/examples/validation.ts b/apps/website/screens/components/wizard/usage/examples/validation.ts deleted file mode 100644 index 71c6c7964e..0000000000 --- a/apps/website/screens/components/wizard/usage/examples/validation.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { DxcWizard, DxcInset, DxcFlex } from "@dxc-technology/halstack-react"; - -const code = `() => { - return ( - - - - - - - ); -}`; - -const scope = { - DxcWizard, - DxcInset, - DxcFlex, -}; - -export default { code, scope }; diff --git a/apps/website/screens/components/wizard/usage/examples/variants.ts b/apps/website/screens/components/wizard/usage/examples/variants.ts deleted file mode 100644 index 218247ea1b..0000000000 --- a/apps/website/screens/components/wizard/usage/examples/variants.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { DxcWizard, DxcInset, DxcFlex } from "@dxc-technology/halstack-react"; - -const code = `() => { - return ( - - - - - - - ); -}`; - -const scope = { - DxcWizard, - DxcInset, - DxcFlex, -}; - -export default { code, scope }; diff --git a/packages/lib/src/wizard/Icons.tsx b/packages/lib/src/wizard/Icons.tsx index 34398df76c..d67c5866fb 100644 --- a/packages/lib/src/wizard/Icons.tsx +++ b/packages/lib/src/wizard/Icons.tsx @@ -1,36 +1,17 @@ const icons = { valid: ( - - + - ), invalid: ( - - - + ), diff --git a/packages/lib/src/wizard/Wizard.stories.tsx b/packages/lib/src/wizard/Wizard.stories.tsx index fcf3ca7902..5271e88c31 100644 --- a/packages/lib/src/wizard/Wizard.stories.tsx +++ b/packages/lib/src/wizard/Wizard.stories.tsx @@ -1,9 +1,9 @@ import { userEvent, within } from "@storybook/test"; import ExampleContainer from "../../.storybook/components/ExampleContainer"; import Title from "../../.storybook/components/Title"; -import { HalstackProvider } from "../HalstackContext"; import DxcWizard from "./Wizard"; import { Meta, StoryObj } from "@storybook/react"; +import DxcContainer from "../container/Container"; export default { title: "Wizard", @@ -63,17 +63,17 @@ const stepWithLongDescription = [ { label: "First step", description: - "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. ", + "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.", }, { label: "Second step", description: - "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. ", + "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.", }, { label: "Third step", description: - "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. ", + "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.", }, ]; @@ -148,133 +148,95 @@ const stepMaterialSymbols = [ }, ]; -const opinionatedTheme = { - wizard: { - baseColor: "#5f249f", - fontColor: "#000000", - selectedStepFontColor: "#ffffff", +const stepsDifferentLabelLengths = [ + { + label: "Billing information", }, -}; + { + label: "Payment", + }, + { + label: "Confirm details", + }, + { + label: "Review & submit", + }, +]; const Wizard = () => ( <> - + - - + + + + + + + + - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - + - + - + - + - + - + - + - - - - - - - + ); -const WizardSelected = () => ( - - - - -); - type Story = StoryObj; export const Chromatic: Story = { render: Wizard, }; - -export const WizardStepActived: Story = { - render: WizardSelected, - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const option = canvas.getByText("Third step"); - await userEvent.click(option); - }, -}; diff --git a/packages/lib/src/wizard/Wizard.tsx b/packages/lib/src/wizard/Wizard.tsx index 5e50e4dece..b31e5b1971 100644 --- a/packages/lib/src/wizard/Wizard.tsx +++ b/packages/lib/src/wizard/Wizard.tsx @@ -1,303 +1,230 @@ -import { useContext, useMemo, useState } from "react"; -import styled, { ThemeProvider } from "styled-components"; +import { useState } from "react"; +import styled from "styled-components"; import { spaces } from "../common/variables"; +import DxcDivider from "../divider/Divider"; import DxcIcon from "../icon/Icon"; -import HalstackContext from "../HalstackContext"; import WizardPropsType, { StepProps } from "./types"; +import DxcFlex from "../flex/Flex"; import icons from "./Icons"; -const StepsContainer = styled.div<{ - mode: WizardPropsType["mode"]; +const Wizard = styled.div<{ margin: WizardPropsType["margin"]; + mode: WizardPropsType["mode"]; }>` display: flex; - flex-direction: ${(props) => (props.mode === "vertical" ? "column" : "row")}; + flex-direction: ${({ mode }) => (mode === "vertical" ? "column" : "row")}; justify-content: center; - ${(props) => props.mode === "vertical" && "height: 500px"}; - font-family: ${(props) => props.theme.fontFamily}; - 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] : ""}; + ${({ mode }) => mode === "vertical" && "height: 100%; width: fit-content;"}; + margin: ${({ margin }) => (margin && typeof margin !== "object" ? spaces[margin] : "")}; + margin-top: ${({ margin }) => (margin && typeof margin === "object" && margin.top ? spaces[margin.top] : "")}; + margin-right: ${({ margin }) => (margin && typeof margin === "object" && margin.right ? spaces[margin.right] : "")}; + margin-bottom: ${({ margin }) => + margin && typeof margin === "object" && margin.bottom ? spaces[margin.bottom] : ""}; + margin-left: ${({ margin }) => (margin && typeof margin === "object" && margin.left ? spaces[margin.left] : "")}; `; const StepContainer = styled.div<{ - mode: WizardPropsType["mode"]; lastStep: boolean; -}>` - display: inline-flex; - ${(props) => props.mode !== "vertical" && "align-items: center;"} - flex-grow: ${(props) => (props.lastStep ? "0" : "1")}; - flex-direction: ${(props) => (props.mode === "vertical" ? "column" : "row")}; - ${(props) => props.mode === "vertical" && "width: fit-content;"} -`; - -const Step = styled.button<{ mode: WizardPropsType["mode"]; - disabled: StepProps["disabled"]; - first: boolean; - last: boolean; }>` - display: flex; - justify-content: flex-start; - align-items: center; - gap: 0.75rem; - border: none; - border-radius: 0.25rem; - background: inherit; - margin: ${(props) => - props.first - ? props.mode === "vertical" - ? "0 0 24px 0" - : "0 24px 0 0" - : props.last - ? props.mode === "vertical" - ? "24px 0 0 0" - : "0 0 0 24px" - : props.mode === "vertical" - ? "24px 0" - : "0 24px"}; - - padding: 0px; - ${(props) => (props.disabled ? "cursor: not-allowed" : "")}; - - &:hover { - ${(props) => (props.disabled ? "" : "cursor: pointer")}; - } - &:focus { - outline: 2px solid ${(props) => props.theme.focusColor}; - } -`; - -const StepHeader = styled.div<{ validityIcon: boolean }>` - position: relative; - display: inline-flex; - ${(props) => props.validityIcon && "padding-bottom: 4px;"} + flex-grow: ${({ lastStep }) => (lastStep ? "0" : "1")}; + display: grid; + ${({ mode }) => (mode === "horizontal" ? "grid-template-columns: auto 1fr;" : "grid-template-rows: auto 1fr;")} `; const IconContainer = styled.div<{ current: boolean; - visited: boolean; disabled: StepProps["disabled"]; + visited: boolean; }>` - width: ${(props) => - props.disabled - ? props.theme.disabledStepWidth - : props.current - ? props.theme.selectedStepWidth - : props.theme.stepWidth}; - height: ${(props) => - props.disabled - ? props.theme.disabledStepHeight - : props.current - ? props.theme.selectedStepHeight - : props.theme.stepHeight}; - - ${(props) => ` - ${ - props.disabled - ? `border: ${props.theme.disabledStepBorderThickness} ${props.theme.disabledStepBorderStyle} ${props.theme.disabledStepBorderColor};` - : props.current - ? `border: ${props.theme.selectedStepBorderThickness} ${props.theme.selectedStepBorderStyle} ${props.theme.selectedStepBorderColor};` - : props.visited - ? `border: ${props.theme.stepBorderThickness} ${props.theme.stepBorderStyle} ${props.theme.visitedStepBorderColor};` - : `border: ${props.theme.stepBorderThickness} ${props.theme.stepBorderStyle} ${props.theme.unvisitedStepBorderColor};` - } - background: ${ - props.disabled - ? `${props.theme.disabledStepBackgroundColor}` - : props.current - ? `${props.theme.selectedStepBackgroundColor}` - : !props.visited - ? `${props.theme.unvisitedStepBackgroundColor}` - : `${props.theme.visitedStepBackgroundColor}` - }; - `} - ${(props) => - props.disabled - ? `color: ${props.theme.disabledStepFontColor};` - : `color: ${ - props.current - ? props.theme.selectedStepFontColor - : !props.visited - ? props.theme.unvisitedStepFontColor - : props.theme.visitedStepFontColor - };`}; - - border-radius: ${(props) => - !props.current && !props.disabled - ? props.theme.stepBorderRadius - : props.current - ? props.theme.selectedStepBorderRadius - : props.disabled - ? props.theme.disabledStepBorderRadius - : ""}; - - display: flex; - justify-content: center; - align-items: center; - overflow: hidden; - font-size: ${(props) => props.theme.stepIconSize}; + box-sizing: border-box; + display: grid; + place-items: center; + border-radius: 50%; + border: var(--border-width-m) var(--border-style-default) var(--border-color-neutral-dark); + height: var(--height-m); + width: 32px; + font-size: var(--height-xxs); svg { - width: ${(props) => props.theme.stepIconSize}; - height: ${(props) => props.theme.stepIconSize}; + height: var(--height-xxs); + width: 16px; } `; -const Number = styled.p` - font-size: ${(props) => props.theme.stepFontSize}; - font-family: ${(props) => props.theme.stepFontFamily}; - font-style: ${(props) => props.theme.stepFontStyle}; - font-weight: ${(props) => props.theme.stepFontWeight}; - letter-spacing: ${(props) => props.theme.stepFontTracking}; - opacity: 1; - margin: 0px 0px 0px 1px; +const Number = styled.span` + color: var(--color-fg-neutral-dark); + font-family: var(--typography-font-family); + font-size: var(--typography-label-l); + font-weight: var(--typography-label-regular); `; -const ValidityIconContainer = styled.div` - width: 18px; - height: 18px; - position: absolute; - top: 22.5px; - left: 22.5px; +const Label = styled.span` + color: var(--color-fg-neutral-dark); + font-family: var(--typography-font-family); + font-size: var(--typography-label-l); + font-weight: var(--typography-label-regular); + white-space: nowrap; `; -const Label = styled.p<{ - current: boolean; - visited: boolean; - disabled: StepProps["disabled"]; +const Description = styled.span` + color: var(--color-fg-neutral-dark); + font-family: var(--typography-font-family); + font-size: var(--typography-helper-text-m); + font-weight: var(--typography-helper-text-regular); + text-align: left; +`; + +const Step = styled.button<{ + mode: WizardPropsType["mode"]; + unvisited: boolean; }>` - text-align: ${(props) => props.theme.labelTextAlign}; - font-family: ${(props) => props.theme.labelFontFamily}; - font-size: ${(props) => props.theme.labelFontSize}; - font-style: ${(props) => props.theme.labelFontStyle}; - font-weight: ${(props) => props.theme.labelFontWeight}; - letter-spacing: ${(props) => props.theme.labelFontTracking}; - ${(props) => - props.disabled - ? `color: ${props.theme.disabledLabelFontColor};` - : `color: ${ - !props.visited - ? props.theme.unvisitedLabelFontColor - : props.current - ? props.theme.selectedLabelFontColor - : props.theme.visitedLabelFontColor - };`}; - text-transform: ${(props) => props.theme.labelFontTextTransform}; - margin: 0; + display: flex; + align-items: center; + gap: var(--spacing-gap-s); + background-color: transparent; + border: none; + border-radius: var(--border-radius-s); + margin: ${({ mode }) => + mode === "horizontal" + ? "var(--spacing-padding-none) var(--spacing-padding-l)" + : "var(--spacing-padding-l) var(--spacing-padding-none)"}; + padding: var(--spacing-padding-none); + width: fit-content; + cursor: pointer; + + &[aria-current="step"] { + ${IconContainer} { + background-color: var(--color-bg-primary-strong); + border: none; + } + ${IconContainer}, ${Number} { + color: var(--color-fg-neutral-bright); + } + } + ${({ unvisited }) => + unvisited && + `${IconContainer} { + border-color: var(--border-color-neutral-strongest); + } + ${IconContainer}, ${Number}, ${Label}, ${Description} { + color: var(--color-fg-neutral-stronger); + } + `} + &:focus:enabled { + outline: var(--border-width-m) var(--border-style-default) var(--border-color-secondary-medium); + } + &:disabled { + cursor: not-allowed; + ${IconContainer} { + background-color: var(--color-bg-neutral-light); + border: none; + } + ${IconContainer}, ${Number}, ${Label}, ${Description} { + color: var(--color-fg-neutral-medium); + } + } `; -const Description = styled.p<{ - current: boolean; - visited: boolean; - disabled: StepProps["disabled"]; +const StepIndicator = styled.div<{ + hasValidityIcon: boolean; }>` - text-align: ${(props) => props.theme.helperTextTextAlign}; - font-family: ${(props) => props.theme.helperTextFontFamily}; - font-size: ${(props) => props.theme.helperTextFontSize}; - font-style: ${(props) => props.theme.helperTextFontStyle}; - font-weight: ${(props) => props.theme.helperTextFontWeight}; - letter-spacing: ${(props) => props.theme.helperTextFontTracking}; - text-transform: ${(props) => props.theme.helperTextFontTextTransform}; - ${(props) => - props.disabled - ? `color: ${props.theme.disabledHelperTextFontColor};` - : `color: ${ - !props.visited - ? props.theme.unvisitedHelperTextFontColor - : props.current - ? props.theme.selectedHelperTextFontColor - : props.theme.visitedHelperTextFontColor - };`}; - margin: 0; + position: relative; + height: ${({ hasValidityIcon }) => (hasValidityIcon ? "var(--height-l)" : "var(--height-m)")}; + width: ${({ hasValidityIcon }) => (hasValidityIcon ? "36px" : "32px")}; `; -const StepSeparator = styled.div<{ mode: WizardPropsType["mode"] }>` - ${(props) => (props.mode === "horizontal" ? "height: 0;" : "width: 0;")}; - ${(props) => props.mode === "vertical" && "margin: 0 18px;"} - border: ${(props) => - `${props.theme.separatorBorderStyle} ${props.theme.separatorBorderThickness} ${props.theme.separatorColor}`}; - opacity: 1; - flex-grow: 1; +const ValidityIconContainer = styled.div<{ disabled?: boolean; valid: boolean }>` + position: absolute; + bottom: 0; + right: 0; + display: flex; + border-radius: 50%; + ${({ disabled, valid }) => + disabled + ? valid + ? "background-color: var(--color-bg-success-lightest); color: var(--color-fg-success-lighter);" + : "background-color: var(--color-bg-error-lightest); color: var(--color-fg-error-lighter);" + : valid + ? "background-color: var(--color-bg-success-lighter); color: var(--color-fg-success-stronger);" + : "background-color: var(--color-bg-error-lighter); color: var(--color-fg-error-stronger);"} + svg { + width: 16px; + height: var(--height-xxs); + } `; -const DxcWizard = ({ - mode = "horizontal", - defaultCurrentStep = 0, +const DividerContainer = styled.div<{ mode: WizardPropsType["mode"] }>` + display: grid; + place-items: center; + ${({ mode }) => mode === "vertical" && "width: 32px"}; +`; + +export default function DxcWizard({ currentStep, + defaultCurrentStep = 0, + margin, + mode = "horizontal", onStepClick, steps, - margin, tabIndex = 0, -}: WizardPropsType): JSX.Element => { - const colorsTheme = useContext(HalstackContext); +}: WizardPropsType) { const [innerCurrent, setInnerCurrentStep] = useState(defaultCurrentStep); - const renderedCurrent = useMemo(() => currentStep ?? innerCurrent, [currentStep, innerCurrent]); - - const handleStepClick = (newValue: number) => { + const handleStepOnClick = (newValue: number) => { setInnerCurrentStep(newValue); onStepClick?.(newValue); }; return ( - - - {steps.map((step, i) => ( - - { - handleStepClick(i); - }} - disabled={step.disabled} - mode={mode} - first={i === 0} - last={i === steps.length - 1} - aria-current={renderedCurrent === i ? "step" : "false"} - tabIndex={tabIndex} - > - - - {step.icon ? ( - typeof step.icon === "string" ? ( - - ) : ( - step.icon - ) + + {steps.map((step, i) => ( + + { + handleStepOnClick(i); + }} + tabIndex={tabIndex} + unvisited={i > (currentStep ?? innerCurrent)} + > + + + {step.icon ? ( + typeof step.icon === "string" ? ( + ) : ( - {i + 1} - )} - - {step.valid != null && ( - {step.valid ? icons.valid : icons.invalid} + step.icon + ) + ) : ( + {i + 1} )} - - {(step.label || step.description) && ( -
- {step.label && ( - - )} - {step.description && ( - - {step.description} - - )} -
+ + {step.valid != null && ( + + {step.valid ? icons.valid : icons.invalid} + )} - - {i === steps.length - 1 ? "" : } - - ))} - - + + {(step.label || step.description) && ( + + {step.label && } + {step.description && {step.description}} + + )} + + {i !== steps.length - 1 && ( + + + + )} + + ))} + ); -}; - -export default DxcWizard; +}