Add a ⏎ shortcut on Select options (#5641)
fixes #5540 Added onkeyDown to 1. Create new option and 2. Move focus to it. [screen-capture (6).webm](https://github.com/twentyhq/twenty/assets/69167444/ede54ad8-22db-4b09-9617-4d999c6c08c7) --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -1,6 +1,8 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import { Controller, useFormContext } from 'react-hook-form';
|
import { Controller, useFormContext } from 'react-hook-form';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { DropResult } from '@hello-pangea/dnd';
|
import { DropResult } from '@hello-pangea/dnd';
|
||||||
|
import { Key } from 'ts-key-enum';
|
||||||
import { IconPlus } from 'twenty-ui';
|
import { IconPlus } from 'twenty-ui';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
@ -19,6 +21,8 @@ import { CardContent } from '@/ui/layout/card/components/CardContent';
|
|||||||
import { CardFooter } from '@/ui/layout/card/components/CardFooter';
|
import { CardFooter } from '@/ui/layout/card/components/CardFooter';
|
||||||
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
|
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
|
||||||
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
|
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
|
||||||
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
|
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
import { moveArrayItem } from '~/utils/array/moveArrayItem';
|
import { moveArrayItem } from '~/utils/array/moveArrayItem';
|
||||||
import { toSpliced } from '~/utils/array/toSpliced';
|
import { toSpliced } from '~/utils/array/toSpliced';
|
||||||
@ -78,6 +82,7 @@ const StyledButton = styled(LightButton)`
|
|||||||
export const SettingsDataModelFieldSelectForm = ({
|
export const SettingsDataModelFieldSelectForm = ({
|
||||||
fieldMetadataItem,
|
fieldMetadataItem,
|
||||||
}: SettingsDataModelFieldSelectFormProps) => {
|
}: SettingsDataModelFieldSelectFormProps) => {
|
||||||
|
const [focusedOptionId, setFocusedOptionId] = useState('');
|
||||||
const { initialDefaultValue, initialOptions } =
|
const { initialDefaultValue, initialOptions } =
|
||||||
useSelectSettingsFormInitialValues({ fieldMetadataItem });
|
useSelectSettingsFormInitialValues({ fieldMetadataItem });
|
||||||
|
|
||||||
@ -167,6 +172,37 @@ export const SettingsDataModelFieldSelectForm = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getOptionsWithNewOption = () => {
|
||||||
|
const currentOptions = getValues('options');
|
||||||
|
|
||||||
|
const newOptions = [
|
||||||
|
...currentOptions,
|
||||||
|
generateNewSelectOption(currentOptions),
|
||||||
|
];
|
||||||
|
|
||||||
|
return newOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddOption = () => {
|
||||||
|
const newOptions = getOptionsWithNewOption();
|
||||||
|
|
||||||
|
setFormValue('options', newOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
useScopedHotkeys(
|
||||||
|
Key.Enter,
|
||||||
|
() => {
|
||||||
|
const newOptions = getOptionsWithNewOption();
|
||||||
|
|
||||||
|
setFormValue('options', newOptions);
|
||||||
|
|
||||||
|
const lastOptionId = newOptions[newOptions.length - 1].id;
|
||||||
|
|
||||||
|
setFocusedOptionId(lastOptionId);
|
||||||
|
},
|
||||||
|
AppHotkeyScope.App,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Controller
|
<Controller
|
||||||
@ -197,6 +233,7 @@ export const SettingsDataModelFieldSelectForm = ({
|
|||||||
<SettingsDataModelFieldSelectFormOptionRow
|
<SettingsDataModelFieldSelectFormOptionRow
|
||||||
key={option.id}
|
key={option.id}
|
||||||
option={option}
|
option={option}
|
||||||
|
focused={focusedOptionId === option.id}
|
||||||
onChange={(nextOption) => {
|
onChange={(nextOption) => {
|
||||||
const nextOptions = toSpliced(
|
const nextOptions = toSpliced(
|
||||||
options,
|
options,
|
||||||
@ -245,9 +282,7 @@ export const SettingsDataModelFieldSelectForm = ({
|
|||||||
<StyledButton
|
<StyledButton
|
||||||
title="Add option"
|
title="Add option"
|
||||||
Icon={IconPlus}
|
Icon={IconPlus}
|
||||||
onClick={() =>
|
onClick={handleAddOption}
|
||||||
onChange([...options, generateNewSelectOption(options)])
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</StyledFooter>
|
</StyledFooter>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useMemo } from 'react';
|
import { useEffect, useMemo, useRef } from 'react';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import {
|
import {
|
||||||
@ -31,6 +31,7 @@ type SettingsDataModelFieldSelectFormOptionRowProps = {
|
|||||||
onSetAsDefault?: () => void;
|
onSetAsDefault?: () => void;
|
||||||
onRemoveAsDefault?: () => void;
|
onRemoveAsDefault?: () => void;
|
||||||
option: FieldMetadataItemOption;
|
option: FieldMetadataItemOption;
|
||||||
|
focused?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledRow = styled.div`
|
const StyledRow = styled.div`
|
||||||
@ -63,12 +64,18 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
|
|||||||
onSetAsDefault,
|
onSetAsDefault,
|
||||||
onRemoveAsDefault,
|
onRemoveAsDefault,
|
||||||
option,
|
option,
|
||||||
|
focused,
|
||||||
}: SettingsDataModelFieldSelectFormOptionRowProps) => {
|
}: SettingsDataModelFieldSelectFormOptionRowProps) => {
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const dropdownIds = useMemo(() => {
|
const dropdownIds = useMemo(() => {
|
||||||
const baseScopeId = `select-field-option-row-${v4()}`;
|
const baseScopeId = `select-field-option-row-${v4()}`;
|
||||||
return { color: `${baseScopeId}-color`, actions: `${baseScopeId}-actions` };
|
return {
|
||||||
|
color: `${baseScopeId}-color`,
|
||||||
|
actions: `${baseScopeId}-actions`,
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { closeDropdown: closeColorDropdown } = useDropdown(dropdownIds.color);
|
const { closeDropdown: closeColorDropdown } = useDropdown(dropdownIds.color);
|
||||||
@ -76,6 +83,12 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
|
|||||||
dropdownIds.actions,
|
dropdownIds.actions,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (focused === true) {
|
||||||
|
inputRef.current?.focus();
|
||||||
|
}
|
||||||
|
}, [focused]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledRow className={className}>
|
<StyledRow className={className}>
|
||||||
<IconGripVertical
|
<IconGripVertical
|
||||||
@ -109,9 +122,15 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StyledOptionInput
|
<StyledOptionInput
|
||||||
|
ref={inputRef}
|
||||||
|
disableHotkeys
|
||||||
value={option.label}
|
value={option.label}
|
||||||
onChange={(label) =>
|
onChange={(label) =>
|
||||||
onChange({ ...option, label, value: getOptionValueFromLabel(label) })
|
onChange({
|
||||||
|
...option,
|
||||||
|
label,
|
||||||
|
value: getOptionValueFromLabel(label),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
RightIcon={isDefault ? IconCheck : undefined}
|
RightIcon={isDefault ? IconCheck : undefined}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user