Conversation
|
Build successful! 🎉 |
|
Build successful! 🎉 |
|
|
||
| ### Unavailable menu items | ||
|
|
||
| Wrap a `<MenuItem>` and a `<Popover>` with a `<UnavailableMenuItemTrigger>` and apply `isUnavailable` to disable the item's default action and show an informational popover instead. |
There was a problem hiding this comment.
I opted to have put the Popover directly into the example rather than create a UnavailableMenuItemTrigger wrapper in the starters since I felt it might be confusing to have Popover mentioned here but not show up in the example. Open to opinions though, not too bothered if we wanna go the same route as SubmenuTrigger and wrap the Popover in the starters
| // TODO: consider if we should instead pull the RAC defined trigger here into S2. Feels useful for RAC users though, but | ||
| // maybe shouldn't be specific to unavailable. It is very similar to SubmenuTrigger but it renders a dialog type behavior (see useSubmenuTrigger) | ||
| // and has the concept of isUnavailable which will make the item render normally when false | ||
| function UnavailableMenuItemTrigger(props: UnavailableMenuItemTriggerProps): JSX.Element { |
There was a problem hiding this comment.
UnavailableMenuItemTrigger seems a bit specific in RAC honestly, open to opinions if there should be a more generic naming/component
|
Build successful! 🎉 |
| // TODO: this is quite specific, but description and keyboardShortcut above are also very specific | ||
| /** Props for the descriptive element in the end slot inside the menu item (e.g. info icon, chevron). */ | ||
| endSlotProps: DOMAttributes, | ||
|
|
There was a problem hiding this comment.
I'm quite iffy about this, as stated above it is very specific. I think ideally we'd give the user a way to make arbitrary child elements in a MenuItem part of that MenuItem's aria-describedby. I could've opted to just allow the user to provide a generic aria-describedBy to the menu item and then apply ids to various other elements but we already have description and keyboard shortcut handled so figured we may be able to make end slot a thing too
There was a problem hiding this comment.
Also note that due to how collection items work in RAC (aka UnavailableMenuItemTrigger in RAC is a collection branch that only renders the popover child as is), I believe that RAC level change is unavoidable since there isn't a good way to generate a id at the S2 UnavailableMenuItemTrigger level and propagate it down via context
There was a problem hiding this comment.
What about allowing 'aria-describedby' as a prop and merging that with the ones we generate? Then this could be generated by S2 MenuItem and passed in. The only question would be in what order do we do the merge?
There was a problem hiding this comment.
I can go this route instead, just felt potentially confusing to the end user that they'd be expected to pass aria-describedby for some things but that we'd handle doing this for such specific things like keyboard shortcut icon and description.
I think we'd should merge the user's provided value first. Is the concern here the announcement order or more so what would happen if the user provided their own ids to the keyboard icon/description and then used those in their aria-described?
|
Build successful! 🎉 |
|
Build successful! 🎉 |
… modality this was for accessibility, ideally users should be moved into submenu/submenu dialogs unless it is hover
|
Build successful! 🎉 |
| } | ||
|
|
||
| interface SubmenuTriggerProps extends Omit<AriaMenuItemProps, 'key'> { | ||
| interface SubmenuTriggerProps extends Omit<AriaMenuItemProps, 'key' | 'onAction'> { |
There was a problem hiding this comment.
technically no longer needed since this only was a problem surfaced with the RAC UnavailableItem implementation but kept it for correctness. We may want to actually support firing onAction in a non-submenu case (aka if the overlay opened is more informational/tooltip like), but then we'd need to be able to tell between that case an the standard submenu case
| // needed so combobox/picker does not need to provide slot="title" to their provided | ||
| // ContextualHelp (they get the aria-labelled by from the button) | ||
| // otherwise, use the heading if available aka unavaiable menu item | ||
| [DEFAULT_SLOT]: {styles: headingStyles}, | ||
| title: {id: titleId, styles: headingStyles} |
There was a problem hiding this comment.
This was a bit interesting, would it be more correct for users to provide the title slot in their ContextualHelp's? It falls back to associating with the trigger button at the moment
| marginBottom: space(8) // This only makes it 10px on mobile and should be 12px | ||
| }); | ||
|
|
||
| // TODO: docs to come after release, for now this is just mentioned in unavaiable menu docs |
There was a problem hiding this comment.
We will need to discuss how we'd want to document this as well as what use cases we'd like to officially support
There was a problem hiding this comment.
UnavailableMenuItemTrigger is pretty long of a name lol, opted for wrapping
| // Skip this for submenus since hovering a submenutrigger should keep focus on the trigger | ||
| useEffect(() => { | ||
| if (isDialog && props.trigger !== 'SubmenuTrigger' && ref.current && !isFocusWithin(ref.current)) { | ||
| if (isDialog && (props.trigger !== 'SubmenuTrigger' || getInteractionModality() !== 'pointer') && ref.current && !isFocusWithin(ref.current)) { |
There was a problem hiding this comment.
this seems to work from testing, will need to pay special attention to this in our next test session
|
Build successful! 🎉 |
| <Text slot="label">Delete…</Text> | ||
| <Keyboard>⌘⌫</Keyboard> | ||
| </MenuItem> | ||
| <UnavailableMenuItemTrigger isUnavailable> |
There was a problem hiding this comment.
Do we want to add this pattern to RAC, even in the examples? I thought it was pretty Spectrum-specific.
There was a problem hiding this comment.
I figured it would serve as a good common use example since otherwise I would just have a generic informational popover which felt too much like a tooltip
| Wrap a `<MenuItem>` and a `<ContextualHelpPopover>` with a `<UnavailableMenuItemTrigger>` and apply `isUnavailable` to disable the item's default action and show an informational popover instead. | ||
| The `<ContextualHelpPopover>` supports `<Heading slot="title">`, `<Content>`, and `<Footer>` slots. |
There was a problem hiding this comment.
This is a bit verbose IMO. I think we can let the example code speak for itself.
| Wrap a `<MenuItem>` and a `<ContextualHelpPopover>` with a `<UnavailableMenuItemTrigger>` and apply `isUnavailable` to disable the item's default action and show an informational popover instead. | |
| The `<ContextualHelpPopover>` supports `<Heading slot="title">`, `<Content>`, and `<Footer>` slots. | |
| Wrap a `<MenuItem>` with an `<UnavailableMenuItemTrigger>` to disable the item's default action and show a [contextual help](ContextualHelp) popover instead. |
| </SubmenuTrigger> | ||
| <UnavailableMenuItemTrigger> | ||
| <MenuItem /> | ||
| <Popover /> |
There was a problem hiding this comment.
Should be ContextualHelpPopover now.
There was a problem hiding this comment.
whoops good catch. I've decided to also omit ContextualHelpPopover from the API at the bottom until the API settles too.
|
Build successful! 🎉 |
| "label.(optional)": "(選填)", | ||
| "label.(required)": "(必填)", | ||
| "menu.moreActions": "更多動作", | ||
| "menu.unavailable": "無法使用,展開以取得詳細資料", |
There was a problem hiding this comment.
add this to the migrate intl script so that it isn't removed accidentally in the future if someone uses that to migrate more strings
|
Build successful! 🎉 |
## API Changes
@react-aria/menu/@react-aria/menu:AriaMenuItemProps AriaMenuItemProps {
aria-controls?: string
+ aria-describedby?: string
aria-expanded?: boolean | 'true' | 'false'
aria-haspopup?: 'menu' | 'dialog'
aria-label?: string
id?: string
key: Key
onBlur?: (FocusEvent<Target>) => void
onClick?: (MouseEvent<FocusableElement>) => void
onFocus?: (FocusEvent<Target>) => void
onFocusChange?: (boolean) => void
onHoverChange?: (boolean) => void
onHoverEnd?: (HoverEvent) => void
onHoverStart?: (HoverEvent) => void
onKeyDown?: (KeyboardEvent) => void
onKeyUp?: (KeyboardEvent) => void
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
onPressUp?: (PressEvent) => void
selectionManager?: SelectionManager
shouldCloseOnSelect?: boolean
}@react-spectrum/s2/@react-spectrum/s2:TreeView TreeView <T extends {}> {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
autoFocus?: boolean | FocusStrategy
children?: ReactNode | (T) => ReactNode
defaultExpandedKeys?: Iterable<Key>
defaultSelectedKeys?: 'all' | Iterable<Key>
dependencies?: ReadonlyArray<any>
disabledBehavior?: DisabledBehavior = 'all'
disabledKeys?: Iterable<Key>
disallowEmptySelection?: boolean
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
expandedKeys?: Iterable<Key>
id?: string
items?: Iterable<T>
onAction?: (Key) => void
onExpandedChange?: (Set<Key>) => any
onSelectionChange?: (Selection) => void
- renderActionBar?: ('all' | Set<Key>) => ReactElement
renderEmptyState?: (TreeEmptyStateRenderProps) => ReactNode
selectedKeys?: 'all' | Iterable<Key>
selectionMode?: SelectionMode
shouldSelectOnPressUp?: boolean
styles?: StylesPropWithHeight
}/@react-spectrum/s2:TreeViewProps TreeViewProps <T> {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
autoFocus?: boolean | FocusStrategy
children?: ReactNode | (T) => ReactNode
defaultExpandedKeys?: Iterable<Key>
defaultSelectedKeys?: 'all' | Iterable<Key>
dependencies?: ReadonlyArray<any>
disabledBehavior?: DisabledBehavior = 'all'
disabledKeys?: Iterable<Key>
disallowEmptySelection?: boolean
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
expandedKeys?: Iterable<Key>
id?: string
items?: Iterable<T>
onAction?: (Key) => void
onExpandedChange?: (Set<Key>) => any
onSelectionChange?: (Selection) => void
- renderActionBar?: ('all' | Set<Key>) => ReactElement
renderEmptyState?: (TreeEmptyStateRenderProps) => ReactNode
selectedKeys?: 'all' | Iterable<Key>
selectionMode?: SelectionMode
shouldSelectOnPressUp?: boolean
styles?: StylesPropWithHeight
}/@react-spectrum/s2:ContextualHelpPopover+ContextualHelpPopover {
+ UNSAFE_className?: UnsafeClassName
+ UNSAFE_style?: CSSProperties
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ children: ReactNode
+ containerPadding?: number = 12
+ crossOffset?: number = 0
+ hideArrow?: boolean = false
+ id?: string
+ isOpen?: boolean
+ offset?: number = 8
+ onOpenChange?: (boolean) => void
+ padding?: 'default' | 'none' = 'default'
+ placement?: Placement = 'bottom'
+ role?: 'dialog' | 'alertdialog' = 'dialog'
+ shouldFlip?: boolean = true
+ size?: 'S' | 'M' | 'L'
+ slot?: string | null
+ styles?: PopoverStylesProp
+ triggerRef?: RefObject<Element | null>
+}/@react-spectrum/s2:UnavailableMenuItemTrigger+UnavailableMenuItemTrigger {
+ children: Array<ReactElement>
+ isUnavailable?: boolean = false
+}/@react-spectrum/s2:ContextualHelpProps+ContextualHelpProps {
+ UNSAFE_className?: UnsafeClassName
+ UNSAFE_style?: CSSProperties
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ children: ReactNode
+ containerPadding?: number = 12
+ crossOffset?: number = 0
+ defaultOpen?: boolean
+ id?: string
+ isOpen?: boolean
+ offset?: number = 8
+ onOpenChange?: (boolean) => void
+ placement?: Placement = 'bottom start'
+ shouldFlip?: boolean = true
+ size?: 'XS' | 'S' = 'XS'
+ styles?: StylesProp
+ variant?: 'info' | 'help' = 'help'
+}/@react-spectrum/s2:ContextualHelpStyleProps+ContextualHelpStyleProps {
+ variant?: 'info' | 'help' = 'help'
+}/@react-spectrum/s2:ContextualHelpPopoverProps+ContextualHelpPopoverProps {
+ UNSAFE_className?: UnsafeClassName
+ UNSAFE_style?: CSSProperties
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ children: ReactNode
+ containerPadding?: number = 12
+ crossOffset?: number = 0
+ hideArrow?: boolean = false
+ id?: string
+ isOpen?: boolean
+ offset?: number = 8
+ onOpenChange?: (boolean) => void
+ padding?: 'default' | 'none' = 'default'
+ placement?: Placement = 'bottom'
+ role?: 'dialog' | 'alertdialog' = 'dialog'
+ shouldFlip?: boolean = true
+ size?: 'S' | 'M' | 'L'
+ slot?: string | null
+ styles?: PopoverStylesProp
+ triggerRef?: RefObject<Element | null>
+}/@react-spectrum/s2:UnavailableMenuItemTriggerProps+UnavailableMenuItemTriggerProps {
+ children: Array<ReactElement>
+ isUnavailable?: boolean = false
+} |
…aseline-tracker * origin/main: feat(S2): S2 ListView (adobe#8878) refactor: Centralize expandedKeys logic in TreeCollection (adobe#9711) chore: Warn if user has interactive elements in their custom Picker value (adobe#9710) feat: S2 unavailable menu item (adobe#9657) fix: Ensure that opening a submenu via enter/space moves focus to first item in submenu (adobe#9691) fix: prevent docs crash by making template elements always append children into .content (adobe#9703) docs(RAC): Add TreeSection docs (adobe#9699) docs(S2): add Typography search view (adobe#9524) docs(S2): fix clipping in Picker custom value AvatarGroup example (adobe#9702) fix: patch additional methods so React doesnt break with template elements (adobe#9385) tentative fix (adobe#9635) docs(S2): fix icon import clipboard content to add underscore for icons starting with number (adobe#9698) feat(S2): add ActionBar support to TreeView (adobe#9695) fix: combobox interactoutside (adobe#9646) fix: skip native Date fast path when local timezone is overridden via setLocalTimeZone (adobe#9678) chore: update storybook to 9 (adobe#8634) docs: improve custom render value S2 Picker example (adobe#9682) # Conflicts: # yarn.lock
Closes
✅ Pull Request Checklist:
📝 Test Instructions:
Test both the S2 and RAC implementations in both storybook and docs.
🧢 Your Project:
RSP