From 05f357db81f23848f7238efe57f555cc9d530cb6 Mon Sep 17 00:00:00 2001 From: kostasdano Date: Thu, 17 Oct 2024 15:08:03 +0300 Subject: [PATCH 1/9] feat(tabs): change tabId to id --- src/components/Tabs/Tabs.stories.tsx | 8 ++++---- src/components/Tabs/Tabs.tsx | 2 +- src/components/Tabs/components/Tab/Tab.mdx | 6 +++--- src/components/Tabs/components/Tab/Tab.tsx | 4 ++-- src/components/Tabs/components/TabList/TabList.mdx | 6 +++--- .../Tabs/components/TabPanel/TabPanel.mdx | 14 +++++++------- .../Tabs/components/TabPanel/TabPanel.tsx | 4 ++-- src/components/Tabs/types.ts | 6 +++--- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/components/Tabs/Tabs.stories.tsx b/src/components/Tabs/Tabs.stories.tsx index ce2fee268..766ce2450 100644 --- a/src/components/Tabs/Tabs.stories.tsx +++ b/src/components/Tabs/Tabs.stories.tsx @@ -50,7 +50,7 @@ export const TabsWithContent = { items={getItems(false)} > {Object.keys(content).map((friend) => ( - + {content[friend]} ))} @@ -81,7 +81,7 @@ export const TabsOrientation = { items={getItems(false)} > {Object.keys(content).map((friend) => ( - + {content[friend]} ))} @@ -109,7 +109,7 @@ export const TabsWithCounter = { items={getItems(true)} > {Object.keys(content).map((friend) => ( - + {content[friend]} ))} @@ -140,7 +140,7 @@ export const Playground = { items={getItems(hasCounter)} > {Object.keys(content).map((friend) => ( - + {content[friend]} ))} diff --git a/src/components/Tabs/Tabs.tsx b/src/components/Tabs/Tabs.tsx index 1803c288a..981200e30 100644 --- a/src/components/Tabs/Tabs.tsx +++ b/src/components/Tabs/Tabs.tsx @@ -29,7 +29,7 @@ const Tabs = React.forwardRef((props, ref) => { const isActive = id === selectedKey; return ( - + {label} {Boolean(counter) && ( // - + Tab 1 - + Tab 2 - + Tab 3 // diff --git a/src/components/Tabs/components/Tab/Tab.tsx b/src/components/Tabs/components/Tab/Tab.tsx index abd3ff432..2b8763131 100644 --- a/src/components/Tabs/components/Tab/Tab.tsx +++ b/src/components/Tabs/components/Tab/Tab.tsx @@ -5,10 +5,10 @@ import { containerStyles } from './Tab.style'; import type { TabProps } from '../../types'; const Tab = React.forwardRef((props, ref) => { - const { children, tabId, ...rest } = props; + const { children, ...rest } = props; return ( - + {children} ); diff --git a/src/components/Tabs/components/TabList/TabList.mdx b/src/components/Tabs/components/TabList/TabList.mdx index d4e45444f..21130bc1c 100644 --- a/src/components/Tabs/components/TabList/TabList.mdx +++ b/src/components/Tabs/components/TabList/TabList.mdx @@ -46,13 +46,13 @@ Use the TabList to add aria-label and style overrides. // onSelectionChange={setSelectedKey} // > - // + // // Tab 1 // - // + // // Tab 2 // - // + // // Tab 3 // diff --git a/src/components/Tabs/components/TabPanel/TabPanel.mdx b/src/components/Tabs/components/TabPanel/TabPanel.mdx index 9d7d22c6c..ccb919736 100644 --- a/src/components/Tabs/components/TabPanel/TabPanel.mdx +++ b/src/components/Tabs/components/TabPanel/TabPanel.mdx @@ -24,7 +24,7 @@ TabPanel component is used to conditionally render the content that corresponds Use TabPanels as children of (monolith) Tabs or under Tab components in a composition to toggle content when selecting tabs. -The tabId should be the same as in the Tab component that the TabPanel corresponds to +The id should be the same as in the Tab component that the TabPanel corresponds to ```js const [selectedKey, setSelectedKey] = React.useState('tab_1'); @@ -36,18 +36,18 @@ Use TabPanels as children of (monolith) Tabs or under Tab components in a compos // onSelectionChange={setSelectedKey} // > // - // + // // Tab 1 // - // + // // Tab 2 // - // + // // Tab 3 // - This is Tab 1 content - This is Tab 2 content - This is Tab 3 content + This is Tab 1 content + This is Tab 2 content + This is Tab 3 content // // diff --git a/src/components/Tabs/components/TabPanel/TabPanel.tsx b/src/components/Tabs/components/TabPanel/TabPanel.tsx index 690b6672c..21d9ad7ec 100644 --- a/src/components/Tabs/components/TabPanel/TabPanel.tsx +++ b/src/components/Tabs/components/TabPanel/TabPanel.tsx @@ -4,10 +4,10 @@ import { TabPanel as ReactAriaTabPanel } from 'react-aria-components'; import type { TabPanelProps } from '../../types'; const TabPanel = React.forwardRef((props, ref) => { - const { children, tabId, ...rest } = props; + const { children, ...rest } = props; return ( - + {children} ); diff --git a/src/components/Tabs/types.ts b/src/components/Tabs/types.ts index 32713329e..a825b0364 100644 --- a/src/components/Tabs/types.ts +++ b/src/components/Tabs/types.ts @@ -9,7 +9,7 @@ export type { Orientation as TabOrientation } from 'react-aria'; export type { Key as TabKey } from 'react-aria-components'; export type TabItem = { - /** A unique if for the tab item */ + /** A unique id for the tab item */ id: string; /** The label of the tab */ label: string; @@ -42,7 +42,7 @@ export type TabsContainerProps = { export type TabProps = { /** The id of the tab; it's also used as an indicator of the tab when using selection state */ - tabId?: string; + id: string; children: any; }; @@ -54,7 +54,7 @@ export type TabListProps = { export type TabPanelProps = { /** The id of the tab the content corresponds to */ - tabId: string; + id: string; /** The content of the tab panel */ children: any; }; From be52a1a8fb1df182f396217569e024c78c5675ae Mon Sep 17 00:00:00 2001 From: kostasdano Date: Thu, 17 Oct 2024 16:09:40 +0300 Subject: [PATCH 2/9] feat(tabstepper): implementation --- src/components/TabStepper/TabStepper.tsx | 40 ++++++++ .../components/TabStep/TabStep.style.ts | 31 +++++++ .../TabStepper/components/TabStep/TabStep.tsx | 62 +++++++++++++ .../TabStepper/components/TabStep/index.ts | 1 + .../TabStepList/TabStepList.style.ts | 93 +++++++++++++++++++ .../components/TabStepList/TabStepList.tsx | 19 ++++ .../components/TabStepList/index.ts | 1 + src/components/TabStepper/components/index.ts | 2 + src/components/TabStepper/index.ts | 3 + src/components/TabStepper/types.ts | 35 +++++++ .../TabsContainer/TabsContainer.style.ts | 4 +- src/index.ts | 2 + 12 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 src/components/TabStepper/TabStepper.tsx create mode 100644 src/components/TabStepper/components/TabStep/TabStep.style.ts create mode 100644 src/components/TabStepper/components/TabStep/TabStep.tsx create mode 100644 src/components/TabStepper/components/TabStep/index.ts create mode 100644 src/components/TabStepper/components/TabStepList/TabStepList.style.ts create mode 100644 src/components/TabStepper/components/TabStepList/TabStepList.tsx create mode 100644 src/components/TabStepper/components/TabStepList/index.ts create mode 100644 src/components/TabStepper/components/index.ts create mode 100644 src/components/TabStepper/index.ts create mode 100644 src/components/TabStepper/types.ts diff --git a/src/components/TabStepper/TabStepper.tsx b/src/components/TabStepper/TabStepper.tsx new file mode 100644 index 000000000..3106961cc --- /dev/null +++ b/src/components/TabStepper/TabStepper.tsx @@ -0,0 +1,40 @@ +import React from 'react'; + +import { TabStep, TabStepList } from './components'; +import type { StepperProps } from './types'; +import { TabsContainer } from '../Tabs'; + +const TabStepper = React.forwardRef((props, ref) => { + const { + orientation = 'horizontal', + selectedKey, + onSelectionChange, + items, + dataTestPrefixId = 'ictinus', + children, + } = props; + + return ( + + + {items.map((item) => ( + + ))} + + {children} + + ); +}); + +TabStepper.displayName = 'TabStepper'; + +export default TabStepper; diff --git a/src/components/TabStepper/components/TabStep/TabStep.style.ts b/src/components/TabStepper/components/TabStep/TabStep.style.ts new file mode 100644 index 000000000..7b1190f66 --- /dev/null +++ b/src/components/TabStepper/components/TabStep/TabStep.style.ts @@ -0,0 +1,31 @@ +import { css } from '@emotion/react'; + +import type { Theme } from '~/theme'; +import { flex, flexCenterVertical } from '~/theme/functions'; + +export const stepStyles = () => + css` + cursor: pointer; + `; + +export const stepContainer = () => (theme: Theme) => + css` + ${flex}; + flex-direction: column; + gap: ${theme.globals.spacing.get('3')}; + padding: ${theme.globals.spacing.get('3')}; + `; + +export const stepTitle = () => (theme: Theme) => + css` + ${flexCenterVertical}; + justify-content: space-between; + + ${theme.tokens.typography.get('normal.title02')} + `; + +export const stepSubtitle = () => (theme: Theme) => + css` + ${theme.tokens.typography.get('normal.body03')} + color: ${theme.tokens.colors.get('textColor.default.secondary')} + `; diff --git a/src/components/TabStepper/components/TabStep/TabStep.tsx b/src/components/TabStepper/components/TabStep/TabStep.tsx new file mode 100644 index 000000000..058e09fa3 --- /dev/null +++ b/src/components/TabStepper/components/TabStep/TabStep.tsx @@ -0,0 +1,62 @@ +import React, { useMemo } from 'react'; +import { Tab as ReactAriaTab } from 'react-aria-components'; + +import { stepContainer, stepStyles, stepSubtitle, stepTitle } from './TabStep.style'; +import type { TabStepProps } from '../../types'; + +import Icon from '~/components/Icon'; +import useTheme from '~/hooks/useTheme'; + +const TabStep = React.forwardRef((props, ref) => { + const { children, id, title, subtitle, status = 'pending', dataTestPrefixId, ...rest } = props; + + const hasIcon = status !== 'pending'; + + const theme = useTheme(); + + const iconName = useMemo(() => { + if (status === 'done') { + return 'success'; + } + + return 'warning'; + }, [status]); + + const iconColor = useMemo(() => { + if (status === 'done') { + return 'textColor.default.active'; + } + + return 'indicators.error'; + }, [status]); + + return ( + + {children ?? ( +
+
+ {title} + {hasIcon && ( + + )} +
+
+ {subtitle} +
+
+ )} +
+ ); +}); + +TabStep.displayName = 'TabStep'; + +export default TabStep; diff --git a/src/components/TabStepper/components/TabStep/index.ts b/src/components/TabStepper/components/TabStep/index.ts new file mode 100644 index 000000000..f24fb1bab --- /dev/null +++ b/src/components/TabStepper/components/TabStep/index.ts @@ -0,0 +1 @@ +export { default } from './TabStep'; diff --git a/src/components/TabStepper/components/TabStepList/TabStepList.style.ts b/src/components/TabStepper/components/TabStepList/TabStepList.style.ts new file mode 100644 index 000000000..2f2559a77 --- /dev/null +++ b/src/components/TabStepper/components/TabStepList/TabStepList.style.ts @@ -0,0 +1,93 @@ +import type { CSSObject } from '@emotion/react'; +import { css } from '@emotion/react'; + +import type { Theme } from '~/theme'; + +export const containerStyles = (sx?: CSSObject) => (theme: Theme) => + css` + display: flex; + + [role='tab'] { + position: relative; + width: fit-content; + } + + [role='tab']:not([data-selected]):hover { + background: ${theme.tokens.colors.get('backgroundColor.alt')}; + transition: background ease-in-out 0.2s; + } + + [role='tab'][data-focus-visible]:after { + content: ''; + position: absolute; + + border-radius: ${theme.globals.borderRadius.get('2')}; + border: ${theme.dimension.borderWidth.get('focused')} solid + ${theme.tokens.colors.get('borderColor.interactive.upsell')}; + } + + [role='tab'][data-status='warning'] { + [data-role='title'] { + color: ${theme.tokens.colors.get('indicators.error')}; + } + } + + [role='tab'][data-selected]:not([data-status='warning']) { + [data-role='title'] { + color: ${theme.tokens.colors.get('textColor.default.active')}; + } + } + + &[data-orientation='horizontal'] { + gap: ${theme.globals.spacing.get('7')}; + border-bottom: ${theme.globals.borderWidth.get('2')} solid + ${theme.tokens.colors.get('borderColor.decorative.default')}; + + [role='tab'] { + padding: ${theme.globals.spacing.get('3')}; + } + + [role='tab']:after { + inset: -8px -12px; + } + + [role='tab'][data-selected] { + border-bottom: ${theme.globals.borderWidth.get('2')} solid + ${theme.tokens.colors.get('borderColor.interactive.active')}; + + &[data-status='warning'] { + border-color: ${theme.tokens.colors.get('indicators.error')}; + } + + margin-bottom: -${theme.globals.borderWidth.get('2')}; + } + } + + &[data-orientation='vertical'] { + flex-direction: column; + gap: ${theme.globals.spacing.get('6')}; + border-left: ${theme.globals.borderWidth.get('2')} solid + ${theme.tokens.colors.get('borderColor.decorative.default')}; + + [role='tab'][data-focus-visible]:after { + inset: -4px -2px; + } + + [role='tab'] { + padding: ${theme.globals.spacing.get('4')} ${theme.globals.spacing.get('5')}; + } + + [role='tab'][data-selected] { + border-left: ${theme.globals.borderWidth.get('2')} solid + ${theme.tokens.colors.get('borderColor.interactive.active')}; + + &[data-status='warning'] { + border-color: ${theme.tokens.colors.get('indicators.error')}; + } + + margin-left: -${theme.globals.borderWidth.get('2')}; + } + } + + ${sx}; + `; diff --git a/src/components/TabStepper/components/TabStepList/TabStepList.tsx b/src/components/TabStepper/components/TabStepList/TabStepList.tsx new file mode 100644 index 000000000..78843666f --- /dev/null +++ b/src/components/TabStepper/components/TabStepList/TabStepList.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { TabList as AriaTabList } from 'react-aria-components'; + +import { containerStyles } from './TabStepList.style'; +import type { TabStepListProps } from '../../types'; + +const TabStepList = React.forwardRef((props, ref) => { + const { children, sx, ...rest } = props; + + return ( + + {children} + + ); +}); + +TabStepList.displayName = 'TabStepList'; + +export default TabStepList; diff --git a/src/components/TabStepper/components/TabStepList/index.ts b/src/components/TabStepper/components/TabStepList/index.ts new file mode 100644 index 000000000..2664d3964 --- /dev/null +++ b/src/components/TabStepper/components/TabStepList/index.ts @@ -0,0 +1 @@ +export { default } from './TabStepList'; diff --git a/src/components/TabStepper/components/index.ts b/src/components/TabStepper/components/index.ts new file mode 100644 index 000000000..0fc4a6653 --- /dev/null +++ b/src/components/TabStepper/components/index.ts @@ -0,0 +1,2 @@ +export { default as TabStep } from './TabStep'; +export { default as TabStepList } from './TabStepList'; diff --git a/src/components/TabStepper/index.ts b/src/components/TabStepper/index.ts new file mode 100644 index 000000000..d8d7a2e7a --- /dev/null +++ b/src/components/TabStepper/index.ts @@ -0,0 +1,3 @@ +export { default } from './TabStepper'; +export * from './components'; +export * from './types'; diff --git a/src/components/TabStepper/types.ts b/src/components/TabStepper/types.ts new file mode 100644 index 000000000..2d9983a89 --- /dev/null +++ b/src/components/TabStepper/types.ts @@ -0,0 +1,35 @@ +import type { CSSObject } from '@emotion/react'; +import type { AriaAttributes } from 'react'; + +import type { TabsProps } from '../Tabs'; + +import type { TestProps } from '~/utils/types'; + +export type TabStepItem = { + /** A unique id for the tab step item */ + id: string; + /** The title of the step */ + title: string; + /** The subtitle of the step */ + subtitle?: string; + /** The status of the step */ + status?: 'pending' | 'done' | 'warning'; +}; + +export type StepperProps = { + /** The items (steps) */ + items: TabStepItem[]; + children?: any; +} & Pick & + TestProps; + +export type TabStepProps = { + children?: any; +} & TabStepItem & + TestProps; + +export type TabStepListProps = { + /** Style overrides for TabSteps */ + sx?: CSSObject; + children: any; +} & AriaAttributes; diff --git a/src/components/Tabs/components/TabsContainer/TabsContainer.style.ts b/src/components/Tabs/components/TabsContainer/TabsContainer.style.ts index 88e640678..487b58d90 100644 --- a/src/components/Tabs/components/TabsContainer/TabsContainer.style.ts +++ b/src/components/Tabs/components/TabsContainer/TabsContainer.style.ts @@ -3,7 +3,9 @@ import { css } from '@emotion/react'; import type { TabOrientation } from '../../types'; export const tabsContainerStyles = (orientation: TabOrientation) => css` - width: fit-content; display: flex; flex-direction: ${orientation === 'horizontal' ? 'column' : 'row'}; + + ${orientation === 'horizontal' && `width: fit-content;`} + ${orientation === 'vertical' && `height: fit-content;`} `; diff --git a/src/index.ts b/src/index.ts index 439a275f3..6a9abf451 100644 --- a/src/index.ts +++ b/src/index.ts @@ -102,6 +102,8 @@ export { default as Tag } from './components/Tag'; export * from './components/Tag'; export { default as Tabs } from './components/Tabs'; export * from './components/Tabs'; +export { default as TabStepper } from './components/TabStepper'; +export * from './components/TabStepper'; export { default as TextField } from './components/TextField'; export * from './components/TextField'; export { default as Search } from './components/Search'; From fb828932aff5c3e0954062d05180b25bc3d5c5c3 Mon Sep 17 00:00:00 2001 From: kostasdano Date: Thu, 17 Oct 2024 16:15:09 +0300 Subject: [PATCH 3/9] feat: types --- src/components/TabStepper/types.ts | 2 +- src/components/Tabs/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/TabStepper/types.ts b/src/components/TabStepper/types.ts index 2d9983a89..f75edf3fa 100644 --- a/src/components/TabStepper/types.ts +++ b/src/components/TabStepper/types.ts @@ -9,7 +9,7 @@ export type TabStepItem = { /** A unique id for the tab step item */ id: string; /** The title of the step */ - title: string; + title?: string; /** The subtitle of the step */ subtitle?: string; /** The status of the step */ diff --git a/src/components/Tabs/types.ts b/src/components/Tabs/types.ts index a825b0364..39cd75940 100644 --- a/src/components/Tabs/types.ts +++ b/src/components/Tabs/types.ts @@ -32,7 +32,7 @@ export type TabsProps = { export type TabsContainerProps = { /** The orientation of the Tab */ - orientation: TabOrientation; + orientation?: TabOrientation; /** The id of the selected tab */ selectedKey: TabKey; /** Callback to change the selected tab */ From ad3a9a3450720f2ac7e89ae8a0321027dfafbe70 Mon Sep 17 00:00:00 2001 From: kostasdano Date: Thu, 17 Oct 2024 16:33:02 +0300 Subject: [PATCH 4/9] feat(tabstepper): styles --- .../components/TabStep/TabStep.style.ts | 4 ++ .../TabStepper/components/TabStep/TabStep.tsx | 38 ++++++++++--------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/components/TabStepper/components/TabStep/TabStep.style.ts b/src/components/TabStepper/components/TabStep/TabStep.style.ts index 7b1190f66..daacc11aa 100644 --- a/src/components/TabStepper/components/TabStep/TabStep.style.ts +++ b/src/components/TabStepper/components/TabStep/TabStep.style.ts @@ -6,6 +6,9 @@ import { flex, flexCenterVertical } from '~/theme/functions'; export const stepStyles = () => css` cursor: pointer; + display: flex; + flex-direction: column; + justify-content: center; `; export const stepContainer = () => (theme: Theme) => @@ -20,6 +23,7 @@ export const stepTitle = () => (theme: Theme) => css` ${flexCenterVertical}; justify-content: space-between; + gap: ${theme.globals.spacing.get('7')}; ${theme.tokens.typography.get('normal.title02')} `; diff --git a/src/components/TabStepper/components/TabStep/TabStep.tsx b/src/components/TabStepper/components/TabStep/TabStep.tsx index 058e09fa3..ab9d1aaa2 100644 --- a/src/components/TabStepper/components/TabStep/TabStep.tsx +++ b/src/components/TabStepper/components/TabStep/TabStep.tsx @@ -34,23 +34,27 @@ const TabStep = React.forwardRef((props, ref) => { {children ?? (
-
- {title} - {hasIcon && ( - - )} -
-
- {subtitle} -
+ {title && ( +
+ {title} + {hasIcon && ( + + )} +
+ )} + {subtitle && ( +
+ {subtitle} +
+ )}
)}
From 2c68abb0e7a7f29fd865ca7a8688202a1e139a45 Mon Sep 17 00:00:00 2001 From: kostasdano Date: Thu, 17 Oct 2024 16:35:15 +0300 Subject: [PATCH 5/9] feat(tabstepper): documentation --- src/components/TabStepper/TabStepper.mdx | 78 ++++++ .../TabStepper/TabStepper.stories.tsx | 256 ++++++++++++++++++ .../TabStepper/components/TabStep/TabStep.mdx | 56 ++++ .../components/TabStep/TabStep.stories.tsx | 13 + .../components/TabStepList/TabStepList.mdx | 61 +++++ .../TabStepList/TabStepList.stories.tsx | 13 + src/components/TabStepper/constants.tsx | 46 ++++ .../utils/TabStepperStoryComponents.tsx | 4 + .../TabsContainer/TabsContainer.mdx | 44 ++- 9 files changed, 566 insertions(+), 5 deletions(-) create mode 100644 src/components/TabStepper/TabStepper.mdx create mode 100644 src/components/TabStepper/TabStepper.stories.tsx create mode 100644 src/components/TabStepper/components/TabStep/TabStep.mdx create mode 100644 src/components/TabStepper/components/TabStep/TabStep.stories.tsx create mode 100644 src/components/TabStepper/components/TabStepList/TabStepList.mdx create mode 100644 src/components/TabStepper/components/TabStepList/TabStepList.stories.tsx create mode 100644 src/components/TabStepper/constants.tsx create mode 100644 src/components/TabStepper/utils/TabStepperStoryComponents.tsx diff --git a/src/components/TabStepper/TabStepper.mdx b/src/components/TabStepper/TabStepper.mdx new file mode 100644 index 000000000..5100a0988 --- /dev/null +++ b/src/components/TabStepper/TabStepper.mdx @@ -0,0 +1,78 @@ +import { Meta, Canvas, ArgTypes } from '@storybook/blocks'; +import TabStepper from './TabStepper'; +import * as TabStepperStories from './TabStepper.stories'; +import { TabStepItemStory } from './utils/TabStepperStoryComponents'; + + + + + +## Overview + +A TabStepper is a multi-step process that helps users to complete a task or achieve a goal by breaking it down into smaller, more manageable steps. + + + This component is a monolith that covers most basic use cases. If you want to customize your + TabStepper to fit your specific needs, visit the Components section to learn how you can use the + tab stepper modules to create your own solutions. + + + + +### TabStepper + + + +## TabStepItem + + + +## Usage + + + + + +## TabStepper types + +TabStepper can be simple (with only a title) or also display a subtitle. + + + +## TabStepper with content + +In order to add content to your TabStepper and render it conditionally based on the selected TabStep, you can use the TabPanel component. Otherwise you can just use the TabStepper component without children and implement a custom panel functionality. + + + +## Orientation + +TabStepper can be horizontal or vertical. Use the `orientation` prop to toggle this feature. + + + +## TabStepper statuses + +There are 3 statuses for the TabStep: `pending` (the default one), `warning` and `done`. You can set the statuses thought the TabStepper's `items` prop, or the TabStep's `status` prop when creating a composition. + + + +## Playground + + diff --git a/src/components/TabStepper/TabStepper.stories.tsx b/src/components/TabStepper/TabStepper.stories.tsx new file mode 100644 index 000000000..b7e9d575a --- /dev/null +++ b/src/components/TabStepper/TabStepper.stories.tsx @@ -0,0 +1,256 @@ +import React from 'react'; +import TabStepper from '~/components/TabStepper'; +import { TabKey, TabPanel } from '../Tabs'; +import { TabStepItem } from './types'; +import { getContent } from './constants'; + +export default { + title: 'Updated Components/Tabs/TabStepper', + component: TabStepper, + + argTypes: { + orientation: { type: 'radio', options: ['horizontal', 'vertical'] }, + status: { type: 'select', options: ['pending', 'warning', 'done'] }, + status1: { name: 'Step 1 Status', type: 'select', options: ['pending', 'warning', 'done'] }, + status2: { name: 'Step 2 Status', type: 'select', options: ['pending', 'warning', 'done'] }, + status3: { name: 'Step 3 Status', type: 'select', options: ['pending', 'warning', 'done'] }, + hasSubtitle: { name: 'has Subtitle', type: 'boolean' }, + }, + + args: { + orientation: 'horizontal', + status: 'pending', + status1: 'pending', + status2: 'pending', + status3: 'pending', + hasSubtitle: false, + }, +}; + +export const TabStepperTypes = { + render: () => { + const [key1, setKey1] = React.useState('step_1'); + const [key2, setKey2] = React.useState('step_1'); + + const getItems = (hasSubtitle: boolean): TabStepItem[] => { + return [ + { + id: 'step_1', + title: 'Step 1', + ...(hasSubtitle && { subtitle: 'This is subtitle for step 1' }), + }, + { + id: 'step_2', + title: 'Step 2', + ...(hasSubtitle && { subtitle: 'This is subtitle for step 2' }), + }, + { + id: 'step_3', + title: 'Step 3', + ...(hasSubtitle && { subtitle: 'This is subtitle for step 3' }), + }, + ]; + }; + + return ( +
+ + +
+ ); + }, + + name: 'TabStepper types', + + parameters: { + controls: { disable: true }, + }, +}; + +export const TabStepperWithContent = { + render: () => { + const [key, setKey] = React.useState('step_1'); + + const items: TabStepItem[] = [ + { + id: 'step_1', + title: 'Step 1', + subtitle: 'This is subtitle for step 1', + }, + { + id: 'step_2', + title: 'Step 2', + subtitle: 'This is subtitle for step 2', + }, + { + id: 'step_3', + title: 'Step 3', + subtitle: 'This is subtitle for step 3', + }, + ]; + + return ( + + {items.map((item) => ( + + {getContent(item.id, 'horizontal')} + + ))} + + ); + }, + + name: 'TabStepper with content', + + parameters: { + controls: { disable: true }, + }, +}; + +export const TabStepperOrientation = { + render: (args) => { + const { orientation } = args; + + const [key, setKey] = React.useState('step_1'); + + const items: TabStepItem[] = [ + { + id: 'step_1', + title: 'Step 1', + subtitle: 'This is subtitle for step 1', + }, + { + id: 'step_2', + title: 'Step 2', + subtitle: 'This is subtitle for step 2', + }, + { + id: 'step_3', + title: 'Step 3', + subtitle: 'This is subtitle for step 3', + }, + ]; + + return ( + + {items.map((item) => ( + + {getContent(item.id, orientation)} + + ))} + + ); + }, + + name: 'Orientation', + + parameters: { + controls: { include: ['orientation'] }, + }, +}; + +export const TabStepperStatuses = { + render: (args) => { + const { status } = args; + + const [key, setKey] = React.useState('step_1'); + + const items: TabStepItem[] = [ + { + id: 'step_1', + title: 'Step 1', + subtitle: 'This is subtitle for step 1', + status, + }, + { + id: 'step_2', + title: 'Step 2', + subtitle: 'This is subtitle for step 2', + status, + }, + { + id: 'step_3', + title: 'Step 3', + subtitle: 'This is subtitle for step 3', + status, + }, + ]; + + return ( + + {items.map((item) => ( + + {getContent(item.id, 'horizontal')} + + ))} + + ); + }, + + name: 'TabStepper statuses', + + parameters: { + controls: { include: ['status'] }, + }, +}; + +export const Playground = { + render: (args) => { + const { orientation, status1, status2, status3, hasSubtitle } = args; + + const [key, setKey] = React.useState('step_1'); + + const getItems = (hasSubtitle: boolean): TabStepItem[] => { + return [ + { + id: 'step_1', + title: 'Step 1', + ...(hasSubtitle && { subtitle: 'This is subtitle for step 1' }), + status: status1, + }, + { + id: 'step_2', + title: 'Step 2', + ...(hasSubtitle && { subtitle: 'This is subtitle for step 2' }), + status: status2, + }, + { + id: 'step_3', + title: 'Step 3', + ...(hasSubtitle && { subtitle: 'This is subtitle for step 3' }), + status: status3, + }, + ]; + }; + + return ( + +
+ {getItems(hasSubtitle).map((item) => ( + + {getContent(item.id, orientation)} + + ))} +
+
+ ); + }, + + name: 'Playground', + + parameters: { + controls: { + include: ['orientation', 'Step 1 Status', 'Step 2 Status', 'Step 3 Status', 'has Subtitle'], + }, + }, +}; diff --git a/src/components/TabStepper/components/TabStep/TabStep.mdx b/src/components/TabStepper/components/TabStep/TabStep.mdx new file mode 100644 index 000000000..860ff7a7e --- /dev/null +++ b/src/components/TabStepper/components/TabStep/TabStep.mdx @@ -0,0 +1,56 @@ +import { Meta, Canvas, ArgTypes } from '@storybook/blocks'; +import TabStep from './TabStep'; + + + + + +## Overview + +TabStep component is used to render the main content of the TabsStepper. + +## Props + + + +## Usage + +Use TabStep as a leaf component to render the content of the TabStep as children. + + + Check also + [TabsContainer](../?path=/docs/updated-components-tabs-components-tabscontainer--overview) and + [TabStepList](../?path=/docs/updated-components-tabs-components-tabsteplist--overview) as they are + needed for the implementation. + + +```js + const [selectedKey, setSelectedKey] = React.useState('step_1'); + + return ( + // + // + + Tab 1 + + + Tab 2 + + + Tab 3 + + // + // + +``` diff --git a/src/components/TabStepper/components/TabStep/TabStep.stories.tsx b/src/components/TabStepper/components/TabStep/TabStep.stories.tsx new file mode 100644 index 000000000..6d777f51b --- /dev/null +++ b/src/components/TabStepper/components/TabStep/TabStep.stories.tsx @@ -0,0 +1,13 @@ +import TabStep from '.'; + +export default { + title: 'Updated Components/Tabs/Components/TabStep', + component: TabStep, + + parameters: { + storyshots: { + disable: true, + }, + controls: { disable: true }, + }, +}; diff --git a/src/components/TabStepper/components/TabStepList/TabStepList.mdx b/src/components/TabStepper/components/TabStepList/TabStepList.mdx new file mode 100644 index 000000000..e7efe125b --- /dev/null +++ b/src/components/TabStepper/components/TabStepList/TabStepList.mdx @@ -0,0 +1,61 @@ +import { Meta, Canvas, ArgTypes } from '@storybook/blocks'; +import TabStepList from './TabStepList'; + + + + + +## Overview + +TabStepList is the wrapper of the TabStep components. Here you can use the sx prop to add custom styles for the TabSteps. + +## Props + + + +## Usage + +Use the TabStepList to add aria-label and style overrides. + + + Check also + [TabsContainer](../?path=/docs/updated-components-tabs-components-tabscontainer--overview) and + [TabStep](../?path=/docs/updated-components-tabs-components-tabstep--overview) as they are needed + for the implementation. + + + + When using custom tab stepper components for children, use `role='tab'` on every child tab in + order for the TabStepList styles to be applied. + + +```js + const [selectedKey, setSelectedKey] = React.useState('step_1'); + + return ( + // + + // + // Tab 1 + // + // + // Tab 2 + // + // + // Tab 3 + // + + // + +``` diff --git a/src/components/TabStepper/components/TabStepList/TabStepList.stories.tsx b/src/components/TabStepper/components/TabStepList/TabStepList.stories.tsx new file mode 100644 index 000000000..32d7db1fd --- /dev/null +++ b/src/components/TabStepper/components/TabStepList/TabStepList.stories.tsx @@ -0,0 +1,13 @@ +import TabStepList from '.'; + +export default { + title: 'Updated Components/Tabs/Components/TabStepList', + component: TabStepList, + + parameters: { + storyshots: { + disable: true, + }, + controls: { disable: true }, + }, +}; diff --git a/src/components/TabStepper/constants.tsx b/src/components/TabStepper/constants.tsx new file mode 100644 index 000000000..873fea06f --- /dev/null +++ b/src/components/TabStepper/constants.tsx @@ -0,0 +1,46 @@ +import type { TabOrientation } from 'index'; +import { TextArea, TextField } from 'index'; + +import { Radio, RadioGroup } from '../Controls'; + +export const getContent = (key: string, orientation: TabOrientation) => { + if (key === 'step_1') { + return ( +
+ + + +
+ ); + } + + if (key === 'step_2') { + return ( + + Option 1 + Option 2 + Option 3 + + ); + } + + return ( +
+