diff --git a/front/src/modules/settings/data-model/components/SettingsObjectFieldTypeSelectSection.tsx b/front/src/modules/settings/data-model/components/SettingsObjectFieldTypeSelectSection.tsx
index 2099105e9..801582e49 100644
--- a/front/src/modules/settings/data-model/components/SettingsObjectFieldTypeSelectSection.tsx
+++ b/front/src/modules/settings/data-model/components/SettingsObjectFieldTypeSelectSection.tsx
@@ -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,
diff --git a/front/src/modules/settings/data-model/components/__stories__/SettingsObjectFieldPreview.stories.tsx b/front/src/modules/settings/data-model/components/__stories__/SettingsObjectFieldPreview.stories.tsx
index adad8371e..4a75619cd 100644
--- a/front/src/modules/settings/data-model/components/__stories__/SettingsObjectFieldPreview.stories.tsx
+++ b/front/src/modules/settings/data-model/components/__stories__/SettingsObjectFieldPreview.stories.tsx
@@ -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,
diff --git a/front/src/modules/settings/data-model/constants/dataTypes.ts b/front/src/modules/settings/data-model/constants/dataTypes.ts
index 378e03035..d675c04c9 100644
--- a/front/src/modules/settings/data-model/constants/dataTypes.ts
+++ b/front/src/modules/settings/data-model/constants/dataTypes.ts
@@ -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 },
};
diff --git a/front/src/modules/settings/data-model/types/ObjectFieldDataType.ts b/front/src/modules/settings/data-model/types/ObjectFieldDataType.ts
index 0c47e0585..bf1b72f3c 100644
--- a/front/src/modules/settings/data-model/types/ObjectFieldDataType.ts
+++ b/front/src/modules/settings/data-model/types/ObjectFieldDataType.ts
@@ -1,6 +1,7 @@
export type MetadataFieldDataType =
| 'BOOLEAN'
| 'DATE'
+ | 'ENUM'
| 'MONEY'
| 'NUMBER'
| 'RELATION'
diff --git a/front/src/modules/ui/display/tag/components/Tag.tsx b/front/src/modules/ui/display/tag/components/Tag.tsx
index 3eff13037..b198052e8 100644
--- a/front/src/modules/ui/display/tag/components/Tag.tsx
+++ b/front/src/modules/ui/display/tag/components/Tag.tsx
@@ -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}
+ {text}
);
diff --git a/front/src/modules/ui/display/tag/components/__stories__/Tag.stories.tsx b/front/src/modules/ui/display/tag/components/__stories__/Tag.stories.tsx
index 12995bf1c..e376a056e 100644
--- a/front/src/modules/ui/display/tag/components/__stories__/Tag.stories.tsx
+++ b/front/src/modules/ui/display/tag/components/__stories__/Tag.stories.tsx
@@ -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 = {
args: { text: 'Urgent' },
argTypes: {
diff --git a/front/src/modules/ui/object/field/components/FieldDisplay.tsx b/front/src/modules/ui/object/field/components/FieldDisplay.tsx
index f0498a7ec..1651fd44a 100644
--- a/front/src/modules/ui/object/field/components/FieldDisplay.tsx
+++ b/front/src/modules/ui/object/field/components/FieldDisplay.tsx
@@ -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 = () => {
) : isFieldDoubleTextChip(fieldDefinition) ? (
+ ) : isFieldEnum(fieldDefinition) ? (
+
) : (
<>>
)}
diff --git a/front/src/modules/ui/object/field/meta-types/display/components/EnumFieldDisplay.tsx b/front/src/modules/ui/object/field/meta-types/display/components/EnumFieldDisplay.tsx
new file mode 100644
index 000000000..798d0586d
--- /dev/null
+++ b/front/src/modules/ui/object/field/meta-types/display/components/EnumFieldDisplay.tsx
@@ -0,0 +1,9 @@
+import { Tag } from '@/ui/display/tag/components/Tag';
+
+import { useEnumField } from '../../hooks/useEnumField';
+
+export const EnumFieldDisplay = () => {
+ const { fieldValue } = useEnumField();
+
+ return ;
+};
diff --git a/front/src/modules/ui/object/field/meta-types/display/components/__stories__/EnumFieldDisplay.stories.tsx b/front/src/modules/ui/object/field/meta-types/display/components/__stories__/EnumFieldDisplay.stories.tsx
new file mode 100644
index 000000000..79fe1496b
--- /dev/null
+++ b/front/src/modules/ui/object/field/meta-types/display/components/__stories__/EnumFieldDisplay.stories.tsx
@@ -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 }) => (
+
+
+
+
+ ),
+ ComponentDecorator,
+ ],
+ component: EnumFieldDisplay,
+ args: {
+ value: { color: 'purple', text: 'Lorem ipsum' },
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {};
diff --git a/front/src/modules/ui/object/field/meta-types/hooks/useEnumField.ts b/front/src/modules/ui/object/field/meta-types/hooks/useEnumField.ts
new file mode 100644
index 000000000..9e09be1f6
--- /dev/null
+++ b/front/src/modules/ui/object/field/meta-types/hooks/useEnumField.ts
@@ -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(
+ 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,
+ };
+};
diff --git a/front/src/modules/ui/object/field/types/FieldMetadata.ts b/front/src/modules/ui/object/field/types/FieldMetadata.ts
index 448fc8327..efcd272cf 100644
--- a/front/src/modules/ui/object/field/types/FieldMetadata.ts
+++ b/front/src/modules/ui/object/field/types/FieldMetadata.ts
@@ -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 };
diff --git a/front/src/modules/ui/object/field/types/FieldType.ts b/front/src/modules/ui/object/field/types/FieldType.ts
index 9eb28cab0..4ddcece85 100644
--- a/front/src/modules/ui/object/field/types/FieldType.ts
+++ b/front/src/modules/ui/object/field/types/FieldType.ts
@@ -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';
diff --git a/front/src/modules/ui/object/field/types/guards/assertFieldMetadata.ts b/front/src/modules/ui/object/field/types/guards/assertFieldMetadata.ts
index 320743295..5f05b75e9 100644
--- a/front/src/modules/ui/object/field/types/guards/assertFieldMetadata.ts
+++ b/front/src/modules/ui/object/field/types/guards/assertFieldMetadata.ts
@@ -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'
diff --git a/front/src/modules/ui/object/field/types/guards/isFieldEnum.ts b/front/src/modules/ui/object/field/types/guards/isFieldEnum.ts
new file mode 100644
index 000000000..69f337a68
--- /dev/null
+++ b/front/src/modules/ui/object/field/types/guards/isFieldEnum.ts
@@ -0,0 +1,6 @@
+import { FieldDefinition } from '../FieldDefinition';
+import { FieldEnumMetadata, FieldMetadata } from '../FieldMetadata';
+
+export const isFieldEnum = (
+ field: FieldDefinition,
+): field is FieldDefinition => field.type === 'ENUM';
diff --git a/front/src/modules/ui/object/field/types/guards/isFieldEnumValue.ts b/front/src/modules/ui/object/field/types/guards/isFieldEnumValue.ts
new file mode 100644
index 000000000..834457aac
--- /dev/null
+++ b/front/src/modules/ui/object/field/types/guards/isFieldEnumValue.ts
@@ -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 =>
+ enumValueSchema.safeParse(fieldValue).success;