diff --git a/packages/components/progress-tracker/src/ProgressTracker.doc.mdx b/packages/components/progress-tracker/src/ProgressTracker.doc.mdx index 3b9cb9a30..f2667b9cf 100644 --- a/packages/components/progress-tracker/src/ProgressTracker.doc.mdx +++ b/packages/components/progress-tracker/src/ProgressTracker.doc.mdx @@ -47,6 +47,14 @@ import { ProgressTracker } from '@spark-ui/progress-tracker' Use `readOnly` prop to indicate that the component is not interactive. + + +### Disabled + +Use `disabled` prop on a step to disable it. + + + ### Sizes Use `size` prop to set the size of the progress indicators. Smallest size doesn't get any content. @@ -61,10 +69,6 @@ Use `intent` prop to set the color. Use `design` prop to set the look and feel. -### Disabled - -Use `disabled` prop on a step to disable it. - ### Orientation Use `orientation` prop to set `horizontal|vertical` orientation. diff --git a/packages/components/progress-tracker/src/ProgressTracker.stories.tsx b/packages/components/progress-tracker/src/ProgressTracker.stories.tsx index 6d76f9103..65e8ee72f 100644 --- a/packages/components/progress-tracker/src/ProgressTracker.stories.tsx +++ b/packages/components/progress-tracker/src/ProgressTracker.stories.tsx @@ -14,21 +14,37 @@ const sizes: ProgressTrackerProps['size'][] = ['sm', 'md', 'lg'] export const Default: StoryFn = _args => ( console.log('Clicked on', id)}> - - - + + + + +) + +export const Disabled: StoryFn = _args => ( + console.log('Clicked on', id)}> + + + + +) + +export const Readonly: StoryFn = _args => ( + + + + ) export const Size: StoryFn = _args => ( -
+
{sizes.map(size => (
{`${size}${size === 'lg' ? ' (default)' : ''}`} - - - - + + + +
))} diff --git a/packages/components/progress-tracker/src/ProgressTracker.styles.ts b/packages/components/progress-tracker/src/ProgressTracker.styles.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/components/progress-tracker/src/ProgressTracker.test.tsx b/packages/components/progress-tracker/src/ProgressTracker.test.tsx index 48e41e0c4..abcdb4861 100644 --- a/packages/components/progress-tracker/src/ProgressTracker.test.tsx +++ b/packages/components/progress-tracker/src/ProgressTracker.test.tsx @@ -2,26 +2,66 @@ import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { describe, expect, it, vi } from 'vitest' -import { ProgressTracker } from './ProgressTracker' +import { ProgressTracker } from './' + +const defaultProps = { + stepIndex: 1, + children: ( + <> + + + + + ), +} describe('ProgressTracker', () => { - it('should render', () => { - render(Hello World!) + beforeEach(() => vi.clearAllMocks()) + + it('should render with steps', () => { + render() + + expect(screen.getByLabelText('progress')).toBeInTheDocument() - expect(screen.getByText('Hello World!')).toBeInTheDocument() + expect(screen.getByText('Build').closest('li')).toHaveAttribute('data-state', 'complete') + expect(screen.getByText('Deploy').closest('li')).toHaveAttribute('data-state', 'active') + expect(screen.getByText('Iterate').closest('li')).not.toHaveAttribute('data-state') }) - it('should trigger click event', async () => { + it('should handle callback on step click, except if step is disabled', async () => { const user = userEvent.setup() - const clickEvent = vi.fn() - // Given - render(
Hello World!
) + const props = { + stepIndex: 1, + onStepClick: vi.fn(), + } + + render( + + + + + + ) + + await user.click(screen.getByText('Build')) + await user.click(screen.getByText('Iterate')) + + expect(props.onStepClick).toHaveBeenCalledTimes(1) + }) + + it('should not handle callback on readonly mode', async () => { + const user = userEvent.setup() + + const props = { + ...defaultProps, + onStepClick: vi.fn(), + } + + render() - // When - await user.click(screen.getByText('Hello World!')) + await user.click(screen.getByText('Build')) - // Then - expect(clickEvent).toHaveBeenCalledTimes(1) + expect(props.onStepClick).not.toHaveBeenCalled() }) }) diff --git a/packages/components/progress-tracker/src/ProgressTracker.tsx b/packages/components/progress-tracker/src/ProgressTracker.tsx index 1b9729bee..88b36839a 100644 --- a/packages/components/progress-tracker/src/ProgressTracker.tsx +++ b/packages/components/progress-tracker/src/ProgressTracker.tsx @@ -1,7 +1,10 @@ import { cx } from 'class-variance-authority' import { type ComponentPropsWithoutRef, forwardRef, type PropsWithChildren, useState } from 'react' -import { ProgressTrackerContext } from './ProgressTrackerContext' +import { + ProgressTrackerContext, + type ProgressTrackerContextInterface, +} from './ProgressTrackerContext' import type { ProgressTrackerStepVariantsProps } from './ProgressTrackerStep.styles' export interface ProgressTrackerProps @@ -36,19 +39,28 @@ export const ProgressTracker = forwardRef { - const [steps, setSteps] = useState>(new Set()) + const [steps, setSteps] = useState(new Set()) const [stepIndex, setStepIndex] = useState(propStepIndex) const onStepClick = (stepId: string) => { - setStepIndex([...steps].indexOf(stepId)) + const stepIndex = [...steps].findIndex(step => step.id === stepId) + + setStepIndex(stepIndex) onStepClickProp?.(stepId) } const Component = readOnly ? 'div' : 'nav' return ( - - + +
    {children}
diff --git a/packages/components/progress-tracker/src/ProgressTrackerContext.ts b/packages/components/progress-tracker/src/ProgressTrackerContext.ts index 8a6e448ff..ae3bad778 100644 --- a/packages/components/progress-tracker/src/ProgressTrackerContext.ts +++ b/packages/components/progress-tracker/src/ProgressTrackerContext.ts @@ -2,11 +2,16 @@ import { createContext, type Dispatch, type SetStateAction, useContext } from 'r import type { ProgressTrackerProps } from './ProgressTracker' +interface Step { + id: string + disabled: boolean +} + export type ProgressTrackerContextInterface = Required< - Pick + Pick > & { - steps: Set - setSteps: Dispatch>> + steps: Set + setSteps: Dispatch>> } export const ProgressTrackerContext = createContext( diff --git a/packages/components/progress-tracker/src/ProgressTrackerStep.styles.ts b/packages/components/progress-tracker/src/ProgressTrackerStep.styles.ts index 16b5a40bf..a351875f5 100644 --- a/packages/components/progress-tracker/src/ProgressTrackerStep.styles.ts +++ b/packages/components/progress-tracker/src/ProgressTrackerStep.styles.ts @@ -29,8 +29,23 @@ export const stepItemVariant = cva( 'before:right-[calc(50%+20px)] after:left-[calc(50%+20px)]', ], }, + disabled: { + true: 'before:opacity-dim-3 after:opacity-dim-3', + false: '', + }, + disabledBefore: { + true: 'before:opacity-dim-3', + false: '', + }, + disabledAfter: { + true: 'after:opacity-dim-3', + false: '', + }, }, defaultVariants: { + disabled: false, + disabledBefore: false, + disabledAfter: false, size: 'lg', }, } @@ -59,20 +74,33 @@ export const stepButtonVariant = cva( true: [ 'before:content-[url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PScwIDAgMjQgMjQnIGhlaWdodD0nMTYnIHdpZHRoPScxNicgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJyBmaWxsPSdjdXJyZW50Q29sb3InIHN0cm9rZT0nbm9uZSc+PHBhdGggZD0nbTguOTIsMTkuMDhjLS4xOCwwLS4zNi0uMDMtLjUzLS4xcy0uMzMtLjE3LS40Ny0uMzFsLTUuNDktNS4zNGMtLjI4LS4yOC0uNDItLjYxLS40Mi0xcy4xNC0uNzMuNDItMWMuMjgtLjI4LjYyLS40MSwxLjAyLS40MXMuNzQuMTQsMS4wNS40MWw0LjQzLDQuMywxMC42Mi0xMC4yOWMuMjgtLjI4LjYyLS40MiwxLjAyLS40My4zOSwwLC43My4xMywxLjAyLjQzLjI4LjI4LjQyLjYxLjQyLDFzLS4xNC43My0uNDIsMWwtMTEuNjUsMTEuMzJjLS4xNC4xNC0uMy4yNC0uNDcuMzEtLjE3LjA3LS4zNS4xLS41My4xWic+PC9wYXRoPjwvc3ZnPg==)]', 'before:inline-flex before:leading-none', - 'hover:before:bg-basic/dim-5', ], false: '', }, active: { - true: ['before:bg-basic-container', 'hover:before:bg-basic/dim-5'], + true: ['before:bg-basic-container', 'cursor-default'], false: '', }, disabled: { - true: 'opacity-dim-3', + true: 'before:opacity-dim-3', + false: '', + }, + readOnly: { + true: ['cursor-default'], false: '', }, }, + compoundVariants: [ + { + readOnly: false, + disabled: false, + active: false, + complete: [true, false], + class: 'hover:before:bg-basic/dim-5', + }, + ], defaultVariants: { + readOnly: false, disabled: false, active: false, complete: false, diff --git a/packages/components/progress-tracker/src/ProgressTrackerStep.tsx b/packages/components/progress-tracker/src/ProgressTrackerStep.tsx index fc0886832..25bdb259a 100644 --- a/packages/components/progress-tracker/src/ProgressTrackerStep.tsx +++ b/packages/components/progress-tracker/src/ProgressTrackerStep.tsx @@ -16,43 +16,57 @@ export const ProgressTrackerStep = forwardRef step.id === stepId) - const [progressState, setProgressState] = useState< - 'active' | 'complete' | 'disabled' | undefined - >(() => (disabled ? 'disabled' : undefined)) + const [progressState, setProgressState] = useState<'active' | 'complete' | undefined>() - useEffect(() => setSteps(steps => steps.add(stepId)), []) + useEffect( + () => setSteps(steps => steps.add({ id: stepId, disabled })), + [disabled, stepId, setSteps] + ) useEffect(() => { - if ([...steps].indexOf(stepId) === activeStepIndex) { + if (stepIndex === activeStepIndex) { setProgressState('active') - } else if ([...steps].indexOf(stepId) < activeStepIndex) { + } else if (stepIndex < activeStepIndex) { setProgressState('complete') } else { setProgressState(undefined) } - }, [activeStepIndex, steps, stepId]) + }, [activeStepIndex, stepIndex]) return (