From 2f3c41620c0d0ae3b3963da3c8f0c689eaa08967 Mon Sep 17 00:00:00 2001 From: Guillim Date: Mon, 18 Nov 2024 17:36:19 +0100 Subject: [PATCH] Wrap Long text fields (textarea) (#8557) Here we add the option for Text inputs to be wrapped, and to select on how many lines text should be displayed. Fix #7552 --------- Co-authored-by: guillim --- .../display/components/TextFieldDisplay.tsx | 9 +- .../meta-types/hooks/useTextFieldDisplay.ts | 4 + .../record-field/types/FieldMetadata.ts | 4 +- .../components/RecordInlineCellContainer.tsx | 15 ++- .../RecordInlineCellDisplayMode.tsx | 8 +- ...SettingsDataModelFieldSettingsFormCard.tsx | 17 ++++ .../text/SettingsDataModelFieldTextForm.tsx | 91 +++++++++++++++++++ ...ingsDataModelFieldTextSettingsFormCard.tsx | 45 +++++++++ .../SettingsDataModelFieldPreview.tsx | 7 +- .../display/components/DoubleTextDisplay.tsx | 3 - .../field/display/components/TextDisplay.tsx | 5 +- .../field-metadata-validation.service.ts | 24 +++-- .../field-metadata-settings.interface.ts | 5 + .../tooltip/OverflowingTextWithTooltip.tsx | 25 ++++- 14 files changed, 235 insertions(+), 27 deletions(-) create mode 100644 packages/twenty-front/src/modules/settings/data-model/fields/forms/components/text/SettingsDataModelFieldTextForm.tsx create mode 100644 packages/twenty-front/src/modules/settings/data-model/fields/forms/components/text/SettingsDataModelFieldTextSettingsFormCard.tsx delete mode 100644 packages/twenty-front/src/modules/ui/field/display/components/DoubleTextDisplay.tsx diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/TextFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/TextFieldDisplay.tsx index ce56ff7a6..803afa626 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/TextFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/TextFieldDisplay.tsx @@ -2,7 +2,12 @@ import { useTextFieldDisplay } from '@/object-record/record-field/meta-types/hoo import { TextDisplay } from '@/ui/field/display/components/TextDisplay'; export const TextFieldDisplay = () => { - const { fieldValue } = useTextFieldDisplay(); + const { fieldValue, fieldDefinition } = useTextFieldDisplay(); - return ; + return ( + + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useTextFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useTextFieldDisplay.ts index 0eb831aea..0bd7c660f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useTextFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useTextFieldDisplay.ts @@ -2,11 +2,15 @@ import { useContext } from 'react'; import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; +import { assertFieldMetadata } from '@/object-record/record-field/types/guards/assertFieldMetadata'; +import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldContext } from '../../contexts/FieldContext'; export const useTextFieldDisplay = () => { const { recordId, fieldDefinition, hotkeyScope } = useContext(FieldContext); + assertFieldMetadata(FieldMetadataType.Text, isFieldText, fieldDefinition); const fieldName = fieldDefinition.metadata.fieldName; const fieldValue = diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/FieldMetadata.ts b/packages/twenty-front/src/modules/object-record/record-field/types/FieldMetadata.ts index 53e037dbd..f739af431 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/types/FieldMetadata.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/types/FieldMetadata.ts @@ -23,7 +23,9 @@ export type FieldTextMetadata = { objectMetadataNameSingular?: string; placeHolder: string; fieldName: string; - settings?: Record; + settings?: { + displayedMaxRows?: number; + }; }; export type FieldDateTimeMetadata = { diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellContainer.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellContainer.tsx index 740039656..ed0b7c83a 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellContainer.tsx @@ -14,6 +14,9 @@ import { RecordInlineCellValue } from '@/object-record/record-inline-cell/compon import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; +import { assertFieldMetadata } from '@/object-record/record-field/types/guards/assertFieldMetadata'; +import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; import { useRecordInlineCellContext } from './RecordInlineCellContext'; const StyledIconContainer = styled.div` @@ -36,6 +39,7 @@ const StyledLabelAndIconContainer = styled.div` color: ${({ theme }) => theme.font.color.tertiary}; display: flex; gap: ${({ theme }) => theme.spacing(1)}; + height: 24px; `; const StyledValueContainer = styled.div` @@ -52,11 +56,12 @@ const StyledLabelContainer = styled.div<{ width?: number }>` `; const StyledInlineCellBaseContainer = styled.div` - align-items: center; + align-items: flex-start; box-sizing: border-box; width: 100%; display: flex; - height: 24px; + height: fit-content; + line-height: 24px; gap: ${({ theme }) => theme.spacing(1)}; user-select: none; justify-content: center; @@ -88,6 +93,10 @@ export const RecordInlineCellContainer = () => { const { recordId, fieldDefinition } = useContext(FieldContext); + if (isFieldText(fieldDefinition)) { + assertFieldMetadata(FieldMetadataType.Text, isFieldText, fieldDefinition); + } + const { setIsFocused } = useFieldFocus(); const handleContainerMouseEnter = () => { @@ -122,7 +131,7 @@ export const RecordInlineCellContainer = () => { )} {showLabel && label && ( - + )} {/* TODO: Displaying Tooltips on the board is causing performance issues https://react-tooltip.com/docs/examples/render */} diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellDisplayMode.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellDisplayMode.tsx index cf57e47ac..857df1b0d 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellDisplayMode.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellDisplayMode.tsx @@ -23,8 +23,8 @@ const StyledRecordInlineCellNormalModeOuterContainer = styled.div< isDisplayModeFixHeight ? '16px' : 'auto'}; min-height: 16px; overflow: hidden; - padding: ${({ theme }) => theme.spacing(1)}; - + padding-right: ${({ theme }) => theme.spacing(1)}; + padding-left: ${({ theme }) => theme.spacing(1)}; ${(props) => { if (props.isHovered === true) { return css` @@ -39,15 +39,17 @@ const StyledRecordInlineCellNormalModeOuterContainer = styled.div< `; const StyledRecordInlineCellNormalModeInnerContainer = styled.div` + align-content: center; align-items: center; color: ${({ theme }) => theme.font.color.primary}; font-size: 'inherit'; + font-weight: 'inherit'; height: fit-content; + min-height: 24px; overflow: hidden; - text-overflow: ellipsis; white-space: nowrap; `; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx index 4614b607b..281c20522 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx @@ -7,6 +7,8 @@ import { SettingsDataModelPreviewFormCard } from '@/settings/data-model/componen import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs'; import { settingsDataModelFieldBooleanFormSchema } from '@/settings/data-model/fields/forms/boolean/components/SettingsDataModelFieldBooleanForm'; import { SettingsDataModelFieldBooleanSettingsFormCard } from '@/settings/data-model/fields/forms/boolean/components/SettingsDataModelFieldBooleanSettingsFormCard'; +import { settingsDataModelFieldtextFormSchema } from '@/settings/data-model/fields/forms/components/text/SettingsDataModelFieldTextForm'; +import { SettingsDataModelFieldTextSettingsFormCard } from '@/settings/data-model/fields/forms/components/text/SettingsDataModelFieldTextSettingsFormCard'; import { settingsDataModelFieldCurrencyFormSchema } from '@/settings/data-model/fields/forms/currency/components/SettingsDataModelFieldCurrencyForm'; import { SettingsDataModelFieldCurrencySettingsFormCard } from '@/settings/data-model/fields/forms/currency/components/SettingsDataModelFieldCurrencySettingsFormCard'; import { settingsDataModelFieldDateFormSchema } from '@/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateForm'; @@ -58,6 +60,10 @@ const numberFieldFormSchema = z .object({ type: z.literal(FieldMetadataType.Number) }) .merge(settingsDataModelFieldNumberFormSchema); +const textFieldFormSchema = z + .object({ type: z.literal(FieldMetadataType.Text) }) + .merge(settingsDataModelFieldtextFormSchema); + const otherFieldsFormSchema = z.object({ type: z.enum( Object.keys( @@ -70,6 +76,7 @@ const otherFieldsFormSchema = z.object({ FieldMetadataType.Date, FieldMetadataType.DateTime, FieldMetadataType.Number, + FieldMetadataType.Text, ]), ) as [FieldMetadataType, ...FieldMetadataType[]], ), @@ -86,6 +93,7 @@ export const settingsDataModelFieldSettingsFormSchema = z.discriminatedUnion( selectFieldFormSchema, multiSelectFieldFormSchema, numberFieldFormSchema, + textFieldFormSchema, otherFieldsFormSchema, ], ); @@ -183,6 +191,15 @@ export const SettingsDataModelFieldSettingsFormCard = ({ ); } + if (fieldMetadataItem.type === FieldMetadataType.Text) { + return ( + + ); + } + if ( fieldMetadataItem.type === FieldMetadataType.Select || fieldMetadataItem.type === FieldMetadataType.MultiSelect diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/text/SettingsDataModelFieldTextForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/text/SettingsDataModelFieldTextForm.tsx new file mode 100644 index 000000000..2c10f1f1f --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/text/SettingsDataModelFieldTextForm.tsx @@ -0,0 +1,91 @@ +import { Controller, useFormContext } from 'react-hook-form'; + +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { Select } from '@/ui/input/components/Select'; +import styled from '@emotion/styled'; +import { CardContent } from 'twenty-ui'; +import { z } from 'zod'; + +const StyledFormCardTitle = styled.div` + color: ${({ theme }) => theme.font.color.light}; + font-size: ${({ theme }) => theme.font.size.xs}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; + margin-bottom: ${({ theme }) => theme.spacing(1)}; +`; + +type SettingsDataModelFieldTextFormProps = { + disabled?: boolean; + fieldMetadataItem: Pick< + FieldMetadataItem, + 'icon' | 'label' | 'type' | 'defaultValue' | 'settings' + >; +}; + +export const textFieldDefaultValueSchema = z.object({ + displayedMaxRows: z.number().nullable(), +}); + +export const settingsDataModelFieldtextFormSchema = z.object({ + settings: textFieldDefaultValueSchema, +}); + +export type SettingsDataModelFieldTextFormValues = z.infer< + typeof settingsDataModelFieldtextFormSchema +>; + +export const SettingsDataModelFieldTextForm = ({ + disabled, + fieldMetadataItem, +}: SettingsDataModelFieldTextFormProps) => { + const { control } = useFormContext(); + return ( + + { + const displayedMaxRows = value?.displayedMaxRows ?? 0; + + return ( + <> + Wrap on record pages +