From dee9807eb3047c35c22162f6bfc2838534db3ca4 Mon Sep 17 00:00:00 2001 From: gitstart-twenty <140154534+gitstart-twenty@users.noreply.github.com> Date: Fri, 20 Oct 2023 22:19:43 +0300 Subject: [PATCH] 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 Co-authored-by: FellipeMTX * Fix the tests Co-authored-by: Benjamin Mayanja V Co-authored-by: FellipeMTX * Simplify Dropdown * Remove console.log --------- Co-authored-by: Benjamin Mayanja V Co-authored-by: FellipeMTX Co-authored-by: Charles Bochet --- front/.storybook/preview-head.html | 13 ++ front/package.json | 2 +- .../components/CompanyProgressPicker.tsx | 12 +- .../modules/people/components/PeopleCard.tsx | 6 +- .../pipeline/components/PipelineAddButton.tsx | 58 +++--- .../SettingsObjectDisabledMenuDropDown.tsx | 8 +- .../components/MatchColumnSelect.tsx | 10 +- .../components/ColumnHeadWithDropdown.tsx | 4 +- .../DataTableColumnDropdownMenu.tsx | 6 +- .../components/DataTableHeaderPlusButton.tsx | 4 +- .../components/TableOptionsDropdown.tsx | 19 +- .../TableOptionsDropdownContent.tsx | 45 ++--- .../components/FilterDropdownEntitySelect.tsx | 4 +- .../MultipleFiltersDropdownButton.tsx | 17 +- .../MultipleFiltersDropdownContent.tsx | 63 +++--- .../SingleEntityFilterDropdownButton.tsx | 9 +- .../components/SortDropdownButton.tsx | 99 ++++----- .../components/UpdateViewButtonGroup.tsx | 17 +- .../components/ViewBarDropdownButton.tsx | 49 ----- .../components/ViewsDropdownButton.tsx | 106 +++++----- .../ui/input/components/IconPicker.tsx | 14 +- .../CountryPickerDropdownButton.tsx | 4 +- .../CountryPickerDropdownSelect.tsx | 10 +- .../components/MultipleEntitySelect.tsx | 10 +- .../components/SingleEntitySelect.tsx | 10 +- .../components/SingleEntitySelectBase.tsx | 4 +- .../components/BoardColumnEditTitleMenu.tsx | 6 +- .../board/components/BoardColumnMenu.tsx | 6 +- .../board/components/BoardOptionsDropdown.tsx | 28 +-- .../BoardOptionsDropdownContent.tsx | 50 ++--- .../layout/dropdown/components/Dropdown.tsx | 117 +++++++++++ .../dropdown/components/DropdownMenu.tsx | 114 ++--------- .../components/DropdownMenuContainer.tsx | 47 ----- .../dropdown/components/DropdownMenuInput.tsx | 25 ++- .../components/DropdownMenuInputContainer.tsx | 9 - ...eparator.tsx => DropdownMenuSeparator.tsx} | 4 +- .../components/StyledDropdownMenu.tsx | 23 --- .../__stories__/DropdownMenu.stories.tsx | 191 ++++++++++-------- .../__stories__/DropdownMenuInput.stories.tsx | 4 - .../components/ShowPageAddButton.tsx | 75 +++---- .../context-menu/components/ContextMenu.tsx | 6 +- 41 files changed, 634 insertions(+), 674 deletions(-) delete mode 100644 front/src/modules/ui/data/view-bar/components/ViewBarDropdownButton.tsx create mode 100644 front/src/modules/ui/layout/dropdown/components/Dropdown.tsx delete mode 100644 front/src/modules/ui/layout/dropdown/components/DropdownMenuContainer.tsx delete mode 100644 front/src/modules/ui/layout/dropdown/components/DropdownMenuInputContainer.tsx rename front/src/modules/ui/layout/dropdown/components/{StyledDropdownMenuSeparator.tsx => DropdownMenuSeparator.tsx} (54%) delete mode 100644 front/src/modules/ui/layout/dropdown/components/StyledDropdownMenu.tsx diff --git a/front/.storybook/preview-head.html b/front/.storybook/preview-head.html index 6657ced2c..b846fe879 100644 --- a/front/.storybook/preview-head.html +++ b/front/.storybook/preview-head.html @@ -16,4 +16,17 @@ html { .sbdocs-wrapper { padding: 0 !important; } +*::-webkit-scrollbar { + height: 4px; + width: 4px; + } + + *::-webkit-scrollbar-corner { + background-color: transparent; + } + + *::-webkit-scrollbar-thumb { + background-color: transparent; + border-radius: 2px; + } \ No newline at end of file diff --git a/front/package.json b/front/package.json index 85e483413..21cd338be 100644 --- a/front/package.json +++ b/front/package.json @@ -181,4 +181,4 @@ "msw": { "workerDirectory": "public" } -} \ No newline at end of file +} diff --git a/front/src/modules/companies/components/CompanyProgressPicker.tsx b/front/src/modules/companies/components/CompanyProgressPicker.tsx index d997cc5c2..e6bebfa56 100644 --- a/front/src/modules/companies/components/CompanyProgressPicker.tsx +++ b/front/src/modules/companies/components/CompanyProgressPicker.tsx @@ -6,11 +6,11 @@ import { IconChevronDown } from '@/ui/display/icon'; import { SingleEntitySelectBase } from '@/ui/input/relation-picker/components/SingleEntitySelectBase'; import { useEntitySelectSearch } from '@/ui/input/relation-picker/hooks/useEntitySelectSearch'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; +import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader'; 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 { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; @@ -79,7 +79,7 @@ export const CompanyProgressPicker = ({ ); return ( - @@ -105,13 +105,13 @@ export const CompanyProgressPicker = ({ > {selectedPipelineStage?.name} - + - + )} - + ); }; diff --git a/front/src/modules/people/components/PeopleCard.tsx b/front/src/modules/people/components/PeopleCard.tsx index e1780043e..a273d3226 100644 --- a/front/src/modules/people/components/PeopleCard.tsx +++ b/front/src/modules/people/components/PeopleCard.tsx @@ -6,8 +6,8 @@ import { autoUpdate, flip, offset, useFloating } from '@floating-ui/react'; import { IconDotsVertical, IconLinkOff, IconTrash } from '@/ui/display/icon'; import { FloatingIconButton } from '@/ui/input/button/components/FloatingIconButton'; +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 { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { Avatar } from '@/users/components/Avatar'; @@ -164,7 +164,7 @@ export const PeopleCard = ({ Icon={IconDotsVertical} /> {isOptionsOpen && ( - - + )} )} diff --git a/front/src/modules/pipeline/components/PipelineAddButton.tsx b/front/src/modules/pipeline/components/PipelineAddButton.tsx index f335112c6..7d695224e 100644 --- a/front/src/modules/pipeline/components/PipelineAddButton.tsx +++ b/front/src/modules/pipeline/components/PipelineAddButton.tsx @@ -1,13 +1,14 @@ import { CompanyProgressPicker } from '@/companies/components/CompanyProgressPicker'; import { useCreateCompanyProgress } from '@/companies/hooks/useCreateCompanyProgress'; import { PageHotkeyScope } from '@/types/PageHotkeyScope'; -import { ViewBarDropdownButton } from '@/ui/data/view-bar/components/ViewBarDropdownButton'; import { IconPlus } from '@/ui/display/icon/index'; import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar'; import { IconButton } from '@/ui/input/button/components/IconButton'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; +import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; import { logError } from '~/utils/logError'; export const PipelineAddButton = () => { @@ -51,32 +52,33 @@ export const PipelineAddButton = () => { }; return ( - - } - dropdownComponents={ - - } - hotkey={{ - key: 'c', - scope: PageHotkeyScope.OpportunitiesPage, - }} - dropdownHotkeyScope={{ - scope: RelationPickerHotkeyScope.RelationPicker, - }} - /> + + + } + dropdownComponents={ + + } + hotkey={{ + key: 'c', + scope: PageHotkeyScope.OpportunitiesPage, + }} + dropdownHotkeyScope={{ + scope: RelationPickerHotkeyScope.RelationPicker, + }} + /> + ); }; diff --git a/front/src/modules/settings/data-model/objects/SettingsObjectDisabledMenuDropDown.tsx b/front/src/modules/settings/data-model/objects/SettingsObjectDisabledMenuDropDown.tsx index d090ac454..ea969e25a 100644 --- a/front/src/modules/settings/data-model/objects/SettingsObjectDisabledMenuDropDown.tsx +++ b/front/src/modules/settings/data-model/objects/SettingsObjectDisabledMenuDropDown.tsx @@ -1,9 +1,9 @@ import { IconDotsVertical, IconTrash } from '@/ui/display/icon'; import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; import { IconArchiveOff } from '@/ui/input/constants/icons'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; 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 { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; @@ -22,12 +22,12 @@ export const SettingsObjectDisabledMenuDropDown = ({ - } dropdownComponents={ - + - + } dropdownHotkeyScope={{ scope: scopeKey + '-settings-object-disabled-menu-dropdown', diff --git a/front/src/modules/spreadsheet-import/components/MatchColumnSelect.tsx b/front/src/modules/spreadsheet-import/components/MatchColumnSelect.tsx index 1fa871c20..44a3c43e9 100644 --- a/front/src/modules/spreadsheet-import/components/MatchColumnSelect.tsx +++ b/front/src/modules/spreadsheet-import/components/MatchColumnSelect.tsx @@ -14,10 +14,10 @@ import { ReadonlyDeep } from 'type-fest'; import { SelectOption } from '@/spreadsheet-import/types'; import { AppTooltip } from '@/ui/display/tooltip/AppTooltip'; +import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; 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 { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItemSelect } from '@/ui/navigation/menu-item/components/MenuItemSelect'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; @@ -116,7 +116,7 @@ export const MatchColumnSelect = ({ {isOpen && createPortal( - - + {options?.map((option) => ( <> @@ -156,7 +156,7 @@ export const MatchColumnSelect = ({ ))} {options?.length === 0 && } - + , document.body, )} diff --git a/front/src/modules/ui/data/data-table/components/ColumnHeadWithDropdown.tsx b/front/src/modules/ui/data/data-table/components/ColumnHeadWithDropdown.tsx index 3c51c476a..9b86e3b41 100644 --- a/front/src/modules/ui/data/data-table/components/ColumnHeadWithDropdown.tsx +++ b/front/src/modules/ui/data/data-table/components/ColumnHeadWithDropdown.tsx @@ -1,5 +1,5 @@ import { FieldMetadata } from '@/ui/data/field/types/FieldMetadata'; -import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; import { ColumnDefinition } from '../types/ColumnDefinition'; @@ -22,7 +22,7 @@ export const ColumnHeadWithDropdown = ({ }: ColumnHeadWithDropdownProps) => { return ( - } dropdownComponents={ ) : ( - + {!isFirstColumn && ( - + ); }; diff --git a/front/src/modules/ui/data/data-table/components/DataTableHeaderPlusButton.tsx b/front/src/modules/ui/data/data-table/components/DataTableHeaderPlusButton.tsx index 18e5bbe8b..521dd7e7e 100644 --- a/front/src/modules/ui/data/data-table/components/DataTableHeaderPlusButton.tsx +++ b/front/src/modules/ui/data/data-table/components/DataTableHeaderPlusButton.tsx @@ -3,8 +3,8 @@ import styled from '@emotion/styled'; import { FieldMetadata } from '@/ui/data/field/types/FieldMetadata'; import { IconPlus } from '@/ui/display/icon'; +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 { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; @@ -14,7 +14,7 @@ import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRe import { hiddenTableColumnsScopedSelector } from '../states/selectors/hiddenTableColumnsScopedSelector'; import { ColumnDefinition } from '../types/ColumnDefinition'; -const StyledHeaderPlusButton = styled(StyledDropdownMenu)` +const StyledHeaderPlusButton = styled(DropdownMenu)` font-weight: ${({ theme }) => theme.font.weight.regular}; `; diff --git a/front/src/modules/ui/data/data-table/options/components/TableOptionsDropdown.tsx b/front/src/modules/ui/data/data-table/options/components/TableOptionsDropdown.tsx index f6d2c062d..b99cbe534 100644 --- a/front/src/modules/ui/data/data-table/options/components/TableOptionsDropdown.tsx +++ b/front/src/modules/ui/data/data-table/options/components/TableOptionsDropdown.tsx @@ -1,7 +1,8 @@ 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 '@/ui/layout/dropdown/components/Dropdown'; +import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId'; @@ -19,12 +20,14 @@ export const TableOptionsDropdown = ({ const resetViewEditMode = useResetRecoilState(viewEditModeState); return ( - } - dropdownHotkeyScope={customHotkeyScope} - dropdownId={TableOptionsDropdownId} - dropdownComponents={} - onClickOutside={resetViewEditMode} - /> + + } + dropdownHotkeyScope={customHotkeyScope} + dropdownOffset={{ y: 8 }} + dropdownComponents={} + onClickOutside={resetViewEditMode} + /> + ); }; diff --git a/front/src/modules/ui/data/data-table/options/components/TableOptionsDropdownContent.tsx b/front/src/modules/ui/data/data-table/options/components/TableOptionsDropdownContent.tsx index 5a45cf672..e27495189 100644 --- a/front/src/modules/ui/data/data-table/options/components/TableOptionsDropdownContent.tsx +++ b/front/src/modules/ui/data/data-table/options/components/TableOptionsDropdownContent.tsx @@ -12,10 +12,8 @@ import { viewEditModeState } from '@/ui/data/view-bar/states/viewEditModeState'; import { IconChevronLeft, IconFileImport, IconTag } 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 { 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 { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; @@ -128,28 +126,23 @@ export const TableOptionsDropdownContent = () => { ); return ( - + <> {!currentMenu && ( <> - - - - + + handleSelectMenu('fields')} @@ -171,7 +164,7 @@ export const TableOptionsDropdownContent = () => { Fields - + { /> {hiddenTableColumns.length > 0 && ( <> - + { )} )} - + ); }; diff --git a/front/src/modules/ui/data/view-bar/components/FilterDropdownEntitySelect.tsx b/front/src/modules/ui/data/view-bar/components/FilterDropdownEntitySelect.tsx index 59df71071..4e5384dd1 100644 --- a/front/src/modules/ui/data/view-bar/components/FilterDropdownEntitySelect.tsx +++ b/front/src/modules/ui/data/view-bar/components/FilterDropdownEntitySelect.tsx @@ -1,4 +1,4 @@ -import { StyledDropdownMenuSeparator } from '@/ui/layout/dropdown/components/StyledDropdownMenuSeparator'; +import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; @@ -19,7 +19,7 @@ export const FilterDropdownEntitySelect = () => { return ( <> - + {filterDefinitionUsedInDropdown.entitySelectComponent} diff --git a/front/src/modules/ui/data/view-bar/components/MultipleFiltersDropdownButton.tsx b/front/src/modules/ui/data/view-bar/components/MultipleFiltersDropdownButton.tsx index 1e212d958..3e265274a 100644 --- a/front/src/modules/ui/data/view-bar/components/MultipleFiltersDropdownButton.tsx +++ b/front/src/modules/ui/data/view-bar/components/MultipleFiltersDropdownButton.tsx @@ -1,10 +1,11 @@ +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { FilterDropdownId } from '../constants/FilterDropdownId'; import { MultipleFiltersButton } from './MultipleFiltersButton'; import { MultipleFiltersDropdownContent } from './MultipleFiltersDropdownContent'; -import { ViewBarDropdownButton } from './ViewBarDropdownButton'; type MultipleFiltersDropdownButtonProps = { hotkeyScope: HotkeyScope; @@ -14,11 +15,13 @@ export const MultipleFiltersDropdownButton = ({ hotkeyScope, }: MultipleFiltersDropdownButtonProps) => { return ( - } - dropdownComponents={} - dropdownHotkeyScope={hotkeyScope} - /> + + } + dropdownComponents={} + dropdownHotkeyScope={hotkeyScope} + dropdownOffset={{ y: 8 }} + /> + ); }; diff --git a/front/src/modules/ui/data/view-bar/components/MultipleFiltersDropdownContent.tsx b/front/src/modules/ui/data/view-bar/components/MultipleFiltersDropdownContent.tsx index 4b9275ede..b31f9421b 100644 --- a/front/src/modules/ui/data/view-bar/components/MultipleFiltersDropdownContent.tsx +++ b/front/src/modules/ui/data/view-bar/components/MultipleFiltersDropdownContent.tsx @@ -1,5 +1,4 @@ -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 { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useViewBarContext } from '../hooks/useViewBarContext'; @@ -35,36 +34,34 @@ export const MultipleFiltersDropdownContent = () => { ); return ( - - <> - {!filterDefinitionUsedInDropdown ? ( - - ) : isFilterDropdownOperandSelectUnfolded ? ( - - ) : ( - selectedOperandInDropdown && ( - <> - - - {filterDefinitionUsedInDropdown.type === 'text' && ( - - )} - {filterDefinitionUsedInDropdown.type === 'number' && ( - - )} - {filterDefinitionUsedInDropdown.type === 'date' && ( - - )} - {filterDefinitionUsedInDropdown.type === 'entity' && ( - - )} - {filterDefinitionUsedInDropdown.type === 'entity' && ( - - )} - - ) - )} - - + <> + {!filterDefinitionUsedInDropdown ? ( + + ) : isFilterDropdownOperandSelectUnfolded ? ( + + ) : ( + selectedOperandInDropdown && ( + <> + + + {filterDefinitionUsedInDropdown.type === 'text' && ( + + )} + {filterDefinitionUsedInDropdown.type === 'number' && ( + + )} + {filterDefinitionUsedInDropdown.type === 'date' && ( + + )} + {filterDefinitionUsedInDropdown.type === 'entity' && ( + + )} + {filterDefinitionUsedInDropdown.type === 'entity' && ( + + )} + + ) + )} + ); }; diff --git a/front/src/modules/ui/data/view-bar/components/SingleEntityFilterDropdownButton.tsx b/front/src/modules/ui/data/view-bar/components/SingleEntityFilterDropdownButton.tsx index 346640b58..efa7baaf1 100644 --- a/front/src/modules/ui/data/view-bar/components/SingleEntityFilterDropdownButton.tsx +++ b/front/src/modules/ui/data/view-bar/components/SingleEntityFilterDropdownButton.tsx @@ -2,8 +2,7 @@ import React from 'react'; import { useTheme } from '@emotion/react'; import { IconChevronDown } from '@/ui/display/icon/index'; -import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; -import { DropdownMenuContainer } from '@/ui/layout/dropdown/components/DropdownMenuContainer'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton'; import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; @@ -63,7 +62,7 @@ export const SingleEntityFilterDropdownButton = ({ return ( - } dropdownComponents={ - + <> - + } /> diff --git a/front/src/modules/ui/data/view-bar/components/SortDropdownButton.tsx b/front/src/modules/ui/data/view-bar/components/SortDropdownButton.tsx index 407314519..8d7e23bbf 100644 --- a/front/src/modules/ui/data/view-bar/components/SortDropdownButton.tsx +++ b/front/src/modules/ui/data/view-bar/components/SortDropdownButton.tsx @@ -3,11 +3,12 @@ import { produce } from 'immer'; import { IconChevronDown } from '@/ui/display/icon'; import { LightButton } from '@/ui/input/button/components/LightButton'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; -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 { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; @@ -19,8 +20,6 @@ import { sortsScopedState } from '../states/sortsScopedState'; import { SortDefinition } from '../types/SortDefinition'; import { SORT_DIRECTIONS, SortDirection } from '../types/SortDirection'; -import { ViewBarDropdownButton } from './ViewBarDropdownButton'; - export type SortDropdownButtonProps = { hotkeyScope: HotkeyScope; isPrimaryButton?: boolean; @@ -91,56 +90,58 @@ export const SortDropdownButton = ({ }; return ( - - } - dropdownComponents={ - - {isSortDirectionMenuUnfolded ? ( - - {SORT_DIRECTIONS.map((sortOrder, index) => ( - { - setSelectedSortDirection(sortOrder); - setIsSortDirectionMenuUnfolded(false); - }} - text={sortOrder === 'asc' ? 'Ascending' : 'Descending'} - /> - ))} - - ) : ( - <> - setIsSortDirectionMenuUnfolded(true)} - > - {selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'} - - + + + } + dropdownComponents={ + <> + {isSortDirectionMenuUnfolded ? ( - {availableSorts.map((availableSort, index) => ( + {SORT_DIRECTIONS.map((sortOrder, index) => ( handleAddSort(availableSort)} - LeftIcon={availableSort.Icon} - text={availableSort.label} + onClick={() => { + setSelectedSortDirection(sortOrder); + setIsSortDirectionMenuUnfolded(false); + }} + text={sortOrder === 'asc' ? 'Ascending' : 'Descending'} /> ))} - - )} - - } - onClose={handleDropdownButtonClose} - > + ) : ( + <> + setIsSortDirectionMenuUnfolded(true)} + > + {selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'} + + + + {availableSorts.map((availableSort, index) => ( + handleAddSort(availableSort)} + LeftIcon={availableSort.Icon} + text={availableSort.label} + /> + ))} + + + )} + + } + onClose={handleDropdownButtonClose} + /> + ); }; diff --git a/front/src/modules/ui/data/view-bar/components/UpdateViewButtonGroup.tsx b/front/src/modules/ui/data/view-bar/components/UpdateViewButtonGroup.tsx index ba7880533..97414a1b7 100644 --- a/front/src/modules/ui/data/view-bar/components/UpdateViewButtonGroup.tsx +++ b/front/src/modules/ui/data/view-bar/components/UpdateViewButtonGroup.tsx @@ -14,7 +14,6 @@ import { viewEditModeState } from '@/ui/data/view-bar/states/viewEditModeState'; import { IconChevronDown, IconPlus } from '@/ui/display/icon'; import { Button } from '@/ui/input/button/components/Button'; import { ButtonGroup } from '@/ui/input/button/components/ButtonGroup'; -import { DropdownMenuContainer } from '@/ui/layout/dropdown/components/DropdownMenuContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; @@ -126,15 +125,13 @@ export const UpdateViewButtonGroup = ({ {isDropdownOpen && ( - - - - - + + + )} ); diff --git a/front/src/modules/ui/data/view-bar/components/ViewBarDropdownButton.tsx b/front/src/modules/ui/data/view-bar/components/ViewBarDropdownButton.tsx deleted file mode 100644 index f764782cf..000000000 --- a/front/src/modules/ui/data/view-bar/components/ViewBarDropdownButton.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Keys } from 'react-hotkeys-hook'; -import { Placement } from '@floating-ui/react'; - -import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; -import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; -import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; - -type ViewBarDropdownButtonProps = { - buttonComponent: JSX.Element | JSX.Element[]; - dropdownComponents: JSX.Element | JSX.Element[]; - dropdownId: string; - hotkey?: { - key: Keys; - scope: string; - }; - dropdownHotkeyScope: HotkeyScope; - dropdownPlacement?: Placement; - onClickOutside?: () => void; - onClose?: () => void; - onOpen?: () => void; -}; - -export const ViewBarDropdownButton = ({ - buttonComponent, - dropdownComponents, - dropdownId, - hotkey, - dropdownHotkeyScope, - dropdownPlacement = 'bottom-end', - onClickOutside, - onClose, - onOpen, -}: ViewBarDropdownButtonProps) => { - return ( - - - - ); -}; diff --git a/front/src/modules/ui/data/view-bar/components/ViewsDropdownButton.tsx b/front/src/modules/ui/data/view-bar/components/ViewsDropdownButton.tsx index 07396bf4b..0cc3d4f04 100644 --- a/front/src/modules/ui/data/view-bar/components/ViewsDropdownButton.tsx +++ b/front/src/modules/ui/data/view-bar/components/ViewsDropdownButton.tsx @@ -24,11 +24,12 @@ import { IconPlus, IconTrash, } from '@/ui/display/icon'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { StyledDropdownButtonContainer } from '@/ui/layout/dropdown/components/StyledDropdownButtonContainer'; -import { StyledDropdownMenu } from '@/ui/layout/dropdown/components/StyledDropdownMenu'; -import { StyledDropdownMenuSeparator } from '@/ui/layout/dropdown/components/StyledDropdownMenuSeparator'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; +import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; @@ -41,8 +42,6 @@ import { ViewsDropdownId } from '../constants/ViewsDropdownId'; import { ViewBarContext } from '../contexts/ViewBarContext'; import { useRemoveView } from '../hooks/useRemoveView'; -import { ViewBarDropdownButton } from './ViewBarDropdownButton'; - const StyledBoldDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)` font-weight: ${({ theme }) => theme.font.weight.regular}; `; @@ -159,56 +158,57 @@ export const ViewsDropdownButton = ({ }; return ( - - - - {currentView?.name || defaultViewName} - - - · {entityCount} - - - } - dropdownComponents={ - - - {views.map((view) => ( + + + + + {currentView?.name || defaultViewName} + + + · {entityCount} + + + } + dropdownComponents={ + <> + + {views.map((view) => ( + ) => + handleEditViewButtonClick(event, view.id), + }, + views.length > 1 + ? { + Icon: IconTrash, + onClick: (event: MouseEvent) => + handleDeleteViewButtonClick(event, view.id), + } + : null, + ].filter(assertNotNull)} + onClick={() => handleViewSelect(view.id)} + LeftIcon={IconList} + text={view.name} + /> + ))} + + + ) => - handleEditViewButtonClick(event, view.id), - }, - views.length > 1 - ? { - Icon: IconTrash, - onClick: (event: MouseEvent) => - handleDeleteViewButtonClick(event, view.id), - } - : null, - ].filter(assertNotNull)} - onClick={() => handleViewSelect(view.id)} - LeftIcon={IconList} - text={view.name} + onClick={handleAddViewButtonClick} + LeftIcon={IconPlus} + text="Add view" /> - ))} - - - - - - - } - /> + + + } + /> + ); }; diff --git a/front/src/modules/ui/input/components/IconPicker.tsx b/front/src/modules/ui/input/components/IconPicker.tsx index 3bb8fcb63..16b5458da 100644 --- a/front/src/modules/ui/input/components/IconPicker.tsx +++ b/front/src/modules/ui/input/components/IconPicker.tsx @@ -2,11 +2,11 @@ import { useMemo, useState } from 'react'; import styled from '@emotion/styled'; import { IconComponent } from '@/ui/display/icon/types/IconComponent'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; 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 { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; @@ -73,7 +73,7 @@ export const IconPicker = ({ return ( - } dropdownComponents={ - + setSearchString(event.target.value)} /> - + {isLoading ? ( @@ -111,7 +111,7 @@ export const IconPicker = ({ )} - + } onClickOutside={onClickOutside} onClose={() => { @@ -119,7 +119,7 @@ export const IconPicker = ({ setSearchString(''); }} onOpen={onOpen} - > + > ); }; diff --git a/front/src/modules/ui/input/components/internal/phone/components/CountryPickerDropdownButton.tsx b/front/src/modules/ui/input/components/internal/phone/components/CountryPickerDropdownButton.tsx index 639cd08d8..6f66a998b 100644 --- a/front/src/modules/ui/input/components/internal/phone/components/CountryPickerDropdownButton.tsx +++ b/front/src/modules/ui/input/components/internal/phone/components/CountryPickerDropdownButton.tsx @@ -8,7 +8,7 @@ import { CountryCallingCode } from 'libphonenumber-js'; import { IconChevronDown } from '@/ui/display/icon'; import { IconWorld } from '@/ui/input/constants/icons'; -import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; @@ -123,7 +123,7 @@ export const CountryPickerDropdownButton = ({ return ( - diff --git a/front/src/modules/ui/input/components/internal/phone/components/CountryPickerDropdownSelect.tsx b/front/src/modules/ui/input/components/internal/phone/components/CountryPickerDropdownSelect.tsx index 9ad78ddc7..4e482252c 100644 --- a/front/src/modules/ui/input/components/internal/phone/components/CountryPickerDropdownSelect.tsx +++ b/front/src/modules/ui/input/components/internal/phone/components/CountryPickerDropdownSelect.tsx @@ -1,10 +1,10 @@ import { useMemo, useState } from 'react'; import styled from '@emotion/styled'; +import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; 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 { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItemSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemSelectAvatar'; @@ -58,13 +58,13 @@ export const CountryPickerDropdownSelect = ({ return ( <> - + setSearchFilter(event.currentTarget.value)} autoFocus /> - + {filteredCountries?.length === 0 ? ( @@ -102,7 +102,7 @@ export const CountryPickerDropdownSelect = ({ )} - + ); diff --git a/front/src/modules/ui/input/relation-picker/components/MultipleEntitySelect.tsx b/front/src/modules/ui/input/relation-picker/components/MultipleEntitySelect.tsx index b7336bbf9..382816501 100644 --- a/front/src/modules/ui/input/relation-picker/components/MultipleEntitySelect.tsx +++ b/front/src/modules/ui/input/relation-picker/components/MultipleEntitySelect.tsx @@ -1,10 +1,10 @@ import { useRef } from 'react'; import debounce from 'lodash.debounce'; +import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; 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 { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItemMultiSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; @@ -72,13 +72,13 @@ export const MultipleEntitySelect = < }); return ( - + - + {entitiesInDropdown?.map((entity) => ( } - + ); }; diff --git a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelect.tsx b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelect.tsx index 12f62e9ac..4eb6be35a 100644 --- a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelect.tsx +++ b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelect.tsx @@ -1,8 +1,8 @@ import { useRef } from 'react'; +import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; 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 { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { isDefined } from '~/utils/isDefined'; @@ -61,7 +61,7 @@ export const SingleEntitySelect = < }); return ( - - + - + ); }; diff --git a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx index dba1832ba..850b3ac0f 100644 --- a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx +++ b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx @@ -4,7 +4,7 @@ import { Key } from 'ts-key-enum'; import { IconPlus } from '@/ui/display/icon'; import { IconComponent } from '@/ui/display/icon/types/IconComponent'; 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 { MenuItemSelect } from '@/ui/navigation/menu-item/components/MenuItemSelect'; import { MenuItemSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemSelectAvatar'; @@ -155,7 +155,7 @@ export const SingleEntitySelectBase = < {showCreateButton && ( <> - + - + {COLUMN_COLOR_OPTIONS.map((colorOption) => ( ))} - + - + {currentMenu === 'actions' && ( )} - + ); }; diff --git a/front/src/modules/ui/layout/board/components/BoardOptionsDropdown.tsx b/front/src/modules/ui/layout/board/components/BoardOptionsDropdown.tsx index 7ebb918c8..cba4bad8c 100644 --- a/front/src/modules/ui/layout/board/components/BoardOptionsDropdown.tsx +++ b/front/src/modules/ui/layout/board/components/BoardOptionsDropdown.tsx @@ -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 ( - } - dropdownComponents={ - - } - dropdownHotkeyScope={customHotkeyScope} - dropdownId={BoardScopeIds.OptionsDropdown} - onClickOutside={resetViewEditMode} - /> + + } + dropdownComponents={ + + } + dropdownHotkeyScope={customHotkeyScope} + onClickOutside={resetViewEditMode} + /> + ); }; diff --git a/front/src/modules/ui/layout/board/components/BoardOptionsDropdownContent.tsx b/front/src/modules/ui/layout/board/components/BoardOptionsDropdownContent.tsx index 0fd2755df..5bf25e65e 100644 --- a/front/src/modules/ui/layout/board/components/BoardOptionsDropdownContent.tsx +++ b/front/src/modules/ui/layout/board/components/BoardOptionsDropdownContent.tsx @@ -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 ( - + <> {!currentMenu && ( <> - - - - + + handleMenuNavigate('fields')} @@ -207,7 +201,7 @@ export const BoardOptionsDropdownContent = ({ Stages - + setCurrentMenu('stage-creation')} @@ -229,7 +223,7 @@ export const BoardOptionsDropdownContent = ({ Fields - + {hasVisibleFields && ( )} - {hasVisibleFields && hasHiddenFields && ( - - )} + {hasVisibleFields && hasHiddenFields && } {hasHiddenFields && ( )} - + ); }; diff --git a/front/src/modules/ui/layout/dropdown/components/Dropdown.tsx b/front/src/modules/ui/layout/dropdown/components/Dropdown.tsx new file mode 100644 index 000000000..46378b850 --- /dev/null +++ b/front/src/modules/ui/layout/dropdown/components/Dropdown.tsx @@ -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(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 ( +
+ {clickableComponent && ( +
+ {clickableComponent} +
+ )} + {hotkey && ( + + )} + {isDropdownOpen && ( + + {dropdownComponents} + + )} + +
+ ); +}; diff --git a/front/src/modules/ui/layout/dropdown/components/DropdownMenu.tsx b/front/src/modules/ui/layout/dropdown/components/DropdownMenu.tsx index 0bc811b0f..6ca095ef4 100644 --- a/front/src/modules/ui/layout/dropdown/components/DropdownMenu.tsx +++ b/front/src/modules/ui/layout/dropdown/components/DropdownMenu.tsx @@ -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(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 ( -
- {clickableComponent && ( -
- {clickableComponent} -
- )} - {hotkey && ( - - )} - {isDropdownOpen && ( -
- {dropdownComponents} -
- )} - -
- ); -}; +export const DropdownMenu = StyledDropdownMenu; diff --git a/front/src/modules/ui/layout/dropdown/components/DropdownMenuContainer.tsx b/front/src/modules/ui/layout/dropdown/components/DropdownMenuContainer.tsx deleted file mode 100644 index afc2a2fe8..000000000 --- a/front/src/modules/ui/layout/dropdown/components/DropdownMenuContainer.tsx +++ /dev/null @@ -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; - -export const DropdownMenuContainer = ({ - anchor = 'right', - children, - onClose, - width, -}: DropdownMenuContainerProps) => { - const dropdownRef = useRef(null); - - useListenClickOutside({ - refs: [dropdownRef], - callback: () => { - onClose?.(); - }, - }); - - return ( - - - {children} - - - ); -}; diff --git a/front/src/modules/ui/layout/dropdown/components/DropdownMenuInput.tsx b/front/src/modules/ui/layout/dropdown/components/DropdownMenuInput.tsx index aa564104d..f024ccb7f 100644 --- a/front/src/modules/ui/layout/dropdown/components/DropdownMenuInput.tsx +++ b/front/src/modules/ui/layout/dropdown/components/DropdownMenuInput.tsx @@ -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 +>(({ autoFocus, defaultValue, placeholder }, ref) => { + return ( + + + + ); +}); diff --git a/front/src/modules/ui/layout/dropdown/components/DropdownMenuInputContainer.tsx b/front/src/modules/ui/layout/dropdown/components/DropdownMenuInputContainer.tsx deleted file mode 100644 index 6bc0c8e27..000000000 --- a/front/src/modules/ui/layout/dropdown/components/DropdownMenuInputContainer.tsx +++ /dev/null @@ -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 }; diff --git a/front/src/modules/ui/layout/dropdown/components/StyledDropdownMenuSeparator.tsx b/front/src/modules/ui/layout/dropdown/components/DropdownMenuSeparator.tsx similarity index 54% rename from front/src/modules/ui/layout/dropdown/components/StyledDropdownMenuSeparator.tsx rename to front/src/modules/ui/layout/dropdown/components/DropdownMenuSeparator.tsx index aee2b5c19..8c8501d45 100644 --- a/front/src/modules/ui/layout/dropdown/components/StyledDropdownMenuSeparator.tsx +++ b/front/src/modules/ui/layout/dropdown/components/DropdownMenuSeparator.tsx @@ -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; diff --git a/front/src/modules/ui/layout/dropdown/components/StyledDropdownMenu.tsx b/front/src/modules/ui/layout/dropdown/components/StyledDropdownMenu.tsx deleted file mode 100644 index d33ea3d99..000000000 --- a/front/src/modules/ui/layout/dropdown/components/StyledDropdownMenu.tsx +++ /dev/null @@ -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'}; -`; diff --git a/front/src/modules/ui/layout/dropdown/components/__stories__/DropdownMenu.stories.tsx b/front/src/modules/ui/layout/dropdown/components/__stories__/DropdownMenu.stories.tsx index 40c67e7ef..50ebea043 100644 --- a/front/src/modules/ui/layout/dropdown/components/__stories__/DropdownMenu.stories.tsx +++ b/front/src/modules/ui/layout/dropdown/components/__stories__/DropdownMenu.stories.tsx @@ -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 = { - title: 'UI/Layout/Dropdown/DropdownMenu', - component: DropdownMenu, +const meta: Meta = { + title: 'UI/Layout/Dropdown/Dropdown', + component: Dropdown, decorators: [ ComponentDecorator, @@ -38,7 +37,7 @@ const meta: Meta = { args: { clickableComponent: