fix: navigate with arrow keys in select/multi-select (#5983)
closes: #4977 https://github.com/twentyhq/twenty/assets/13139771/8121814c-9a8a-4a8d-9290-1aebe145220f --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -79,7 +79,10 @@ export const ObjectFilterDropdownFilterSelect = () => {
|
||||
onClick={() => {
|
||||
setFilterDefinitionUsedInDropdown(availableFilterDefinition);
|
||||
|
||||
if (availableFilterDefinition.type === 'RELATION') {
|
||||
if (
|
||||
availableFilterDefinition.type === 'RELATION' ||
|
||||
availableFilterDefinition.type === 'SELECT'
|
||||
) {
|
||||
setHotkeyScope(RelationPickerHotkeyScope.RelationPicker);
|
||||
}
|
||||
|
||||
|
||||
@ -1,13 +1,21 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||
import { useOptionsForSelect } from '@/object-record/object-filter-dropdown/hooks/useOptionsForSelect';
|
||||
import { MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID } from '@/object-record/relation-picker/constants/MultiObjectRecordSelectSelectableListId';
|
||||
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||
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 { useSelectableListStates } from '@/ui/layout/selectable-list/hooks/internal/useSelectableListStates';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { MenuItemMultiSelect } from '@/ui/navigation/menu-item/components/MenuItemMultiSelect';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const EMPTY_FILTER_VALUE = '';
|
||||
@ -27,6 +35,17 @@ export const ObjectFilterDropdownOptionSelect = () => {
|
||||
selectFilter,
|
||||
} = useFilterDropdown();
|
||||
|
||||
const { closeDropdown } = useDropdown();
|
||||
|
||||
const { selectedItemIdState } = useSelectableListStates({
|
||||
selectableListScopeId: MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID,
|
||||
});
|
||||
|
||||
const { handleResetSelectedPosition } = useSelectableList(
|
||||
MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID,
|
||||
);
|
||||
|
||||
const selectedItemId = useRecoilValue(selectedItemIdState);
|
||||
const filterDefinitionUsedInDropdown = useRecoilValue(
|
||||
filterDefinitionUsedInDropdownState,
|
||||
);
|
||||
@ -67,6 +86,16 @@ export const ObjectFilterDropdownOptionSelect = () => {
|
||||
}
|
||||
}, [objectFilterDropdownSelectedOptionValues, selectOptions]);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
closeDropdown();
|
||||
handleResetSelectedPosition();
|
||||
},
|
||||
RelationPickerHotkeyScope.RelationPicker,
|
||||
[closeDropdown, handleResetSelectedPosition],
|
||||
);
|
||||
|
||||
const handleMultipleOptionSelectChange = (
|
||||
optionChanged: SelectOptionForFilter,
|
||||
isSelected: boolean,
|
||||
@ -108,6 +137,7 @@ export const ObjectFilterDropdownOptionSelect = () => {
|
||||
value: newFilterValue,
|
||||
});
|
||||
}
|
||||
handleResetSelectedPosition();
|
||||
};
|
||||
|
||||
const optionsInDropdown = selectableOptions?.filter((option) =>
|
||||
@ -117,22 +147,36 @@ export const ObjectFilterDropdownOptionSelect = () => {
|
||||
);
|
||||
|
||||
const showNoResult = optionsInDropdown?.length === 0;
|
||||
const objectRecordsIds = optionsInDropdown.map((option) => option.id);
|
||||
|
||||
return (
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{optionsInDropdown?.map((option) => (
|
||||
<MenuItemMultiSelect
|
||||
key={option.id}
|
||||
selected={option.isSelected}
|
||||
onSelectChange={(selected) =>
|
||||
handleMultipleOptionSelectChange(option, selected)
|
||||
}
|
||||
text={option.label}
|
||||
color={option.color}
|
||||
className=""
|
||||
/>
|
||||
))}
|
||||
<SelectableList
|
||||
selectableListId={MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID}
|
||||
selectableItemIdArray={objectRecordsIds}
|
||||
hotkeyScope={RelationPickerHotkeyScope.RelationPicker}
|
||||
onEnter={(itemId) => {
|
||||
const option = optionsInDropdown.find((option) => option.id === itemId);
|
||||
if (isDefined(option)) {
|
||||
handleMultipleOptionSelectChange(option, !option.isSelected);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{optionsInDropdown?.map((option) => (
|
||||
<MenuItemMultiSelect
|
||||
key={option.id}
|
||||
selected={option.isSelected}
|
||||
isKeySelected={option.id === selectedItemId}
|
||||
onSelectChange={(selected) =>
|
||||
handleMultipleOptionSelectChange(option, selected)
|
||||
}
|
||||
text={option.label}
|
||||
color={option.color}
|
||||
className=""
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
{showNoResult && <MenuItem text="No result" />}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import { useRecoilValue } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||
import { MultipleRecordSelectDropdown } from '@/object-record/select/components/MultipleRecordSelectDropdown';
|
||||
import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect';
|
||||
import { SelectableRecord } from '@/object-record/select/types/SelectableRecord';
|
||||
@ -131,6 +132,8 @@ export const ObjectFilterDropdownRecordSelect = ({
|
||||
|
||||
return (
|
||||
<MultipleRecordSelectDropdown
|
||||
selectableListId="object-filter-record-select-id"
|
||||
hotkeyScope={RelationPickerHotkeyScope.RelationPicker}
|
||||
recordsToSelect={recordsToSelect}
|
||||
filteredSelectedRecords={filteredSelectedRecords}
|
||||
selectedRecords={selectedRecords}
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { useMultiSelectField } from '@/object-record/record-field/meta-types/hooks/useMultiSelectField';
|
||||
import { MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID } from '@/object-record/relation-picker/constants/MultiObjectRecordSelectSelectableListId';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { useSelectableListStates } from '@/ui/layout/selectable-list/hooks/internal/useSelectableListStates';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { MenuItemMultiSelectTag } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectTag';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
@ -23,7 +30,15 @@ export type MultiSelectFieldInputProps = {
|
||||
export const MultiSelectFieldInput = ({
|
||||
onCancel,
|
||||
}: MultiSelectFieldInputProps) => {
|
||||
const { persistField, fieldDefinition, fieldValues } = useMultiSelectField();
|
||||
const { selectedItemIdState } = useSelectableListStates({
|
||||
selectableListScopeId: MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID,
|
||||
});
|
||||
const { handleResetSelectedPosition } = useSelectableList(
|
||||
MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID,
|
||||
);
|
||||
const { persistField, fieldDefinition, fieldValues, hotkeyScope } =
|
||||
useMultiSelectField();
|
||||
const selectedItemId = useRecoilValue(selectedItemIdState);
|
||||
const [searchFilter, setSearchFilter] = useState('');
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@ -46,6 +61,16 @@ export const MultiSelectFieldInput = ({
|
||||
}
|
||||
};
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Escape,
|
||||
() => {
|
||||
onCancel?.();
|
||||
handleResetSelectedPosition();
|
||||
},
|
||||
hotkeyScope,
|
||||
[onCancel, handleResetSelectedPosition],
|
||||
);
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [containerRef],
|
||||
callback: (event) => {
|
||||
@ -58,34 +83,52 @@ export const MultiSelectFieldInput = ({
|
||||
if (weAreNotInAnHTMLInput && isDefined(onCancel)) {
|
||||
onCancel();
|
||||
}
|
||||
handleResetSelectedPosition();
|
||||
},
|
||||
});
|
||||
|
||||
const optionIds = optionsInDropDown.map((option) => option.value);
|
||||
|
||||
return (
|
||||
<StyledRelationPickerContainer ref={containerRef}>
|
||||
<DropdownMenu data-select-disable>
|
||||
<DropdownMenuSearchInput
|
||||
value={searchFilter}
|
||||
onChange={(event) => setSearchFilter(event.currentTarget.value)}
|
||||
autoFocus
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{optionsInDropDown.map((option) => {
|
||||
return (
|
||||
<MenuItemMultiSelectTag
|
||||
key={option.value}
|
||||
selected={fieldValues?.includes(option.value) || false}
|
||||
text={option.label}
|
||||
color={option.color}
|
||||
onClick={() =>
|
||||
persistField(formatNewSelectedOptions(option.value))
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
</StyledRelationPickerContainer>
|
||||
<SelectableList
|
||||
selectableListId={MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID}
|
||||
selectableItemIdArray={optionIds}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onEnter={(itemId) => {
|
||||
const option = optionsInDropDown.find(
|
||||
(option) => option.value === itemId,
|
||||
);
|
||||
if (isDefined(option)) {
|
||||
persistField(formatNewSelectedOptions(option.value));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<StyledRelationPickerContainer ref={containerRef}>
|
||||
<DropdownMenu data-select-disable>
|
||||
<DropdownMenuSearchInput
|
||||
value={searchFilter}
|
||||
onChange={(event) => setSearchFilter(event.currentTarget.value)}
|
||||
autoFocus
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{optionsInDropDown.map((option) => {
|
||||
return (
|
||||
<MenuItemMultiSelectTag
|
||||
key={option.value}
|
||||
selected={fieldValues?.includes(option.value) || false}
|
||||
text={option.label}
|
||||
color={option.color}
|
||||
onClick={() =>
|
||||
persistField(formatNewSelectedOptions(option.value))
|
||||
}
|
||||
isKeySelected={selectedItemId === option.value}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
</StyledRelationPickerContainer>
|
||||
</SelectableList>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { useClearField } from '@/object-record/record-field/hooks/useClearField';
|
||||
import { useSelectField } from '@/object-record/record-field/meta-types/hooks/useSelectField';
|
||||
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
||||
import { SINGLE_ENTITY_SELECT_BASE_LIST } from '@/object-record/relation-picker/constants/SingleEntitySelectBaseList';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { useSelectableListStates } from '@/ui/layout/selectable-list/hooks/internal/useSelectableListStates';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { MenuItemSelectTag } from '@/ui/navigation/menu-item/components/MenuItemSelectTag';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
@ -31,8 +36,15 @@ export const SelectFieldInput = ({
|
||||
}: SelectFieldInputProps) => {
|
||||
const { persistField, fieldDefinition, fieldValue, hotkeyScope } =
|
||||
useSelectField();
|
||||
const { selectedItemIdState } = useSelectableListStates({
|
||||
selectableListScopeId: SINGLE_ENTITY_SELECT_BASE_LIST,
|
||||
});
|
||||
const { handleResetSelectedPosition } = useSelectableList(
|
||||
SINGLE_ENTITY_SELECT_BASE_LIST,
|
||||
);
|
||||
const clearField = useClearField();
|
||||
|
||||
const selectedItemId = useRecoilValue(selectedItemIdState);
|
||||
const [searchFilter, setSearchFilter] = useState('');
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@ -69,10 +81,21 @@ export const SelectFieldInput = ({
|
||||
);
|
||||
if (weAreNotInAnHTMLInput && isDefined(onCancel)) {
|
||||
onCancel();
|
||||
handleResetSelectedPosition();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Escape,
|
||||
() => {
|
||||
onCancel?.();
|
||||
handleResetSelectedPosition();
|
||||
},
|
||||
hotkeyScope,
|
||||
[onCancel, handleResetSelectedPosition],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
() => {
|
||||
@ -83,45 +106,71 @@ export const SelectFieldInput = ({
|
||||
if (isDefined(selectedOption)) {
|
||||
onSubmit?.(() => persistField(selectedOption.value));
|
||||
}
|
||||
handleResetSelectedPosition();
|
||||
},
|
||||
hotkeyScope,
|
||||
);
|
||||
|
||||
const optionIds = [
|
||||
`No ${fieldDefinition.label}`,
|
||||
...optionsInDropDown.map((option) => option.value),
|
||||
];
|
||||
|
||||
return (
|
||||
<StyledRelationPickerContainer ref={containerRef}>
|
||||
<DropdownMenu data-select-disable>
|
||||
<DropdownMenuSearchInput
|
||||
value={searchFilter}
|
||||
onChange={(event) => setSearchFilter(event.currentTarget.value)}
|
||||
autoFocus
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<SelectableList
|
||||
selectableListId={SINGLE_ENTITY_SELECT_BASE_LIST}
|
||||
selectableItemIdArray={optionIds}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onEnter={(itemId) => {
|
||||
const option = optionsInDropDown.find(
|
||||
(option) => option.value === itemId,
|
||||
);
|
||||
if (isDefined(option)) {
|
||||
onSubmit?.(() => persistField(option.value));
|
||||
handleResetSelectedPosition();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<StyledRelationPickerContainer ref={containerRef}>
|
||||
<DropdownMenu data-select-disable>
|
||||
<DropdownMenuSearchInput
|
||||
value={searchFilter}
|
||||
onChange={(event) => setSearchFilter(event.currentTarget.value)}
|
||||
autoFocus
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{fieldDefinition.metadata.isNullable && (
|
||||
<MenuItemSelectTag
|
||||
key={`No ${fieldDefinition.label}`}
|
||||
selected={false}
|
||||
text={`No ${fieldDefinition.label}`}
|
||||
color="transparent"
|
||||
variant="outline"
|
||||
onClick={handleClearField}
|
||||
/>
|
||||
)}
|
||||
|
||||
{optionsInDropDown.map((option) => {
|
||||
return (
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{fieldDefinition.metadata.isNullable ?? (
|
||||
<MenuItemSelectTag
|
||||
key={option.value}
|
||||
selected={option.value === fieldValue}
|
||||
text={option.label}
|
||||
color={option.color}
|
||||
onClick={() => onSubmit?.(() => persistField(option.value))}
|
||||
key={`No ${fieldDefinition.label}`}
|
||||
selected={false}
|
||||
text={`No ${fieldDefinition.label}`}
|
||||
color="transparent"
|
||||
variant="outline"
|
||||
onClick={handleClearField}
|
||||
isKeySelected={selectedItemId === `No ${fieldDefinition.label}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
</StyledRelationPickerContainer>
|
||||
)}
|
||||
|
||||
{optionsInDropDown.map((option) => {
|
||||
return (
|
||||
<MenuItemSelectTag
|
||||
key={option.value}
|
||||
selected={option.value === fieldValue}
|
||||
text={option.label}
|
||||
color={option.color}
|
||||
onClick={() => {
|
||||
onSubmit?.(() => persistField(option.value));
|
||||
handleResetSelectedPosition();
|
||||
}}
|
||||
isKeySelected={selectedItemId === option.value}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
</StyledRelationPickerContainer>
|
||||
</SelectableList>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,15 +1,9 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates';
|
||||
import { MultipleObjectRecordOnClickOutsideEffect } from '@/object-record/relation-picker/components/MultipleObjectRecordOnClickOutsideEffect';
|
||||
import { MultipleObjectRecordSelectItem } from '@/object-record/relation-picker/components/MultipleObjectRecordSelectItem';
|
||||
import { MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID } from '@/object-record/relation-picker/constants/MultiObjectRecordSelectSelectableListId';
|
||||
import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates';
|
||||
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext';
|
||||
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||
import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
@ -17,10 +11,18 @@ import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/Dropdow
|
||||
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 { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
import styled from '@emotion/styled';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { IconPlus, isDefined } from 'twenty-ui';
|
||||
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
export const StyledSelectableItem = styled(SelectableItem)`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
@ -35,6 +37,8 @@ export const MultiRecordSelect = ({
|
||||
onCreate?: ((searchInput?: string) => void) | (() => void);
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
|
||||
|
||||
const relationPickerScopedId = useAvailableScopeIdOrThrow(
|
||||
RelationPickerScopeInternalContext,
|
||||
@ -43,6 +47,9 @@ export const MultiRecordSelect = ({
|
||||
const { objectRecordsIdsMultiSelectState, recordMultiSelectIsLoadingState } =
|
||||
useObjectRecordMultiSelectScopedStates(relationPickerScopedId);
|
||||
|
||||
const { handleResetSelectedPosition } = useSelectableList(
|
||||
MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID,
|
||||
);
|
||||
const recordMultiSelectIsLoading = useRecoilValue(
|
||||
recordMultiSelectIsLoadingState,
|
||||
);
|
||||
@ -62,6 +69,21 @@ export const MultiRecordSelect = ({
|
||||
leading: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setHotkeyScope(relationPickerScopedId);
|
||||
}, [setHotkeyScope, relationPickerScopedId]);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Escape,
|
||||
() => {
|
||||
onSubmit?.();
|
||||
goBackToPreviousHotkeyScope();
|
||||
handleResetSelectedPosition();
|
||||
},
|
||||
relationPickerScopedId,
|
||||
[onSubmit, goBackToPreviousHotkeyScope, handleResetSelectedPosition],
|
||||
);
|
||||
|
||||
const debouncedOnCreate = useDebouncedCallback(
|
||||
() => onCreate?.(relationPickerSearchFilter),
|
||||
500,
|
||||
@ -97,14 +119,21 @@ export const MultiRecordSelect = ({
|
||||
<SelectableList
|
||||
selectableListId={MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID}
|
||||
selectableItemIdArray={objectRecordsIdsMultiSelect}
|
||||
hotkeyScope={RelationPickerHotkeyScope.RelationPicker}
|
||||
hotkeyScope={relationPickerScopedId}
|
||||
onEnter={(selectedId) => {
|
||||
onChange?.(selectedId);
|
||||
handleResetSelectedPosition();
|
||||
}}
|
||||
>
|
||||
{objectRecordsIdsMultiSelect?.map((recordId) => {
|
||||
return (
|
||||
<MultipleObjectRecordSelectItem
|
||||
key={recordId}
|
||||
objectRecordId={recordId}
|
||||
onChange={onChange}
|
||||
onChange={(recordId) => {
|
||||
onChange?.(recordId);
|
||||
handleResetSelectedPosition();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -35,13 +35,19 @@ export const MultipleObjectRecordSelectItem = ({
|
||||
RelationPickerScopeInternalContext,
|
||||
);
|
||||
|
||||
const { objectRecordMultiSelectFamilyState } =
|
||||
useObjectRecordMultiSelectScopedStates(scopeId);
|
||||
const {
|
||||
objectRecordMultiSelectFamilyState,
|
||||
objectRecordMultiSelectCheckedRecordsIdsState,
|
||||
} = useObjectRecordMultiSelectScopedStates(scopeId);
|
||||
|
||||
const record = useRecoilValue(
|
||||
objectRecordMultiSelectFamilyState(objectRecordId),
|
||||
);
|
||||
|
||||
const objectRecordMultiSelectCheckedRecordsIds = useRecoilValue(
|
||||
objectRecordMultiSelectCheckedRecordsIdsState,
|
||||
);
|
||||
|
||||
if (!record) {
|
||||
return null;
|
||||
}
|
||||
@ -50,12 +56,18 @@ export const MultipleObjectRecordSelectItem = ({
|
||||
onChange?.(objectRecordId);
|
||||
};
|
||||
|
||||
const { selected, recordIdentifier } = record;
|
||||
const { recordIdentifier } = record;
|
||||
|
||||
if (!isDefined(recordIdentifier)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const selected = objectRecordMultiSelectCheckedRecordsIds.find(
|
||||
(checkedObjectRecord) => checkedObjectRecord === objectRecordId,
|
||||
)
|
||||
? true
|
||||
: false;
|
||||
|
||||
return (
|
||||
<StyledSelectableItem itemId={objectRecordId} key={objectRecordId + v4()}>
|
||||
<MenuItemMultiSelectAvatar
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
import { useRef } from 'react';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { IconComponent, IconPlus } from 'twenty-ui';
|
||||
|
||||
import { SelectableMenuItemSelect } from '@/object-record/relation-picker/components/SelectableMenuItemSelect';
|
||||
import { SINGLE_ENTITY_SELECT_BASE_LIST } from '@/object-record/relation-picker/constants/SingleEntitySelectBaseList';
|
||||
import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
|
||||
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
|
||||
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 { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { MenuItemSelect } from '@/ui/navigation/menu-item/components/MenuItemSelect';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
@ -32,6 +35,7 @@ export type SingleEntitySelectMenuItemsProps = {
|
||||
isAllEntitySelected?: boolean;
|
||||
isAllEntitySelectShown?: boolean;
|
||||
onAllEntitySelected?: () => void;
|
||||
hotkeyScope?: string;
|
||||
};
|
||||
|
||||
export const SingleEntitySelectMenuItems = ({
|
||||
@ -49,21 +53,68 @@ export const SingleEntitySelectMenuItems = ({
|
||||
isAllEntitySelected,
|
||||
isAllEntitySelectShown,
|
||||
onAllEntitySelected,
|
||||
hotkeyScope = RelationPickerHotkeyScope.RelationPicker,
|
||||
}: SingleEntitySelectMenuItemsProps) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const entitiesInDropdown = [selectedEntity, ...entitiesToSelect].filter(
|
||||
const createNewRecord = showCreateButton
|
||||
? {
|
||||
__typename: '',
|
||||
id: 'add-new',
|
||||
name: 'Add New',
|
||||
}
|
||||
: null;
|
||||
|
||||
const selectNone = emptyLabel
|
||||
? {
|
||||
__typename: '',
|
||||
id: 'select-none',
|
||||
name: emptyLabel,
|
||||
}
|
||||
: null;
|
||||
|
||||
const selectAll = isAllEntitySelectShown
|
||||
? {
|
||||
__typename: '',
|
||||
id: 'select-all',
|
||||
name: selectAllLabel,
|
||||
}
|
||||
: null;
|
||||
|
||||
const entitiesInDropdown = [
|
||||
selectAll,
|
||||
selectNone,
|
||||
selectedEntity,
|
||||
...entitiesToSelect,
|
||||
createNewRecord,
|
||||
].filter(
|
||||
(entity): entity is EntityForSelect =>
|
||||
isDefined(entity) && isNonEmptyString(entity.name),
|
||||
);
|
||||
|
||||
const { isSelectedItemIdSelector, handleResetSelectedPosition } =
|
||||
useSelectableList(SINGLE_ENTITY_SELECT_BASE_LIST);
|
||||
|
||||
const isSelectedAddNewButton = useRecoilValue(
|
||||
isSelectedItemIdSelector('add-new'),
|
||||
);
|
||||
|
||||
const isSelectedSelectNoneButton = useRecoilValue(
|
||||
isSelectedItemIdSelector('select-none'),
|
||||
);
|
||||
|
||||
const isSelectedSelectAllButton = useRecoilValue(
|
||||
isSelectedItemIdSelector('select-all'),
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
handleResetSelectedPosition();
|
||||
onCancel?.();
|
||||
},
|
||||
RelationPickerHotkeyScope.RelationPicker,
|
||||
[onCancel],
|
||||
hotkeyScope,
|
||||
[onCancel, handleResetSelectedPosition],
|
||||
);
|
||||
|
||||
const selectableItemIds = entitiesInDropdown.map((entity) => entity.id);
|
||||
@ -71,66 +122,95 @@ export const SingleEntitySelectMenuItems = ({
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
<SelectableList
|
||||
selectableListId="single-entity-select-base-list"
|
||||
selectableListId={SINGLE_ENTITY_SELECT_BASE_LIST}
|
||||
selectableItemIdArray={selectableItemIds}
|
||||
hotkeyScope={RelationPickerHotkeyScope.RelationPicker}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onEnter={(itemId) => {
|
||||
if (showCreateButton === true) {
|
||||
if (itemId === 'add-new' && showCreateButton === true) {
|
||||
onCreate?.();
|
||||
} else {
|
||||
const entity = entitiesInDropdown.findIndex(
|
||||
const entityIndex = entitiesInDropdown.findIndex(
|
||||
(entity) => entity.id === itemId,
|
||||
);
|
||||
onEntitySelected(entitiesInDropdown[entity]);
|
||||
onEntitySelected(entitiesInDropdown[entityIndex]);
|
||||
}
|
||||
handleResetSelectedPosition();
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{loading ? (
|
||||
<DropdownMenuSkeletonItem />
|
||||
) : entitiesInDropdown.length === 0 && !isAllEntitySelectShown ? (
|
||||
<MenuItem text="No result" />
|
||||
) : (
|
||||
<>
|
||||
{isAllEntitySelectShown &&
|
||||
selectAllLabel &&
|
||||
onAllEntitySelected && (
|
||||
<MenuItemSelect
|
||||
key="select-all"
|
||||
onClick={() => onAllEntitySelected()}
|
||||
LeftIcon={SelectAllIcon}
|
||||
text={selectAllLabel}
|
||||
selected={!!isAllEntitySelected}
|
||||
/>
|
||||
)}
|
||||
{emptyLabel && (
|
||||
<MenuItemSelect
|
||||
key="select-none"
|
||||
onClick={() => onEntitySelected()}
|
||||
LeftIcon={EmptyIcon}
|
||||
text={emptyLabel}
|
||||
selected={!selectedEntity}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{entitiesInDropdown?.map((entity) => (
|
||||
<SelectableMenuItemSelect
|
||||
key={entity.id}
|
||||
entity={entity}
|
||||
onEntitySelected={onEntitySelected}
|
||||
selectedEntity={selectedEntity}
|
||||
/>
|
||||
))}
|
||||
{showCreateButton && (
|
||||
<>
|
||||
<MenuItem text="No result" />
|
||||
{entitiesToSelect.length > 0 && <DropdownMenuSeparator />}
|
||||
<CreateNewButton
|
||||
key="add-new"
|
||||
onClick={onCreate}
|
||||
LeftIcon={IconPlus}
|
||||
text="Add New"
|
||||
hovered={isSelectedAddNewButton}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
entitiesInDropdown?.map((entity) => {
|
||||
switch (entity.id) {
|
||||
case 'add-new': {
|
||||
return (
|
||||
<>
|
||||
{entitiesToSelect.length > 0 && <DropdownMenuSeparator />}
|
||||
<CreateNewButton
|
||||
key={entity.id}
|
||||
onClick={onCreate}
|
||||
LeftIcon={IconPlus}
|
||||
text="Add New"
|
||||
hovered={isSelectedAddNewButton}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case 'select-none': {
|
||||
return (
|
||||
emptyLabel && (
|
||||
<MenuItemSelect
|
||||
key={entity.id}
|
||||
onClick={() => onEntitySelected()}
|
||||
LeftIcon={EmptyIcon}
|
||||
text={emptyLabel}
|
||||
selected={!selectedEntity}
|
||||
hovered={isSelectedSelectNoneButton}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
case 'select-all': {
|
||||
return (
|
||||
isAllEntitySelectShown &&
|
||||
selectAllLabel &&
|
||||
onAllEntitySelected && (
|
||||
<MenuItemSelect
|
||||
key={entity.id}
|
||||
onClick={() => onAllEntitySelected()}
|
||||
LeftIcon={SelectAllIcon}
|
||||
text={selectAllLabel}
|
||||
selected={!!isAllEntitySelected}
|
||||
hovered={isSelectedSelectAllButton}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
<SelectableMenuItemSelect
|
||||
key={entity.id}
|
||||
entity={entity}
|
||||
onEntitySelected={onEntitySelected}
|
||||
selectedEntity={selectedEntity}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
|
||||
@ -79,6 +79,7 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
|
||||
? entities.selectedEntities[0]
|
||||
: undefined)
|
||||
}
|
||||
hotkeyScope={relationPickerScopeId}
|
||||
onCreate={onCreateWithInput}
|
||||
{...{
|
||||
EmptyIcon,
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export const SINGLE_ENTITY_SELECT_BASE_LIST = 'single-entity-select-base-list';
|
||||
@ -1,19 +1,30 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { Avatar } from 'twenty-ui';
|
||||
|
||||
import { SelectableRecord } from '@/object-record/select/types/SelectableRecord';
|
||||
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
|
||||
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 { useSelectableListStates } from '@/ui/layout/selectable-list/hooks/internal/useSelectableListStates';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { MenuItemMultiSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
|
||||
export const MultipleRecordSelectDropdown = ({
|
||||
selectableListId,
|
||||
hotkeyScope,
|
||||
recordsToSelect,
|
||||
loadingRecords,
|
||||
filteredSelectedRecords,
|
||||
onChange,
|
||||
searchFilter,
|
||||
}: {
|
||||
selectableListId: string;
|
||||
hotkeyScope: string;
|
||||
recordsToSelect: SelectableRecord[];
|
||||
filteredSelectedRecords: SelectableRecord[];
|
||||
selectedRecords: SelectableRecord[];
|
||||
@ -24,6 +35,15 @@ export const MultipleRecordSelectDropdown = ({
|
||||
) => void;
|
||||
loadingRecords: boolean;
|
||||
}) => {
|
||||
const { closeDropdown } = useDropdown();
|
||||
const { selectedItemIdState } = useSelectableListStates({
|
||||
selectableListScopeId: selectableListId,
|
||||
});
|
||||
|
||||
const { handleResetSelectedPosition } = useSelectableList(selectableListId);
|
||||
|
||||
const selectedItemId = useRecoilValue(selectedItemIdState);
|
||||
|
||||
const handleRecordSelectChange = (
|
||||
recordToSelect: SelectableRecord,
|
||||
newSelectedValue: boolean,
|
||||
@ -51,35 +71,70 @@ export const MultipleRecordSelectDropdown = ({
|
||||
}
|
||||
}, [recordsToSelect, filteredSelectedRecords, loadingRecords]);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
closeDropdown();
|
||||
handleResetSelectedPosition();
|
||||
},
|
||||
hotkeyScope,
|
||||
[closeDropdown, handleResetSelectedPosition],
|
||||
);
|
||||
|
||||
const showNoResult =
|
||||
recordsToSelect?.length === 0 &&
|
||||
searchFilter !== '' &&
|
||||
filteredSelectedRecords?.length === 0 &&
|
||||
!loadingRecords;
|
||||
|
||||
const selectableItemIds = recordsInDropdown.map((record) => record.id);
|
||||
|
||||
return (
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{recordsInDropdown?.map((record) => (
|
||||
<MenuItemMultiSelectAvatar
|
||||
key={record.id}
|
||||
selected={record.isSelected}
|
||||
onSelectChange={(newCheckedValue) =>
|
||||
handleRecordSelectChange(record, newCheckedValue)
|
||||
}
|
||||
avatar={
|
||||
<Avatar
|
||||
avatarUrl={record.avatarUrl}
|
||||
placeholderColorSeed={record.id}
|
||||
placeholder={record.name}
|
||||
size="md"
|
||||
type={record.avatarType ?? 'rounded'}
|
||||
<SelectableList
|
||||
selectableListId={selectableListId}
|
||||
selectableItemIdArray={selectableItemIds}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onEnter={(itemId) => {
|
||||
const record = recordsInDropdown.findIndex(
|
||||
(entity) => entity.id === itemId,
|
||||
);
|
||||
const recordIsSelectedInDropwdown = filteredSelectedRecords.find(
|
||||
(entity) => entity.id === itemId,
|
||||
);
|
||||
handleRecordSelectChange(
|
||||
recordsInDropdown[record],
|
||||
!recordIsSelectedInDropwdown,
|
||||
);
|
||||
handleResetSelectedPosition();
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{recordsInDropdown?.map((record) => {
|
||||
return (
|
||||
<MenuItemMultiSelectAvatar
|
||||
key={record.id}
|
||||
selected={record.isSelected}
|
||||
isKeySelected={record.id === selectedItemId}
|
||||
onSelectChange={(newCheckedValue) => {
|
||||
handleResetSelectedPosition();
|
||||
handleRecordSelectChange(record, newCheckedValue);
|
||||
}}
|
||||
avatar={
|
||||
<Avatar
|
||||
avatarUrl={record.avatarUrl}
|
||||
placeholderColorSeed={record.id}
|
||||
placeholder={record.name}
|
||||
size="md"
|
||||
type={record.avatarType ?? 'rounded'}
|
||||
/>
|
||||
}
|
||||
text={record.name}
|
||||
/>
|
||||
}
|
||||
text={record.name}
|
||||
/>
|
||||
))}
|
||||
{showNoResult && <MenuItem text="No result" />}
|
||||
{loadingRecords && <DropdownMenuSkeletonItem />}
|
||||
</DropdownMenuItemsContainer>
|
||||
);
|
||||
})}
|
||||
{showNoResult && <MenuItem text="No result" />}
|
||||
{loadingRecords && <DropdownMenuSkeletonItem />}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
);
|
||||
};
|
||||
|
||||
@ -10,7 +10,9 @@ import { useRef } from 'react';
|
||||
import { Keys } from 'react-hotkeys-hook';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { SINGLE_ENTITY_SELECT_BASE_LIST } from '@/object-record/relation-picker/constants/SingleEntitySelectBaseList';
|
||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { HotkeyEffect } from '@/ui/utilities/hotkey/components/HotkeyEffect';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
@ -66,6 +68,10 @@ export const Dropdown = ({
|
||||
|
||||
const { isDropdownOpen, toggleDropdown, closeDropdown, dropdownWidth } =
|
||||
useDropdown(dropdownId);
|
||||
|
||||
const { handleResetSelectedPosition } = useSelectableList(
|
||||
SINGLE_ENTITY_SELECT_BASE_LIST,
|
||||
);
|
||||
const offsetMiddlewares = [];
|
||||
|
||||
if (isDefined(dropdownOffset.x)) {
|
||||
@ -94,6 +100,7 @@ export const Dropdown = ({
|
||||
|
||||
if (isDropdownOpen) {
|
||||
closeDropdown();
|
||||
handleResetSelectedPosition();
|
||||
}
|
||||
},
|
||||
});
|
||||
@ -107,9 +114,10 @@ export const Dropdown = ({
|
||||
[Key.Escape],
|
||||
() => {
|
||||
closeDropdown();
|
||||
handleResetSelectedPosition();
|
||||
},
|
||||
dropdownHotkeyScope.scope,
|
||||
[closeDropdown],
|
||||
[closeDropdown, handleResetSelectedPosition],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
import { useResetRecoilState, useSetRecoilState } from 'recoil';
|
||||
import {
|
||||
useRecoilCallback,
|
||||
useResetRecoilState,
|
||||
useSetRecoilState,
|
||||
} from 'recoil';
|
||||
|
||||
import { useSelectableListStates } from '@/ui/layout/selectable-list/hooks/internal/useSelectableListStates';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useSelectableList = (selectableListId?: string) => {
|
||||
const {
|
||||
@ -24,6 +30,18 @@ export const useSelectableList = (selectableListId?: string) => {
|
||||
resetSelectedItemIdState();
|
||||
};
|
||||
|
||||
const handleResetSelectedPosition = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
() => {
|
||||
const selectedItemId = getSnapshotValue(snapshot, selectedItemIdState);
|
||||
if (isDefined(selectedItemId)) {
|
||||
set(selectedItemIdState, null);
|
||||
set(isSelectedItemIdSelector(selectedItemId), false);
|
||||
}
|
||||
},
|
||||
[selectedItemIdState, isSelectedItemIdSelector],
|
||||
);
|
||||
|
||||
return {
|
||||
selectableListId: scopeId,
|
||||
|
||||
@ -31,5 +49,6 @@ export const useSelectableList = (selectableListId?: string) => {
|
||||
isSelectedItemIdSelector,
|
||||
setSelectableListOnEnter,
|
||||
resetSelectedItem,
|
||||
handleResetSelectedPosition,
|
||||
};
|
||||
};
|
||||
|
||||
@ -17,6 +17,7 @@ type MenuItemMultiSelectProps = {
|
||||
color?: ThemeColor;
|
||||
LeftIcon?: IconComponent;
|
||||
selected: boolean;
|
||||
isKeySelected?: boolean;
|
||||
text: string;
|
||||
className: string;
|
||||
onSelectChange?: (selected: boolean) => void;
|
||||
@ -27,6 +28,7 @@ export const MenuItemMultiSelect = ({
|
||||
LeftIcon,
|
||||
text,
|
||||
selected,
|
||||
isKeySelected,
|
||||
className,
|
||||
onSelectChange,
|
||||
}: MenuItemMultiSelectProps) => {
|
||||
@ -35,7 +37,11 @@ export const MenuItemMultiSelect = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledMenuItemBase className={className} onClick={handleOnClick}>
|
||||
<StyledMenuItemBase
|
||||
isKeySelected={isKeySelected}
|
||||
className={className}
|
||||
onClick={handleOnClick}
|
||||
>
|
||||
<StyledLeftContentWithCheckboxContainer>
|
||||
<Checkbox checked={selected} />
|
||||
{color ? (
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
type MenuItemMultiSelectTagProps = {
|
||||
selected: boolean;
|
||||
className?: string;
|
||||
isKeySelected?: boolean;
|
||||
onClick?: () => void;
|
||||
color: ThemeColor;
|
||||
text: string;
|
||||
@ -24,10 +25,15 @@ export const MenuItemMultiSelectTag = ({
|
||||
selected,
|
||||
className,
|
||||
onClick,
|
||||
isKeySelected,
|
||||
text,
|
||||
}: MenuItemMultiSelectTagProps) => {
|
||||
return (
|
||||
<StyledMenuItemBase onClick={onClick} className={className}>
|
||||
<StyledMenuItemBase
|
||||
isKeySelected={isKeySelected}
|
||||
onClick={onClick}
|
||||
className={className}
|
||||
>
|
||||
<Checkbox
|
||||
size={CheckboxSize.Small}
|
||||
shape={CheckboxShape.Squared}
|
||||
|
||||
@ -7,6 +7,7 @@ import { StyledMenuItemSelect } from './MenuItemSelect';
|
||||
|
||||
type MenuItemSelectTagProps = {
|
||||
selected: boolean;
|
||||
isKeySelected?: boolean;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
color: ThemeColor | 'transparent';
|
||||
@ -17,6 +18,7 @@ type MenuItemSelectTagProps = {
|
||||
export const MenuItemSelectTag = ({
|
||||
color,
|
||||
selected,
|
||||
isKeySelected,
|
||||
className,
|
||||
onClick,
|
||||
text,
|
||||
@ -29,6 +31,7 @@ export const MenuItemSelectTag = ({
|
||||
onClick={onClick}
|
||||
className={className}
|
||||
selected={selected}
|
||||
isKeySelected={isKeySelected}
|
||||
>
|
||||
<StyledMenuItemLeftContent>
|
||||
<Tag variant={variant} color={color} text={text} />
|
||||
|
||||
@ -32,6 +32,9 @@ export const StyledMenuItemBase = styled.div<MenuItemBaseProps>`
|
||||
|
||||
padding: var(--vertical-padding) var(--horizontal-padding);
|
||||
|
||||
${({ theme, isKeySelected }) =>
|
||||
isKeySelected ? `background: ${theme.background.transparent.light};` : ''}
|
||||
|
||||
${({ isHoverBackgroundDisabled }) =>
|
||||
isHoverBackgroundDisabled ?? HOVER_BACKGROUND};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user