Skip to content
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

Open
wants to merge 135 commits into
base: main
Choose a base branch
from

Conversation

damianricobelli
Copy link
Contributor

@damianricobelli damianricobelli commented May 8, 2023

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:

const steps = [
  { label: "Step 1" },
  { label: "Step 2" },
  { label: "Step 3" },
] satisfies StepConfig[]

export const StepperDemo = () => {
  const {
    nextStep,
    prevStep,
    resetSteps,
    setStep,
    activeStep,
    isDisabledStep,
    isLastStep,
    isOptionalStep,
  } = useStepper({
    initialStep: 0,
    steps,
  })

  return (
    <>
      <Steps activeStep={activeStep}>
        {steps.map((step, index) => (
          <Step index={index} key={index} {...step}>
            <div className="bg-muted h-40 w-full p-4">
              <p>Step {index + 1} content</p>
            </div>
          </Step>
        ))}
      </Steps>
      <div className="flex items-center justify-end gap-2">
        {activeStep === steps.length ? (
          <>
            <h2>All steps completed!</h2>
            <Button onClick={resetSteps}>Reset</Button>
          </>
        ) : (
          <>
            <Button disabled={isDisabledStep} onClick={prevStep}>
              Prev
            </Button>
            <Button onClick={nextStep}>
              {isLastStep ? "Finish" : isOptionalStep ? "Skip" : "Next"}
            </Button>
          </>
        )}
      </div>
    </>
  )
}

Here is a complete video of the different use cases:

Grabacion.de.pantalla.2023-05-08.a.la.s.13.14.45.mov

@vercel
Copy link

vercel bot commented May 8, 2023

@damianricobelli is attempting to deploy a commit to the shadcn-pro Team on Vercel.

A member of the Team first needs to authorize it.

@damianricobelli damianricobelli changed the title feat(stepper): add component with docs feat(stepper): new stepper component May 8, 2023
@vercel
Copy link

vercel bot commented May 8, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
ui ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 17, 2024 7:14pm
1 Ignored Deployment
Name Status Preview Comments Updated (UTC)
next-template ⬜️ Ignored (Inspect) Visit Preview Apr 17, 2024 7:14pm

@jocarrd
Copy link

jocarrd commented May 9, 2023

love it 👀

@shadcn
Copy link
Collaborator

shadcn commented May 9, 2023

This looks incredible @damianricobelli I'll review.

@its-monotype
Copy link
Contributor

its-monotype commented May 9, 2023

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.
https://github.com/saas-js/saas-ui/tree/main/packages/saas-ui-core/src/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.

@damianricobelli
Copy link
Contributor Author

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. https://github.com/saas-js/saas-ui/tree/main/packages%2Fsaas-ui-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 🫶

@damianricobelli
Copy link
Contributor Author

@shadcn What do you think about this component? Do you think we should adjust anything so that it can be launched on prod?

@destino92
Copy link

Is this is still in progress?

@damianricobelli
Copy link
Contributor Author

damianricobelli commented Jun 20, 2023

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.

@drewhoffer
Copy link

This looks good!

@dan5py
Copy link
Contributor

dan5py commented Jun 30, 2023

Hi @damianricobelli, this component looks very good. Could you please update it to the new version (different themes, registry, docs, etc.)?

@damianricobelli
Copy link
Contributor Author

@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.

@damianricobelli
Copy link
Contributor Author

@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 🤩 🚀

@damianricobelli
Copy link
Contributor Author

damianricobelli commented Jul 4, 2023

Hi @damianricobelli, this component looks very good. Could you please update it to the new version (different themes, registry, docs, etc.)?

Done @dan5py! 🥳

@ImanMahmoudinasab
Copy link

@damianricobelli Thank you for your amazing contribution. I have two questions?

  1. Does new stepper allow its states, e.g. current step, being controlled - passed by its parent?
  2. Can we add/remove steps without losing states?

@merodiro
Copy link

Is it possible to call defineStepper inside the component with dynamic data or does it have to be stable?

@damianricobelli
Copy link
Contributor Author

@ImanMahmoudinasab

  1. can you please show me an example?
  2. If you are referring to being able to remove steps and for it to be visible to the instance, no. That is not possible in runtime with TS. If you define 3 steps, there will be 3 valid steps unless you use your own logic to indicate that any of those steps should not be shown.

@damianricobelli
Copy link
Contributor Author

@merodiro will be stable since you cannot modify the type at runtime. If you need to modify it, you should add some state that stores metadata and can be modified.

I would like to be able to work on an idea of dynamic metadata without being typesafe in the future to solve this

@merodiro
Copy link

@damianricobelli I'm trying to change the metadata dynamically for example changing the title based on the locale or changing something in the schema based on data from the server. Can I change them dynamically or do I need to call defineStepper only once and keep its reference stable across renders?

@damianricobelli
Copy link
Contributor Author

@merodiro if you need to change texts according to locale and you are using a library for this, I recommend using the keys of the terms instead of the text in each of your steps

@damianricobelli
Copy link
Contributor Author

@stepperize/react v5 is out! Dynamic data with the new Metadata API 🔥

cc: @merodiro

@matheussatoshi
Copy link

matheussatoshi commented Jan 30, 2025

Ei@damianricobellie@resatyildiz, Acho que o componente stepper não deve ser vinculado a rotas internamente. Em vez disso, devemos usar um stepper de forma controlada e definir rotas para cada etapa. Quando as rotas mudarem, passe o índice ou ID da etapa relevante para o stepper. Quando o stepper for para a próxima etapa ou para a anterior, atualize a rota da página.

Concordo. Estou tendo bastante dificuldade com o controle de passo com a biblioteca stepperize subjacente. Tanto o controle externo quanto, alternativamente, um onStepChangeaffordance de retorno de chamada parecem usos intuitivos, nenhum dos quais parece fazer parte da API (talvez eles estejam lá, só não documentados??).

Meu caso de uso é um formulário multietapas em que cada etapa deve ser validada antes de permitir a progressão para etapas posteriores. Estou tendo uma quantidade surpreendente de dificuldade para sincronizar este componente com uma loja zustand.

No momento, recorri à implementação manual da lógica "canPrevious", "canNext" no zustand store, com uma assinatura para chamar o goTométodo. Se houver algo que eu esteja esquecendo, o feedback é bem-vindo.

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>
  )
}

I think this design can help you if you need it...

Captura de tela 2025-01-29 231605
Captura de tela 2025-01-29 231611
Captura de tela 2025-01-29 231536
Captura de tela 2025-01-29 231543

https://www.figma.com/design/EaDyK1l09bi7mxaSl5stv4/Untitled?node-id=0-1&p=f&t=dG2WqjbwIsXssRio-0

@shadcn
Copy link
Collaborator

shadcn commented Jan 30, 2025

@damianricobelli Thanks for your work on this. I'll review and make an official component once I'm done with the Tailwind v4 upgrade.

@damianricobelli
Copy link
Contributor Author

New and finals improvements will come this weekend to finalize the component

cc: @shadcn

@damianricobelli
Copy link
Contributor Author

Done! Component updated, documents updated. Completely typesafe

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: roadmap This looks great. We'll add it to the roadmap, review and merge. new component
Projects
None yet
Development

Successfully merging this pull request may close these issues.