Add color to object icon picker in data model (#19368)

Closes [#2291](https://github.com/twentyhq/core-team-issues/issues/2291)
This commit is contained in:
Abdul Rahman
2026-04-08 06:57:08 +05:30
committed by GitHub
parent 5c8b6e395b
commit a0484f686a
12 changed files with 337 additions and 79 deletions
@@ -1,6 +1,7 @@
import { isDDLLockedState } from '@/client-config/states/isDDLLockedState';
import { parseThemeColor } from '@/navigation-menu-item/common/utils/parseThemeColor';
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { isDDLLockedState } from '@/client-config/states/isDDLLockedState';
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
import { computeUpdatedNavigationMemorizedUrlAfterObjectNamePluralChange } from '@/settings/data-model/object-details/utils/computeUpdatedNavigationMemorizedUrlAfterObjectNamePluralChange';
import { SettingsDataModelObjectAboutForm } from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm';
@@ -60,6 +61,9 @@ export const SettingsUpdateDataModelObjectAboutForm = ({
labelSingular,
namePlural,
nameSingular,
...(objectMetadataItem.isCustom
? { color: parseThemeColor(objectMetadataItem.color) }
: {}),
},
});
@@ -99,6 +103,14 @@ export const SettingsUpdateDataModelObjectAboutForm = ({
labelSingular: updatedObject?.data?.updateOneObject.labelSingular,
namePlural: updatedObject?.data?.updateOneObject.namePlural,
nameSingular: updatedObject?.data?.updateOneObject.nameSingular,
...(objectMetadataItem.isCustom
? {
color: parseThemeColor(
updatedObject?.data?.updateOneObject.color ??
objectMetadataItem.color,
),
}
: {}),
});
} else {
formConfig.reset(formValues);
@@ -134,6 +146,7 @@ export const SettingsUpdateDataModelObjectAboutForm = ({
nameSingular: _nameSingular,
namePlural: _namePlural,
isLabelSyncedWithName: _isLabelSyncedWithName,
color: _color,
...payloadWithoutNames
} = updatePayload;
@@ -1,3 +1,4 @@
import { parseThemeColor } from '@/navigation-menu-item/common/utils/parseThemeColor';
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { AdvancedSettingsWrapper } from '@/settings/components/AdvancedSettingsWrapper';
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
@@ -7,9 +8,9 @@ import { IconPicker } from '@/ui/input/components/IconPicker';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { TextArea } from '@/ui/input/components/TextArea';
import { styled } from '@linaria/react';
import { useContext } from 'react';
import { useLingui } from '@lingui/react/macro';
import { plural } from 'pluralize';
import { useContext } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { SettingsPath } from 'twenty-shared/types';
import { capitalize, isDefined } from 'twenty-shared/utils';
@@ -127,8 +128,12 @@ export const SettingsDataModelObjectAboutForm = ({
const labelPlural = watch('labelPlural');
const isStandardObject =
isDefined(objectMetadataItem?.isCustom) && !objectMetadataItem.isCustom;
const showObjectColorInIconPicker =
!isStandardObject &&
(!isDefined(objectMetadataItem) || objectMetadataItem.isCustom);
watch('description');
watch('icon');
const objectIconColor = watch('color');
const apiNameTooltipText =
!isDefined(objectMetadataItem) || objectMetadataItem.isCustom
@@ -186,6 +191,25 @@ export const SettingsDataModelObjectAboutForm = ({
<IconPicker
selectedIconKey={value}
disabled={disableEdition}
dropdownId={
isDefined(objectMetadataItem)
? `settings-object-about-icon-${objectMetadataItem.id}`
: 'settings-new-object-about-icon'
}
iconColorPicker={
showObjectColorInIconPicker
? {
selectedColor: parseThemeColor(objectIconColor),
onColorChange: (nextColor) => {
setValue('color', nextColor, {
shouldDirty: true,
shouldValidate: true,
});
onNewDirtyField?.();
},
}
: undefined
}
onChange={({ iconKey }) => {
if (disableEdition) {
return;
@@ -1,5 +1,44 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`settingsDataModelObjectAboutFormSchema fails when color is not a valid theme color 1`] = `
[ZodError: [
{
"code": "invalid_value",
"values": [
"red",
"ruby",
"crimson",
"tomato",
"orange",
"amber",
"yellow",
"lime",
"grass",
"green",
"jade",
"mint",
"turquoise",
"cyan",
"sky",
"blue",
"iris",
"violet",
"purple",
"plum",
"pink",
"bronze",
"gold",
"brown",
"gray"
],
"path": [
"color"
],
"message": "Invalid option: expected one of \\"red\\"|\\"ruby\\"|\\"crimson\\"|\\"tomato\\"|\\"orange\\"|\\"amber\\"|\\"yellow\\"|\\"lime\\"|\\"grass\\"|\\"green\\"|\\"jade\\"|\\"mint\\"|\\"turquoise\\"|\\"cyan\\"|\\"sky\\"|\\"blue\\"|\\"iris\\"|\\"violet\\"|\\"purple\\"|\\"plum\\"|\\"pink\\"|\\"bronze\\"|\\"gold\\"|\\"brown\\"|\\"gray\\""
}
]]
`;
exports[`settingsDataModelObjectAboutFormSchema fails when isLabelSyncedWithName is not a boolean 1`] = `
[ZodError: [
{
@@ -6,6 +6,7 @@ import { type EachTestingContext } from 'twenty-shared/testing';
describe('settingsDataModelObjectAboutFormSchema', () => {
const validInput: SettingsDataModelObjectAboutFormValues = {
color: 'gray',
description: 'A valid description',
icon: 'IconName',
labelPlural: 'Labels Plural',
@@ -148,6 +149,16 @@ describe('settingsDataModelObjectAboutFormSchema', () => {
expectedSuccess: false,
},
},
{
title: 'fails when color is not a valid theme color',
context: {
input: {
...validInput,
color: 'not-a-color',
},
expectedSuccess: false,
},
},
];
test.each([...passingTestsUseCase, ...failsValidationTestsUseCase])(
@@ -1,5 +1,6 @@
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { t } from '@lingui/core/macro';
import { themeColorSchema } from 'twenty-ui/utilities';
import { type ZodType, z } from 'zod';
import { type ReadonlyKeysArray } from '~/types/ReadonlyKeysArray';
import { zodNonEmptyString } from '~/types/ZodNonEmptyString';
@@ -8,6 +9,7 @@ import { camelCaseStringSchema } from '~/utils/validation-schemas/camelCaseStrin
type ZodTypeSettingsDataModelFormFields = ZodType<
Pick<
EnrichedObjectMetadataItem,
| 'color'
| 'labelSingular'
| 'labelPlural'
| 'description'
@@ -18,6 +20,7 @@ type ZodTypeSettingsDataModelFormFields = ZodType<
> & { skipNameField?: boolean }
>;
const settingsDataModelFormFieldsSchema = z.object({
color: themeColorSchema.optional(),
description: z.string().nullish(),
icon: z.string().optional(),
labelSingular: zodNonEmptyString,