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

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

View File

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

View File

@ -1,5 +1,6 @@
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 { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/components/MultipleEntitySelect';
import { SingleEntitySelectMenuItems } from '@/object-record/relation-picker/components/SingleEntitySelectMenuItems';
@ -21,7 +22,7 @@ export const ObjectFilterDropdownEntitySearchSelect = ({
selectFilter,
} = useFilterDropdown();
const { closeDropdown } = useDropdown();
const { closeDropdown } = useDropdown(ObjectFilterDropdownId);
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 { 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';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
@ -42,34 +41,33 @@ export const SingleEntityObjectFilterDropdownButton = ({
const theme = useTheme();
return (
<DropdownScope dropdownScopeId="single-entity-filter-dropdown">
<Dropdown
dropdownHotkeyScope={hotkeyScope}
dropdownOffset={{ x: 0, y: -28 }}
clickableComponent={
<StyledHeaderDropdownButton>
{selectedFilter ? (
<GenericEntityFilterChip
filter={selectedFilter}
Icon={
selectedFilter.operand === ViewFilterOperand.IsNotNull
? availableFilter.SelectAllIcon
: undefined
}
/>
) : (
'Filter'
)}
<IconChevronDown size={theme.icon.size.md} />
</StyledHeaderDropdownButton>
}
dropdownComponents={
<>
<ObjectFilterDropdownRecordSearchInput />
<ObjectFilterDropdownRecordSelect />
</>
}
/>
</DropdownScope>
<Dropdown
dropdownId="single-entity-filter-dropdown"
dropdownHotkeyScope={hotkeyScope}
dropdownOffset={{ x: 0, y: -28 }}
clickableComponent={
<StyledHeaderDropdownButton>
{selectedFilter ? (
<GenericEntityFilterChip
filter={selectedFilter}
Icon={
selectedFilter.operand === ViewFilterOperand.IsNotNull
? availableFilter.SelectAllIcon
: undefined
}
/>
) : (
'Filter'
)}
<IconChevronDown size={theme.icon.size.md} />
</StyledHeaderDropdownButton>
}
dropdownComponents={
<>
<ObjectFilterDropdownRecordSearchInput />
<ObjectFilterDropdownRecordSelect />
</>
}
/>
);
};

View File

@ -10,7 +10,6 @@ import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenu
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
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';
@ -70,63 +69,60 @@ export const ObjectSortDropdownButton = ({
return (
<ObjectSortDropdownScope sortScopeId={sortDropdownId}>
<DropdownScope dropdownScopeId={ObjectSortDropdownId}>
<Dropdown
dropdownHotkeyScope={hotkeyScope}
dropdownOffset={{ y: 8 }}
clickableComponent={
<LightButton
title="Sort"
active={isSortSelected}
onClick={handleButtonClick}
/>
}
dropdownComponents={
<>
{isSortDirectionMenuUnfolded ? (
<Dropdown
dropdownId={ObjectSortDropdownId}
dropdownHotkeyScope={hotkeyScope}
dropdownOffset={{ y: 8 }}
clickableComponent={
<LightButton
title="Sort"
active={isSortSelected}
onClick={handleButtonClick}
/>
}
dropdownComponents={
<>
{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>
{SORT_DIRECTIONS.map((sortOrder, index) => (
<MenuItem
key={index}
onClick={() => {
setSelectedSortDirection(sortOrder);
setIsSortDirectionMenuUnfolded(false);
}}
text={sortOrder === 'asc' ? 'Ascending' : 'Descending'}
/>
))}
{[...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>
) : (
<>
<DropdownMenuHeader
EndIcon={IconChevronDown}
onClick={() => setIsSortDirectionMenuUnfolded(true)}
>
{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>
</>
)}
</>
}
onClose={handleDropdownButtonClose}
/>
</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 { 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 { RecordBoardOptionsDropdownButton } from './RecordBoardOptionsDropdownButton';
@ -23,19 +22,18 @@ export const RecordBoardOptionsDropdown = ({
const { setViewEditMode } = useViewBar();
return (
<DropdownScope dropdownScopeId={BoardOptionsDropdownId}>
<Dropdown
clickableComponent={<RecordBoardOptionsDropdownButton />}
dropdownComponents={
<RecordBoardOptionsDropdownContent
onStageAdd={onStageAdd}
recordBoardId={recordBoardId}
/>
}
dropdownHotkeyScope={{ scope: BoardOptionsHotkeyScope.Dropdown }}
onClickOutside={() => setViewEditMode('none')}
dropdownMenuWidth={170}
/>
</DropdownScope>
<Dropdown
dropdownId={BoardOptionsDropdownId}
clickableComponent={<RecordBoardOptionsDropdownButton />}
dropdownComponents={
<RecordBoardOptionsDropdownContent
onStageAdd={onStageAdd}
recordBoardId={recordBoardId}
/>
}
dropdownHotkeyScope={{ scope: BoardOptionsHotkeyScope.Dropdown }}
onClickOutside={() => setViewEditMode('none')}
dropdownMenuWidth={170}
/>
);
};

View File

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

View File

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

View File

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

View File

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

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 { IconPlus } from '@/ui/display/icon';
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 { useRecordTableScopedStates } from '../hooks/internal/useRecordTableScopedStates';
@ -48,7 +47,7 @@ const StyledPlusIconContainer = styled.div`
width: 32px;
`;
const HIDDEN_TABLE_COLUMN_DROPDOWN_SCOPE_ID =
export const HIDDEN_TABLE_COLUMN_DROPDOWN_ID =
'hidden-table-columns-dropdown-scope-id';
const HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID =
@ -106,22 +105,19 @@ export const RecordTableHeader = ({
isTableWiderThanScreen={isTableWiderThanScreen}
>
{hiddenTableColumns.length > 0 && (
<DropdownScope
dropdownScopeId={HIDDEN_TABLE_COLUMN_DROPDOWN_SCOPE_ID}
>
<Dropdown
clickableComponent={
<StyledPlusIconContainer>
<IconPlus size={theme.icon.size.md} />
</StyledPlusIconContainer>
}
dropdownComponents={<RecordTableHeaderPlusButtonContent />}
dropdownPlacement="bottom-start"
dropdownHotkeyScope={{
scope: HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID,
}}
/>
</DropdownScope>
<Dropdown
dropdownId={HIDDEN_TABLE_COLUMN_DROPDOWN_ID}
clickableComponent={
<StyledPlusIconContainer>
<IconPlus size={theme.icon.size.md} />
</StyledPlusIconContainer>
}
dropdownComponents={<RecordTableHeaderPlusButtonContent />}
dropdownPlacement="bottom-start"
dropdownHotkeyScope={{
scope: HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID,
}}
/>
)}
</StyledPlusIconHeaderCell>
</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 ?
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 ?
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 { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { useViewBar } from '@/views/hooks/useViewBar';
import { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId';
@ -18,19 +17,18 @@ export const TableOptionsDropdown = ({
const { setViewEditMode } = useViewBar();
return (
<DropdownScope dropdownScopeId={TableOptionsDropdownId}>
<Dropdown
clickableComponent={<TableOptionsDropdownButton />}
dropdownHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }}
dropdownOffset={{ y: 8 }}
dropdownComponents={
<TableOptionsDropdownContent
onImport={onImport}
recordTableId={recordTableId}
/>
}
onClickOutside={() => setViewEditMode('none')}
/>
</DropdownScope>
<Dropdown
dropdownId={TableOptionsDropdownId}
clickableComponent={<TableOptionsDropdownButton />}
dropdownHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }}
dropdownOffset={{ y: 8 }}
dropdownComponents={
<TableOptionsDropdownContent
onImport={onImport}
recordTableId={recordTableId}
/>
}
onClickOutside={() => setViewEditMode('none')}
/>
);
};

View File

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