Refactor and fixes dropdown bugs (#8807)
Fixes https://github.com/twentyhq/twenty/issues/8788 Fixes https://github.com/twentyhq/twenty/issues/8793 Fixes https://github.com/twentyhq/twenty/issues/8791 Fixes https://github.com/twentyhq/twenty/issues/8890 Fixes https://github.com/twentyhq/twenty/issues/8893 - [x] Also : Icon buttons under dropdown are visible without blur :  - [x] Also : <img width="237" alt="image" src="https://github.com/user-attachments/assets/e4c70936-beff-4481-89cb-0a32a36e0ee2"> - [x] Also : <img width="335" alt="image" src="https://github.com/user-attachments/assets/5be60395-6baf-49eb-8d40-197add049e20"> - [x] Also : <img width="287" alt="image" src="https://github.com/user-attachments/assets/a317561f-7986-4d70-a1c0-deee4f4e268a"> - Button create new without padding - Container is expanding - [x] Also : <img width="303" alt="image" src="https://github.com/user-attachments/assets/09f8a27f-91db-4191-acdc-aaaeedaf6da5"> - [x] Also : <img width="133" alt="image" src="https://github.com/user-attachments/assets/fe17b32e-f7a4-46c4-8040-239eaf8198e8"> Font is cut at bottom ? - [x] Also : <img width="385" alt="image" src="https://github.com/user-attachments/assets/7bab2092-2936-4112-a2ee-d32d6737e304"> The component should flip and not resize in this situation - [x] Also : <img width="244" alt="image" src="https://github.com/user-attachments/assets/5384f49a-71f9-4638-a60c-158cc8c83f81"> - [x] Also : 
This commit is contained in:
@ -16,7 +16,6 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
|
||||
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 { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
@ -167,43 +166,41 @@ export const ObjectFilterDropdownFilterSelect = ({
|
||||
setObjectFilterDropdownSearchInput(event.target.value)
|
||||
}
|
||||
/>
|
||||
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||
<SelectableList
|
||||
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
||||
selectableItemIdArray={selectableListItemIds}
|
||||
selectableListId={OBJECT_FILTER_DROPDOWN_ID}
|
||||
onEnter={handleEnter}
|
||||
>
|
||||
<DropdownMenuItemsContainer>
|
||||
{visibleColumnsFilterDefinitions.map(
|
||||
(visibleFilterDefinition, index) => (
|
||||
<SelectableItem
|
||||
itemId={visibleFilterDefinition.fieldMetadataId}
|
||||
key={`visible-select-filter-${index}`}
|
||||
>
|
||||
<ObjectFilterDropdownFilterSelectMenuItem
|
||||
filterDefinition={visibleFilterDefinition}
|
||||
/>
|
||||
</SelectableItem>
|
||||
),
|
||||
)}
|
||||
{shoudShowSeparator && <DropdownMenuSeparator />}
|
||||
{hiddenColumnsFilterDefinitions.map(
|
||||
(hiddenFilterDefinition, index) => (
|
||||
<SelectableItem
|
||||
itemId={hiddenFilterDefinition.fieldMetadataId}
|
||||
key={`hidden-select-filter-${index}`}
|
||||
>
|
||||
<ObjectFilterDropdownFilterSelectMenuItem
|
||||
filterDefinition={hiddenFilterDefinition}
|
||||
/>
|
||||
</SelectableItem>
|
||||
),
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
{shouldShowAdvancedFilterButton && <AdvancedFilterButton />}
|
||||
</ScrollWrapper>
|
||||
<SelectableList
|
||||
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
||||
selectableItemIdArray={selectableListItemIds}
|
||||
selectableListId={OBJECT_FILTER_DROPDOWN_ID}
|
||||
onEnter={handleEnter}
|
||||
>
|
||||
<DropdownMenuItemsContainer>
|
||||
{visibleColumnsFilterDefinitions.map(
|
||||
(visibleFilterDefinition, index) => (
|
||||
<SelectableItem
|
||||
itemId={visibleFilterDefinition.fieldMetadataId}
|
||||
key={`visible-select-filter-${index}`}
|
||||
>
|
||||
<ObjectFilterDropdownFilterSelectMenuItem
|
||||
filterDefinition={visibleFilterDefinition}
|
||||
/>
|
||||
</SelectableItem>
|
||||
),
|
||||
)}
|
||||
{shoudShowSeparator && <DropdownMenuSeparator />}
|
||||
{hiddenColumnsFilterDefinitions.map(
|
||||
(hiddenFilterDefinition, index) => (
|
||||
<SelectableItem
|
||||
itemId={hiddenFilterDefinition.fieldMetadataId}
|
||||
key={`hidden-select-filter-${index}`}
|
||||
>
|
||||
<ObjectFilterDropdownFilterSelectMenuItem
|
||||
filterDefinition={hiddenFilterDefinition}
|
||||
/>
|
||||
</SelectableItem>
|
||||
),
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
{shouldShowAdvancedFilterButton && <AdvancedFilterButton />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -28,9 +28,11 @@ export const ObjectFilterDropdownOperandDropdown = ({
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const dropdownId = `${filterDropdownId}-operand-dropdown`;
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={`${filterDropdownId}-operand-dropdown`}
|
||||
dropdownId={dropdownId}
|
||||
clickableComponent={
|
||||
<StyledDropdownMenuHeader
|
||||
key={'selected-filter-operand'}
|
||||
|
||||
@ -15,7 +15,6 @@ import { useSelectableListStates } from '@/ui/layout/selectable-list/hooks/inter
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { MenuItem, MenuItemMultiSelect } from 'twenty-ui';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
@ -163,23 +162,21 @@ export const ObjectFilterDropdownOptionSelect = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||
<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>
|
||||
</ScrollWrapper>
|
||||
<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" />}
|
||||
</SelectableList>
|
||||
);
|
||||
|
||||
@ -11,7 +11,10 @@ import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-sta
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { within } from '@storybook/test';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
import {
|
||||
ComponentDecorator,
|
||||
getCanvasElementForDropdownTesting,
|
||||
} from 'twenty-ui';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
|
||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||
@ -120,7 +123,7 @@ type Story = StoryObj<typeof TaskGroups>;
|
||||
|
||||
export const Default: Story = {
|
||||
play: async () => {
|
||||
const canvas = within(document.body);
|
||||
const canvas = within(getCanvasElementForDropdownTesting());
|
||||
|
||||
const filterButton = await canvas.findByText('Filter');
|
||||
|
||||
@ -142,7 +145,7 @@ export const Default: Story = {
|
||||
|
||||
export const Date: Story = {
|
||||
play: async () => {
|
||||
const canvas = within(document.body);
|
||||
const canvas = within(getCanvasElementForDropdownTesting());
|
||||
|
||||
const filterButton = await canvas.findByText('Filter');
|
||||
|
||||
@ -156,7 +159,7 @@ export const Date: Story = {
|
||||
|
||||
export const Number: Story = {
|
||||
play: async () => {
|
||||
const canvas = within(document.body);
|
||||
const canvas = within(getCanvasElementForDropdownTesting());
|
||||
|
||||
const filterButton = await canvas.findByText('Filter');
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@ import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hook
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
|
||||
@ -53,19 +52,17 @@ export const ObjectOptionsDropdownFieldsContent = () => {
|
||||
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetContent}>
|
||||
Fields
|
||||
</DropdownMenuHeader>
|
||||
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||
<ViewFieldsVisibilityDropdownSection
|
||||
title="Visible"
|
||||
fields={visibleRecordFields}
|
||||
isDraggable
|
||||
onDragEnd={handleReorderFields}
|
||||
onVisibilityChange={handleChangeFieldVisibility}
|
||||
showSubheader={false}
|
||||
showDragGrip={true}
|
||||
/>
|
||||
</ScrollWrapper>
|
||||
<ViewFieldsVisibilityDropdownSection
|
||||
title="Visible"
|
||||
fields={visibleRecordFields}
|
||||
isDraggable
|
||||
onDragEnd={handleReorderFields}
|
||||
onVisibilityChange={handleChangeFieldVisibility}
|
||||
showSubheader={false}
|
||||
showDragGrip={true}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<DropdownMenuItemsContainer withoutScrollWrapper>
|
||||
<MenuItemNavigate
|
||||
onClick={() => onContentChange('hiddenFields')}
|
||||
LeftIcon={IconEyeOff}
|
||||
|
||||
@ -18,7 +18,6 @@ import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenu
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
|
||||
@ -71,19 +70,16 @@ export const ObjectOptionsDropdownHiddenFieldsContent = () => {
|
||||
Hidden Fields
|
||||
</DropdownMenuHeader>
|
||||
{hiddenRecordFields.length > 0 && (
|
||||
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||
<ViewFieldsVisibilityDropdownSection
|
||||
title="Hidden"
|
||||
fields={hiddenRecordFields}
|
||||
isDraggable={false}
|
||||
onVisibilityChange={handleChangeFieldVisibility}
|
||||
showSubheader={false}
|
||||
showDragGrip={false}
|
||||
/>
|
||||
</ScrollWrapper>
|
||||
<ViewFieldsVisibilityDropdownSection
|
||||
title="Hidden"
|
||||
fields={hiddenRecordFields}
|
||||
isDraggable={false}
|
||||
onVisibilityChange={handleChangeFieldVisibility}
|
||||
showSubheader={false}
|
||||
showDragGrip={false}
|
||||
/>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<UndecoratedLink
|
||||
to={settingsUrl}
|
||||
onClick={() => {
|
||||
@ -91,7 +87,7 @@ export const ObjectOptionsDropdownHiddenFieldsContent = () => {
|
||||
closeDropdown();
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItemsContainer>
|
||||
<DropdownMenuItemsContainer withoutScrollWrapper>
|
||||
<MenuItem LeftIcon={IconSettings} text="Edit Fields" />
|
||||
</DropdownMenuItemsContainer>
|
||||
</UndecoratedLink>
|
||||
|
||||
@ -15,7 +15,6 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
|
||||
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useContext } from 'react';
|
||||
import { SORT_DIRECTIONS } from '../types/SortDirection';
|
||||
@ -184,39 +183,37 @@ export const ObjectSortDropdownButton = ({
|
||||
setObjectSortDropdownSearchInput(event.target.value)
|
||||
}
|
||||
/>
|
||||
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||
<DropdownMenuItemsContainer>
|
||||
{visibleColumnsSortDefinitions.map(
|
||||
(visibleSortDefinition, index) => (
|
||||
<MenuItem
|
||||
testId={`visible-select-sort-${index}`}
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setObjectSortDropdownSearchInput('');
|
||||
handleAddSort(visibleSortDefinition);
|
||||
}}
|
||||
LeftIcon={getIcon(visibleSortDefinition.iconName)}
|
||||
text={visibleSortDefinition.label}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
{shoudShowSeparator && <DropdownMenuSeparator />}
|
||||
{hiddenColumnsSortDefinitions.map(
|
||||
(hiddenSortDefinition, index) => (
|
||||
<MenuItem
|
||||
testId={`hidden-select-sort-${index}`}
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setObjectSortDropdownSearchInput('');
|
||||
handleAddSort(hiddenSortDefinition);
|
||||
}}
|
||||
LeftIcon={getIcon(hiddenSortDefinition.iconName)}
|
||||
text={hiddenSortDefinition.label}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</ScrollWrapper>
|
||||
<DropdownMenuItemsContainer>
|
||||
{visibleColumnsSortDefinitions.map(
|
||||
(visibleSortDefinition, index) => (
|
||||
<MenuItem
|
||||
testId={`visible-select-sort-${index}`}
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setObjectSortDropdownSearchInput('');
|
||||
handleAddSort(visibleSortDefinition);
|
||||
}}
|
||||
LeftIcon={getIcon(visibleSortDefinition.iconName)}
|
||||
text={visibleSortDefinition.label}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
{shoudShowSeparator && <DropdownMenuSeparator />}
|
||||
{hiddenColumnsSortDefinitions.map(
|
||||
(hiddenSortDefinition, index) => (
|
||||
<MenuItem
|
||||
testId={`hidden-select-sort-${index}`}
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setObjectSortDropdownSearchInput('');
|
||||
handleAddSort(hiddenSortDefinition);
|
||||
}}
|
||||
LeftIcon={getIcon(hiddenSortDefinition.iconName)}
|
||||
text={hiddenSortDefinition.label}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
}
|
||||
onClose={handleDropdownButtonClose}
|
||||
|
||||
@ -22,7 +22,7 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco
|
||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
@ -81,7 +81,7 @@ export const RecordBoard = () => {
|
||||
const { resetRecordSelection, setRecordAsSelected } =
|
||||
useRecordBoardSelection(recordBoardId);
|
||||
|
||||
useListenClickOutsideV2({
|
||||
useListenClickOutside({
|
||||
excludeClassNames: [
|
||||
'bottom-bar',
|
||||
'action-menu-dropdown',
|
||||
|
||||
@ -34,6 +34,7 @@ export const RecordBoardColumnDropdownMenu = ({
|
||||
useListenClickOutside({
|
||||
refs: [boardColumnMenuRef],
|
||||
callback: closeMenu,
|
||||
listenerId: 'record-board-column-dropdown-menu',
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@ -48,7 +48,10 @@ type FieldInputProps = {
|
||||
recordFieldInputdId: string;
|
||||
onSubmit?: FieldInputEvent;
|
||||
onCancel?: () => void;
|
||||
onClickOutside?: FieldInputEvent;
|
||||
onClickOutside?: (
|
||||
persist: () => void,
|
||||
event: MouseEvent | TouchEvent,
|
||||
) => void;
|
||||
onEnter?: FieldInputEvent;
|
||||
onEscape?: FieldInputEvent;
|
||||
onTab?: FieldInputEvent;
|
||||
@ -78,7 +81,10 @@ export const FieldInput = ({
|
||||
) : isFieldRelationFromManyObjects(fieldDefinition) ? (
|
||||
<RelationFromManyFieldInput onSubmit={onSubmit} />
|
||||
) : isFieldPhones(fieldDefinition) ? (
|
||||
<PhonesFieldInput onCancel={onCancel} />
|
||||
<PhonesFieldInput
|
||||
onCancel={onCancel}
|
||||
onClickOutside={(event) => onClickOutside?.(() => {}, event)}
|
||||
/>
|
||||
) : isFieldText(fieldDefinition) ? (
|
||||
<TextFieldInput
|
||||
onEnter={onEnter}
|
||||
@ -88,7 +94,10 @@ export const FieldInput = ({
|
||||
onShiftTab={onShiftTab}
|
||||
/>
|
||||
) : isFieldEmails(fieldDefinition) ? (
|
||||
<EmailsFieldInput onCancel={onCancel} />
|
||||
<EmailsFieldInput
|
||||
onCancel={onCancel}
|
||||
onClickOutside={(event) => onClickOutside?.(() => {}, event)}
|
||||
/>
|
||||
) : isFieldFullName(fieldDefinition) ? (
|
||||
<FullNameFieldInput
|
||||
onEnter={onEnter}
|
||||
@ -122,7 +131,10 @@ export const FieldInput = ({
|
||||
onShiftTab={onShiftTab}
|
||||
/>
|
||||
) : isFieldLinks(fieldDefinition) ? (
|
||||
<LinksFieldInput onCancel={onCancel} />
|
||||
<LinksFieldInput
|
||||
onCancel={onCancel}
|
||||
onClickOutside={(event) => onClickOutside?.(() => {}, event)}
|
||||
/>
|
||||
) : isFieldCurrency(fieldDefinition) ? (
|
||||
<CurrencyFieldInput
|
||||
onEnter={onEnter}
|
||||
@ -158,7 +170,10 @@ export const FieldInput = ({
|
||||
) : isFieldRichText(fieldDefinition) ? (
|
||||
<RichTextFieldInput />
|
||||
) : isFieldArray(fieldDefinition) ? (
|
||||
<ArrayFieldInput onCancel={onCancel} />
|
||||
<ArrayFieldInput
|
||||
onCancel={onCancel}
|
||||
onClickOutside={(event) => onClickOutside?.(() => {}, event)}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
@ -108,9 +108,6 @@ export const FormSelectFieldInput = ({
|
||||
goBackToPreviousHotkeyScope();
|
||||
};
|
||||
|
||||
const [selectWrapperRef, setSelectWrapperRef] =
|
||||
useState<HTMLDivElement | null>(null);
|
||||
|
||||
const [filteredOptions, setFilteredOptions] = useState<SelectOption[]>([]);
|
||||
|
||||
const { resetSelectedItem } = useSelectableList(
|
||||
@ -206,7 +203,6 @@ export const FormSelectFieldInput = ({
|
||||
|
||||
<StyledFormFieldInputRowContainer>
|
||||
<StyledFormFieldInputInputContainer
|
||||
ref={setSelectWrapperRef}
|
||||
hasRightElement={isDefined(VariablePicker)}
|
||||
>
|
||||
{draftValue.type === 'static' ? (
|
||||
@ -231,7 +227,6 @@ export const FormSelectFieldInput = ({
|
||||
selectableItemIdArray={optionIds}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onEnter={handleSelectEnter}
|
||||
selectWrapperRef={selectWrapperRef}
|
||||
onOptionSelected={handleSubmit}
|
||||
options={field.metadata.options}
|
||||
onCancel={onCancel}
|
||||
|
||||
@ -4,10 +4,13 @@ import { AddressInput } from '@/ui/field/input/components/AddressInput';
|
||||
|
||||
import { usePersistField } from '../../../hooks/usePersistField';
|
||||
|
||||
import { FieldInputEvent } from './DateTimeFieldInput';
|
||||
import {
|
||||
FieldInputClickOutsideEvent,
|
||||
FieldInputEvent,
|
||||
} from './DateTimeFieldInput';
|
||||
|
||||
export type AddressFieldInputProps = {
|
||||
onClickOutside?: FieldInputEvent;
|
||||
onClickOutside?: FieldInputClickOutsideEvent;
|
||||
onEnter?: FieldInputEvent;
|
||||
onEscape?: FieldInputEvent;
|
||||
onTab?: FieldInputEvent;
|
||||
@ -60,7 +63,7 @@ export const AddressFieldInput = ({
|
||||
event: MouseEvent | TouchEvent,
|
||||
newAddress: FieldAddressDraftValue,
|
||||
) => {
|
||||
onClickOutside?.(() => persistField(convertToAddress(newAddress)));
|
||||
onClickOutside?.(() => persistField(convertToAddress(newAddress)), event);
|
||||
};
|
||||
|
||||
const handleChange = (newAddress: FieldAddressDraftValue) => {
|
||||
|
||||
@ -6,9 +6,13 @@ import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
type ArrayFieldInputProps = {
|
||||
onCancel?: () => void;
|
||||
onClickOutside?: (event: MouseEvent | TouchEvent) => void;
|
||||
};
|
||||
|
||||
export const ArrayFieldInput = ({ onCancel }: ArrayFieldInputProps) => {
|
||||
export const ArrayFieldInput = ({
|
||||
onCancel,
|
||||
onClickOutside,
|
||||
}: ArrayFieldInputProps) => {
|
||||
const { persistArrayField, hotkeyScope, fieldValue } = useArrayField();
|
||||
|
||||
const arrayItems = useMemo<Array<string>>(
|
||||
@ -23,6 +27,7 @@ export const ArrayFieldInput = ({ onCancel }: ArrayFieldInputProps) => {
|
||||
items={arrayItems}
|
||||
onPersist={persistArrayField}
|
||||
onCancel={onCancel}
|
||||
onClickOutside={onClickOutside}
|
||||
placeholder="Enter value"
|
||||
fieldMetadataType={FieldMetadataType.Array}
|
||||
renderItem={({ value, index, handleEdit, handleDelete }) => (
|
||||
|
||||
@ -8,10 +8,13 @@ import { FieldInputOverlay } from '../../../../../ui/field/input/components/Fiel
|
||||
import { useCurrencyField } from '../../hooks/useCurrencyField';
|
||||
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
import { FieldInputEvent } from './DateTimeFieldInput';
|
||||
import {
|
||||
FieldInputClickOutsideEvent,
|
||||
FieldInputEvent,
|
||||
} from './DateTimeFieldInput';
|
||||
|
||||
type CurrencyFieldInputProps = {
|
||||
onClickOutside?: FieldInputEvent;
|
||||
onClickOutside?: FieldInputClickOutsideEvent;
|
||||
onEnter?: FieldInputEvent;
|
||||
onEscape?: FieldInputEvent;
|
||||
onTab?: FieldInputEvent;
|
||||
@ -79,7 +82,7 @@ export const CurrencyFieldInput = ({
|
||||
amountText: newValue,
|
||||
currencyCode,
|
||||
});
|
||||
});
|
||||
}, event);
|
||||
};
|
||||
|
||||
const handleTab = (newValue: string) => {
|
||||
|
||||
@ -5,11 +5,12 @@ import { DateInput } from '@/ui/field/input/components/DateInput';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { usePersistField } from '../../../hooks/usePersistField';
|
||||
import { FieldInputClickOutsideEvent } from '@/object-record/record-field/meta-types/input/components/DateTimeFieldInput';
|
||||
|
||||
type FieldInputEvent = (persist: () => void) => void;
|
||||
|
||||
type DateFieldInputProps = {
|
||||
onClickOutside?: FieldInputEvent;
|
||||
onClickOutside?: FieldInputClickOutsideEvent;
|
||||
onEnter?: FieldInputEvent;
|
||||
onEscape?: FieldInputEvent;
|
||||
onClear?: FieldInputEvent;
|
||||
@ -50,10 +51,10 @@ export const DateFieldInput = ({
|
||||
};
|
||||
|
||||
const handleClickOutside = (
|
||||
_event: MouseEvent | TouchEvent,
|
||||
event: MouseEvent | TouchEvent,
|
||||
newDate: Nullable<Date>,
|
||||
) => {
|
||||
onClickOutside?.(() => persistDate(newDate));
|
||||
onClickOutside?.(() => persistDate(newDate), event);
|
||||
};
|
||||
|
||||
const handleChange = (newDate: Nullable<Date>) => {
|
||||
|
||||
@ -6,9 +6,13 @@ import { usePersistField } from '../../../hooks/usePersistField';
|
||||
import { useDateTimeField } from '../../hooks/useDateTimeField';
|
||||
|
||||
export type FieldInputEvent = (persist: () => void) => void;
|
||||
export type FieldInputClickOutsideEvent = (
|
||||
persist: () => void,
|
||||
event: MouseEvent | TouchEvent,
|
||||
) => void;
|
||||
|
||||
export type DateTimeFieldInputProps = {
|
||||
onClickOutside?: FieldInputEvent;
|
||||
onClickOutside?: FieldInputClickOutsideEvent;
|
||||
onEnter?: FieldInputEvent;
|
||||
onEscape?: FieldInputEvent;
|
||||
onClear?: FieldInputEvent;
|
||||
@ -45,10 +49,10 @@ export const DateTimeFieldInput = ({
|
||||
};
|
||||
|
||||
const handleClickOutside = (
|
||||
_event: MouseEvent | TouchEvent,
|
||||
event: MouseEvent | TouchEvent,
|
||||
newDate: Nullable<Date>,
|
||||
) => {
|
||||
onClickOutside?.(() => persistDate(newDate));
|
||||
onClickOutside?.(() => persistDate(newDate), event);
|
||||
};
|
||||
|
||||
const handleChange = (newDate: Nullable<Date>) => {
|
||||
|
||||
@ -8,9 +8,13 @@ import { MultiItemFieldInput } from './MultiItemFieldInput';
|
||||
|
||||
type EmailsFieldInputProps = {
|
||||
onCancel?: () => void;
|
||||
onClickOutside?: (event: MouseEvent | TouchEvent) => void;
|
||||
};
|
||||
|
||||
export const EmailsFieldInput = ({ onCancel }: EmailsFieldInputProps) => {
|
||||
export const EmailsFieldInput = ({
|
||||
onCancel,
|
||||
onClickOutside,
|
||||
}: EmailsFieldInputProps) => {
|
||||
const { persistEmailsField, hotkeyScope, fieldValue } = useEmailsField();
|
||||
|
||||
const emails = useMemo<string[]>(
|
||||
@ -45,6 +49,7 @@ export const EmailsFieldInput = ({ onCancel }: EmailsFieldInputProps) => {
|
||||
items={emails}
|
||||
onPersist={handlePersistEmails}
|
||||
onCancel={onCancel}
|
||||
onClickOutside={onClickOutside}
|
||||
placeholder="Email"
|
||||
fieldMetadataType={FieldMetadataType.Emails}
|
||||
validateInput={validateInput}
|
||||
|
||||
@ -4,7 +4,10 @@ import { DoubleTextInput } from '@/ui/field/input/components/DoubleTextInput';
|
||||
import { FieldInputOverlay } from '@/ui/field/input/components/FieldInputOverlay';
|
||||
|
||||
import { isDoubleTextFieldEmpty } from '@/object-record/record-field/meta-types/input/utils/isDoubleTextFieldEmpty';
|
||||
import { FieldInputEvent } from './DateTimeFieldInput';
|
||||
import {
|
||||
FieldInputClickOutsideEvent,
|
||||
FieldInputEvent,
|
||||
} from './DateTimeFieldInput';
|
||||
|
||||
const FIRST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS =
|
||||
'First name';
|
||||
@ -13,7 +16,7 @@ const LAST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS =
|
||||
'Last name';
|
||||
|
||||
type FullNameFieldInputProps = {
|
||||
onClickOutside?: FieldInputEvent;
|
||||
onClickOutside?: FieldInputClickOutsideEvent;
|
||||
onEnter?: FieldInputEvent;
|
||||
onEscape?: FieldInputEvent;
|
||||
onTab?: FieldInputEvent;
|
||||
@ -57,8 +60,9 @@ export const FullNameFieldInput = ({
|
||||
event: MouseEvent | TouchEvent,
|
||||
newDoubleText: FieldDoubleText,
|
||||
) => {
|
||||
onClickOutside?.(() =>
|
||||
persistFullNameField(convertToFullName(newDoubleText)),
|
||||
onClickOutside?.(
|
||||
() => persistFullNameField(convertToFullName(newDoubleText)),
|
||||
event,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -8,9 +8,13 @@ import { MultiItemFieldInput } from './MultiItemFieldInput';
|
||||
|
||||
type LinksFieldInputProps = {
|
||||
onCancel?: () => void;
|
||||
onClickOutside?: (event: MouseEvent | TouchEvent) => void;
|
||||
};
|
||||
|
||||
export const LinksFieldInput = ({ onCancel }: LinksFieldInputProps) => {
|
||||
export const LinksFieldInput = ({
|
||||
onCancel,
|
||||
onClickOutside,
|
||||
}: LinksFieldInputProps) => {
|
||||
const { persistLinksField, hotkeyScope, fieldValue } = useLinksField();
|
||||
|
||||
const links = useMemo<{ url: string; label: string }[]>(
|
||||
@ -49,6 +53,7 @@ export const LinksFieldInput = ({ onCancel }: LinksFieldInputProps) => {
|
||||
items={links}
|
||||
onPersist={handlePersistLinks}
|
||||
onCancel={onCancel}
|
||||
onClickOutside={onClickOutside}
|
||||
placeholder="URL"
|
||||
fieldMetadataType={FieldMetadataType.Links}
|
||||
validateInput={(input) => ({
|
||||
|
||||
@ -41,6 +41,7 @@ type MultiItemFieldInputProps<T> = {
|
||||
newItemLabel?: string;
|
||||
fieldMetadataType: FieldMetadataType;
|
||||
renderInput?: DropdownMenuInputProps['renderInput'];
|
||||
onClickOutside?: (event: MouseEvent | TouchEvent) => void;
|
||||
};
|
||||
|
||||
// Todo: the API of this component does not look healthy: we have renderInput, renderItem, formatInput, ...
|
||||
@ -57,24 +58,19 @@ export const MultiItemFieldInput = <T,>({
|
||||
newItemLabel,
|
||||
fieldMetadataType,
|
||||
renderInput,
|
||||
onClickOutside,
|
||||
}: MultiItemFieldInputProps<T>) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const handleDropdownClose = () => {
|
||||
onCancel?.();
|
||||
};
|
||||
|
||||
const handleDropdownCloseOutside = (event: MouseEvent | TouchEvent) => {
|
||||
event.stopImmediatePropagation();
|
||||
if (inputValue?.trim().length > 0) {
|
||||
handleSubmitInput();
|
||||
} else {
|
||||
onCancel?.();
|
||||
}
|
||||
};
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [containerRef],
|
||||
callback: handleDropdownCloseOutside,
|
||||
callback: (event) => {
|
||||
onClickOutside?.(event);
|
||||
},
|
||||
listenerId: hotkeyScope,
|
||||
});
|
||||
|
||||
useScopedHotkeys(Key.Escape, handleDropdownClose, hotkeyScope);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { MenuItemWithOptionDropdown } from '@/ui/navigation/menu-item/components/MenuItemWithOptionDropdown';
|
||||
import { useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
IconBookmark,
|
||||
IconBookmarkPlus,
|
||||
@ -37,23 +37,29 @@ export const MultiItemFieldMenuItem = <T,>({
|
||||
const handleMouseEnter = () => setIsHovered(true);
|
||||
const handleMouseLeave = () => {
|
||||
setIsHovered(false);
|
||||
if (isDropdownOpen) {
|
||||
closeDropdown();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteClick = () => {
|
||||
const handleDeleteClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
closeDropdown();
|
||||
setIsHovered(false);
|
||||
onDelete?.();
|
||||
};
|
||||
|
||||
const handleSetAsPrimaryClick = () => {
|
||||
const handleSetAsPrimaryClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
closeDropdown();
|
||||
onSetAsPrimary?.();
|
||||
};
|
||||
|
||||
const handleEditClick = () => {
|
||||
const handleEditClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
closeDropdown();
|
||||
onEdit?.();
|
||||
};
|
||||
|
||||
@ -88,6 +88,7 @@ export const MultiSelectFieldInput = ({
|
||||
}
|
||||
resetSelectedItem();
|
||||
},
|
||||
listenerId: 'MultiSelectFieldInput',
|
||||
});
|
||||
|
||||
const optionIds = filteredOptionsInDropDown.map((option) => option.value);
|
||||
|
||||
@ -2,11 +2,12 @@ import { TextInput } from '@/ui/field/input/components/TextInput';
|
||||
|
||||
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
|
||||
import { useNumberField } from '../../hooks/useNumberField';
|
||||
import { FieldInputClickOutsideEvent } from '@/object-record/record-field/meta-types/input/components/DateTimeFieldInput';
|
||||
|
||||
export type FieldInputEvent = (persist: () => void) => void;
|
||||
|
||||
export type NumberFieldInputProps = {
|
||||
onClickOutside?: FieldInputEvent;
|
||||
onClickOutside?: FieldInputClickOutsideEvent;
|
||||
onEnter?: FieldInputEvent;
|
||||
onEscape?: FieldInputEvent;
|
||||
onTab?: FieldInputEvent;
|
||||
@ -40,7 +41,7 @@ export const NumberFieldInput = ({
|
||||
event: MouseEvent | TouchEvent,
|
||||
newText: string,
|
||||
) => {
|
||||
onClickOutside?.(() => persistNumberField(newText));
|
||||
onClickOutside?.(() => persistNumberField(newText), event);
|
||||
};
|
||||
|
||||
const handleTab = (newText: string) => {
|
||||
|
||||
@ -49,10 +49,14 @@ const StyledCustomPhoneInput = styled(ReactPhoneNumberInput)`
|
||||
|
||||
type PhonesFieldInputProps = {
|
||||
onCancel?: () => void;
|
||||
onClickOutside?: (event: MouseEvent | TouchEvent) => void;
|
||||
};
|
||||
|
||||
export const PhonesFieldInput = ({ onCancel }: PhonesFieldInputProps) => {
|
||||
const { persistPhonesField, hotkeyScope, fieldDefinition, fieldValue } =
|
||||
export const PhonesFieldInput = ({
|
||||
onCancel,
|
||||
onClickOutside,
|
||||
}: PhonesFieldInputProps) => {
|
||||
const { persistPhonesField, hotkeyScope, fieldValue, fieldDefinition } =
|
||||
usePhonesField();
|
||||
|
||||
const phones = createPhonesFromFieldValue(fieldValue);
|
||||
@ -83,6 +87,7 @@ export const PhonesFieldInput = ({ onCancel }: PhonesFieldInputProps) => {
|
||||
<MultiItemFieldInput
|
||||
items={phones}
|
||||
onPersist={handlePersistPhones}
|
||||
onClickOutside={onClickOutside}
|
||||
onCancel={onCancel}
|
||||
placeholder="Phone"
|
||||
fieldMetadataType={FieldMetadataType.Phones}
|
||||
|
||||
@ -3,10 +3,13 @@ import { TextAreaInput } from '@/ui/field/input/components/TextAreaInput';
|
||||
|
||||
import { useJsonField } from '../../hooks/useJsonField';
|
||||
|
||||
import { FieldInputEvent } from './DateTimeFieldInput';
|
||||
import {
|
||||
FieldInputClickOutsideEvent,
|
||||
FieldInputEvent,
|
||||
} from './DateTimeFieldInput';
|
||||
|
||||
type RawJsonFieldInputProps = {
|
||||
onClickOutside?: FieldInputEvent;
|
||||
onClickOutside?: FieldInputClickOutsideEvent;
|
||||
onEnter?: FieldInputEvent;
|
||||
onEscape?: FieldInputEvent;
|
||||
onTab?: FieldInputEvent;
|
||||
@ -37,10 +40,10 @@ export const RawJsonFieldInput = ({
|
||||
};
|
||||
|
||||
const handleClickOutside = (
|
||||
_event: MouseEvent | TouchEvent,
|
||||
event: MouseEvent | TouchEvent,
|
||||
newText: string,
|
||||
) => {
|
||||
onClickOutside?.(() => persistJsonField(newText));
|
||||
onClickOutside?.(() => persistJsonField(newText), event);
|
||||
};
|
||||
|
||||
const handleTab = (newText: string) => {
|
||||
|
||||
@ -21,8 +21,6 @@ export const SelectFieldInput = ({
|
||||
}: SelectFieldInputProps) => {
|
||||
const { persistField, fieldDefinition, fieldValue, hotkeyScope } =
|
||||
useSelectField();
|
||||
const [selectWrapperRef, setSelectWrapperRef] =
|
||||
useState<HTMLDivElement | null>(null);
|
||||
|
||||
const [filteredOptions, setFilteredOptions] = useState<SelectOption[]>([]);
|
||||
|
||||
@ -62,7 +60,7 @@ export const SelectFieldInput = ({
|
||||
];
|
||||
|
||||
return (
|
||||
<div ref={setSelectWrapperRef}>
|
||||
<div>
|
||||
<SelectInput
|
||||
selectableListId={SINGLE_RECORD_SELECT_BASE_LIST}
|
||||
selectableItemIdArray={optionIds}
|
||||
@ -76,7 +74,6 @@ export const SelectFieldInput = ({
|
||||
resetSelectedItem();
|
||||
}
|
||||
}}
|
||||
selectWrapperRef={selectWrapperRef}
|
||||
onOptionSelected={handleSubmit}
|
||||
options={fieldDefinition.metadata.options}
|
||||
onCancel={onCancel}
|
||||
|
||||
@ -5,10 +5,13 @@ import { usePersistField } from '../../../hooks/usePersistField';
|
||||
import { useTextField } from '../../hooks/useTextField';
|
||||
|
||||
import { turnIntoUndefinedIfWhitespacesOnly } from '~/utils/string/turnIntoUndefinedIfWhitespacesOnly';
|
||||
import { FieldInputEvent } from './DateTimeFieldInput';
|
||||
import {
|
||||
FieldInputClickOutsideEvent,
|
||||
FieldInputEvent,
|
||||
} from './DateTimeFieldInput';
|
||||
|
||||
export type TextFieldInputProps = {
|
||||
onClickOutside?: FieldInputEvent;
|
||||
onClickOutside?: FieldInputClickOutsideEvent;
|
||||
onEnter?: FieldInputEvent;
|
||||
onEscape?: FieldInputEvent;
|
||||
onTab?: FieldInputEvent;
|
||||
@ -38,7 +41,7 @@ export const TextFieldInput = ({
|
||||
event: MouseEvent | TouchEvent,
|
||||
newText: string,
|
||||
) => {
|
||||
onClickOutside?.(() => persistField(newText.trim()));
|
||||
onClickOutside?.(() => persistField(newText.trim()), event);
|
||||
};
|
||||
|
||||
const handleTab = (newText: string) => {
|
||||
|
||||
@ -1,12 +1,5 @@
|
||||
import { Decorator, Meta, StoryObj } from '@storybook/react';
|
||||
import {
|
||||
expect,
|
||||
fireEvent,
|
||||
fn,
|
||||
userEvent,
|
||||
waitFor,
|
||||
within,
|
||||
} from '@storybook/test';
|
||||
import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
|
||||
import { useEffect } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
@ -26,6 +19,7 @@ import {
|
||||
|
||||
import { FieldContextProvider } from '@/object-record/record-field/meta-types/components/FieldContextProvider';
|
||||
import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
|
||||
import { getCanvasElementForDropdownTesting } from 'twenty-ui';
|
||||
import {
|
||||
RelationToOneFieldInput,
|
||||
RelationToOneFieldInputProps,
|
||||
@ -136,8 +130,8 @@ export const Default: Story = {
|
||||
|
||||
export const Submit: Story = {
|
||||
decorators: [ComponentWithRecoilScopeDecorator],
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
play: async () => {
|
||||
const canvas = within(getCanvasElementForDropdownTesting());
|
||||
|
||||
expect(submitJestFn).toHaveBeenCalledTimes(0);
|
||||
|
||||
@ -154,16 +148,16 @@ export const Submit: Story = {
|
||||
|
||||
export const Cancel: Story = {
|
||||
decorators: [ComponentWithRecoilScopeDecorator],
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
play: async () => {
|
||||
const canvas = within(getCanvasElementForDropdownTesting());
|
||||
|
||||
expect(cancelJestFn).toHaveBeenCalledTimes(0);
|
||||
await canvas.findByText('John Wick', undefined, { timeout: 3000 });
|
||||
|
||||
const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div');
|
||||
fireEvent.click(emptyDiv);
|
||||
|
||||
await waitFor(() => {
|
||||
userEvent.click(emptyDiv);
|
||||
expect(cancelJestFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
},
|
||||
|
||||
@ -28,10 +28,10 @@ export const useRegisterInputEvents = <T>({
|
||||
useListenClickOutside({
|
||||
refs: [inputRef, copyRef].filter(isDefined),
|
||||
callback: (event) => {
|
||||
event.stopImmediatePropagation();
|
||||
onClickOutside?.(event, inputValue);
|
||||
},
|
||||
enabled: isDefined(onClickOutside),
|
||||
listenerId: hotkeyScope,
|
||||
});
|
||||
|
||||
useScopedHotkeys(
|
||||
|
||||
@ -14,7 +14,11 @@ import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types
|
||||
import { useInlineCell } from '../hooks/useInlineCell';
|
||||
|
||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||
import { FieldInputClickOutsideEvent } from '@/object-record/record-field/meta-types/input/components/DateTimeFieldInput';
|
||||
import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
|
||||
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
||||
import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDropdownFocusIdState';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { RecordInlineCellContainer } from './RecordInlineCellContainer';
|
||||
import {
|
||||
RecordInlineCellContext,
|
||||
@ -65,10 +69,30 @@ export const RecordInlineCell = ({ loading }: RecordInlineCellProps) => {
|
||||
closeInlineCell();
|
||||
};
|
||||
|
||||
const handleClickOutside: FieldInputEvent = (persistField) => {
|
||||
persistField();
|
||||
closeInlineCell();
|
||||
};
|
||||
const handleClickOutside: FieldInputClickOutsideEvent = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(persistField, event) => {
|
||||
const recordFieldDropdownId = getDropdownFocusIdForRecordField(
|
||||
recordId,
|
||||
fieldDefinition.fieldMetadataId,
|
||||
'inline-cell',
|
||||
);
|
||||
|
||||
const activeDropdownFocusId = snapshot
|
||||
.getLoadable(activeDropdownFocusIdState)
|
||||
.getValue();
|
||||
|
||||
if (recordFieldDropdownId !== activeDropdownFocusId) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
persistField();
|
||||
closeInlineCell();
|
||||
},
|
||||
[closeInlineCell, fieldDefinition.fieldMetadataId, recordId],
|
||||
);
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
|
||||
@ -18,11 +18,10 @@ const StyledInlineCellInput = styled.div`
|
||||
display: flex;
|
||||
|
||||
min-height: 32px;
|
||||
min-width: 240px;
|
||||
|
||||
width: inherit;
|
||||
width: 240px;
|
||||
|
||||
z-index: 1000;
|
||||
z-index: 30;
|
||||
`;
|
||||
|
||||
type RecordInlineCellEditModeProps = {
|
||||
|
||||
@ -7,6 +7,9 @@ import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { useInitDraftValueV2 } from '@/object-record/record-field/hooks/useInitDraftValueV2';
|
||||
import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
|
||||
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
|
||||
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
||||
import { isInlineCellInEditModeScopedState } from '../states/isInlineCellInEditModeScopedState';
|
||||
import { InlineCellHotkeyScope } from '../types/InlineCellHotkeyScope';
|
||||
|
||||
@ -21,6 +24,11 @@ export const useInlineCell = () => {
|
||||
isInlineCellInEditModeScopedState(recoilScopeId),
|
||||
);
|
||||
|
||||
const { setActiveDropdownFocusIdAndMemorizePrevious } =
|
||||
useSetActiveDropdownFocusIdAndMemorizePrevious();
|
||||
const { goBackToPreviousDropdownFocusId } =
|
||||
useGoBackToPreviousDropdownFocusId();
|
||||
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
@ -32,6 +40,8 @@ export const useInlineCell = () => {
|
||||
setIsInlineCellInEditMode(false);
|
||||
|
||||
goBackToPreviousHotkeyScope();
|
||||
|
||||
goBackToPreviousDropdownFocusId();
|
||||
};
|
||||
|
||||
const openInlineCell = (customEditHotkeyScopeForField?: HotkeyScope) => {
|
||||
@ -46,6 +56,14 @@ export const useInlineCell = () => {
|
||||
} else {
|
||||
setHotkeyScopeAndMemorizePreviousScope(InlineCellHotkeyScope.InlineCell);
|
||||
}
|
||||
|
||||
setActiveDropdownFocusIdAndMemorizePrevious(
|
||||
getDropdownFocusIdForRecordField(
|
||||
recordId,
|
||||
fieldDefinition.fieldMetadataId,
|
||||
'inline-cell',
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@ -2,6 +2,7 @@ import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { currentTableCellInEditModePositionComponentState } from '@/object-record/record-table/states/currentTableCellInEditModePositionComponentState';
|
||||
import { isTableCellInEditModeComponentFamilyState } from '@/object-record/record-table/states/isTableCellInEditModeComponentFamilyState';
|
||||
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
|
||||
@ -16,6 +17,9 @@ export const useCloseCurrentTableCellInEditMode = (recordTableId?: string) => {
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const { goBackToPreviousDropdownFocusId } =
|
||||
useGoBackToPreviousDropdownFocusId();
|
||||
|
||||
return useRecoilCallback(
|
||||
({ set, snapshot }) => {
|
||||
return async () => {
|
||||
@ -28,8 +32,14 @@ export const useCloseCurrentTableCellInEditMode = (recordTableId?: string) => {
|
||||
isTableCellInEditModeFamilyState(currentTableCellInEditModePosition),
|
||||
false,
|
||||
);
|
||||
|
||||
goBackToPreviousDropdownFocusId();
|
||||
};
|
||||
},
|
||||
[currentTableCellInEditModePositionState, isTableCellInEditModeFamilyState],
|
||||
[
|
||||
currentTableCellInEditModePositionState,
|
||||
isTableCellInEditModeFamilyState,
|
||||
goBackToPreviousDropdownFocusId,
|
||||
],
|
||||
);
|
||||
};
|
||||
|
||||
@ -5,7 +5,7 @@ import { useLeaveTableFocus } from '@/object-record/record-table/hooks/internal/
|
||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
|
||||
type RecordTableBodyUnselectEffectProps = {
|
||||
tableBodyRef: React.RefObject<HTMLDivElement>;
|
||||
@ -32,7 +32,7 @@ export const RecordTableBodyUnselectEffect = ({
|
||||
TableHotkeyScope.Table,
|
||||
);
|
||||
|
||||
useListenClickOutsideV2({
|
||||
useListenClickOutside({
|
||||
excludeClassNames: [
|
||||
'bottom-bar',
|
||||
'action-menu-dropdown',
|
||||
|
||||
@ -4,19 +4,13 @@ import { FieldInput } from '@/object-record/record-field/components/FieldInput';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
||||
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
|
||||
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
|
||||
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
||||
import { useClickOustideListenerStates } from '@/ui/utilities/pointer-event/hooks/useClickOustideListenerStates';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDropdownFocusIdState';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
export const RecordTableCellFieldInput = () => {
|
||||
const { getClickOutsideListenerIsActivatedState } =
|
||||
useClickOustideListenerStates(RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID);
|
||||
const setClickOutsideListenerIsActivated = useSetRecoilState(
|
||||
getClickOutsideListenerIsActivatedState,
|
||||
);
|
||||
|
||||
const { onUpsertRecord, onMoveFocus, onCloseTableCell } =
|
||||
useContext(RecordTableContext);
|
||||
|
||||
@ -48,17 +42,35 @@ export const RecordTableCellFieldInput = () => {
|
||||
onCloseTableCell();
|
||||
};
|
||||
|
||||
const handleClickOutside: FieldInputEvent = (persistField) => {
|
||||
setClickOutsideListenerIsActivated(false);
|
||||
const handleClickOutside = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(persistField: () => void, event: MouseEvent | TouchEvent) => {
|
||||
const dropdownFocusId = getDropdownFocusIdForRecordField(
|
||||
recordId,
|
||||
fieldDefinition.fieldMetadataId,
|
||||
'table-cell',
|
||||
);
|
||||
|
||||
onUpsertRecord({
|
||||
persistField,
|
||||
recordId,
|
||||
fieldName: fieldDefinition.metadata.fieldName,
|
||||
});
|
||||
const activeDropdownFocusId = snapshot
|
||||
.getLoadable(activeDropdownFocusIdState)
|
||||
.getValue();
|
||||
|
||||
onCloseTableCell();
|
||||
};
|
||||
if (activeDropdownFocusId !== dropdownFocusId) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
onUpsertRecord({
|
||||
persistField,
|
||||
recordId,
|
||||
fieldName: fieldDefinition.metadata.fieldName,
|
||||
});
|
||||
|
||||
onCloseTableCell();
|
||||
},
|
||||
[fieldDefinition, onCloseTableCell, onUpsertRecord, recordId],
|
||||
);
|
||||
|
||||
const handleEscape: FieldInputEvent = (persistField) => {
|
||||
onUpsertRecord({
|
||||
|
||||
@ -10,13 +10,11 @@ import { useIsFieldEmpty } from '@/object-record/record-field/hooks/useIsFieldEm
|
||||
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
||||
import { useToggleEditOnlyInput } from '@/object-record/record-field/hooks/useToggleEditOnlyInput';
|
||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||
import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode';
|
||||
import { RecordTableCellButton } from '@/object-record/record-table/record-table-cell/components/RecordTableCellButton';
|
||||
import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell';
|
||||
import { isSoftFocusUsingMouseState } from '@/object-record/record-table/states/isSoftFocusUsingMouseState';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||
@ -34,7 +32,6 @@ export const RecordTableCellSoftFocusMode = ({
|
||||
nonEditModeContent,
|
||||
}: RecordTableCellSoftFocusModeProps) => {
|
||||
const { columnIndex } = useContext(RecordTableCellContext);
|
||||
const closeCurrentTableCell = useCloseCurrentTableCellInEditMode();
|
||||
|
||||
const isFieldReadOnly = useIsFieldValueReadOnly();
|
||||
|
||||
@ -135,13 +132,6 @@ export const RecordTableCellSoftFocusMode = ({
|
||||
*/
|
||||
};
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [scrollRef],
|
||||
callback: () => {
|
||||
closeCurrentTableCell();
|
||||
},
|
||||
});
|
||||
|
||||
const isFirstColumn = columnIndex === 0;
|
||||
const customButtonIcon = useGetButtonIcon();
|
||||
const buttonIcon = isFirstColumn
|
||||
|
||||
@ -22,6 +22,8 @@ import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
|
||||
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
|
||||
import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
|
||||
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
||||
import { useClickOustideListenerStates } from '@/ui/utilities/pointer-event/hooks/useClickOustideListenerStates';
|
||||
import { useContext } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
@ -69,6 +71,9 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { setActiveDropdownFocusIdAndMemorizePrevious } =
|
||||
useSetActiveDropdownFocusIdAndMemorizePrevious();
|
||||
|
||||
const openTableCell = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
({
|
||||
@ -142,6 +147,14 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
||||
DEFAULT_CELL_SCOPE.customScopes,
|
||||
);
|
||||
}
|
||||
|
||||
setActiveDropdownFocusIdAndMemorizePrevious(
|
||||
getDropdownFocusIdForRecordField(
|
||||
recordId,
|
||||
fieldDefinition.fieldMetadataId,
|
||||
'table-cell',
|
||||
),
|
||||
);
|
||||
},
|
||||
[
|
||||
getClickOutsideListenerIsActivatedState,
|
||||
@ -156,6 +169,7 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
||||
setViewableRecordNameSingular,
|
||||
openRightDrawer,
|
||||
setHotkeyScope,
|
||||
setActiveDropdownFocusIdAndMemorizePrevious,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -15,7 +15,6 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { onToggleColumnFilterComponentState } from '@/object-record/record-table/states/onToggleColumnFilterComponentState';
|
||||
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
|
||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useTableColumns } from '../../hooks/useTableColumns';
|
||||
import { ColumnDefinition } from '../../types/ColumnDefinition';
|
||||
@ -92,45 +91,43 @@ export const RecordTableColumnHeadDropdownMenu = ({
|
||||
const canHide = column.isLabelIdentifier !== true;
|
||||
|
||||
return (
|
||||
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||
<DropdownMenuItemsContainer>
|
||||
{isFilterable && (
|
||||
<MenuItem
|
||||
LeftIcon={IconFilter}
|
||||
onClick={handleFilterClick}
|
||||
text="Filter"
|
||||
/>
|
||||
)}
|
||||
{isSortable && (
|
||||
<MenuItem
|
||||
LeftIcon={IconSortDescending}
|
||||
onClick={handleSortClick}
|
||||
text="Sort"
|
||||
/>
|
||||
)}
|
||||
{showSeparator && <DropdownMenuSeparator />}
|
||||
{canMoveLeft && (
|
||||
<MenuItem
|
||||
LeftIcon={IconArrowLeft}
|
||||
onClick={handleColumnMoveLeft}
|
||||
text="Move left"
|
||||
/>
|
||||
)}
|
||||
{canMoveRight && (
|
||||
<MenuItem
|
||||
LeftIcon={IconArrowRight}
|
||||
onClick={handleColumnMoveRight}
|
||||
text="Move right"
|
||||
/>
|
||||
)}
|
||||
{canHide && (
|
||||
<MenuItem
|
||||
LeftIcon={IconEyeOff}
|
||||
onClick={handleColumnVisibility}
|
||||
text="Hide"
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</ScrollWrapper>
|
||||
<DropdownMenuItemsContainer>
|
||||
{isFilterable && (
|
||||
<MenuItem
|
||||
LeftIcon={IconFilter}
|
||||
onClick={handleFilterClick}
|
||||
text="Filter"
|
||||
/>
|
||||
)}
|
||||
{isSortable && (
|
||||
<MenuItem
|
||||
LeftIcon={IconSortDescending}
|
||||
onClick={handleSortClick}
|
||||
text="Sort"
|
||||
/>
|
||||
)}
|
||||
{showSeparator && <DropdownMenuSeparator />}
|
||||
{canMoveLeft && (
|
||||
<MenuItem
|
||||
LeftIcon={IconArrowLeft}
|
||||
onClick={handleColumnMoveLeft}
|
||||
text="Move left"
|
||||
/>
|
||||
)}
|
||||
{canMoveRight && (
|
||||
<MenuItem
|
||||
LeftIcon={IconArrowRight}
|
||||
onClick={handleColumnMoveRight}
|
||||
text="Move right"
|
||||
/>
|
||||
)}
|
||||
{canHide && (
|
||||
<MenuItem
|
||||
LeftIcon={IconEyeOff}
|
||||
onClick={handleColumnVisibility}
|
||||
text="Hide"
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -13,7 +13,6 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
export const RecordTableHeaderPlusButtonContent = () => {
|
||||
@ -42,20 +41,18 @@ export const RecordTableHeaderPlusButtonContent = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||
<DropdownMenuItemsContainer>
|
||||
{hiddenTableColumns.map((column) => (
|
||||
<MenuItem
|
||||
key={column.fieldMetadataId}
|
||||
onClick={() => handleAddColumn(column)}
|
||||
LeftIcon={getIcon(column.iconName)}
|
||||
text={column.label}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</ScrollWrapper>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
{hiddenTableColumns.map((column) => (
|
||||
<MenuItem
|
||||
key={column.fieldMetadataId}
|
||||
onClick={() => handleAddColumn(column)}
|
||||
LeftIcon={getIcon(column.iconName)}
|
||||
text={column.label}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer withoutScrollWrapper>
|
||||
<UndecoratedLink
|
||||
fullWidth
|
||||
to={`/settings/objects/${getObjectSlug(objectMetadataItem)}`}
|
||||
|
||||
@ -179,7 +179,11 @@ export const MultiRecordSelect = ({
|
||||
{objectRecordsIdsMultiSelect?.length > 0 && (
|
||||
<DropdownMenuSeparator />
|
||||
)}
|
||||
{isDefined(onCreate) && <div>{createNewButton}</div>}
|
||||
{isDefined(onCreate) && (
|
||||
<DropdownMenuItemsContainer withoutScrollWrapper>
|
||||
{createNewButton}
|
||||
</DropdownMenuItemsContainer>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
|
||||
@ -3,6 +3,7 @@ import { useEffect } from 'react';
|
||||
import { MULTI_OBJECT_RECORD_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/relation-picker/constants/MultiObjectRecordClickOutsideListenerId';
|
||||
import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener';
|
||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
|
||||
export const MultipleObjectRecordOnClickOutsideEffect = ({
|
||||
containerRef,
|
||||
@ -11,10 +12,6 @@ export const MultipleObjectRecordOnClickOutsideEffect = ({
|
||||
containerRef: React.RefObject<HTMLDivElement>;
|
||||
onClickOutside: () => void;
|
||||
}) => {
|
||||
const { useListenClickOutside } = useClickOutsideListener(
|
||||
MULTI_OBJECT_RECORD_CLICK_OUTSIDE_LISTENER_ID,
|
||||
);
|
||||
|
||||
const { toggleClickOutsideListener: toggleRightDrawerClickOustideListener } =
|
||||
useClickOutsideListener(RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID);
|
||||
|
||||
@ -35,6 +32,7 @@ export const MultipleObjectRecordOnClickOutsideEffect = ({
|
||||
|
||||
onClickOutside();
|
||||
},
|
||||
listenerId: MULTI_OBJECT_RECORD_CLICK_OUTSIDE_LISTENER_ID,
|
||||
});
|
||||
|
||||
return <></>;
|
||||
|
||||
@ -41,6 +41,7 @@ export const SingleRecordSelect = ({
|
||||
onCancel();
|
||||
}
|
||||
},
|
||||
listenerId: 'single-record-select',
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@ -59,27 +59,6 @@ export const SingleRecordSelectMenuItemsWithSearch = ({
|
||||
/>
|
||||
);
|
||||
|
||||
const results = (
|
||||
<SingleRecordSelectMenuItems
|
||||
recordsToSelect={records.recordsToSelect}
|
||||
loading={records.loading}
|
||||
selectedRecord={
|
||||
records.recordsToSelect.length === 1
|
||||
? records.recordsToSelect[0]
|
||||
: undefined
|
||||
}
|
||||
shouldSelectEmptyOption={selectedRecordIds?.length === 0}
|
||||
hotkeyScope={recordPickerInstanceId}
|
||||
isFiltered={!!recordPickerSearchFilter}
|
||||
{...{
|
||||
EmptyIcon,
|
||||
emptyLabel,
|
||||
onCancel,
|
||||
onRecordSelected,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{dropdownPlacement?.includes('end') && (
|
||||
@ -88,7 +67,26 @@ export const SingleRecordSelectMenuItemsWithSearch = ({
|
||||
{createNewButton}
|
||||
</DropdownMenuItemsContainer>
|
||||
{records.recordsToSelect.length > 0 && <DropdownMenuSeparator />}
|
||||
{records.recordsToSelect.length > 0 && results}
|
||||
{records.recordsToSelect.length > 0 && (
|
||||
<SingleRecordSelectMenuItems
|
||||
recordsToSelect={records.recordsToSelect}
|
||||
loading={records.loading}
|
||||
selectedRecord={
|
||||
records.recordsToSelect.length === 1
|
||||
? records.recordsToSelect[0]
|
||||
: undefined
|
||||
}
|
||||
shouldSelectEmptyOption={selectedRecordIds?.length === 0}
|
||||
hotkeyScope={recordPickerInstanceId}
|
||||
isFiltered={!!recordPickerSearchFilter}
|
||||
{...{
|
||||
EmptyIcon,
|
||||
emptyLabel,
|
||||
onCancel,
|
||||
onRecordSelected,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
@ -97,7 +95,26 @@ export const SingleRecordSelectMenuItemsWithSearch = ({
|
||||
isUndefinedOrNull(dropdownPlacement)) && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
{records.recordsToSelect.length > 0 && results}
|
||||
{records.recordsToSelect.length > 0 && (
|
||||
<SingleRecordSelectMenuItems
|
||||
recordsToSelect={records.recordsToSelect}
|
||||
loading={records.loading}
|
||||
selectedRecord={
|
||||
records.recordsToSelect.length === 1
|
||||
? records.recordsToSelect[0]
|
||||
: undefined
|
||||
}
|
||||
shouldSelectEmptyOption={selectedRecordIds?.length === 0}
|
||||
hotkeyScope={recordPickerInstanceId}
|
||||
isFiltered={!!recordPickerSearchFilter}
|
||||
{...{
|
||||
EmptyIcon,
|
||||
emptyLabel,
|
||||
onCancel,
|
||||
onRecordSelected,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{records.recordsToSelect.length > 0 && isDefined(onCreate) && (
|
||||
<DropdownMenuSeparator />
|
||||
)}
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
export const getDropdownFocusIdForRecordField = (
|
||||
recordId: string,
|
||||
fieldMetadataId: string,
|
||||
componentType: 'table-cell' | 'inline-cell',
|
||||
) => {
|
||||
return `dropdown-${componentType}-record-${recordId}-field-${fieldMetadataId}`;
|
||||
};
|
||||
Reference in New Issue
Block a user