From cc7a37b0cce6f0def3de2a59b0746a84bd3bdd0a Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Tue, 17 Jun 2025 16:10:44 +0200 Subject: [PATCH] Fix "No results found" in record pickers (#12685) This PR fixes small bugs around the "No results found" record picker. Before : https://github.com/user-attachments/assets/e2eee648-1cb1-40fb-ad3c-fe4724f7314e After : https://github.com/user-attachments/assets/714e6dea-3c65-4e04-9fef-ee718c94bbba Minor improvements : - Created a new component `RecordPickerNoRecordFoundMenuItem` to abstract this "No records found" menu item - Removed `not-allowed` cursor from MenuItem in disabled state. Fixes https://github.com/twentyhq/twenty/issues/12666 --- .../RecordPickerNoRecordFoundMenuItem.tsx | 5 + .../MultipleRecordPickerMenuItems.tsx | 11 +- .../SingleRecordPickerMenuItems.tsx | 113 ++++++++---------- .../SingleRecordPickerMenuItemsWithSearch.tsx | 81 ++++++++----- .../components/MenuItemDraggable.tsx | 2 +- .../components/StyledMenuItemBase.tsx | 6 +- 6 files changed, 106 insertions(+), 112 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-picker/components/RecordPickerNoRecordFoundMenuItem.tsx diff --git a/packages/twenty-front/src/modules/object-record/record-picker/components/RecordPickerNoRecordFoundMenuItem.tsx b/packages/twenty-front/src/modules/object-record/record-picker/components/RecordPickerNoRecordFoundMenuItem.tsx new file mode 100644 index 000000000..0df81b487 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-picker/components/RecordPickerNoRecordFoundMenuItem.tsx @@ -0,0 +1,5 @@ +import { MenuItem } from 'twenty-ui/navigation'; + +export const RecordPickerNoRecordFoundMenuItem = () => { + return ; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItems.tsx b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItems.tsx index 842f9e774..4e0c0cd89 100644 --- a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItems.tsx +++ b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItems.tsx @@ -1,5 +1,6 @@ import styled from '@emotion/styled'; +import { RecordPickerNoRecordFoundMenuItem } from '@/object-record/record-picker/components/RecordPickerNoRecordFoundMenuItem'; import { MultipleRecordPickerFetchMoreLoader } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerFetchMoreLoader'; import { MultipleRecordPickerMenuItem } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItem'; import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext'; @@ -22,14 +23,6 @@ export const StyledSelectableItem = styled(SelectableListItem)` width: 100%; `; -const StyledEmptyText = styled.div` - align-items: center; - color: ${({ theme }) => theme.font.color.light}; - display: flex; - justify-content: center; - padding: ${({ theme }) => theme.spacing(2)}; -`; - type MultipleRecordPickerMenuItemsProps = { onChange?: (morphItem: RecordPickerPickableMorphItem) => void; }; @@ -87,7 +80,7 @@ export const MultipleRecordPickerMenuItems = ({ return ( {pickableRecordIds.length === 0 ? ( - No results found + ) : ( void; selectedRecord?: SingleRecordPickerRecord; hotkeyScope?: string; - isFiltered: boolean; }; -const StyledContainer = styled.div` - display: flex; -`; - export const SingleRecordPickerMenuItems = ({ EmptyIcon, emptyLabel, @@ -49,10 +41,7 @@ export const SingleRecordPickerMenuItems = ({ onRecordSelected, selectedRecord, hotkeyScope = SingleRecordPickerHotkeyScope.SingleRecordPicker, - isFiltered, }: SingleRecordPickerMenuItemsProps) => { - const containerRef = useRef(null); - const selectNone = emptyLabel ? { __typename: '', @@ -104,60 +93,54 @@ export const SingleRecordPickerMenuItems = ({ ); return ( - - - - {loading && !isFiltered ? ( - - ) : recordsInDropdown.length === 0 && !loading ? ( - <> - ) : ( - recordsInDropdown?.map((record) => { - switch (record.id) { - case 'select-none': { - return ( - emptyLabel && ( - { - setSelectedRecordId(undefined); - onRecordSelected(); - }} - > - { - setSelectedRecordId(undefined); - onRecordSelected(); - }} - LeftIcon={EmptyIcon} - text={emptyLabel} - selected={isUndefined(selectedRecordId)} - focused={isSelectedSelectNoneButton} - /> - - ) - ); - } - default: { - return ( - + {loading ? ( + + ) : ( + recordsInDropdown?.map((record) => { + switch (record.id) { + case 'select-none': { + return ( + emptyLabel && ( + { + setSelectedRecordId(undefined); + onRecordSelected(); + }} + > + { + setSelectedRecordId(undefined); + onRecordSelected(); + }} + LeftIcon={EmptyIcon} + text={emptyLabel} + selected={isUndefined(selectedRecordId)} + focused={isSelectedSelectNoneButton} /> - ); - } - } - }) - )} - - - + + ) + ); + } + default: { + return ( + + ); + } + } + }) + )} + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItemsWithSearch.tsx b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItemsWithSearch.tsx index 4c2f1740b..2e22f4b5e 100644 --- a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItemsWithSearch.tsx +++ b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItemsWithSearch.tsx @@ -1,5 +1,6 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject'; +import { RecordPickerNoRecordFoundMenuItem } from '@/object-record/record-picker/components/RecordPickerNoRecordFoundMenuItem'; import { SingleRecordPickerMenuItems, SingleRecordPickerMenuItemsProps, @@ -15,6 +16,7 @@ import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/Dropdow import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { isNonEmptyString } from '@sniptt/guards'; import { isDefined } from 'twenty-shared/utils'; import { IconPlus } from 'twenty-ui/display'; @@ -69,13 +71,14 @@ export const SingleRecordPickerMenuItemsWithSearch = ({ const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords; - const createNewButton = isDefined(onCreate) && ( - onCreate?.(recordPickerSearchFilter)} - LeftIcon={IconPlus} - text="Add New" - /> - ); + const searchHasNoResults = + isNonEmptyString(recordPickerSearchFilter) && + records.recordsToSelect.length === 0 && + !records.loading; + + const handleCreateNew = () => { + onCreate?.(recordPickerSearchFilter); + }; return ( <> @@ -84,23 +87,29 @@ export const SingleRecordPickerMenuItemsWithSearch = ({ {isDefined(onCreate) && hasObjectUpdatePermissions && ( <> - {createNewButton} + )} - + + {searchHasNoResults && } + + )} @@ -112,23 +121,29 @@ export const SingleRecordPickerMenuItemsWithSearch = ({ {layoutDirection === 'search-bar-on-top' && ( <> - + + + {searchHasNoResults && } + {isDefined(onCreate) && hasObjectUpdatePermissions && ( <> - {createNewButton} + )} diff --git a/packages/twenty-ui/src/navigation/menu-item/components/MenuItemDraggable.tsx b/packages/twenty-ui/src/navigation/menu-item/components/MenuItemDraggable.tsx index 59cb8771f..9a751d4b8 100644 --- a/packages/twenty-ui/src/navigation/menu-item/components/MenuItemDraggable.tsx +++ b/packages/twenty-ui/src/navigation/menu-item/components/MenuItemDraggable.tsx @@ -36,7 +36,7 @@ export const MenuItemDraggable = ({ const cursorType = showGrip ? isDragDisabled - ? 'not-allowed' + ? 'default' : 'drag' : 'default'; diff --git a/packages/twenty-ui/src/navigation/menu-item/internals/components/StyledMenuItemBase.tsx b/packages/twenty-ui/src/navigation/menu-item/internals/components/StyledMenuItemBase.tsx index 9e8806fb4..6aa395f83 100644 --- a/packages/twenty-ui/src/navigation/menu-item/internals/components/StyledMenuItemBase.tsx +++ b/packages/twenty-ui/src/navigation/menu-item/internals/components/StyledMenuItemBase.tsx @@ -131,7 +131,7 @@ export const StyledDraggableItem = styled.div` export const StyledHoverableMenuItemBase = styled(StyledMenuItemBase)<{ disabled?: boolean; isIconDisplayedOnHoverOnly?: boolean; - cursor?: 'drag' | 'default' | 'not-allowed'; + cursor?: 'drag' | 'default'; }>` ${({ isIconDisplayedOnHoverOnly, theme }) => isIconDisplayedOnHoverOnly && @@ -156,14 +156,12 @@ export const StyledHoverableMenuItemBase = styled(StyledMenuItemBase)<{ cursor: ${({ cursor, disabled }) => { if (!isUndefined(disabled) && disabled !== false) { - return 'not-allowed'; + return 'default'; } switch (cursor) { case 'drag': return 'grab'; - case 'not-allowed': - return 'not-allowed'; default: return 'pointer'; }