512 Ability to navigate dropdown menus with keyboard (#11735)
# Ability to navigate dropdown menus with keyboard The aim of this PR is to improve accessibility by allowing the user to navigate inside the dropdown menus with the keyboard. This PR refactors the `SelectableList` and `SelectableListItem` components to move the Enter event handling responsibility from `SelectableList` to the individual `SelectableListItem` components. Closes [512](https://github.com/twentyhq/core-team-issues/issues/512) ## Key Changes: - All dropdowns are now navigable with arrow keys ## Technical Implementation: - Each `SelectableListItem` now has direct access to its own `Enter` key handler, improving component encapsulation - Removed the central `Enter` key handler logic from `SelectableList` - Added `SelectableList` and `SelectableListItem` to all `Dropdown` components inside the app - Updated all component implementations to adapt to the new pattern: - Action menu components (`ActionDropdownItem`, `ActionListItem`) - Command menu components - Object filter, sort and options dropdowns - Record picker components - Select components --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -1,17 +1,14 @@
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
|
||||
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
||||
|
||||
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
|
||||
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
@ -22,7 +19,6 @@ import { ObjectFilterDropdownFilterSelectMenuItemV2 } from '@/object-record/obje
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
|
||||
import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState';
|
||||
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
|
||||
import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
|
||||
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
@ -37,8 +33,10 @@ export const AdvancedFilterFieldSelectMenu = ({
|
||||
}: AdvancedFilterFieldSelectMenuProps) => {
|
||||
const { recordIndexId } = useRecordIndexContextOrThrow();
|
||||
|
||||
const { closeAdvancedFilterFieldSelectDropdown } =
|
||||
useAdvancedFilterFieldSelectDropdown(recordFilterId);
|
||||
const {
|
||||
closeAdvancedFilterFieldSelectDropdown,
|
||||
advancedFilterFieldSelectDropdownId,
|
||||
} = useAdvancedFilterFieldSelectDropdown(recordFilterId);
|
||||
|
||||
const [objectFilterDropdownSearchInput] = useRecoilComponentStateV2(
|
||||
objectFilterDropdownSearchInputComponentState,
|
||||
@ -76,12 +74,10 @@ export const AdvancedFilterFieldSelectMenu = ({
|
||||
(fieldMetadataItem) => !visibleColumnsIds.includes(fieldMetadataItem.id),
|
||||
);
|
||||
|
||||
const selectableFieldMetadataItemIds = filterableFieldMetadataItems.map(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id,
|
||||
const { resetSelectedItem } = useSelectableList(
|
||||
advancedFilterFieldSelectDropdownId,
|
||||
);
|
||||
|
||||
const { resetSelectedItem } = useSelectableList(OBJECT_FILTER_DROPDOWN_ID);
|
||||
|
||||
const { selectFieldUsedInAdvancedFilterDropdown } =
|
||||
useSelectFieldUsedInAdvancedFilterDropdown();
|
||||
|
||||
@ -98,18 +94,6 @@ export const AdvancedFilterFieldSelectMenu = ({
|
||||
fieldMetadataItemIdUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const handleEnter = (fieldMetadataItemId: string) => {
|
||||
const selectedFieldMetadataItem = filterableFieldMetadataItems.find(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id === fieldMetadataItemId,
|
||||
);
|
||||
|
||||
if (!isDefined(selectedFieldMetadataItem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleFieldMetadataItemSelect(selectedFieldMetadataItem);
|
||||
};
|
||||
|
||||
const handleFieldMetadataItemSelect = (
|
||||
selectedFieldMetadataItem: FieldMetadataItem,
|
||||
) => {
|
||||
@ -138,41 +122,55 @@ export const AdvancedFilterFieldSelectMenu = ({
|
||||
visibleColumnsFieldMetadataItems.length > 0 &&
|
||||
hiddenColumnsFieldMetadataItems.length > 0;
|
||||
|
||||
const selectableItemIdArray = [
|
||||
...visibleColumnsFieldMetadataItems.map(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id,
|
||||
),
|
||||
...hiddenColumnsFieldMetadataItems.map(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id,
|
||||
),
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<AdvancedFilterFieldSelectSearchInput />
|
||||
<SelectableList
|
||||
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
||||
selectableItemIdArray={selectableFieldMetadataItemIds}
|
||||
selectableListInstanceId={OBJECT_FILTER_DROPDOWN_ID}
|
||||
onEnter={handleEnter}
|
||||
hotkeyScope={advancedFilterFieldSelectDropdownId}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
selectableListInstanceId={advancedFilterFieldSelectDropdownId}
|
||||
>
|
||||
<DropdownMenuItemsContainer>
|
||||
{visibleColumnsFieldMetadataItems.map(
|
||||
(visibleFieldMetadataItem, index) => (
|
||||
<SelectableItem
|
||||
<SelectableListItem
|
||||
itemId={visibleFieldMetadataItem.id}
|
||||
key={`visible-select-filter-${index}`}
|
||||
onEnter={() => {
|
||||
handleFieldMetadataItemSelect(visibleFieldMetadataItem);
|
||||
}}
|
||||
>
|
||||
<ObjectFilterDropdownFilterSelectMenuItemV2
|
||||
fieldMetadataItemToSelect={visibleFieldMetadataItem}
|
||||
onClick={handleFieldMetadataItemSelect}
|
||||
/>
|
||||
</SelectableItem>
|
||||
</SelectableListItem>
|
||||
),
|
||||
)}
|
||||
{shouldShowSeparator && <DropdownMenuSeparator />}
|
||||
{hiddenColumnsFieldMetadataItems.map(
|
||||
(hiddenFieldMetadataItem, index) => (
|
||||
<SelectableItem
|
||||
<SelectableListItem
|
||||
itemId={hiddenFieldMetadataItem.id}
|
||||
key={`hidden-select-filter-${index}`}
|
||||
onEnter={() => {
|
||||
handleFieldMetadataItemSelect(hiddenFieldMetadataItem);
|
||||
}}
|
||||
>
|
||||
<ObjectFilterDropdownFilterSelectMenuItemV2
|
||||
fieldMetadataItemToSelect={hiddenFieldMetadataItem}
|
||||
onClick={handleFieldMetadataItemSelect}
|
||||
/>
|
||||
</SelectableItem>
|
||||
</SelectableListItem>
|
||||
),
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
|
||||
@ -11,6 +11,9 @@ import { SelectControl } from '@/ui/input/components/SelectControl';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import styled from '@emotion/styled';
|
||||
@ -85,6 +88,11 @@ export const AdvancedFilterRecordFilterOperandSelect = ({
|
||||
})
|
||||
: [];
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
if (isDisabled === true) {
|
||||
return (
|
||||
<SelectControl
|
||||
@ -115,15 +123,31 @@ export const AdvancedFilterRecordFilterOperandSelect = ({
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
{operandsForFilterType.map((filterOperand, index) => (
|
||||
<MenuItem
|
||||
key={`select-filter-operand-${index}`}
|
||||
onClick={() => {
|
||||
handleOperandChange(filterOperand);
|
||||
}}
|
||||
text={getOperandLabel(filterOperand)}
|
||||
/>
|
||||
))}
|
||||
<SelectableList
|
||||
hotkeyScope={dropdownId}
|
||||
selectableItemIdArray={operandsForFilterType.map(
|
||||
(operand) => operand,
|
||||
)}
|
||||
selectableListInstanceId={dropdownId}
|
||||
>
|
||||
{operandsForFilterType.map((filterOperand, index) => (
|
||||
<SelectableListItem
|
||||
itemId={filterOperand}
|
||||
key={`select-filter-operand-${index}`}
|
||||
onEnter={() => {
|
||||
handleOperandChange(filterOperand);
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === filterOperand}
|
||||
onClick={() => {
|
||||
handleOperandChange(filterOperand);
|
||||
}}
|
||||
text={getOperandLabel(filterOperand)}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
|
||||
@ -15,6 +15,9 @@ import { isCompositeFieldTypeSubFieldsFilterable } from '@/object-record/record-
|
||||
import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { IconApps, IconChevronLeft, useIcons } from 'twenty-ui/display';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
@ -72,6 +75,14 @@ export const AdvancedFilterSubFieldSelectMenu = ({
|
||||
setObjectFilterDropdownIsSelectingCompositeField(false);
|
||||
};
|
||||
|
||||
const { advancedFilterFieldSelectDropdownId } =
|
||||
useAdvancedFilterFieldSelectDropdown(recordFilterId);
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
advancedFilterFieldSelectDropdownId,
|
||||
);
|
||||
|
||||
if (!isDefined(objectFilterDropdownSubMenuFieldType)) {
|
||||
return null;
|
||||
}
|
||||
@ -86,6 +97,11 @@ export const AdvancedFilterSubFieldSelectMenu = ({
|
||||
fieldMetadataItemUsedInDropdown.type,
|
||||
);
|
||||
|
||||
const selectableItemIdArray = [
|
||||
'-1',
|
||||
...options.map((subFieldName) => subFieldName),
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader
|
||||
@ -99,35 +115,58 @@ export const AdvancedFilterSubFieldSelectMenu = ({
|
||||
{getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)}
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
key={`select-filter-${-1}`}
|
||||
testId={`select-filter-${-1}`}
|
||||
onClick={() => {
|
||||
handleSelectFilter(fieldMetadataItemUsedInDropdown);
|
||||
}}
|
||||
LeftIcon={IconApps}
|
||||
text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`}
|
||||
/>
|
||||
{subFieldsAreFilterable &&
|
||||
options.map((subFieldName, index) => (
|
||||
<SelectableList
|
||||
hotkeyScope={advancedFilterFieldSelectDropdownId}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
selectableListInstanceId={advancedFilterFieldSelectDropdownId}
|
||||
>
|
||||
<SelectableListItem
|
||||
itemId={'-1'}
|
||||
key={`select-filter-${-1}`}
|
||||
onEnter={() => {
|
||||
handleSelectFilter(fieldMetadataItemUsedInDropdown);
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
key={`select-filter-${index}`}
|
||||
testId={`select-filter-${index}`}
|
||||
focused={selectedItemId === '-1'}
|
||||
onClick={() => {
|
||||
if (isDefined(fieldMetadataItemUsedInDropdown)) {
|
||||
handleSelectFilter(fieldMetadataItemUsedInDropdown);
|
||||
}}
|
||||
LeftIcon={IconApps}
|
||||
text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
{subFieldsAreFilterable &&
|
||||
options.map((subFieldName, index) => (
|
||||
<SelectableListItem
|
||||
itemId={subFieldName}
|
||||
key={`select-filter-${index}`}
|
||||
onEnter={() => {
|
||||
handleSelectFilter(
|
||||
fieldMetadataItemUsedInDropdown,
|
||||
subFieldName,
|
||||
);
|
||||
}
|
||||
}}
|
||||
text={getCompositeSubFieldLabel(
|
||||
objectFilterDropdownSubMenuFieldType,
|
||||
subFieldName,
|
||||
)}
|
||||
LeftIcon={getIcon(fieldMetadataItemUsedInDropdown?.icon)}
|
||||
/>
|
||||
))}
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === subFieldName}
|
||||
key={`select-filter-${index}`}
|
||||
testId={`select-filter-${index}`}
|
||||
onClick={() => {
|
||||
handleSelectFilter(
|
||||
fieldMetadataItemUsedInDropdown,
|
||||
subFieldName,
|
||||
);
|
||||
}}
|
||||
text={getCompositeSubFieldLabel(
|
||||
objectFilterDropdownSubMenuFieldType,
|
||||
subFieldName,
|
||||
)}
|
||||
LeftIcon={getIcon(fieldMetadataItemUsedInDropdown?.icon)}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -94,9 +94,6 @@ export const ObjectFilterDropdownBooleanSelect = () => {
|
||||
selectableListInstanceId="boolean-select"
|
||||
selectableItemIdArray={options.map((option) => option.toString())}
|
||||
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker}
|
||||
onEnter={(itemId) => {
|
||||
handleOptionSelect(itemId === 'true');
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{options.map((option) => (
|
||||
|
||||
@ -11,14 +11,9 @@ import { objectFilterDropdownSearchInputComponentState } from '@/object-record/o
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
|
||||
import { useSelectFilterUsedInDropdown } from '@/object-record/object-filter-dropdown/hooks/useSelectFilterUsedInDropdown';
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
|
||||
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
@ -96,36 +91,6 @@ export const ObjectFilterDropdownFilterSelect = ({
|
||||
(fieldMetadataItem) => !visibleColumnsIds.includes(fieldMetadataItem.id),
|
||||
);
|
||||
|
||||
const selectableFieldMetadataItemIds = filterableFieldMetadataItems.map(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id,
|
||||
);
|
||||
|
||||
const { selectFilterUsedInDropdown } = useSelectFilterUsedInDropdown();
|
||||
|
||||
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const { resetSelectedItem } = useSelectableList(OBJECT_FILTER_DROPDOWN_ID);
|
||||
|
||||
const handleEnter = (fieldMetadataItemId: string) => {
|
||||
const selectedFieldMetadataItem = filterableFieldMetadataItems.find(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id === fieldMetadataItemId,
|
||||
);
|
||||
|
||||
if (!isDefined(selectedFieldMetadataItem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
resetSelectedItem();
|
||||
|
||||
selectFilterUsedInDropdown({
|
||||
fieldMetadataItemId,
|
||||
});
|
||||
|
||||
setFieldMetadataItemIdUsedInDropdown(fieldMetadataItemId);
|
||||
};
|
||||
|
||||
const shouldShowSeparator =
|
||||
visibleColumnsFieldMetadataItems.length > 0 &&
|
||||
hiddenColumnsFieldMetadataItems.length > 0;
|
||||
@ -137,6 +102,15 @@ export const ObjectFilterDropdownFilterSelect = ({
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
const selectableFieldMetadataItemIds = [
|
||||
...visibleColumnsFieldMetadataItems.map(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id,
|
||||
),
|
||||
...hiddenColumnsFieldMetadataItems.map(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id,
|
||||
),
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledInput
|
||||
@ -151,34 +125,21 @@ export const ObjectFilterDropdownFilterSelect = ({
|
||||
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
||||
selectableItemIdArray={selectableFieldMetadataItemIds}
|
||||
selectableListInstanceId={OBJECT_FILTER_DROPDOWN_ID}
|
||||
onEnter={handleEnter}
|
||||
>
|
||||
<DropdownMenuItemsContainer>
|
||||
{visibleColumnsFieldMetadataItems.map(
|
||||
(visibleFieldMetadataItem, index) => (
|
||||
<SelectableItem
|
||||
itemId={visibleFieldMetadataItem.id}
|
||||
key={`visible-select-filter-${index}`}
|
||||
>
|
||||
<ObjectFilterDropdownFilterSelectMenuItem
|
||||
fieldMetadataItemToSelect={visibleFieldMetadataItem}
|
||||
/>
|
||||
</SelectableItem>
|
||||
),
|
||||
)}
|
||||
{visibleColumnsFieldMetadataItems.map((visibleFieldMetadataItem) => (
|
||||
<ObjectFilterDropdownFilterSelectMenuItem
|
||||
key={visibleFieldMetadataItem.id}
|
||||
fieldMetadataItemToSelect={visibleFieldMetadataItem}
|
||||
/>
|
||||
))}
|
||||
{shouldShowSeparator && <DropdownMenuSeparator />}
|
||||
{hiddenColumnsFieldMetadataItems.map(
|
||||
(hiddenFieldMetadataItem, index) => (
|
||||
<SelectableItem
|
||||
itemId={hiddenFieldMetadataItem.id}
|
||||
key={`hidden-select-filter-${index}`}
|
||||
>
|
||||
<ObjectFilterDropdownFilterSelectMenuItem
|
||||
fieldMetadataItemToSelect={hiddenFieldMetadataItem}
|
||||
/>
|
||||
</SelectableItem>
|
||||
),
|
||||
)}
|
||||
{hiddenColumnsFieldMetadataItems.map((hiddenFieldMetadataItem) => (
|
||||
<ObjectFilterDropdownFilterSelectMenuItem
|
||||
key={hiddenFieldMetadataItem.id}
|
||||
fieldMetadataItemToSelect={hiddenFieldMetadataItem}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
{shouldShowAdvancedFilterButton && <AdvancedFilterButton />}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
||||
@ -9,6 +10,7 @@ import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-rec
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
|
||||
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
|
||||
import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel';
|
||||
import { getFilterableFieldTypeLabel } from '@/object-record/object-filter-dropdown/utils/getFilterableFieldTypeLabel';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
@ -19,6 +21,9 @@ import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/con
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
@ -130,6 +135,10 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
|
||||
setObjectFilterDropdownFilterIsSelected(false);
|
||||
setSubFieldNameUsedInDropdown(null);
|
||||
};
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
OBJECT_FILTER_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
if (!isDefined(objectFilterDropdownSubMenuFieldType)) {
|
||||
return null;
|
||||
@ -170,35 +179,65 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
|
||||
}
|
||||
/> */}
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
key={`select-filter-${-1}`}
|
||||
testId={`select-filter-${-1}`}
|
||||
onClick={() => {
|
||||
handleSelectFilter(fieldMetadataItemUsedInDropdown);
|
||||
}}
|
||||
LeftIcon={IconApps}
|
||||
text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`}
|
||||
/>
|
||||
{subFieldsAreFilterable &&
|
||||
options.map((subFieldName, index) => (
|
||||
<SelectableList
|
||||
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
||||
selectableItemIdArray={['-1', ...options]}
|
||||
selectableListInstanceId={OBJECT_FILTER_DROPDOWN_ID}
|
||||
>
|
||||
<SelectableListItem
|
||||
itemId={'-1'}
|
||||
key={`select-filter-${-1}`}
|
||||
onEnter={() => {
|
||||
handleSelectFilter(fieldMetadataItemUsedInDropdown);
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
key={`select-filter-${index}`}
|
||||
testId={`select-filter-${index}`}
|
||||
focused={selectedItemId === '-1'}
|
||||
key={`select-filter-${-1}`}
|
||||
testId={`select-filter-${-1}`}
|
||||
onClick={() => {
|
||||
if (isDefined(fieldMetadataItemUsedInDropdown)) {
|
||||
handleSelectFilter(fieldMetadataItemUsedInDropdown);
|
||||
}}
|
||||
LeftIcon={IconApps}
|
||||
text={`Any ${getFilterableFieldTypeLabel(
|
||||
objectFilterDropdownSubMenuFieldType,
|
||||
)} field`}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
|
||||
{subFieldsAreFilterable &&
|
||||
options.map((subFieldName, index) => (
|
||||
<SelectableListItem
|
||||
itemId={subFieldName}
|
||||
key={`select-filter-${index}`}
|
||||
onEnter={() => {
|
||||
handleSelectFilter(
|
||||
fieldMetadataItemUsedInDropdown,
|
||||
subFieldName,
|
||||
);
|
||||
}
|
||||
}}
|
||||
text={getCompositeSubFieldLabel(
|
||||
objectFilterDropdownSubMenuFieldType,
|
||||
subFieldName,
|
||||
)}
|
||||
LeftIcon={getIcon(fieldMetadataItemUsedInDropdown?.icon)}
|
||||
/>
|
||||
))}
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === subFieldName}
|
||||
key={`select-filter-${index}`}
|
||||
testId={`select-filter-${index}`}
|
||||
onClick={() => {
|
||||
if (isDefined(fieldMetadataItemUsedInDropdown)) {
|
||||
handleSelectFilter(
|
||||
fieldMetadataItemUsedInDropdown,
|
||||
subFieldName,
|
||||
);
|
||||
}
|
||||
}}
|
||||
text={getCompositeSubFieldLabel(
|
||||
objectFilterDropdownSubMenuFieldType,
|
||||
subFieldName,
|
||||
)}
|
||||
LeftIcon={getIcon(fieldMetadataItemUsedInDropdown?.icon)}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -14,6 +14,7 @@ import { currentRecordFiltersComponentState } from '@/object-record/record-filte
|
||||
import { findDuplicateRecordFilterInNonAdvancedRecordFilters } from '@/object-record/record-filter/utils/findDuplicateRecordFilterInNonAdvancedRecordFilters';
|
||||
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
@ -23,7 +24,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
|
||||
export type ObjectFilterDropdownFilterSelectMenuItemProps = {
|
||||
fieldMetadataItemToSelect: FieldMetadataItem;
|
||||
@ -132,13 +133,17 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<MenuItemSelect
|
||||
selected={false}
|
||||
hovered={isSelectedItem}
|
||||
onClick={handleClick}
|
||||
LeftIcon={Icon}
|
||||
text={fieldMetadataItemToSelect.label}
|
||||
hasSubMenu={shouldShowSubMenu}
|
||||
/>
|
||||
<SelectableListItem
|
||||
itemId={fieldMetadataItemToSelect.id}
|
||||
onEnter={handleClick}
|
||||
>
|
||||
<MenuItem
|
||||
focused={isSelectedItem}
|
||||
onClick={handleClick}
|
||||
LeftIcon={Icon}
|
||||
text={fieldMetadataItemToSelect.label}
|
||||
hasSubMenu={shouldShowSubMenu}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
);
|
||||
};
|
||||
|
||||
@ -6,7 +6,7 @@ import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectab
|
||||
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
|
||||
export type ObjectFilterDropdownFilterSelectMenuItemV2Props = {
|
||||
fieldMetadataItemToSelect: FieldMetadataItem;
|
||||
@ -37,9 +37,8 @@ export const ObjectFilterDropdownFilterSelectMenuItemV2 = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<MenuItemSelect
|
||||
selected={false}
|
||||
hovered={isSelectedItem}
|
||||
<MenuItem
|
||||
focused={isSelectedItem}
|
||||
onClick={handleClick}
|
||||
LeftIcon={Icon}
|
||||
text={fieldMetadataItemToSelect.label}
|
||||
|
||||
@ -167,12 +167,6 @@ export const ObjectFilterDropdownOptionSelect = () => {
|
||||
selectableListInstanceId={componentInstanceId}
|
||||
selectableItemIdArray={objectRecordsIds}
|
||||
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker}
|
||||
onEnter={(itemId) => {
|
||||
const option = optionsInDropdown.find((option) => option.id === itemId);
|
||||
if (isDefined(option)) {
|
||||
handleMultipleOptionSelectChange(option, !option.isSelected);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{optionsInDropdown?.map((option) => (
|
||||
|
||||
@ -1,13 +1,18 @@
|
||||
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
|
||||
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
|
||||
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
||||
import { useSetViewTypeFromLayoutOptionsMenu } from '@/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu';
|
||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
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 { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
|
||||
@ -74,6 +79,18 @@ export const ObjectOptionsDropdownLayoutContent = () => {
|
||||
const isDefaultView = currentView?.key === 'INDEX';
|
||||
const nbsp = '\u00A0';
|
||||
|
||||
const selectableItemIdArray = [
|
||||
ViewType.Table,
|
||||
...(isDefaultView ? [] : [ViewType.Kanban]),
|
||||
ViewOpenRecordInType.SIDE_PANEL,
|
||||
...(currentView?.type === ViewType.Kanban ? ['Group', 'Compact view'] : []),
|
||||
];
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
OBJECT_OPTIONS_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader
|
||||
@ -86,81 +103,132 @@ export const ObjectOptionsDropdownLayoutContent = () => {
|
||||
>
|
||||
{t`Layout`}
|
||||
</DropdownMenuHeader>
|
||||
|
||||
{!!currentView && (
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemSelect
|
||||
LeftIcon={IconTable}
|
||||
text={t`Table`}
|
||||
selected={currentView?.type === ViewType.Table}
|
||||
onClick={async () => {
|
||||
if (currentView?.type !== ViewType.Table) {
|
||||
await setAndPersistViewType(ViewType.Table);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<MenuItemSelect
|
||||
LeftIcon={viewTypeIconMapping(ViewType.Kanban)}
|
||||
text={t`Kanban`}
|
||||
disabled={isDefaultView}
|
||||
contextualText={
|
||||
isDefaultView ? (
|
||||
<>
|
||||
{nbsp}·{nbsp}
|
||||
<OverflowingTextWithTooltip
|
||||
text={t`Not available for default view`}
|
||||
/>
|
||||
</>
|
||||
) : availableFieldsForKanban.length === 0 ? (
|
||||
t`Create Select...`
|
||||
) : undefined
|
||||
}
|
||||
selected={currentView?.type === ViewType.Kanban}
|
||||
onClick={handleSelectKanbanViewType}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<MenuItem
|
||||
onClick={() => onContentChange('layoutOpenIn')}
|
||||
LeftIcon={
|
||||
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
|
||||
? IconLayoutSidebarRight
|
||||
: IconLayoutNavbar
|
||||
}
|
||||
text={t`Open in`}
|
||||
contextualText={
|
||||
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
|
||||
? t`Side Panel`
|
||||
: t`Record Page`
|
||||
}
|
||||
hasSubMenu
|
||||
/>
|
||||
{currentView?.type === ViewType.Kanban && (
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={() =>
|
||||
isDefined(recordGroupFieldMetadata)
|
||||
? onContentChange('recordGroups')
|
||||
: onContentChange('recordGroupFields')
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
|
||||
hotkeyScope={TableOptionsHotkeyScope.Dropdown}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
<SelectableListItem
|
||||
itemId={ViewType.Table}
|
||||
onEnter={() => {
|
||||
setAndPersistViewType(ViewType.Table);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
LeftIcon={IconTable}
|
||||
text={t`Table`}
|
||||
selected={currentView?.type === ViewType.Table}
|
||||
focused={selectedItemId === ViewType.Table}
|
||||
onClick={async () => {
|
||||
if (currentView?.type !== ViewType.Table) {
|
||||
await setAndPersistViewType(ViewType.Table);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
<SelectableListItem
|
||||
itemId={ViewType.Kanban}
|
||||
onEnter={() => {
|
||||
setAndPersistViewType(ViewType.Kanban);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
LeftIcon={viewTypeIconMapping(ViewType.Kanban)}
|
||||
text={t`Kanban`}
|
||||
disabled={isDefaultView}
|
||||
focused={selectedItemId === ViewType.Kanban}
|
||||
contextualText={
|
||||
isDefaultView ? (
|
||||
<>
|
||||
{nbsp}·{nbsp}
|
||||
<OverflowingTextWithTooltip
|
||||
text={t`Not available for default view`}
|
||||
/>
|
||||
</>
|
||||
) : availableFieldsForKanban.length === 0 ? (
|
||||
t`Create Select...`
|
||||
) : undefined
|
||||
}
|
||||
selected={currentView?.type === ViewType.Kanban}
|
||||
onClick={handleSelectKanbanViewType}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
<DropdownMenuSeparator />
|
||||
<SelectableListItem
|
||||
itemId={ViewOpenRecordInType.SIDE_PANEL}
|
||||
onEnter={() => {
|
||||
onContentChange('layoutOpenIn');
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === ViewOpenRecordInType.SIDE_PANEL}
|
||||
LeftIcon={
|
||||
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
|
||||
? IconLayoutSidebarRight
|
||||
: IconLayoutNavbar
|
||||
}
|
||||
text={t`Open in`}
|
||||
contextualText={
|
||||
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
|
||||
? t`Side Panel`
|
||||
: t`Record Page`
|
||||
}
|
||||
LeftIcon={IconLayoutList}
|
||||
text={t`Group`}
|
||||
contextualText={recordGroupFieldMetadata?.label}
|
||||
hasSubMenu
|
||||
/>
|
||||
</SelectableListItem>
|
||||
{currentView?.type === ViewType.Kanban && (
|
||||
<>
|
||||
<SelectableListItem
|
||||
itemId={'Group'}
|
||||
onEnter={() => {
|
||||
isDefined(recordGroupFieldMetadata)
|
||||
? onContentChange('recordGroups')
|
||||
: onContentChange('recordGroupFields');
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Group'}
|
||||
onClick={() =>
|
||||
isDefined(recordGroupFieldMetadata)
|
||||
? onContentChange('recordGroups')
|
||||
: onContentChange('recordGroupFields')
|
||||
}
|
||||
LeftIcon={IconLayoutList}
|
||||
text={t`Group`}
|
||||
contextualText={recordGroupFieldMetadata?.label}
|
||||
hasSubMenu
|
||||
/>
|
||||
</SelectableListItem>
|
||||
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconBaselineDensitySmall}
|
||||
onToggleChange={() =>
|
||||
setAndPersistIsCompactModeActive(
|
||||
!isCompactModeActive,
|
||||
currentView,
|
||||
)
|
||||
}
|
||||
toggled={isCompactModeActive}
|
||||
text={t`Compact view`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<SelectableListItem
|
||||
itemId={'Compact view'}
|
||||
onEnter={() => {
|
||||
setAndPersistIsCompactModeActive(
|
||||
!isCompactModeActive,
|
||||
currentView,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<MenuItemToggle
|
||||
focused={selectedItemId === 'Compact view'}
|
||||
LeftIcon={IconBaselineDensitySmall}
|
||||
onToggleChange={() =>
|
||||
setAndPersistIsCompactModeActive(
|
||||
!isCompactModeActive,
|
||||
currentView,
|
||||
)
|
||||
}
|
||||
toggled={isCompactModeActive}
|
||||
text={t`Compact view`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</>
|
||||
)}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
|
||||
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
||||
import { useUpdateObjectViewOptions } from '@/object-record/object-options-dropdown/hooks/useUpdateObjectViewOptions';
|
||||
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
|
||||
import { t } from '@lingui/core/macro';
|
||||
@ -21,6 +27,16 @@ export const ObjectOptionsDropdownLayoutOpenInContent = () => {
|
||||
const { currentView } = useGetCurrentViewOnly();
|
||||
const { setAndPersistOpenRecordIn } = useUpdateObjectViewOptions();
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
OBJECT_OPTIONS_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const selectableItemIdArray = [
|
||||
ViewOpenRecordInType.SIDE_PANEL,
|
||||
ViewOpenRecordInType.RECORD_PAGE,
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader
|
||||
@ -34,30 +50,60 @@ export const ObjectOptionsDropdownLayoutOpenInContent = () => {
|
||||
{t`Open in`}
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemSelect
|
||||
LeftIcon={IconLayoutSidebarRight}
|
||||
text={t`Side Panel`}
|
||||
selected={recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL}
|
||||
onClick={() =>
|
||||
setAndPersistOpenRecordIn(
|
||||
ViewOpenRecordInType.SIDE_PANEL,
|
||||
currentView,
|
||||
)
|
||||
}
|
||||
/>
|
||||
<MenuItemSelect
|
||||
LeftIcon={IconLayoutNavbar}
|
||||
text={t`Record Page`}
|
||||
selected={
|
||||
recordIndexOpenRecordIn === ViewOpenRecordInType.RECORD_PAGE
|
||||
}
|
||||
onClick={() =>
|
||||
setAndPersistOpenRecordIn(
|
||||
ViewOpenRecordInType.RECORD_PAGE,
|
||||
currentView,
|
||||
)
|
||||
}
|
||||
/>
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
|
||||
hotkeyScope={TableOptionsHotkeyScope.Dropdown}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
<SelectableListItem
|
||||
itemId={ViewOpenRecordInType.SIDE_PANEL}
|
||||
onEnter={() =>
|
||||
setAndPersistOpenRecordIn(
|
||||
ViewOpenRecordInType.SIDE_PANEL,
|
||||
currentView,
|
||||
)
|
||||
}
|
||||
>
|
||||
<MenuItemSelect
|
||||
LeftIcon={IconLayoutSidebarRight}
|
||||
text={t`Side Panel`}
|
||||
selected={
|
||||
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
|
||||
}
|
||||
focused={selectedItemId === ViewOpenRecordInType.SIDE_PANEL}
|
||||
onClick={() =>
|
||||
setAndPersistOpenRecordIn(
|
||||
ViewOpenRecordInType.SIDE_PANEL,
|
||||
currentView,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
<SelectableListItem
|
||||
itemId={ViewOpenRecordInType.RECORD_PAGE}
|
||||
onEnter={() =>
|
||||
setAndPersistOpenRecordIn(
|
||||
ViewOpenRecordInType.RECORD_PAGE,
|
||||
currentView,
|
||||
)
|
||||
}
|
||||
>
|
||||
<MenuItemSelect
|
||||
LeftIcon={IconLayoutNavbar}
|
||||
text={t`Record Page`}
|
||||
selected={
|
||||
recordIndexOpenRecordIn === ViewOpenRecordInType.RECORD_PAGE
|
||||
}
|
||||
onClick={() =>
|
||||
setAndPersistOpenRecordIn(
|
||||
ViewOpenRecordInType.RECORD_PAGE,
|
||||
currentView,
|
||||
)
|
||||
}
|
||||
focused={selectedItemId === ViewOpenRecordInType.RECORD_PAGE}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { ObjectOptionsDropdownMenuViewName } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuViewName';
|
||||
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
|
||||
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
|
||||
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||
@ -9,6 +10,9 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
@ -75,92 +79,155 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
|
||||
const isDefaultView = currentView?.key === 'INDEX';
|
||||
|
||||
const selectableItemIdArray = [
|
||||
'Layout',
|
||||
'Fields',
|
||||
...(isDefaultView ? [] : ['Group']),
|
||||
'Copy link to view',
|
||||
...(isDefaultView ? [] : ['Delete view']),
|
||||
];
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
OBJECT_OPTIONS_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{currentView && (
|
||||
<ObjectOptionsDropdownMenuViewName currentView={currentView} />
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<MenuItem
|
||||
onClick={() => onContentChange('layout')}
|
||||
LeftIcon={viewTypeIconMapping(currentView?.type ?? ViewType.Table)}
|
||||
text={t`Layout`}
|
||||
contextualText={`${capitalize(currentView?.type ?? '')}`}
|
||||
hasSubMenu
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<MenuItem
|
||||
onClick={() => onContentChange('fields')}
|
||||
LeftIcon={IconListDetails}
|
||||
text={t`Fields`}
|
||||
contextualText={`${visibleBoardFields.length} shown`}
|
||||
hasSubMenu
|
||||
/>
|
||||
|
||||
<div id="group-by-menu-item">
|
||||
<MenuItem
|
||||
onClick={() =>
|
||||
isDefined(recordGroupFieldMetadata)
|
||||
? onContentChange('recordGroups')
|
||||
: onContentChange('recordGroupFields')
|
||||
}
|
||||
LeftIcon={IconLayoutList}
|
||||
text={t`Group`}
|
||||
contextualText={
|
||||
isDefaultView
|
||||
? t`Not available on Default View`
|
||||
: recordGroupFieldMetadata?.label
|
||||
}
|
||||
hasSubMenu
|
||||
disabled={isDefaultView}
|
||||
/>
|
||||
</div>
|
||||
{!isGroupByEnabled && (
|
||||
<AppTooltip
|
||||
anchorSelect={`#group-by-menu-item`}
|
||||
content={t`Not available on Default View`}
|
||||
noArrow
|
||||
place="bottom"
|
||||
width="100%"
|
||||
/>
|
||||
)}
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
|
||||
hotkeyScope={TableOptionsHotkeyScope.Dropdown}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<SelectableListItem
|
||||
itemId="Layout"
|
||||
onEnter={() => onContentChange('layout')}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Layout'}
|
||||
onClick={() => onContentChange('layout')}
|
||||
LeftIcon={viewTypeIconMapping(
|
||||
currentView?.type ?? ViewType.Table,
|
||||
)}
|
||||
text={t`Layout`}
|
||||
contextualText={`${capitalize(currentView?.type ?? '')}`}
|
||||
hasSubMenu
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
const currentUrl = window.location.href;
|
||||
navigator.clipboard.writeText(currentUrl);
|
||||
enqueueSnackBar('Link copied to clipboard', {
|
||||
variant: SnackBarVariant.Success,
|
||||
icon: <IconCopy size={theme.icon.size.md} />,
|
||||
duration: 2000,
|
||||
});
|
||||
}}
|
||||
LeftIcon={IconCopy}
|
||||
text={t`Copy link to view`}
|
||||
/>
|
||||
<div id="delete-view-menu-item">
|
||||
<MenuItem
|
||||
onClick={() => handleDelete()}
|
||||
LeftIcon={IconTrash}
|
||||
text={t`Delete view`}
|
||||
disabled={currentView?.key === 'INDEX'}
|
||||
/>
|
||||
</div>
|
||||
{currentView?.key === 'INDEX' && (
|
||||
<AppTooltip
|
||||
anchorSelect={`#delete-view-menu-item`}
|
||||
content={t`Not available on Default View`}
|
||||
noArrow
|
||||
place="bottom"
|
||||
width="100%"
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<SelectableListItem
|
||||
itemId="Fields"
|
||||
onEnter={() => onContentChange('fields')}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Fields'}
|
||||
onClick={() => onContentChange('fields')}
|
||||
LeftIcon={IconListDetails}
|
||||
text={t`Fields`}
|
||||
contextualText={`${visibleBoardFields.length} shown`}
|
||||
hasSubMenu
|
||||
/>
|
||||
</SelectableListItem>
|
||||
|
||||
<div id="group-by-menu-item">
|
||||
<SelectableListItem
|
||||
itemId="Group"
|
||||
onEnter={() =>
|
||||
isDefined(recordGroupFieldMetadata)
|
||||
? onContentChange('recordGroups')
|
||||
: onContentChange('recordGroupFields')
|
||||
}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Group'}
|
||||
onClick={() =>
|
||||
isDefined(recordGroupFieldMetadata)
|
||||
? onContentChange('recordGroups')
|
||||
: onContentChange('recordGroupFields')
|
||||
}
|
||||
LeftIcon={IconLayoutList}
|
||||
text={t`Group`}
|
||||
contextualText={
|
||||
isDefaultView
|
||||
? t`Not available on Default View`
|
||||
: recordGroupFieldMetadata?.label
|
||||
}
|
||||
hasSubMenu
|
||||
disabled={isDefaultView}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</div>
|
||||
{!isGroupByEnabled && (
|
||||
<AppTooltip
|
||||
anchorSelect={`#group-by-menu-item`}
|
||||
content={t`Not available on Default View`}
|
||||
noArrow
|
||||
place="bottom"
|
||||
width="100%"
|
||||
/>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<SelectableListItem
|
||||
itemId="Copy link to view"
|
||||
onEnter={() => {
|
||||
const currentUrl = window.location.href;
|
||||
navigator.clipboard.writeText(currentUrl);
|
||||
enqueueSnackBar('Link copied to clipboard', {
|
||||
variant: SnackBarVariant.Success,
|
||||
icon: <IconCopy size={theme.icon.size.md} />,
|
||||
duration: 2000,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Copy link to view'}
|
||||
onClick={() => {
|
||||
const currentUrl = window.location.href;
|
||||
navigator.clipboard.writeText(currentUrl);
|
||||
enqueueSnackBar('Link copied to clipboard', {
|
||||
variant: SnackBarVariant.Success,
|
||||
icon: <IconCopy size={theme.icon.size.md} />,
|
||||
duration: 2000,
|
||||
});
|
||||
}}
|
||||
LeftIcon={IconCopy}
|
||||
text={t`Copy link to view`}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
<div id="delete-view-menu-item">
|
||||
<SelectableListItem
|
||||
itemId="Delete view"
|
||||
onEnter={() => handleDelete()}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Delete view'}
|
||||
onClick={() => handleDelete()}
|
||||
LeftIcon={IconTrash}
|
||||
text={t`Delete view`}
|
||||
disabled={currentView?.key === 'INDEX'}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</div>
|
||||
{currentView?.key === 'INDEX' && (
|
||||
<AppTooltip
|
||||
anchorSelect={`#delete-view-menu-item`}
|
||||
content={t`Not available on Default View`}
|
||||
noArrow
|
||||
place="bottom"
|
||||
width="100%"
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
|
||||
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
||||
import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/hiddenRecordGroupIdsComponentSelector';
|
||||
import { RecordGroupSort } from '@/object-record/record-group/types/RecordGroupSort';
|
||||
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import {
|
||||
IconChevronLeft,
|
||||
IconHandMove,
|
||||
@ -32,6 +37,11 @@ export const ObjectOptionsDropdownRecordGroupSortContent = () => {
|
||||
setRecordGroupSort(sort);
|
||||
};
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
OBJECT_OPTIONS_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
currentContentId === 'hiddenRecordGroups' &&
|
||||
@ -41,6 +51,12 @@ export const ObjectOptionsDropdownRecordGroupSortContent = () => {
|
||||
}
|
||||
}, [hiddenRecordGroupIds, currentContentId, onContentChange]);
|
||||
|
||||
const selectableItemIdArray = [
|
||||
RecordGroupSort.Manual,
|
||||
RecordGroupSort.Alphabetical,
|
||||
RecordGroupSort.ReverseAlphabetical,
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader
|
||||
@ -54,28 +70,58 @@ export const ObjectOptionsDropdownRecordGroupSortContent = () => {
|
||||
Sort
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemSelect
|
||||
onClick={() => handleRecordGroupSortChange(RecordGroupSort.Manual)}
|
||||
LeftIcon={IconHandMove}
|
||||
text={RecordGroupSort.Manual}
|
||||
selected={recordGroupSort === RecordGroupSort.Manual}
|
||||
/>
|
||||
<MenuItemSelect
|
||||
onClick={() =>
|
||||
handleRecordGroupSortChange(RecordGroupSort.Alphabetical)
|
||||
}
|
||||
LeftIcon={IconSortAZ}
|
||||
text={RecordGroupSort.Alphabetical}
|
||||
selected={recordGroupSort === RecordGroupSort.Alphabetical}
|
||||
/>
|
||||
<MenuItemSelect
|
||||
onClick={() =>
|
||||
handleRecordGroupSortChange(RecordGroupSort.ReverseAlphabetical)
|
||||
}
|
||||
LeftIcon={IconSortZA}
|
||||
text={RecordGroupSort.ReverseAlphabetical}
|
||||
selected={recordGroupSort === RecordGroupSort.ReverseAlphabetical}
|
||||
/>
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
|
||||
hotkeyScope={TableOptionsHotkeyScope.Dropdown}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
<SelectableListItem
|
||||
itemId={RecordGroupSort.Manual}
|
||||
onEnter={() => handleRecordGroupSortChange(RecordGroupSort.Manual)}
|
||||
>
|
||||
<MenuItemSelect
|
||||
onClick={() =>
|
||||
handleRecordGroupSortChange(RecordGroupSort.Manual)
|
||||
}
|
||||
LeftIcon={IconHandMove}
|
||||
text={RecordGroupSort.Manual}
|
||||
selected={recordGroupSort === RecordGroupSort.Manual}
|
||||
focused={selectedItemId === RecordGroupSort.Manual}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
<SelectableListItem
|
||||
itemId={RecordGroupSort.Alphabetical}
|
||||
onEnter={() =>
|
||||
handleRecordGroupSortChange(RecordGroupSort.Alphabetical)
|
||||
}
|
||||
>
|
||||
<MenuItemSelect
|
||||
onClick={() =>
|
||||
handleRecordGroupSortChange(RecordGroupSort.Alphabetical)
|
||||
}
|
||||
LeftIcon={IconSortAZ}
|
||||
text={RecordGroupSort.Alphabetical}
|
||||
selected={recordGroupSort === RecordGroupSort.Alphabetical}
|
||||
focused={selectedItemId === RecordGroupSort.Alphabetical}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
<SelectableListItem
|
||||
itemId={RecordGroupSort.ReverseAlphabetical}
|
||||
onEnter={() =>
|
||||
handleRecordGroupSortChange(RecordGroupSort.ReverseAlphabetical)
|
||||
}
|
||||
>
|
||||
<MenuItemSelect
|
||||
onClick={() =>
|
||||
handleRecordGroupSortChange(RecordGroupSort.ReverseAlphabetical)
|
||||
}
|
||||
LeftIcon={IconSortZA}
|
||||
text={RecordGroupSort.ReverseAlphabetical}
|
||||
selected={recordGroupSort === RecordGroupSort.ReverseAlphabetical}
|
||||
focused={selectedItemId === RecordGroupSort.ReverseAlphabetical}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
|
||||
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
||||
import { RecordGroupReorderConfirmationModal } from '@/object-record/record-group/components/RecordGroupReorderConfirmationModal';
|
||||
import { RecordGroupsVisibilityDropdownSection } from '@/object-record/record-group/components/RecordGroupsVisibilityDropdownSection';
|
||||
@ -10,10 +11,14 @@ import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-gr
|
||||
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||
import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState';
|
||||
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
@ -89,6 +94,17 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
||||
}
|
||||
}, [hiddenRecordGroupIds, currentContentId, onContentChange]);
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
OBJECT_OPTIONS_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const selectableItemIdArray = [
|
||||
...(currentView?.key !== 'INDEX' ? ['GroupBy', 'Sort'] : []),
|
||||
'HideEmptyGroups',
|
||||
...(hiddenRecordGroupIds.length > 0 ? ['HiddenGroups'] : []),
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader
|
||||
@ -102,31 +118,55 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
||||
Group
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
{currentView?.key !== 'INDEX' && (
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={() => onContentChange('recordGroupFields')}
|
||||
LeftIcon={IconLayoutList}
|
||||
text={t`Group by`}
|
||||
contextualText={recordGroupFieldMetadata?.label}
|
||||
hasSubMenu
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
|
||||
hotkeyScope={TableOptionsHotkeyScope.Dropdown}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
{currentView?.key !== 'INDEX' && (
|
||||
<>
|
||||
<SelectableListItem
|
||||
itemId="GroupBy"
|
||||
onEnter={() => onContentChange('recordGroupFields')}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'GroupBy'}
|
||||
onClick={() => onContentChange('recordGroupFields')}
|
||||
LeftIcon={IconLayoutList}
|
||||
text={t`Group by`}
|
||||
contextualText={recordGroupFieldMetadata?.label}
|
||||
hasSubMenu
|
||||
/>
|
||||
</SelectableListItem>
|
||||
<SelectableListItem
|
||||
itemId="Sort"
|
||||
onEnter={() => onContentChange('recordGroupSort')}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Sort'}
|
||||
onClick={() => onContentChange('recordGroupSort')}
|
||||
LeftIcon={IconSortDescending}
|
||||
text={t`Sort`}
|
||||
contextualText={recordGroupSort}
|
||||
hasSubMenu
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</>
|
||||
)}
|
||||
<SelectableListItem
|
||||
itemId="HideEmptyGroups"
|
||||
onEnter={() => handleHideEmptyRecordGroupChange()}
|
||||
>
|
||||
<MenuItemToggle
|
||||
focused={selectedItemId === 'HideEmptyGroups'}
|
||||
LeftIcon={IconCircleOff}
|
||||
onToggleChange={handleHideEmptyRecordGroupChange}
|
||||
toggled={hideEmptyRecordGroup}
|
||||
text={t`Hide empty groups`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
<MenuItem
|
||||
onClick={() => onContentChange('recordGroupSort')}
|
||||
LeftIcon={IconSortDescending}
|
||||
text={t`Sort`}
|
||||
contextualText={recordGroupSort}
|
||||
hasSubMenu
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconCircleOff}
|
||||
onToggleChange={handleHideEmptyRecordGroupChange}
|
||||
toggled={hideEmptyRecordGroup}
|
||||
text={t`Hide empty groups`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
{visibleRecordGroupIds.length > 0 && (
|
||||
<>
|
||||
@ -145,11 +185,16 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<MenuItemNavigate
|
||||
onClick={() => onContentChange('hiddenRecordGroups')}
|
||||
LeftIcon={IconEyeOff}
|
||||
text={`Hidden ${recordGroupFieldMetadata?.label ?? ''}`}
|
||||
/>
|
||||
<SelectableListItem
|
||||
itemId="HiddenGroups"
|
||||
onEnter={() => onContentChange('hiddenRecordGroups')}
|
||||
>
|
||||
<MenuItemNavigate
|
||||
onClick={() => onContentChange('hiddenRecordGroups')}
|
||||
LeftIcon={IconEyeOff}
|
||||
text={`Hidden ${recordGroupFieldMetadata?.label ?? ''}`}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -24,16 +24,19 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { IconChevronDown, useIcons } from 'twenty-ui/display';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const StyledInput = styled.input`
|
||||
background: transparent;
|
||||
@ -191,6 +194,21 @@ export const ObjectSortDropdownButton = ({
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const selectableItemIdArray = [
|
||||
...visibleFieldMetadataItems.map((item) => item.id),
|
||||
...hiddenFieldMetadataItems.map((item) => item.id),
|
||||
];
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
OBJECT_SORT_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const setSelectedItemId = useSetRecoilComponentStateV2(
|
||||
selectedItemIdComponentState,
|
||||
OBJECT_SORT_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={OBJECT_SORT_DROPDOWN_ID}
|
||||
@ -198,20 +216,28 @@ export const ObjectSortDropdownButton = ({
|
||||
dropdownOffset={{ y: 8 }}
|
||||
clickableComponent={
|
||||
<StyledHeaderDropdownButton
|
||||
onClick={handleButtonClick}
|
||||
onClick={() => {
|
||||
handleButtonClick();
|
||||
setSelectedItemId(selectableItemIdArray[0]);
|
||||
}}
|
||||
isUnfolded={isDropdownOpen}
|
||||
>
|
||||
<Trans>Sort</Trans>
|
||||
</StyledHeaderDropdownButton>
|
||||
}
|
||||
dropdownComponents={
|
||||
<>
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_SORT_DROPDOWN_ID}
|
||||
hotkeyScope={hotkeyScope.scope}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
{isRecordSortDirectionMenuUnfolded && (
|
||||
<StyledSelectedSortDirectionContainer>
|
||||
<DropdownMenuItemsContainer>
|
||||
{RECORD_SORT_DIRECTIONS.map((sortDirection, index) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
focused={selectedItemId === sortDirection}
|
||||
onClick={() => handleSortDirectionClick(sortDirection)}
|
||||
text={
|
||||
sortDirection === 'asc' ? t`Ascending` : t`Descending`
|
||||
@ -244,27 +270,39 @@ export const ObjectSortDropdownButton = ({
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{visibleFieldMetadataItems.map(
|
||||
(visibleFieldMetadataItem, index) => (
|
||||
<MenuItem
|
||||
testId={`visible-select-sort-${index}`}
|
||||
key={index}
|
||||
onClick={() => handleAddSort(visibleFieldMetadataItem)}
|
||||
LeftIcon={getIcon(visibleFieldMetadataItem.icon)}
|
||||
text={visibleFieldMetadataItem.label}
|
||||
/>
|
||||
<SelectableListItem
|
||||
key={visibleFieldMetadataItem.id}
|
||||
itemId={visibleFieldMetadataItem.id}
|
||||
onEnter={() => handleAddSort(visibleFieldMetadataItem)}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === visibleFieldMetadataItem.id}
|
||||
testId={`visible-select-sort-${index}`}
|
||||
onClick={() => handleAddSort(visibleFieldMetadataItem)}
|
||||
LeftIcon={getIcon(visibleFieldMetadataItem.icon)}
|
||||
text={visibleFieldMetadataItem.label}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
),
|
||||
)}
|
||||
{shouldShowSeparator && <DropdownMenuSeparator />}
|
||||
{hiddenFieldMetadataItems.map((hiddenFieldMetadataItem, index) => (
|
||||
<MenuItem
|
||||
testId={`hidden-select-sort-${index}`}
|
||||
key={index}
|
||||
onClick={() => handleAddSort(hiddenFieldMetadataItem)}
|
||||
LeftIcon={getIcon(hiddenFieldMetadataItem.icon)}
|
||||
text={hiddenFieldMetadataItem.label}
|
||||
/>
|
||||
<SelectableListItem
|
||||
key={hiddenFieldMetadataItem.id}
|
||||
itemId={hiddenFieldMetadataItem.id}
|
||||
onEnter={() => handleAddSort(hiddenFieldMetadataItem)}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === hiddenFieldMetadataItem.id}
|
||||
testId={`hidden-select-sort-${index}`}
|
||||
onClick={() => handleAddSort(hiddenFieldMetadataItem)}
|
||||
LeftIcon={getIcon(hiddenFieldMetadataItem.icon)}
|
||||
text={hiddenFieldMetadataItem.label}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</SelectableList>
|
||||
}
|
||||
onClose={handleDropdownButtonClose}
|
||||
/>
|
||||
|
||||
@ -4,9 +4,8 @@ import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/get
|
||||
import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId';
|
||||
import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState';
|
||||
import { recordBoardSelectedRecordIdsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardSelectedRecordIdsComponentSelector';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
|
||||
export const useRecordBoardSelection = (recordBoardId: string) => {
|
||||
@ -22,17 +21,16 @@ export const useRecordBoardSelection = (recordBoardId: string) => {
|
||||
recordBoardId,
|
||||
);
|
||||
|
||||
const isActionMenuDropdownOpenState = extractComponentState(
|
||||
isDropdownOpenComponentState,
|
||||
getActionMenuDropdownIdFromActionMenuId(
|
||||
getActionMenuIdFromRecordIndexId(recordBoardId),
|
||||
),
|
||||
const { closeDropdown } = useDropdownV2();
|
||||
|
||||
const dropdownId = getActionMenuDropdownIdFromActionMenuId(
|
||||
getActionMenuIdFromRecordIndexId(recordBoardId),
|
||||
);
|
||||
|
||||
const resetRecordSelection = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
() => {
|
||||
set(isActionMenuDropdownOpenState, false);
|
||||
closeDropdown(dropdownId);
|
||||
|
||||
const recordIds = getSnapshotValue(
|
||||
snapshot,
|
||||
@ -44,7 +42,8 @@ export const useRecordBoardSelection = (recordBoardId: string) => {
|
||||
}
|
||||
},
|
||||
[
|
||||
isActionMenuDropdownOpenState,
|
||||
closeDropdown,
|
||||
dropdownId,
|
||||
recordBoardSelectedRecordIdsSelector,
|
||||
isRecordBoardCardSelectedFamilyState,
|
||||
],
|
||||
@ -67,17 +66,17 @@ export const useRecordBoardSelection = (recordBoardId: string) => {
|
||||
);
|
||||
|
||||
const checkIfLastUnselectAndCloseDropdown = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const recordIds = getSnapshotValue(
|
||||
snapshot,
|
||||
recordBoardSelectedRecordIdsSelector,
|
||||
);
|
||||
if (recordIds.length === 0) {
|
||||
set(isActionMenuDropdownOpenState, false);
|
||||
closeDropdown(dropdownId);
|
||||
}
|
||||
},
|
||||
[recordBoardSelectedRecordIdsSelector, isActionMenuDropdownOpenState],
|
||||
[recordBoardSelectedRecordIdsSelector, closeDropdown, dropdownId],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@ -7,6 +7,7 @@ import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/r
|
||||
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
|
||||
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
|
||||
|
||||
import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope';
|
||||
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
|
||||
import { RecordBoardCardBody } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBody';
|
||||
import { RecordBoardCardHeader } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeader';
|
||||
@ -121,7 +122,9 @@ export const RecordBoardCard = () => {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
openDropdown(actionMenuDropdownId);
|
||||
openDropdown(actionMenuDropdownId, {
|
||||
scope: ActionMenuDropdownHotkeyScope.ActionMenuDropdown,
|
||||
});
|
||||
};
|
||||
|
||||
const handleCardClick = () => {
|
||||
|
||||
@ -15,7 +15,7 @@ import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
@ -28,7 +28,7 @@ import { Key } from 'ts-key-enum';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
|
||||
export const StyledSelectableItem = styled(SelectableItem)`
|
||||
export const StyledSelectableItem = styled(SelectableListItem)`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@ -3,10 +3,10 @@ import styled from '@emotion/styled';
|
||||
import { useRecordPickerGetSearchRecordAndObjectMetadataItemFromRecordId } from '@/object-record/record-picker/hooks/useRecordPickerGetSearchRecordAndObjectMetadataItemFromRecordId';
|
||||
import { MultipleRecordPickerMenuItemContent } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItemContent';
|
||||
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const StyledSelectableItem = styled(SelectableItem)`
|
||||
export const StyledSelectableItem = styled(SelectableListItem)`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@ -6,7 +6,7 @@ import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/re
|
||||
import { multipleRecordPickerIsSelectedComponentFamilySelector } from '@/object-record/record-picker/multiple-record-picker/states/selectors/multipleRecordPickerIsSelectedComponentFamilySelector';
|
||||
import { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId';
|
||||
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
@ -14,7 +14,7 @@ import { Avatar } from 'twenty-ui/display';
|
||||
import { MenuItemMultiSelectAvatar } from 'twenty-ui/navigation';
|
||||
import { SearchRecord } from '~/generated-metadata/graphql';
|
||||
|
||||
export const StyledSelectableItem = styled(SelectableItem)`
|
||||
export const StyledSelectableItem = styled(SelectableListItem)`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
@ -62,6 +62,7 @@ export const MultipleRecordPickerMenuItemContent = ({
|
||||
<StyledSelectableItem
|
||||
itemId={searchRecord.recordId}
|
||||
key={searchRecord.recordId}
|
||||
onEnter={() => handleSelectChange(!isRecordSelectedWithObjectItem)}
|
||||
>
|
||||
<MenuItemMultiSelectAvatar
|
||||
onSelectChange={(isSelected) => handleSelectChange(isSelected)}
|
||||
|
||||
@ -4,21 +4,19 @@ import { MultipleRecordPickerMenuItem } from '@/object-record/record-picker/mult
|
||||
import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
|
||||
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
|
||||
import { multipleRecordPickerPickableRecordIdsMatchingSearchComponentSelector } from '@/object-record/record-picker/multiple-record-picker/states/selectors/multipleRecordPickerPickableRecordIdsMatchingSearchComponentSelector';
|
||||
import { multipleRecordPickerSinglePickableMorphItemComponentFamilySelector } from '@/object-record/record-picker/multiple-record-picker/states/selectors/multipleRecordPickerSinglePickableMorphItemComponentFamilySelector';
|
||||
import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope';
|
||||
import { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId';
|
||||
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const StyledSelectableItem = styled(SelectableItem)`
|
||||
export const StyledSelectableItem = styled(SelectableListItem)`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
@ -45,11 +43,6 @@ export const MultipleRecordPickerMenuItems = ({
|
||||
const { resetSelectedItem } = useSelectableList(
|
||||
selectableListComponentInstanceId,
|
||||
);
|
||||
const singlePickableMorphItemFamilySelector =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
multipleRecordPickerSinglePickableMorphItemComponentFamilySelector,
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const multipleRecordPickerPickableMorphItemsState =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
@ -82,42 +75,12 @@ export const MultipleRecordPickerMenuItems = ({
|
||||
[multipleRecordPickerPickableMorphItemsState],
|
||||
);
|
||||
|
||||
const handleEnter = useRecoilCallback(
|
||||
({ snapshot }) => {
|
||||
return (selectedId: string) => {
|
||||
const pickableMorphItem = snapshot
|
||||
.getLoadable(singlePickableMorphItemFamilySelector(selectedId))
|
||||
.getValue();
|
||||
|
||||
if (!isDefined(pickableMorphItem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedMorphItem = {
|
||||
...pickableMorphItem,
|
||||
isSelected: !pickableMorphItem.isSelected,
|
||||
};
|
||||
|
||||
handleChange(selectedMorphItem);
|
||||
onChange?.(selectedMorphItem);
|
||||
resetSelectedItem();
|
||||
};
|
||||
},
|
||||
[
|
||||
handleChange,
|
||||
onChange,
|
||||
resetSelectedItem,
|
||||
singlePickableMorphItemFamilySelector,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
<SelectableList
|
||||
selectableListInstanceId={selectableListComponentInstanceId}
|
||||
selectableItemIdArray={pickableRecordIds}
|
||||
hotkeyScope={MultipleRecordPickerHotkeyScope.MultipleRecordPicker}
|
||||
onEnter={handleEnter}
|
||||
>
|
||||
{pickableRecordIds.map((recordId) => {
|
||||
return (
|
||||
|
||||
@ -1,19 +1,11 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch';
|
||||
import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
|
||||
import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const StyledSelectableItem = styled(SelectableItem)`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const MultipleRecordPickerSearchInput = () => {
|
||||
const componentInstanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
MultipleRecordPickerComponentInstanceContext,
|
||||
|
||||
@ -3,7 +3,7 @@ import styled from '@emotion/styled';
|
||||
import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext';
|
||||
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
|
||||
import { getSingleRecordPickerSelectableListId } from '@/object-record/record-picker/single-record-picker/utils/getSingleRecordPickerSelectableListId';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
@ -16,7 +16,7 @@ type SingleRecordPickerMenuItemProps = {
|
||||
selectedRecord?: SingleRecordPickerRecord;
|
||||
};
|
||||
|
||||
const StyledSelectableItem = styled(SelectableItem)`
|
||||
const StyledSelectableItem = styled(SelectableListItem)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@ -40,14 +40,19 @@ export const SingleRecordPickerMenuItem = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledSelectableItem itemId={record.id} key={record.id}>
|
||||
<StyledSelectableItem
|
||||
itemId={record.id}
|
||||
key={record.id}
|
||||
onEnter={() => {
|
||||
onRecordSelected(record);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelectAvatar
|
||||
key={record.id}
|
||||
testId="menu-item"
|
||||
onClick={() => onRecordSelected(record)}
|
||||
text={record.name}
|
||||
selected={selectedRecord?.id === record.id}
|
||||
hovered={isSelectedItemId}
|
||||
focused={isSelectedItemId}
|
||||
avatar={
|
||||
<Avatar
|
||||
avatarUrl={record.avatarUrl}
|
||||
|
||||
@ -14,6 +14,7 @@ import { singleRecordPickerSelectedIdComponentState } from '@/object-record/reco
|
||||
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
|
||||
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
|
||||
import { getSingleRecordPickerSelectableListId } from '@/object-record/record-picker/single-record-picker/utils/getSingleRecordPickerSelectableListId';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
@ -108,14 +109,6 @@ export const SingleRecordPickerMenuItems = ({
|
||||
selectableListInstanceId={selectableListComponentInstanceId}
|
||||
selectableItemIdArray={selectableItemIds}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onEnter={(itemId) => {
|
||||
const recordIndex = recordsInDropdown.findIndex(
|
||||
(record) => record.id === itemId,
|
||||
);
|
||||
setSelectedRecordId(itemId);
|
||||
onRecordSelected(recordsInDropdown[recordIndex]);
|
||||
resetSelectedItem();
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{loading && !isFiltered ? (
|
||||
@ -128,17 +121,25 @@ export const SingleRecordPickerMenuItems = ({
|
||||
case 'select-none': {
|
||||
return (
|
||||
emptyLabel && (
|
||||
<MenuItemSelect
|
||||
<SelectableListItem
|
||||
key={record.id}
|
||||
onClick={() => {
|
||||
itemId={record.id}
|
||||
onEnter={() => {
|
||||
setSelectedRecordId(undefined);
|
||||
onRecordSelected();
|
||||
}}
|
||||
LeftIcon={EmptyIcon}
|
||||
text={emptyLabel}
|
||||
selected={isUndefined(selectedRecordId)}
|
||||
hovered={isSelectedSelectNoneButton}
|
||||
/>
|
||||
>
|
||||
<MenuItemSelect
|
||||
onClick={() => {
|
||||
setSelectedRecordId(undefined);
|
||||
onRecordSelected();
|
||||
}}
|
||||
LeftIcon={EmptyIcon}
|
||||
text={emptyLabel}
|
||||
selected={isUndefined(selectedRecordId)}
|
||||
focused={isSelectedSelectNoneButton}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,15 +2,15 @@ import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
|
||||
import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope';
|
||||
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||
|
||||
export const useTriggerActionMenuDropdown = ({
|
||||
recordTableId,
|
||||
}: {
|
||||
@ -25,18 +25,17 @@ export const useTriggerActionMenuDropdown = ({
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const actionMenuDropdownId =
|
||||
getActionMenuDropdownIdFromActionMenuId(actionMenuInstanceId);
|
||||
|
||||
const recordIndexActionMenuDropdownPositionState = extractComponentState(
|
||||
recordIndexActionMenuDropdownPositionComponentState,
|
||||
getActionMenuDropdownIdFromActionMenuId(actionMenuInstanceId),
|
||||
actionMenuDropdownId,
|
||||
);
|
||||
|
||||
const isActionMenuDropdownOpenState = extractComponentState(
|
||||
isDropdownOpenComponentState,
|
||||
getActionMenuDropdownIdFromActionMenuId(actionMenuInstanceId),
|
||||
);
|
||||
const { openDropdown } = useDropdown(actionMenuDropdownId);
|
||||
|
||||
const { setActiveDropdownFocusIdAndMemorizePrevious } =
|
||||
useSetActiveDropdownFocusIdAndMemorizePrevious();
|
||||
const { closeCommandMenu } = useCommandMenu();
|
||||
|
||||
const triggerActionMenuDropdown = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
@ -57,19 +56,17 @@ export const useTriggerActionMenuDropdown = ({
|
||||
set(isRowSelectedFamilyState(recordId), true);
|
||||
}
|
||||
|
||||
set(isActionMenuDropdownOpenState, true);
|
||||
closeCommandMenu();
|
||||
|
||||
const actionMenuDropdownId =
|
||||
getActionMenuDropdownIdFromActionMenuId(actionMenuInstanceId);
|
||||
|
||||
setActiveDropdownFocusIdAndMemorizePrevious(actionMenuDropdownId);
|
||||
openDropdown({
|
||||
scope: ActionMenuDropdownHotkeyScope.ActionMenuDropdown,
|
||||
});
|
||||
},
|
||||
[
|
||||
isActionMenuDropdownOpenState,
|
||||
isRowSelectedFamilyState,
|
||||
recordIndexActionMenuDropdownPositionState,
|
||||
setActiveDropdownFocusIdAndMemorizePrevious,
|
||||
actionMenuInstanceId,
|
||||
isRowSelectedFamilyState,
|
||||
closeCommandMenu,
|
||||
openDropdown,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
@ -93,43 +94,38 @@ export const MultipleSelectDropdown = ({
|
||||
selectableListInstanceId={selectableListId}
|
||||
selectableItemIdArray={selectableItemIds}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onEnter={(itemId) => {
|
||||
const item = itemsInDropdown.findIndex(
|
||||
(entity) => entity.id === itemId,
|
||||
);
|
||||
const itemIsSelectedInDropwdown = filteredSelectedItems.find(
|
||||
(entity) => entity.id === itemId,
|
||||
);
|
||||
handleItemSelectChange(
|
||||
itemsInDropdown[item],
|
||||
!itemIsSelectedInDropwdown,
|
||||
);
|
||||
resetSelectedItem();
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{itemsInDropdown?.map((item) => {
|
||||
return (
|
||||
<MenuItemMultiSelectAvatar
|
||||
key={item.id}
|
||||
selected={item.isSelected}
|
||||
isKeySelected={item.id === selectedItemId}
|
||||
onSelectChange={(newCheckedValue) => {
|
||||
<SelectableListItem
|
||||
itemId={item.id}
|
||||
onEnter={() => {
|
||||
resetSelectedItem();
|
||||
handleItemSelectChange(item, newCheckedValue);
|
||||
handleItemSelectChange(item, !item.isSelected);
|
||||
}}
|
||||
avatar={
|
||||
<StyledMultipleSelectDropdownAvatarChip
|
||||
className="avatar-icon-container"
|
||||
name={item.name}
|
||||
avatarUrl={item.avatarUrl}
|
||||
LeftIcon={item.AvatarIcon}
|
||||
avatarType={item.avatarType}
|
||||
isIconInverted={item.isIconInverted}
|
||||
placeholderColorSeed={item.id}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
>
|
||||
<MenuItemMultiSelectAvatar
|
||||
key={item.id}
|
||||
selected={item.isSelected}
|
||||
isKeySelected={item.id === selectedItemId}
|
||||
onSelectChange={(newCheckedValue) => {
|
||||
resetSelectedItem();
|
||||
handleItemSelectChange(item, newCheckedValue);
|
||||
}}
|
||||
avatar={
|
||||
<StyledMultipleSelectDropdownAvatarChip
|
||||
className="avatar-icon-container"
|
||||
name={item.name}
|
||||
avatarUrl={item.avatarUrl}
|
||||
LeftIcon={item.AvatarIcon}
|
||||
avatarType={item.avatarType}
|
||||
isIconInverted={item.isIconInverted}
|
||||
placeholderColorSeed={item.id}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
);
|
||||
})}
|
||||
{showNoResult && <MenuItem text="No results" />}
|
||||
|
||||
Reference in New Issue
Block a user