-
-
Notifications
You must be signed in to change notification settings - Fork 75
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 component added #523
base: main
Are you sure you want to change the base?
Conversation
✅ Deploy Preview for kobalte ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR!
There's a few changes needed around accessibility, mostly using a <Tabs>
component internally instead of re-implementing them.
Thanks for the detailed review. I've push the new commit and used the |
The previous button is not functioning correctly. When at step 1, it works as expected. However, when navigating to step 2 and then clicking previous, instead of returning to step 1, it moves to step 3. |
its old deployment, I've fixed it in new commits. |
const [local, others] = splitProps(props as StepperNextTriggerProps, ["onClick"]); | ||
|
||
const onClick: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent> = (e) => { | ||
context.setStep(context.step() + 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice if you had context.moveForward
or context.moveNext
instead. It would be more encapsulated and straightforward and could be used more efficiently by end users.
Also consider adding something like context.canMoveForward
or context.canMoveNext
and call them in context.moveForward
or context.moveNext
so the logic can't be broken.
In addition, you will be able to directly use onClick={context.moveForward}
or onClick={context.moveNext}
.
const [local, others] = splitProps(props as StepperPrevTriggerProps, ["onClick"]); | ||
|
||
const onClick: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent> = (e) => { | ||
if (context.step() > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice if you had context.moveBackward
or context.movePrevious
instead. It would be more encapsulated and straightforward and could be used more efficiently by end users.
Also consider adding something like context.canMoveBackward
or context.canMovePrevious
and call them in context.moveBackward
or context.movePrevious
so the logic can't be broken.
In addition, you will be able to directly use onClick={context.moveBackward}
or onClick={context.movePrevious}
.
context.setStep(context.step() + 1); | ||
}; | ||
|
||
const isDisabled = () => context.isDisabled() || context.isCompleted(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you add something like context.canMoveForward
or context.canMoveNext
, add it here too.
} | ||
}; | ||
|
||
const isDisabled = () => context.isDisabled() || context.step() === 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you add something like context.canMoveBackward
or context.canMovePrevious
, add it here too.
const isDisabled = () => context.isDisabled() || context.step() === 0; | ||
|
||
return ( | ||
<Show when={!context.isCompleted()}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this logic be handled directly by end users? What if I still want to see these buttons and view all my steps?
disabled?: boolean; | ||
|
||
/** The maximum number of steps in the stepper. */ | ||
maxSteps: number; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can steps be added dynamically? If so, the term maxSteps
may not be appropriate. I would suggest using terms like stepsCount
or length
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are you suggesting, to handle this maxSteps count internally instead of having this as prop
? or suggesting it to change it's name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion, it would be more effective to manage the maxSteps
count internally rather than exposing it as a prop. This approach will minimize the potential for human errors and simplify the component overall. You might consider adding context.register
in the onMount
and context.unregister
in the onCleanup
method in the Stepper.Content
and maybe Stepper.CompletedContent
. These functions can be used to increment or decrement the stepsCount
or length
prop accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't the term StepperStep
more suitable than StepperContent
?
Additionally, can you determine if the current step is active within this component? It would also be helpful to listen for an event when the current step becomes active, especially in complex multi-step forms.
import { type ElementOf, type PolymorphicProps } from "../polymorphic"; | ||
|
||
export interface StepperContentOptions { | ||
index: number; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this index be calculated dynamically? For instance, if I don't need the step triggers at the top and want to have dynamic steps?
To accomplish this, I would use a "register and unregister" pattern to adjust the index dynamically and set it for the tab content.
See for example
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Usually, stepper forms are not dynamic and have a fixed number of steps. So, I thought it would be better not to consider this edge case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your approach is quite reasonable. In JSX, you can place step components anywhere and manually set their indexes.
However, let's consider a scenario where you have a room booking form. A room may or may not have zones (i.e. it's large or not), and these zones can also be booked.
In this case, if you have a room selection step and want to dynamically add the next step based on whether the selected room has zones, would it be more effective to implement a regular step skip (i.e. context.setCurrentIndex(context.currentIndex + 2)
) or to dynamically add a new step?
Additionally, how would this look if we use Stepper.Trigger
s for the steps? How will it be displayed visually? Let's say empty or not completed? In dynamic implementation there are also disadvantages for this: UI can "jump"
|
||
const onClick: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent> = (e) => { | ||
if (context.step() > 0) { | ||
context.setStep(context.step() + 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
context.setStep(context.step() + 1); | |
context.setStep(context.step() - 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, my bad! I think that was a typo—thanks for catching it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah! That's why I suggested adding functions like moveForward
and moveBackward
to the context. You'll reduce the number of human typos like this!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about adding another example without step triggers? I think it will be useful for some people
Why are there two components |
In the form example the "Enter" key is ignored. I think this is an accessibility issue |
What do you think about introducing something like Or you can add events to the step component directly, so you don't need to compare the index each time. This is a cool feature for end users and extensibility. By using |
No description provided.