From 9efe69fae23763f65cd0b93f94518588a702c536 Mon Sep 17 00:00:00 2001 From: Thivi Date: Tue, 21 Feb 2023 18:22:09 +0530 Subject: [PATCH] feat(react): add Stepper component --- packages/react/.storybook/story-config.ts | 4 + .../components/Stepper/Stepper.stories.mdx | 128 ++++++++++++++++++ .../react/src/components/Stepper/Stepper.tsx | 103 ++++++++++++++ .../Stepper/__tests__/Stepper.test.tsx | 41 ++++++ .../__snapshots__/Stepper.test.tsx.snap | 25 ++++ .../react/src/components/Stepper/index.ts | 20 +++ .../react/src/components/Stepper/stepper.scss | 30 ++++ packages/react/src/models/theme.ts | 3 + packages/react/src/theme/default-theme.ts | 9 +- 9 files changed, 362 insertions(+), 1 deletion(-) create mode 100644 packages/react/src/components/Stepper/Stepper.stories.mdx create mode 100644 packages/react/src/components/Stepper/Stepper.tsx create mode 100644 packages/react/src/components/Stepper/__tests__/Stepper.test.tsx create mode 100644 packages/react/src/components/Stepper/__tests__/__snapshots__/Stepper.test.tsx.snap create mode 100644 packages/react/src/components/Stepper/index.ts create mode 100644 packages/react/src/components/Stepper/stepper.scss diff --git a/packages/react/.storybook/story-config.ts b/packages/react/.storybook/story-config.ts index c34b0de5..3ea05e90 100644 --- a/packages/react/.storybook/story-config.ts +++ b/packages/react/.storybook/story-config.ts @@ -66,6 +66,7 @@ export type Stories = | 'UserDropdownMenu' | 'Navbar' | 'SignIn' + | 'Stepper' | 'TextField' | 'Toolbar' | 'Tooltip' @@ -203,6 +204,9 @@ const StoryConfig: StorybookConfig = { SignIn: { hierarchy: `${StorybookCategories.Patterns}/Sign In`, }, + Stepper: { + hierarchy: `${StorybookCategories.Surfaces}/Stepper`, + }, TextField: { hierarchy: `${StorybookCategories.Inputs}/Text Field`, }, diff --git a/packages/react/src/components/Stepper/Stepper.stories.mdx b/packages/react/src/components/Stepper/Stepper.stories.mdx new file mode 100644 index 00000000..05f88145 --- /dev/null +++ b/packages/react/src/components/Stepper/Stepper.stories.mdx @@ -0,0 +1,128 @@ +import {ArgsTable, Source, Story, Canvas, Meta} from '@storybook/addon-docs'; +import Stepper from './Stepper.tsx'; +import dedent from 'ts-dedent'; +import Typography from '../Typography'; +import StoryConfig from '../../../.storybook/story-config.ts'; + +export const meta = { + component: Stepper, + title: StoryConfig.Stepper.hierarchy, +}; + + + +export const Template = args => ; + +# ActionCard + +- [Overview](#overview) +- [Props](#props) +- [Usage](#usage) + +## Overview + +Stepper can be used to compose wizards and carousels. + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec auctor porttitor dolor eget tristique. Nam + elementum, quam vel varius porttitor, purus est vestibulum augue, sed suscipit ligula metus at nibh. Donec + eleifend suscipit nisi mollis sollicitudin. Vestibulum fermentum odio at maximus lacinia. Phasellus leo ipsum, + vestibulum hendrerit enim vitae, ullamcorper tincidunt erat. Sed quam nulla, pharetra non mattis non, + fringilla non massa. Donec maximus finibus dui et suscipit. Suspendisse potenti. In imperdiet hendrerit + accumsan. Vivamus lacus nunc, mollis ut elementum eget, tempus vitae lectus. Ut molestie ante quis quam + aliquam pretium. Vestibulum dignissim, odio vel volutpat porta, enim nisl auctor turpis, vel imperdiet nisl + lorem id ligula. Proin varius scelerisque ligula ac consequat. + , + + Aliquam vel ex tortor. Proin sed ullamcorper massa. Sed eu fringilla risus, a faucibus tortor. Duis euismod + enim sit amet nunc condimentum, eget tristique ex ultricies. Suspendisse potenti. Phasellus risus ligula, + imperdiet in imperdiet ac, hendrerit eu quam. Aliquam leo risus, vulputate nec auctor viverra, dictum a elit. + Curabitur a accumsan lorem. Cras nec metus sed diam vehicula luctus nec sit amet dolor. Donec ac nibh finibus, + varius arcu sit amet, dapibus neque. Morbi orci augue, commodo vitae tincidunt vel, tincidunt at justo. + Quisque sem mauris, consectetur sit amet lobortis vel, consectetur sit amet massa. + , + + Praesent varius porta tellus, ac mattis quam blandit at. Vestibulum in nisi at est rhoncus posuere ac vitae + ligula. Phasellus molestie purus ac nulla vestibulum gravida. Morbi lacinia vehicula aliquam. Praesent mollis + mollis arcu eu finibus. Morbi at nunc quam. Aliquam sed urna quis erat elementum bibendum vitae eu massa. + , + + Sed placerat molestie tristique. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac + turpis egestas. Vestibulum at libero bibendum, tempor nunc vel, luctus ipsum. Aenean ut diam ligula. Ut + auctor, justo a tincidunt fermentum, nibh dui consectetur massa, eu congue sapien eros ut nisl. Nam nec + fringilla sem. Pellentesque facilisis fermentum nibh, in volutpat ipsum porttitor ut. Quisque auctor lorem et + dolor suscipit, nec rhoncus justo aliquam. Praesent elit sapien, tempor id mi et, fermentum accumsan justo. Ut + a justo tortor. Proin in nisl vel arcu congue tristique. Proin mattis condimentum orci, quis accumsan neque + auctor vel. + , + ], + }} + > + {Template.bind({})} + + + +## Props + + + +## Usage + +Import and use the `Stepper` component in your components as follows. + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec auctor porttitor dolor eget tristique. Nam + elementum, quam vel varius porttitor, purus est vestibulum augue, sed suscipit ligula metus at nibh. Donec + eleifend suscipit nisi mollis sollicitudin. Vestibulum fermentum odio at maximus lacinia. Phasellus leo ipsum, + vestibulum hendrerit enim vitae, ullamcorper tincidunt erat. Sed quam nulla, pharetra non mattis non, + fringilla non massa. Donec maximus finibus dui et suscipit. Suspendisse potenti. In imperdiet hendrerit + accumsan. Vivamus lacus nunc, mollis ut elementum eget, tempus vitae lectus. Ut molestie ante quis quam + aliquam pretium. Vestibulum dignissim, odio vel volutpat porta, enim nisl auctor turpis, vel imperdiet nisl + lorem id ligula. Proin varius scelerisque ligula ac consequat. + , + + Aliquam vel ex tortor. Proin sed ullamcorper massa. Sed eu fringilla risus, a faucibus tortor. Duis euismod + enim sit amet nunc condimentum, eget tristique ex ultricies. Suspendisse potenti. Phasellus risus ligula, + imperdiet in imperdiet ac, hendrerit eu quam. Aliquam leo risus, vulputate nec auctor viverra, dictum a elit. + Curabitur a accumsan lorem. Cras nec metus sed diam vehicula luctus nec sit amet dolor. Donec ac nibh finibus, + varius arcu sit amet, dapibus neque. Morbi orci augue, commodo vitae tincidunt vel, tincidunt at justo. + Quisque sem mauris, consectetur sit amet lobortis vel, consectetur sit amet massa. + , + + Praesent varius porta tellus, ac mattis quam blandit at. Vestibulum in nisi at est rhoncus posuere ac vitae + ligula. Phasellus molestie purus ac nulla vestibulum gravida. Morbi lacinia vehicula aliquam. Praesent mollis + mollis arcu eu finibus. Morbi at nunc quam. Aliquam sed urna quis erat elementum bibendum vitae eu massa. + , + + Sed placerat molestie tristique. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac + turpis egestas. Vestibulum at libero bibendum, tempor nunc vel, luctus ipsum. Aenean ut diam ligula. Ut + auctor, justo a tincidunt fermentum, nibh dui consectetur massa, eu congue sapien eros ut nisl. Nam nec + fringilla sem. Pellentesque facilisis fermentum nibh, in volutpat ipsum porttitor ut. Quisque auctor lorem et + dolor suscipit, nec rhoncus justo aliquam. Praesent elit sapien, tempor id mi et, fermentum accumsan justo. Ut + a justo tortor. Proin in nisl vel arcu congue tristique. Proin mattis condimentum orci, quis accumsan neque + auctor vel. + , + ] + } + /> + ); +}`} +/> diff --git a/packages/react/src/components/Stepper/Stepper.tsx b/packages/react/src/components/Stepper/Stepper.tsx new file mode 100644 index 00000000..3db5fa3d --- /dev/null +++ b/packages/react/src/components/Stepper/Stepper.tsx @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import clsx from 'clsx'; +import {FC, MutableRefObject, ReactElement, useCallback, useEffect, useRef, useState} from 'react'; +import {WithWrapperProps} from '../../models'; +import {baseTheme} from '../../theme/default-theme'; +import {composeComponentDisplayName} from '../../utils'; +import Box from '../Box'; +import './stepper.scss'; + +export interface StepperProps { + animateOnSlide?: boolean; + className?: string; + currentStep: number; + steps: ReactElement[]; +} + +const COMPONENT_NAME: string = 'Stepper'; + +const Stepper: FC & WithWrapperProps = (props: StepperProps): ReactElement => { + const {animateOnSlide, className, currentStep, steps} = props; + + const [slideLeftPosition, setSlideLeftPosition] = useState(0); + const [slideContainerWidth, setSlideContainerWidth] = useState(0); + + const slideContainerRef: MutableRefObject = useRef(null); + + const classes: string = clsx('oxygen-stepper', className); + + const slideContainer: (position: number) => void = useCallback( + (position: number): void => { + if (!animateOnSlide) { + return; + } + + const slideBy: number = position; + setSlideLeftPosition(slideBy * -1 * currentStep); + }, + [currentStep, baseTheme, animateOnSlide], + ); + + useEffect(() => { + if (!slideContainerRef?.current || !animateOnSlide) { + return () => {}; + } + + setSlideContainerWidth(slideContainerRef.current.offsetWidth); + + const handleResize = (): void => { + const width: number = slideContainerRef.current.offsetWidth; + setSlideContainerWidth(width); + slideContainer(width); + }; + + window.addEventListener('resize', handleResize); + + return (): void => { + window.removeEventListener('resize', handleResize); + }; + }, [slideContainerRef.current, slideContainer]); + + useEffect(() => { + slideContainer(slideContainerWidth); + }, [slideContainerWidth, slideContainer]); + + if (animateOnSlide) { + return ( + + + {steps.map((step: ReactElement) => ( + {step} + ))} + + + ); + } + + return steps[currentStep]; +}; + +Stepper.displayName = composeComponentDisplayName(COMPONENT_NAME); +Stepper.muiName = COMPONENT_NAME; +Stepper.defaultProps = { + currentStep: 0, +}; + +export default Stepper; diff --git a/packages/react/src/components/Stepper/__tests__/Stepper.test.tsx b/packages/react/src/components/Stepper/__tests__/Stepper.test.tsx new file mode 100644 index 00000000..1dd6ba6e --- /dev/null +++ b/packages/react/src/components/Stepper/__tests__/Stepper.test.tsx @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import {render} from '@unit-testing'; +import {ReactElement} from 'react'; +import Typography from '../../Typography'; +import Stepper from '../Stepper'; + +const steps: ReactElement[] = [ + Step 1, + Step 2, + Step 3, + Step 4, +]; + +describe('Stepper', () => { + it('should render successfully', () => { + const {baseElement} = render(); + expect(baseElement).toBeTruthy(); + }); + + it('should match the snapshot', () => { + const {baseElement} = render(); + expect(baseElement).toMatchSnapshot(); + }); +}); diff --git a/packages/react/src/components/Stepper/__tests__/__snapshots__/Stepper.test.tsx.snap b/packages/react/src/components/Stepper/__tests__/__snapshots__/Stepper.test.tsx.snap new file mode 100644 index 00000000..d333a158 --- /dev/null +++ b/packages/react/src/components/Stepper/__tests__/__snapshots__/Stepper.test.tsx.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SignIn should match the snapshot 1`] = ` + +
+

+ Step 1 +

+
+ +`; + +exports[`Stepper should match the snapshot 1`] = ` + +
+

+ Step 1 +

+
+ +`; diff --git a/packages/react/src/components/Stepper/index.ts b/packages/react/src/components/Stepper/index.ts new file mode 100644 index 00000000..43cd9b95 --- /dev/null +++ b/packages/react/src/components/Stepper/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export {default as Stepper} from './Stepper'; +export type {StepperProps} from './Stepper'; diff --git a/packages/react/src/components/Stepper/stepper.scss b/packages/react/src/components/Stepper/stepper.scss new file mode 100644 index 00000000..854bbafb --- /dev/null +++ b/packages/react/src/components/Stepper/stepper.scss @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +.oxygen-stepper { + overflow: hidden; + + .oxygen-stepper-container { + display: flex; + flex-direction: row; + justify-content: flex-start; + width: max-content; + position: relative; + transition: left 0.5s ease-in-out; + } +} diff --git a/packages/react/src/models/theme.ts b/packages/react/src/models/theme.ts index 52edf2e7..130a4952 100644 --- a/packages/react/src/models/theme.ts +++ b/packages/react/src/models/theme.ts @@ -64,6 +64,9 @@ interface CustomTheme { Navbar?: { properties?: Record; }; + Stepper?: { + properties?: Record; + }; }; } diff --git a/packages/react/src/theme/default-theme.ts b/packages/react/src/theme/default-theme.ts index b3faefc6..4ca0f241 100644 --- a/packages/react/src/theme/default-theme.ts +++ b/packages/react/src/theme/default-theme.ts @@ -99,7 +99,7 @@ export const generateDefaultThemeOptions = (baseTheme: Theme): RecursivePartial< MuiCard: { styleOverrides: { root: { - padding: '24px 40px 24px 24px', + padding: baseTheme.spacing(3), }, }, }, @@ -120,6 +120,13 @@ export const generateDefaultThemeOptions = (baseTheme: Theme): RecursivePartial< width: '240px', }, }, + Stepper: { + properties: { + 'action-margin-bottom': baseTheme.spacing(4), + 'progress-gap': baseTheme.spacing(1), + 'right-button-gap': baseTheme.spacing(1), + }, + }, }, shadows: ['0px 2px 20px 0px #1d20281a', ...baseTheme.shadows], shape: {