Skip to content

Commit

Permalink
feat(menu): submenu functionality (#1092)
Browse files Browse the repository at this point in the history
* feat(menu): submenu functionality

* chore(changeset): submenu feature

---------

Co-authored-by: Michael Wolf <[email protected]>
  • Loading branch information
w0ofy and Michael Wolf authored Jan 19, 2024
1 parent aa44577 commit 5803f94
Show file tree
Hide file tree
Showing 20 changed files with 187 additions and 75 deletions.
9 changes: 9 additions & 0 deletions .changeset/clever-crabs-act.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@real-system/ariakit-library': patch
'@real-system/menu-primitive': patch
'@real-system/styled-library': patch
'@real-system/select': patch
'@real-system/menu': patch
---

add SubMenu functionality to Menu
19 changes: 10 additions & 9 deletions packages/components/menu/src/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as React from 'react';
import {
MenuProviderPrimitive,
type MenuProviderPrimitiveProps,
useMenuStorePrimitive,
} from '@real-system/menu-primitive';

import { MenuGroup } from './MenuGroup/index';
Expand Down Expand Up @@ -35,16 +36,16 @@ function Menu({
onSelect,
setOpen,
}: MenuProps) {
const store = useMenuStorePrimitive({
placement,
open,
values,
defaultValues,
setValues: onSelect,
setOpen,
});
return (
<MenuProviderPrimitive
placement={placement}
open={open}
setOpen={setOpen}
values={values}
setValues={onSelect}
defaultValues={defaultValues}>
{children}
</MenuProviderPrimitive>
<MenuProviderPrimitive store={store}>{children}</MenuProviderPrimitive>
);
}

Expand Down
18 changes: 17 additions & 1 deletion packages/components/menu/src/MenuButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ import React, { forwardRef } from 'react';
import type { ButtonProps } from '@real-system/button';
import { Button } from '@real-system/button';
import { Icon } from '@real-system/icon';
import { MenuButtonPrimitive } from '@real-system/menu-primitive';
import {
MenuButtonPrimitive,
useMenuContextPrimitive,
} from '@real-system/menu-primitive';
import { makeTestId } from '@real-system/utils-library';

import { MenuItem } from './MenuItem';

type MenuButtonProps = (
| {
trailingArrow?: boolean;
Expand All @@ -23,6 +28,17 @@ const MenuButton = forwardRef<HTMLButtonElement, MenuButtonProps>(
{ children, trailingArrow, leadingArrow, ...restProps },
ref
) {
const menu = useMenuContextPrimitive();

if (menu?.parent) {
return (
<MenuItem isSubmenu>
{children}
<MenuItem.Icon icon="chevron-right" alignRight />
</MenuItem>
);
}

return (
<MenuButtonPrimitive
data-testid={makeTestId('menu-button')}
Expand Down
8 changes: 4 additions & 4 deletions packages/components/menu/src/MenuGroup/MenuGroupLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ const MenuGroupLabel = ({ children, ...restProps }: MenuGroupLabelProps) => {
<MenuGroupLabelPrimitive
render={
<real.div
fontScale="label"
fontWeight="bold"
letterSpacing="label"
fontScale="group-label"
fontWeight="group-label"
letterSpacing="group-label"
color="gray-500"
margin={0}
marginLeft={7}
paddingX={8}
marginBottom={4}
cursor="text"
/>
Expand Down
25 changes: 14 additions & 11 deletions packages/components/menu/src/MenuItem/MenuItem.style.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import type { StylishProps } from '@real-system/styled-library';

import type { CommonMenuItemProps } from './MenuItem.model';

const menuItemStyles: StylishProps = {
const menuItemStyles = {
transition: 'background-color 150ms ease-out, color 150ms ease-out',
paddingX: 7,
paddingX: 8,
paddingY: 5,
borderRadius: 4,
display: 'inline-flex',
alignItems: 'center',
width: '100%',
Expand All @@ -19,25 +20,27 @@ const menuItemStyles: StylishProps = {
textDecoration: 'none',
cursor: 'pointer',
_hover: {
backgroundColor: 'gray-50',
color: 'gray-600',
backgroundColor: `gray-50`,
color: `gray-500`,
},
_focus: {
outline: 'none',
backgroundColor: 'gray-50',
color: 'gray-600',
backgroundColor: `gray-50`,
color: `gray-500`,
},
_active: {
backgroundColor: 'gray-100',
color: 'gray-700',
bgColor: `gray-50`,
color: `gray-500`,
},
_checked: {
color: 'gray-700',
color: `gray-500`,
},
_disabled: { backgroundColor: 'none', color: 'gray-300' },
};
} satisfies StylishProps;

const MenuItemWrapper = forwardRef<HTMLDivElement, CommonMenuItemProps>(
type MenuItemWrapperProps = CommonMenuItemProps;

const MenuItemWrapper = forwardRef<HTMLDivElement, MenuItemWrapperProps>(
function MenuItemWrapper({ children, ...restProps }, ref) {
return (
<real.div {...menuItemStyles} {...restProps} ref={ref}>
Expand Down
17 changes: 13 additions & 4 deletions packages/components/menu/src/MenuItem/MenuItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { forwardRef } from 'react';

import { MenuButtonPrimitive } from '@real-system/menu-primitive';
import { MenuItemPrimitive } from '@real-system/menu-primitive';
import { type ColorSchemes } from '@real-system/styled-library';
import { makeTestId } from '@real-system/utils-library';

import type { CommonMenuItemProps } from './MenuItem.model';
Expand All @@ -11,7 +13,10 @@ import { MenuItemIcon } from './MenuItemIcon';
import { MenuItemLink } from './MenuItemLink';
import { MenuItemRadio } from './MenuItemRadio';

type MenuItemProps = CommonMenuItemProps;
type MenuItemProps = CommonMenuItemProps & {
colorScheme?: ColorSchemes;
isSubmenu?: boolean;
};

export interface MenuItemComponent
extends React.ForwardRefExoticComponent<MenuItemProps> {
Expand All @@ -24,12 +29,16 @@ export interface MenuItemComponent

// @ts-ignore MenuItem properties are defined below
const MenuItem: MenuItemComponent = forwardRef<HTMLDivElement, MenuItemProps>(
function MenuItem({ children, ...restProps }, ref) {
function MenuItem({ children, isSubmenu, ...restProps }, ref) {
return (
<MenuItemPrimitive
render={<MenuItemWrapper />}
render={
<MenuItemWrapper
as={isSubmenu && MenuButtonPrimitive}
{...restProps}
/>
}
data-testid={makeTestId('menu-item')}
{...restProps}
ref={ref}>
{children}
</MenuItemPrimitive>
Expand Down
24 changes: 7 additions & 17 deletions packages/components/menu/src/MenuItem/MenuItemIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,21 @@ import type { IconProps } from '@real-system/icon';
import { Icon } from '@real-system/icon';
import { makeTestId } from '@real-system/utils-library';

type MenuItemIconProps = (
| {
alignLeft?: boolean;
alignRight?: never;
}
| {
alignLeft?: never;
alignRight?: boolean;
}
) &
Omit<IconProps, 'size'>;
type MenuItemIconProps = {
alignRight?: boolean;
} & Omit<IconProps, 'size'>;

const MenuItemIcon = forwardRef<HTMLSpanElement, MenuItemIconProps>(
function MenuItemIcon({ alignRight, alignLeft = true, ...restProps }, ref) {
const spaceProps = alignLeft
? { marginRight: 5 }
: alignRight
function MenuItemIcon({ alignRight, ...restProps }, ref) {
const spaceProps = alignRight
? { marginLeft: 'auto', marginRight: 0 }
: {};
: { marginRight: 5 };

return (
<Icon
data-testid={makeTestId('menu-item-icon')}
{...spaceProps}
{...restProps}
{...spaceProps}
ref={ref}
/>
);
Expand Down
16 changes: 14 additions & 2 deletions packages/components/menu/src/MenuList.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import React, { forwardRef } from 'react';

import { MenuPrimitive } from '@real-system/menu-primitive';
import {
MenuPrimitive,
useMenuContextPrimitive,
} from '@real-system/menu-primitive';
import styled from '@real-system/styled-library';
import { makeTestId } from '@real-system/utils-library';

import type { CommonMenuProps } from './types';

const StyledMenuList = styled(MenuPrimitive)({
pos: 'relative',
py: 4,
px: 4,
zIndex: 'dropdown',
backgroundColor: 'white',
boxShadow: 'menu',
borderRadius: 4,
width: 'auto',
minWidth: '15rem',
minWidth: '18rem',
maxWidth: '22rem',
maxHeight: '65rem',
outline: 'none',
overflow: 'visible',
overscrollBehavior: 'contain',
});

type MenuListProps = {
Expand All @@ -26,10 +34,14 @@ const MenuList = forwardRef<HTMLDivElement, MenuListProps>(function MenuList(
{ children, ...restProps },
ref
) {
const menu = useMenuContextPrimitive();

return (
<StyledMenuList
data-testid={makeTestId('menu-list')}
{...restProps}
gutter={8}
shift={menu?.parent ? -8 : 0}
ref={ref}>
{children}
</StyledMenuList>
Expand Down
58 changes: 58 additions & 0 deletions packages/components/menu/stories/Menu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const Default = () => (
Edit
<Menu.Item.Command>E</Menu.Item.Command>
</Menu.Item>

<Menu.Item>
<Menu.Item.Icon icon="share" />
Share
Expand Down Expand Up @@ -47,6 +48,63 @@ export const Default = () => (
</Box>
);

export const Submenu = () => (
<Box height="24rem">
<Menu>
<Menu.Button variant="fill">Actions</Menu.Button>
<Menu.List>
<Menu.Item>
<Menu.Item.Icon icon="pencil-alt" />
Edit
<Menu.Item.Command>E</Menu.Item.Command>
</Menu.Item>

<Menu.Item>
<Menu.Item.Icon icon="share" />
Share
<Menu.Item.Command>S</Menu.Item.Command>
</Menu.Item>
<Menu.Item>
<Menu.Item.Icon icon="archive" />
Archive
<Menu.Item.Command>A</Menu.Item.Command>
</Menu.Item>
<Menu.Item disabled>
<Menu.Item.Icon icon="trash" />
Delete
<Menu.Item.Command>D</Menu.Item.Command>
</Menu.Item>
<Menu.Separator />
<Menu placement="right-start">
<Menu.Button>Get Support</Menu.Button>
<Menu.List>
<Menu.Item.Link
href="https://system.themikewolf.com"
external
showExternal>
Report issue
</Menu.Item.Link>
<Menu.Item>
Chat Support
<Menu.Item.Icon icon="chat" alignRight />
</Menu.Item>
<Menu placement="right-start">
<Menu.Button>Other Options</Menu.Button>
<Menu.List>
<Menu.Item>
<Menu.Item.Icon icon="trash" />
Delete
<Menu.Item.Command>E</Menu.Item.Command>
</Menu.Item>
</Menu.List>
</Menu>
</Menu.List>
</Menu>
</Menu.List>
</Menu>
</Box>
);

export const MenuGroups = () => (
<Box height="24rem">
<Menu>
Expand Down
1 change: 1 addition & 0 deletions packages/components/select/src/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const Select: SelectComponent = forwardRef<HTMLButtonElement, SelectProps>(
placement={placement}
setValue={onChange}
setOpen={setOpen}
animated
value={value}
defaultValue={defaultValue}
open={open}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ const SelectGroupLabel = ({
return (
<StyledGroupLabel
data-testid={makeTestId('select-group')}
fontScale="label"
fontWeight="label"
letterSpacing="body"
fontScale="group-label"
fontWeight="group-label"
letterSpacing="group-label"
color="gray-500"
m={0}
ml={7}
Expand Down
5 changes: 3 additions & 2 deletions packages/components/select/src/SelectItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,17 @@ const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(
scrollMargin={4}
alignItems="center"
gap={4}
paddingX={7}
paddingX={8}
paddingY={5}
borderRadius={4}
color="gray-500"
fontScale="select-item"
fontWeight="select-item"
cursor="pointer"
_hover={{ bgColor: 'gray-50' }}
_focus={{ bgColor: 'gray-50', color: 'gray-600' }}
_focusVisible={{ bgColor: 'gray-50', color: 'gray-600' }}
_active={{ bgColor: 'gray-100', color: 'gray-700' }}
_selected={{ bgColor: 'gray-100', color: 'gray-700' }}
_disabled={{ backgroundColor: 'none', color: 'gray-300' }}
{...restProps}
ref={ref}>
Expand Down
Loading

0 comments on commit 5803f94

Please sign in to comment.