-
Notifications
You must be signed in to change notification settings - Fork 5.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(stepper): new stepper component #318
base: main
Are you sure you want to change the base?
Conversation
@damianricobelli is attempting to deploy a commit to the shadcn-pro Team on Vercel. A member of the Team first needs to authorize it. |
The latest updates on your projects. Learn more about Vercel for Git ↗︎
1 Ignored Deployment
|
love it 👀 |
This looks incredible @damianricobelli I'll review. |
Looks amazing 😍 Unfortunately I don't have time to review and research about this component. However, look what I recently found: https://saas-ui.dev/docs/components/navigation/stepper. This can serve as reference to improve or borrow ideas to simplify the implementation. From what I can suggest it is to rename Step to StepperStep and useSteps to useStepper to comply with the general API conventions of the components and it will be more unique name to prevent conflicts. |
Thank you very much for your feedback! I'll be reviewing tomorrow what you just shared and your suggestions 🫶 |
@shadcn What do you think about this component? Do you think we should adjust anything so that it can be launched on prod? |
Is this is still in progress? |
@destino92 From my side the component is ready. Just need to know if @shadcn agrees to move forward and add it to the CLI that brings and details that you think are missing in terms of documentation. |
This looks good! |
Hi @damianricobelli, this component looks very good. Could you please update it to the new version (different themes, registry, docs, etc.)? |
@dan5py yes of course. Between today and Monday I will be making the necessary changes so that the component allows the last addition you mention. |
@shadcn Could you check this? I've already updated the code with all the latest stuff in the main branch. There are already several people watching the release of this component 🤩 🚀 |
Done @dan5py! 🥳 |
My goal is to maximize the flexibility of this component by allowing developers to use basic cases, but if they require something more advanced, they can do so as well |
Stepperize 4.1 is out! Next step: document the components and update this PR. I will do it in the next weeks |
@damianricobelli thnx for the good work only how to implement this in because your code with the |
Exploring and polishing the API for shadcn. getting closer! I've been thinking about the power of Scoped + typesafe for a while now, and I think I've found the right API. This is an example to generate the same circled example we have in the stepperize documentation. The key is that by default a Scope will be created (you can avoid it, I will explain it in the docs) and you will have access both in the navigation and in the panels to the stepper methods (coming from useStepper). export const Testing = () => {
return (
<Stepper
instance={stepperInstance}
variant="circle"
className="space-y-6 p-6 border rounded-lg w-[450px] m-4"
>
<StepperNavigation>
{({ methods }) => {
return steps.map((step) => (
<StepperStep
key={step.id}
of={step}
onClick={() => methods.goTo(step.id)}
icon={step.icon}
>
<StepperTitle>{step.title}</StepperTitle>
<StepperDescription>{step.description}</StepperDescription>
</StepperStep>
))
}}
</StepperNavigation>
{steps.map((step) => {
return (
<StepperPanel key={step.id} when={step}>
{({ step, methods }) => {
return (
<div className="flex flex-col gap-2">
<span>Content for {step.title} step</span>
<div className="space-y-4">
{!methods.isLast ? (
<div className="flex justify-end gap-4">
<Button
variant="secondary"
onClick={methods.prev}
disabled={methods.isFirst}
>
Back
</Button>
<Button onClick={methods.next}>
{methods.isLast ? "Complete" : "Next"}
</Button>
</div>
) : (
<Button onClick={methods.reset}>Reset</Button>
)}
</div>
</div>
)
}}
</StepperPanel>
)
})}
</Stepper>
)
} And the result: |
This looks awesome! Is there a way to make the stepper responsive by default? Switch between horz/vert on desktop/mobile? |
@kmalloy24 I will make this possible through a hook that checks matchMedia API 🤝 |
d5fbdb6
to
f8a0b2e
Compare
I am cleaning up the branch to initialize the new changes with the final version using |
- Introduced a new UI component "stepper" with its dependencies and file path. - Added an example component "stepper-demo" that utilizes the "stepper". - Updated registry files to include both components in the respective JSON and TypeScript files.
@damianricobelli hi! It's looking awesome, thank you for your effort. Have you thought before about bind popstate event to this component? You know, some user would want to back to prev step when click to browser back button. |
Hey @damianricobelli and @resatyildiz, I think stepper component shouldn't get bind to routes internally. Instead, we should use a stepper in a controlled way and define routes for each step. When the routes change, pass the relevant step index or ID to the stepper. When the stepper goes to the next or previous step, update the route of the page. |
@ImanMahmoudinasab yea, you're right. The component should not handle anything related to routing. Also, it is much simpler to handle query params instead of routes for something like a stepper. |
Estimated date for review of the PR by the entire community and @shadcn -> tomorrow 👀 |
@ImanMahmoudinasab actually I agree with you. Already it can bind to window history with query params. You can think this feature @damianricobelli |
All documentation and component ready for review! @shadcn 👍 |
Agreed. I'm having quite a bit of difficulty with step control with the underlying stepperize library. Either external control or alternatively an My use case is a multi-step form where each step must be validated prior to allowing progression to later steps. I'm having a surprising amount of difficulty synchronizing this component with a zustand store. Right now I've resorted to manually implementing the "canPrevious", "canNext" logic in the zustand store, with a subscription to call the import { CardContent, CardFooter, CardHeader } from '@repo/ui/components/card'
import {
defineStepper,
Stepper,
StepperAction,
StepperControls,
StepperNavigation,
StepperPanel,
StepperStep,
StepperTitle,
} from '@repo/ui/components/stepper'
import { SellPassStepOne } from './sell-pass-step-one'
import { SellPassStepThree } from './sell-pass-step-three'
import { SellPassStepTwo } from './sell-pass-step-two'
import { sellProductStore, useSellProductStore } from './sell-product.store'
import { useEffect } from 'react'
const stepper = defineStepper(
{
id: 'step1',
title: 'Type',
header: 'Sell a Pass or Enforcement?',
Component: SellPassStepOne,
},
{
id: 'step2',
title: 'Customer',
header: 'Please enter the customer details',
Component: SellPassStepTwo,
},
{
id: 'step3',
title: 'Payment',
header: 'Please enter the payment details',
Component: SellPassStepThree,
},
)
export const SellForm = () => {
const s = stepper.useStepper()
const store = useSellProductStore()
const steps = s.all
useEffect(() => {
sellProductStore.subscribe((state) => {
s.goTo(state.stepId)
})
}, [s])
return (
<Stepper instance={stepper}>
<CardHeader>
<StepperNavigation>
{({ methods }) =>
steps.map((step) => {
return (
<StepperStep
key={step.id}
of={step}
disabled={!store.enabledSteps.includes(step.id)}
onClick={() => store.setStep(step.id)}
>
<StepperTitle>{step.title}</StepperTitle>
</StepperStep>
)
})
}
</StepperNavigation>
</CardHeader>
<CardContent>
{steps.map(({ Component, ...step }) => (
<StepperPanel key={step.id} when={step}>
<Component />
</StepperPanel>
))}
</CardContent>
<CardFooter>
<StepperControls className="flex justify-items-end w-full">
<StepperAction action="prev" disabled={!store.canPrev}>
Previous
</StepperAction>
<StepperAction action="next" disabled={!store.canNext}>
Next
</StepperAction>
<StepperAction action="reset" disabled={!store.canReset}>
Reset
</StepperAction>
</StepperControls>
</CardFooter>
</Stepper>
)
} |
@JoelVenable I have added in the PR an example of a form with react hook form. And I have updated a little the final logic of methods, where they are now obtained as part of the children of to maintain the typesafe API. Remember that the useStepper hook will work in these components if you use it within the Stepper component since it is a Provider. |
Hi! In this opportunity I present a new component: Stepper.
The idea of this in its beginnings was to make it as modular and flexible as possible for development.
A basic example of the application is this:
Here is a complete video of the different use cases:
Grabacion.de.pantalla.2023-05-08.a.la.s.13.14.45.mov