feat: add EnumFieldDisplay and Enum field preview (#2487)

Closes #2428

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Thaïs
2023-11-17 23:15:35 +01:00
committed by GitHub
parent e72917c69c
commit fea0bbeb2a
15 changed files with 200 additions and 18 deletions

View File

@ -32,8 +32,8 @@ const StyledSettingsObjectFieldTypeCard = styled(SettingsObjectFieldTypeCard)`
margin-top: ${({ theme }) => theme.spacing(4)};
`;
// TODO: remove "relation" type for now, add it back when the backend is ready.
const { RELATION: _, ...dataTypesWithoutRelation } = dataTypes;
// TODO: remove "enum" and "relation" types for now, add them back when the backend is ready.
const { ENUM: _ENUM, RELATION: _RELATION, ...allowedDataTypes } = dataTypes;
export const SettingsObjectFieldTypeSelectSection = ({
disabled,
@ -57,12 +57,10 @@ export const SettingsObjectFieldTypeSelectSection = ({
dropdownScopeId="object-field-type-select"
value={fieldType}
onChange={onChange}
options={Object.entries(dataTypesWithoutRelation).map(
([key, dataType]) => ({
value: key as FieldMetadataType,
...dataType,
}),
)}
options={Object.entries(allowedDataTypes).map(([key, dataType]) => ({
value: key as FieldMetadataType,
...dataType,
}))}
/>
{['BOOLEAN', 'DATE', 'MONEY', 'NUMBER', 'TEXT', 'URL'].includes(
fieldType,

View File

@ -73,6 +73,14 @@ export const Number: Story = {
},
};
export const Select: Story = {
args: {
fieldIconKey: 'IconBuildingFactory2',
fieldLabel: 'Industry',
fieldType: FieldMetadataType.Enum,
},
};
export const CustomObject: Story = {
args: {
isObjectCustom: true,

View File

@ -8,6 +8,7 @@ import {
IconNumbers,
IconPhone,
IconPlug,
IconTag,
IconTextSize,
IconUser,
} from '@/ui/display/icon';
@ -52,6 +53,11 @@ export const dataTypes: Record<
Icon: IconCalendarEvent,
defaultValue: defaultDateValue.toISOString(),
},
[FieldMetadataType.Enum]: {
label: 'Select',
Icon: IconTag,
defaultValue: { color: 'green', text: 'Option 1' },
},
[FieldMetadataType.Currency]: {
label: 'Currency',
Icon: IconCoins,
@ -66,5 +72,4 @@ export const dataTypes: Record<
defaultValue: 50,
},
[FieldMetadataType.FullName]: { label: 'Full Name', Icon: IconUser },
[FieldMetadataType.Enum]: { label: 'Enum', Icon: IconPlug },
};

View File

@ -1,6 +1,7 @@
export type MetadataFieldDataType =
| 'BOOLEAN'
| 'DATE'
| 'ENUM'
| 'MONEY'
| 'NUMBER'
| 'RELATION'

View File

@ -35,10 +35,17 @@ const StyledTag = styled.h3<{
gap: ${({ theme }) => theme.spacing(2)};
height: ${({ theme }) => theme.spacing(5)};
margin: 0;
overflow: hidden;
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(2)};
`;
const StyledContent = styled.span`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
export type TagProps = {
className?: string;
color: ThemeColor;
@ -52,6 +59,6 @@ export const Tag = ({ className, color, text, onClick }: TagProps) => (
color={castToTagColor(color)}
onClick={onClick}
>
{text}
<StyledContent>{text}</StyledContent>
</StyledTag>
);

View File

@ -34,6 +34,17 @@ export const Default: Story = {
},
};
export const WithLongText: Story = {
decorators: [ComponentDecorator],
args: {
color: 'green',
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
},
parameters: {
container: { width: 100 },
},
};
export const Catalog: CatalogStory<Story, typeof Tag> = {
args: { text: 'Urgent' },
argTypes: {

View File

@ -1,9 +1,11 @@
import { useContext } from 'react';
import { FullNameFieldDisplay } from '@/ui/object/field/meta-types/display/components/FullNameFieldDisplay';
import { LinkFieldDisplay } from '@/ui/object/field/meta-types/display/components/LinkFieldDisplay';
import { RelationFieldDisplay } from '@/ui/object/field/meta-types/display/components/RelationFieldDisplay';
import { UuidFieldDisplay } from '@/ui/object/field/meta-types/display/components/UuidFieldDisplay';
import { isFieldFullName } from '@/ui/object/field/types/guards/isFieldFullName';
import { isFieldLink } from '@/ui/object/field/types/guards/isFieldLink';
import { isFieldUuid } from '@/ui/object/field/types/guards/isFieldUuid';
import { FieldContext } from '../contexts/FieldContext';
@ -12,7 +14,7 @@ import { CurrencyFieldDisplay } from '../meta-types/display/components/CurrencyF
import { DateFieldDisplay } from '../meta-types/display/components/DateFieldDisplay';
import { DoubleTextChipFieldDisplay } from '../meta-types/display/components/DoubleTextChipFieldDisplay';
import { EmailFieldDisplay } from '../meta-types/display/components/EmailFieldDisplay';
import { LinkFieldDisplay } from '../meta-types/display/components/LinkFieldDisplay';
import { EnumFieldDisplay } from '../meta-types/display/components/EnumFieldDisplay';
import { MoneyFieldDisplay } from '../meta-types/display/components/MoneyFieldDisplay';
import { NumberFieldDisplay } from '../meta-types/display/components/NumberFieldDisplay';
import { PhoneFieldDisplay } from '../meta-types/display/components/PhoneFieldDisplay';
@ -23,7 +25,7 @@ import { isFieldCurrency } from '../types/guards/isFieldCurrency';
import { isFieldDate } from '../types/guards/isFieldDate';
import { isFieldDoubleTextChip } from '../types/guards/isFieldDoubleTextChip';
import { isFieldEmail } from '../types/guards/isFieldEmail';
import { isFieldLink } from '../types/guards/isFieldLink';
import { isFieldEnum } from '../types/guards/isFieldEnum';
import { isFieldMoney } from '../types/guards/isFieldMoney';
import { isFieldNumber } from '../types/guards/isFieldNumber';
import { isFieldPhone } from '../types/guards/isFieldPhone';
@ -64,6 +66,8 @@ export const FieldDisplay = () => {
<ChipFieldDisplay />
) : isFieldDoubleTextChip(fieldDefinition) ? (
<DoubleTextChipFieldDisplay />
) : isFieldEnum(fieldDefinition) ? (
<EnumFieldDisplay />
) : (
<></>
)}

View File

@ -0,0 +1,9 @@
import { Tag } from '@/ui/display/tag/components/Tag';
import { useEnumField } from '../../hooks/useEnumField';
export const EnumFieldDisplay = () => {
const { fieldValue } = useEnumField();
return <Tag color={fieldValue.color} text={fieldValue.text} />;
};

View File

@ -0,0 +1,55 @@
import { useEffect } from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { FieldContext } from '../../../../contexts/FieldContext';
import { FieldEnumValue } from '../../../../types/FieldMetadata';
import { useEnumField } from '../../../hooks/useEnumField';
import { EnumFieldDisplay } from '../EnumFieldDisplay';
const EnumFieldValueSetterEffect = ({ value }: { value: FieldEnumValue }) => {
const { setFieldValue } = useEnumField();
useEffect(() => {
setFieldValue(value);
}, [setFieldValue, value]);
return null;
};
const meta: Meta = {
title: 'UI/Data/Field/Display/EnumFieldDisplay',
decorators: [
(Story, { args }) => (
<FieldContext.Provider
value={{
entityId: '',
fieldDefinition: {
fieldMetadataId: 'enum',
label: 'Enum',
type: 'ENUM',
metadata: {
fieldName: 'Enum',
},
},
hotkeyScope: 'hotkey-scope',
}}
>
<EnumFieldValueSetterEffect value={args.value} />
<Story />
</FieldContext.Provider>
),
ComponentDecorator,
],
component: EnumFieldDisplay,
args: {
value: { color: 'purple', text: 'Lorem ipsum' },
},
};
export default meta;
type Story = StoryObj<typeof EnumFieldDisplay>;
export const Default: Story = {};

View File

@ -0,0 +1,47 @@
import { useContext } from 'react';
import { useRecoilState } from 'recoil';
import { FieldEnumValue } from '@/ui/object/field/types/FieldMetadata';
import { ThemeColor } from '@/ui/theme/constants/colors';
import { FieldContext } from '../../contexts/FieldContext';
import { useFieldInitialValue } from '../../hooks/useFieldInitialValue';
import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector';
import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';
import { isFieldEnum } from '../../types/guards/isFieldEnum';
import { isFieldEnumValue } from '../../types/guards/isFieldEnumValue';
export const useEnumField = () => {
const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext);
assertFieldMetadata('ENUM', isFieldEnum, fieldDefinition);
const { fieldName } = fieldDefinition.metadata;
const [fieldValue, setFieldValue] = useRecoilState<FieldEnumValue>(
entityFieldsFamilySelector({
entityId: entityId,
fieldName: fieldName,
}),
);
const fieldEnumValue = isFieldEnumValue(fieldValue)
? fieldValue
: { color: 'green' as ThemeColor, text: '' };
const fieldInitialValue = useFieldInitialValue();
const initialValue = {
color: 'green' as ThemeColor,
text: fieldInitialValue?.isEmpty
? ''
: fieldInitialValue?.value ?? fieldEnumValue?.text ?? '',
};
return {
fieldDefinition,
fieldValue: fieldEnumValue,
initialValue,
setFieldValue,
hotkeyScope,
};
};

View File

@ -1,5 +1,6 @@
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { ThemeColor } from '@/ui/theme/constants/colors';
export type FieldUuidMetadata = {
placeHolder: string;
@ -94,6 +95,10 @@ export type FieldBooleanMetadata = {
fieldName: string;
};
export type FieldEnumMetadata = {
fieldName: string;
};
export type FieldMetadata =
| FieldBooleanMetadata
| FieldChipMetadata
@ -107,6 +112,7 @@ export type FieldMetadata =
| FieldNumberMetadata
| FieldPhoneMetadata
| FieldProbabilityMetadata
| FieldEnumMetadata
| FieldRelationMetadata
| FieldTextMetadata
| FieldURLMetadata
@ -140,3 +146,5 @@ export type FieldDoubleTextChipValue = {
};
export type FieldRelationValue = EntityForSelect | null;
export type FieldEnumValue = { color: ThemeColor; text: string };

View File

@ -1,19 +1,25 @@
export type FieldType =
| 'BOOLEAN'
| 'UUID'
| 'TEXT'
| 'RELATION'
| 'CHIP'
| 'DATE'
| 'DOUBLE_TEXT_CHIP'
| 'DOUBLE_TEXT'
| 'NUMBER'
| 'EMAIL'
| 'BOOLEAN'
| 'DATE'
| 'ENUM'
| 'MONEY_AMOUNT_V2'
| 'MONEY_AMOUNT'
| 'MONEY'
| 'NUMBER'
| 'PHONE'
| 'PROBABILITY'
| 'RELATION'
| 'TEXT'
| 'URL'
| 'PHONE'
| 'URL'
| 'LINK'
| 'PROBABILITY'
| 'CURRENCY'
| 'MONEY_AMOUNT'
| 'MONEY'
| 'FULL_NAME';

View File

@ -7,6 +7,7 @@ import {
FieldDoubleTextChipMetadata,
FieldDoubleTextMetadata,
FieldEmailMetadata,
FieldEnumMetadata,
FieldFullnameMetadata,
FieldLinkMetadata,
FieldMetadata,
@ -43,6 +44,8 @@ type AssertFieldMetadataFunction = <
? FieldLinkMetadata
: E extends 'MONEY_AMOUNT'
? FieldMoneyMetadata
: E extends 'ENUM'
? FieldEnumMetadata
: E extends 'NUMBER'
? FieldNumberMetadata
: E extends 'PHONE'

View File

@ -0,0 +1,6 @@
import { FieldDefinition } from '../FieldDefinition';
import { FieldEnumMetadata, FieldMetadata } from '../FieldMetadata';
export const isFieldEnum = (
field: FieldDefinition<FieldMetadata>,
): field is FieldDefinition<FieldEnumMetadata> => field.type === 'ENUM';

View File

@ -0,0 +1,14 @@
import { z } from 'zod';
import { mainColors, ThemeColor } from '@/ui/theme/constants/colors';
const enumColors = Object.keys(mainColors) as [ThemeColor, ...ThemeColor[]];
const enumValueSchema = z.object({
color: z.enum(enumColors),
text: z.string(),
});
export const isFieldEnumValue = (
fieldValue: unknown,
): fieldValue is z.infer<typeof enumValueSchema> =>
enumValueSchema.safeParse(fieldValue).success;