Skip to content

Commit

Permalink
feat(suite): use popover component
Browse files Browse the repository at this point in the history
  • Loading branch information
izmy authored and tomasklim committed Jan 28, 2025
1 parent c420a26 commit bef1b3c
Show file tree
Hide file tree
Showing 24 changed files with 158 additions and 346 deletions.
20 changes: 6 additions & 14 deletions packages/components/src/components/Dropdown/Dropdown.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ export const Dropdown: StoryObj<DropdownProps> = {
label: 'some link',
icon: 'arrowRightLong',
},
alignMenu: 'right-top',
placement: {
position: 'bottom',
alignment: 'center',
},
items: [
{
key: '1',
Expand Down Expand Up @@ -135,24 +138,13 @@ export const Dropdown: StoryObj<DropdownProps> = {
argTypes: {
addon: { control: { disable: true } },
items: { control: { disable: true } },
alignMenu: {
placement: {
control: {
type: 'select',
type: 'object',
},
options: [
'bottom-left',
'bottom-right',
'left-bottom',
'left-top',
'right-bottom',
'right-top',
'top-left',
'top-right',
],
},
content: { control: { disable: true } },
className: { control: { disable: true } },
coords: { control: { disable: true } },
...getFramePropsStory(allowedDropdownFrameProps).argTypes,
},
};
177 changes: 21 additions & 156 deletions packages/components/src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,23 @@
import {
useState,
useRef,
useLayoutEffect,
forwardRef,
useImperativeHandle,
cloneElement,
RefObject,
ReactElement,
MouseEvent,
useEffect,
} from 'react';
import { createPortal } from 'react-dom';

import styled, { css } from 'styled-components';
import styled from 'styled-components';

import { useOnClickOutside } from '@trezor/react-utils';
import { borders } from '@trezor/theme';

import { Menu, MenuProps, DropdownMenuItemProps } from './Menu';
import { Coords, getAdjustedCoords } from './getAdjustedCoords';
import { Menu, MenuProps, DropdownMenuItemProps } from '../Menu/Menu';
import { IconButton } from '../buttons/IconButton/IconButton';
import { focusStyleTransition, getFocusShadowStyle } from '../../utils/utils';
import {
FrameProps,
FramePropsKeys,
pickAndPrepareFrameProps,
withFrameProps,
} from '../../utils/frameProps';
import { TransientProps } from '../../utils/transientProps';
import { FrameProps, FramePropsKeys } from '../../utils/frameProps';
import { Popover } from '../Popover/Popover';
import { PopoverPlacement } from '../Popover/utils';

export const allowedDropdownFrameProps = ['width'] as const satisfies FramePropsKeys[];
type AllowedFrameProps = Pick<FrameProps, (typeof allowedDropdownFrameProps)[number]>;
Expand All @@ -41,56 +31,9 @@ const MoreIcon = styled(IconButton)<{ $isToggled: boolean }>`
}
`;

const Container = styled.div<
{ $disabled?: boolean; $hasCustomChildren: boolean } & TransientProps<AllowedFrameProps>
>`
all: unset;
width: fit-content;
height: fit-content;
transition: ${focusStyleTransition};
border: 1px solid transparent;
${getFocusShadowStyle()};
cursor: ${({ $disabled }) => ($disabled ? 'default' : 'pointer')};
${withFrameProps}
${({ $hasCustomChildren }) =>
$hasCustomChildren
? undefined
: css`
border-radius: ${borders.radii.full};
`}
`;

const getPlacementData = (
toggleRef: RefObject<HTMLElement>,
menuRef: RefObject<HTMLUListElement>,
clickPos: Coords | undefined,
) => {
if (!toggleRef.current || !menuRef.current) {
return {};
}

let coordsToUse: Coords;
let toggleDimensions;
if (clickPos) {
coordsToUse = clickPos;
} else {
const { x, y, width, height } = toggleRef.current.getBoundingClientRect();

coordsToUse = { x, y };
toggleDimensions = { width, height };
}

if (!coordsToUse) {
return {};
}

return { coordsToUse, toggleDimensions };
};

export type DropdownProps = Omit<MenuProps, 'setToggled'> &
AllowedFrameProps & {
placement?: PopoverPlacement;
isDisabled?: boolean;
renderOnClickPosition?: boolean;
onToggle?: (isToggled: boolean) => void;
Expand All @@ -112,53 +55,19 @@ export const Dropdown = forwardRef(
items,
content,
isDisabled,
renderOnClickPosition,
addon,
alignMenu = 'bottom-left',
offsetX,
offsetY,
placement,
onToggle,
className,
children,
'data-testid': dataTest,
...rest
}: DropdownProps,
ref,
) => {
const [isToggled, setIsToggledState] = useState(false);
const [coords, setCoords] = useState<Coords>();
const [clickPos, setClickPos] = useState<Coords>();

const menuRef = useRef<HTMLUListElement>(null);
const toggleRef = useRef<HTMLDivElement>(null);

// when toggled, calculate the position of the menu
// takes into account the toggle position, size and the menu alignment
useLayoutEffect(() => {
const { coordsToUse, toggleDimensions } = getPlacementData(
toggleRef,
menuRef,
clickPos,
);

if (!coordsToUse || !menuRef.current) {
return;
}

const { width, height } = menuRef.current.getBoundingClientRect();

const adjustedCoords = getAdjustedCoords({
coords: coordsToUse,
alignMenu,
menuDimensions: { width, height },
toggleDimensions,
offsetX,
offsetY,
});

setCoords(adjustedCoords);
}, [isToggled, clickPos, alignMenu, offsetX, offsetY]);

useEffect(() => {
if (!isToggled) {
toggleRef.current?.blur();
Expand Down Expand Up @@ -187,82 +96,38 @@ export const Dropdown = forwardRef(
}
});

const onToggleClick = (e: MouseEvent) => {
e.stopPropagation();
e.preventDefault();

if (isDisabled) {
return;
}

// do not loose focus when clicking within the menu
if (!content && document.activeElement === menuRef.current) {
toggleRef.current?.focus();

return;
}

setToggled(!isToggled);
if (renderOnClickPosition) {
setClickPos({ x: e.pageX, y: e.pageY });
}
};

const hasCustomChildren = children !== undefined && children !== null;
const childComponent = typeof children === 'function' ? children(isToggled) : children;

const ToggleComponent = childComponent ? (
cloneElement(childComponent, {
isDisabled,
onClick: (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
childComponent?.props.onClick?.(e);
},
})
<div>
{cloneElement(childComponent, {
isDisabled,
onClick: (e: MouseEvent) => {
childComponent?.props.onClick?.(e);
},
})}
</div>
) : (
<MoreIcon
size="small"
variant="tertiary"
icon="dotsThree"
tabIndex={-1}
onClick={e => e.stopPropagation()}
$isToggled={isToggled}
isDisabled={isDisabled}
data-testid={dataTest}
/>
);

const PortalMenu = createPortal(
<Menu
ref={menuRef}
items={items}
content={content}
coords={coords}
setToggled={setToggled}
alignMenu={alignMenu}
addon={addon}
/>,
document.body,
);

const frameProps = pickAndPrepareFrameProps(rest, allowedDropdownFrameProps);

return (
<Container
ref={toggleRef}
className={className}
tabIndex={renderOnClickPosition ? -1 : 0}
$disabled={isDisabled}
onClick={onToggleClick}
onFocus={() => !isDisabled && !renderOnClickPosition && setToggled(true)}
onBlur={e => !menuRef.current?.contains(e.relatedTarget) && setToggled(false)}
$hasCustomChildren={hasCustomChildren}
{...frameProps}
<Popover
placement={placement}
content={
<Menu items={items} content={content} setToggled={setToggled} addon={addon} />
}
>
{ToggleComponent}
{isToggled && PortalMenu}
</Container>
</Popover>
);
},
);
96 changes: 0 additions & 96 deletions packages/components/src/components/Dropdown/getAdjustedCoords.ts

This file was deleted.

Loading

0 comments on commit bef1b3c

Please sign in to comment.