Skip to content

Commit

Permalink
feat(progress-tracker): add disabled and readonly features
Browse files Browse the repository at this point in the history
  • Loading branch information
soykje committed Dec 7, 2023
1 parent 1e07cbb commit 7f27df7
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 34 deletions.
12 changes: 8 additions & 4 deletions packages/components/progress-tracker/src/ProgressTracker.doc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ import { ProgressTracker } from '@spark-ui/progress-tracker'

Use `readOnly` prop to indicate that the component is not interactive.

<Canvas of={stories.Readonly} />

### Disabled

Use `disabled` prop on a step to disable it.

<Canvas of={stories.Disabled} />

### Sizes

Use `size` prop to set the size of the progress indicators. Smallest size doesn't get any content.
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,37 @@ const sizes: ProgressTrackerProps['size'][] = ['sm', 'md', 'lg']

export const Default: StoryFn = _args => (
<ProgressTracker stepIndex={1} onStepClick={id => console.log('Clicked on', id)}>
<ProgressTracker.Step label="Etape 1" />
<ProgressTracker.Step label="Etape 2" />
<ProgressTracker.Step label="Etape 3" />
<ProgressTracker.Step label="Build" />
<ProgressTracker.Step label="Deploy" />
<ProgressTracker.Step label="Iterate" />
</ProgressTracker>
)

export const Disabled: StoryFn = _args => (
<ProgressTracker stepIndex={1} onStepClick={id => console.log('Clicked on', id)}>
<ProgressTracker.Step label="Build lorem ipsum" />
<ProgressTracker.Step label="Deploy" />
<ProgressTracker.Step label="Iterateazd aezjf iazoejf;ezfiopjaze fpoiezaf" disabled />
</ProgressTracker>
)

export const Readonly: StoryFn = _args => (
<ProgressTracker stepIndex={1} readOnly>
<ProgressTracker.Step label="Build" />
<ProgressTracker.Step label="Deploy" />
<ProgressTracker.Step label="Iterate" />
</ProgressTracker>
)

export const Size: StoryFn = _args => (
<div className="flex flex-wrap items-center gap-md">
<div className="flex flex-wrap items-center gap-lg">
{sizes.map(size => (
<div key={size}>
<StoryLabel>{`${size}${size === 'lg' ? ' (default)' : ''}`}</StoryLabel>
<ProgressTracker size={size as ProgressTrackerProps['size']}>
<ProgressTracker.Step label="Etape 1" />
<ProgressTracker.Step label="Etape 2" />
<ProgressTracker.Step label="Etape 3" />
<ProgressTracker stepIndex={1} size={size as ProgressTrackerProps['size']}>
<ProgressTracker.Step label="Build" />
<ProgressTracker.Step label="Deploy" />
<ProgressTracker.Step label="Iterate" />
</ProgressTracker>
</div>
))}
Expand Down
Empty file.
22 changes: 17 additions & 5 deletions packages/components/progress-tracker/src/ProgressTracker.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -36,19 +39,28 @@ export const ProgressTracker = forwardRef<HTMLDivElement, PropsWithChildren<Prog
},
ref
) => {
const [steps, setSteps] = useState<Set<string>>(new Set())
const [steps, setSteps] = useState<ProgressTrackerContextInterface['steps']>(new Set())
const [stepIndex, setStepIndex] = useState<number>(propStepIndex)

const onStepClick = (stepId: string) => {
setStepIndex([...steps].indexOf(stepId))
const stepIndex = [...steps].findIndex(step => step.id === stepId)

Check warning on line 46 in packages/components/progress-tracker/src/ProgressTracker.tsx

View check run for this annotation

Codecov / codecov/patch

packages/components/progress-tracker/src/ProgressTracker.tsx#L46

Added line #L46 was not covered by tests

setStepIndex(stepIndex)
onStepClickProp?.(stepId)

Check warning on line 49 in packages/components/progress-tracker/src/ProgressTracker.tsx

View check run for this annotation

Codecov / codecov/patch

packages/components/progress-tracker/src/ProgressTracker.tsx#L48-L49

Added lines #L48 - L49 were not covered by tests
}

const Component = readOnly ? 'div' : 'nav'

return (
<ProgressTrackerContext.Provider value={{ stepIndex, onStepClick, steps, setSteps, size }}>
<Component ref={ref} className={cx('inline-flex', className)} {...rest}>
<ProgressTrackerContext.Provider
value={{ stepIndex, onStepClick, steps, setSteps, size, readOnly }}
>
<Component
ref={ref}
aria-label="progress"
className={cx('inline-flex', className)}
{...rest}
>
<ol className="flex w-full flex-nowrap" style={{ counterReset: 'step' }}>
{children}
</ol>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ProgressTrackerProps, 'stepIndex' | 'onStepClick' | 'size'>
Pick<ProgressTrackerProps, 'stepIndex' | 'onStepClick' | 'size' | 'readOnly'>
> & {
steps: Set<string>
setSteps: Dispatch<SetStateAction<Set<string>>>
steps: Set<Step>
setSteps: Dispatch<SetStateAction<Set<Step>>>
}

export const ProgressTrackerContext = createContext<ProgressTrackerContextInterface>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
}
Expand Down Expand Up @@ -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,
Expand Down
32 changes: 21 additions & 11 deletions packages/components/progress-tracker/src/ProgressTrackerStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,53 @@ export const ProgressTrackerStep = forwardRef<HTMLLIElement, ProgressTrackerStep
onStepClick,
setSteps,
size,
readOnly,
} = useProgressTrackerContext()

const ID = useId()
const stepId = `step-${ID}`
const stepIndex = [...steps].findIndex(step => 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 })), [])

Check warning on line 28 in packages/components/progress-tracker/src/ProgressTrackerStep.tsx

View workflow job for this annotation

GitHub Actions / Lint

React Hook useEffect has missing dependencies: 'disabled', 'setSteps', and 'stepId'. Either include them or remove the dependency array

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 (
<li
id={stepId}
ref={ref}
data-state={progressState}
className={stepItemVariant({ size })}
aria-current={progressState === 'active'}
className={stepItemVariant({
size,
disabled,
disabledBefore: !![...steps][stepIndex - 1]?.disabled,
disabledAfter: !![...steps][stepIndex + 1]?.disabled,
})}
{...rest}
>
<button
type="button"
onClick={() => onStepClick(stepId)}
disabled={progressState === 'disabled'}
{...(!['disabled', 'active'].includes(`${progressState}`) &&
!readOnly && {
onClick: () => onStepClick(stepId),
})}
disabled={disabled}
className={stepButtonVariant({
complete: progressState === 'complete',
active: progressState === 'active',
disabled: progressState === 'disabled',
disabled,
readOnly,
size,
className,
})}
Expand Down

0 comments on commit 7f27df7

Please sign in to comment.