From 824fd239cf4e15f3f4f9925875177d1f14908bc2 Mon Sep 17 00:00:00 2001 From: hamed-musallam <35760236+hamed-musallam@users.noreply.github.com> Date: Fri, 28 Apr 2023 10:15:31 +0200 Subject: [PATCH] feat: adapt the DropdownMenu to accept 'as' prop to specify an element to be a wrapper (#476) --- src/components/dropdown-menu/DropdownMenu.tsx | 102 +++++++++++------- stories/components/dropdown.stories.tsx | 89 +++++++++++++++ 2 files changed, 150 insertions(+), 41 deletions(-) diff --git a/src/components/dropdown-menu/DropdownMenu.tsx b/src/components/dropdown-menu/DropdownMenu.tsx index b1872066..31170770 100644 --- a/src/components/dropdown-menu/DropdownMenu.tsx +++ b/src/components/dropdown-menu/DropdownMenu.tsx @@ -1,7 +1,6 @@ -import styled from '@emotion/styled'; import { Menu } from '@headlessui/react'; import type { Placement } from '@popperjs/core'; -import { ReactNode, useRef } from 'react'; +import { ReactNode, useRef, ElementType, ComponentProps } from 'react'; import { useModifiedPopper } from '../hooks/useModifiedPopper'; import { useOnClickOutside } from '../hooks/useOnClickOutside'; @@ -21,6 +20,10 @@ interface DropdownMenuBaseProps { onSelect: (selected: MenuOption) => void; } +type ElementProps = E extends ElementType + ? ComponentProps + : never; + interface DropdownMenuClickProps extends DropdownMenuBaseProps { /** * Node to be inside the Button @@ -29,33 +32,42 @@ interface DropdownMenuClickProps extends DropdownMenuBaseProps { trigger: 'click'; } -interface DropdownMenuContextProps extends DropdownMenuBaseProps { +interface DropdownMenuContextProps extends DropdownMenuBaseProps { trigger: 'contextMenu'; children: ReactNode; + as?: E; } -export type DropdownMenuProps = - | DropdownMenuContextProps +export type DropdownMenuProps = + | DropdownMenuContextProps | DropdownMenuClickProps; -export function DropdownMenu(props: DropdownMenuProps) { +export function DropdownMenu( + props: DropdownMenuProps & + Omit, keyof DropdownMenuProps>, +) { const { trigger, ...otherProps } = props; if (trigger === 'contextMenu') { - return ; + return {...props} />; } - return ( {props.children} ); } -const HandleMenuContextDiv = styled.div` - display: contents; -`; - -function DropdownContextMenu(props: Omit, 'trigger'>) { - const { children, onSelect, ...otherProps } = props; +function DropdownContextMenu( + props: Omit, 'trigger'> & + Omit, keyof DropdownMenuProps>, +) { + const { + children, + onSelect, + as: Wrapper = 'div', + placement = 'right-start', + options, + ...otherProps + } = props; const { isPopperElementOpen, @@ -64,43 +76,51 @@ function DropdownContextMenu(props: Omit, 'trigger'>) { setPopperElement, styles, attributes, - } = useContextMenuPlacement(otherProps.placement || 'right-start'); + } = useContextMenuPlacement(placement); const ref = useRef(null); useOnClickOutside(ref, closePopperElement); + const { style = {}, ...otherWrapperProps } = otherProps as ElementProps; + return ( - - {isPopperElementOpen && ( - -
-
- - { - closePopperElement(); - onSelect(selected); - }} - {...otherProps} - /> - + + <> + {isPopperElementOpen && ( + +
+
+ + { + closePopperElement(); + onSelect(selected); + }} + options={options} + /> + +
-
- - )} + + )} - {children} - + {children} + + ); } -function DropdownClickMenu( - props: Omit, 'trigger'> & { children: ReactNode }, +function DropdownClickMenu( + props: Omit, 'trigger'> & { children: ReactNode }, ) { const { placement = 'bottom-start', onSelect, ...otherProps } = props; diff --git a/stories/components/dropdown.stories.tsx b/stories/components/dropdown.stories.tsx index fe352808..4c2a40ff 100644 --- a/stories/components/dropdown.stories.tsx +++ b/stories/components/dropdown.stories.tsx @@ -268,3 +268,92 @@ function ColumnWithDropdownMenu({ ); } + +const TableWithContext = styled.table` + border: 0.55px solid gray; + th, + td { + border: 0.55px solid gray; + padding: 0.4em; + } +`; + +export function TableWithContextMenu() { + const headerOptions = useMemo>(() => { + return [ + { label: 'Copy', type: 'option', icon: }, + { + label: 'Past', + type: 'option', + disabled: true, + icon: , + }, + ]; + }, []); + const options = useMemo>(() => { + return [ + { label: 'Back', type: 'option', icon: }, + { + label: 'Forward', + type: 'option', + disabled: true, + icon: , + }, + { label: 'Refresh', type: 'option', icon: }, + { type: 'divider' }, + { label: 'Save as', type: 'option', icon: }, + { label: 'Print', type: 'option', icon: }, + { label: 'Cast media to device', type: 'option', icon: }, + { type: 'divider' }, + { + label: 'Send page to your devices', + type: 'option', + icon: , + }, + { + label: 'Create QR Code for this page', + type: 'option', + icon: , + }, + ]; + }, []); + + return ( + + + + id + name + rn + mw + em + isExpensive + + + + {data.slice(0, 2).map(({ id, name, rn, mw, em, isExpensive }) => ( + + {id} + {name} + {rn} + {mw} + {em} + {isExpensive} + + ))} + + + ); +}