diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuItemNumberInput.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuItemNumberInput.tsx index 399ad04b72..b68ee73497 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuItemNumberInput.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuItemNumberInput.tsx @@ -1,44 +1,55 @@ +import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; +import { useRegisterInputEvents } from '@/object-record/record-field/ui/meta-types/input/hooks/useRegisterInputEvents'; import { TextInput } from '@/ui/input/components/TextInput'; +import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; +import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById'; +import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; import styled from '@emotion/styled'; -import { useState } from 'react'; -import { Key } from 'ts-key-enum'; +import { useRef, useState } from 'react'; import { isDefined } from 'twenty-shared/utils'; +import { type IconComponent } from 'twenty-ui/display'; import { canBeCastAsNumberOrNull, castAsNumberOrNull, } from '~/utils/cast-as-number-or-null'; type CommandMenuItemNumberInputProps = { + id: string; + label: string; + Icon?: IconComponent; value: string; onChange: (value: number | null) => void; onValidate?: (value: number | null) => boolean; placeholder?: string; }; + const StyledRightAlignedTextInput = styled(TextInput)` input { text-align: right; } `; + export const CommandMenuItemNumberInput = ({ + id, + label, + Icon, value, onChange, onValidate, placeholder, }: CommandMenuItemNumberInputProps) => { + const inputRef = useRef(null); + const focusId = `${id}-input`; const [draftValue, setDraftValue] = useState(value); const [hasError, setHasError] = useState(false); - const handleChange = (text: string) => { - setDraftValue(text); - if (hasError) { - setHasError(false); - } - }; + const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack(); + const { removeFocusItemFromFocusStackById } = + useRemoveFocusItemFromFocusStackById(); - const handleCommit = () => { + const handleCommit = (draftValue: string) => { if (!canBeCastAsNumberOrNull(draftValue)) { setHasError(true); - setDraftValue(value); return; } @@ -58,32 +69,76 @@ export const CommandMenuItemNumberInput = ({ const handleFocus = (event: React.FocusEvent) => { event.target.select(); + pushFocusItemToFocusStack({ + focusId, + component: { + type: FocusComponentType.TEXT_INPUT, + instanceId: focusId, + }, + globalHotkeysConfig: { + enableGlobalHotkeysConflictingWithKeyboard: false, + }, + }); }; const handleBlur = () => { - handleCommit(); + removeFocusItemFromFocusStackById({ focusId }); }; - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === Key.Enter || event.key === Key.Escape) { - event.stopPropagation(); - handleCommit(); - } else { - event.stopPropagation(); + const handleEscape = () => { + setDraftValue(value); + inputRef.current?.blur(); + }; + + const handleClickOutside = () => { + handleCommit(draftValue); + }; + + const handleEnter = () => { + handleCommit(draftValue); + inputRef.current?.blur(); + }; + + useRegisterInputEvents({ + focusId, + inputRef: inputRef, + inputValue: draftValue, + onEscape: handleEscape, + onEnter: handleEnter, + onClickOutside: handleClickOutside, + }); + + const handleChange = (text: string) => { + setDraftValue(text); + if (hasError) { + setHasError(false); } }; + const focusInput = () => { + inputRef.current?.focus(); + }; + return ( - + } /> ); }; diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuItemTextInput.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuItemTextInput.tsx index a2fca0f082..c333eca5fd 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuItemTextInput.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuItemTextInput.tsx @@ -1,9 +1,17 @@ +import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; +import { useRegisterInputEvents } from '@/object-record/record-field/ui/meta-types/input/hooks/useRegisterInputEvents'; import { TextInput } from '@/ui/input/components/TextInput'; +import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; +import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById'; +import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; import styled from '@emotion/styled'; -import { useState } from 'react'; -import { Key } from 'ts-key-enum'; +import { useRef, useState } from 'react'; +import { type IconComponent } from 'twenty-ui/display'; type CommandMenuItemTextInputProps = { + id: string; + label: string; + Icon?: IconComponent; value: string; onChange: (value: string) => void; placeholder?: string; @@ -16,46 +24,84 @@ const StyledRightAlignedTextInput = styled(TextInput)` `; export const CommandMenuItemTextInput = ({ + id, + label, + Icon, value, onChange, placeholder, }: CommandMenuItemTextInputProps) => { + const inputRef = useRef(null); + const focusId = `${id}-input`; const [draftValue, setDraftValue] = useState(value); - const handleChange = (text: string) => { - setDraftValue(text); - }; - - const handleCommit = () => { - onChange(draftValue); - }; + const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack(); + const { removeFocusItemFromFocusStackById } = + useRemoveFocusItemFromFocusStackById(); const handleFocus = (event: React.FocusEvent) => { event.target.select(); + pushFocusItemToFocusStack({ + focusId, + component: { + type: FocusComponentType.TEXT_INPUT, + instanceId: focusId, + }, + globalHotkeysConfig: { + enableGlobalHotkeysConflictingWithKeyboard: false, + }, + }); }; const handleBlur = () => { - handleCommit(); + removeFocusItemFromFocusStackById({ focusId }); }; - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === Key.Enter || event.key === Key.Escape) { - event.stopPropagation(); - handleCommit(); - } else { - event.stopPropagation(); - } + const handleEscape = () => { + setDraftValue(value); + inputRef.current?.blur(); + }; + + const handleEnter = () => { + onChange(draftValue); + inputRef.current?.blur(); + }; + + const handleClickOutside = () => { + onChange(draftValue); + }; + + useRegisterInputEvents({ + focusId, + inputRef: inputRef, + inputValue: draftValue, + onEscape: handleEscape, + onEnter: handleEnter, + onClickOutside: handleClickOutside, + }); + + const focusInput = () => { + inputRef.current?.focus(); }; return ( - + } /> ); }; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/chart-settings/ChartSettingItem.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/chart-settings/ChartSettingItem.tsx index 74d20a6041..f3f0ba5be3 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/chart-settings/ChartSettingItem.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/chart-settings/ChartSettingItem.tsx @@ -110,28 +110,24 @@ export const ChartSettingItem = ({ return ( - - !isDefined(value) || - isMinMaxRangeValid( - item.id as - | CHART_CONFIGURATION_SETTING_IDS.MIN_RANGE - | CHART_CONFIGURATION_SETTING_IDS.MAX_RANGE, - value, - configuration, - ) - } - placeholder={ - item.inputPlaceholder ? t(item.inputPlaceholder) : undefined - } - /> + value={stringValue} + onChange={handleInputChange} + onValidate={(value) => + !isDefined(value) || + isMinMaxRangeValid( + item.id as + | CHART_CONFIGURATION_SETTING_IDS.MIN_RANGE + | CHART_CONFIGURATION_SETTING_IDS.MAX_RANGE, + value, + configuration, + ) + } + placeholder={ + item.inputPlaceholder ? t(item.inputPlaceholder) : undefined } /> @@ -144,18 +140,14 @@ export const ChartSettingItem = ({ return ( - + value={stringValue} + onChange={handleTextInputChange} + placeholder={ + item.inputPlaceholder ? t(item.inputPlaceholder) : undefined } />