Add rich-text field widget (#19512)

## Context
- Extend the FIELD widget to support RICH_TEXT fields alongside existing
RELATION/MORPH_RELATION fields
- Add EDITOR display mode that renders a full rich text editor, and
FIELD display mode that shows a compact single-line preview
- Enforce mutual exclusivity: EDITOR is only available for RICH_TEXT,
CARD only for RELATION, FIELD for both
- Refactor widget configuration into a centralized FIELD_WIDGET_CONFIG
constant and shared useFieldWidgetEligibleFields hook for better
scalability. Later we might use discriminative union from graphql
scehma)

<details>
<summary>
EDITOR mode
</summary>
<img width="1095" height="731" alt="Screenshot 2026-04-09 at 17 31 41"
src="https://github.com/user-attachments/assets/cebffd0e-07ea-4f74-a3dc-ef987daa17ea"
/>
</details>
<details>
<summary>
FIELD mode
</summary>
<img width="986" height="378" alt="Screenshot 2026-04-09 at 17 35 00"
src="https://github.com/user-attachments/assets/c90a8046-fdd0-4321-8ba6-f47d89e9d42a"
/>
</details>
<details>
<summary>
Open FIELD mode
</summary>
<img width="758" height="480" alt="Screenshot 2026-04-09 at 17 35 04"
src="https://github.com/user-attachments/assets/c53cc120-0b6f-47a0-808c-27b26f9f53ec"
/>
</details>
This commit is contained in:
Weiko
2026-04-09 18:52:30 +02:00
committed by GitHub
parent 77498d2f73
commit afdd914b83
19 changed files with 287 additions and 57 deletions
@@ -1,6 +1,6 @@
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
@@ -53,9 +53,7 @@ export const RecordInlineCellAnchoredPortal = ({
prefix: instanceIdPrefix,
});
const anchorElement = document.body.querySelector<HTMLAnchorElement>(
`#${fieldInstanceId}`,
);
const anchorElement = document.getElementById(fieldInstanceId);
const isRecordFieldReadOnly = useIsRecordFieldReadOnly({
fieldMetadataId: fieldMetadataItem.id,
@@ -1,10 +1,12 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useFieldListFieldMetadataItems } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataItems';
import { isDefined } from 'twenty-shared/utils';
import { usePageLayoutContentContext } from '@/page-layout/contexts/PageLayoutContentContext';
import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab';
import { createDefaultFieldWidget } from '@/page-layout/utils/createDefaultFieldWidget';
import { useFieldWidgetEligibleFields } from '@/page-layout/widgets/field/hooks/useFieldWidgetEligibleFields';
import { getFieldWidgetDefaultDisplayMode } from '@/page-layout/widgets/field/utils/getFieldWidgetDisplayModeConfig';
import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord';
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
import { useStore } from 'jotai';
@@ -21,9 +23,9 @@ export const useCreateRecordPageFieldWidget = () => {
objectNameSingular: targetObjectNameSingular,
});
const { boxedRelationFieldMetadataItems } = useFieldListFieldMetadataItems({
objectNameSingular: targetObjectNameSingular,
});
const allFieldWidgetFields = useFieldWidgetEligibleFields(
targetObjectNameSingular,
);
const { currentPageLayout } = useCurrentPageLayoutOrThrow();
@@ -52,12 +54,11 @@ export const useCreateRecordPageFieldWidget = () => {
}),
);
const unusedRelationField = boxedRelationFieldMetadataItems.find(
const unusedField = allFieldWidgetFields.find(
(field) => !usedFieldMetadataIds.has(field.id),
);
const selectedField =
unusedRelationField ?? boxedRelationFieldMetadataItems[0];
const selectedField = unusedField ?? allFieldWidgetFields[0];
const fieldMetadataId = selectedField?.id ?? '';
const title = selectedField?.label ?? '';
@@ -70,6 +71,9 @@ export const useCreateRecordPageFieldWidget = () => {
pageLayoutTabId: tabId,
title,
fieldMetadataId,
fieldDisplayMode: isDefined(selectedField)
? getFieldWidgetDefaultDisplayMode(selectedField.type)
: undefined,
objectMetadataId: objectMetadataItem.id,
positionIndex,
});
@@ -79,7 +83,7 @@ export const useCreateRecordPageFieldWidget = () => {
tabs: addWidgetToTab(prev.tabs, tabId, newWidget),
}));
}, [
boxedRelationFieldMetadataItems,
allFieldWidgetFields,
currentPageLayout.tabs,
objectMetadataItem.id,
pageLayoutDraftState,
@@ -11,6 +11,7 @@ export const createDefaultFieldWidget = ({
pageLayoutTabId,
title,
fieldMetadataId,
fieldDisplayMode = FieldDisplayMode.CARD,
objectMetadataId,
positionIndex,
}: {
@@ -18,6 +19,7 @@ export const createDefaultFieldWidget = ({
pageLayoutTabId: string;
title: string;
fieldMetadataId: string;
fieldDisplayMode?: FieldDisplayMode;
objectMetadataId: string;
positionIndex: number;
}): PageLayoutWidget => {
@@ -31,7 +33,7 @@ export const createDefaultFieldWidget = ({
__typename: 'FieldConfiguration',
configurationType: WidgetConfigurationType.FIELD,
fieldMetadataId,
fieldDisplayMode: FieldDisplayMode.CARD,
fieldDisplayMode,
},
gridPosition: {
__typename: 'GridPosition',
@@ -3,10 +3,12 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation';
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
import { isFieldRichText } from '@/object-record/record-field/ui/types/guards/isFieldRichText';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { useResolveFieldMetadataIdFromNameOrId } from '@/page-layout/hooks/useResolveFieldMetadataIdFromNameOrId';
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
import { FieldWidgetDisplay } from '@/page-layout/widgets/field/components/FieldWidgetDisplay';
import { FieldWidgetRichTextEditor } from '@/page-layout/widgets/field/components/FieldWidgetRichTextEditor';
import { FieldWidgetMorphRelationCard } from '@/page-layout/widgets/field/components/FieldWidgetMorphRelationCard';
import { FieldWidgetMorphRelationField } from '@/page-layout/widgets/field/components/FieldWidgetMorphRelationField';
import { FieldWidgetRelationCard } from '@/page-layout/widgets/field/components/FieldWidgetRelationCard';
@@ -135,6 +137,19 @@ export const FieldWidget = ({ widget }: FieldWidgetProps) => {
);
}
if (
isFieldRichText(fieldDefinition) &&
fieldDisplayMode === FieldDisplayMode.EDITOR
) {
return (
<FieldWidgetRichTextEditor
fieldMetadataItem={fieldMetadataItem}
objectMetadataItem={objectMetadataItem}
recordId={targetRecord.id}
/>
);
}
return (
<FieldWidgetDisplay
fieldDefinition={fieldDefinition}
@@ -0,0 +1,96 @@
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
import { styled } from '@linaria/react';
import { lazy, Suspense, useContext } from 'react';
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import { isUndefined } from '@sniptt/guards';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const RichTextFieldEditor = lazy(() =>
import(
'@/object-record/record-field/ui/meta-types/input/components/RichTextFieldEditor'
).then((module) => ({
default: module.RichTextFieldEditor,
})),
);
const StyledContainer = styled.div`
box-sizing: border-box;
display: flex;
flex-direction: column;
width: 100%;
`;
const StyledEditorContainer = styled.div`
box-sizing: border-box;
padding-inline: 44px;
width: 100%;
`;
const StyledSkeletonContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 ${themeCssVariables.spacing[4]};
`;
const LoadingSkeleton = () => {
const { theme } = useContext(ThemeContext);
return (
<StyledSkeletonContainer>
<SkeletonTheme
baseColor={theme.background.tertiary}
highlightColor={theme.background.transparent.lighter}
borderRadius={theme.border.radius.sm}
>
<Skeleton height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} />
</SkeletonTheme>
</StyledSkeletonContainer>
);
};
type FieldWidgetRichTextEditorProps = {
fieldMetadataItem: FieldMetadataItem;
objectMetadataItem: EnrichedObjectMetadataItem;
recordId: string;
};
export const FieldWidgetRichTextEditor = ({
fieldMetadataItem,
objectMetadataItem,
recordId,
}: FieldWidgetRichTextEditorProps) => {
const objectNameSingular = objectMetadataItem.nameSingular;
const fieldName = fieldMetadataItem.name;
const fieldValue = useAtomFamilySelectorValue(recordStoreFamilySelector, {
recordId,
fieldName,
});
if (isUndefined(fieldValue)) {
return <LoadingSkeleton />;
}
return (
<StyledContainer>
<ScrollWrapper
componentInstanceId={`scroll-wrapper-field-widget-rich-text-${recordId}-${fieldName}`}
>
<StyledEditorContainer>
<Suspense fallback={<LoadingSkeleton />}>
<RichTextFieldEditor
recordId={recordId}
objectNameSingular={objectNameSingular}
fieldName={fieldName}
/>
</Suspense>
</StyledEditorContainer>
</ScrollWrapper>
</StyledContainer>
);
};
@@ -0,0 +1,24 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldDisplayMode } from '~/generated-metadata/graphql';
type FieldWidgetFieldTypeConfig = {
availableDisplayModes: FieldDisplayMode[];
defaultDisplayMode: FieldDisplayMode;
};
export const FIELD_WIDGET_CONFIG: Partial<
Record<FieldMetadataType, FieldWidgetFieldTypeConfig>
> = {
[FieldMetadataType.RELATION]: {
availableDisplayModes: [FieldDisplayMode.FIELD, FieldDisplayMode.CARD],
defaultDisplayMode: FieldDisplayMode.CARD,
},
[FieldMetadataType.MORPH_RELATION]: {
availableDisplayModes: [FieldDisplayMode.FIELD, FieldDisplayMode.CARD],
defaultDisplayMode: FieldDisplayMode.CARD,
},
[FieldMetadataType.RICH_TEXT]: {
availableDisplayModes: [FieldDisplayMode.FIELD, FieldDisplayMode.EDITOR],
defaultDisplayMode: FieldDisplayMode.EDITOR,
},
};
@@ -0,0 +1,7 @@
import { type FieldMetadataType } from 'twenty-shared/types';
import { FIELD_WIDGET_CONFIG } from '@/page-layout/widgets/field/constants/fieldWidgetConfig';
export const FIELD_WIDGET_SUPPORTED_FIELD_TYPES = Object.keys(
FIELD_WIDGET_CONFIG,
) as FieldMetadataType[];
@@ -0,0 +1,16 @@
import { useFieldListFieldMetadataItems } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataItems';
import { FIELD_WIDGET_SUPPORTED_FIELD_TYPES } from '@/page-layout/widgets/field/constants/fieldWidgetSupportedFieldTypes';
import { useMemo } from 'react';
export const useFieldWidgetEligibleFields = (objectNameSingular: string) => {
const { boxedRelationFieldMetadataItems, inlineFieldMetadataItems } =
useFieldListFieldMetadataItems({ objectNameSingular });
return useMemo(() => {
const eligibleInlineFields = inlineFieldMetadataItems.filter((field) =>
FIELD_WIDGET_SUPPORTED_FIELD_TYPES.includes(field.type),
);
return [...boxedRelationFieldMetadataItems, ...eligibleInlineFields];
}, [boxedRelationFieldMetadataItems, inlineFieldMetadataItems]);
};
@@ -0,0 +1,24 @@
import { type FieldMetadataType } from 'twenty-shared/types';
import { FieldDisplayMode } from '~/generated-metadata/graphql';
import { FIELD_WIDGET_CONFIG } from '@/page-layout/widgets/field/constants/fieldWidgetConfig';
export const getFieldWidgetConfig = (fieldType: FieldMetadataType) =>
FIELD_WIDGET_CONFIG[fieldType];
export const getFieldWidgetDefaultDisplayMode = (
fieldType: FieldMetadataType,
) =>
getFieldWidgetConfig(fieldType)?.defaultDisplayMode ?? FieldDisplayMode.FIELD;
export const getFieldWidgetAvailableDisplayModes = (
fieldType: FieldMetadataType,
) =>
getFieldWidgetConfig(fieldType)?.availableDisplayModes ?? [
FieldDisplayMode.FIELD,
];
export const isDisplayModeValidForFieldType = (
fieldType: FieldMetadataType,
displayMode: FieldDisplayMode,
) => getFieldWidgetAvailableDisplayModes(fieldType).includes(displayMode);
@@ -1,7 +1,6 @@
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { FIND_MANY_FRONT_COMPONENTS } from '@/front-components/graphql/queries/findManyFrontComponents';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useFieldListFieldMetadataItems } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataItems';
import { useInsertCreatedWidgetAtContext } from '@/page-layout/hooks/useInsertCreatedWidgetAtContext';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
@@ -14,6 +13,8 @@ import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils
import { getWidgetConfigurationViewId } from '@/page-layout/utils/getWidgetConfigurationViewId';
import { isVerticalListPosition } from '@/page-layout/utils/isVerticalListPosition';
import { removeWidgetFromTab } from '@/page-layout/utils/removeWidgetFromTab';
import { useFieldWidgetEligibleFields } from '@/page-layout/widgets/field/hooks/useFieldWidgetEligibleFields';
import { getFieldWidgetDefaultDisplayMode } from '@/page-layout/widgets/field/utils/getFieldWidgetDisplayModeConfig';
import { useCreateViewForFieldsWidget } from '@/page-layout/widgets/fields/hooks/useCreateViewForFieldsWidget';
import { useDeleteViewForFieldsWidget } from '@/page-layout/widgets/fields/hooks/useDeleteViewForFieldsWidget';
import { useDeleteViewForRecordTableWidget } from '@/page-layout/widgets/record-table/hooks/useDeleteViewForRecordTableWidget';
@@ -102,9 +103,9 @@ export const SidePanelPageLayoutRecordPageWidgetTypeSelect = () => {
objectNameSingular: targetObjectNameSingular,
});
const { boxedRelationFieldMetadataItems } = useFieldListFieldMetadataItems({
objectNameSingular: targetObjectNameSingular,
});
const allFieldWidgetFields = useFieldWidgetEligibleFields(
targetObjectNameSingular,
);
const editingWidgetTab = isDefined(pageLayoutEditingWidgetId)
? pageLayoutDraft.tabs.find((tab) =>
@@ -274,12 +275,11 @@ export const SidePanelPageLayoutRecordPageWidgetTypeSelect = () => {
}),
);
const unusedRelationField = boxedRelationFieldMetadataItems.find(
const unusedField = allFieldWidgetFields.find(
(field) => !usedFieldMetadataIds.has(field.id),
);
const selectedField =
unusedRelationField ?? boxedRelationFieldMetadataItems[0];
const selectedField = unusedField ?? allFieldWidgetFields[0];
const fieldMetadataId = selectedField?.id ?? '';
const title = selectedField?.label ?? '';
@@ -289,6 +289,9 @@ export const SidePanelPageLayoutRecordPageWidgetTypeSelect = () => {
pageLayoutTabId: tabId,
title,
fieldMetadataId,
fieldDisplayMode: isDefined(selectedField)
? getFieldWidgetDefaultDisplayMode(selectedField.type)
: undefined,
objectMetadataId: objectMetadataItem.id,
positionIndex,
});
@@ -307,7 +310,7 @@ export const SidePanelPageLayoutRecordPageWidgetTypeSelect = () => {
resetNavigationStack: true,
});
}, [
boxedRelationFieldMetadataItems,
allFieldWidgetFields,
getExistingWidgetPositionIndex,
insertCreatedWidgetAtContext,
navigatePageLayoutSidePanel,
@@ -1,6 +1,11 @@
import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
import { useFieldListFieldMetadataItems } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataItems';
import { isDefined } from 'twenty-shared/utils';
import { useUpdatePageLayoutWidget } from '@/page-layout/hooks/useUpdatePageLayoutWidget';
import { useFieldWidgetEligibleFields } from '@/page-layout/widgets/field/hooks/useFieldWidgetEligibleFields';
import {
getFieldWidgetDefaultDisplayMode,
isDisplayModeValidForFieldType,
} from '@/page-layout/widgets/field/utils/getFieldWidgetDisplayModeConfig';
import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore';
import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode';
@@ -35,9 +40,8 @@ export const FieldWidgetFieldDropdownContent = () => {
const currentFieldMetadataId = fieldConfiguration?.fieldMetadataId;
const { boxedRelationFieldMetadataItems } = useFieldListFieldMetadataItems({
objectNameSingular,
});
const allFieldWidgetFieldMetadataItems =
useFieldWidgetEligibleFields(objectNameSingular);
const dropdownId = useAvailableComponentInstanceIdOrThrow(
DropdownComponentInstanceContext,
@@ -58,7 +62,7 @@ export const FieldWidgetFieldDropdownContent = () => {
const { getIcon } = useIcons();
const availableFields = filterBySearchQuery({
items: boxedRelationFieldMetadataItems,
items: allFieldWidgetFieldMetadataItems,
searchQuery,
getSearchableValues: (item) => [item.label],
});
@@ -67,16 +71,28 @@ export const FieldWidgetFieldDropdownContent = () => {
useFieldMetadataItemById(currentFieldMetadataId ?? '');
const handleSelectField = (fieldMetadataId: string) => {
const selectedField = allFieldWidgetFieldMetadataItems.find(
(field) => field.id === fieldMetadataId,
);
const currentDisplayMode = fieldConfiguration?.fieldDisplayMode;
const needsDisplayModeSwitch =
isDefined(selectedField) &&
isDefined(currentDisplayMode) &&
!isDisplayModeValidForFieldType(selectedField.type, currentDisplayMode);
updateCurrentWidgetConfig({
configToUpdate: {
fieldMetadataId,
...(needsDisplayModeSwitch && {
fieldDisplayMode: getFieldWidgetDefaultDisplayMode(
selectedField.type,
),
}),
},
});
const selectedField = boxedRelationFieldMetadataItems.find(
(field) => field.id === fieldMetadataId,
);
if (widgetInEditMode && selectedField) {
updatePageLayoutWidget(widgetInEditMode.id, {
title: selectedField.label,
@@ -1,3 +1,5 @@
import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
import { getFieldWidgetAvailableDisplayModes } from '@/page-layout/widgets/field/utils/getFieldWidgetDisplayModeConfig';
import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore';
import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode';
@@ -10,23 +12,25 @@ import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useLingui } from '@lingui/react/macro';
import { IconLayoutKanban, IconListDetails } from 'twenty-ui/display';
import { useMemo } from 'react';
import {
type IconComponent,
IconFileText,
IconLayoutKanban,
IconListDetails,
} from 'twenty-ui/display';
import { MenuItemSelect } from 'twenty-ui/navigation';
import {
FieldDisplayMode,
type FieldConfiguration,
} from '~/generated-metadata/graphql';
const LAYOUT_OPTIONS = [
{
id: FieldDisplayMode.FIELD,
Icon: IconListDetails,
},
{
id: FieldDisplayMode.CARD,
Icon: IconLayoutKanban,
},
] as const;
const DISPLAY_MODE_ICONS: Record<FieldDisplayMode, IconComponent> = {
[FieldDisplayMode.FIELD]: IconListDetails,
[FieldDisplayMode.CARD]: IconLayoutKanban,
[FieldDisplayMode.EDITOR]: IconFileText,
[FieldDisplayMode.VIEW]: IconListDetails,
};
export const FieldWidgetLayoutDropdownContent = () => {
const { t } = useLingui();
@@ -40,6 +44,19 @@ export const FieldWidgetLayoutDropdownContent = () => {
| undefined;
const currentDisplayMode = fieldConfiguration?.fieldDisplayMode;
const currentFieldMetadataId = fieldConfiguration?.fieldMetadataId;
const { fieldMetadataItem } = useFieldMetadataItemById(
currentFieldMetadataId ?? '',
);
const layoutOptions = useMemo(
() =>
fieldMetadataItem
? getFieldWidgetAvailableDisplayModes(fieldMetadataItem.type)
: [FieldDisplayMode.FIELD],
[fieldMetadataItem],
);
const dropdownId = useAvailableComponentInstanceIdOrThrow(
DropdownComponentInstanceContext,
@@ -64,9 +81,10 @@ export const FieldWidgetLayoutDropdownContent = () => {
closeDropdown();
};
const layoutLabels: Record<(typeof LAYOUT_OPTIONS)[number]['id'], string> = {
const layoutLabels: Record<string, string> = {
[FieldDisplayMode.FIELD]: t`Field`,
[FieldDisplayMode.CARD]: t`Card`,
[FieldDisplayMode.EDITOR]: t`Editor`,
};
return (
@@ -74,23 +92,23 @@ export const FieldWidgetLayoutDropdownContent = () => {
<SelectableList
selectableListInstanceId={dropdownId}
focusId={dropdownId}
selectableItemIdArray={LAYOUT_OPTIONS.map((option) => option.id)}
selectableItemIdArray={layoutOptions}
>
{LAYOUT_OPTIONS.map((option) => (
{layoutOptions.map((displayMode) => (
<SelectableListItem
key={option.id}
itemId={option.id}
key={displayMode}
itemId={displayMode}
onEnter={() => {
handleSelectLayout(option.id);
handleSelectLayout(displayMode);
}}
>
<MenuItemSelect
text={layoutLabels[option.id]}
selected={currentDisplayMode === option.id}
focused={selectedItemId === option.id}
LeftIcon={option.Icon}
text={layoutLabels[displayMode]}
selected={currentDisplayMode === displayMode}
focused={selectedItemId === displayMode}
LeftIcon={DISPLAY_MODE_ICONS[displayMode]}
onClick={() => {
handleSelectLayout(option.id);
handleSelectLayout(displayMode);
}}
/>
</SelectableListItem>
@@ -62,6 +62,7 @@ export const SidePanelRecordPageFieldSettings = () => {
const displayModeLabels: Record<string, string> = {
[FieldDisplayMode.FIELD]: t`Field`,
[FieldDisplayMode.CARD]: t`Card`,
[FieldDisplayMode.EDITOR]: t`Editor`,
};
const layoutLabel = isDefined(currentDisplayMode)