refactor: use react-hook-form for Field type config forms (#5326)
Closes #4295 Note: for the sake of an easier code review, I did not rename/move some files and added "todo" comments instead so Github is able to match those files with their previous version.
This commit is contained in:
@ -1,17 +1,26 @@
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconCheck, IconX } from 'twenty-ui';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
||||
|
||||
type SettingsDataModelDefaultValueFormProps = {
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
onChange?: (defaultValue: SettingsDataModelDefaultValue) => void;
|
||||
value?: SettingsDataModelDefaultValue;
|
||||
};
|
||||
// TODO: rename to SettingsDataModelFieldBooleanForm and move to settings/data-model/fields/forms/components
|
||||
|
||||
export type SettingsDataModelDefaultValue = any;
|
||||
export const settingsDataModelFieldBooleanFormSchema = z.object({
|
||||
defaultValue: z.boolean(),
|
||||
});
|
||||
|
||||
type SettingsDataModelFieldBooleanFormValues = z.infer<
|
||||
typeof settingsDataModelFieldBooleanFormSchema
|
||||
>;
|
||||
|
||||
type SettingsDataModelFieldBooleanFormProps = {
|
||||
className?: string;
|
||||
fieldMetadataItem?: Pick<FieldMetadataItem, 'defaultValue'>;
|
||||
};
|
||||
|
||||
const StyledContainer = styled(CardContent)`
|
||||
padding-bottom: ${({ theme }) => theme.spacing(3.5)};
|
||||
@ -26,34 +35,42 @@ const StyledLabel = styled.span`
|
||||
margin-top: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
export const SettingsDataModelDefaultValueForm = ({
|
||||
export const SettingsDataModelFieldBooleanForm = ({
|
||||
className,
|
||||
disabled,
|
||||
onChange,
|
||||
value,
|
||||
}: SettingsDataModelDefaultValueFormProps) => {
|
||||
fieldMetadataItem,
|
||||
}: SettingsDataModelFieldBooleanFormProps) => {
|
||||
const { control } = useFormContext<SettingsDataModelFieldBooleanFormValues>();
|
||||
|
||||
const initialValue = fieldMetadataItem?.defaultValue ?? true;
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledLabel>Default Value</StyledLabel>
|
||||
<Select
|
||||
className={className}
|
||||
fullWidth
|
||||
disabled={disabled}
|
||||
dropdownId="object-field-default-value-select"
|
||||
value={value}
|
||||
onChange={(value) => onChange?.(value)}
|
||||
options={[
|
||||
{
|
||||
value: true,
|
||||
label: 'True',
|
||||
Icon: IconCheck,
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: 'False',
|
||||
Icon: IconX,
|
||||
},
|
||||
]}
|
||||
<Controller
|
||||
name="defaultValue"
|
||||
control={control}
|
||||
defaultValue={initialValue}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Select
|
||||
className={className}
|
||||
fullWidth
|
||||
dropdownId="object-field-default-value-select"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
options={[
|
||||
{
|
||||
value: true,
|
||||
label: 'True',
|
||||
Icon: IconCheck,
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: 'False',
|
||||
Icon: IconX,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
|
||||
@ -1,39 +1,66 @@
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode';
|
||||
import { SETTINGS_FIELD_CURRENCY_CODES } from '@/settings/data-model/constants/SettingsFieldCurrencyCodes';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
||||
|
||||
import { SETTINGS_FIELD_CURRENCY_CODES } from '../constants/SettingsFieldCurrencyCodes';
|
||||
// TODO: rename to SettingsDataModelFieldCurrencyForm and move to settings/data-model/fields/forms/components
|
||||
|
||||
export type SettingsObjectFieldCurrencyFormValues = {
|
||||
currencyCode: CurrencyCode;
|
||||
};
|
||||
export const settingsDataModelFieldCurrencyFormSchema = z.object({
|
||||
defaultValue: z.object({
|
||||
currencyCode: z.nativeEnum(CurrencyCode),
|
||||
}),
|
||||
});
|
||||
|
||||
type SettingsObjectFieldCurrencyFormProps = {
|
||||
type SettingsDataModelFieldCurrencyFormValues = z.infer<
|
||||
typeof settingsDataModelFieldCurrencyFormSchema
|
||||
>;
|
||||
|
||||
type SettingsDataModelFieldCurrencyFormProps = {
|
||||
disabled?: boolean;
|
||||
onChange: (values: Partial<SettingsObjectFieldCurrencyFormValues>) => void;
|
||||
values: SettingsObjectFieldCurrencyFormValues;
|
||||
fieldMetadataItem?: Pick<FieldMetadataItem, 'defaultValue'>;
|
||||
};
|
||||
|
||||
export const SettingsObjectFieldCurrencyForm = ({
|
||||
disabled,
|
||||
onChange,
|
||||
values,
|
||||
}: SettingsObjectFieldCurrencyFormProps) => (
|
||||
<CardContent>
|
||||
<Select
|
||||
fullWidth
|
||||
disabled={disabled}
|
||||
label="Default Unit"
|
||||
dropdownId="currency-unit-select"
|
||||
value={values.currencyCode}
|
||||
options={Object.entries(SETTINGS_FIELD_CURRENCY_CODES).map(
|
||||
([value, { label, Icon }]) => ({
|
||||
label,
|
||||
value: value as CurrencyCode,
|
||||
Icon,
|
||||
}),
|
||||
)}
|
||||
onChange={(value) => onChange({ currencyCode: value })}
|
||||
/>
|
||||
</CardContent>
|
||||
const OPTIONS = Object.entries(SETTINGS_FIELD_CURRENCY_CODES).map(
|
||||
([value, { label, Icon }]) => ({
|
||||
label,
|
||||
value: value as CurrencyCode,
|
||||
Icon,
|
||||
}),
|
||||
);
|
||||
|
||||
export const SettingsDataModelFieldCurrencyForm = ({
|
||||
disabled,
|
||||
fieldMetadataItem,
|
||||
}: SettingsDataModelFieldCurrencyFormProps) => {
|
||||
const { control } =
|
||||
useFormContext<SettingsDataModelFieldCurrencyFormValues>();
|
||||
|
||||
const initialValue =
|
||||
(fieldMetadataItem?.defaultValue?.currencyCode as CurrencyCode) ??
|
||||
CurrencyCode.USD;
|
||||
|
||||
return (
|
||||
<CardContent>
|
||||
<Controller
|
||||
name="defaultValue.currencyCode"
|
||||
control={control}
|
||||
defaultValue={initialValue}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Select
|
||||
fullWidth
|
||||
disabled={disabled}
|
||||
label="Default Unit"
|
||||
dropdownId="currency-unit-select"
|
||||
value={value}
|
||||
options={OPTIONS}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,28 +1,46 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import styled from '@emotion/styled';
|
||||
import { useIcons } from 'twenty-ui';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation';
|
||||
import { validateMetadataLabel } from '@/object-metadata/utils/validateMetadataLabel';
|
||||
import { fieldMetadataItemSchema } from '@/object-metadata/validation-schemas/fieldMetadataItemSchema';
|
||||
import { IconPicker } from '@/ui/input/components/IconPicker';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { Field } from '~/generated-metadata/graphql';
|
||||
import { RelationMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { RELATION_TYPES } from '../constants/RelationTypes';
|
||||
import { RelationType } from '../types/RelationType';
|
||||
|
||||
export type SettingsObjectFieldRelationFormValues = {
|
||||
field: Pick<Field, 'icon' | 'label'>;
|
||||
objectMetadataId: string;
|
||||
type: RelationType;
|
||||
};
|
||||
// TODO: rename to SettingsDataModelFieldRelationForm and move to settings/data-model/fields/forms/components
|
||||
|
||||
type SettingsObjectFieldRelationFormProps = {
|
||||
disableFieldEdition?: boolean;
|
||||
disableRelationEdition?: boolean;
|
||||
onChange: (values: Partial<SettingsObjectFieldRelationFormValues>) => void;
|
||||
values: SettingsObjectFieldRelationFormValues;
|
||||
export const settingsDataModelFieldRelationFormSchema = z.object({
|
||||
relation: z.object({
|
||||
field: fieldMetadataItemSchema.pick({
|
||||
icon: true,
|
||||
label: true,
|
||||
}),
|
||||
objectMetadataId: z.string().uuid(),
|
||||
type: z.enum(
|
||||
Object.keys(RELATION_TYPES) as [RelationType, ...RelationType[]],
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
type SettingsDataModelFieldRelationFormValues = z.infer<
|
||||
typeof settingsDataModelFieldRelationFormSchema
|
||||
>;
|
||||
|
||||
type SettingsDataModelFieldRelationFormProps = {
|
||||
fieldMetadataItem?: Pick<
|
||||
FieldMetadataItem,
|
||||
'fromRelationMetadata' | 'toRelationMetadata' | 'type'
|
||||
>;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
@ -50,84 +68,119 @@ const StyledInputsContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const SettingsObjectFieldRelationForm = ({
|
||||
disableFieldEdition,
|
||||
disableRelationEdition,
|
||||
onChange,
|
||||
values,
|
||||
}: SettingsObjectFieldRelationFormProps) => {
|
||||
const RELATION_TYPE_OPTIONS = Object.entries(RELATION_TYPES)
|
||||
.filter(([value]) => 'ONE_TO_ONE' !== value)
|
||||
.map(([value, { label, Icon }]) => ({
|
||||
label,
|
||||
value: value as RelationType,
|
||||
Icon,
|
||||
}));
|
||||
|
||||
export const SettingsDataModelFieldRelationForm = ({
|
||||
fieldMetadataItem,
|
||||
}: SettingsDataModelFieldRelationFormProps) => {
|
||||
const { control } =
|
||||
useFormContext<SettingsDataModelFieldRelationFormValues>();
|
||||
const { getIcon } = useIcons();
|
||||
const { objectMetadataItems, findObjectMetadataItemById } =
|
||||
useFilteredObjectMetadataItems();
|
||||
const { objectMetadataItems } = useFilteredObjectMetadataItems();
|
||||
|
||||
const getRelationMetadata = useGetRelationMetadata();
|
||||
const {
|
||||
relationFieldMetadataItem,
|
||||
relationType,
|
||||
relationObjectMetadataItem,
|
||||
} =
|
||||
useMemo(
|
||||
() =>
|
||||
fieldMetadataItem ? getRelationMetadata({ fieldMetadataItem }) : null,
|
||||
[fieldMetadataItem, getRelationMetadata],
|
||||
) ?? {};
|
||||
|
||||
const disableFieldEdition =
|
||||
relationFieldMetadataItem && !relationFieldMetadataItem.isCustom;
|
||||
|
||||
const disableRelationEdition = !!relationFieldMetadataItem;
|
||||
|
||||
const selectedObjectMetadataItem =
|
||||
(values.objectMetadataId
|
||||
? findObjectMetadataItemById(values.objectMetadataId)
|
||||
: undefined) || objectMetadataItems[0];
|
||||
relationObjectMetadataItem ?? objectMetadataItems[0];
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledSelectsContainer>
|
||||
<Select
|
||||
label="Relation type"
|
||||
dropdownId="relation-type-select"
|
||||
fullWidth
|
||||
disabled={disableRelationEdition}
|
||||
value={values.type}
|
||||
options={Object.entries(RELATION_TYPES)
|
||||
.filter(([value]) => 'ONE_TO_ONE' !== value)
|
||||
.map(([value, { label, Icon }]) => ({
|
||||
label,
|
||||
value: value as RelationType,
|
||||
Icon,
|
||||
}))}
|
||||
onChange={(value) => onChange({ type: value })}
|
||||
<Controller
|
||||
name="relation.type"
|
||||
control={control}
|
||||
defaultValue={relationType ?? RelationMetadataType.OneToMany}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Select
|
||||
label="Relation type"
|
||||
dropdownId="relation-type-select"
|
||||
fullWidth
|
||||
disabled={disableRelationEdition}
|
||||
value={value}
|
||||
options={RELATION_TYPE_OPTIONS}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Select
|
||||
label="Object destination"
|
||||
dropdownId="object-destination-select"
|
||||
fullWidth
|
||||
disabled={disableRelationEdition}
|
||||
value={values.objectMetadataId}
|
||||
options={objectMetadataItems
|
||||
.filter((objectMetadataItem) =>
|
||||
isObjectMetadataAvailableForRelation(objectMetadataItem),
|
||||
)
|
||||
.map((objectMetadataItem) => ({
|
||||
label: objectMetadataItem.labelPlural,
|
||||
value: objectMetadataItem.id,
|
||||
Icon: getIcon(objectMetadataItem.icon),
|
||||
}))}
|
||||
onChange={(value) => onChange({ objectMetadataId: value })}
|
||||
<Controller
|
||||
name="relation.objectMetadataId"
|
||||
control={control}
|
||||
defaultValue={selectedObjectMetadataItem?.id}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Select
|
||||
label="Object destination"
|
||||
dropdownId="object-destination-select"
|
||||
fullWidth
|
||||
disabled={disableRelationEdition}
|
||||
value={value}
|
||||
options={objectMetadataItems
|
||||
.filter(isObjectMetadataAvailableForRelation)
|
||||
.map((objectMetadataItem) => ({
|
||||
label: objectMetadataItem.labelPlural,
|
||||
value: objectMetadataItem.id,
|
||||
Icon: getIcon(objectMetadataItem.icon),
|
||||
}))}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</StyledSelectsContainer>
|
||||
<StyledInputsLabel>
|
||||
Field on {selectedObjectMetadataItem?.labelPlural}
|
||||
</StyledInputsLabel>
|
||||
<StyledInputsContainer>
|
||||
<IconPicker
|
||||
disabled={disableFieldEdition}
|
||||
dropdownId="field-destination-icon-picker"
|
||||
selectedIconKey={values.field.icon || undefined}
|
||||
onChange={(value) =>
|
||||
onChange({
|
||||
field: { ...values.field, icon: value.iconKey },
|
||||
})
|
||||
<Controller
|
||||
name="relation.field.icon"
|
||||
control={control}
|
||||
defaultValue={
|
||||
relationFieldMetadataItem?.icon ??
|
||||
relationObjectMetadataItem?.icon ??
|
||||
'IconUsers'
|
||||
}
|
||||
variant="primary"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<IconPicker
|
||||
disabled={disableFieldEdition}
|
||||
dropdownId="field-destination-icon-picker"
|
||||
selectedIconKey={value ?? undefined}
|
||||
onChange={({ iconKey }) => onChange(iconKey)}
|
||||
variant="primary"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<TextInput
|
||||
disabled={disableFieldEdition}
|
||||
placeholder="Field name"
|
||||
value={values.field.label}
|
||||
onChange={(value) => {
|
||||
if (!value || validateMetadataLabel(value)) {
|
||||
onChange({
|
||||
field: { ...values.field, label: value },
|
||||
});
|
||||
}
|
||||
}}
|
||||
fullWidth
|
||||
<Controller
|
||||
name="relation.field.label"
|
||||
control={control}
|
||||
defaultValue={relationFieldMetadataItem?.label}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<TextInput
|
||||
disabled={disableFieldEdition}
|
||||
placeholder="Field name"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</StyledInputsContainer>
|
||||
</StyledContainer>
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import styled from '@emotion/styled';
|
||||
import { DropResult } from '@hello-pangea/dnd';
|
||||
import { IconPlus } from 'twenty-ui';
|
||||
import { v4 } from 'uuid';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { SettingsObjectFieldSelectFormOption } from '@/settings/data-model/types/SettingsObjectFieldSelectFormOption';
|
||||
import { LightButton } from '@/ui/input/button/components/LightButton';
|
||||
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
||||
import { CardFooter } from '@/ui/layout/card/components/CardFooter';
|
||||
@ -12,18 +16,32 @@ import {
|
||||
MAIN_COLOR_NAMES,
|
||||
ThemeColor,
|
||||
} from '@/ui/theme/constants/MainColorNames';
|
||||
import { themeColorSchema } from '@/ui/theme/utils/themeColorSchema';
|
||||
import { moveArrayItem } from '~/utils/array/moveArrayItem';
|
||||
|
||||
import { SettingsObjectFieldSelectFormOption } from '../types/SettingsObjectFieldSelectFormOption';
|
||||
|
||||
import { SettingsObjectFieldSelectFormOptionRow } from './SettingsObjectFieldSelectFormOptionRow';
|
||||
|
||||
export type SettingsObjectFieldSelectFormValues =
|
||||
SettingsObjectFieldSelectFormOption[];
|
||||
// TODO: rename to SettingsDataModelFieldSelectForm and move to settings/data-model/fields/forms/components
|
||||
|
||||
type SettingsObjectFieldSelectFormProps = {
|
||||
onChange: (values: SettingsObjectFieldSelectFormValues) => void;
|
||||
values: SettingsObjectFieldSelectFormValues;
|
||||
export const settingsDataModelFieldSelectFormSchema = z.object({
|
||||
options: z
|
||||
.array(
|
||||
z.object({
|
||||
color: themeColorSchema,
|
||||
value: z.string(),
|
||||
isDefault: z.boolean().optional(),
|
||||
label: z.string().min(1),
|
||||
}),
|
||||
)
|
||||
.min(1),
|
||||
});
|
||||
|
||||
export type SettingsDataModelFieldSelectFormValues = z.infer<
|
||||
typeof settingsDataModelFieldSelectFormSchema
|
||||
>;
|
||||
|
||||
type SettingsDataModelFieldSelectFormProps = {
|
||||
fieldMetadataItem?: Pick<FieldMetadataItem, 'defaultValue' | 'options'>;
|
||||
isMultiSelect?: boolean;
|
||||
};
|
||||
|
||||
@ -58,12 +76,55 @@ const getNextColor = (currentColor: ThemeColor) => {
|
||||
return MAIN_COLOR_NAMES[nextColorIndex];
|
||||
};
|
||||
|
||||
export const SettingsObjectFieldSelectForm = ({
|
||||
onChange,
|
||||
values,
|
||||
const getDefaultValueOptionIndexes = (
|
||||
fieldMetadataItem?: Pick<FieldMetadataItem, 'defaultValue' | 'options'>,
|
||||
) =>
|
||||
fieldMetadataItem?.options?.reduce<number[]>((result, option, index) => {
|
||||
if (
|
||||
Array.isArray(fieldMetadataItem?.defaultValue) &&
|
||||
fieldMetadataItem?.defaultValue.includes(`'${option.value}'`)
|
||||
) {
|
||||
return [...result, index];
|
||||
}
|
||||
|
||||
// Ensure default value is unique for simple Select field
|
||||
if (
|
||||
!result.length &&
|
||||
fieldMetadataItem?.defaultValue === `'${option.value}'`
|
||||
) {
|
||||
return [index];
|
||||
}
|
||||
|
||||
return result;
|
||||
}, []);
|
||||
|
||||
const DEFAULT_OPTION: SettingsObjectFieldSelectFormOption = {
|
||||
color: 'green',
|
||||
label: 'Option 1',
|
||||
value: v4(),
|
||||
};
|
||||
|
||||
export const SettingsDataModelFieldSelectForm = ({
|
||||
fieldMetadataItem,
|
||||
isMultiSelect = false,
|
||||
}: SettingsObjectFieldSelectFormProps) => {
|
||||
const handleDragEnd = (result: DropResult) => {
|
||||
}: SettingsDataModelFieldSelectFormProps) => {
|
||||
const { control } = useFormContext<SettingsDataModelFieldSelectFormValues>();
|
||||
|
||||
const initialDefaultValueOptionIndexes =
|
||||
getDefaultValueOptionIndexes(fieldMetadataItem);
|
||||
|
||||
const initialValue = fieldMetadataItem?.options
|
||||
?.map((option, index) => ({
|
||||
...option,
|
||||
isDefault: initialDefaultValueOptionIndexes?.includes(index),
|
||||
}))
|
||||
.sort((optionA, optionB) => optionA.position - optionB.position);
|
||||
|
||||
const handleDragEnd = (
|
||||
values: SettingsObjectFieldSelectFormOption[],
|
||||
result: DropResult,
|
||||
onChange: (options: SettingsObjectFieldSelectFormOption[]) => void,
|
||||
) => {
|
||||
if (!result.destination) return;
|
||||
|
||||
const nextOptions = moveArrayItem(values, {
|
||||
@ -74,27 +135,7 @@ export const SettingsObjectFieldSelectForm = ({
|
||||
onChange(nextOptions);
|
||||
};
|
||||
|
||||
const handleDefaultValueChange = (
|
||||
index: number,
|
||||
option: SettingsObjectFieldSelectFormOption,
|
||||
nextOption: SettingsObjectFieldSelectFormOption,
|
||||
forceUniqueDefaultValue: boolean,
|
||||
) => {
|
||||
const computeUniqueDefaultValue =
|
||||
forceUniqueDefaultValue && option.isDefault !== nextOption.isDefault;
|
||||
|
||||
const nextOptions = computeUniqueDefaultValue
|
||||
? values.map((value) => ({
|
||||
...value,
|
||||
isDefault: false,
|
||||
}))
|
||||
: [...values];
|
||||
|
||||
nextOptions.splice(index, 1, nextOption);
|
||||
onChange(nextOptions);
|
||||
};
|
||||
|
||||
const findNewLabel = () => {
|
||||
const findNewLabel = (values: SettingsObjectFieldSelectFormOption[]) => {
|
||||
let optionIndex = values.length + 1;
|
||||
while (optionIndex < 100) {
|
||||
const newLabel = `Option ${optionIndex}`;
|
||||
@ -107,65 +148,75 @@ export const SettingsObjectFieldSelectForm = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledContainer>
|
||||
<StyledLabel>Options</StyledLabel>
|
||||
<DraggableList
|
||||
onDragEnd={handleDragEnd}
|
||||
draggableItems={
|
||||
<>
|
||||
{values.map((option, index) => (
|
||||
<DraggableItem
|
||||
key={option.value}
|
||||
draggableId={option.value}
|
||||
index={index}
|
||||
isDragDisabled={values.length === 1}
|
||||
itemComponent={
|
||||
<SettingsObjectFieldSelectFormOptionRow
|
||||
<Controller
|
||||
name="options"
|
||||
control={control}
|
||||
defaultValue={initialValue?.length ? initialValue : [DEFAULT_OPTION]}
|
||||
render={({ field: { onChange, value: options } }) => (
|
||||
<>
|
||||
<StyledContainer>
|
||||
<StyledLabel>Options</StyledLabel>
|
||||
<DraggableList
|
||||
onDragEnd={(result) => handleDragEnd(options, result, onChange)}
|
||||
draggableItems={
|
||||
<>
|
||||
{options.map((option, index) => (
|
||||
<DraggableItem
|
||||
key={option.value}
|
||||
isDefault={option.isDefault}
|
||||
onChange={(nextOption) => {
|
||||
handleDefaultValueChange(
|
||||
index,
|
||||
option,
|
||||
nextOption,
|
||||
!isMultiSelect,
|
||||
);
|
||||
}}
|
||||
onRemove={
|
||||
values.length > 1
|
||||
? () => {
|
||||
const nextOptions = [...values];
|
||||
nextOptions.splice(index, 1);
|
||||
onChange(nextOptions);
|
||||
}
|
||||
: undefined
|
||||
draggableId={option.value}
|
||||
index={index}
|
||||
isDragDisabled={options.length === 1}
|
||||
itemComponent={
|
||||
<SettingsObjectFieldSelectFormOptionRow
|
||||
key={option.value}
|
||||
isDefault={option.isDefault}
|
||||
onChange={(nextOption) => {
|
||||
const nextOptions =
|
||||
isMultiSelect || !nextOption.isDefault
|
||||
? [...options]
|
||||
: // Reset simple Select default option before setting the new one
|
||||
options.map<SettingsObjectFieldSelectFormOption>(
|
||||
(value) => ({ ...value, isDefault: false }),
|
||||
);
|
||||
nextOptions.splice(index, 1, nextOption);
|
||||
onChange(nextOptions);
|
||||
}}
|
||||
onRemove={
|
||||
options.length > 1
|
||||
? () => {
|
||||
const nextOptions = [...options];
|
||||
nextOptions.splice(index, 1);
|
||||
onChange(nextOptions);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
option={option}
|
||||
/>
|
||||
}
|
||||
option={option}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</StyledContainer>
|
||||
<StyledFooter>
|
||||
<StyledButton
|
||||
title="Add option"
|
||||
Icon={IconPlus}
|
||||
onClick={() =>
|
||||
onChange([
|
||||
...values,
|
||||
{
|
||||
color: getNextColor(values[values.length - 1].color),
|
||||
label: findNewLabel(),
|
||||
value: v4(),
|
||||
},
|
||||
])
|
||||
}
|
||||
/>
|
||||
</StyledFooter>
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</StyledContainer>
|
||||
<StyledFooter>
|
||||
<StyledButton
|
||||
title="Add option"
|
||||
Icon={IconPlus}
|
||||
onClick={() =>
|
||||
onChange([
|
||||
...options,
|
||||
{
|
||||
color: getNextColor(options[options.length - 1].color),
|
||||
label: findNewLabel(options),
|
||||
value: v4(),
|
||||
},
|
||||
])
|
||||
}
|
||||
/>
|
||||
</StyledFooter>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user