From 8583f967cd1e4de0293186417b87ea3ab9d07849 Mon Sep 17 00:00:00 2001 From: Chris Cedrone Date: Tue, 12 Sep 2023 13:02:16 -0400 Subject: [PATCH 1/8] adding docs, tests, changed context usage, added console log for nested panels --- .changeset/feat-dropdownExpandableMenu.md | 5 + .../components/Dropdown/Dropdown.stories.tsx | 72 ++++- .../src/components/Dropdown/Dropdown.test.js | 283 +++++++++++++++++- .../components/Dropdown/DropdownContent.tsx | 20 ++ .../Dropdown/DropdownExpandableMenuButton.tsx | 64 ++++ .../Dropdown/DropdownExpandableMenuGroup.tsx | 48 +++ .../Dropdown/DropdownExpandableMenuItem.tsx | 76 +++++ .../Dropdown/DropdownExpandableMenuPanel.tsx | 41 +++ .../components/Dropdown/DropdownMenuItem.tsx | 29 +- .../src/components/Dropdown/index.tsx | 5 + packages/react-magma-dom/src/index.ts | 27 +- .../src/pages/api/dropdown.mdx | 61 +++- 12 files changed, 700 insertions(+), 31 deletions(-) create mode 100644 .changeset/feat-dropdownExpandableMenu.md create mode 100644 packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuButton.tsx create mode 100644 packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuGroup.tsx create mode 100644 packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuItem.tsx create mode 100644 packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx diff --git a/.changeset/feat-dropdownExpandableMenu.md b/.changeset/feat-dropdownExpandableMenu.md new file mode 100644 index 000000000..a5e90546d --- /dev/null +++ b/.changeset/feat-dropdownExpandableMenu.md @@ -0,0 +1,5 @@ +--- +'react-magma-dom': minor +--- + +feat(DropdownExpandableMenu): A new menu item display for the Dropdown component which enables expandable lists by one level. diff --git a/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx b/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx index 4d2886609..44f718f5b 100644 --- a/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx @@ -17,10 +17,20 @@ import { Card, CardBody } from '../Card'; import { Input } from '../Input'; import { Checkbox } from '../Checkbox'; import { PasswordInput } from '../PasswordInput'; -import { SettingsIcon, MenuIcon } from 'react-magma-icons'; +import { + LocalPizzaIcon, + LunchDiningIcon, + MenuIcon, + RestaurantMenuIcon, + SettingsIcon, +} from 'react-magma-icons'; import { Story, Meta } from '@storybook/react/types-6-0'; import { Paragraph, Spacer } from '../..'; import { ButtonGroup } from '../ButtonGroup'; +import { DropdownExpandableMenuButton } from './DropdownExpandableMenuButton'; +import { DropdownExpandableMenuItem } from './DropdownExpandableMenuItem'; +import { DropdownExpandableMenuGroup } from './DropdownExpandableMenuGroup'; +import { DropdownExpandableMenuPanel } from './DropdownExpandableMenuPanel'; const Template: Story = args => (
@@ -407,3 +417,63 @@ export const NoItems = args => { ); }; + +export const ExpandableItems = args => { + return ( + + Expandable Items Dropdown + + + + Pasta + + Fresh + Processed + + + + + Prosciutto + + + Domestic + Speck + + + + + + ); +}; + +export const ExpandableItemsWithIcons = args => { + return ( + + Expandable Items Dropdown + + + + }> + Pasta + + + Fresh + Processed + + + + }> + Prosciutto + + + Domestic + Speck + + + + + }>Pizza + + + ); +}; diff --git a/packages/react-magma-dom/src/components/Dropdown/Dropdown.test.js b/packages/react-magma-dom/src/components/Dropdown/Dropdown.test.js index 3cb3e8c7d..5f9ed0fd3 100644 --- a/packages/react-magma-dom/src/components/Dropdown/Dropdown.test.js +++ b/packages/react-magma-dom/src/components/Dropdown/Dropdown.test.js @@ -1,18 +1,25 @@ import React from 'react'; import { AsteriskIcon } from 'react-magma-icons'; import { Dropdown } from '.'; -import { DropdownContent } from './DropdownContent'; -import { DropdownDivider } from './DropdownDivider'; -import { DropdownHeader } from './DropdownHeader'; -import { DropdownMenuItem } from './DropdownMenuItem'; -import { DropdownMenuGroup } from './DropdownMenuGroup'; -import { DropdownSplitButton } from './DropdownSplitButton'; -import { DropdownButton } from './DropdownButton'; -import { DropdownMenuNavItem } from './DropdownMenuNavItem'; +import { + DropdownContent, + DropdownDivider, + DropdownHeader, + DropdownMenuItem, + DropdownMenuGroup, + DropdownSplitButton, + DropdownButton, + DropdownMenuNavItem, + DropdownExpandableMenuGroup, + DropdownExpandableMenuItem, + DropdownExpandableMenuButton, + DropdownExpandableMenuPanel, +} from './'; import { magma } from '../../theme/magma'; +import { RestaurantMenuIcon } from 'react-magma-icons'; import { transparentize } from 'polished'; -import { act, render, fireEvent } from '@testing-library/react'; +import { act, render, fireEvent, getByTestId } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; describe('Dropdown', () => { @@ -791,4 +798,262 @@ describe('Dropdown', () => { jest.useRealTimers(); }); }); + + describe('dropdown with expandable menu', () => { + const testId = 'expandable group'; + const testId2 = 'expandable item'; + const testId3 = 'expandable button'; + const testId4 = 'expandable panel'; + + it('should render an expandable menu group', () => { + const { getByTestId } = render( + + Expandable Items Dropdown + + + + + Pasta + + + + + + ); + expect(getByTestId(testId)).toBeInTheDocument(); + expect(getByTestId(testId2)).toBeInTheDocument(); + expect(getByTestId(testId3)).toBeInTheDocument(); + }); + + it('should render an expandable menu group with icons', () => { + const { getByText } = render( + + Expandable Items Dropdown + + + + }> + Pasta + + + + + + ); + expect(getByText('Pasta').querySelector('svg')).toBeInTheDocument(); + }); + + it('should render an expanded panel of menu items when the DropdownExpandableMenuButton is clicked', () => { + const { getByTestId, getByText } = render( + + Expandable Items Dropdown + + + + + Pasta + + + Fresh + Processed + + + + + + ); + + fireEvent.click(getByText('Pasta')); + + expect(getByTestId(testId4)).toBeInTheDocument(); + }); + + it('should close an expanded panel of menu items when the DropdownExpandableMenuButton is clicked', () => { + const { getByTestId, getByText } = render( + + Expandable Items Dropdown + + + + + Pasta + + + Fresh + Processed + + + + + + ); + + fireEvent.click(getByText('Pasta')); + + expect(getByTestId(testId4)).toBeInTheDocument(); + + fireEvent.click(getByText('Pasta')); + + expect(getByText('Fresh')).not.toBeVisible(); + }); + + it('should have a default expanded item set by the user with defaultIndex', () => { + const { getByTestId, getByText, queryByTestId } = render( + + Expandable Items Dropdown + + + + + Pasta + + + Fresh + Processed + + + + + + Bacon + + + Fresh + Processed + + + + + + ); + + fireEvent.click(getByText('Expandable Items Dropdown')); + + expect(getByTestId(testId4)).toBeInTheDocument(); + + expect(queryByTestId(`${testId4}-2`)).not.toBeInTheDocument(); + }); + + it('should have only allow one open menu item when isMulti is used', () => { + const { getByTestId, getByText, queryByTestId } = render( + + Expandable Items Dropdown + + + + + Pasta + + + Fresh + Processed Stuff + + + + + + Bacon + + + Fresh + Processed + + + + + + ); + + fireEvent.click(getByText('Expandable Items Dropdown')); + + fireEvent.click(getByText('Pasta')); + + expect(getByTestId(testId4)).toBeInTheDocument(); + + expect(queryByTestId(`${testId4}-2`)).not.toBeInTheDocument(); + + fireEvent.click(getByText('Bacon')); + + expect(getByTestId(`${testId4}-2`)).toBeInTheDocument(); + + expect(queryByTestId(testId4)).not.toBeVisible(); + }); + + describe('dropdown with expandable menu styling', () => { + it(`DropdownExpandableMenuPanel items should have standard padding if DropdownExpandableMenuButton doesn't have an icon`, () => { + const { getByTestId, getByText } = render( + + Expandable Items Dropdown + + + + }> + Pasta + + + Fresh + Processed + + + + + + ); + fireEvent.click(getByText('Pasta')); + + expect(getByText('Fresh')).toHaveStyleRule( + 'padding', + `${magma.spaceScale.spacing03} ${magma.spaceScale.spacing05} ${magma.spaceScale.spacing03} 72px` + ); + }); + + it(`DropdownExpandableMenuPanel items should have additional padding if DropdownExpandableMenuButton has an icon`, () => { + const { getByTestId, getByText } = render( + + Expandable Items Dropdown + + + + + Pasta + + + Fresh + Processed + + + + + + ); + fireEvent.click(getByText('Pasta')); + + expect(getByText('Fresh')).toHaveStyleRule( + 'padding', + `${magma.spaceScale.spacing03} ${magma.spaceScale.spacing05} ${magma.spaceScale.spacing03} ${magma.spaceScale.spacing08}` + ); + }); + it(`should support isInverse mode`, () => { + const { getByTestId } = render( + + Expandable Items Dropdown + + + + + ); + + expect(getByTestId(testId)).toHaveStyleRule( + 'background', + 'transparent' + ); + expect(getByTestId(testId)).toHaveStyleRule( + 'color', + magma.colors.neutral100 + ); + }); + }); + }); }); diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownContent.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownContent.tsx index d0f3553b9..571107dca 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownContent.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownContent.tsx @@ -9,6 +9,10 @@ import { } from './Dropdown'; import { ThemeContext } from '../../theme/ThemeContext'; import { useForkedRef } from '../../utils'; +import { + DropdownExpandableMenuGroup, + DropdownExpandableMenuGroupProps, +} from './DropdownExpandableMenuGroup'; /** * @children required @@ -127,6 +131,19 @@ export const DropdownContent = React.forwardRef< } }); + // For Expandable Dropdowns that don't require a max-height + let hasExpandable = false; + + React.Children.map(children, child => { + const item = child as React.ReactElement< + React.PropsWithChildren + >; + + if (item.type === DropdownExpandableMenuGroup) { + hasExpandable = true; + } + }); + return ( ; + testId?: string; +} + +const StyledAccordionButton = styled(AccordionButton)<{ + icon?: React.ReactElement; +}>` + font-weight: 400; + padding: 8px 16px; + margin: 0; + border-top: 0; + &:hover, + &:focus { + background: ${menuBackground}; + } + > span { + display: flex; + } +`; + +const StyledIconWrapper = styled(IconWrapper)` + justify-content: center; + align-items: center; +`; + +export const DropdownExpandableMenuButton = React.forwardRef< + HTMLDivElement, + DropdownExpandableMenuButtonProps +>(props => { + const { children, icon, testId } = props; + + const theme = React.useContext(ThemeContext); + const context = React.useContext(DropdownContext); + + const ref = React.useRef(); + + // const i18n = React.useContext(I18nContext); + + return ( + + {icon && ( + + {icon} + + )} + {children} + + ); +}); diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuGroup.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuGroup.tsx new file mode 100644 index 000000000..4438f7839 --- /dev/null +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuGroup.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; +import styled from '../../theme/styled'; +import { useGenerateId } from '../../utils'; +import { Accordion, AccordionProps } from '../Accordion'; +import { DropdownContext } from './Dropdown'; + +/** + * @children required + */ +export interface DropdownExpandableMenuGroupProps + extends React.HTMLAttributes { + isInverse?: boolean; + testId?: string; +} + +const StyledAccordion = styled(Accordion)` + border: none; +`; + +export const DropdownExpandableMenuGroup = React.forwardRef< + HTMLDivElement, + AccordionProps +>(props => { + const { children, id: defaultId, testId, ...other } = props; + + const id = useGenerateId(defaultId); + + const context = React.useContext(DropdownContext); + + const countEachGroup = document.querySelectorAll('[role="group"]').length; + if (countEachGroup > 1) { + console.log( + `Only one group level is supported, anything nested two levels or more isn't accounted for in the styling` + ); + } + + return ( + + {children} + + ); +}); diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuItem.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuItem.tsx new file mode 100644 index 000000000..30a275e81 --- /dev/null +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuItem.tsx @@ -0,0 +1,76 @@ +import * as React from 'react'; +import styled from '../../theme/styled'; +import { IconProps } from 'react-magma-icons'; +import { AccordionItem, AccordionItemProps } from '../Accordion'; + +export interface DropdownExpandableMenuItemProps extends AccordionItemProps { + /** + * Leading icon for the menu item + */ + icon?: React.ReactElement; + /** + * @internal + */ + index?: number; + /** + * @internal + */ + isInverse?: boolean; + /** + * If true, item will be disabled; it will appear dimmed and onClick event (or any other events) will not fire + * @default false + */ + disabled?: boolean; + testId?: string; + /** + * Action that fires when the menu item is clicked. If the menuitem also has a value prop, the value will be passed to the onClick handler + */ + /** + * Value of the component, gets passed to the onClick event + */ + value?: string | number; +} + +export interface DropdownExpandableMenuItemContext { + hasIcon?: boolean; + isExpandablePanel?: boolean; +} + +export const DropdownExpandableMenuItemContext = + React.createContext({}); + +const StyledAccordionItem = styled(AccordionItem)``; + +export const DropdownExpandableMenuItem = React.forwardRef< + HTMLDivElement, + DropdownExpandableMenuItemProps +>(props => { + const { children, isInverse, testId, ...other } = props; + + const dropdownExpandableItems = React.Children.toArray(children); + + const hasIcon = dropdownExpandableItems.some(child => { + if (React.isValidElement(child)) { + return Object.keys(child.props).includes('icon'); + } + }); + + const isExpandablePanel = dropdownExpandableItems.some(child => { + if (React.isValidElement(child)) { + return true; + } + }); + + return ( + + + {children} + + + ); +}); diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx new file mode 100644 index 000000000..383a9cd14 --- /dev/null +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import styled from '../../theme/styled'; +import { AccordionPanel, AccordionPanelProps } from '../Accordion'; +import { DropdownExpandableMenuItemContext } from './DropdownExpandableMenuItem'; + +export interface DropdownExpandableMenuPanelProps extends AccordionPanelProps { + testId?: string; + isExpandablePanel?: boolean; +} + +const StyledAccordionPanel = styled(AccordionPanel)<{ + hasIcon?: boolean; + isExpandablePanel?: boolean; +}>` + padding: 0; +`; + +export const DropdownExpandableMenuPanel = React.forwardRef< + HTMLDivElement, + DropdownExpandableMenuPanelProps +>(props => { + const { children, isExpandablePanel, testId } = props; + + const context = React.useContext(DropdownExpandableMenuItemContext); + + // const theme = React.useContext(ThemeContext); + + // const ref = React.useRef(); + + // const i18n = React.useContext(I18nContext); + + return ( + + {children} + + ); +}); diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownMenuItem.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownMenuItem.tsx index 6e77231b9..073e74800 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownMenuItem.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownMenuItem.tsx @@ -7,6 +7,7 @@ import { DropdownContext } from './Dropdown'; import { IconProps, CheckIcon } from 'react-magma-icons'; import { transparentize } from 'polished'; import { Omit, useForkedRef } from '../../utils'; +import { DropdownExpandableMenuItemContext } from './DropdownExpandableMenuItem'; export interface DropdownMenuItemProps extends Omit, 'onClick'> { @@ -60,6 +61,21 @@ export function menuBackground(props) { return props.theme.colors.neutral200; } +function menuItemPadding(props) { + //For DropdownExpandableMenu styling with an icon + if (props.hasIcon && props.isExpandablePanel) { + return `${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing05} ${props.theme.spaceScale.spacing03} 72px`; + } + //For DropdownExpandableMenu styling without an icon + else if (props.isExpandablePanel) { + return `${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing05} ${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing08}`; + } else if (props.isInactive) { + return `${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing05} ${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing11}`; + } else { + return `${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing05}`; + } +} + export const MenuItemStyles = props => { return css` align-items: center; @@ -70,9 +86,7 @@ export const MenuItemStyles = props => { font-family: ${props.theme.bodyFont}; line-height: ${props.theme.typeScale.size03.lineHeight}; margin: 0; - padding: ${props.isInactive - ? `${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing05} ${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing11}` - : `${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing05}`}; + padding: ${menuItemPadding(props)}; white-space: ${props.isFixedWidth ? 'normal' : 'nowrap'}; &:hover, @@ -97,6 +111,9 @@ export const MenuItemStyles = props => { const StyledItem = styled.div<{ as?: string; disabled?: boolean; + hasIcon?: boolean; + isExpandablePanel?: boolean; + isFixedWidth?: boolean; isInactive?: boolean; isInverse?: boolean; @@ -129,6 +146,10 @@ export const DropdownMenuItem = React.forwardRef< const theme = React.useContext(ThemeContext); const context = React.useContext(DropdownContext); + const dropdownExpandableContext = React.useContext( + DropdownExpandableMenuItemContext + ); + const ref = useForkedRef(forwardedRef, ownRef); const index = context.itemRefArray.current.findIndex(({ current: item }) => { @@ -176,6 +197,8 @@ export const DropdownMenuItem = React.forwardRef< {...other} aria-disabled={disabled} disabled={disabled} + hasIcon={dropdownExpandableContext.hasIcon} + isExpandablePanel={dropdownExpandableContext.isExpandablePanel} isFixedWidth={context.isFixedWidth} isInactive={isInactive} isInverse={context.isInverse} diff --git a/packages/react-magma-dom/src/components/Dropdown/index.tsx b/packages/react-magma-dom/src/components/Dropdown/index.tsx index 6f43398e9..b053ece1c 100644 --- a/packages/react-magma-dom/src/components/Dropdown/index.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/index.tsx @@ -5,4 +5,9 @@ export * from './DropdownDivider'; export * from './DropdownHeader'; export * from './DropdownMenuGroup'; export * from './DropdownMenuItem'; +export * from './DropdownMenuNavItem'; export * from './DropdownSplitButton'; +export * from './DropdownExpandableMenuButton'; +export * from './DropdownExpandableMenuGroup'; +export * from './DropdownExpandableMenuPanel'; +export * from './DropdownExpandableMenuItem'; diff --git a/packages/react-magma-dom/src/index.ts b/packages/react-magma-dom/src/index.ts index 6a33d212f..36d9ab141 100644 --- a/packages/react-magma-dom/src/index.ts +++ b/packages/react-magma-dom/src/index.ts @@ -53,39 +53,32 @@ export { DropdownAlignment, DropdownDropDirection, DropdownProps, -} from './components/Dropdown'; -export { DropdownContent, DropdownContentProps, -} from './components/Dropdown/DropdownContent'; -export { DropdownDivider, DropdownDividerProps, -} from './components/Dropdown/DropdownDivider'; -export { DropdownHeader, DropdownHeaderProps, -} from './components/Dropdown/DropdownHeader'; -export { DropdownMenuGroup, DropdownMenuGroupProps, -} from './components/Dropdown/DropdownMenuGroup'; -export { DropdownMenuItem, DropdownMenuItemProps, -} from './components/Dropdown/DropdownMenuItem'; -export { DropdownMenuNavItem, DropdownMenuNavItemProps, -} from './components/Dropdown/DropdownMenuNavItem'; -export { DropdownSplitButton, DropdownSplitButtonProps, -} from './components/Dropdown/DropdownSplitButton'; -export { DropdownButton, DropdownButtonProps, -} from './components/Dropdown/DropdownButton'; + DropdownExpandableMenuGroup, + DropdownExpandableMenuGroupProps, + DropdownExpandableMenuItem, + DropdownExpandableMenuItemProps, + DropdownExpandableMenuButton, + DropdownExpandableMenuButtonProps, + DropdownExpandableMenuPanel, + DropdownExpandableMenuPanelProps, +} from './components/Dropdown'; + export * from './components/Flex'; export { Form, FormProps } from './components/Form'; export { FormGroup, FormGroupProps } from './components/FormGroup'; diff --git a/website/react-magma-docs/src/pages/api/dropdown.mdx b/website/react-magma-docs/src/pages/api/dropdown.mdx index a952af179..4d1c5ac5e 100644 --- a/website/react-magma-docs/src/pages/api/dropdown.mdx +++ b/website/react-magma-docs/src/pages/api/dropdown.mdx @@ -85,6 +85,66 @@ export function Example() { } ``` +## Expandable Menu Items + +If a nested menu layout is needed within the `Dropdown`, the `DropdownExpandableMenuGroup` adds an expandable / collapsible menu structure. + +The components of `DropdownExpandableMenuGroup`, `DropdownExpandableMenuItem`, `DropdownExpandableMenuButton`, `DropdownExpandableMenuPanel`, and `DropdownMenuItem` necessitate the proper structure of this menu list. + +Please note that only one child panel is supported from a styling standpoint. + +```tsx +import React from 'react'; +import { + Dropdown, + DropdownButton, + DropdownContent, + DropdownExpandableMenuGroup, + DropdownExpandableMenuItem, + DropdownExpandableMenuButton, + DropdownExpandableMenuPanel, + DropdownMenuItem, +} from 'react-magma-dom'; + +import { + RestaurantMenuIcon, + LunchDiningIcon, + LocalPizzaIcon, +} from 'react-magma-icons'; + +export function Example() { + return ( + + Expandable Items Dropdown + + + + }> + Pasta + + + Fresh + Processed + + + + }> + Prosciutto + + + Domestic + Speck + + + + + }>Pizza + + + ); +} +``` + ## Drop Direction Using the `dropDirection` prop will change where the menu appears. Options are `down` and `up`, with `down` being default. @@ -558,7 +618,6 @@ export function Example() { ); } - ``` ### Dropdown with Informational Content From 923b4597f52f48467e27b7b1aeb007a70e70caba Mon Sep 17 00:00:00 2001 From: Chris Cedrone Date: Wed, 20 Sep 2023 09:57:06 -0400 Subject: [PATCH 2/8] First review of Laura's findings, bereft of the test revisions at present, pushing changes for keyboard navigation tweaks --- .../components/Dropdown/Dropdown.stories.tsx | 57 ++++++++++++++- .../src/components/Dropdown/Dropdown.tsx | 1 - .../components/Dropdown/DropdownContent.tsx | 25 ++----- .../Dropdown/DropdownExpandableMenuButton.tsx | 27 ++++++-- .../Dropdown/DropdownExpandableMenuGroup.tsx | 69 +++++++++++-------- .../Dropdown/DropdownExpandableMenuItem.tsx | 64 +++-------------- .../Dropdown/DropdownExpandableMenuPanel.tsx | 44 ++++++------ .../components/Dropdown/DropdownMenuItem.tsx | 10 ++- packages/react-magma-dom/src/index.ts | 29 ++++++-- 9 files changed, 183 insertions(+), 143 deletions(-) diff --git a/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx b/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx index 44f718f5b..a0c614302 100644 --- a/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx @@ -448,14 +448,45 @@ export const ExpandableItems = args => { export const ExpandableItemsWithIcons = args => { return ( - + Expandable Items Dropdown - + }> - Pasta + Pasta antidisestablishmentarianism Pasta Pasta Pasta Pasta Pasta + Pasta Pasta Pasta Pasta Pasta Pasta + + + Fresh + Processed + + + + }> + Prosciutto + + Domestic + Speck + + + + + }>Pizza + + + ); +}; + +export const ExpandableItemsWithIconsAndConsoleWarning = args => { + return ( + + Expandable Items Dropdown + + + + Pasta Fresh Processed @@ -468,6 +499,26 @@ export const ExpandableItemsWithIcons = args => { Domestic Speck + + + }> + Pasta + + + Fresh + Processed + + + + }> + Prosciutto + + + Domestic + Speck + + + diff --git a/packages/react-magma-dom/src/components/Dropdown/Dropdown.tsx b/packages/react-magma-dom/src/components/Dropdown/Dropdown.tsx index 3681ec996..4c1eba91b 100644 --- a/packages/react-magma-dom/src/components/Dropdown/Dropdown.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/Dropdown.tsx @@ -174,7 +174,6 @@ export const Dropdown = React.forwardRef( function handleKeyDown(event: React.KeyboardEvent) { if (event.key === 'Escape') { - event.nativeEvent.stopImmediatePropagation(); closeDropdown(event); } diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownContent.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownContent.tsx index 571107dca..96a87b2b4 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownContent.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownContent.tsx @@ -9,10 +9,6 @@ import { } from './Dropdown'; import { ThemeContext } from '../../theme/ThemeContext'; import { useForkedRef } from '../../utils'; -import { - DropdownExpandableMenuGroup, - DropdownExpandableMenuGroupProps, -} from './DropdownExpandableMenuGroup'; /** * @children required @@ -121,27 +117,20 @@ export const DropdownContent = React.forwardRef< let hasItemChildren = false; + // Default styling for Expandable Menus to override the max-height used for regular dropdowns + let hasExpandableItems = false; + React.Children.forEach(children, (child: any) => { if ( child.type?.displayName === 'DropdownMenuItem' || child.type?.displayName === 'DropdownMenuGroup' ) { hasItemChildren = true; - return; } - }); - - // For Expandable Dropdowns that don't require a max-height - let hasExpandable = false; - - React.Children.map(children, child => { - const item = child as React.ReactElement< - React.PropsWithChildren - >; - - if (item.type === DropdownExpandableMenuGroup) { - hasExpandable = true; + if (child.type?.displayName === 'DropdownExpandableMenuGroup') { + hasExpandableItems = true; } + return; }); return ( @@ -155,7 +144,7 @@ export const DropdownContent = React.forwardRef< maxHeight={context.maxHeight} ref={ref} style={ - hasExpandable ? { maxHeight: 'inherit', overflow: 'hidden' } : null + hasExpandableItems ? { maxHeight: 'inherit', overflow: 'hidden' } : null } tabIndex={-1} testId={testId || 'dropdownContent'} diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuButton.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuButton.tsx index e2f55c9b4..58439ea4d 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuButton.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuButton.tsx @@ -5,6 +5,8 @@ import { IconWrapper, menuBackground } from './DropdownMenuItem'; import { IconProps } from 'react-magma-icons'; import { ThemeContext } from '../../theme/ThemeContext'; import { DropdownContext } from './Dropdown'; +import { DropdownExpandableContext } from './DropdownExpandableMenuGroup'; +import { useForkedRef } from '../../utils'; export interface DropdownExpandableMenuButtonProps extends AccordionButtonProps { @@ -13,10 +15,14 @@ export interface DropdownExpandableMenuButtonProps } const StyledAccordionButton = styled(AccordionButton)<{ + hasIcon?: boolean; icon?: React.ReactElement; }>` font-weight: 400; - padding: 8px 16px; + padding: ${props => + !props.icon && props.hasIcon + ? `${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing05} ${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing11}` + : `${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing05}`}; margin: 0; border-top: 0; &:hover, @@ -30,26 +36,33 @@ const StyledAccordionButton = styled(AccordionButton)<{ const StyledIconWrapper = styled(IconWrapper)` justify-content: center; - align-items: center; + /* align-items: center; */ `; export const DropdownExpandableMenuButton = React.forwardRef< HTMLDivElement, DropdownExpandableMenuButtonProps ->(props => { - const { children, icon, testId } = props; +>((props, forwardedRef) => { + const { children, icon, testId, ...other } = props; const theme = React.useContext(ThemeContext); const context = React.useContext(DropdownContext); + const expandableContext = React.useContext(DropdownExpandableContext); - const ref = React.useRef(); + const ownRef = React.useRef(); + const ref = useForkedRef(forwardedRef, ownRef); - // const i18n = React.useContext(I18nContext); + React.useEffect(() => { + context.registerDropdownMenuItem(context.itemRefArray, ownRef); + }, []); return ( @@ -62,3 +75,5 @@ export const DropdownExpandableMenuButton = React.forwardRef< ); }); + +DropdownExpandableMenuButton.displayName = 'DropdownExpandableMenuButton'; diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuGroup.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuGroup.tsx index 4438f7839..efe2d40aa 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuGroup.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuGroup.tsx @@ -1,48 +1,61 @@ import * as React from 'react'; import styled from '../../theme/styled'; -import { useGenerateId } from '../../utils'; import { Accordion, AccordionProps } from '../Accordion'; import { DropdownContext } from './Dropdown'; -/** - * @children required - */ -export interface DropdownExpandableMenuGroupProps - extends React.HTMLAttributes { - isInverse?: boolean; +const StyledAccordion = styled(Accordion)<{ testId?: string; -} - -const StyledAccordion = styled(Accordion)` +}>` border: none; `; +export interface DropdownExpandableContext { + hasIcon?: boolean; + isExpandablePanel?: boolean; +} + +export const DropdownExpandableContext = + React.createContext({}); + export const DropdownExpandableMenuGroup = React.forwardRef< HTMLDivElement, AccordionProps >(props => { - const { children, id: defaultId, testId, ...other } = props; - - const id = useGenerateId(defaultId); + const { children, testId, ...other } = props; const context = React.useContext(DropdownContext); - const countEachGroup = document.querySelectorAll('[role="group"]').length; - if (countEachGroup > 1) { - console.log( - `Only one group level is supported, anything nested two levels or more isn't accounted for in the styling` - ); - } + let hasIcon = false; + let isExpandablePanel = false; + + React.Children.forEach(children, (child: any) => { + if (child.type?.displayName === 'DropdownExpandableMenuItem') { + React.Children.forEach(child.props.children, (c: any) => { + if (c.type?.displayName === 'DropdownExpandableMenuButton') { + if (c.props.icon) { + hasIcon = true; + return; + } + } + }); + if (React.isValidElement(child)) { + isExpandablePanel = true; + } + } + }); return ( - - {children} - + + + {children} + + ); }); + +DropdownExpandableMenuGroup.displayName = 'DropdownExpandableMenuGroup'; diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuItem.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuItem.tsx index 30a275e81..112cf5cbd 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuItem.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuItem.tsx @@ -1,76 +1,34 @@ import * as React from 'react'; -import styled from '../../theme/styled'; import { IconProps } from 'react-magma-icons'; import { AccordionItem, AccordionItemProps } from '../Accordion'; - export interface DropdownExpandableMenuItemProps extends AccordionItemProps { - /** - * Leading icon for the menu item - */ - icon?: React.ReactElement; - /** - * @internal - */ - index?: number; - /** - * @internal - */ - isInverse?: boolean; /** * If true, item will be disabled; it will appear dimmed and onClick event (or any other events) will not fire * @default false */ disabled?: boolean; - testId?: string; /** - * Action that fires when the menu item is clicked. If the menuitem also has a value prop, the value will be passed to the onClick handler + * Leading icon for the menu item */ + icon?: React.ReactElement; /** - * Value of the component, gets passed to the onClick event + * @internal */ - value?: string | number; -} - -export interface DropdownExpandableMenuItemContext { - hasIcon?: boolean; - isExpandablePanel?: boolean; + index?: number; + testId?: string; } -export const DropdownExpandableMenuItemContext = - React.createContext({}); - -const StyledAccordionItem = styled(AccordionItem)``; - export const DropdownExpandableMenuItem = React.forwardRef< HTMLDivElement, DropdownExpandableMenuItemProps >(props => { - const { children, isInverse, testId, ...other } = props; - - const dropdownExpandableItems = React.Children.toArray(children); - - const hasIcon = dropdownExpandableItems.some(child => { - if (React.isValidElement(child)) { - return Object.keys(child.props).includes('icon'); - } - }); - - const isExpandablePanel = dropdownExpandableItems.some(child => { - if (React.isValidElement(child)) { - return true; - } - }); + const { children, disabled, testId, ...other } = props; return ( - - - {children} - - + + {children} + ); }); + +DropdownExpandableMenuItem.displayName = 'DropdownExpandableMenuItem'; diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx index 383a9cd14..6b079ec8c 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx @@ -1,17 +1,11 @@ import * as React from 'react'; import styled from '../../theme/styled'; import { AccordionPanel, AccordionPanelProps } from '../Accordion'; -import { DropdownExpandableMenuItemContext } from './DropdownExpandableMenuItem'; +import { DropdownExpandableMenuGroup } from './DropdownExpandableMenuGroup'; -export interface DropdownExpandableMenuPanelProps extends AccordionPanelProps { - testId?: string; - isExpandablePanel?: boolean; -} +export interface DropdownExpandableMenuPanelProps extends AccordionPanelProps {} -const StyledAccordionPanel = styled(AccordionPanel)<{ - hasIcon?: boolean; - isExpandablePanel?: boolean; -}>` +const StyledAccordionPanel = styled(AccordionPanel)` padding: 0; `; @@ -19,23 +13,27 @@ export const DropdownExpandableMenuPanel = React.forwardRef< HTMLDivElement, DropdownExpandableMenuPanelProps >(props => { - const { children, isExpandablePanel, testId } = props; - - const context = React.useContext(DropdownExpandableMenuItemContext); - - // const theme = React.useContext(ThemeContext); - - // const ref = React.useRef(); - - // const i18n = React.useContext(I18nContext); + const { children, testId, ...other } = props; + + React.Children.map(children, child => { + const item = child as React.ReactElement; + + if (item.type === DropdownExpandableMenuGroup) { + console.warn( + ` + -------------------------------------------------------------------------------------------------------- + Only one group level is supported, anything nested two levels or more isn't accounted for in the styling + -------------------------------------------------------------------------------------------------------- + ` + ); + } + }); return ( - + {children} ); }); + +DropdownExpandableMenuPanel.displayName = 'DropdownExpandableMenuPanel'; diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownMenuItem.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownMenuItem.tsx index 073e74800..966fbad21 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownMenuItem.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownMenuItem.tsx @@ -7,7 +7,7 @@ import { DropdownContext } from './Dropdown'; import { IconProps, CheckIcon } from 'react-magma-icons'; import { transparentize } from 'polished'; import { Omit, useForkedRef } from '../../utils'; -import { DropdownExpandableMenuItemContext } from './DropdownExpandableMenuItem'; +import { DropdownExpandableContext } from './DropdownExpandableMenuGroup'; export interface DropdownMenuItemProps extends Omit, 'onClick'> { @@ -146,9 +146,7 @@ export const DropdownMenuItem = React.forwardRef< const theme = React.useContext(ThemeContext); const context = React.useContext(DropdownContext); - const dropdownExpandableContext = React.useContext( - DropdownExpandableMenuItemContext - ); + const expandableContext = React.useContext(DropdownExpandableContext); const ref = useForkedRef(forwardedRef, ownRef); @@ -197,8 +195,8 @@ export const DropdownMenuItem = React.forwardRef< {...other} aria-disabled={disabled} disabled={disabled} - hasIcon={dropdownExpandableContext.hasIcon} - isExpandablePanel={dropdownExpandableContext.isExpandablePanel} + hasIcon={expandableContext.hasIcon} + isExpandablePanel={expandableContext.isExpandablePanel} isFixedWidth={context.isFixedWidth} isInactive={isInactive} isInverse={context.isInverse} diff --git a/packages/react-magma-dom/src/index.ts b/packages/react-magma-dom/src/index.ts index 36d9ab141..23a59623a 100644 --- a/packages/react-magma-dom/src/index.ts +++ b/packages/react-magma-dom/src/index.ts @@ -49,36 +49,55 @@ export { Container, ContainerProps } from './components/Container'; export { Datagrid, DatagridProps } from './components/Datagrid'; export { DatePicker, DatePickerProps } from './components/DatePicker'; export { - Dropdown, DropdownAlignment, DropdownDropDirection, DropdownProps, +} from './components/Dropdown'; +export { DropdownContent, DropdownContentProps, +} from './components/Dropdown/DropdownContent'; +export { DropdownDivider, DropdownDividerProps, +} from './components/Dropdown/DropdownDivider'; +export { DropdownHeader, DropdownHeaderProps, +} from './components/Dropdown/DropdownHeader'; +export { DropdownMenuGroup, DropdownMenuGroupProps, +} from './components/Dropdown/DropdownMenuGroup'; +export { DropdownMenuItem, DropdownMenuItemProps, +} from './components/Dropdown/DropdownMenuItem'; +export { DropdownMenuNavItem, DropdownMenuNavItemProps, +} from './components/Dropdown/DropdownMenuNavItem'; +export { DropdownSplitButton, DropdownSplitButtonProps, +} from './components/Dropdown/DropdownSplitButton'; +export { DropdownButton, DropdownButtonProps, - DropdownExpandableMenuGroup, - DropdownExpandableMenuGroupProps, +} from './components/Dropdown/DropdownButton'; +export { DropdownExpandableMenuGroup } from './components/Dropdown/DropdownExpandableMenuGroup'; +export { DropdownExpandableMenuItem, DropdownExpandableMenuItemProps, +} from './components/Dropdown/DropdownExpandableMenuItem'; +export { DropdownExpandableMenuButton, DropdownExpandableMenuButtonProps, +} from './components/Dropdown/DropdownExpandableMenuButton'; +export { DropdownExpandableMenuPanel, DropdownExpandableMenuPanelProps, -} from './components/Dropdown'; - +} from './components/Dropdown/DropdownExpandableMenuPanel'; export * from './components/Flex'; export { Form, FormProps } from './components/Form'; export { FormGroup, FormGroupProps } from './components/FormGroup'; From 41a7ba727aa346a641edef3d6abcca0003639baa Mon Sep 17 00:00:00 2001 From: Chris Cedrone Date: Thu, 21 Sep 2023 14:41:02 -0400 Subject: [PATCH 3/8] fixing keyboard navigation, added a prop to Accordion to accommodate, amendments per Laura's review, still working on tests --- .../components/Accordion/AccordionButton.tsx | 13 ++++++++-- .../components/Dropdown/Dropdown.stories.tsx | 2 +- .../src/components/Dropdown/Dropdown.tsx | 1 + .../components/Dropdown/DropdownContent.tsx | 6 +++-- .../Dropdown/DropdownExpandableMenuButton.tsx | 21 +++++++++------- .../Dropdown/DropdownExpandableMenuGroup.tsx | 24 +++++++++---------- .../Dropdown/DropdownExpandableMenuItem.tsx | 4 ++-- .../Dropdown/DropdownExpandableMenuPanel.tsx | 6 ++--- .../components/Dropdown/DropdownMenuItem.tsx | 12 +++++----- packages/react-magma-dom/src/index.ts | 1 + 10 files changed, 54 insertions(+), 36 deletions(-) diff --git a/packages/react-magma-dom/src/components/Accordion/AccordionButton.tsx b/packages/react-magma-dom/src/components/Accordion/AccordionButton.tsx index 3d9b353a3..db3a2d399 100644 --- a/packages/react-magma-dom/src/components/Accordion/AccordionButton.tsx +++ b/packages/react-magma-dom/src/components/Accordion/AccordionButton.tsx @@ -23,10 +23,12 @@ export interface AccordionButtonProps /** * @internal */ + hasCustomArray?: boolean; theme?: ThemeInterface; } const StyledButton = styled.button<{ + hasCustomArray?: boolean; isInverse?: boolean; isExpanded?: boolean; }>` @@ -90,7 +92,13 @@ export const AccordionButton = React.forwardRef< HTMLButtonElement, AccordionButtonProps >((props, forwardedRef) => { - const { children, testId, isInverse: isInverseProp, ...rest } = props; + const { + children, + testId, + hasCustomArray, + isInverse: isInverseProp, + ...rest + } = props; const theme = React.useContext(ThemeContext); const isInverse = useIsInverse(isInverseProp); @@ -122,11 +130,12 @@ export const AccordionButton = React.forwardRef< aria-expanded={Boolean(isExpanded)} data-testid={testId} disabled={isDisabled} + hasCustomArray={hasCustomArray} id={buttonId} isExpanded={isExpanded} isInverse={isInverse} onClick={handleClick} - onKeyDown={handleKeyDown} + onKeyDown={hasCustomArray ? null : handleKeyDown} ref={ref} theme={theme} > diff --git a/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx b/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx index a0c614302..1e7962ed6 100644 --- a/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx @@ -448,7 +448,7 @@ export const ExpandableItems = args => { export const ExpandableItemsWithIcons = args => { return ( - + Expandable Items Dropdown diff --git a/packages/react-magma-dom/src/components/Dropdown/Dropdown.tsx b/packages/react-magma-dom/src/components/Dropdown/Dropdown.tsx index 4c1eba91b..3681ec996 100644 --- a/packages/react-magma-dom/src/components/Dropdown/Dropdown.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/Dropdown.tsx @@ -174,6 +174,7 @@ export const Dropdown = React.forwardRef( function handleKeyDown(event: React.KeyboardEvent) { if (event.key === 'Escape') { + event.nativeEvent.stopImmediatePropagation(); closeDropdown(event); } diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownContent.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownContent.tsx index 96a87b2b4..364a02cb2 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownContent.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownContent.tsx @@ -117,7 +117,7 @@ export const DropdownContent = React.forwardRef< let hasItemChildren = false; - // Default styling for Expandable Menus to override the max-height used for regular dropdowns + // For Expandable Dropdowns that don't require a max-height let hasExpandableItems = false; React.Children.forEach(children, (child: any) => { @@ -144,7 +144,9 @@ export const DropdownContent = React.forwardRef< maxHeight={context.maxHeight} ref={ref} style={ - hasExpandableItems ? { maxHeight: 'inherit', overflow: 'hidden' } : null + hasExpandableItems + ? { maxHeight: 'inherit', overflow: 'hidden' } + : props.style } tabIndex={-1} testId={testId || 'dropdownContent'} diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuButton.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuButton.tsx index 58439ea4d..3e5b5d4e1 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuButton.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuButton.tsx @@ -5,7 +5,7 @@ import { IconWrapper, menuBackground } from './DropdownMenuItem'; import { IconProps } from 'react-magma-icons'; import { ThemeContext } from '../../theme/ThemeContext'; import { DropdownContext } from './Dropdown'; -import { DropdownExpandableContext } from './DropdownExpandableMenuGroup'; +import { DropdownExpandableMenuGroupContext } from './DropdownExpandableMenuGroup'; import { useForkedRef } from '../../utils'; export interface DropdownExpandableMenuButtonProps @@ -15,12 +15,13 @@ export interface DropdownExpandableMenuButtonProps } const StyledAccordionButton = styled(AccordionButton)<{ - hasIcon?: boolean; + hasCustomArray?: boolean; + expandableMenuButtonHasIcon?: boolean; icon?: React.ReactElement; }>` font-weight: 400; padding: ${props => - !props.icon && props.hasIcon + !props.icon && props.expandableMenuButtonHasIcon ? `${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing05} ${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing11}` : `${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing05}`}; margin: 0; @@ -36,18 +37,19 @@ const StyledAccordionButton = styled(AccordionButton)<{ const StyledIconWrapper = styled(IconWrapper)` justify-content: center; - /* align-items: center; */ `; export const DropdownExpandableMenuButton = React.forwardRef< HTMLDivElement, DropdownExpandableMenuButtonProps >((props, forwardedRef) => { - const { children, icon, testId, ...other } = props; + const { children, hasCustomArray, icon, testId, ...other } = props; const theme = React.useContext(ThemeContext); const context = React.useContext(DropdownContext); - const expandableContext = React.useContext(DropdownExpandableContext); + const expandableContext = React.useContext( + DropdownExpandableMenuGroupContext + ); const ownRef = React.useRef(); const ref = useForkedRef(forwardedRef, ownRef); @@ -60,9 +62,12 @@ export const DropdownExpandableMenuButton = React.forwardRef< diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuGroup.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuGroup.tsx index efe2d40aa..a4ae66db9 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuGroup.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuGroup.tsx @@ -3,19 +3,17 @@ import styled from '../../theme/styled'; import { Accordion, AccordionProps } from '../Accordion'; import { DropdownContext } from './Dropdown'; -const StyledAccordion = styled(Accordion)<{ - testId?: string; -}>` +const StyledAccordion = styled(Accordion)<{}>` border: none; `; -export interface DropdownExpandableContext { - hasIcon?: boolean; +export interface DropdownExpandableMenuGroupContextInterface { + expandableMenuButtonHasIcon?: boolean; isExpandablePanel?: boolean; } -export const DropdownExpandableContext = - React.createContext({}); +export const DropdownExpandableMenuGroupContext = + React.createContext({}); export const DropdownExpandableMenuGroup = React.forwardRef< HTMLDivElement, @@ -25,7 +23,7 @@ export const DropdownExpandableMenuGroup = React.forwardRef< const context = React.useContext(DropdownContext); - let hasIcon = false; + let expandableMenuButtonHasIcon = false; let isExpandablePanel = false; React.Children.forEach(children, (child: any) => { @@ -33,7 +31,7 @@ export const DropdownExpandableMenuGroup = React.forwardRef< React.Children.forEach(child.props.children, (c: any) => { if (c.type?.displayName === 'DropdownExpandableMenuButton') { if (c.props.icon) { - hasIcon = true; + expandableMenuButtonHasIcon = true; return; } } @@ -45,16 +43,18 @@ export const DropdownExpandableMenuGroup = React.forwardRef< }); return ( - + {children} - + ); }); diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuItem.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuItem.tsx index 112cf5cbd..50e244e59 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuItem.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuItem.tsx @@ -21,11 +21,11 @@ export interface DropdownExpandableMenuItemProps extends AccordionItemProps { export const DropdownExpandableMenuItem = React.forwardRef< HTMLDivElement, DropdownExpandableMenuItemProps ->(props => { +>((props, ref) => { const { children, disabled, testId, ...other } = props; return ( - + {children} ); diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx index 6b079ec8c..bb640e243 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx @@ -12,7 +12,7 @@ const StyledAccordionPanel = styled(AccordionPanel)` export const DropdownExpandableMenuPanel = React.forwardRef< HTMLDivElement, DropdownExpandableMenuPanelProps ->(props => { +>((props, ref) => { const { children, testId, ...other } = props; React.Children.map(children, child => { @@ -22,7 +22,7 @@ export const DropdownExpandableMenuPanel = React.forwardRef< console.warn( ` -------------------------------------------------------------------------------------------------------- - Only one group level is supported, anything nested two levels or more isn't accounted for in the styling + React Magma Warning: Only one group level is supported for Expandable Dropdowns, anything nested two levels or more isn't accounted for in the styling -------------------------------------------------------------------------------------------------------- ` ); @@ -30,7 +30,7 @@ export const DropdownExpandableMenuPanel = React.forwardRef< }); return ( - + {children} ); diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownMenuItem.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownMenuItem.tsx index 966fbad21..dfbb665db 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownMenuItem.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownMenuItem.tsx @@ -7,7 +7,7 @@ import { DropdownContext } from './Dropdown'; import { IconProps, CheckIcon } from 'react-magma-icons'; import { transparentize } from 'polished'; import { Omit, useForkedRef } from '../../utils'; -import { DropdownExpandableContext } from './DropdownExpandableMenuGroup'; +import { DropdownExpandableMenuGroupContext } from './DropdownExpandableMenuGroup'; export interface DropdownMenuItemProps extends Omit, 'onClick'> { @@ -63,7 +63,7 @@ export function menuBackground(props) { function menuItemPadding(props) { //For DropdownExpandableMenu styling with an icon - if (props.hasIcon && props.isExpandablePanel) { + if (props.expandableMenuButtonHasIcon && props.isExpandablePanel) { return `${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing05} ${props.theme.spaceScale.spacing03} 72px`; } //For DropdownExpandableMenu styling without an icon @@ -111,7 +111,7 @@ export const MenuItemStyles = props => { const StyledItem = styled.div<{ as?: string; disabled?: boolean; - hasIcon?: boolean; + expandableMenuButtonHasIcon?: boolean; isExpandablePanel?: boolean; isFixedWidth?: boolean; @@ -146,7 +146,7 @@ export const DropdownMenuItem = React.forwardRef< const theme = React.useContext(ThemeContext); const context = React.useContext(DropdownContext); - const expandableContext = React.useContext(DropdownExpandableContext); + const menuGroupContext = React.useContext(DropdownExpandableMenuGroupContext); const ref = useForkedRef(forwardedRef, ownRef); @@ -195,8 +195,8 @@ export const DropdownMenuItem = React.forwardRef< {...other} aria-disabled={disabled} disabled={disabled} - hasIcon={expandableContext.hasIcon} - isExpandablePanel={expandableContext.isExpandablePanel} + expandableMenuButtonHasIcon={menuGroupContext.expandableMenuButtonHasIcon} + isExpandablePanel={menuGroupContext.isExpandablePanel} isFixedWidth={context.isFixedWidth} isInactive={isInactive} isInverse={context.isInverse} diff --git a/packages/react-magma-dom/src/index.ts b/packages/react-magma-dom/src/index.ts index 23a59623a..1597ee444 100644 --- a/packages/react-magma-dom/src/index.ts +++ b/packages/react-magma-dom/src/index.ts @@ -49,6 +49,7 @@ export { Container, ContainerProps } from './components/Container'; export { Datagrid, DatagridProps } from './components/Datagrid'; export { DatePicker, DatePickerProps } from './components/DatePicker'; export { + Dropdown, DropdownAlignment, DropdownDropDirection, DropdownProps, From 6ab793072891020ea5cad13213b435f6ee9b5308 Mon Sep 17 00:00:00 2001 From: Chris Cedrone Date: Mon, 2 Oct 2023 13:10:11 -0400 Subject: [PATCH 4/8] fixing keyboard navigation on disabled items, Laura's notes --- .../components/Accordion/AccordionButton.tsx | 11 +++++----- .../components/Dropdown/Dropdown.stories.tsx | 8 +++---- .../Dropdown/DropdownExpandableMenuButton.tsx | 14 ++++++++---- .../Dropdown/DropdownExpandableMenuItem.tsx | 22 ++++++++++++++++++- .../Dropdown/DropdownExpandableMenuPanel.tsx | 2 -- 5 files changed, 41 insertions(+), 16 deletions(-) diff --git a/packages/react-magma-dom/src/components/Accordion/AccordionButton.tsx b/packages/react-magma-dom/src/components/Accordion/AccordionButton.tsx index db3a2d399..b1fcabd82 100644 --- a/packages/react-magma-dom/src/components/Accordion/AccordionButton.tsx +++ b/packages/react-magma-dom/src/components/Accordion/AccordionButton.tsx @@ -20,15 +20,17 @@ import { transparentize } from 'polished'; export interface AccordionButtonProps extends UseAccordionButtonProps, React.HTMLAttributes { + /** + * For use in components repurposing Accordion with custom keyboard navigation with it's elements. + */ + hasCustomOnKeyDown?: boolean; /** * @internal */ - hasCustomArray?: boolean; theme?: ThemeInterface; } const StyledButton = styled.button<{ - hasCustomArray?: boolean; isInverse?: boolean; isExpanded?: boolean; }>` @@ -95,7 +97,7 @@ export const AccordionButton = React.forwardRef< const { children, testId, - hasCustomArray, + hasCustomOnKeyDown, isInverse: isInverseProp, ...rest } = props; @@ -130,12 +132,11 @@ export const AccordionButton = React.forwardRef< aria-expanded={Boolean(isExpanded)} data-testid={testId} disabled={isDisabled} - hasCustomArray={hasCustomArray} id={buttonId} isExpanded={isExpanded} isInverse={isInverse} onClick={handleClick} - onKeyDown={hasCustomArray ? null : handleKeyDown} + onKeyDown={hasCustomOnKeyDown ? null : handleKeyDown} ref={ref} theme={theme} > diff --git a/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx b/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx index 1e7962ed6..6ab803979 100644 --- a/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx @@ -448,11 +448,11 @@ export const ExpandableItems = args => { export const ExpandableItemsWithIcons = args => { return ( - + Expandable Items Dropdown - + }> Pasta antidisestablishmentarianism Pasta Pasta Pasta Pasta Pasta Pasta Pasta Pasta Pasta Pasta Pasta @@ -462,7 +462,7 @@ export const ExpandableItemsWithIcons = args => { Processed - + }> Prosciutto @@ -481,7 +481,7 @@ export const ExpandableItemsWithIcons = args => { export const ExpandableItemsWithIconsAndConsoleWarning = args => { return ( - + Expandable Items Dropdown diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuButton.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuButton.tsx index 3e5b5d4e1..88864af79 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuButton.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuButton.tsx @@ -10,16 +10,18 @@ import { useForkedRef } from '../../utils'; export interface DropdownExpandableMenuButtonProps extends AccordionButtonProps { + disabled?: boolean; icon?: React.ReactElement; testId?: string; } const StyledAccordionButton = styled(AccordionButton)<{ - hasCustomArray?: boolean; + disabled?: boolean; expandableMenuButtonHasIcon?: boolean; icon?: React.ReactElement; }>` font-weight: 400; + overflow-wrap: anywhere; padding: ${props => !props.icon && props.expandableMenuButtonHasIcon ? `${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing05} ${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing11}` @@ -43,7 +45,8 @@ export const DropdownExpandableMenuButton = React.forwardRef< HTMLDivElement, DropdownExpandableMenuButtonProps >((props, forwardedRef) => { - const { children, hasCustomArray, icon, testId, ...other } = props; + const { children, disabled, hasCustomOnKeyDown, icon, testId, ...other } = + props; const theme = React.useContext(ThemeContext); const context = React.useContext(DropdownContext); @@ -55,14 +58,17 @@ export const DropdownExpandableMenuButton = React.forwardRef< const ref = useForkedRef(forwardedRef, ownRef); React.useEffect(() => { - context.registerDropdownMenuItem(context.itemRefArray, ownRef); + if (!disabled) { + context.registerDropdownMenuItem(context.itemRefArray, ownRef); + } }, []); return ( ((props, ref) => { const { children, disabled, testId, ...other } = props; + const dropdownExpandableMenuItemChildren = React.Children.map( + children, + child => { + const item = child as React.ReactElement< + React.PropsWithChildren + >; + + if (item.type === DropdownExpandableMenuButton) { + if (disabled) { + return React.cloneElement(item, { disabled: true }); + } + } + return child; + } + ); + return ( - {children} + {dropdownExpandableMenuItemChildren} ); }); diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx index bb640e243..67dac20fe 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx @@ -21,9 +21,7 @@ export const DropdownExpandableMenuPanel = React.forwardRef< if (item.type === DropdownExpandableMenuGroup) { console.warn( ` - -------------------------------------------------------------------------------------------------------- React Magma Warning: Only one group level is supported for Expandable Dropdowns, anything nested two levels or more isn't accounted for in the styling - -------------------------------------------------------------------------------------------------------- ` ); } From 8eed276cc9d2186f7d31ccc5b890134ee537ea74 Mon Sep 17 00:00:00 2001 From: Chris Cedrone Date: Thu, 5 Oct 2023 11:30:21 -0400 Subject: [PATCH 5/8] Laura's latest review, adding some tests --- .../components/Accordion/AccordionButton.tsx | 7 +- .../components/Dropdown/Dropdown.stories.tsx | 4 +- .../src/components/Dropdown/Dropdown.test.js | 197 +++++++++++++++--- .../Dropdown/DropdownExpandableMenuButton.tsx | 16 +- .../Dropdown/DropdownExpandableMenuGroup.tsx | 5 +- .../Dropdown/DropdownExpandableMenuItem.tsx | 1 - .../Dropdown/DropdownExpandableMenuPanel.tsx | 2 +- .../src/pages/api/dropdown.mdx | 187 +++++++++++------ 8 files changed, 311 insertions(+), 108 deletions(-) diff --git a/packages/react-magma-dom/src/components/Accordion/AccordionButton.tsx b/packages/react-magma-dom/src/components/Accordion/AccordionButton.tsx index b1fcabd82..0ac8a7702 100644 --- a/packages/react-magma-dom/src/components/Accordion/AccordionButton.tsx +++ b/packages/react-magma-dom/src/components/Accordion/AccordionButton.tsx @@ -22,8 +22,9 @@ export interface AccordionButtonProps React.HTMLAttributes { /** * For use in components repurposing Accordion with custom keyboard navigation with it's elements. + * @internal */ - hasCustomOnKeyDown?: boolean; + customOnKeyDown?: () => void; /** * @internal */ @@ -97,7 +98,7 @@ export const AccordionButton = React.forwardRef< const { children, testId, - hasCustomOnKeyDown, + customOnKeyDown, isInverse: isInverseProp, ...rest } = props; @@ -136,7 +137,7 @@ export const AccordionButton = React.forwardRef< isExpanded={isExpanded} isInverse={isInverse} onClick={handleClick} - onKeyDown={hasCustomOnKeyDown ? null : handleKeyDown} + onKeyDown={customOnKeyDown ? customOnKeyDown : handleKeyDown} ref={ref} theme={theme} > diff --git a/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx b/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx index 6ab803979..813aae81e 100644 --- a/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/Dropdown.stories.tsx @@ -454,8 +454,8 @@ export const ExpandableItemsWithIcons = args => { }> - Pasta antidisestablishmentarianism Pasta Pasta Pasta Pasta Pasta - Pasta Pasta Pasta Pasta Pasta Pasta + Longer title area breaking lines within the + DropdownExpandableMenuButton component Fresh diff --git a/packages/react-magma-dom/src/components/Dropdown/Dropdown.test.js b/packages/react-magma-dom/src/components/Dropdown/Dropdown.test.js index 560f3aada..4a47692ca 100644 --- a/packages/react-magma-dom/src/components/Dropdown/Dropdown.test.js +++ b/packages/react-magma-dom/src/components/Dropdown/Dropdown.test.js @@ -836,19 +836,20 @@ describe('Dropdown', () => { }); describe('dropdown with expandable menu', () => { - const testId = 'expandable group'; - const testId2 = 'expandable item'; - const testId3 = 'expandable button'; - const testId4 = 'expandable panel'; + const expandableGroupId = 'expandable group'; + const expandableItemId = 'expandable item'; + const expandableButtonId = 'expandable button'; + const expandablePanelId = 'expandable panel'; + const expandablePanelTwoId = 'expandable panel two'; it('should render an expandable menu group', () => { const { getByTestId } = render( Expandable Items Dropdown - - - + + + Pasta @@ -856,9 +857,9 @@ describe('Dropdown', () => { ); - expect(getByTestId(testId)).toBeInTheDocument(); - expect(getByTestId(testId2)).toBeInTheDocument(); - expect(getByTestId(testId3)).toBeInTheDocument(); + expect(getByTestId(expandableGroupId)).toBeInTheDocument(); + expect(getByTestId(expandableItemId)).toBeInTheDocument(); + expect(getByTestId(expandableButtonId)).toBeInTheDocument(); }); it('should render an expandable menu group with icons', () => { @@ -889,7 +890,7 @@ describe('Dropdown', () => { Pasta - + Fresh Processed @@ -901,7 +902,7 @@ describe('Dropdown', () => { fireEvent.click(getByText('Pasta')); - expect(getByTestId(testId4)).toBeInTheDocument(); + expect(getByTestId(expandablePanelId)).toBeInTheDocument(); }); it('should close an expanded panel of menu items when the DropdownExpandableMenuButton is clicked', () => { @@ -914,7 +915,7 @@ describe('Dropdown', () => { Pasta - + Fresh Processed @@ -926,7 +927,7 @@ describe('Dropdown', () => { fireEvent.click(getByText('Pasta')); - expect(getByTestId(testId4)).toBeInTheDocument(); + expect(getByTestId(expandablePanelId)).toBeInTheDocument(); fireEvent.click(getByText('Pasta')); @@ -943,7 +944,7 @@ describe('Dropdown', () => { Pasta - + Fresh Processed @@ -953,7 +954,7 @@ describe('Dropdown', () => { Bacon - + Fresh Processed @@ -965,12 +966,54 @@ describe('Dropdown', () => { fireEvent.click(getByText('Expandable Items Dropdown')); - expect(getByTestId(testId4)).toBeInTheDocument(); + expect(getByTestId(expandablePanelId)).toBeInTheDocument(); - expect(queryByTestId(`${testId4}-2`)).not.toBeInTheDocument(); + expect(queryByTestId(expandablePanelTwoId)).not.toBeInTheDocument(); }); - it('should have only allow one open menu item when isMulti is used', () => { + it('should have multiple open menu items when isMulti is true', () => { + const { getByTestId, getByText } = render( + + Expandable Items Dropdown + + + + + Pasta + + + Fresh + Processed Stuff + + + + + + Bacon + + + Fresh + Processed + + + + + + ); + + fireEvent.click(getByText('Expandable Items Dropdown')); + + fireEvent.click(getByText('Pasta')); + + expect(getByTestId(expandablePanelId)).toBeInTheDocument(); + + fireEvent.click(getByText('Bacon')); + + expect(getByTestId(expandablePanelId)).toBeInTheDocument(); + expect(getByTestId(expandablePanelTwoId)).toBeInTheDocument(); + }); + + it('should have only allow one open menu item when isMulti is false', () => { const { getByTestId, getByText, queryByTestId } = render( Expandable Items Dropdown @@ -980,7 +1023,7 @@ describe('Dropdown', () => { Pasta - + Fresh Processed Stuff @@ -990,7 +1033,7 @@ describe('Dropdown', () => { Bacon - + Fresh Processed @@ -1004,20 +1047,20 @@ describe('Dropdown', () => { fireEvent.click(getByText('Pasta')); - expect(getByTestId(testId4)).toBeInTheDocument(); + expect(getByTestId(expandablePanelId)).toBeInTheDocument(); - expect(queryByTestId(`${testId4}-2`)).not.toBeInTheDocument(); + expect(queryByTestId(expandablePanelTwoId)).not.toBeInTheDocument(); fireEvent.click(getByText('Bacon')); - expect(getByTestId(`${testId4}-2`)).toBeInTheDocument(); + expect(getByTestId(expandablePanelTwoId)).toBeInTheDocument(); - expect(queryByTestId(testId4)).not.toBeVisible(); + expect(queryByTestId(expandablePanelId)).not.toBeVisible(); }); describe('dropdown with expandable menu styling', () => { - it(`DropdownExpandableMenuPanel items should have standard padding if DropdownExpandableMenuButton doesn't have an icon`, () => { - const { getByTestId, getByText } = render( + it(`DropdownExpandableMenuPanel items should have additional padding if DropdownExpandableMenuButton has an icon`, () => { + const { getByText } = render( Expandable Items Dropdown @@ -1026,7 +1069,7 @@ describe('Dropdown', () => { }> Pasta - + Fresh Processed @@ -1043,8 +1086,8 @@ describe('Dropdown', () => { ); }); - it(`DropdownExpandableMenuPanel items should have additional padding if DropdownExpandableMenuButton has an icon`, () => { - const { getByTestId, getByText } = render( + it(`DropdownExpandableMenuPanel items should have standard padding if DropdownExpandableMenuButton doesn't have an icon`, () => { + const { getByText } = render( Expandable Items Dropdown @@ -1053,7 +1096,7 @@ describe('Dropdown', () => { Pasta - + Fresh Processed @@ -1069,23 +1112,109 @@ describe('Dropdown', () => { `${magma.spaceScale.spacing03} ${magma.spaceScale.spacing05} ${magma.spaceScale.spacing03} ${magma.spaceScale.spacing08}` ); }); + + it(`DropdownExpandableMenuPanel items should have additional padding if DropdownExpandableMenuButton has an icon and a text only menu item`, () => { + const { getByTestId, getByText } = render( + + Expandable Items Dropdown + + + + + Pasta + + + Fresh + Processed + + + + } + testId={`${expandableButtonId}-2`} + > + Prosciutto + + + Domestic + Speck + + + + + + ); + fireEvent.click(getByText('Pasta')); + fireEvent.click(getByText('Prosciutto')); + + expect(getByTestId(expandableButtonId)).toHaveStyleRule( + 'padding', + `${magma.spaceScale.spacing03} ${magma.spaceScale.spacing05} ${magma.spaceScale.spacing03} ${magma.spaceScale.spacing11}` + ); + + expect(getByTestId(`${expandableButtonId}-2`)).toHaveStyleRule( + 'padding', + `${magma.spaceScale.spacing03} ${magma.spaceScale.spacing05}` + ); + + expect(getByText('Fresh')).toHaveStyleRule( + 'padding', + `${magma.spaceScale.spacing03} ${magma.spaceScale.spacing05} ${magma.spaceScale.spacing03} 72px` + ); + expect(getByText('Domestic')).toHaveStyleRule( + 'padding', + `${magma.spaceScale.spacing03} ${magma.spaceScale.spacing05} ${magma.spaceScale.spacing03} 72px` + ); + }); + + it('should fire the customOnKeyDown function if used', () => { + const onChangeMock = jest.fn(); + const { getByTestId } = render( + + Expandable Items Dropdown + + + + + Pasta + + + Fresh + Processed + + + + + + ); + + const dropdownExpandableButton = getByTestId(expandableButtonId); + + fireEvent.keyDown(dropdownExpandableButton, { key: 'Enter' }); + + expect(onChangeMock).toHaveBeenCalledTimes(1); + }); + it(`should support isInverse mode`, () => { const { getByTestId } = render( Expandable Items Dropdown ); - expect(getByTestId(testId)).toHaveStyleRule( + expect(getByTestId(expandableGroupId)).toHaveStyleRule( 'background', 'transparent' ); - expect(getByTestId(testId)).toHaveStyleRule( + expect(getByTestId(expandableGroupId)).toHaveStyleRule( 'color', magma.colors.neutral100 ); diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuButton.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuButton.tsx index 88864af79..2c0271588 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuButton.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuButton.tsx @@ -45,12 +45,11 @@ export const DropdownExpandableMenuButton = React.forwardRef< HTMLDivElement, DropdownExpandableMenuButtonProps >((props, forwardedRef) => { - const { children, disabled, hasCustomOnKeyDown, icon, testId, ...other } = - props; + const { children, disabled, customOnKeyDown, icon, testId, ...other } = props; const theme = React.useContext(ThemeContext); const context = React.useContext(DropdownContext); - const expandableContext = React.useContext( + const expandableMenuGroupContext = React.useContext( DropdownExpandableMenuGroupContext ); @@ -63,16 +62,23 @@ export const DropdownExpandableMenuButton = React.forwardRef< } }, []); + //Allows a custom function to be called when a key is pressed, if no function is needed, this overrides the default AccordionButton onKeyDown event. + function handleCustomOnKeyDown() { + if (props.customOnKeyDown && typeof props.customOnKeyDown === 'function') { + return props.customOnKeyDown(); + } + } + return ( ` +const StyledAccordion = styled(Accordion)` border: none; `; @@ -18,7 +18,7 @@ export const DropdownExpandableMenuGroupContext = export const DropdownExpandableMenuGroup = React.forwardRef< HTMLDivElement, AccordionProps ->(props => { +>((props, ref) => { const { children, testId, ...other } = props; const context = React.useContext(DropdownContext); @@ -49,6 +49,7 @@ export const DropdownExpandableMenuGroup = React.forwardRef< diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuItem.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuItem.tsx index df1e66f49..47dce0c8b 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuItem.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuItem.tsx @@ -18,7 +18,6 @@ export interface DropdownExpandableMenuItemProps extends AccordionItemProps { /** * @internal */ - index?: number; testId?: string; } diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx index 67dac20fe..181d78b11 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuPanel.tsx @@ -28,7 +28,7 @@ export const DropdownExpandableMenuPanel = React.forwardRef< }); return ( - + {children} ); diff --git a/website/react-magma-docs/src/pages/api/dropdown.mdx b/website/react-magma-docs/src/pages/api/dropdown.mdx index 4d1c5ac5e..685146815 100644 --- a/website/react-magma-docs/src/pages/api/dropdown.mdx +++ b/website/react-magma-docs/src/pages/api/dropdown.mdx @@ -10,6 +10,9 @@ props: - DropdownMenuNavItemProps - DropdownButtonProps - DropdownSplitButtonProps + - DropdownExpandableMenuItemProps + - DropdownExpandableMenuButtonProps + - DropdownExpandableMenuPanelProps --- import { LeadParagraph } from '../../components/LeadParagraph'; @@ -85,66 +88,6 @@ export function Example() { } ``` -## Expandable Menu Items - -If a nested menu layout is needed within the `Dropdown`, the `DropdownExpandableMenuGroup` adds an expandable / collapsible menu structure. - -The components of `DropdownExpandableMenuGroup`, `DropdownExpandableMenuItem`, `DropdownExpandableMenuButton`, `DropdownExpandableMenuPanel`, and `DropdownMenuItem` necessitate the proper structure of this menu list. - -Please note that only one child panel is supported from a styling standpoint. - -```tsx -import React from 'react'; -import { - Dropdown, - DropdownButton, - DropdownContent, - DropdownExpandableMenuGroup, - DropdownExpandableMenuItem, - DropdownExpandableMenuButton, - DropdownExpandableMenuPanel, - DropdownMenuItem, -} from 'react-magma-dom'; - -import { - RestaurantMenuIcon, - LunchDiningIcon, - LocalPizzaIcon, -} from 'react-magma-icons'; - -export function Example() { - return ( - - Expandable Items Dropdown - - - - }> - Pasta - - - Fresh - Processed - - - - }> - Prosciutto - - - Domestic - Speck - - - - - }>Pizza - - - ); -} -``` - ## Drop Direction Using the `dropDirection` prop will change where the menu appears. Options are `down` and `up`, with `down` being default. @@ -413,6 +356,112 @@ export function Example() { } ``` +## Expandable Menu Items + +If a nested menu layout is needed within the `Dropdown`, the `DropdownExpandableMenuGroup` adds an expandable / collapsible menu structure. + +The components of `DropdownExpandableMenuGroup`, `DropdownExpandableMenuItem`, `DropdownExpandableMenuButton`, `DropdownExpandableMenuPanel`, and `DropdownMenuItem` necessitate the proper structure of this menu list. + +Please note that only one child panel is supported from a styling standpoint. + +```tsx +import React from 'react'; +import { + Dropdown, + DropdownButton, + DropdownContent, + DropdownExpandableMenuGroup, + DropdownExpandableMenuItem, + DropdownExpandableMenuButton, + DropdownExpandableMenuPanel, + DropdownMenuItem, +} from 'react-magma-dom'; + +import { + RestaurantMenuIcon, + LunchDiningIcon, + LocalPizzaIcon, +} from 'react-magma-icons'; + +export function Example() { + return ( + + Expandable Items Dropdown + + + + }> + Pasta + + + Fresh + Processed + + + + }> + Prosciutto + + + Domestic + Speck + + + + + }>Pizza + + + ); +} +``` + +### Text only Expandable Menu Item + +```tsx +import React from 'react'; +import { + Dropdown, + DropdownButton, + DropdownContent, + DropdownExpandableMenuGroup, + DropdownExpandableMenuItem, + DropdownExpandableMenuButton, + DropdownExpandableMenuPanel, + DropdownMenuItem, +} from 'react-magma-dom'; + +export function Example() { + return ( + + Expandable Items Dropdown + + + + Pasta + + Fresh + Processed + + + + + Prosciutto + + + Domestic + Speck + + + + + Pizza + + + ); +} +``` + ## Custom DropdownMenuItem Wrappers `DropdownContent` expects its children to be of type `DropdownMenuItem`, be an element in which its lowest child is of type `DropdownMenuItem`, or a custom component @@ -975,4 +1024,22 @@ The DropdownButton can also accept an `icon` property, as in the + {children} + + ); +}); + +DropdownExpandableMenuListItem.displayName = 'DropdownExpandableMenuListItem'; diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownMenuItem.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownMenuItem.tsx index dfbb665db..5b3160c43 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownMenuItem.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownMenuItem.tsx @@ -7,7 +7,6 @@ import { DropdownContext } from './Dropdown'; import { IconProps, CheckIcon } from 'react-magma-icons'; import { transparentize } from 'polished'; import { Omit, useForkedRef } from '../../utils'; -import { DropdownExpandableMenuGroupContext } from './DropdownExpandableMenuGroup'; export interface DropdownMenuItemProps extends Omit, 'onClick'> { @@ -62,14 +61,7 @@ export function menuBackground(props) { } function menuItemPadding(props) { - //For DropdownExpandableMenu styling with an icon - if (props.expandableMenuButtonHasIcon && props.isExpandablePanel) { - return `${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing05} ${props.theme.spaceScale.spacing03} 72px`; - } - //For DropdownExpandableMenu styling without an icon - else if (props.isExpandablePanel) { - return `${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing05} ${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing08}`; - } else if (props.isInactive) { + if (props.isInactive) { return `${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing05} ${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing11}`; } else { return `${props.theme.spaceScale.spacing03} ${props.theme.spaceScale.spacing05}`; @@ -111,9 +103,6 @@ export const MenuItemStyles = props => { const StyledItem = styled.div<{ as?: string; disabled?: boolean; - expandableMenuButtonHasIcon?: boolean; - isExpandablePanel?: boolean; - isFixedWidth?: boolean; isInactive?: boolean; isInverse?: boolean; @@ -146,8 +135,6 @@ export const DropdownMenuItem = React.forwardRef< const theme = React.useContext(ThemeContext); const context = React.useContext(DropdownContext); - const menuGroupContext = React.useContext(DropdownExpandableMenuGroupContext); - const ref = useForkedRef(forwardedRef, ownRef); const index = context.itemRefArray.current.findIndex(({ current: item }) => { @@ -195,8 +182,6 @@ export const DropdownMenuItem = React.forwardRef< {...other} aria-disabled={disabled} disabled={disabled} - expandableMenuButtonHasIcon={menuGroupContext.expandableMenuButtonHasIcon} - isExpandablePanel={menuGroupContext.isExpandablePanel} isFixedWidth={context.isFixedWidth} isInactive={isInactive} isInverse={context.isInverse} diff --git a/packages/react-magma-dom/src/components/Dropdown/index.tsx b/packages/react-magma-dom/src/components/Dropdown/index.tsx index b053ece1c..50dd9baf6 100644 --- a/packages/react-magma-dom/src/components/Dropdown/index.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/index.tsx @@ -11,3 +11,4 @@ export * from './DropdownExpandableMenuButton'; export * from './DropdownExpandableMenuGroup'; export * from './DropdownExpandableMenuPanel'; export * from './DropdownExpandableMenuItem'; +export * from './DropdownExpandableMenuListItem'; diff --git a/packages/react-magma-dom/src/index.ts b/packages/react-magma-dom/src/index.ts index 1597ee444..8999061a8 100644 --- a/packages/react-magma-dom/src/index.ts +++ b/packages/react-magma-dom/src/index.ts @@ -91,6 +91,10 @@ export { DropdownExpandableMenuItem, DropdownExpandableMenuItemProps, } from './components/Dropdown/DropdownExpandableMenuItem'; +export { + DropdownExpandableMenuListItem, + DropdownExpandableMenuListItemProps, +} from './components/Dropdown/DropdownExpandableMenuListItem'; export { DropdownExpandableMenuButton, DropdownExpandableMenuButtonProps, diff --git a/website/react-magma-docs/src/pages/api/dropdown.mdx b/website/react-magma-docs/src/pages/api/dropdown.mdx index 685146815..3076c50dc 100644 --- a/website/react-magma-docs/src/pages/api/dropdown.mdx +++ b/website/react-magma-docs/src/pages/api/dropdown.mdx @@ -11,6 +11,7 @@ props: - DropdownButtonProps - DropdownSplitButtonProps - DropdownExpandableMenuItemProps + - DropdownExpandableMenuListItemProps - DropdownExpandableMenuButtonProps - DropdownExpandableMenuPanelProps --- @@ -360,7 +361,7 @@ export function Example() { If a nested menu layout is needed within the `Dropdown`, the `DropdownExpandableMenuGroup` adds an expandable / collapsible menu structure. -The components of `DropdownExpandableMenuGroup`, `DropdownExpandableMenuItem`, `DropdownExpandableMenuButton`, `DropdownExpandableMenuPanel`, and `DropdownMenuItem` necessitate the proper structure of this menu list. +The components of `DropdownExpandableMenuGroup`, `DropdownExpandableMenuItem`, `DropdownExpandableMenuButton`, `DropdownExpandableMenuPanel`, and `DropdownExpandableMenuListItem` necessitate the proper structure of this menu list. Please note that only one child panel is supported from a styling standpoint. @@ -374,7 +375,7 @@ import { DropdownExpandableMenuItem, DropdownExpandableMenuButton, DropdownExpandableMenuPanel, - DropdownMenuItem, + DropdownExpandableMenuListItem, } from 'react-magma-dom'; import { @@ -394,8 +395,12 @@ export function Example() { Pasta - Fresh - Processed + + Fresh + + + Processed + @@ -403,13 +408,19 @@ export function Example() { Prosciutto - Domestic - Speck + + Domestic + + + Speck + - }>Pizza + }> + Pizza + ); @@ -428,7 +439,7 @@ import { DropdownExpandableMenuItem, DropdownExpandableMenuButton, DropdownExpandableMenuPanel, - DropdownMenuItem, + DropdownExpandableMenuListItem, } from 'react-magma-dom'; export function Example() { @@ -440,8 +451,12 @@ export function Example() { Pasta - Fresh - Processed + + Fresh + + + Processed + @@ -449,13 +464,17 @@ export function Example() { Prosciutto - Domestic - Speck + + Domestic + + + Speck + - Pizza + Pizza ); @@ -1042,4 +1061,10 @@ The DropdownButton can also accept an `icon` property, as in the {children} From 55898ea1a3a4271624f3e93496ef0a79a51ccdcc Mon Sep 17 00:00:00 2001 From: Chris Cedrone Date: Fri, 6 Oct 2023 13:57:02 -0400 Subject: [PATCH 8/8] Laura's note, removing unnecessary props --- .../src/components/Dropdown/DropdownExpandableMenuListItem.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuListItem.tsx b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuListItem.tsx index 346d0600a..7e057c5d8 100644 --- a/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuListItem.tsx +++ b/packages/react-magma-dom/src/components/Dropdown/DropdownExpandableMenuListItem.tsx @@ -21,11 +21,8 @@ function menuItemPadding(props) { } const StyledDropdownMenuItem = styled(DropdownMenuItem)<{ - disabled?: boolean; expandableMenuButtonHasIcon?: boolean; isExpandablePanel?: boolean; - isFixedWidth?: boolean; - isInverse?: boolean; }>` padding: ${menuItemPadding}; `;