diff --git a/src/components/Stepper/README-ru.md b/src/components/Stepper/README-ru.md index b7f98daaa..7167f3277 100644 --- a/src/components/Stepper/README-ru.md +++ b/src/components/Stepper/README-ru.md @@ -170,6 +170,27 @@ const Separator = () => { +### Step with floating element + + + +```jsx +}> + + Step 1 + + Step 2 + Step 3 + Step 4 with very long title + +``` + + + + + + + ## Properties | Name | Description | Type | Default | diff --git a/src/components/Stepper/README.md b/src/components/Stepper/README.md index c3f5b3d73..141ca3928 100644 --- a/src/components/Stepper/README.md +++ b/src/components/Stepper/README.md @@ -170,6 +170,29 @@ const Separator = () => { + + +### Step with floating element + + + +```jsx +}> + + Step 1 + + Step 2 + Step 3 + Step 4 with very long title + +``` + + + + + + + ## Properties | Name | Description | Type | Default | diff --git a/src/components/Stepper/Stepper.scss b/src/components/Stepper/Stepper.scss index d5a009f19..08f3c1dab 100644 --- a/src/components/Stepper/Stepper.scss +++ b/src/components/Stepper/Stepper.scss @@ -11,9 +11,6 @@ $block: '.#{variables.$ns}stepper'; display: flex; gap: var(--_--step-gap); - flex-wrap: nowrap; - overflow-x: auto; - &__list-item { display: flex; flex-wrap: nowrap; @@ -22,10 +19,6 @@ $block: '.#{variables.$ns}stepper'; } &__item { - &:hover:not(&_disabled) { - background-color: var(--g-color-base-generic); - } - &_selected:not(&_disabled) { border-color: var(--g-color-line-info); } diff --git a/src/components/Stepper/Stepper.tsx b/src/components/Stepper/Stepper.tsx index 4186bb2c9..8e8037779 100644 --- a/src/components/Stepper/Stepper.tsx +++ b/src/components/Stepper/Stepper.tsx @@ -6,17 +6,17 @@ import type {AriaLabelingProps, DOMProps, QAProps} from '../types'; import {filterDOMProps} from '../utils/filterDOMProps'; import {StepperItem} from './StepperItem'; -import type {StepperItemProps} from './StepperItem'; import {StepperSeparator} from './StepperSeparator'; +import {StepperContext} from './context'; import type {StepperSize} from './types'; import {b} from './utils'; import './Stepper.scss'; export interface StepperProps extends DOMProps, AriaLabelingProps, QAProps { - children: React.ReactElement | React.ReactElement[]; + children: React.ReactElement | React.ReactElement[]; value?: number | string; - onUpdate: (id?: number | string) => void; + onUpdate?: (id?: number | string) => void; size?: StepperSize; separator?: React.ReactNode; } @@ -26,42 +26,33 @@ export const Stepper = (props: StepperProps) => { const stepItems = React.useMemo(() => { return React.Children.map(children, (child, index) => { - const id = child.props.id ?? index; + const itemId = child.props?.id || index; + const clonedChild = React.cloneElement(child, {id: itemId}); return ( -
  • - +
  • + {clonedChild} {Boolean(index !== React.Children.count(children) - 1) && ( )}
  • ); }); - }, [children, value, size, onUpdate, separator]); + }, [children, separator]); return ( -
      - {stepItems} -
    + +
      + {stepItems} +
    +
    ); }; -function Item(_props: StepperItemProps): React.ReactElement | null { - return null; -} - -Stepper.Item = Item; +Stepper.Item = StepperItem; Stepper.displayName = 'Stepper'; - -export default Stepper; diff --git a/src/components/Stepper/StepperItem.tsx b/src/components/Stepper/StepperItem.tsx index a2a3719e6..d3b42bece 100644 --- a/src/components/Stepper/StepperItem.tsx +++ b/src/components/Stepper/StepperItem.tsx @@ -3,17 +3,16 @@ import * as React from 'react'; import {CircleCheck, CircleDashed, CircleExclamation} from '@gravity-ui/icons'; import {Button} from '../Button'; +import type {ButtonButtonProps} from '../Button'; import {Icon} from '../Icon'; import type {SVGIconData} from '../Icon/types'; import {Text} from '../Text'; -import type {StepperProps} from './Stepper'; +import {useStepperContext} from './context'; import type {StepperItemView} from './types'; import {b} from './utils'; -import './Stepper.scss'; - -export interface StepperItemProps { +export type StepperItemProps = Omit & { id?: string | number; children: React.ReactNode; view?: StepperItemView; @@ -21,24 +20,21 @@ export interface StepperItemProps { icon?: SVGIconData; onClick?: (event: React.MouseEvent) => void; className?: string; -} - -type ComponentProps = StepperItemProps & - Pick & {selected?: string | number}; +}; -export const StepperItem = (props: ComponentProps) => { +export const StepperItem = React.forwardRef((props, ref) => { const { id, - size, children, view = 'idle', disabled = false, - selected = false, className, - onUpdate, icon: customIcon, + ...restButtonProps } = props; + const {onUpdate, value, size} = useStepperContext(); + const onClick = (e: React.MouseEvent) => { props.onClick?.(e); @@ -66,10 +62,12 @@ export const StepperItem = (props: ComponentProps) => { } }, [view, customIcon]); - const selectedItem = id === selected; + const selectedItem = id === undefined ? false : id === value; return ( ); -}; +}); + +StepperItem.displayName = 'StepperItem'; diff --git a/src/components/Stepper/StepperSeparator.tsx b/src/components/Stepper/StepperSeparator.tsx index ac54ff1ed..01c966615 100644 --- a/src/components/Stepper/StepperSeparator.tsx +++ b/src/components/Stepper/StepperSeparator.tsx @@ -1,19 +1,19 @@ -import {ChevronRight} from '@gravity-ui/icons'; +import {ChevronLeft, ChevronRight} from '@gravity-ui/icons'; import {Icon} from '../Icon'; +import {useDirection} from '../theme'; import {b} from './utils'; -import './Stepper.scss'; - type StepperSeparatorProps = { separator?: React.ReactNode; }; export const StepperSeparator = ({separator}: StepperSeparatorProps) => { + const direction = useDirection(); return (
    - {separator ?? } + {separator ?? }
    ); }; diff --git a/src/components/Stepper/__stories__/Docs.mdx b/src/components/Stepper/__stories__/Docs.mdx index 6c6e1a242..ed08c8aef 100644 --- a/src/components/Stepper/__stories__/Docs.mdx +++ b/src/components/Stepper/__stories__/Docs.mdx @@ -17,6 +17,9 @@ export const StepperCustomSeparator = () => ( ); export const StepperDisabled = () => ; +export const StepperWithFloatingElements = () => ( + +); export const StepperInteractiveShowcase = () => ( ); @@ -36,6 +39,7 @@ export const StepperInteractiveShowcase = () => ( StepperCustomSeparator, StepperDisabled, StepperInteractiveShowcase, + StepperWithFloatingElements, }, }} > diff --git a/src/components/Stepper/__stories__/Stepper.stories.tsx b/src/components/Stepper/__stories__/Stepper.stories.tsx index 83e641883..1d5773e14 100644 --- a/src/components/Stepper/__stories__/Stepper.stories.tsx +++ b/src/components/Stepper/__stories__/Stepper.stories.tsx @@ -2,10 +2,10 @@ import {Cloud, CreditCard, Rocket} from '@gravity-ui/icons'; import type {Meta, StoryObj} from '@storybook/react'; import {Text} from '../../Text'; -import {Flex} from '../../layout'; +import {Tooltip} from '../../Tooltip'; import {Stepper} from '../Stepper'; -import {StepperShowcase} from './StepperShowcase'; +import {StepperInteractiveShowcase, StepperSizeShowcase} from './StepperShowcase'; export default { title: 'Components/Navigation/Stepper', @@ -59,28 +59,8 @@ export const View = { } satisfies Story; export const Size = { - render: (args) => { - return ( - - - Step 1 - Step 2 - Step 3 - - - - Step 1 - Step 2 - Step 3 - - - - Step 1 - Step 2 - Step 3 - - - ); + render: () => { + return ; }, } satisfies Story; @@ -133,6 +113,21 @@ export const CustomSeparator = { export const InteractiveShowcase = { render: (args) => { - return ; + return ; + }, +} satisfies Story; + +export const WithFloatingElements = { + render: (args) => { + return ( + }> + + Step 1 + + Step 2 + Step 3 + Step 4 with very long title + + ); }, } satisfies Story; diff --git a/src/components/Stepper/__stories__/StepperShowcase.tsx b/src/components/Stepper/__stories__/StepperShowcase.tsx index 8353ae126..bd8d8bdbb 100644 --- a/src/components/Stepper/__stories__/StepperShowcase.tsx +++ b/src/components/Stepper/__stories__/StepperShowcase.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; -import Stepper from '../Stepper'; +import {Flex} from '../../layout/Flex/Flex'; +import {Stepper} from '../Stepper'; import type {StepperProps} from '../Stepper'; -export const StepperShowcase = (props: StepperProps) => { +export const StepperInteractiveShowcase = (props: StepperProps) => { const [value, setValue] = React.useState(0); return ( @@ -15,3 +16,27 @@ export const StepperShowcase = (props: StepperProps) => { ); }; + +export const StepperSizeShowcase = () => { + return ( + + + Step 1 + Step 2 + Step 3 + + + + Step 1 + Step 2 + Step 3 + + + + Step 1 + Step 2 + Step 3 + + + ); +}; diff --git a/src/components/Stepper/context.ts b/src/components/Stepper/context.ts new file mode 100644 index 000000000..7d1f54807 --- /dev/null +++ b/src/components/Stepper/context.ts @@ -0,0 +1,17 @@ +import * as React from 'react'; + +import type {StepperProps} from './Stepper'; + +export type StepperContextProps = Pick; + +export const StepperContext = React.createContext({ + size: 'm', + onUpdate: undefined, + value: undefined, +}); + +export const useStepperContext = () => { + const data = React.useContext(StepperContext); + + return data; +};