Migrate dropdown to scope map (#3338)

* Migrate dropdown to scope map

* Run lintr

* Move Dropdown Scope internally

* Fix

* Fix lint

---------

Co-authored-by: Thomas Trompette <thomast@twenty.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Thomas Trompette
2024-01-10 15:46:37 +01:00
committed by GitHub
parent fbf7496fab
commit 2713285a0f
47 changed files with 803 additions and 881 deletions

View File

@ -4,7 +4,6 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; 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 { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
type AttachmentDropdownProps = { type AttachmentDropdownProps = {
@ -18,9 +17,9 @@ export const AttachmentDropdown = ({
onDelete, onDelete,
scopeKey, scopeKey,
}: AttachmentDropdownProps) => { }: AttachmentDropdownProps) => {
const dropdownScopeId = `${scopeKey}-settings-field-active-action-dropdown`; const dropdownId = `${scopeKey}-settings-field-active-action-dropdown`;
const { closeDropdown } = useDropdown(dropdownScopeId); const { closeDropdown } = useDropdown(dropdownId);
const handleDownload = () => { const handleDownload = () => {
onDownload(); onDownload();
@ -33,32 +32,31 @@ export const AttachmentDropdown = ({
}; };
return ( return (
<DropdownScope dropdownScopeId={dropdownScopeId}> <Dropdown
<Dropdown dropdownId={dropdownId}
clickableComponent={ clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" /> <LightIconButton Icon={IconDotsVertical} accent="tertiary" />
} }
dropdownComponents={ dropdownComponents={
<DropdownMenu width="160px"> <DropdownMenu width="160px">
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<MenuItem <MenuItem
text="Download" text="Download"
LeftIcon={IconDownload} LeftIcon={IconDownload}
onClick={handleDownload} onClick={handleDownload}
/> />
<MenuItem <MenuItem
text="Delete" text="Delete"
accent="danger" accent="danger"
LeftIcon={IconTrash} LeftIcon={IconTrash}
onClick={handleDelete} onClick={handleDelete}
/> />
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownMenu> </DropdownMenu>
} }
dropdownHotkeyScope={{ dropdownHotkeyScope={{
scope: dropdownScopeId, scope: dropdownId,
}} }}
/> />
</DropdownScope>
); );
}; };

View File

@ -6,6 +6,7 @@ import {
RecordBoardProps, RecordBoardProps,
} from '@/object-record/record-board/components/RecordBoard'; } from '@/object-record/record-board/components/RecordBoard';
import { RecordBoardEffect } from '@/object-record/record-board/components/RecordBoardEffect'; import { RecordBoardEffect } from '@/object-record/record-board/components/RecordBoardEffect';
import { BoardOptionsDropdownId } from '@/object-record/record-board/constants/BoardOptionsDropdownId';
import { RecordBoardOptionsDropdown } from '@/object-record/record-board/options/components/RecordBoardOptionsDropdown'; import { RecordBoardOptionsDropdown } from '@/object-record/record-board/options/components/RecordBoardOptionsDropdown';
import { ViewBar } from '@/views/components/ViewBar'; import { ViewBar } from '@/views/components/ViewBar';
import { useViewFields } from '@/views/hooks/internal/useViewFields'; import { useViewFields } from '@/views/hooks/internal/useViewFields';
@ -43,7 +44,7 @@ export const CompanyBoard = ({
optionsDropdownButton={ optionsDropdownButton={
<RecordBoardOptionsDropdown recordBoardId={recordBoardId} /> <RecordBoardOptionsDropdown recordBoardId={recordBoardId} />
} }
optionsDropdownScopeId={recordBoardId} optionsDropdownScopeId={BoardOptionsDropdownId}
/> />
<HooksCompanyBoardEffect <HooksCompanyBoardEffect

View File

@ -1,6 +1,5 @@
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; 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 { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { ObjectFilterDropdownId } from '../constants/ObjectFilterDropdownId'; import { ObjectFilterDropdownId } from '../constants/ObjectFilterDropdownId';
@ -18,14 +17,13 @@ export const MultipleFiltersDropdownButton = ({
const { resetFilter } = useFilterDropdown(); const { resetFilter } = useFilterDropdown();
return ( return (
<DropdownScope dropdownScopeId={ObjectFilterDropdownId}> <Dropdown
<Dropdown dropdownId={ObjectFilterDropdownId}
onClose={resetFilter} onClose={resetFilter}
clickableComponent={<MultipleFiltersButton />} clickableComponent={<MultipleFiltersButton />}
dropdownComponents={<MultipleFiltersDropdownContent />} dropdownComponents={<MultipleFiltersDropdownContent />}
dropdownHotkeyScope={hotkeyScope} dropdownHotkeyScope={hotkeyScope}
dropdownOffset={{ y: 8 }} dropdownOffset={{ y: 8 }}
/> />
</DropdownScope>
); );
}; };

View File

@ -1,5 +1,6 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { ObjectFilterDropdownId } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
export const MultipleFiltersDropdownFilterOnFilterChangedEffect = ({ export const MultipleFiltersDropdownFilterOnFilterChangedEffect = ({
@ -7,7 +8,7 @@ export const MultipleFiltersDropdownFilterOnFilterChangedEffect = ({
}: { }: {
filterDefinitionUsedInDropdownType: string | undefined; filterDefinitionUsedInDropdownType: string | undefined;
}) => { }) => {
const { setDropdownWidth } = useDropdown(); const { setDropdownWidth } = useDropdown(ObjectFilterDropdownId);
useEffect(() => { useEffect(() => {
switch (filterDefinitionUsedInDropdownType) { switch (filterDefinitionUsedInDropdownType) {

View File

@ -1,5 +1,6 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { ObjectFilterDropdownId } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/components/MultipleEntitySelect'; import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/components/MultipleEntitySelect';
import { SingleEntitySelectMenuItems } from '@/object-record/relation-picker/components/SingleEntitySelectMenuItems'; import { SingleEntitySelectMenuItems } from '@/object-record/relation-picker/components/SingleEntitySelectMenuItems';
@ -21,7 +22,7 @@ export const ObjectFilterDropdownEntitySearchSelect = ({
selectFilter, selectFilter,
} = useFilterDropdown(); } = useFilterDropdown();
const { closeDropdown } = useDropdown(); const { closeDropdown } = useDropdown(ObjectFilterDropdownId);
const [isAllEntitySelected, setIsAllEntitySelected] = useState(false); const [isAllEntitySelected, setIsAllEntitySelected] = useState(false);

View File

@ -5,7 +5,6 @@ import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/
import { IconChevronDown } from '@/ui/display/icon/index'; import { IconChevronDown } from '@/ui/display/icon/index';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton'; import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
@ -42,34 +41,33 @@ export const SingleEntityObjectFilterDropdownButton = ({
const theme = useTheme(); const theme = useTheme();
return ( return (
<DropdownScope dropdownScopeId="single-entity-filter-dropdown"> <Dropdown
<Dropdown dropdownId="single-entity-filter-dropdown"
dropdownHotkeyScope={hotkeyScope} dropdownHotkeyScope={hotkeyScope}
dropdownOffset={{ x: 0, y: -28 }} dropdownOffset={{ x: 0, y: -28 }}
clickableComponent={ clickableComponent={
<StyledHeaderDropdownButton> <StyledHeaderDropdownButton>
{selectedFilter ? ( {selectedFilter ? (
<GenericEntityFilterChip <GenericEntityFilterChip
filter={selectedFilter} filter={selectedFilter}
Icon={ Icon={
selectedFilter.operand === ViewFilterOperand.IsNotNull selectedFilter.operand === ViewFilterOperand.IsNotNull
? availableFilter.SelectAllIcon ? availableFilter.SelectAllIcon
: undefined : undefined
} }
/> />
) : ( ) : (
'Filter' 'Filter'
)} )}
<IconChevronDown size={theme.icon.size.md} /> <IconChevronDown size={theme.icon.size.md} />
</StyledHeaderDropdownButton> </StyledHeaderDropdownButton>
} }
dropdownComponents={ dropdownComponents={
<> <>
<ObjectFilterDropdownRecordSearchInput /> <ObjectFilterDropdownRecordSearchInput />
<ObjectFilterDropdownRecordSelect /> <ObjectFilterDropdownRecordSelect />
</> </>
} }
/> />
</DropdownScope>
); );
}; };

View File

@ -10,7 +10,6 @@ import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenu
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; 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 { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
@ -70,63 +69,60 @@ export const ObjectSortDropdownButton = ({
return ( return (
<ObjectSortDropdownScope sortScopeId={sortDropdownId}> <ObjectSortDropdownScope sortScopeId={sortDropdownId}>
<DropdownScope dropdownScopeId={ObjectSortDropdownId}> <Dropdown
<Dropdown dropdownId={ObjectSortDropdownId}
dropdownHotkeyScope={hotkeyScope} dropdownHotkeyScope={hotkeyScope}
dropdownOffset={{ y: 8 }} dropdownOffset={{ y: 8 }}
clickableComponent={ clickableComponent={
<LightButton <LightButton
title="Sort" title="Sort"
active={isSortSelected} active={isSortSelected}
onClick={handleButtonClick} onClick={handleButtonClick}
/> />
} }
dropdownComponents={ dropdownComponents={
<> <>
{isSortDirectionMenuUnfolded ? ( {isSortDirectionMenuUnfolded ? (
<DropdownMenuItemsContainer>
{SORT_DIRECTIONS.map((sortOrder, index) => (
<MenuItem
key={index}
onClick={() => {
setSelectedSortDirection(sortOrder);
setIsSortDirectionMenuUnfolded(false);
}}
text={sortOrder === 'asc' ? 'Ascending' : 'Descending'}
/>
))}
</DropdownMenuItemsContainer>
) : (
<>
<DropdownMenuHeader
EndIcon={IconChevronDown}
onClick={() => setIsSortDirectionMenuUnfolded(true)}
>
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
</DropdownMenuHeader>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
{SORT_DIRECTIONS.map((sortOrder, index) => ( {[...availableSortDefinitions]
<MenuItem .sort((a, b) => a.label.localeCompare(b.label))
key={index} .map((availableSortDefinition, index) => (
onClick={() => { <MenuItem
setSelectedSortDirection(sortOrder); testId={`select-sort-${index}`}
setIsSortDirectionMenuUnfolded(false); key={index}
}} onClick={() => handleAddSort(availableSortDefinition)}
text={sortOrder === 'asc' ? 'Ascending' : 'Descending'} LeftIcon={getIcon(availableSortDefinition.iconName)}
/> text={availableSortDefinition.label}
))} />
))}
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
) : ( </>
<> )}
<DropdownMenuHeader </>
EndIcon={IconChevronDown} }
onClick={() => setIsSortDirectionMenuUnfolded(true)} onClose={handleDropdownButtonClose}
> />
{selectedSortDirection === 'asc'
? 'Ascending'
: 'Descending'}
</DropdownMenuHeader>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
{[...availableSortDefinitions]
.sort((a, b) => a.label.localeCompare(b.label))
.map((availableSortDefinition, index) => (
<MenuItem
testId={`select-sort-${index}`}
key={index}
onClick={() => handleAddSort(availableSortDefinition)}
LeftIcon={getIcon(availableSortDefinition.iconName)}
text={availableSortDefinition.label}
/>
))}
</DropdownMenuItemsContainer>
</>
)}
</>
}
onClose={handleDropdownButtonClose}
/>
</DropdownScope>
</ObjectSortDropdownScope> </ObjectSortDropdownScope>
); );
}; };

View File

@ -0,0 +1 @@
export const BoardOptionsDropdownId = 'board-options-dropdown-id';

View File

@ -1,8 +1,7 @@
import { BoardOptionsDropdownId } from '@/object-record/record-board/constants/BoardOptionsDropdownId';
import { useViewBar } from '@/views/hooks/useViewBar'; import { useViewBar } from '@/views/hooks/useViewBar';
import { Dropdown } from '../../../../ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '../../../../ui/layout/dropdown/components/Dropdown';
import { DropdownScope } from '../../../../ui/layout/dropdown/scopes/DropdownScope';
import { BoardOptionsDropdownId } from '../../components/constants/BoardOptionsDropdownId';
import { BoardOptionsHotkeyScope } from '../../types/BoardOptionsHotkeyScope'; import { BoardOptionsHotkeyScope } from '../../types/BoardOptionsHotkeyScope';
import { RecordBoardOptionsDropdownButton } from './RecordBoardOptionsDropdownButton'; import { RecordBoardOptionsDropdownButton } from './RecordBoardOptionsDropdownButton';
@ -23,19 +22,18 @@ export const RecordBoardOptionsDropdown = ({
const { setViewEditMode } = useViewBar(); const { setViewEditMode } = useViewBar();
return ( return (
<DropdownScope dropdownScopeId={BoardOptionsDropdownId}> <Dropdown
<Dropdown dropdownId={BoardOptionsDropdownId}
clickableComponent={<RecordBoardOptionsDropdownButton />} clickableComponent={<RecordBoardOptionsDropdownButton />}
dropdownComponents={ dropdownComponents={
<RecordBoardOptionsDropdownContent <RecordBoardOptionsDropdownContent
onStageAdd={onStageAdd} onStageAdd={onStageAdd}
recordBoardId={recordBoardId} recordBoardId={recordBoardId}
/> />
} }
dropdownHotkeyScope={{ scope: BoardOptionsHotkeyScope.Dropdown }} dropdownHotkeyScope={{ scope: BoardOptionsHotkeyScope.Dropdown }}
onClickOutside={() => setViewEditMode('none')} onClickOutside={() => setViewEditMode('none')}
dropdownMenuWidth={170} dropdownMenuWidth={170}
/> />
</DropdownScope>
); );
}; };

View File

@ -1,8 +1,11 @@
import { BoardOptionsDropdownId } from '@/object-record/record-board/constants/BoardOptionsDropdownId';
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton'; import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
export const RecordBoardOptionsDropdownButton = () => { export const RecordBoardOptionsDropdownButton = () => {
const { isDropdownOpen, toggleDropdown } = useDropdown(); const { isDropdownOpen, toggleDropdown } = useDropdown(
BoardOptionsDropdownId,
);
const handleClick = () => { const handleClick = () => {
toggleDropdown(); toggleDropdown();

View File

@ -4,6 +4,7 @@ import { useRecoilState, useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { BoardOptionsDropdownId } from '@/object-record/record-board/constants/BoardOptionsDropdownId';
import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates'; import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates';
import { import {
IconBaselineDensitySmall, IconBaselineDensitySmall,
@ -110,7 +111,7 @@ export const RecordBoardOptionsDropdownContent = ({
recordBoardScopeId: recordBoardId, recordBoardScopeId: recordBoardId,
}); });
const { closeDropdown } = useDropdown(); const { closeDropdown } = useDropdown(BoardOptionsDropdownId);
const handleReorderField: OnDragEndResponder = useCallback( const handleReorderField: OnDragEndResponder = useCallback(
(result) => { (result) => {

View File

@ -23,7 +23,6 @@ import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
import { Card } from '@/ui/layout/card/components/Card'; import { Card } from '@/ui/layout/card/components/Card';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
const StyledAddDropdown = styled(Dropdown)` const StyledAddDropdown = styled(Dropdown)`
@ -129,9 +128,9 @@ export const RecordRelationFieldCardSection = () => {
); );
}, [relationRecords, upsertRecordFromState]); }, [relationRecords, upsertRecordFromState]);
const dropdownScopeId = `record-field-card-relation-picker-${fieldDefinition.label}`; const dropdownId = `record-field-card-relation-picker-${fieldDefinition.label}`;
const { closeDropdown, isDropdownOpen } = useDropdown(dropdownScopeId); const { closeDropdown, isDropdownOpen } = useDropdown(dropdownId);
const { const {
identifiersMapper, identifiersMapper,
@ -196,30 +195,29 @@ export const RecordRelationFieldCardSection = () => {
<Section> <Section>
<StyledHeader isDropdownOpen={isDropdownOpen}> <StyledHeader isDropdownOpen={isDropdownOpen}>
<StyledTitle>{fieldDefinition.label}</StyledTitle> <StyledTitle>{fieldDefinition.label}</StyledTitle>
<DropdownScope dropdownScopeId={dropdownScopeId}> <StyledAddDropdown
<StyledAddDropdown dropdownId={dropdownId}
dropdownPlacement="right-start" dropdownPlacement="right-start"
onClose={handleCloseRelationPickerDropdown} onClose={handleCloseRelationPickerDropdown}
clickableComponent={ clickableComponent={
<LightIconButton <LightIconButton
className="displayOnHover" className="displayOnHover"
Icon={IconPlus} Icon={IconPlus}
accent="tertiary" accent="tertiary"
/> />
} }
dropdownComponents={ dropdownComponents={
<SingleEntitySelectMenuItemsWithSearch <SingleEntitySelectMenuItemsWithSearch
EmptyIcon={IconForbid} EmptyIcon={IconForbid}
entitiesToSelect={entities.entitiesToSelect} entitiesToSelect={entities.entitiesToSelect}
loading={entities.loading} loading={entities.loading}
onEntitySelected={handleRelationPickerEntitySelected} onEntitySelected={handleRelationPickerEntitySelected}
/> />
} }
dropdownHotkeyScope={{ dropdownHotkeyScope={{
scope: dropdownScopeId, scope: dropdownId,
}} }}
/> />
</DropdownScope>
</StyledHeader> </StyledHeader>
{!!relationRecords.length && ( {!!relationRecords.length && (
<Card> <Card>

View File

@ -1,7 +1,6 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { FieldMetadata } from '../../field/types/FieldMetadata'; import { FieldMetadata } from '../../field/types/FieldMetadata';
import { ColumnDefinition } from '../types/ColumnDefinition'; import { ColumnDefinition } from '../types/ColumnDefinition';
@ -28,21 +27,20 @@ export const ColumnHeadWithDropdown = ({
primaryColumnKey, primaryColumnKey,
}: ColumnHeadWithDropdownProps) => { }: ColumnHeadWithDropdownProps) => {
return ( return (
<DropdownScope dropdownScopeId={column.fieldMetadataId + '-header'}> <StyledDropdown
<StyledDropdown dropdownId={column.fieldMetadataId + '-header'}
clickableComponent={<ColumnHead column={column} />} clickableComponent={<ColumnHead column={column} />}
dropdownComponents={ dropdownComponents={
<RecordTableColumnDropdownMenu <RecordTableColumnDropdownMenu
column={column} column={column}
isFirstColumn={isFirstColumn} isFirstColumn={isFirstColumn}
isLastColumn={isLastColumn} isLastColumn={isLastColumn}
primaryColumnKey={primaryColumnKey} primaryColumnKey={primaryColumnKey}
/> />
} }
dropdownOffset={{ x: -1 }} dropdownOffset={{ x: -1 }}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownHotkeyScope={{ scope: column.fieldMetadataId + '-header' }} dropdownHotkeyScope={{ scope: column.fieldMetadataId + '-header' }}
/> />
</DropdownScope>
); );
}; };

View File

@ -6,7 +6,6 @@ import { RecordTableHeaderCell } from '@/object-record/record-table/components/R
import { getRecordTableScopeInjector } from '@/object-record/record-table/utils/getRecordTableScopeInjector'; import { getRecordTableScopeInjector } from '@/object-record/record-table/utils/getRecordTableScopeInjector';
import { IconPlus } from '@/ui/display/icon'; import { IconPlus } from '@/ui/display/icon';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { useScrollWrapperScopedRef } from '@/ui/utilities/scroll/hooks/useScrollWrapperScopedRef'; import { useScrollWrapperScopedRef } from '@/ui/utilities/scroll/hooks/useScrollWrapperScopedRef';
import { useRecordTableScopedStates } from '../hooks/internal/useRecordTableScopedStates'; import { useRecordTableScopedStates } from '../hooks/internal/useRecordTableScopedStates';
@ -48,7 +47,7 @@ const StyledPlusIconContainer = styled.div`
width: 32px; width: 32px;
`; `;
const HIDDEN_TABLE_COLUMN_DROPDOWN_SCOPE_ID = export const HIDDEN_TABLE_COLUMN_DROPDOWN_ID =
'hidden-table-columns-dropdown-scope-id'; 'hidden-table-columns-dropdown-scope-id';
const HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID = const HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID =
@ -106,22 +105,19 @@ export const RecordTableHeader = ({
isTableWiderThanScreen={isTableWiderThanScreen} isTableWiderThanScreen={isTableWiderThanScreen}
> >
{hiddenTableColumns.length > 0 && ( {hiddenTableColumns.length > 0 && (
<DropdownScope <Dropdown
dropdownScopeId={HIDDEN_TABLE_COLUMN_DROPDOWN_SCOPE_ID} dropdownId={HIDDEN_TABLE_COLUMN_DROPDOWN_ID}
> clickableComponent={
<Dropdown <StyledPlusIconContainer>
clickableComponent={ <IconPlus size={theme.icon.size.md} />
<StyledPlusIconContainer> </StyledPlusIconContainer>
<IconPlus size={theme.icon.size.md} /> }
</StyledPlusIconContainer> dropdownComponents={<RecordTableHeaderPlusButtonContent />}
} dropdownPlacement="bottom-start"
dropdownComponents={<RecordTableHeaderPlusButtonContent />} dropdownHotkeyScope={{
dropdownPlacement="bottom-start" scope: HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID,
dropdownHotkeyScope={{ }}
scope: HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID, />
}}
/>
</DropdownScope>
)} )}
</StyledPlusIconHeaderCell> </StyledPlusIconHeaderCell>
</tr> </tr>

View File

@ -1 +1 @@
export const ColumnHeadDropdownId = 'table-head-options'; export const ColumnHeadDropdownId = 'table-head-options-dropdown-id';

View File

@ -1,2 +1,2 @@
// We should either apply the constant all caps case or maybe define a more general enum to store those ids ? // We should either apply the constant all caps case or maybe define a more general enum to store those ids ?
export const BoardOptionsDropdownId = 'board-options'; export const TableHiddenFieldsDropdownId = 'table-hidden-fields-dropdown-id';

View File

@ -1,2 +1,2 @@
// We should either apply the constant all caps case or maybe define a more general enum to store those ids ? // We should either apply the constant all caps case or maybe define a more general enum to store those ids ?
export const TableOptionsDropdownId = 'table-options'; export const TableOptionsDropdownId = 'table-options-dropdown-id';

View File

@ -1,5 +1,4 @@
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { useViewBar } from '@/views/hooks/useViewBar'; import { useViewBar } from '@/views/hooks/useViewBar';
import { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId'; import { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId';
@ -18,19 +17,18 @@ export const TableOptionsDropdown = ({
const { setViewEditMode } = useViewBar(); const { setViewEditMode } = useViewBar();
return ( return (
<DropdownScope dropdownScopeId={TableOptionsDropdownId}> <Dropdown
<Dropdown dropdownId={TableOptionsDropdownId}
clickableComponent={<TableOptionsDropdownButton />} clickableComponent={<TableOptionsDropdownButton />}
dropdownHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }} dropdownHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }}
dropdownOffset={{ y: 8 }} dropdownOffset={{ y: 8 }}
dropdownComponents={ dropdownComponents={
<TableOptionsDropdownContent <TableOptionsDropdownContent
onImport={onImport} onImport={onImport}
recordTableId={recordTableId} recordTableId={recordTableId}
/> />
} }
onClickOutside={() => setViewEditMode('none')} onClickOutside={() => setViewEditMode('none')}
/> />
</DropdownScope>
); );
}; };

View File

@ -3,6 +3,7 @@ import { OnDragEndResponder } from '@hello-pangea/dnd';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { TableOptionsDropdownId } from '@/object-record/record-table/constants/TableOptionsDropdownId';
import { useRecordTableScopedStates } from '@/object-record/record-table/hooks/internal/useRecordTableScopedStates'; import { useRecordTableScopedStates } from '@/object-record/record-table/hooks/internal/useRecordTableScopedStates';
import { getRecordTableScopeInjector } from '@/object-record/record-table/utils/getRecordTableScopeInjector'; import { getRecordTableScopeInjector } from '@/object-record/record-table/utils/getRecordTableScopeInjector';
import { IconChevronLeft, IconFileImport, IconTag } from '@/ui/display/icon'; import { IconChevronLeft, IconFileImport, IconTag } from '@/ui/display/icon';
@ -34,7 +35,7 @@ export const TableOptionsDropdownContent = ({
const viewEditMode = useRecoilValue(viewEditModeState); const viewEditMode = useRecoilValue(viewEditModeState);
const currentView = useRecoilValue(currentViewSelector); const currentView = useRecoilValue(currentViewSelector);
const { closeDropdown } = useDropdown(); const { closeDropdown } = useDropdown(TableOptionsDropdownId);
const [currentMenu, setCurrentMenu] = useState<TableOptionsMenu | undefined>( const [currentMenu, setCurrentMenu] = useState<TableOptionsMenu | undefined>(
undefined, undefined,

View File

@ -8,7 +8,6 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { IconButton } from '@/ui/input/button/components/IconButton'; import { IconButton } from '@/ui/input/button/components/IconButton';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { logError } from '~/utils/logError'; import { logError } from '~/utils/logError';
export const PipelineAddButton = () => { export const PipelineAddButton = () => {
@ -48,33 +47,32 @@ export const PipelineAddButton = () => {
}; };
return ( return (
<DropdownScope dropdownScopeId="add-pipeline-progress"> <Dropdown
<Dropdown dropdownId="add-pipeline-progress"
clickableComponent={ clickableComponent={
<IconButton <IconButton
Icon={IconPlus} Icon={IconPlus}
size="medium" size="medium"
dataTestId="add-company-progress-button" dataTestId="add-company-progress-button"
accent="default" accent="default"
variant="secondary" variant="secondary"
onClick={toggleDropdown} onClick={toggleDropdown}
/> />
} }
dropdownComponents={ dropdownComponents={
<OpportunityPicker <OpportunityPicker
companyId={null} companyId={null}
onSubmit={handleCompanySelected} onSubmit={handleCompanySelected}
onCancel={closeDropdown} onCancel={closeDropdown}
/> />
} }
hotkey={{ hotkey={{
key: 'c', key: 'c',
scope: PageHotkeyScope.OpportunitiesPage, scope: PageHotkeyScope.OpportunitiesPage,
}} }}
dropdownHotkeyScope={{ dropdownHotkeyScope={{
scope: RelationPickerHotkeyScope.RelationPicker, scope: RelationPickerHotkeyScope.RelationPicker,
}} }}
/> />
</DropdownScope>
); );
}; };

View File

@ -7,7 +7,6 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; 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 { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
type SettingsAccountsRowDropdownMenuProps = { type SettingsAccountsRowDropdownMenuProps = {
@ -21,46 +20,45 @@ export const SettingsAccountsRowDropdownMenu = ({
className, className,
onRemove, onRemove,
}: SettingsAccountsRowDropdownMenuProps) => { }: SettingsAccountsRowDropdownMenuProps) => {
const dropdownScopeId = `settings-account-row-${account.id}`; const dropdownId = `settings-account-row-${account.id}`;
const navigate = useNavigate(); const navigate = useNavigate();
const { closeDropdown } = useDropdown(dropdownScopeId); const { closeDropdown } = useDropdown(dropdownId);
return ( return (
<DropdownScope dropdownScopeId={dropdownScopeId}> <Dropdown
<Dropdown dropdownId={dropdownId}
className={className} className={className}
dropdownPlacement="right-start" dropdownPlacement="right-start"
dropdownHotkeyScope={{ scope: dropdownScopeId }} dropdownHotkeyScope={{ scope: dropdownId }}
clickableComponent={ clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" /> <LightIconButton Icon={IconDotsVertical} accent="tertiary" />
} }
dropdownComponents={ dropdownComponents={
<DropdownMenu> <DropdownMenu>
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<MenuItem
LeftIcon={IconMail}
text="Emails settings"
onClick={() => {
navigate(`/settings/accounts/emails/${account.id}`);
closeDropdown();
}}
/>
{!!onRemove && (
<MenuItem <MenuItem
LeftIcon={IconMail} accent="danger"
text="Emails settings" LeftIcon={IconTrash}
text="Remove account"
onClick={() => { onClick={() => {
navigate(`/settings/accounts/emails/${account.id}`); onRemove(account.id);
closeDropdown(); closeDropdown();
}} }}
/> />
{!!onRemove && ( )}
<MenuItem </DropdownMenuItemsContainer>
accent="danger" </DropdownMenu>
LeftIcon={IconTrash} }
text="Remove account" />
onClick={() => {
onRemove(account.id);
closeDropdown();
}}
/>
)}
</DropdownMenuItemsContainer>
</DropdownMenu>
}
/>
</DropdownScope>
); );
}; };

View File

@ -105,7 +105,7 @@ export const SettingsObjectFieldRelationForm = ({
<StyledInputsContainer> <StyledInputsContainer>
<IconPicker <IconPicker
disabled={disableFieldEdition} disabled={disableFieldEdition}
dropdownScopeId="field-destination-icon-picker" dropdownId="field-destination-icon-picker"
selectedIconKey={values.field.icon || undefined} selectedIconKey={values.field.icon || undefined}
onChange={(value) => onChange={(value) =>
onChange({ onChange({

View File

@ -17,7 +17,6 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; 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 { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MenuItemSelectColor } from '@/ui/navigation/menu-item/components/MenuItemSelectColor'; import { MenuItemSelectColor } from '@/ui/navigation/menu-item/components/MenuItemSelectColor';
import { mainColorNames } from '@/ui/theme/constants/colors'; import { mainColorNames } from '@/ui/theme/constants/colors';
@ -63,16 +62,14 @@ export const SettingsObjectFieldSelectFormOptionRow = ({
}: SettingsObjectFieldSelectFormOptionRowProps) => { }: SettingsObjectFieldSelectFormOptionRowProps) => {
const theme = useTheme(); const theme = useTheme();
const dropdownScopeIds = useMemo(() => { const dropdownIds = useMemo(() => {
const baseScopeId = `select-field-option-row-${v4()}`; const baseScopeId = `select-field-option-row-${v4()}`;
return { color: `${baseScopeId}-color`, actions: `${baseScopeId}-actions` }; return { color: `${baseScopeId}-color`, actions: `${baseScopeId}-actions` };
}, []); }, []);
const { closeDropdown: closeColorDropdown } = useDropdown( const { closeDropdown: closeColorDropdown } = useDropdown(dropdownIds.color);
dropdownScopeIds.color,
);
const { closeDropdown: closeActionsDropdown } = useDropdown( const { closeDropdown: closeActionsDropdown } = useDropdown(
dropdownScopeIds.actions, dropdownIds.actions,
); );
return ( return (
@ -82,82 +79,80 @@ export const SettingsObjectFieldSelectFormOptionRow = ({
stroke={theme.icon.stroke.sm} stroke={theme.icon.stroke.sm}
color={theme.font.color.extraLight} color={theme.font.color.extraLight}
/> />
<DropdownScope dropdownScopeId={dropdownScopeIds.color}> <Dropdown
<Dropdown dropdownId={dropdownIds.color}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownHotkeyScope={{ dropdownHotkeyScope={{
scope: dropdownScopeIds.color, scope: dropdownIds.color,
}} }}
clickableComponent={<StyledColorSample colorName={option.color} />} clickableComponent={<StyledColorSample colorName={option.color} />}
dropdownComponents={ dropdownComponents={
<DropdownMenu> <DropdownMenu>
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
{mainColorNames.map((colorName) => ( {mainColorNames.map((colorName) => (
<MenuItemSelectColor <MenuItemSelectColor
key={colorName} key={colorName}
onClick={() => { onClick={() => {
onChange({ ...option, color: colorName }); onChange({ ...option, color: colorName });
closeColorDropdown(); closeColorDropdown();
}} }}
color={colorName} color={colorName}
selected={colorName === option.color} selected={colorName === option.color}
/> />
))} ))}
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownMenu> </DropdownMenu>
} }
/> />
</DropdownScope>
<StyledOptionInput <StyledOptionInput
value={option.label} value={option.label}
onChange={(label) => onChange({ ...option, label })} onChange={(label) => onChange({ ...option, label })}
RightIcon={isDefault ? IconCheck : undefined} RightIcon={isDefault ? IconCheck : undefined}
/> />
<DropdownScope dropdownScopeId={dropdownScopeIds.actions}> <Dropdown
<Dropdown dropdownId={dropdownIds.actions}
dropdownPlacement="right-start" dropdownPlacement="right-start"
dropdownHotkeyScope={{ dropdownHotkeyScope={{
scope: dropdownScopeIds.actions, scope: dropdownIds.actions,
}} }}
clickableComponent={<LightIconButton Icon={IconDotsVertical} />} clickableComponent={<LightIconButton Icon={IconDotsVertical} />}
dropdownComponents={ dropdownComponents={
<DropdownMenu> <DropdownMenu>
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
{isDefault ? ( {isDefault ? (
<MenuItem <MenuItem
LeftIcon={IconX} LeftIcon={IconX}
text="Remove as default" text="Remove as default"
onClick={() => { onClick={() => {
onChange({ ...option, isDefault: false }); onChange({ ...option, isDefault: false });
closeActionsDropdown(); closeActionsDropdown();
}} }}
/> />
) : ( ) : (
<MenuItem <MenuItem
LeftIcon={IconCheck} LeftIcon={IconCheck}
text="Set as default" text="Set as default"
onClick={() => { onClick={() => {
onChange({ ...option, isDefault: true }); onChange({ ...option, isDefault: true });
closeActionsDropdown(); closeActionsDropdown();
}} }}
/> />
)} )}
{!!onRemove && ( {!!onRemove && (
<MenuItem <MenuItem
accent="danger" accent="danger"
LeftIcon={IconTrash} LeftIcon={IconTrash}
text="Remove option" text="Remove option"
onClick={() => { onClick={() => {
onRemove(); onRemove();
closeActionsDropdown(); closeActionsDropdown();
}} }}
/> />
)} )}
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownMenu> </DropdownMenu>
} }
/> />
</DropdownScope>
</StyledRow> </StyledRow>
); );
}; };

View File

@ -12,7 +12,6 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
@ -44,7 +43,7 @@ const StyledTag = styled(Tag)`
height: ${({ theme }) => theme.spacing(6)}; height: ${({ theme }) => theme.spacing(6)};
`; `;
const dropdownScopeId = 'settings-object-edit-about-menu-dropdown'; const dropdownId = 'settings-object-edit-about-menu-dropdown';
export const SettingsAboutSection = ({ export const SettingsAboutSection = ({
iconKey = '', iconKey = '',
@ -57,7 +56,7 @@ export const SettingsAboutSection = ({
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const Icon = getIcon(iconKey); const Icon = getIcon(iconKey);
const { closeDropdown } = useDropdown(dropdownScopeId); const { closeDropdown } = useDropdown(dropdownId);
const handleEdit = () => { const handleEdit = () => {
onEdit(); onEdit();
@ -83,32 +82,31 @@ export const SettingsAboutSection = ({
) : ( ) : (
<StyledTag color="blue" text="Standard" weight="medium" /> <StyledTag color="blue" text="Standard" weight="medium" />
)} )}
<DropdownScope dropdownScopeId={dropdownScopeId}> <Dropdown
<Dropdown dropdownId={dropdownId}
clickableComponent={ clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" /> <LightIconButton Icon={IconDotsVertical} accent="tertiary" />
} }
dropdownComponents={ dropdownComponents={
<DropdownMenu width="160px"> <DropdownMenu width="160px">
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<MenuItem <MenuItem
text="Edit" text="Edit"
LeftIcon={IconPencil} LeftIcon={IconPencil}
onClick={handleEdit} onClick={handleEdit}
/> />
<MenuItem <MenuItem
text="Disable" text="Disable"
LeftIcon={IconArchive} LeftIcon={IconArchive}
onClick={handleDisable} onClick={handleDisable}
/> />
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownMenu> </DropdownMenu>
} }
dropdownHotkeyScope={{ dropdownHotkeyScope={{
scope: dropdownScopeId, scope: dropdownId,
}} }}
/> />
</DropdownScope>
</StyledCardContent> </StyledCardContent>
</Card> </Card>
</Section> </Section>

View File

@ -9,7 +9,6 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; 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 { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
type SettingsObjectFieldActiveActionDropdownProps = { type SettingsObjectFieldActiveActionDropdownProps = {
@ -25,9 +24,9 @@ export const SettingsObjectFieldActiveActionDropdown = ({
onEdit, onEdit,
scopeKey, scopeKey,
}: SettingsObjectFieldActiveActionDropdownProps) => { }: SettingsObjectFieldActiveActionDropdownProps) => {
const dropdownScopeId = `${scopeKey}-settings-field-active-action-dropdown`; const dropdownId = `${scopeKey}-settings-field-active-action-dropdown`;
const { closeDropdown } = useDropdown(dropdownScopeId); const { closeDropdown } = useDropdown(dropdownId);
const handleEdit = () => { const handleEdit = () => {
onEdit(); onEdit();
@ -40,33 +39,32 @@ export const SettingsObjectFieldActiveActionDropdown = ({
}; };
return ( return (
<DropdownScope dropdownScopeId={dropdownScopeId}> <Dropdown
<Dropdown dropdownId={dropdownId}
clickableComponent={ clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" /> <LightIconButton Icon={IconDotsVertical} accent="tertiary" />
} }
dropdownComponents={ dropdownComponents={
<DropdownMenu width="160px"> <DropdownMenu width="160px">
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<MenuItem
text={isCustomField ? 'Edit' : 'View'}
LeftIcon={isCustomField ? IconPencil : IconEye}
onClick={handleEdit}
/>
{!!onDisable && (
<MenuItem <MenuItem
text={isCustomField ? 'Edit' : 'View'} text="Disable"
LeftIcon={isCustomField ? IconPencil : IconEye} LeftIcon={IconArchive}
onClick={handleEdit} onClick={handleDisable}
/> />
{!!onDisable && ( )}
<MenuItem </DropdownMenuItemsContainer>
text="Disable" </DropdownMenu>
LeftIcon={IconArchive} }
onClick={handleDisable} dropdownHotkeyScope={{
/> scope: dropdownId,
)} }}
</DropdownMenuItemsContainer> />
</DropdownMenu>
}
dropdownHotkeyScope={{
scope: dropdownScopeId,
}}
/>
</DropdownScope>
); );
}; };

View File

@ -4,7 +4,6 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; 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 { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
type SettingsObjectFieldDisabledActionDropdownProps = { type SettingsObjectFieldDisabledActionDropdownProps = {
@ -18,9 +17,9 @@ export const SettingsObjectFieldDisabledActionDropdown = ({
onActivate, onActivate,
scopeKey, scopeKey,
}: SettingsObjectFieldDisabledActionDropdownProps) => { }: SettingsObjectFieldDisabledActionDropdownProps) => {
const dropdownScopeId = `${scopeKey}-settings-field-disabled-action-dropdown`; const dropdownId = `${scopeKey}-settings-field-disabled-action-dropdown`;
const { closeDropdown } = useDropdown(dropdownScopeId); const { closeDropdown } = useDropdown(dropdownId);
const handleActivate = () => { const handleActivate = () => {
onActivate(); onActivate();
@ -33,20 +32,20 @@ export const SettingsObjectFieldDisabledActionDropdown = ({
// }; // };
return ( return (
<DropdownScope dropdownScopeId={dropdownScopeId}> <Dropdown
<Dropdown dropdownId={dropdownId}
clickableComponent={ clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" /> <LightIconButton Icon={IconDotsVertical} accent="tertiary" />
} }
dropdownComponents={ dropdownComponents={
<DropdownMenu width="160px"> <DropdownMenu width="160px">
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<MenuItem <MenuItem
text="Activate" text="Activate"
LeftIcon={IconArchiveOff} LeftIcon={IconArchiveOff}
onClick={handleActivate} onClick={handleActivate}
/> />
{/* {isCustomField && ( {/* {isCustomField && (
<MenuItem <MenuItem
text="Erase" text="Erase"
accent="danger" accent="danger"
@ -54,13 +53,12 @@ export const SettingsObjectFieldDisabledActionDropdown = ({
onClick={handleErase} onClick={handleErase}
/> />
)} */} )} */}
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownMenu> </DropdownMenu>
} }
dropdownHotkeyScope={{ dropdownHotkeyScope={{
scope: dropdownScopeId, scope: dropdownId,
}} }}
/> />
</DropdownScope>
); );
}; };

View File

@ -4,7 +4,6 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; 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 { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
type SettingsObjectDisabledMenuDropDownProps = { type SettingsObjectDisabledMenuDropDownProps = {
@ -20,9 +19,9 @@ export const SettingsObjectDisabledMenuDropDown = ({
onErase, onErase,
isCustomObject, isCustomObject,
}: SettingsObjectDisabledMenuDropDownProps) => { }: SettingsObjectDisabledMenuDropDownProps) => {
const dropdownScopeId = `${scopeKey}-settings-object-disabled-menu-dropdown`; const dropdownId = `${scopeKey}-settings-object-disabled-menu-dropdown`;
const { closeDropdown } = useDropdown(dropdownScopeId); const { closeDropdown } = useDropdown(dropdownId);
const handleActivate = () => { const handleActivate = () => {
onActivate(); onActivate();
@ -35,34 +34,33 @@ export const SettingsObjectDisabledMenuDropDown = ({
}; };
return ( return (
<DropdownScope dropdownScopeId={dropdownScopeId}> <Dropdown
<Dropdown dropdownId={dropdownId}
clickableComponent={ clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" /> <LightIconButton Icon={IconDotsVertical} accent="tertiary" />
} }
dropdownComponents={ dropdownComponents={
<DropdownMenu width="160px"> <DropdownMenu width="160px">
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<MenuItem
text="Activate"
LeftIcon={IconArchiveOff}
onClick={handleActivate}
/>
{isCustomObject && (
<MenuItem <MenuItem
text="Activate" text="Erase"
LeftIcon={IconArchiveOff} LeftIcon={IconTrash}
onClick={handleActivate} accent="danger"
onClick={handleErase}
/> />
{isCustomObject && ( )}
<MenuItem </DropdownMenuItemsContainer>
text="Erase" </DropdownMenu>
LeftIcon={IconTrash} }
accent="danger" dropdownHotkeyScope={{
onClick={handleErase} scope: dropdownId,
/> }}
)} />
</DropdownMenuItemsContainer>
</DropdownMenu>
}
dropdownHotkeyScope={{
scope: dropdownScopeId,
}}
/>
</DropdownScope>
); );
}; };

View File

@ -11,7 +11,6 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
@ -23,7 +22,7 @@ import { IconPickerHotkeyScope } from '../types/IconPickerHotkeyScope';
type IconPickerProps = { type IconPickerProps = {
disabled?: boolean; disabled?: boolean;
dropdownScopeId?: string; dropdownId?: string;
onChange: (params: { iconKey: string; Icon: IconComponent }) => void; onChange: (params: { iconKey: string; Icon: IconComponent }) => void;
selectedIconKey?: string; selectedIconKey?: string;
onClickOutside?: () => void; onClickOutside?: () => void;
@ -80,7 +79,7 @@ const IconPickerIcon = ({
export const IconPicker = ({ export const IconPicker = ({
disabled, disabled,
dropdownScopeId = 'icon-picker', dropdownId = 'icon-picker',
onChange, onChange,
selectedIconKey, selectedIconKey,
onClickOutside, onClickOutside,
@ -95,7 +94,7 @@ export const IconPicker = ({
setHotkeyScopeAndMemorizePreviousScope, setHotkeyScopeAndMemorizePreviousScope,
} = usePreviousHotkeyScope(); } = usePreviousHotkeyScope();
const { closeDropdown } = useDropdown(dropdownScopeId); const { closeDropdown } = useDropdown(dropdownId);
const { getIcons, getIcon } = useIcons(); const { getIcons, getIcon } = useIcons();
const icons = getIcons(); const icons = getIcons();
@ -126,71 +125,70 @@ export const IconPicker = ({
); );
return ( return (
<DropdownScope dropdownScopeId={dropdownScopeId}> <div className={className}>
<div className={className}> <Dropdown
<Dropdown dropdownId={dropdownId}
dropdownHotkeyScope={{ scope: IconPickerHotkeyScope.IconPicker }} dropdownHotkeyScope={{ scope: IconPickerHotkeyScope.IconPicker }}
clickableComponent={ clickableComponent={
<IconButton <IconButton
disabled={disabled} disabled={disabled}
Icon={selectedIconKey ? getIcon(selectedIconKey) : IconApps} Icon={selectedIconKey ? getIcon(selectedIconKey) : IconApps}
variant={variant} variant={variant}
/> />
} }
dropdownMenuWidth={176} dropdownMenuWidth={176}
dropdownComponents={ dropdownComponents={
<SelectableList <SelectableList
selectableListId="icon-list" selectableListId="icon-list"
selectableItemIdMatrix={iconKeys2d} selectableItemIdMatrix={iconKeys2d}
hotkeyScope={IconPickerHotkeyScope.IconPicker} hotkeyScope={IconPickerHotkeyScope.IconPicker}
onEnter={(iconKey) => { onEnter={(iconKey) => {
onChange({ iconKey, Icon: getIcon(iconKey) }); onChange({ iconKey, Icon: getIcon(iconKey) });
closeDropdown(); closeDropdown();
}} }}
> >
<DropdownMenu width={176}> <DropdownMenu width={176}>
<DropdownMenuSearchInput <DropdownMenuSearchInput
placeholder="Search icon" placeholder="Search icon"
autoFocus autoFocus
onChange={(event) => setSearchString(event.target.value)} onChange={(event) => setSearchString(event.target.value)}
/> />
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<div <div
onMouseEnter={() => { onMouseEnter={() => {
setHotkeyScopeAndMemorizePreviousScope( setHotkeyScopeAndMemorizePreviousScope(
IconPickerHotkeyScope.IconPicker, IconPickerHotkeyScope.IconPicker,
); );
}} }}
onMouseLeave={goBackToPreviousHotkeyScope} onMouseLeave={goBackToPreviousHotkeyScope}
> >
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<StyledMenuIconItemsContainer> <StyledMenuIconItemsContainer>
{iconKeys.map((iconKey) => ( {iconKeys.map((iconKey) => (
<IconPickerIcon <IconPickerIcon
key={iconKey} key={iconKey}
iconKey={iconKey} iconKey={iconKey}
onClick={() => { onClick={() => {
onChange({ iconKey, Icon: getIcon(iconKey) }); onChange({ iconKey, Icon: getIcon(iconKey) });
closeDropdown(); closeDropdown();
}} }}
selectedIconKey={selectedIconKey} selectedIconKey={selectedIconKey}
Icon={getIcon(iconKey)} Icon={getIcon(iconKey)}
/> />
))} ))}
</StyledMenuIconItemsContainer> </StyledMenuIconItemsContainer>
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</div> </div>
</DropdownMenu> </DropdownMenu>
</SelectableList> </SelectableList>
} }
onClickOutside={onClickOutside} onClickOutside={onClickOutside}
onClose={() => { onClose={() => {
onClose?.(); onClose?.();
setSearchString(''); setSearchString('');
}} }}
onOpen={onOpen} onOpen={onOpen}
/> />
</div> </div>
</DropdownScope>
); );
}; };

View File

@ -6,7 +6,6 @@ import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; 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 { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { SelectHotkeyScope } from '../types/SelectHotkeyScope'; import { SelectHotkeyScope } from '../types/SelectHotkeyScope';
@ -98,31 +97,30 @@ export const Select = <Value extends string | number | null>({
{selectControl} {selectControl}
</div> </div>
) : ( ) : (
<DropdownScope dropdownScopeId={dropdownScopeId}> <div className={className}>
<div className={className}> {!!label && <StyledLabel>{label}</StyledLabel>}
{!!label && <StyledLabel>{label}</StyledLabel>} <Dropdown
<Dropdown dropdownId="select"
dropdownMenuWidth={176} dropdownMenuWidth={176}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
clickableComponent={selectControl} clickableComponent={selectControl}
dropdownComponents={ dropdownComponents={
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
{options.map((option) => ( {options.map((option) => (
<MenuItem <MenuItem
key={option.value} key={option.value}
LeftIcon={option.Icon} LeftIcon={option.Icon}
text={option.label} text={option.label}
onClick={() => { onClick={() => {
onChange?.(option.value); onChange?.(option.value);
closeDropdown(); closeDropdown();
}} }}
/> />
))} ))}
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
} }
dropdownHotkeyScope={{ scope: SelectHotkeyScope.Select }} dropdownHotkeyScope={{ scope: SelectHotkeyScope.Select }}
/> />
</div> </div>
</DropdownScope>
); );
}; };

View File

@ -9,7 +9,6 @@ import { CountryCallingCode } from 'libphonenumber-js';
import { IconChevronDown, IconWorld } from '@/ui/display/icon'; import { IconChevronDown, IconWorld } from '@/ui/display/icon';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { CountryPickerHotkeyScope } from '../types/CountryPickerHotkeyScope'; import { CountryPickerHotkeyScope } from '../types/CountryPickerHotkeyScope';
@ -119,27 +118,26 @@ export const CountryPickerDropdownButton = ({
}, [countries, value]); }, [countries, value]);
return ( return (
<DropdownScope dropdownScopeId="country-picker"> <Dropdown
<Dropdown dropdownId="country-picker-dropdown-id"
dropdownHotkeyScope={{ scope: CountryPickerHotkeyScope.CountryPicker }} dropdownHotkeyScope={{ scope: CountryPickerHotkeyScope.CountryPicker }}
clickableComponent={ clickableComponent={
<StyledDropdownButtonContainer isUnfolded={isDropdownOpen}> <StyledDropdownButtonContainer isUnfolded={isDropdownOpen}>
<StyledIconContainer> <StyledIconContainer>
{selectedCountry ? <selectedCountry.Flag /> : <IconWorld />} {selectedCountry ? <selectedCountry.Flag /> : <IconWorld />}
<IconChevronDown size={theme.icon.size.sm} /> <IconChevronDown size={theme.icon.size.sm} />
</StyledIconContainer> </StyledIconContainer>
</StyledDropdownButtonContainer> </StyledDropdownButtonContainer>
} }
dropdownComponents={ dropdownComponents={
<CountryPickerDropdownSelect <CountryPickerDropdownSelect
countries={countries} countries={countries}
selectedCountry={selectedCountry} selectedCountry={selectedCountry}
onChange={handleChange} onChange={handleChange}
/> />
} }
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownOffset={{ x: 0, y: 4 }} dropdownOffset={{ x: 0, y: 4 }}
/> />
</DropdownScope>
); );
}; };

View File

@ -9,6 +9,7 @@ import {
} from '@floating-ui/react'; } from '@floating-ui/react';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { HotkeyEffect } from '@/ui/utilities/hotkey/components/HotkeyEffect'; import { HotkeyEffect } from '@/ui/utilities/hotkey/components/HotkeyEffect';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
@ -29,6 +30,7 @@ type DropdownProps = {
scope: string; scope: string;
}; };
dropdownHotkeyScope: HotkeyScope; dropdownHotkeyScope: HotkeyScope;
dropdownId: string;
dropdownPlacement?: Placement; dropdownPlacement?: Placement;
dropdownMenuWidth?: number; dropdownMenuWidth?: number;
dropdownOffset?: { x?: number; y?: number }; dropdownOffset?: { x?: number; y?: number };
@ -43,6 +45,7 @@ export const Dropdown = ({
dropdownComponents, dropdownComponents,
dropdownMenuWidth, dropdownMenuWidth,
hotkey, hotkey,
dropdownId,
dropdownHotkeyScope, dropdownHotkeyScope,
dropdownPlacement = 'bottom-end', dropdownPlacement = 'bottom-end',
dropdownOffset = { x: 0, y: 0 }, dropdownOffset = { x: 0, y: 0 },
@ -53,8 +56,7 @@ export const Dropdown = ({
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const { isDropdownOpen, toggleDropdown, closeDropdown, dropdownWidth } = const { isDropdownOpen, toggleDropdown, closeDropdown, dropdownWidth } =
useDropdown(); useDropdown(dropdownId);
const offsetMiddlewares = []; const offsetMiddlewares = [];
if (dropdownOffset.x) { if (dropdownOffset.x) {
@ -87,6 +89,7 @@ export const Dropdown = ({
}); });
useInternalHotkeyScopeManagement({ useInternalHotkeyScopeManagement({
dropdownScopeId: `${dropdownId}-scope`,
dropdownHotkeyScopeFromParent: dropdownHotkeyScope, dropdownHotkeyScopeFromParent: dropdownHotkeyScope,
}); });
@ -100,36 +103,38 @@ export const Dropdown = ({
); );
return ( return (
<div ref={containerRef} className={className}> <DropdownScope dropdownScopeId={`${dropdownId}-scope`}>
{clickableComponent && ( <div ref={containerRef} className={className}>
<div {clickableComponent && (
ref={refs.setReference} <div
onClick={toggleDropdown} ref={refs.setReference}
className={className} onClick={toggleDropdown}
> className={className}
{clickableComponent} >
</div> {clickableComponent}
)} </div>
{hotkey && ( )}
<HotkeyEffect {hotkey && (
hotkey={hotkey} <HotkeyEffect
onHotkeyTriggered={handleHotkeyTriggered} hotkey={hotkey}
onHotkeyTriggered={handleHotkeyTriggered}
/>
)}
{isDropdownOpen && (
<DropdownMenu
width={dropdownMenuWidth ?? dropdownWidth}
data-select-disable
ref={refs.setFloating}
style={floatingStyles}
>
{dropdownComponents}
</DropdownMenu>
)}
<DropdownOnToggleEffect
onDropdownClose={onClose}
onDropdownOpen={onOpen}
/> />
)} </div>
{isDropdownOpen && ( </DropdownScope>
<DropdownMenu
width={dropdownMenuWidth ?? dropdownWidth}
data-select-disable
ref={refs.setFloating}
style={floatingStyles}
>
{dropdownComponents}
</DropdownMenu>
)}
<DropdownOnToggleEffect
onDropdownClose={onClose}
onDropdownOpen={onOpen}
/>
</div>
); );
}; };

View File

@ -12,7 +12,6 @@ import { MenuItemSelectAvatar } from '@/ui/navigation/menu-item/components/MenuI
import { Avatar } from '@/users/components/Avatar'; import { Avatar } from '@/users/components/Avatar';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { DropdownScope } from '../../scopes/DropdownScope';
import { Dropdown } from '../Dropdown'; import { Dropdown } from '../Dropdown';
import { DropdownMenuHeader } from '../DropdownMenuHeader'; import { DropdownMenuHeader } from '../DropdownMenuHeader';
import { DropdownMenuInput } from '../DropdownMenuInput'; import { DropdownMenuInput } from '../DropdownMenuInput';
@ -25,18 +24,12 @@ const meta: Meta<typeof Dropdown> = {
title: 'UI/Layout/Dropdown/Dropdown', title: 'UI/Layout/Dropdown/Dropdown',
component: Dropdown, component: Dropdown,
decorators: [ decorators: [ComponentDecorator, (Story) => <Story />],
ComponentDecorator,
(Story) => (
<DropdownScope dropdownScopeId="testDropdownMenu">
<Story />
</DropdownScope>
),
],
args: { args: {
clickableComponent: <Button title="Open Dropdown" />, clickableComponent: <Button title="Open Dropdown" />,
dropdownHotkeyScope: { scope: 'testDropdownMenu' }, dropdownHotkeyScope: { scope: 'testDropdownMenu' },
dropdownOffset: { x: 0, y: 8 }, dropdownOffset: { x: 0, y: 8 },
dropdownId: 'test-dropdown-id',
}, },
argTypes: { argTypes: {
clickableComponent: { control: false }, clickableComponent: { control: false },

View File

@ -1,31 +0,0 @@
import { DropdownScopeInternalContext } from '@/ui/layout/dropdown/scopes/scope-internal-context/DropdownScopeInternalContext';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { useScopedState } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useScopedState';
type UseDropdownScopedStatesProps = {
dropdownScopeId?: string;
};
export const useDropdownScopedStates = ({
dropdownScopeId,
}: UseDropdownScopedStatesProps) => {
const scopeId = useAvailableScopeIdOrThrow(
DropdownScopeInternalContext,
dropdownScopeId,
);
const {
getScopedState,
getScopedFamilyState,
getScopedSnapshotValue,
getScopedFamilySnapshotValue,
} = useScopedState(scopeId);
return {
scopeId,
injectStateWithDropdownScopeId: getScopedState,
injectFamilyStateWithDropdownScopeId: getScopedFamilyState,
injectSnapshotValueWithDropdownScopeId: getScopedSnapshotValue,
injectFamilySnapshotValueWithDropdownScopeId: getScopedFamilySnapshotValue,
};
};

View File

@ -0,0 +1,26 @@
import { DropdownScopeInternalContext } from '@/ui/layout/dropdown/scopes/scope-internal-context/DropdownScopeInternalContext';
import { dropdownHotkeyStateScopeMap } from '@/ui/layout/dropdown/states/dropdownHotkeyStateScopeMap';
import { dropdownWidthStateScopeMap } from '@/ui/layout/dropdown/states/dropdownWidthStateScopeMap';
import { isDropdownOpenStateScopeMap } from '@/ui/layout/dropdown/states/isDropdownOpenStateScopeMap';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { getState } from '@/ui/utilities/recoil-scope/utils/getState';
type UseDropdownStatesProps = {
dropdownScopeId?: string;
};
export const useDropdownStates = ({
dropdownScopeId,
}: UseDropdownStatesProps) => {
const scopeId = useAvailableScopeIdOrThrow(
DropdownScopeInternalContext,
dropdownScopeId,
);
return {
scopeId,
dropdownHotkeyScopeState: getState(dropdownHotkeyStateScopeMap, scopeId),
dropdownWidthState: getState(dropdownWidthStateScopeMap, scopeId),
isDropdownOpenState: getState(isDropdownOpenStateScopeMap, scopeId),
};
};

View File

@ -1,36 +1,29 @@
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { useDropdownScopedStates } from '@/ui/layout/dropdown/hooks/internal/useDropdownScopedStates'; import { useDropdownStates } from '@/ui/layout/dropdown/hooks/internal/useDropdownStates';
import { getDropdownScopeInjectors } from '@/ui/layout/dropdown/utils/internal/getDropdownScopeInjectors';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
export const useDropdown = (dropdownId?: string) => { export const useDropdown = (dropdownId?: string) => {
const { injectStateWithDropdownScopeId, scopeId } = useDropdownScopedStates({
dropdownScopeId: dropdownId,
});
const { const {
dropdownHotkeyScopeScopeInjector, scopeId,
dropdownWidthScopeInjector, dropdownHotkeyScopeState,
isDropdownOpenScopeInjector, dropdownWidthState,
} = getDropdownScopeInjectors(); isDropdownOpenState,
} = useDropdownStates({
dropdownScopeId: `${dropdownId}-scope`,
});
const { const {
setHotkeyScopeAndMemorizePreviousScope, setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope, goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope(); } = usePreviousHotkeyScope();
const [dropdownHotkeyScope, setDropdownHotkeyScope] = useRecoilState( const [dropdownHotkeyScope] = useRecoilState(dropdownHotkeyScopeState);
injectStateWithDropdownScopeId(dropdownHotkeyScopeScopeInjector),
);
const [dropdownWidth, setDropdownWidth] = useRecoilState( const [dropdownWidth, setDropdownWidth] = useRecoilState(dropdownWidthState);
injectStateWithDropdownScopeId(dropdownWidthScopeInjector),
);
const [isDropdownOpen, setIsDropdownOpen] = useRecoilState( const [isDropdownOpen, setIsDropdownOpen] =
injectStateWithDropdownScopeId(isDropdownOpenScopeInjector), useRecoilState(isDropdownOpenState);
);
const closeDropdown = () => { const closeDropdown = () => {
goBackToPreviousHotkeyScope(); goBackToPreviousHotkeyScope();
@ -61,8 +54,6 @@ export const useDropdown = (dropdownId?: string) => {
closeDropdown, closeDropdown,
toggleDropdown, toggleDropdown,
openDropdown, openDropdown,
dropdownHotkeyScope,
setDropdownHotkeyScope,
dropdownWidth, dropdownWidth,
setDropdownWidth, setDropdownWidth,
}; };

View File

@ -1,16 +1,22 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useRecoilState } from 'recoil';
import { useDropdownStates } from '@/ui/layout/dropdown/hooks/internal/useDropdownStates';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { useDropdown } from './useDropdown';
export const useInternalHotkeyScopeManagement = ({ export const useInternalHotkeyScopeManagement = ({
dropdownScopeId,
dropdownHotkeyScopeFromParent, dropdownHotkeyScopeFromParent,
}: { }: {
dropdownScopeId: string;
dropdownHotkeyScopeFromParent?: HotkeyScope; dropdownHotkeyScopeFromParent?: HotkeyScope;
}) => { }) => {
const { dropdownHotkeyScope, setDropdownHotkeyScope } = useDropdown(); const { dropdownHotkeyScopeState } = useDropdownStates({ dropdownScopeId });
const [dropdownHotkeyScope, setDropdownHotkeyScope] = useRecoilState(
dropdownHotkeyScopeState,
);
useEffect(() => { useEffect(() => {
if (!isDeeplyEqual(dropdownHotkeyScopeFromParent, dropdownHotkeyScope)) { if (!isDeeplyEqual(dropdownHotkeyScopeFromParent, dropdownHotkeyScope)) {

View File

@ -1,9 +1,9 @@
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap'; import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
export const dropdownHotkeyScopeScopedState = createStateScopeMap< export const dropdownHotkeyStateScopeMap = createStateScopeMap<
HotkeyScope | null | undefined HotkeyScope | null | undefined
>({ >({
key: 'dropdownHotkeyScopeScopedState', key: 'dropdownHotkeyStateScopeMap',
defaultValue: null, defaultValue: null,
}); });

View File

@ -1,8 +0,0 @@
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
export const dropdownWidthScopedState = createStateScopeMap<number | undefined>(
{
key: 'dropdownWidthScopedState',
defaultValue: 160,
},
);

View File

@ -0,0 +1,8 @@
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
export const dropdownWidthStateScopeMap = createStateScopeMap<
number | undefined
>({
key: 'dropdownWidthStateScopeMap',
defaultValue: 160,
});

View File

@ -1,6 +1,6 @@
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap'; import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
export const isDropdownOpenScopedState = createStateScopeMap<boolean>({ export const isDropdownOpenStateScopeMap = createStateScopeMap<boolean>({
key: 'isDropdownOpenScopedState', key: 'isDropdownOpenStateScopeMap',
defaultValue: false, defaultValue: false,
}); });

View File

@ -1,22 +0,0 @@
import { dropdownHotkeyScopeScopedState } from '@/ui/layout/dropdown/states/dropdownHotkeyScopeScopedState';
import { dropdownWidthScopedState } from '@/ui/layout/dropdown/states/dropdownWidthScopedState';
import { isDropdownOpenScopedState } from '@/ui/layout/dropdown/states/isDropdownOpenScopedState';
import { getScopeInjector } from '@/ui/utilities/recoil-scope/utils/getScopeInjector';
export const getDropdownScopeInjectors = () => {
const dropdownHotkeyScopeScopeInjector = getScopeInjector(
dropdownHotkeyScopeScopedState,
);
const dropdownWidthScopeInjector = getScopeInjector(dropdownWidthScopedState);
const isDropdownOpenScopeInjector = getScopeInjector(
isDropdownOpenScopedState,
);
return {
dropdownHotkeyScopeScopeInjector,
dropdownWidthScopeInjector,
isDropdownOpenScopeInjector,
};
};

View File

@ -12,7 +12,6 @@ import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { Dropdown } from '../../dropdown/components/Dropdown'; import { Dropdown } from '../../dropdown/components/Dropdown';
import { DropdownMenu } from '../../dropdown/components/DropdownMenu'; import { DropdownMenu } from '../../dropdown/components/DropdownMenu';
import { DropdownScope } from '../../dropdown/scopes/DropdownScope';
const StyledContainer = styled.div` const StyledContainer = styled.div`
z-index: 1; z-index: 1;
@ -33,41 +32,40 @@ export const ShowPageAddButton = ({
return ( return (
<StyledContainer> <StyledContainer>
<DropdownScope dropdownScopeId="add-show-page"> <Dropdown
<Dropdown dropdownId="show-page-add-button-dropdown-id"
clickableComponent={ clickableComponent={
<IconButton <IconButton
Icon={IconPlus} Icon={IconPlus}
size="medium" size="medium"
dataTestId="add-showpage-button" dataTestId="add-showpage-button"
accent="default" accent="default"
variant="secondary" variant="secondary"
onClick={toggleDropdown} onClick={toggleDropdown}
/> />
} }
dropdownComponents={ dropdownComponents={
<DropdownMenu> <DropdownMenu>
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<MenuItem <MenuItem
onClick={() => handleSelect('Note')} onClick={() => handleSelect('Note')}
accent="default" accent="default"
LeftIcon={IconNotes} LeftIcon={IconNotes}
text="Note" text="Note"
/> />
<MenuItem <MenuItem
onClick={() => handleSelect('Task')} onClick={() => handleSelect('Task')}
accent="default" accent="default"
LeftIcon={IconCheckbox} LeftIcon={IconCheckbox}
text="Task" text="Task"
/> />
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownMenu> </DropdownMenu>
} }
dropdownHotkeyScope={{ dropdownHotkeyScope={{
scope: PageHotkeyScope.ShowPage, scope: PageHotkeyScope.ShowPage,
}} }}
/> />
</DropdownScope>
</StyledContainer> </StyledContainer>
); );
}; };

View File

@ -13,7 +13,6 @@ import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMe
import { Dropdown } from '../../dropdown/components/Dropdown'; import { Dropdown } from '../../dropdown/components/Dropdown';
import { DropdownMenu } from '../../dropdown/components/DropdownMenu'; import { DropdownMenu } from '../../dropdown/components/DropdownMenu';
import { DropdownScope } from '../../dropdown/scopes/DropdownScope';
const StyledContainer = styled.div` const StyledContainer = styled.div`
z-index: 1; z-index: 1;
@ -42,35 +41,34 @@ export const ShowPageMoreButton = ({
return ( return (
<StyledContainer> <StyledContainer>
<DropdownScope dropdownScopeId="more-show-page"> <Dropdown
<Dropdown dropdownId="more-show-page"
clickableComponent={ clickableComponent={
<IconButton <IconButton
Icon={IconDotsVertical} Icon={IconDotsVertical}
size="medium" size="medium"
dataTestId="more-showpage-button" dataTestId="more-showpage-button"
accent="default" accent="default"
variant="secondary" variant="secondary"
onClick={toggleDropdown} onClick={toggleDropdown}
/> />
} }
dropdownComponents={ dropdownComponents={
<DropdownMenu> <DropdownMenu>
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<MenuItem <MenuItem
onClick={handleDelete} onClick={handleDelete}
accent="danger" accent="danger"
LeftIcon={IconTrash} LeftIcon={IconTrash}
text="Delete" text="Delete"
/> />
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownMenu> </DropdownMenu>
} }
dropdownHotkeyScope={{ dropdownHotkeyScope={{
scope: PageHotkeyScope.ShowPage, scope: PageHotkeyScope.ShowPage,
}} }}
/> />
</DropdownScope>
</StyledContainer> </StyledContainer>
); );
}; };

View File

@ -68,6 +68,7 @@ export const EditableFilterDropdownButton = ({
return ( return (
<Dropdown <Dropdown
dropdownId={viewFilter.fieldMetadataId}
clickableComponent={ clickableComponent={
<EditableFilterChip viewFilter={viewFilter} onRemove={handleRemove} /> <EditableFilterChip viewFilter={viewFilter} onRemove={handleRemove} />
} }

View File

@ -7,7 +7,6 @@ import { Button } from '@/ui/input/button/components/Button';
import { ButtonGroup } from '@/ui/input/button/components/ButtonGroup'; import { ButtonGroup } from '@/ui/input/button/components/ButtonGroup';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { UpdateViewDropdownId } from '@/views/constants/UpdateViewDropdownId'; import { UpdateViewDropdownId } from '@/views/constants/UpdateViewDropdownId';
@ -58,25 +57,24 @@ export const UpdateViewButtonGroup = ({
<StyledContainer> <StyledContainer>
<ButtonGroup size="small" accent="blue"> <ButtonGroup size="small" accent="blue">
<Button title="Update view" onClick={handleViewSubmit} /> <Button title="Update view" onClick={handleViewSubmit} />
<DropdownScope dropdownScopeId={UpdateViewDropdownId}> <Dropdown
<Dropdown dropdownId={UpdateViewDropdownId}
dropdownHotkeyScope={hotkeyScope} dropdownHotkeyScope={hotkeyScope}
clickableComponent={ clickableComponent={
<Button size="small" accent="blue" Icon={IconChevronDown} /> <Button size="small" accent="blue" Icon={IconChevronDown} />
} }
dropdownComponents={ dropdownComponents={
<> <>
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<MenuItem <MenuItem
onClick={handleCreateViewButtonClick} onClick={handleCreateViewButtonClick}
LeftIcon={IconPlus} LeftIcon={IconPlus}
text="Create view" text="Create view"
/> />
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</> </>
} }
/> />
</DropdownScope>
</ButtonGroup> </ButtonGroup>
</StyledContainer> </StyledContainer>
); );

View File

@ -5,7 +5,6 @@ import { useRecoilValue } from 'recoil';
import { AddObjectFilterFromDetailsButton } from '@/object-record/object-filter-dropdown/components/AddObjectFilterFromDetailsButton'; import { AddObjectFilterFromDetailsButton } from '@/object-record/object-filter-dropdown/components/AddObjectFilterFromDetailsButton';
import { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope'; import { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope';
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope'; import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { EditableFilterDropdownButton } from '@/views/components/EditableFilterDropdownButton'; import { EditableFilterDropdownButton } from '@/views/components/EditableFilterDropdownButton';
import { EditableSortChip } from '@/views/components/EditableSortChip'; import { EditableSortChip } from '@/views/components/EditableSortChip';
import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect'; import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect';
@ -146,19 +145,17 @@ export const ViewBarDetails = ({
<ObjectFilterDropdownScope <ObjectFilterDropdownScope
filterScopeId={viewFilter.fieldMetadataId} filterScopeId={viewFilter.fieldMetadataId}
> >
<DropdownScope dropdownScopeId={viewFilter.fieldMetadataId}> <ViewBarFilterEffect
<ViewBarFilterEffect filterDropdownId={viewFilter.fieldMetadataId}
filterDropdownId={viewFilter.fieldMetadataId} onFilterSelect={upsertViewFilter}
onFilterSelect={upsertViewFilter} />
/> <EditableFilterDropdownButton
<EditableFilterDropdownButton viewFilter={viewFilter}
viewFilter={viewFilter} hotkeyScope={{
hotkeyScope={{ scope: FiltersHotkeyScope.ObjectFilterDropdownButton,
scope: FiltersHotkeyScope.ObjectFilterDropdownButton, }}
}} viewFilterDropdownId={viewFilter.fieldMetadataId}
viewFilterDropdownId={viewFilter.fieldMetadataId} />
/>
</DropdownScope>
</ObjectFilterDropdownScope> </ObjectFilterDropdownScope>
); );
})} })}

View File

@ -15,7 +15,6 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { StyledDropdownButtonContainer } from '@/ui/layout/dropdown/components/StyledDropdownButtonContainer'; import { StyledDropdownButtonContainer } from '@/ui/layout/dropdown/components/StyledDropdownButtonContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; 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 { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme'; import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
@ -130,56 +129,55 @@ export const ViewsDropdownButton = ({
}; };
return ( return (
<DropdownScope dropdownScopeId={ViewsDropdownId}> <Dropdown
<Dropdown dropdownId={ViewsDropdownId}
dropdownHotkeyScope={hotkeyScope} dropdownHotkeyScope={hotkeyScope}
clickableComponent={ clickableComponent={
<StyledDropdownButtonContainer isUnfolded={isViewsDropdownOpen}> <StyledDropdownButtonContainer isUnfolded={isViewsDropdownOpen}>
<StyledViewIcon size={theme.icon.size.md} /> <StyledViewIcon size={theme.icon.size.md} />
<StyledViewName>{currentView?.name ?? 'All'}</StyledViewName> <StyledViewName>{currentView?.name ?? 'All'}</StyledViewName>
<StyledDropdownLabelAdornments> <StyledDropdownLabelAdornments>
· {entityCountInCurrentView}{' '} · {entityCountInCurrentView}{' '}
<IconChevronDown size={theme.icon.size.sm} /> <IconChevronDown size={theme.icon.size.sm} />
</StyledDropdownLabelAdornments> </StyledDropdownLabelAdornments>
</StyledDropdownButtonContainer> </StyledDropdownButtonContainer>
} }
dropdownComponents={ dropdownComponents={
<> <>
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
{views.map((view) => ( {views.map((view) => (
<MenuItem
key={view.id}
iconButtons={[
{
Icon: IconPencil,
onClick: (event: MouseEvent<HTMLButtonElement>) =>
handleEditViewButtonClick(event, view.id),
},
views.length > 1
? {
Icon: IconTrash,
onClick: (event: MouseEvent<HTMLButtonElement>) =>
handleDeleteViewButtonClick(event, view.id),
}
: null,
].filter(assertNotNull)}
onClick={() => handleViewSelect(view.id)}
LeftIcon={IconList}
text={view.name}
/>
))}
</DropdownMenuItemsContainer>
<DropdownMenuSeparator />
<StyledBoldDropdownMenuItemsContainer>
<MenuItem <MenuItem
onClick={handleAddViewButtonClick} key={view.id}
LeftIcon={IconPlus} iconButtons={[
text="Add view" {
Icon: IconPencil,
onClick: (event: MouseEvent<HTMLButtonElement>) =>
handleEditViewButtonClick(event, view.id),
},
views.length > 1
? {
Icon: IconTrash,
onClick: (event: MouseEvent<HTMLButtonElement>) =>
handleDeleteViewButtonClick(event, view.id),
}
: null,
].filter(assertNotNull)}
onClick={() => handleViewSelect(view.id)}
LeftIcon={IconList}
text={view.name}
/> />
</StyledBoldDropdownMenuItemsContainer> ))}
</> </DropdownMenuItemsContainer>
} <DropdownMenuSeparator />
/> <StyledBoldDropdownMenuItemsContainer>
</DropdownScope> <MenuItem
onClick={handleAddViewButtonClick}
LeftIcon={IconPlus}
text="Add view"
/>
</StyledBoldDropdownMenuItemsContainer>
</>
}
/>
); );
}; };