-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6481 from Sage/FE-6278
feat(step-flow): add new component
- Loading branch information
Showing
16 changed files
with
1,574 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { Page } from "playwright-core"; | ||
import { | ||
STEP_FLOW_PROGRESS_INDICATOR, | ||
STEP_FLOW_CATEGORY_TEXT, | ||
STEP_FLOW_TITLE_TEXT_WRAPPER, | ||
STEP_FLOW_TITLE_TEXT, | ||
STEP_FLOW_VISUALLY_HIDDEN_TITLE_TEXT, | ||
STEP_FLOW_PROGRESS_INDICATOR_BAR, | ||
STEP_FLOW_LABEL, | ||
STEP_FLOW_DISMISS_ICON, | ||
} from "./locators"; | ||
|
||
// component preview locators | ||
export const stepFlowProgressIndicator = (page: Page) => | ||
page.locator(STEP_FLOW_PROGRESS_INDICATOR); | ||
|
||
export const stepFlowCategoryText = (page: Page) => | ||
page.locator(STEP_FLOW_CATEGORY_TEXT); | ||
|
||
export const stepFlowTitleText = (page: Page) => | ||
page.locator(STEP_FLOW_TITLE_TEXT); | ||
|
||
export const stepFlowTitleTextWrapper = (page: Page) => | ||
page.locator(STEP_FLOW_TITLE_TEXT_WRAPPER); | ||
|
||
export const stepFlowVisuallyHiddenTitleText = (page: Page) => | ||
page.locator(STEP_FLOW_VISUALLY_HIDDEN_TITLE_TEXT); | ||
|
||
export const stepFlowProgressIndicatorBar = (page: Page) => | ||
page.locator(STEP_FLOW_PROGRESS_INDICATOR_BAR); | ||
|
||
export const stepFlowLabel = (page: Page) => page.locator(STEP_FLOW_LABEL); | ||
|
||
export const stepFlowDismissIcon = (page: Page) => | ||
page.locator(STEP_FLOW_DISMISS_ICON); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export const STEP_FLOW_PROGRESS_INDICATOR = | ||
'[data-element="progress-indicator"]'; | ||
export const STEP_FLOW_CATEGORY_TEXT = '[data-element="category-text"]'; | ||
export const STEP_FLOW_TITLE_TEXT_WRAPPER = | ||
'[data-element="title-text-wrapper"]'; | ||
export const STEP_FLOW_TITLE_TEXT = '[data-element="visible-title-text"]'; | ||
export const STEP_FLOW_VISUALLY_HIDDEN_TITLE_TEXT = | ||
'[data-element="visually-hidden-title-text"]'; | ||
export const STEP_FLOW_PROGRESS_INDICATOR_BAR = | ||
'[data-element="progress-indicator-bar"]'; | ||
export const STEP_FLOW_LABEL = '[data-element="step-label"]'; | ||
export const STEP_FLOW_DISMISS_ICON = 'span[data-element="close"]'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import React, { useRef } from "react"; | ||
import Button from "../button"; | ||
import Box from "../box"; | ||
import { StepFlow, StepFlowProps } from "."; | ||
import { StepFlowHandle } from "./step-flow.component"; | ||
|
||
export const StepFlowComponent = (props: Partial<StepFlowProps>) => ( | ||
<StepFlow title="foo" currentStep={1} totalSteps={8} {...props} /> | ||
); | ||
|
||
export const StepFlowComponentWithRefAndButtons = ( | ||
props: Partial<StepFlowProps> | ||
) => { | ||
const stepFlowHandle = useRef<StepFlowHandle>(null); | ||
|
||
const focusOnTitle = () => { | ||
stepFlowHandle.current?.focus(); | ||
}; | ||
|
||
return ( | ||
<Box> | ||
<StepFlow | ||
title="foo" | ||
currentStep={1} | ||
totalSteps={8} | ||
ref={stepFlowHandle} | ||
{...props} | ||
/> | ||
<Button buttonType="tertiary" onClick={() => focusOnTitle()} mr={2}> | ||
Back | ||
</Button> | ||
<Button buttonType="primary" onClick={() => focusOnTitle()}> | ||
Continue | ||
</Button> | ||
</Box> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { default as StepFlow } from "./step-flow.component"; | ||
export type { StepFlowProps } from "./step-flow.component"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import React from "react"; | ||
import { StepFlow, StepFlowProps } from "."; | ||
|
||
export default { | ||
title: "Step Flow/Test", | ||
includeStories: ["Default"], | ||
parameters: { | ||
info: { disable: true }, | ||
chromatic: { | ||
disableSnapshot: true, | ||
}, | ||
}, | ||
argTypes: { | ||
category: { | ||
control: { | ||
type: "text", | ||
}, | ||
}, | ||
title: { | ||
control: { | ||
type: "text", | ||
}, | ||
}, | ||
totalSteps: { | ||
control: { | ||
min: 1, | ||
max: 8, | ||
step: 1, | ||
type: "range", | ||
}, | ||
}, | ||
currentStep: { | ||
control: { | ||
min: 1, | ||
max: 8, | ||
step: 1, | ||
type: "range", | ||
}, | ||
}, | ||
showProgressIndicator: { | ||
control: { | ||
type: "boolean", | ||
}, | ||
}, | ||
showCloseIcon: { | ||
control: { | ||
type: "boolean", | ||
}, | ||
}, | ||
}, | ||
}; | ||
|
||
export const Default = (props: Partial<StepFlowProps>) => ( | ||
<StepFlow title="default" currentStep={1} totalSteps={8} {...props} /> | ||
); | ||
|
||
Default.storyName = "default"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
import React, { useImperativeHandle, useRef, forwardRef } from "react"; | ||
import { MarginProps } from "styled-system"; | ||
import Icon from "../icon"; | ||
import IconButton from "../icon-button"; | ||
import { | ||
StyledStepFlow, | ||
StyledStepContent, | ||
StyledStepContentText, | ||
StyledStepLabelAndProgress, | ||
StyledProgressIndicatorBar, | ||
StyledProgressIndicator, | ||
StyledTitleFocusWrapper, | ||
} from "./step-flow.style"; | ||
import tagComponent, { | ||
TagProps, | ||
} from "../../__internal__/utils/helpers/tags/tags"; | ||
import Typography from "../typography"; | ||
import useLocale from "../../hooks/__internal__/useLocale"; | ||
|
||
export type Steps = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8; | ||
|
||
export interface StepFlowProps extends MarginProps, TagProps { | ||
/** A category for the user journey. */ | ||
category?: string; | ||
/** The title of the current step. */ | ||
title: string; | ||
/** Set the variant of the internal 'Typography' component which contains the title. | ||
* However, despite the chosen variant the styling will always be overridden. | ||
*/ | ||
titleVariant?: "h1" | "h2"; | ||
/** The total steps in the user journey. */ | ||
totalSteps: Steps; | ||
/** | ||
* The current step of the user journey. If the set `currentStep` is higher than | ||
* `totalSteps`the value of `currentStep` will be that of `totalSteps` instead. | ||
*/ | ||
currentStep: Steps; | ||
/** Determines if the progress indicator is shown. */ | ||
showProgressIndicator?: boolean; | ||
/** Determines if the close icon button is shown */ | ||
showCloseIcon?: boolean; | ||
/** function runs when user click dismiss button */ | ||
onDismiss?: ( | ||
e: | ||
| React.KeyboardEvent<HTMLButtonElement> | ||
| React.MouseEvent<HTMLButtonElement> | ||
) => void; | ||
} | ||
|
||
export type StepFlowHandle = { | ||
/** Programmatically focus on root container of Dialog. */ | ||
focus: () => void; | ||
} | null; | ||
|
||
export const StepFlow = forwardRef<StepFlowHandle, StepFlowProps>( | ||
( | ||
{ | ||
category, | ||
title, | ||
titleVariant, | ||
totalSteps, | ||
currentStep, | ||
showProgressIndicator = false, | ||
showCloseIcon = false, | ||
onDismiss, | ||
...rest | ||
}, | ||
ref | ||
) => { | ||
const totalStepsArray = Array.from( | ||
{ length: totalSteps }, | ||
(_, index) => index + 1 | ||
); | ||
|
||
const validatedCurrentStep = | ||
currentStep > totalSteps ? totalSteps : currentStep; | ||
|
||
let currentStepWarnTriggered = false; | ||
let noRefWarnTriggered = false; | ||
|
||
/* eslint-disable no-console */ | ||
if (!currentStepWarnTriggered && currentStep > totalSteps) { | ||
currentStepWarnTriggered = true; | ||
console.warn( | ||
"[WARNING] The `currentStep` prop should not be higher than the `totalSteps`prop in `StepFlow`." + | ||
" Please ensure `currentStep`s value does not exceed that of `totalSteps`, in the meantime" + | ||
" we have set `currentStep` value to that of `totalSteps`, and all indicators have been marked as completed." | ||
); | ||
} | ||
if (!noRefWarnTriggered && !ref) { | ||
noRefWarnTriggered = true; | ||
console.warn( | ||
"[WARNING] A `ref` should be provided to ensure focus is programmatically focused back to a title div," + | ||
" this ensures screen reader users are informed regarding any changes and can navigate back down the page." | ||
); | ||
} | ||
|
||
const progressIndicators = totalStepsArray.map((step) => { | ||
const generateDataState = () => { | ||
if (step === validatedCurrentStep) { | ||
return "in-progress"; | ||
} | ||
if (step < validatedCurrentStep) { | ||
return "is-completed"; | ||
} | ||
return "not-completed"; | ||
}; | ||
|
||
return ( | ||
<StyledProgressIndicator | ||
key={step} | ||
aria-hidden="true" | ||
data-element="progress-indicator" | ||
isCompleted={step < validatedCurrentStep} | ||
isInProgress={step === validatedCurrentStep} | ||
data-state={generateDataState()} | ||
> | ||
| ||
</StyledProgressIndicator> | ||
); | ||
}); | ||
|
||
const locale = useLocale(); | ||
|
||
const closeIcon = ( | ||
<IconButton | ||
data-element="close" | ||
aria-label={locale.stepFlow.closeIconAriaLabel?.()} | ||
onClick={onDismiss} | ||
> | ||
<Icon type="close" /> | ||
</IconButton> | ||
); | ||
|
||
const titleRef = useRef<HTMLDivElement>(null); | ||
|
||
useImperativeHandle<StepFlowHandle, StepFlowHandle>( | ||
ref, | ||
() => ({ | ||
focus() { | ||
titleRef.current?.focus(); | ||
}, | ||
}), | ||
[] | ||
); | ||
|
||
const stepFlowTitle = ( | ||
<StyledTitleFocusWrapper | ||
data-element="title-text-wrapper" | ||
tabIndex={-1} | ||
ref={titleRef} | ||
> | ||
<Typography variant={titleVariant || "h1"} data-element="title-text"> | ||
<Typography | ||
fontWeight="900" | ||
fontSize="var(--fontSizes600)" | ||
lineHeight="var(--sizing375)" | ||
variant="span" | ||
aria-hidden="true" | ||
data-element="visible-title-text" | ||
> | ||
{title} | ||
</Typography> | ||
<Typography | ||
variant="span" | ||
data-element="visually-hidden-title-text" | ||
screenReaderOnly | ||
> | ||
{locale.stepFlow.screenReaderOnlyTitle( | ||
title, | ||
validatedCurrentStep, | ||
totalSteps, | ||
category | ||
)} | ||
</Typography> | ||
</Typography> | ||
</StyledTitleFocusWrapper> | ||
); | ||
|
||
const stepFlowLabel = ( | ||
<Typography | ||
variant="span" | ||
fontWeight="400" | ||
fontSize="var(--fontSizes200)" | ||
lineHeight="var(--sizing300)" | ||
data-element="step-label" | ||
aria-hidden="true" | ||
> | ||
{locale.stepFlow.stepLabel(validatedCurrentStep, totalSteps)} | ||
</Typography> | ||
); | ||
|
||
return ( | ||
<StyledStepFlow {...rest} {...tagComponent("step-flow", rest)}> | ||
<StyledStepContent> | ||
{category ? ( | ||
<StyledStepContentText> | ||
<Typography | ||
fontWeight="500" | ||
fontSize="var(--fontSizes100)" | ||
lineHeight="var(--sizing250)" | ||
variant="span" | ||
data-element="category-text" | ||
aria-hidden="true" | ||
> | ||
{category} | ||
</Typography> | ||
{stepFlowTitle} | ||
</StyledStepContentText> | ||
) : ( | ||
stepFlowTitle | ||
)} | ||
{showCloseIcon ? closeIcon : null} | ||
</StyledStepContent> | ||
{showProgressIndicator ? ( | ||
<StyledStepLabelAndProgress> | ||
{stepFlowLabel} | ||
<StyledProgressIndicatorBar data-element="progress-indicator-bar"> | ||
{progressIndicators} | ||
</StyledProgressIndicatorBar> | ||
</StyledStepLabelAndProgress> | ||
) : ( | ||
stepFlowLabel | ||
)} | ||
</StyledStepFlow> | ||
); | ||
} | ||
); | ||
|
||
export default StepFlow; |
Oops, something went wrong.