forked from cerc-io/snowballtools-base
⚡️ feat: implement steps/ timeline
This commit is contained in:
parent
adb64f2db1
commit
6c42a22972
@ -0,0 +1,43 @@
|
|||||||
|
import { VariantProps, tv } from 'tailwind-variants';
|
||||||
|
|
||||||
|
export const stepTheme = tv({
|
||||||
|
slots: {
|
||||||
|
wrapper: ['relative', 'px-1.5', 'py-1.5', 'flex', 'gap-2', 'items-center'],
|
||||||
|
step: [
|
||||||
|
'bg-base-bg-emphasized',
|
||||||
|
'rounded-full',
|
||||||
|
'w-7',
|
||||||
|
'h-7',
|
||||||
|
'flex',
|
||||||
|
'items-center',
|
||||||
|
'justify-center',
|
||||||
|
'text-elements-mid-em',
|
||||||
|
'shadow-button',
|
||||||
|
'shrink-0',
|
||||||
|
],
|
||||||
|
label: ['text-sm', 'font-sans', 'text-elements-mid-em'],
|
||||||
|
connector: ['bg-border-interactive-hovered'],
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
orientation: {
|
||||||
|
vertical: { connector: ['w-px', 'h-3', 'ml-5'] },
|
||||||
|
horizontal: { connector: ['h-px', 'w-full'] },
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
true: {
|
||||||
|
step: ['bg-controls-secondary-hovered', 'text-elements-on-secondary'],
|
||||||
|
label: ['text-elements-high-em'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completed: {
|
||||||
|
true: {
|
||||||
|
step: ['text-controls-primary'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
orientation: 'vertical',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type StepTheme = VariantProps<typeof stepTheme>;
|
60
packages/frontend/src/components/shared/Steps/Step/Step.tsx
Normal file
60
packages/frontend/src/components/shared/Steps/Step/Step.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import React, { useCallback, ComponentPropsWithoutRef } from 'react';
|
||||||
|
import { stepTheme, StepTheme } from './Step.theme';
|
||||||
|
import { CheckRoundFilledIcon } from 'components/shared/CustomIcon';
|
||||||
|
|
||||||
|
export interface StepProps extends ComponentPropsWithoutRef<'li'>, StepTheme {
|
||||||
|
/**
|
||||||
|
* The label for the step
|
||||||
|
*/
|
||||||
|
label: string;
|
||||||
|
/**
|
||||||
|
* The index of the step
|
||||||
|
*/
|
||||||
|
index: number;
|
||||||
|
/**
|
||||||
|
* The total number of steps
|
||||||
|
*/
|
||||||
|
currentIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Step = ({
|
||||||
|
label,
|
||||||
|
index,
|
||||||
|
currentIndex,
|
||||||
|
orientation,
|
||||||
|
...props
|
||||||
|
}: StepProps) => {
|
||||||
|
const theme = stepTheme();
|
||||||
|
|
||||||
|
const active = currentIndex === index;
|
||||||
|
const completed = currentIndex > index;
|
||||||
|
|
||||||
|
const renderConnector = useCallback(
|
||||||
|
(index: number) => {
|
||||||
|
if (index === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div aria-hidden className={theme.connector({ orientation })} />;
|
||||||
|
},
|
||||||
|
[theme],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{renderConnector(index)}
|
||||||
|
<li className={theme.wrapper()} {...props}>
|
||||||
|
{
|
||||||
|
<div className={theme.step({ active, completed })}>
|
||||||
|
{completed ? (
|
||||||
|
<CheckRoundFilledIcon className="w-full h-full" />
|
||||||
|
) : (
|
||||||
|
index + 1
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<p className={theme.label()}>{label}</p>
|
||||||
|
</li>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './Step';
|
||||||
|
export * from './Step.theme';
|
18
packages/frontend/src/components/shared/Steps/Steps.theme.ts
Normal file
18
packages/frontend/src/components/shared/Steps/Steps.theme.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { VariantProps, tv } from 'tailwind-variants';
|
||||||
|
|
||||||
|
export const stepsTheme = tv({
|
||||||
|
slots: {
|
||||||
|
root: [],
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
orientation: {
|
||||||
|
vertical: { root: ['flex', 'flex-col'] },
|
||||||
|
horizontal: { root: ['flex', 'items-center'] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
orientation: 'vertical',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type StepsTheme = VariantProps<typeof stepsTheme>;
|
42
packages/frontend/src/components/shared/Steps/Steps.tsx
Normal file
42
packages/frontend/src/components/shared/Steps/Steps.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import React, { Fragment, ComponentPropsWithoutRef } from 'react';
|
||||||
|
import { stepsTheme, StepsTheme } from './Steps.theme';
|
||||||
|
import { Step, StepProps, StepTheme } from './Step';
|
||||||
|
|
||||||
|
interface StepsProps
|
||||||
|
extends ComponentPropsWithoutRef<'ul'>,
|
||||||
|
StepsTheme,
|
||||||
|
Pick<StepTheme, 'orientation'> {
|
||||||
|
/**
|
||||||
|
* The index of the current step
|
||||||
|
*/
|
||||||
|
currentIndex: number;
|
||||||
|
/**
|
||||||
|
* The steps to render
|
||||||
|
*/
|
||||||
|
steps: Pick<StepProps, 'label'>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Steps = ({
|
||||||
|
currentIndex,
|
||||||
|
steps = [],
|
||||||
|
className,
|
||||||
|
orientation,
|
||||||
|
...props
|
||||||
|
}: StepsProps) => {
|
||||||
|
const theme = stepsTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className={theme.root({ class: className, orientation })} {...props}>
|
||||||
|
{steps.map((step, i) => (
|
||||||
|
<Fragment key={i}>
|
||||||
|
<Step
|
||||||
|
{...step}
|
||||||
|
orientation={orientation}
|
||||||
|
currentIndex={currentIndex}
|
||||||
|
index={i}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
2
packages/frontend/src/components/shared/Steps/index.ts
Normal file
2
packages/frontend/src/components/shared/Steps/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './Steps';
|
||||||
|
export * from './Steps.theme';
|
Loading…
Reference in New Issue
Block a user