Repository navigation
feat: RAC StepList and Wizard - #10794
snowystinger wants to merge 15 commits into
Conversation
|
Build successful! 🎉 |
| <InternalStepListContext.Provider value={{selectedId, groupId}}> | ||
| <dom.ol | ||
| {...mergeProps(DOMProps, renderProps)} | ||
| role="list" |
There was a problem hiding this comment.
Are the roles needed here, aren't they implicit?
There was a problem hiding this comment.
Oh, TIL Safari removes semantics when you add list-style: none https://www.havardbrynjulfsen.design/writing/a-new-way-to-remove-list-styles-in-safari
There was a problem hiding this comment.
yeah... that was a really annoying one to track down and fix, I think it's easier than the strange display empty string in that blog, though another interesting approach. Can't ensure it for RAC though since we are generally style-less
|
|
||
| // Need this so I can get an id on the contents so that the announcement makes sense. It lands on this element, not the `li` itself. | ||
| export const StepListItemContent = /*#__PURE__*/ (forwardRef as forwardRefType)( | ||
| function StepListItemContent(props: StepListItemContentProps, ref: ForwardedRef<HTMLDivElement>) { |
There was a problem hiding this comment.
Should this have render props?
There was a problem hiding this comment.
Honestly, I'm still trying to get rid of this component entirely, open to ideas. One idea I had was using Text and its context.
| {...DOMProps} | ||
| ref={objRef} | ||
| id={id} | ||
| role="link" // No role === default group which means three arrow keys to get through each item |
There was a problem hiding this comment.
img, perhaps with localized aria-roledescription, to avoid VO+Space announcement?
There was a problem hiding this comment.
yeah, both of them feel like a lie :-/ but i like the idea of img, less implied interaction. I'd still like for people to be able to pass a link in here (Step by step example) and neither link nor img would allow for that
There was a problem hiding this comment.
tested some more with img and actual links, I think it's a better experience with img, lets users know it's not interactive and it's just representing a future step I think. I'll get our a11y team to review it as well, but tentatively moving to it now.
|
Build successful! 🎉 |
|
Build successful! 🎉 |
|
Build successful! 🎉 |
|
Build successful! 🎉 |
| </span> | ||
| )} | ||
| {/* Every step has an href, but only completed steps can be navigated back to. */} | ||
| <StepListItemContent href={isCompleted ? step.href : undefined}> |
There was a problem hiding this comment.
StepListItemLink? It can be either a link or not depending if an href is supplied
| @@ -0,0 +1,255 @@ | |||
| /* | |||
There was a problem hiding this comment.
Which should be Stepper and which should be Wizard
See: https://reui.io/docs/components/base/stepper guidelines on when not to use to find link to wizard as well
Right now ours are inverse
| /** | ||
| * A StepList displays the steps of a multi step process, and which of them the user has completed. | ||
| */ | ||
| export const StepList = /*#__PURE__*/ (forwardRef as forwardRefType)(function StepList<T>( |
There was a problem hiding this comment.
Do we want renderProps here? what would be useful states/values/properties to send into it
|
|
||
| const StepListItemContext = createContext<{hiddenId: string | undefined} | undefined>(undefined); | ||
|
|
||
| export const StepListItemContent = /*#__PURE__*/ (forwardRef as forwardRefType)( |
There was a problem hiding this comment.
Didn't want to use Link directly because I thought that might be more confusing since an href is not required. Link would theoretically just render a span in that case.
I also wanted element structure parity between the non-link and link cases to make it easier to style
However, I need other opinions. See the stories to get an idea of how it's used with and without hrefs.
| * and is never rolled back unless it's in a controlled state, | ||
| * so previously reached steps stay navigable after navigating backwards. | ||
| */ | ||
| export function useWizardState(options: WizardStateOptions): WizardState { |
There was a problem hiding this comment.
I will move these out to react-aria, I've kept them here for ease of iteration and hopefully review
There was a problem hiding this comment.
follow up, should I build this based on RAC Tabs or base it instead on the react aria/stately hooks and have wizard be it's own RAC and the hooks just call the tabs hooks
|
Build successful! 🎉 |
|
Build successful! 🎉 |
|
Build successful! 🎉 |
|
Build successful! 🎉 |
## API Changes
react-aria-components/react-aria-components:StepList+StepList <T> {
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ children?: ReactNode | (T) => ReactNode
+ className?: string = 'react-aria-StepList'
+ dependencies?: ReadonlyArray<any>
+ items?: Iterable<T>
+ render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, undefined>
+ selectedKey: Key
+ slot?: string | null
+ style?: CSSProperties
+}/react-aria-components:StepListContext+StepListContext {
+ UNTYPED
+}/react-aria-components:StepListItem+StepListItem {
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ children?: ChildrenOrFunction<StepListItemRenderProps>
+ className?: ClassNameOrFunction<StepListItemRenderProps> = 'react-aria-StepListItem'
+ id?: Key
+ render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, StepListItemRenderProps>
+ style?: StyleOrFunction<StepListItemRenderProps>
+}/react-aria-components:StepListItemContent+StepListItemContent {
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ children?: ReactNode
+ className?: string
+ download?: boolean | string
+ href?: Href
+ hrefLang?: string
+ id?: string
+ ping?: string
+ referrerPolicy?: HTMLAttributeReferrerPolicy
+ rel?: string
+ render?: (DetailedHTMLProps<LinkWithRequiredHref, HTMLAnchorElement> | React.JSX.IntrinsicElements[keyof React.JSX.IntrinsicElements], undefined) => ReactElement
+ routerOptions?: RouterOptions
+ style?: CSSProperties
+ target?: HTMLAttributeAnchorTarget
+}/react-aria-components:Wizard+Wizard {
+ allowsRandomAccess?: boolean
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ children?: ReactNode
+ className?: ClassNameOrFunction<TabsRenderProps> = 'react-aria-Wizard'
+ defaultLastCompletedStep?: Key
+ defaultSelectedKey?: Key
+ disabledKeys?: Iterable<Key>
+ id?: string
+ isDisabled?: boolean
+ keyboardActivation?: 'automatic' | 'manual' = 'automatic'
+ lastCompletedStep?: Key | null
+ onLastCompletedStepChange?: (Key | null) => void
+ onSelectionChange?: (Key) => void
+ orientation?: Orientation = 'horizontal'
+ render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, TabsRenderProps>
+ selectedKey?: Key | null
+ slot?: string | null
+ style?: StyleOrFunction<TabsRenderProps>
+}/react-aria-components:WizardStepList+WizardStepList <T extends {}> {
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ children?: ReactNode | ({}) => ReactNode
+ className?: ClassNameOrFunction<TabListRenderProps> = 'react-aria-WizardStepList'
+ dependencies?: ReadonlyArray<any>
+ items?: Iterable<{}>
+ render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, TabListRenderProps>
+ style?: StyleOrFunction<TabListRenderProps>
+}/react-aria-components:WizardStep+WizardStep {
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ children?: ChildrenOrFunction<T>
+ className?: ClassNameOrFunction<WizardStepRenderProps> = 'react-aria-WizardStep'
+ download?: boolean | string
+ href?: Href
+ hrefLang?: string
+ id?: Key
+ isDisabled?: boolean
+ onBlur?: (FocusEvent<Target>) => void
+ onClick?: (MouseEvent<FocusableElement>) => void
+ onFocus?: (FocusEvent<Target>) => void
+ onFocusChange?: (boolean) => void
+ onHoverChange?: (boolean) => void
+ onHoverEnd?: (HoverEvent) => void
+ onHoverStart?: (HoverEvent) => void
+ onPress?: (PressEvent) => void
+ onPressChange?: (boolean) => void
+ onPressEnd?: (PressEvent) => void
+ onPressStart?: (PressEvent) => void
+ onPressUp?: (PressEvent) => void
+ ping?: string
+ referrerPolicy?: HTMLAttributeReferrerPolicy
+ rel?: string
+ render?: (DetailedHTMLProps<LinkWithRequiredHref, HTMLAnchorElement> | React.JSX.IntrinsicElements[keyof React.JSX.IntrinsicElements], WizardStepRenderProps) => ReactElement
+ routerOptions?: RouterOptions
+ style?: StyleOrFunction<T>
+ target?: HTMLAttributeAnchorTarget
+}/react-aria-components:WizardPanels+WizardPanels <T extends {}> {
+ children?: ReactNode | ({}) => ReactNode
+ className?: string = 'react-aria-WizardPanels'
+ dependencies?: ReadonlyArray<any>
+ items?: Iterable<{}>
+ render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, undefined>
+ style?: CSSProperties
+}/react-aria-components:WizardPanel+WizardPanel {
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ children?: ChildrenOrFunction<TabPanelRenderProps>
+ className?: ClassNameOrFunction<TabPanelRenderProps> = 'react-aria-WizardPanel'
+ id?: Key
+ render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, TabPanelRenderProps>
+ shouldForceMount?: boolean = false
+ style?: StyleOrFunction<TabPanelRenderProps>
+}/react-aria-components:StepListProps+StepListProps <T> {
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ children?: ReactNode | (T) => ReactNode
+ className?: string = 'react-aria-StepList'
+ dependencies?: ReadonlyArray<any>
+ items?: Iterable<T>
+ render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, undefined>
+ selectedKey: Key
+ slot?: string | null
+ style?: CSSProperties
+}/react-aria-components:StepListItemProps+StepListItemProps {
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ children?: ChildrenOrFunction<StepListItemRenderProps>
+ className?: ClassNameOrFunction<StepListItemRenderProps> = 'react-aria-StepListItem'
+ id?: Key
+ render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, StepListItemRenderProps>
+ style?: StyleOrFunction<StepListItemRenderProps>
+}/react-aria-components:StepListItemRenderProps+StepListItemRenderProps {
+ isCompleted: boolean
+ isCurrent: boolean
+ stepNumber: number
+}/react-aria-components:StepListItemContentProps+StepListItemContentProps {
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ children?: ReactNode
+ className?: string
+ download?: boolean | string
+ href?: Href
+ hrefLang?: string
+ id?: string
+ ping?: string
+ referrerPolicy?: HTMLAttributeReferrerPolicy
+ rel?: string
+ render?: (DetailedHTMLProps<LinkWithRequiredHref, HTMLAnchorElement> | React.JSX.IntrinsicElements[keyof React.JSX.IntrinsicElements], undefined) => ReactElement
+ routerOptions?: RouterOptions
+ style?: CSSProperties
+ target?: HTMLAttributeAnchorTarget
+}/react-aria-components:WizardProps+WizardProps {
+ allowsRandomAccess?: boolean
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ children?: ReactNode
+ className?: ClassNameOrFunction<TabsRenderProps> = 'react-aria-Wizard'
+ defaultLastCompletedStep?: Key
+ defaultSelectedKey?: Key
+ disabledKeys?: Iterable<Key>
+ id?: string
+ isDisabled?: boolean
+ keyboardActivation?: 'automatic' | 'manual' = 'automatic'
+ lastCompletedStep?: Key | null
+ onLastCompletedStepChange?: (Key | null) => void
+ onSelectionChange?: (Key) => void
+ orientation?: Orientation = 'horizontal'
+ render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, TabsRenderProps>
+ selectedKey?: Key | null
+ slot?: string | null
+ style?: StyleOrFunction<TabsRenderProps>
+}/react-aria-components:WizardStepListProps+WizardStepListProps <T> {
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ children?: ReactNode | (T) => ReactNode
+ className?: ClassNameOrFunction<TabListRenderProps> = 'react-aria-WizardStepList'
+ dependencies?: ReadonlyArray<any>
+ items?: Iterable<T>
+ render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, TabListRenderProps>
+ style?: StyleOrFunction<TabListRenderProps>
+}/react-aria-components:WizardStepProps+WizardStepProps {
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ children?: ChildrenOrFunction<T>
+ className?: ClassNameOrFunction<WizardStepRenderProps> = 'react-aria-WizardStep'
+ download?: boolean | string
+ href?: Href
+ hrefLang?: string
+ id?: Key
+ isDisabled?: boolean
+ onBlur?: (FocusEvent<Target>) => void
+ onClick?: (MouseEvent<FocusableElement>) => void
+ onFocus?: (FocusEvent<Target>) => void
+ onFocusChange?: (boolean) => void
+ onHoverChange?: (boolean) => void
+ onHoverEnd?: (HoverEvent) => void
+ onHoverStart?: (HoverEvent) => void
+ onPress?: (PressEvent) => void
+ onPressChange?: (boolean) => void
+ onPressEnd?: (PressEvent) => void
+ onPressStart?: (PressEvent) => void
+ onPressUp?: (PressEvent) => void
+ ping?: string
+ referrerPolicy?: HTMLAttributeReferrerPolicy
+ rel?: string
+ render?: (DetailedHTMLProps<LinkWithRequiredHref, HTMLAnchorElement> | React.JSX.IntrinsicElements[keyof React.JSX.IntrinsicElements], WizardStepRenderProps) => ReactElement
+ routerOptions?: RouterOptions
+ style?: StyleOrFunction<T>
+ target?: HTMLAttributeAnchorTarget
+}/react-aria-components:WizardStepRenderProps+WizardStepRenderProps {
+ isCompleted: boolean
+ isCurrent: boolean
+ isDisabled: boolean
+ isFocusVisible: boolean
+ isFocused: boolean
+ isHovered: boolean
+ isPressed: boolean
+ isSelected: boolean
+ stepNumber: number
+}/react-aria-components:WizardPanelsProps+WizardPanelsProps <T> {
+ children?: ReactNode | (T) => ReactNode
+ className?: string = 'react-aria-WizardPanels'
+ dependencies?: ReadonlyArray<any>
+ items?: Iterable<T>
+ render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, undefined>
+ style?: CSSProperties
+}/react-aria-components:WizardPanelProps+WizardPanelProps {
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ children?: ChildrenOrFunction<TabPanelRenderProps>
+ className?: ClassNameOrFunction<TabPanelRenderProps> = 'react-aria-WizardPanel'
+ id?: Key
+ render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, TabPanelRenderProps>
+ shouldForceMount?: boolean = false
+ style?: StyleOrFunction<TabPanelRenderProps>
+}/react-aria-components:WizardStepAria+WizardStepAria {
+ isCompleted: boolean
+ isCurrent: boolean
+ stepNumber: number
+}/react-aria-components:WizardState+WizardState <T> {
+ collection: Collection<Node<T>>
+ disabledKeys: Set<Key>
+ lastCompletedStep: Key | null
+ selectedKey: Key | null
+ setSelectedKey: (Key) => void
+}/react-aria-components:WizardStateOptions+WizardStateOptions <T> {
+ allowsRandomAccess?: boolean
+ collection: Collection<Node<T>>
+ defaultLastCompletedStep?: Key
+ defaultSelectedKey?: Key
+ disabledKeys?: Iterable<Key>
+ lastCompletedStep?: Key | null
+ onLastCompletedStepChange?: (Key | null) => void
+ onSelectionChange?: (Key) => void
+ selectedKey?: Key | null
+} |
Closes
From discussion, split into two components. A "readonly" StepList based on html
oland an interactive Wizard based on tabs.✅ Pull Request Checklist:
📝 Test Instructions:
🧢 Your Project: