Chore(front): Create Storybook tests for the DropdownMenu component (#2157)

* Chore(front): Create Storybook tests for the DropdownMenu component

Co-authored-by: Benjamin Mayanja V <vibenjamin6@gmail.com>
Co-authored-by: FellipeMTX <fellipefacdir@gmail.com>

* Fix the tests

Co-authored-by: Benjamin Mayanja V <vibenjamin6@gmail.com>
Co-authored-by: FellipeMTX <fellipefacdir@gmail.com>

* Simplify Dropdown

* Remove console.log

---------

Co-authored-by: Benjamin Mayanja V <vibenjamin6@gmail.com>
Co-authored-by: FellipeMTX <fellipefacdir@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
gitstart-twenty
2023-10-20 22:19:43 +03:00
committed by GitHub
parent eea7470571
commit dee9807eb3
41 changed files with 634 additions and 674 deletions

View File

@ -4,7 +4,7 @@ import { useRecoilState } from 'recoil';
import { IconTrash } from '@/ui/display/icon';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/layout/dropdown/components/StyledDropdownMenuSeparator';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MenuItemSelectColor } from '@/ui/navigation/menu-item/components/MenuItemSelectColor';
import { ThemeColor } from '@/ui/theme/constants/colors';
@ -123,7 +123,7 @@ export const BoardColumnEditTitleMenu = ({
autoFocus
/>
</StyledEditTitleContainer>
<StyledDropdownMenuSeparator />
<DropdownMenuSeparator />
{COLUMN_COLOR_OPTIONS.map((colorOption) => (
<MenuItemSelectColor
key={colorOption.name}
@ -135,7 +135,7 @@ export const BoardColumnEditTitleMenu = ({
text={colorOption.name}
/>
))}
<StyledDropdownMenuSeparator />
<DropdownMenuSeparator />
<MenuItem
onClick={handleDelete}
LeftIcon={IconTrash}

View File

@ -15,8 +15,8 @@ import { SingleEntitySelect } from '@/ui/input/relation-picker/components/Single
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { StyledDropdownMenu } from '@/ui/layout/dropdown/components/StyledDropdownMenu';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
@ -138,7 +138,7 @@ export const BoardColumnMenu = ({
return (
<StyledMenuContainer ref={boardColumnMenuRef}>
<StyledDropdownMenu data-select-disable>
<DropdownMenu data-select-disable>
{currentMenu === 'actions' && (
<DropdownMenuItemsContainer>
<MenuItem
@ -183,7 +183,7 @@ export const BoardColumnMenu = ({
selectedEntity={companies.selectedEntities[0]}
/>
)}
</StyledDropdownMenu>
</DropdownMenu>
</StyledMenuContainer>
);
};

View File

@ -1,8 +1,9 @@
import { useResetRecoilState } from 'recoil';
import { ViewBarDropdownButton } from '@/ui/data/view-bar/components/ViewBarDropdownButton';
import { viewEditModeState } from '@/ui/data/view-bar/states/viewEditModeState';
import { Dropdown } from '../../dropdown/components/Dropdown';
import { DropdownScope } from '../../dropdown/scopes/DropdownScope';
import { BoardScopeIds } from '../types/enums/BoardScopeIds';
import { BoardOptionsDropdownButton } from './BoardOptionsDropdownButton';
@ -23,17 +24,18 @@ export const BoardOptionsDropdown = ({
const resetViewEditMode = useResetRecoilState(viewEditModeState);
return (
<ViewBarDropdownButton
buttonComponent={<BoardOptionsDropdownButton />}
dropdownComponents={
<BoardOptionsDropdownContent
customHotkeyScope={customHotkeyScope}
onStageAdd={onStageAdd}
/>
}
dropdownHotkeyScope={customHotkeyScope}
dropdownId={BoardScopeIds.OptionsDropdown}
onClickOutside={resetViewEditMode}
/>
<DropdownScope dropdownScopeId={BoardScopeIds.OptionsDropdown}>
<Dropdown
clickableComponent={<BoardOptionsDropdownButton />}
dropdownComponents={
<BoardOptionsDropdownContent
customHotkeyScope={customHotkeyScope}
onStageAdd={onStageAdd}
/>
}
dropdownHotkeyScope={customHotkeyScope}
onClickOutside={resetViewEditMode}
/>
</DropdownScope>
);
};

View File

@ -22,11 +22,9 @@ import {
} from '@/ui/display/icon';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
import { DropdownMenuInput } from '@/ui/layout/dropdown/components/DropdownMenuInput';
import { DropdownMenuInputContainer } from '@/ui/layout/dropdown/components/DropdownMenuInputContainer';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { StyledDropdownMenu } from '@/ui/layout/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuSeparator } from '@/ui/layout/dropdown/components/StyledDropdownMenuSeparator';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MenuItemNavigate } from '@/ui/navigation/menu-item/components/MenuItemNavigate';
@ -166,28 +164,24 @@ export const BoardOptionsDropdownContent = ({
);
return (
<StyledDropdownMenu>
<>
{!currentMenu && (
<>
<DropdownMenuInputContainer>
<DropdownMenuInput
ref={viewEditInputRef}
autoFocus={
viewEditMode.mode === 'create' || !!viewEditMode.viewId
}
placeholder={
viewEditMode.mode === 'create' ? 'New view' : 'View name'
}
defaultValue={
viewEditMode.mode === 'create'
? ''
: viewEditMode.viewId
? viewsById[viewEditMode.viewId]?.name
: currentView?.name
}
/>
</DropdownMenuInputContainer>
<StyledDropdownMenuSeparator />
<DropdownMenuInput
ref={viewEditInputRef}
autoFocus={viewEditMode.mode === 'create' || !!viewEditMode.viewId}
placeholder={
viewEditMode.mode === 'create' ? 'New view' : 'View name'
}
defaultValue={
viewEditMode.mode === 'create'
? ''
: viewEditMode.viewId
? viewsById[viewEditMode.viewId]?.name
: currentView?.name
}
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
<MenuItemNavigate
onClick={() => handleMenuNavigate('fields')}
@ -207,7 +201,7 @@ export const BoardOptionsDropdownContent = ({
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetMenu}>
Stages
</DropdownMenuHeader>
<StyledDropdownMenuSeparator />
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
<MenuItem
onClick={() => setCurrentMenu('stage-creation')}
@ -229,7 +223,7 @@ export const BoardOptionsDropdownContent = ({
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetMenu}>
Fields
</DropdownMenuHeader>
<StyledDropdownMenuSeparator />
<DropdownMenuSeparator />
{hasVisibleFields && (
<ViewFieldsVisibilityDropdownSection
title="Visible"
@ -238,9 +232,7 @@ export const BoardOptionsDropdownContent = ({
isDraggable={true}
/>
)}
{hasVisibleFields && hasHiddenFields && (
<StyledDropdownMenuSeparator />
)}
{hasVisibleFields && hasHiddenFields && <DropdownMenuSeparator />}
{hasHiddenFields && (
<ViewFieldsVisibilityDropdownSection
title="Hidden"
@ -251,6 +243,6 @@ export const BoardOptionsDropdownContent = ({
)}
</>
)}
</StyledDropdownMenu>
</>
);
};

View File

@ -0,0 +1,117 @@
import { useRef } from 'react';
import { Keys } from 'react-hotkeys-hook';
import { flip, offset, Placement, useFloating } from '@floating-ui/react';
import { Key } from 'ts-key-enum';
import { HotkeyEffect } from '@/ui/utilities/hotkey/components/HotkeyEffect';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useDropdown } from '../hooks/useDropdown';
import { useInternalHotkeyScopeManagement } from '../hooks/useInternalHotkeyScopeManagement';
import { DropdownMenu } from './DropdownMenu';
import { DropdownToggleEffect } from './DropdownToggleEffect';
type DropdownProps = {
clickableComponent?: JSX.Element | JSX.Element[];
dropdownComponents: JSX.Element | JSX.Element[];
hotkey?: {
key: Keys;
scope: string;
};
dropdownHotkeyScope: HotkeyScope;
dropdownPlacement?: Placement;
dropdownMenuWidth?: number;
dropdownOffset?: { x?: number; y?: number };
onClickOutside?: () => void;
onClose?: () => void;
onOpen?: () => void;
};
export const Dropdown = ({
clickableComponent,
dropdownComponents,
dropdownMenuWidth = 160,
hotkey,
dropdownHotkeyScope,
dropdownPlacement = 'bottom-end',
dropdownOffset = { x: 0, y: 0 },
onClickOutside,
onClose,
onOpen,
}: DropdownProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const { isDropdownOpen, toggleDropdown, closeDropdown } = useDropdown();
const offsetMiddlewares = [];
if (dropdownOffset.x) {
offsetMiddlewares.push(offset({ crossAxis: dropdownOffset.x }));
}
if (dropdownOffset.y) {
offsetMiddlewares.push(offset({ mainAxis: dropdownOffset.y }));
}
const { refs, floatingStyles } = useFloating({
placement: dropdownPlacement,
middleware: [flip(), ...offsetMiddlewares],
});
const handleHotkeyTriggered = () => {
toggleDropdown();
};
useListenClickOutside({
refs: [containerRef],
callback: () => {
onClickOutside?.();
if (isDropdownOpen) {
closeDropdown();
}
},
});
useInternalHotkeyScopeManagement({
dropdownHotkeyScopeFromParent: dropdownHotkeyScope,
});
useScopedHotkeys(
Key.Escape,
() => {
closeDropdown();
},
dropdownHotkeyScope.scope,
[closeDropdown],
);
return (
<div ref={containerRef}>
{clickableComponent && (
<div ref={refs.setReference} onClick={toggleDropdown}>
{clickableComponent}
</div>
)}
{hotkey && (
<HotkeyEffect
hotkey={hotkey}
onHotkeyTriggered={handleHotkeyTriggered}
/>
)}
{isDropdownOpen && (
<DropdownMenu
width={dropdownMenuWidth}
data-select-disable
ref={refs.setFloating}
style={floatingStyles}
>
{dropdownComponents}
</DropdownMenu>
)}
<DropdownToggleEffect onDropdownClose={onClose} onDropdownOpen={onOpen} />
</div>
);
};

View File

@ -1,103 +1,25 @@
import { useRef } from 'react';
import { Keys } from 'react-hotkeys-hook';
import { flip, offset, Placement, useFloating } from '@floating-ui/react';
import { Key } from 'ts-key-enum';
import styled from '@emotion/styled';
import { HotkeyEffect } from '@/ui/utilities/hotkey/components/HotkeyEffect';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
const StyledDropdownMenu = styled.div<{
disableBlur?: boolean;
width?: `${string}px` | 'auto' | number;
}>`
backdrop-filter: ${({ disableBlur }) =>
disableBlur ? 'none' : 'blur(20px)'};
import { useDropdown } from '../hooks/useDropdown';
import { useInternalHotkeyScopeManagement } from '../hooks/useInternalHotkeyScopeManagement';
background: ${({ theme }) => theme.background.secondary};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.md};
box-shadow: ${({ theme }) => theme.boxShadow.strong};
import { DropdownToggleEffect } from './DropdownToggleEffect';
display: flex;
type DropdownMenuProps = {
clickableComponent?: JSX.Element | JSX.Element[];
dropdownComponents: JSX.Element | JSX.Element[];
hotkey?: {
key: Keys;
scope: string;
};
dropdownHotkeyScope: HotkeyScope;
dropdownPlacement?: Placement;
dropdownOffset?: { x: number; y: number };
onClickOutside?: () => void;
onClose?: () => void;
onOpen?: () => void;
};
flex-direction: column;
export const DropdownMenu = ({
clickableComponent,
dropdownComponents,
hotkey,
dropdownHotkeyScope,
dropdownPlacement = 'bottom-end',
dropdownOffset = { x: 0, y: 0 },
onClickOutside,
onClose,
onOpen,
}: DropdownMenuProps) => {
const containerRef = useRef<HTMLDivElement>(null);
overflow: hidden;
const { isDropdownOpen, toggleDropdown, closeDropdown } = useDropdown();
width: ${({ width }) =>
width ? `${typeof width === 'number' ? `${width}px` : width}` : '160px'};
`;
const { refs, floatingStyles } = useFloating({
placement: dropdownPlacement,
middleware: [
flip(),
offset({ mainAxis: dropdownOffset.y, crossAxis: dropdownOffset.x }),
],
});
const handleHotkeyTriggered = () => {
toggleDropdown();
};
useListenClickOutside({
refs: [containerRef],
callback: () => {
onClickOutside?.();
if (isDropdownOpen) {
closeDropdown();
}
},
});
useInternalHotkeyScopeManagement({
dropdownHotkeyScopeFromParent: dropdownHotkeyScope,
});
useScopedHotkeys(
Key.Escape,
() => {
closeDropdown();
},
dropdownHotkeyScope.scope,
[closeDropdown],
);
return (
<div ref={containerRef}>
{clickableComponent && (
<div ref={refs.setReference} onClick={toggleDropdown}>
{clickableComponent}
</div>
)}
{hotkey && (
<HotkeyEffect
hotkey={hotkey}
onHotkeyTriggered={handleHotkeyTriggered}
/>
)}
{isDropdownOpen && (
<div data-select-disable ref={refs.setFloating} style={floatingStyles}>
{dropdownComponents}
</div>
)}
<DropdownToggleEffect onDropdownClose={onClose} onDropdownOpen={onOpen} />
</div>
);
};
export const DropdownMenu = StyledDropdownMenu;

View File

@ -1,47 +0,0 @@
import { HTMLAttributes, useRef } from 'react';
import styled from '@emotion/styled';
import { StyledDropdownMenu } from '@/ui/layout/dropdown/components/StyledDropdownMenu';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
const StyledDropdownMenuContainer = styled.ul<{
anchor: 'left' | 'right';
}>`
padding: 0;
position: absolute;
${({ anchor }) => {
if (anchor === 'right') return 'right: 0';
}};
top: 14px;
`;
export type DropdownMenuContainerProps = {
anchor?: 'left' | 'right';
children: React.ReactNode;
onClose?: () => void;
width?: `${string}px` | 'auto' | number;
} & HTMLAttributes<HTMLUListElement>;
export const DropdownMenuContainer = ({
anchor = 'right',
children,
onClose,
width,
}: DropdownMenuContainerProps) => {
const dropdownRef = useRef<HTMLDivElement>(null);
useListenClickOutside({
refs: [dropdownRef],
callback: () => {
onClose?.();
},
});
return (
<StyledDropdownMenuContainer data-select-disable anchor={anchor}>
<StyledDropdownMenu ref={dropdownRef} width={width}>
{children}
</StyledDropdownMenu>
</StyledDropdownMenuContainer>
);
};

View File

@ -1,9 +1,10 @@
import { forwardRef, InputHTMLAttributes } from 'react';
import styled from '@emotion/styled';
import { rgba } from '@/ui/theme/constants/colors';
import { textInputStyle } from '@/ui/theme/constants/effects';
const StyledViewNameInput = styled.input`
const StyledInput = styled.input`
${textInputStyle}
border: 1px solid ${({ theme }) => theme.border.color.medium};
@ -20,4 +21,24 @@ const StyledViewNameInput = styled.input`
}
`;
export { StyledViewNameInput as DropdownMenuInput };
const StyledInputContainer = styled.div`
box-sizing: border-box;
padding: ${({ theme }) => theme.spacing(1)};
width: 100%;
`;
export const DropdownMenuInput = forwardRef<
HTMLInputElement,
InputHTMLAttributes<HTMLInputElement>
>(({ autoFocus, defaultValue, placeholder }, ref) => {
return (
<StyledInputContainer>
<StyledInput
autoFocus={autoFocus}
defaultValue={defaultValue}
placeholder={placeholder}
ref={ref}
/>
</StyledInputContainer>
);
});

View File

@ -1,9 +0,0 @@
import styled from '@emotion/styled';
const StyledInputContainer = styled.div`
box-sizing: border-box;
padding: ${({ theme }) => theme.spacing(1)};
width: 100%;
`;
export { StyledInputContainer as DropdownMenuInputContainer };

View File

@ -1,8 +1,10 @@
import styled from '@emotion/styled';
export const StyledDropdownMenuSeparator = styled.div`
const StyledDropdownMenuSeparator = styled.div`
background-color: ${({ theme }) => theme.border.color.light};
height: 1px;
width: 100%;
`;
export const DropdownMenuSeparator = StyledDropdownMenuSeparator;

View File

@ -1,23 +0,0 @@
import styled from '@emotion/styled';
export const StyledDropdownMenu = styled.div<{
disableBlur?: boolean;
width?: `${string}px` | 'auto' | number;
}>`
backdrop-filter: ${({ disableBlur }) =>
disableBlur ? 'none' : 'blur(20px)'};
background: ${({ theme }) => theme.background.secondary};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.md};
box-shadow: ${({ theme }) => theme.boxShadow.strong};
display: flex;
flex-direction: column;
overflow: hidden;
width: ${({ width }) =>
width ? `${typeof width === 'number' ? `${width}px` : width}` : '160px'};
`;

View File

@ -1,7 +1,8 @@
import { useState } from 'react';
import styled from '@emotion/styled';
import { expect } from '@storybook/jest';
import { Decorator, Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import { userEvent, waitFor, within } from '@storybook/testing-library';
import { PlayFunction } from '@storybook/types';
import { Button } from '@/ui/input/button/components/Button';
@ -13,19 +14,17 @@ import { Avatar } from '@/users/components/Avatar';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { DropdownScope } from '../../scopes/DropdownScope';
import { DropdownMenu } from '../DropdownMenu';
import { Dropdown } from '../Dropdown';
import { DropdownMenuHeader } from '../DropdownMenuHeader';
import { DropdownMenuInput } from '../DropdownMenuInput';
import { DropdownMenuInputContainer } from '../DropdownMenuInputContainer';
import { DropdownMenuItemsContainer } from '../DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '../DropdownMenuSearchInput';
import { StyledDropdownMenu } from '../StyledDropdownMenu';
import { StyledDropdownMenuSeparator } from '../StyledDropdownMenuSeparator';
import { DropdownMenuSeparator } from '../DropdownMenuSeparator';
import { StyledDropdownMenuSubheader } from '../StyledDropdownMenuSubheader';
const meta: Meta<typeof DropdownMenu> = {
title: 'UI/Layout/Dropdown/DropdownMenu',
component: DropdownMenu,
const meta: Meta<typeof Dropdown> = {
title: 'UI/Layout/Dropdown/Dropdown',
component: Dropdown,
decorators: [
ComponentDecorator,
@ -38,7 +37,7 @@ const meta: Meta<typeof DropdownMenu> = {
args: {
clickableComponent: <Button title="Open Dropdown" />,
dropdownHotkeyScope: { scope: 'testDropdownMenu' },
dropdownOffset: { x: 0, y: -8 },
dropdownOffset: { x: 0, y: 8 },
},
argTypes: {
clickableComponent: { control: false },
@ -49,26 +48,9 @@ const meta: Meta<typeof DropdownMenu> = {
};
export default meta;
type Story = StoryObj<typeof DropdownMenu>;
type Story = StoryObj<typeof Dropdown>;
const FakeContentBelow = () => (
<div style={{ position: 'absolute' }}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.
</div>
);
const avatarUrl =
'https://s3-alpha-sig.figma.com/img/bbb5/4905/f0a52cc2b9aaeb0a82a360d478dae8bf?Expires=1687132800&Signature=iVBr0BADa3LHoFVGbwqO-wxC51n1o~ZyFD-w7nyTyFP4yB-Y6zFawL-igewaFf6PrlumCyMJThDLAAc-s-Cu35SBL8BjzLQ6HymzCXbrblUADMB208PnMAvc1EEUDq8TyryFjRO~GggLBk5yR0EXzZ3zenqnDEGEoQZR~TRqS~uDF-GwQB3eX~VdnuiU2iittWJkajIDmZtpN3yWtl4H630A3opQvBnVHZjXAL5YPkdh87-a-H~6FusWvvfJxfNC2ZzbrARzXofo8dUFtH7zUXGCC~eUk~hIuLbLuz024lFQOjiWq2VKyB7dQQuGFpM-OZQEV8tSfkViP8uzDLTaCg__&Key-Pair-Id=APKAQ4GOSFWCVNEHN3O4';
const StyledFakeMenuContent = styled.div`
height: 400px;
width: 100%;
`;
const StyledFakeBelowContainer = styled.div`
const StyledContainer = styled.div`
height: 600px;
position: relative;
@ -77,12 +59,62 @@ const StyledFakeBelowContainer = styled.div`
const StyledMenuAbsolutePositionWrapper = styled.div`
height: fit-content;
position: absolute;
width: fit-content;
`;
const mockSelectArray = [
const WithContentBelowDecorator: Decorator = (Story) => (
<StyledContainer>
<StyledMenuAbsolutePositionWrapper>
<Story />
</StyledMenuAbsolutePositionWrapper>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.
</StyledContainer>
);
const StyledEmptyDropdownContent = styled.div`
height: 400px;
width: 100%;
`;
export const Empty: Story = {
args: {
dropdownComponents: (
<StyledEmptyDropdownContent data-testid="dropdown-content" />
),
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = await canvas.findByRole('button');
userEvent.click(button);
await waitFor(async () => {
const fakeMenu = await canvas.findByTestId('dropdown-content');
expect(fakeMenu).toBeInTheDocument();
});
userEvent.click(button);
await waitFor(async () => {
const fakeMenu = await canvas.findByTestId('dropdown-content');
expect(fakeMenu).not.toBeInTheDocument();
});
userEvent.click(button);
await waitFor(async () => {
const fakeMenu = await canvas.findByTestId('dropdown-content');
expect(fakeMenu).toBeInTheDocument();
});
},
};
const avatarUrl =
'https://s3-alpha-sig.figma.com/img/bbb5/4905/f0a52cc2b9aaeb0a82a360d478dae8bf?Expires=1687132800&Signature=iVBr0BADa3LHoFVGbwqO-wxC51n1o~ZyFD-w7nyTyFP4yB-Y6zFawL-igewaFf6PrlumCyMJThDLAAc-s-Cu35SBL8BjzLQ6HymzCXbrblUADMB208PnMAvc1EEUDq8TyryFjRO~GggLBk5yR0EXzZ3zenqnDEGEoQZR~TRqS~uDF-GwQB3eX~VdnuiU2iittWJkajIDmZtpN3yWtl4H630A3opQvBnVHZjXAL5YPkdh87-a-H~6FusWvvfJxfNC2ZzbrARzXofo8dUFtH7zUXGCC~eUk~hIuLbLuz024lFQOjiWq2VKyB7dQQuGFpM-OZQEV8tSfkViP8uzDLTaCg__&Key-Pair-Id=APKAQ4GOSFWCVNEHN3O4';
const optionsMock = [
{
id: '1',
name: 'Company A',
@ -120,7 +152,7 @@ const FakeSelectableMenuItemList = ({ hasAvatar }: { hasAvatar?: boolean }) => {
return (
<>
{mockSelectArray.map((item) => (
{optionsMock.map((item) => (
<MenuItemSelectAvatar
key={item.id}
selected={selectedItem === item.id}
@ -149,7 +181,7 @@ const FakeCheckableMenuItemList = ({ hasAvatar }: { hasAvatar?: boolean }) => {
return (
<>
{mockSelectArray.map((item) => (
{optionsMock.map((item) => (
<MenuItemMultiSelectAvatar
key={item.id}
selected={selectedItemsById[item.id]}
@ -176,55 +208,40 @@ const FakeCheckableMenuItemList = ({ hasAvatar }: { hasAvatar?: boolean }) => {
);
};
const WithContentBelowDecorator: Decorator = (Story) => (
<StyledFakeBelowContainer>
<FakeContentBelow />
<StyledMenuAbsolutePositionWrapper>
<Story />
</StyledMenuAbsolutePositionWrapper>
</StyledFakeBelowContainer>
);
const playInteraction: PlayFunction<any, any> = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = await canvas.findByRole('button');
userEvent.click(button);
};
export const Empty: Story = {
args: {
dropdownComponents: (
<StyledDropdownMenu>
<StyledFakeMenuContent />
</StyledDropdownMenu>
),
},
play: playInteraction,
await waitFor(async () => {
expect(canvas.getByText('Company A')).toBeInTheDocument();
});
};
export const WithHeaders: Story = {
decorators: [WithContentBelowDecorator],
args: {
dropdownComponents: (
<StyledDropdownMenu>
<>
<DropdownMenuHeader>Header</DropdownMenuHeader>
<StyledDropdownMenuSeparator />
<DropdownMenuSeparator />
<StyledDropdownMenuSubheader>Subheader 1</StyledDropdownMenuSubheader>
<DropdownMenuItemsContainer>
{mockSelectArray.slice(0, 3).map(({ name }) => (
<MenuItem text={name} />
))}
<DropdownMenuItemsContainer hasMaxHeight>
<>
{optionsMock.slice(0, 3).map(({ name }) => (
<MenuItem text={name} />
))}
</>
</DropdownMenuItemsContainer>
<StyledDropdownMenuSeparator />
<DropdownMenuSeparator />
<StyledDropdownMenuSubheader>Subheader 2</StyledDropdownMenuSubheader>
<DropdownMenuItemsContainer>
{mockSelectArray.slice(3).map(({ name }) => (
{optionsMock.slice(3).map(({ name }) => (
<MenuItem text={name} />
))}
</DropdownMenuItemsContainer>
</StyledDropdownMenu>
</>
),
},
play: playInteraction,
@ -234,33 +251,45 @@ export const SearchWithLoadingMenu: Story = {
decorators: [WithContentBelowDecorator],
args: {
dropdownComponents: (
<StyledDropdownMenu>
<>
<DropdownMenuSearchInput value="query" autoFocus />
<StyledDropdownMenuSeparator />
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
<DropdownMenuSkeletonItem />
</DropdownMenuItemsContainer>
</StyledDropdownMenu>
</>
),
},
play: playInteraction,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = await canvas.findByRole('button');
await waitFor(() => {
userEvent.click(button);
expect(canvas.getByDisplayValue('query')).toBeInTheDocument();
});
await waitFor(() => {
userEvent.click(button);
expect(canvas.queryByDisplayValue('query')).not.toBeInTheDocument();
});
},
};
export const WithInput: Story = {
decorators: [WithContentBelowDecorator],
args: {
dropdownComponents: (
<StyledDropdownMenu>
<DropdownMenuInputContainer>
<DropdownMenuInput defaultValue="Lorem ipsum" autoFocus />
</DropdownMenuInputContainer>
<StyledDropdownMenuSeparator />
<>
<DropdownMenuInput defaultValue="Lorem ipsum" autoFocus />
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
{mockSelectArray.map(({ name }) => (
{optionsMock.map(({ name }) => (
<MenuItem text={name} />
))}
</DropdownMenuItemsContainer>
</StyledDropdownMenu>
</>
),
},
play: playInteraction,
@ -270,11 +299,9 @@ export const SelectableMenuItemWithAvatar: Story = {
decorators: [WithContentBelowDecorator],
args: {
dropdownComponents: (
<StyledDropdownMenu>
<DropdownMenuItemsContainer hasMaxHeight>
<FakeSelectableMenuItemList hasAvatar />
</DropdownMenuItemsContainer>
</StyledDropdownMenu>
<DropdownMenuItemsContainer hasMaxHeight>
<FakeSelectableMenuItemList hasAvatar />
</DropdownMenuItemsContainer>
),
},
play: playInteraction,
@ -284,11 +311,9 @@ export const CheckableMenuItemWithAvatar: Story = {
decorators: [WithContentBelowDecorator],
args: {
dropdownComponents: (
<StyledDropdownMenu>
<DropdownMenuItemsContainer hasMaxHeight>
<FakeCheckableMenuItemList hasAvatar />
</DropdownMenuItemsContainer>
</StyledDropdownMenu>
<DropdownMenuItemsContainer hasMaxHeight>
<FakeCheckableMenuItemList hasAvatar />
</DropdownMenuItemsContainer>
),
},
play: playInteraction,

View File

@ -9,10 +9,6 @@ const meta: Meta<typeof DropdownMenuInput> = {
component: DropdownMenuInput,
decorators: [ComponentDecorator],
args: { defaultValue: 'Lorem ipsum' },
argTypes: {
as: { table: { disable: true } },
theme: { table: { disable: true } },
},
};
export default meta;

View File

@ -3,15 +3,17 @@ import styled from '@emotion/styled';
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { ViewBarDropdownButton } from '@/ui/data/view-bar/components/ViewBarDropdownButton';
import { IconCheckbox, IconNotes, IconPlus } from '@/ui/display/icon/index';
import { IconButton } from '@/ui/input/button/components/IconButton';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { StyledDropdownMenu } from '@/ui/layout/dropdown/components/StyledDropdownMenu';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { ActivityType } from '~/generated/graphql';
import { Dropdown } from '../../dropdown/components/Dropdown';
import { DropdownMenu } from '../../dropdown/components/DropdownMenu';
import { DropdownScope } from '../../dropdown/scopes/DropdownScope';
const StyledContainer = styled.div`
z-index: 1;
`;
@ -33,40 +35,41 @@ export const ShowPageAddButton = ({
return (
<StyledContainer>
<ViewBarDropdownButton
dropdownId="add-show-page"
buttonComponent={
<IconButton
Icon={IconPlus}
size="medium"
dataTestId="add-showpage-button"
accent="default"
variant="secondary"
onClick={toggleDropdown}
/>
}
dropdownComponents={
<StyledDropdownMenu>
<DropdownMenuItemsContainer>
<MenuItem
onClick={() => handleSelect(ActivityType.Note)}
accent="default"
LeftIcon={IconNotes}
text="Note"
/>
<MenuItem
onClick={() => handleSelect(ActivityType.Task)}
accent="default"
LeftIcon={IconCheckbox}
text="Task"
/>
</DropdownMenuItemsContainer>
</StyledDropdownMenu>
}
dropdownHotkeyScope={{
scope: PageHotkeyScope.ShowPage,
}}
/>
<DropdownScope dropdownScopeId="add-show-page">
<Dropdown
clickableComponent={
<IconButton
Icon={IconPlus}
size="medium"
dataTestId="add-showpage-button"
accent="default"
variant="secondary"
onClick={toggleDropdown}
/>
}
dropdownComponents={
<DropdownMenu>
<DropdownMenuItemsContainer>
<MenuItem
onClick={() => handleSelect(ActivityType.Note)}
accent="default"
LeftIcon={IconNotes}
text="Note"
/>
<MenuItem
onClick={() => handleSelect(ActivityType.Task)}
accent="default"
LeftIcon={IconCheckbox}
text="Task"
/>
</DropdownMenuItemsContainer>
</DropdownMenu>
}
dropdownHotkeyScope={{
scope: PageHotkeyScope.ShowPage,
}}
/>
</DropdownScope>
</StyledContainer>
);
};