diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCell.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCell.tsx index 092119ec4..d8ac5328a 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCell.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCell.tsx @@ -17,7 +17,7 @@ import { RecordFieldComponentInstanceContext } from '@/object-record/record-fiel import { isInlineCellInEditModeScopedState } from '@/object-record/record-inline-cell/states/isInlineCellInEditModeScopedState'; import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField'; import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId'; -import { currentFocusIdSelector } from '@/ui/utilities/focus/states/currentFocusIdSelector'; +import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDropdownFocusIdState'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { useIcons } from 'twenty-ui/display'; @@ -129,17 +129,17 @@ export const RecordInlineCell = ({ const handleClickOutside: FieldInputClickOutsideEvent = useRecoilCallback( ({ snapshot }) => (persistField, event) => { - const currentFocusId = snapshot - .getLoadable(currentFocusIdSelector) + const currentDropdownFocusId = snapshot + .getLoadable(activeDropdownFocusIdState) .getValue(); - const expectedFocusId = getDropdownFocusIdForRecordField( + const expectedDropdownFocusId = getDropdownFocusIdForRecordField( recordId, fieldDefinition.fieldMetadataId, 'inline-cell', ); - if (currentFocusId !== expectedFocusId) { + if (currentDropdownFocusId !== expectedDropdownFocusId) { return; } event.preventDefault(); 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 c424f98e1..1c515a69b 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 @@ -12,7 +12,6 @@ import { getMultipleRecordPickerSelectableListId } from '@/object-record/record- import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; -import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; @@ -39,10 +38,6 @@ export const MultipleRecordPickerMenuItems = ({ componentInstanceId, ); - const { resetSelectedItem } = useSelectableList( - selectableListComponentInstanceId, - ); - const multipleRecordPickerPickableMorphItemsState = useRecoilComponentCallbackStateV2( multipleRecordPickerPickableMorphItemsComponentState, @@ -107,7 +102,6 @@ export const MultipleRecordPickerMenuItems = ({ onChange={(morphItem) => { handleChange(morphItem); onChange?.(morphItem); - resetSelectedItem(); }} /> ); diff --git a/packages/twenty-front/src/modules/ui/input/constants/SlashMenuListId.ts b/packages/twenty-front/src/modules/ui/input/constants/SlashMenuListId.ts new file mode 100644 index 000000000..ac1e4f518 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/constants/SlashMenuListId.ts @@ -0,0 +1 @@ +export const SLASH_MENU_LIST_ID = 'editor-slash-menu-list'; diff --git a/packages/twenty-front/src/modules/ui/input/editor/components/CustomSlashMenu.tsx b/packages/twenty-front/src/modules/ui/input/editor/components/CustomSlashMenu.tsx index aa360c8bc..080e2b8fe 100644 --- a/packages/twenty-front/src/modules/ui/input/editor/components/CustomSlashMenu.tsx +++ b/packages/twenty-front/src/modules/ui/input/editor/components/CustomSlashMenu.tsx @@ -1,42 +1,51 @@ -import type { SuggestionMenuProps } from '@blocknote/react'; import styled from '@emotion/styled'; +import { autoUpdate, useFloating } from '@floating-ui/react'; +import { motion } from 'framer-motion'; +import { useEffect } from 'react'; +import { createPortal } from 'react-dom'; import { SLASH_MENU_DROPDOWN_CLICK_OUTSIDE_ID } from '@/ui/input/constants/SlashMenuDropdownClickOutsideId'; +import { SLASH_MENU_LIST_ID } from '@/ui/input/constants/SlashMenuListId'; +import { CustomSlashMenuListItem } from '@/ui/input/editor/components/CustomSlashMenuListItem'; +import { + CustomSlashMenuProps, + SuggestionItem, +} from '@/ui/input/editor/components/types'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer'; -import { autoUpdate, useFloating } from '@floating-ui/react'; -import { motion } from 'framer-motion'; -import { createPortal } from 'react-dom'; -import { IconComponent } from 'twenty-ui/display'; -import { MenuItemSuggestion } from 'twenty-ui/navigation'; +import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; +import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; +import { isDefined } from 'twenty-shared/utils'; -export type SuggestionItem = { - title: string; - onItemClick: () => void; - aliases?: string[]; - Icon?: IconComponent; -}; - -type CustomSlashMenuProps = SuggestionMenuProps; +export type { SuggestionItem }; const StyledContainer = styled.div` height: 1px; width: 1px; `; -const StyledInnerContainer = styled.div` - color: ${({ theme }) => theme.font.color.secondary}; - height: 250px; - width: 100%; -`; - -export const CustomSlashMenu = (props: CustomSlashMenuProps) => { +export const CustomSlashMenu = ({ + items, + selectedIndex, +}: CustomSlashMenuProps) => { const { refs, floatingStyles } = useFloating({ placement: 'bottom-start', whileElementsMounted: autoUpdate, }); + const { setSelectedItemId } = useSelectableList(SLASH_MENU_LIST_ID); + + useEffect(() => { + if (!isDefined(selectedIndex)) return; + + const selectedItem = items[selectedIndex]; + + if (isDefined(selectedItem)) { + setSelectedItemId(selectedItem.title); + } + }, [items, selectedIndex, setSelectedItemId]); + return ( {createPortal( @@ -50,21 +59,19 @@ export const CustomSlashMenu = (props: CustomSlashMenuProps) => { style={floatingStyles} data-click-outside-id={SLASH_MENU_DROPDOWN_CLICK_OUTSIDE_ID} > - - - - {props.items.map((item, index) => ( - item.onItemClick()} - text={item.title} - LeftIcon={item.Icon} - selected={props.selectedIndex === index} - /> + + + item.title)} + > + {items.map((item) => ( + ))} - - - + + + , document.body, diff --git a/packages/twenty-front/src/modules/ui/input/editor/components/CustomSlashMenuListItem.tsx b/packages/twenty-front/src/modules/ui/input/editor/components/CustomSlashMenuListItem.tsx new file mode 100644 index 000000000..7414a644a --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/editor/components/CustomSlashMenuListItem.tsx @@ -0,0 +1,38 @@ +import { SLASH_MENU_LIST_ID } from '@/ui/input/constants/SlashMenuListId'; +import { SuggestionItem } from '@/ui/input/editor/components/types'; +import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; +import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; +import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector'; +import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; +import { MenuItemSuggestion } from 'twenty-ui/navigation'; + +export type CustomSlashMenuListItemProps = { + item: SuggestionItem; +}; + +export const CustomSlashMenuListItem = ({ + item, +}: CustomSlashMenuListItemProps) => { + const { resetSelectedItem } = useSelectableList(SLASH_MENU_LIST_ID); + + const isSelectedItem = useRecoilComponentFamilyValueV2( + isSelectedItemIdComponentFamilySelector, + item.title, + ); + + const handleClick = () => { + resetSelectedItem(); + item.onItemClick(); + }; + + return ( + + + + ); +}; diff --git a/packages/twenty-front/src/modules/ui/input/editor/components/types.ts b/packages/twenty-front/src/modules/ui/input/editor/components/types.ts new file mode 100644 index 000000000..df22163b5 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/editor/components/types.ts @@ -0,0 +1,11 @@ +import type { SuggestionMenuProps } from '@blocknote/react'; +import { IconComponent } from 'twenty-ui/display'; + +export type SuggestionItem = { + title: string; + onItemClick: () => void; + aliases?: string[]; + Icon?: IconComponent; +}; + +export type CustomSlashMenuProps = SuggestionMenuProps; diff --git a/packages/twenty-front/src/modules/ui/layout/selectable-list/components/SelectableListItem.tsx b/packages/twenty-front/src/modules/ui/layout/selectable-list/components/SelectableListItem.tsx index 8dc5d04ea..9cc38d274 100644 --- a/packages/twenty-front/src/modules/ui/layout/selectable-list/components/SelectableListItem.tsx +++ b/packages/twenty-front/src/modules/ui/layout/selectable-list/components/SelectableListItem.tsx @@ -3,8 +3,17 @@ import { ReactNode, useEffect, useRef } from 'react'; import { SelectableListItemHotkeyEffect } from '@/ui/layout/selectable-list/components/SelectableListItemHotkeyEffect'; import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector'; import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; +import styled from '@emotion/styled'; import { isDefined } from 'twenty-shared/utils'; +const StyledListItemContainer = styled.div` + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + box-sizing: border-box; +`; + export type SelectableListItemProps = { itemId: string; children: ReactNode; @@ -21,11 +30,14 @@ export const SelectableListItem = ({ itemId, ); - const scrollRef = useRef(null); + const listItemRef = useRef(null); useEffect(() => { if (isSelectedItemId) { - scrollRef.current?.scrollIntoView({ block: 'nearest' }); + listItemRef.current?.scrollIntoView({ + behavior: 'smooth', + block: 'center', + }); } }, [isSelectedItemId]); @@ -34,7 +46,9 @@ export const SelectableListItem = ({ {isSelectedItemId && isDefined(onEnter) && ( )} - {children} + + {children} + ); };