Skip to content

Commit

Permalink
DropdownMenu V2 tweaks (#56041)
Browse files Browse the repository at this point in the history
* Expose help text subcomponent (uses truncate under the hood)

* Remove DropdownMenuGroupLabel

* Submenu trigger always display chevron next to suffix

* Tweak gutter so that submenus overlap by 8px

* Implement auto-indentation via context

* Revert "Implement auto-indentation via context"

This reverts commit a1e2d3af0285f1f28d1677d41005bcdb25fd0659.

* Use CSS grid/subgrid to impement auto-indentation

* Fix slotfill example

* Tweak sizes, spacing, and colors. Tidy up styles.

* Add label subcomponent

* Update storybook examples

* Export help text and label subcomponents

* CHANGELOG

* README

* Fix unit test

* Allow disabled items to be keyboard focusable / accessible

* Display the "not allowed" cursor on disabled menu items

* Add scroll-margin to make sure that menu items are fully visible when scrolled into view

* Update unit tests

* Use inline styles instead of !important

* Apply design feedback

* CHANGELOG

* Update submenu shift
  • Loading branch information
ciampo authored Nov 23, 2023
1 parent a7d76c3 commit b55f86f
Show file tree
Hide file tree
Showing 8 changed files with 472 additions and 268 deletions.
3 changes: 2 additions & 1 deletion packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

## Unreleased

### Enhancements
### Enhancements

- `Button`: Add focus rings to focusable disabled buttons ([#56383](https://github.com/WordPress/gutenberg/pull/56383)).

### Experimental

- `Tabs`: Memoize and expose the component context ([#56224](https://github.com/WordPress/gutenberg/pull/56224)).
- `DropdownMenuV2`: Design tweaks ([#56041](https://github.com/WordPress/gutenberg/pull/56041))

### Internal

Expand Down
24 changes: 19 additions & 5 deletions packages/components/src/dropdown-menu-v2-ariakit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,23 +284,37 @@ Event handler called when the checked radio menu item changes.

- Required: no

### `DropdownMenuGroup`
### `DropdownMenuItemLabel`

Used to group menu items.
Used to render the menu item's label.

#### Props

The component accepts the following props:

##### `children`: `React.ReactNode`

The contents of the group.
The label contents.

- Required: yes

### `DropdownMenuItemHelpText`

Used to render the menu item's help text.

#### Props

The component accepts the following props:

##### `children`: `React.ReactNode`

The help text contents.

- Required: yes

### `DropdownMenuGroupLabel`
### `DropdownMenuGroup`

Used to render a group label.
Used to group menu items.

#### Props

Expand Down
121 changes: 85 additions & 36 deletions packages/components/src/dropdown-menu-v2-ariakit/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import type {
DropdownMenuContext as DropdownMenuContextType,
DropdownMenuProps,
DropdownMenuGroupProps,
DropdownMenuGroupLabelProps,
DropdownMenuItemProps,
DropdownMenuCheckboxItemProps,
DropdownMenuRadioItemProps,
Expand All @@ -60,16 +59,23 @@ export const DropdownMenuItem = forwardRef<
<Styled.DropdownMenuItem
ref={ ref }
{ ...props }
accessibleWhenDisabled
hideOnClick={ hideOnClick }
store={ dropdownMenuContext?.store }
>
{ prefix && (
<Styled.ItemPrefixWrapper>{ prefix }</Styled.ItemPrefixWrapper>
) }
{ children }
{ suffix && (
<Styled.ItemSuffixWrapper>{ suffix }</Styled.ItemSuffixWrapper>
) }
<Styled.ItemPrefixWrapper>{ prefix }</Styled.ItemPrefixWrapper>

<Styled.DropdownMenuItemContentWrapper>
<Styled.DropdownMenuItemChildrenWrapper>
{ children }
</Styled.DropdownMenuItemChildrenWrapper>

{ suffix && (
<Styled.ItemSuffixWrapper>
{ suffix }
</Styled.ItemSuffixWrapper>
) }
</Styled.DropdownMenuItemContentWrapper>
</Styled.DropdownMenuItem>
);
} );
Expand All @@ -87,20 +93,30 @@ export const DropdownMenuCheckboxItem = forwardRef<
<Styled.DropdownMenuCheckboxItem
ref={ ref }
{ ...props }
accessibleWhenDisabled
hideOnClick={ hideOnClick }
store={ dropdownMenuContext?.store }
>
<Ariakit.MenuItemCheck
store={ dropdownMenuContext?.store }
render={ <Styled.ItemPrefixWrapper /> }
// Override some ariakit inline styles
style={ { width: 'auto', height: 'auto' } }
>
<Icon icon={ check } size={ 24 } />
</Ariakit.MenuItemCheck>

{ children }
{ suffix && (
<Styled.ItemSuffixWrapper>{ suffix }</Styled.ItemSuffixWrapper>
) }
<Styled.DropdownMenuItemContentWrapper>
<Styled.DropdownMenuItemChildrenWrapper>
{ children }
</Styled.DropdownMenuItemChildrenWrapper>

{ suffix && (
<Styled.ItemSuffixWrapper>
{ suffix }
</Styled.ItemSuffixWrapper>
) }
</Styled.DropdownMenuItemContentWrapper>
</Styled.DropdownMenuCheckboxItem>
);
} );
Expand All @@ -124,17 +140,30 @@ export const DropdownMenuRadioItem = forwardRef<
<Styled.DropdownMenuRadioItem
ref={ ref }
{ ...props }
accessibleWhenDisabled
hideOnClick={ hideOnClick }
store={ dropdownMenuContext?.store }
>
<Ariakit.MenuItemCheck
store={ dropdownMenuContext?.store }
render={ <Styled.ItemPrefixWrapper /> }
// Override some ariakit inline styles
style={ { width: 'auto', height: 'auto' } }
>
<Icon icon={ radioCheck } size={ 24 } />
</Ariakit.MenuItemCheck>
{ children }
{ suffix }

<Styled.DropdownMenuItemContentWrapper>
<Styled.DropdownMenuItemChildrenWrapper>
{ children }
</Styled.DropdownMenuItemChildrenWrapper>

{ suffix && (
<Styled.ItemSuffixWrapper>
{ suffix }
</Styled.ItemSuffixWrapper>
) }
</Styled.DropdownMenuItemContentWrapper>
</Styled.DropdownMenuRadioItem>
);
} );
Expand All @@ -153,20 +182,6 @@ export const DropdownMenuGroup = forwardRef<
);
} );

export const DropdownMenuGroupLabel = forwardRef<
HTMLDivElement,
WordPressComponentProps< DropdownMenuGroupLabelProps, 'div', false >
>( function DropdownMenuGroupLabel( props, ref ) {
const dropdownMenuContext = useContext( DropdownMenuContext );
return (
<Styled.DropdownMenuGroupLabel
ref={ ref }
{ ...props }
store={ dropdownMenuContext?.store }
/>
);
} );

const UnconnectedDropdownMenu = (
props: WordPressComponentProps< DropdownMenuProps, 'div', false >,
ref: React.ForwardedRef< HTMLDivElement >
Expand Down Expand Up @@ -293,12 +308,16 @@ const UnconnectedDropdownMenu = (
dropdownMenuStore.parent
? cloneElement( trigger, {
// Add submenu arrow, unless a `suffix` is explicitly specified
suffix: trigger.props.suffix ?? (
<Styled.SubmenuChevronIcon
aria-hidden="true"
icon={ chevronRightSmall }
size={ 24 }
/>
suffix: (
<>
{ trigger.props.suffix }
<Styled.SubmenuChevronIcon
aria-hidden="true"
icon={ chevronRightSmall }
size={ 24 }
preserveAspectRatio="xMidYMid slice"
/>
</>
),
} )
: trigger
Expand All @@ -310,8 +329,12 @@ const UnconnectedDropdownMenu = (
{ ...otherProps }
modal={ modal }
store={ dropdownMenuStore }
gutter={ gutter ?? ( dropdownMenuStore.parent ? 16 : 8 ) }
shift={ shift ?? ( dropdownMenuStore.parent ? -8 : 0 ) }
// Root menu has an 8px distance from its trigger,
// otherwise 0 (which causes the submenu to slightly overlap)
gutter={ gutter ?? ( dropdownMenuStore.parent ? 0 : 8 ) }
// Align nested menu by the same (but opposite) amount
// as the menu container's padding.
shift={ shift ?? ( dropdownMenuStore.parent ? -4 : 0 ) }
hideOnHoverOutside={ false }
data-side={ appliedPlacementSide }
variant={ variant }
Expand Down Expand Up @@ -346,3 +369,29 @@ export const DropdownMenuSeparator = forwardRef<
/>
);
} );

export const DropdownMenuItemLabel = forwardRef<
HTMLSpanElement,
WordPressComponentProps< { children: React.ReactNode }, 'span', true >
>( function DropdownMenuItemLabel( props, ref ) {
return (
<Styled.DropdownMenuItemLabel
numberOfLines={ 1 }
ref={ ref }
{ ...props }
/>
);
} );

export const DropdownMenuItemHelpText = forwardRef<
HTMLSpanElement,
WordPressComponentProps< { children: React.ReactNode }, 'span', true >
>( function DropdownMenuItemHelpText( props, ref ) {
return (
<Styled.DropdownMenuItemHelpText
numberOfLines={ 2 }
ref={ ref }
{ ...props }
/>
);
} );
Loading

0 comments on commit b55f86f

Please sign in to comment.