Skip to content

Commit

Permalink
DropdownMenu v2: tweak styles, add toolbar-specific styles (#51097)
Browse files Browse the repository at this point in the history
* Update border radius to 2px

* Add toolbar variant styles to top content wrapper

* Add toolbar variant styles to sub content wrapper and separators via internal react context

* Connect `DropdownMenu` to the components context system

* Add temporary Storybook example

* Memoize internal context value to avoid extra re-renders

* CHANGELOG

* Set the toolbar dropdown menu variant in the toolbar component via the context system

* Tweak box shadow to match the existing popover

* Update icon size and helper text styles in Storybook example

* Tweak sub menu offset

* add TODO comment
  • Loading branch information
ciampo authored May 31, 2023
1 parent e40d5c0 commit dc289ea
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 57 deletions.
2 changes: 1 addition & 1 deletion packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

### Experimental

- `DropdownMenu` v2: Tweak styles ([#50967](https://github.com/WordPress/gutenberg/pull/50967)).
- `DropdownMenu` v2: Tweak styles ([#50967](https://github.com/WordPress/gutenberg/pull/50967), [#51097](https://github.com/WordPress/gutenberg/pull/51097)).
- `DropdownMenu` v2: Render in the default `Popover.Slot` ([#51046](https://github.com/WordPress/gutenberg/pull/51046)).

## 25.0.0 (2023-05-24)
Expand Down
96 changes: 65 additions & 31 deletions packages/components/src/dropdown-menu-v2/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
/**
* WordPress dependencies
*/
import { forwardRef, createContext, useContext } from '@wordpress/element';
import {
forwardRef,
createContext,
useContext,
useMemo,
} from '@wordpress/element';
import { isRTL } from '@wordpress/i18n';
import { check, chevronRightSmall, lineSolid } from '@wordpress/icons';
import { SVG, Circle } from '@wordpress/primitives';

/**
* Internal dependencies
*/
import { useContextSystem, contextConnectWithoutRef } from '../ui/context';
import { useSlot } from '../slot-fill';
import Icon from '../icon';
import { SLOT_NAME as POPOVER_DEFAULT_SLOT_NAME } from '../popover';
Expand All @@ -29,42 +35,56 @@ import type {
DropdownMenuRadioItemProps,
DropdownMenuSeparatorProps,
DropdownSubMenuTriggerProps,
DropdownMenuContext,
DropdownMenuPrivateContext as DropdownMenuPrivateContextType,
} from './types';

// Menu content's side padding + 4px
const SUB_MENU_OFFSET_SIDE = 12;
const SUB_MENU_OFFSET_SIDE = 16;
// Opposite amount of the top padding of the menu item
const SUB_MENU_OFFSET_ALIGN = -8;

const DropdownMenuPrivateContext = createContext< {
portalContainer: HTMLElement | null;
} >( {
portalContainer: null,
} );
const DropdownMenuPrivateContext =
createContext< DropdownMenuPrivateContextType >( {
variant: undefined,
portalContainer: null,
} );

const UnconnectedDropdownMenu = ( props: DropdownMenuProps ) => {
const {
// Root props
defaultOpen,
open,
onOpenChange,
modal = true,
// Content positioning props
side = 'bottom',
sideOffset = 0,
align = 'center',
alignOffset = 0,
// Render props
children,
trigger,

// From internal components context
variant,
} = useContextSystem<
// Adding `className` to the context type to avoid a TS error
DropdownMenuProps & DropdownMenuContext & { className?: string }
>( props, 'DropdownMenu' );

/**
* `DropdownMenu` displays a menu to the user (such as a set of actions
* or functions) triggered by a button.
*/
export const DropdownMenu = ( {
// Root props
defaultOpen,
open,
onOpenChange,
modal = true,
// Content positioning props
side = 'bottom',
sideOffset = 0,
align = 'center',
alignOffset = 0,
// Render props
children,
trigger,
}: DropdownMenuProps ) => {
// Render the portal in the default slot used by the legacy Popover component.
const slot = useSlot( POPOVER_DEFAULT_SLOT_NAME );
const portalContainer = slot.ref?.current;

const privateContextValue = useMemo(
() => ( {
variant,
portalContainer,
} ),
[ variant, portalContainer ]
);

return (
<DropdownMenuPrimitive.Root
defaultOpen={ defaultOpen }
Expand All @@ -83,9 +103,10 @@ export const DropdownMenu = ( {
sideOffset={ sideOffset }
alignOffset={ alignOffset }
loop={ true }
variant={ variant }
>
<DropdownMenuPrivateContext.Provider
value={ { portalContainer } }
value={ privateContextValue }
>
{ children }
</DropdownMenuPrivateContext.Provider>
Expand All @@ -95,6 +116,15 @@ export const DropdownMenu = ( {
);
};

/**
* `DropdownMenu` displays a menu to the user (such as a set of actions
* or functions) triggered by a button.
*/
export const DropdownMenu = contextConnectWithoutRef(
UnconnectedDropdownMenu,
'DropdownMenu'
);

export const DropdownSubMenuTrigger = ( {
prefix,
suffix = (
Expand Down Expand Up @@ -134,7 +164,9 @@ export const DropdownSubMenu = ( {
children,
trigger,
}: DropdownSubMenuProps ) => {
const { portalContainer } = useContext( DropdownMenuPrivateContext );
const { variant, portalContainer } = useContext(
DropdownMenuPrivateContext
);

return (
<DropdownMenuPrimitive.Sub
Expand All @@ -153,6 +185,7 @@ export const DropdownSubMenu = ( {
loop
sideOffset={ SUB_MENU_OFFSET_SIDE }
alignOffset={ SUB_MENU_OFFSET_ALIGN }
variant={ variant }
>
{ children }
</DropdownMenuStyled.SubContent>
Expand Down Expand Up @@ -254,6 +287,7 @@ export const DropdownMenuRadioItem = ( {
);
};

export const DropdownMenuSeparator = ( props: DropdownMenuSeparatorProps ) => (
<DropdownMenuStyled.Separator { ...props } />
);
export const DropdownMenuSeparator = ( props: DropdownMenuSeparatorProps ) => {
const { variant } = useContext( DropdownMenuPrivateContext );
return <DropdownMenuStyled.Separator { ...props } variant={ variant } />;
};
24 changes: 21 additions & 3 deletions packages/components/src/dropdown-menu-v2/stories/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import styled from '@emotion/styled';
/**
* Internal dependencies
*/
import { COLORS } from '../../utils';
import {
DropdownMenu,
DropdownMenuItem,
Expand All @@ -33,6 +34,7 @@ import { menu, wordpress } from '@wordpress/icons';
* Internal dependencies
*/
import Icon from '../../icon';
import { ContextSystemProvider } from '../../ui/context';

const meta: ComponentMeta< typeof DropdownMenu > = {
title: 'Components (Experimental)/DropdownMenu v2',
Expand Down Expand Up @@ -73,8 +75,8 @@ const meta: ComponentMeta< typeof DropdownMenu > = {
export default meta;

const ItemHelpText = styled.span`
font-size: 10px;
color: #777;
font-size: 12px;
color: ${ COLORS.gray[ '700' ] };
/* "> * > &" syntax is to target only immediate parent menu item */
[data-highlighted] > * > &,
Expand Down Expand Up @@ -144,7 +146,7 @@ Default.args = {
<DropdownMenuGroup>
<DropdownMenuItem>Menu item</DropdownMenuItem>
<DropdownMenuItem
prefix={ <Icon icon={ wordpress } size={ 18 } /> }
prefix={ <Icon icon={ wordpress } size={ 24 } /> }
>
Menu item with prefix
</DropdownMenuItem>
Expand Down Expand Up @@ -197,3 +199,19 @@ Default.args = {
</>
),
};

const toolbarVariantContextValue = {
DropdownMenu: {
variant: 'toolbar',
},
};
export const ToolbarVariant: ComponentStory< typeof DropdownMenu > = (
props
) => (
<ContextSystemProvider value={ toolbarVariantContextValue }>
<DropdownMenu { ...props } />
</ContextSystemProvider>
);
ToolbarVariant.args = {
...Default.args,
};
45 changes: 30 additions & 15 deletions packages/components/src/dropdown-menu-v2/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
/**
* Internal dependencies
*/
import { COLORS, font, rtl } from '../utils';
import { COLORS, font, rtl, CONFIG } from '../utils';
import { space } from '../ui/utils/space';
import Icon from '../icon';
import type { DropdownMenuContext } from './types';

const ANIMATION_PARAMS = {
SLIDE_AMOUNT: '2px',
Expand All @@ -23,6 +24,12 @@ const ITEM_PREFIX_WIDTH = space( 7 );
const ITEM_PADDING_INLINE_START = space( 2 );
const ITEM_PADDING_INLINE_END = space( 2.5 );

// TODO: should bring this into the config, and make themeable
const DEFAULT_BORDER_COLOR = COLORS.ui.borderDisabled;
const TOOLBAR_VARIANT_BORDER_COLOR = COLORS.gray[ '900' ];
const DEFAULT_BOX_SHADOW = `0 0 0 ${ CONFIG.borderWidth } ${ DEFAULT_BORDER_COLOR }, ${ CONFIG.popoverShadow }`;
const TOOLBAR_VARIANT_BOX_SHADOW = `0 0 0 ${ CONFIG.borderWidth } ${ TOOLBAR_VARIANT_BORDER_COLOR }`;

const slideUpAndFade = keyframes( {
'0%': {
opacity: 0,
Expand Down Expand Up @@ -55,15 +62,14 @@ const slideLeftAndFade = keyframes( {
'100%': { opacity: 1, transform: 'translateX(0)' },
} );

const baseContent = css`
const baseContent = ( variant: DropdownMenuContext[ 'variant' ] ) => css`
min-width: 220px;
background-color: ${ COLORS.ui.background };
border-radius: 6px;
border-radius: ${ CONFIG.radiusBlockUi };
padding: ${ CONTENT_WRAPPER_PADDING };
box-shadow: 0.1px 4px 16.4px -0.5px rgba( 0, 0, 0, 0.1 ),
0px 5.5px 7.8px -0.3px rgba( 0, 0, 0, 0.1 ),
0px 2.7px 3.8px -0.2px rgba( 0, 0, 0, 0.1 ),
0px 0.7px 1px rgba( 0, 0, 0, 0.1 );
box-shadow: ${ variant === 'toolbar'
? TOOLBAR_VARIANT_BOX_SHADOW
: DEFAULT_BOX_SHADOW };
animation-duration: ${ ANIMATION_PARAMS.DURATION };
animation-timing-function: ${ ANIMATION_PARAMS.EASING };
will-change: transform, opacity;
Expand Down Expand Up @@ -156,7 +162,7 @@ const baseItem = css`
font-weight: normal;
line-height: 20px;
color: ${ COLORS.gray[ 900 ] };
border-radius: 3px;
border-radius: ${ CONFIG.radiusBlockUi };
display: flex;
align-items: center;
padding: ${ space( 2 ) } ${ ITEM_PADDING_INLINE_END } ${ space( 2 ) }
Expand Down Expand Up @@ -193,11 +199,15 @@ const baseItem = css`
}
`;

export const Content = styled( DropdownMenu.Content )`
${ baseContent }
export const Content = styled( DropdownMenu.Content )<
Pick< DropdownMenuContext, 'variant' >
>`
${ ( props ) => baseContent( props.variant ) }
`;
export const SubContent = styled( DropdownMenu.SubContent )`
${ baseContent }
export const SubContent = styled( DropdownMenu.SubContent )<
Pick< DropdownMenuContext, 'variant' >
>`
${ ( props ) => baseContent( props.variant ) }
`;

export const Item = styled( DropdownMenu.Item )`
Expand Down Expand Up @@ -235,10 +245,15 @@ export const Label = styled( DropdownMenu.Label )`
text-transform: uppercase;
`;

export const Separator = styled( DropdownMenu.Separator )`
height: 1px;
export const Separator = styled( DropdownMenu.Separator )<
Pick< DropdownMenuContext, 'variant' >
>`
height: ${ CONFIG.borderWidth };
/* TODO: doesn't match border color from variables */
background-color: ${ COLORS.ui.borderDisabled };
background-color: ${ ( props ) =>
props.variant === 'toolbar'
? TOOLBAR_VARIANT_BORDER_COLOR
: DEFAULT_BORDER_COLOR };
/* Negative horizontal margin to make separator go from side to side */
margin: ${ space( 2 ) } calc( -1 * ${ CONTENT_WRAPPER_PADDING } );
`;
Expand Down
15 changes: 15 additions & 0 deletions packages/components/src/dropdown-menu-v2/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,3 +248,18 @@ export type DropdownMenuGroupProps = {
};

export type DropdownMenuSeparatorProps = {};

export type DropdownMenuContext = {
/**
* This variant can be used to change the appearance of the component in
* specific contexts, ie. when rendered inside the `Toolbar` component.
*/
variant?: 'toolbar';
};

export type DropdownMenuPrivateContext = Pick<
DropdownMenuContext,
'variant'
> & {
portalContainer: HTMLElement | null;
};
31 changes: 24 additions & 7 deletions packages/components/src/toolbar/toolbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,22 @@ import deprecated from '@wordpress/deprecated';
import ToolbarGroup from '../toolbar-group';
import ToolbarContainer from './toolbar-container';
import type { ToolbarProps } from './types';
import type { WordPressComponentProps } from '../../ui/context';
import {
WordPressComponentProps,
ContextSystemProvider,
} from '../../ui/context';

// TODO:
// - (optional) make the legacy `DropdownMenu` read the context variable
// - swap the legacy `DropdownMenu` with the new version of the component
// once it's stable
const CONTEXT_SYSTEM_VALUE = {
DropdownMenu: {
// Note: the legacy `DropdownMenu` component is not yet reactive to this
// context variant. See https://github.com/WordPress/gutenberg/pull/51097.
variant: 'toolbar',
},
};

function UnforwardedToolbar(
{
Expand All @@ -40,12 +55,14 @@ function UnforwardedToolbar(
className
);
return (
<ToolbarContainer
className={ finalClassName }
label={ label }
ref={ ref }
{ ...props }
/>
<ContextSystemProvider value={ CONTEXT_SYSTEM_VALUE }>
<ToolbarContainer
className={ finalClassName }
label={ label }
ref={ ref }
{ ...props }
/>
</ContextSystemProvider>
);
}

Expand Down
1 change: 1 addition & 0 deletions packages/components/src/ui/context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export {
} from './context-system-provider';
export {
contextConnect,
contextConnectWithoutRef,
hasConnectNamespace,
getConnectNamespace,
} from './context-connect';
Expand Down
Loading

0 comments on commit dc289ea

Please sign in to comment.