feat: add Text field preview in settings (#2389)

Closes #2325
This commit is contained in:
Thaïs
2023-11-09 08:25:46 +01:00
committed by GitHub
parent 4efbe4d798
commit 1f5492b4a7
23 changed files with 461 additions and 540 deletions

View File

@ -1,17 +1,28 @@
import { ReactNode } from 'react';
import { useEffect } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { useFindManyObjects } from '@/metadata/hooks/useFindManyObjects';
import { Tag } from '@/ui/display/tag/components/Tag';
import { useLazyLoadIcon } from '@/ui/input/hooks/useLazyLoadIcon';
import { FieldDisplay } from '@/ui/object/field/components/FieldDisplay';
import { FieldContext } from '@/ui/object/field/contexts/FieldContext';
import { entityFieldsFamilySelector } from '@/ui/object/field/states/selectors/entityFieldsFamilySelector';
import { assertNotNull } from '~/utils/assert';
type SettingsObjectFieldPreviewProps = {
objectIconKey: string;
objectLabelPlural: string;
isObjectCustom: boolean;
fieldIconKey: string;
import { dataTypes } from '../constants/dataTypes';
import { MetadataFieldDataType } from '../types/ObjectFieldDataType';
export type SettingsObjectFieldPreviewProps = {
fieldIconKey?: string | null;
fieldLabel: string;
fieldValue: ReactNode;
fieldName?: string;
fieldType: MetadataFieldDataType;
isObjectCustom: boolean;
objectIconKey?: string | null;
objectLabelPlural: string;
objectNamePlural: string;
};
const StyledContainer = styled.div`
@ -27,6 +38,7 @@ const StyledContainer = styled.div`
const StyledObjectSummary = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
justify-content: space-between;
margin-bottom: ${({ theme }) => theme.spacing(2)};
`;
@ -47,7 +59,7 @@ const StyledFieldPreview = styled.div`
gap: ${({ theme }) => theme.spacing(2)};
height: ${({ theme }) => theme.spacing(8)};
overflow: hidden;
padding-left: ${({ theme }) => theme.spacing(2)};
padding: 0 ${({ theme }) => theme.spacing(2)};
white-space: nowrap;
`;
@ -59,16 +71,38 @@ const StyledFieldLabel = styled.div`
`;
export const SettingsObjectFieldPreview = ({
objectIconKey,
objectLabelPlural,
isObjectCustom,
fieldIconKey,
fieldLabel,
fieldValue,
fieldName,
fieldType,
isObjectCustom,
objectIconKey,
objectLabelPlural,
objectNamePlural,
}: SettingsObjectFieldPreviewProps) => {
const theme = useTheme();
const { Icon: ObjectIcon } = useLazyLoadIcon(objectIconKey);
const { Icon: FieldIcon } = useLazyLoadIcon(fieldIconKey);
const { Icon: ObjectIcon } = useLazyLoadIcon(objectIconKey ?? '');
const { Icon: FieldIcon } = useLazyLoadIcon(fieldIconKey ?? '');
const { objects } = useFindManyObjects({
objectNamePlural,
skip: !fieldName,
});
const [fieldValue, setFieldValue] = useRecoilState(
entityFieldsFamilySelector({
entityId: objects[0]?.id ?? objectNamePlural,
fieldName: fieldName || 'new-field',
}),
);
useEffect(() => {
setFieldValue(
fieldName && assertNotNull(objects[0]?.[fieldName])
? objects[0][fieldName]
: dataTypes[fieldType].defaultValue,
);
}, [fieldName, fieldType, fieldValue, objects, setFieldValue]);
return (
<StyledContainer>
@ -98,7 +132,21 @@ export const SettingsObjectFieldPreview = ({
)}
{fieldLabel}:
</StyledFieldLabel>
{fieldValue}
<FieldContext.Provider
value={{
entityId: objects[0]?.id ?? objectNamePlural,
fieldDefinition: {
type: fieldType,
Icon: FieldIcon,
fieldId: '',
label: fieldLabel,
metadata: { fieldName: fieldName || 'new-field' },
},
hotkeyScope: 'field-preview',
}}
>
<FieldDisplay />
</FieldContext.Provider>
</StyledFieldPreview>
</StyledContainer>
);

View File

@ -1,7 +1,8 @@
import { ReactNode } from 'react';
import styled from '@emotion/styled';
type SettingsObjectFieldPreviewCardProps = {
type SettingsObjectFieldTypeCardProps = {
className?: string;
preview: ReactNode;
form?: ReactNode;
};
@ -36,12 +37,13 @@ const StyledFormContainer = styled.div`
padding: ${({ theme }) => theme.spacing(4)};
`;
export const SettingsObjectFieldPreviewCard = ({
export const SettingsObjectFieldTypeCard = ({
className,
preview,
form,
}: SettingsObjectFieldPreviewCardProps) => {
}: SettingsObjectFieldTypeCardProps) => {
return (
<div>
<div className={className}>
<StyledPreviewContainer>
<StyledTitle>Preview</StyledTitle>
{preview}

View File

@ -1,3 +1,5 @@
import styled from '@emotion/styled';
import { H2Title } from '@/ui/display/typography/components/H2Title';
import { Select } from '@/ui/input/components/Select';
import { Section } from '@/ui/layout/section/components/Section';
@ -5,19 +7,45 @@ import { Section } from '@/ui/layout/section/components/Section';
import { dataTypes } from '../constants/dataTypes';
import { MetadataFieldDataType } from '../types/ObjectFieldDataType';
import {
SettingsObjectFieldPreview,
SettingsObjectFieldPreviewProps,
} from './SettingsObjectFieldPreview';
import { SettingsObjectFieldTypeCard } from './SettingsObjectFieldTypeCard';
type SettingsObjectFieldTypeSelectSectionProps = {
disabled?: boolean;
onChange?: (value: MetadataFieldDataType) => void;
type: MetadataFieldDataType;
};
} & Pick<
SettingsObjectFieldPreviewProps,
| 'fieldIconKey'
| 'fieldLabel'
| 'fieldName'
| 'fieldType'
| 'isObjectCustom'
| 'objectIconKey'
| 'objectLabelPlural'
| 'objectNamePlural'
>;
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;
export const SettingsObjectFieldTypeSelectSection = ({
disabled,
fieldIconKey,
fieldLabel,
fieldName,
fieldType,
isObjectCustom,
objectIconKey,
objectLabelPlural,
objectNamePlural,
onChange,
type,
}: SettingsObjectFieldTypeSelectSectionProps) => (
<Section>
<H2Title
@ -27,7 +55,7 @@ export const SettingsObjectFieldTypeSelectSection = ({
<Select
disabled={disabled}
dropdownScopeId="object-field-type-select"
value={type}
value={fieldType}
onChange={onChange}
options={Object.entries(dataTypesWithoutRelation).map(
([key, dataType]) => ({
@ -36,5 +64,21 @@ export const SettingsObjectFieldTypeSelectSection = ({
}),
)}
/>
{fieldType === 'TEXT' && (
<StyledSettingsObjectFieldTypeCard
preview={
<SettingsObjectFieldPreview
fieldIconKey={fieldIconKey}
fieldLabel={fieldLabel}
fieldName={fieldName}
fieldType={fieldType}
isObjectCustom={isObjectCustom}
objectIconKey={objectIconKey}
objectLabelPlural={objectLabelPlural}
objectNamePlural={objectNamePlural}
/>
}
/>
)}
</Section>
);

View File

@ -9,12 +9,12 @@ const meta: Meta<typeof SettingsObjectFieldPreview> = {
component: SettingsObjectFieldPreview,
decorators: [ComponentDecorator],
args: {
objectIconKey: 'IconUser',
objectLabelPlural: 'People',
fieldIconKey: 'IconNotes',
fieldLabel: 'Description',
fieldValue:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum magna enim, dapibus non enim in, lacinia faucibus nunc. Sed interdum ante sed felis facilisis, eget ultricies neque molestie. Mauris auctor, justo eu volutpat cursus, libero erat tempus nulla, non sodales lorem lacus a est.',
fieldType: 'TEXT',
objectIconKey: 'IconUser',
objectLabelPlural: 'People',
objectNamePlural: 'people',
},
};

View File

@ -4,28 +4,29 @@ import { TextInput } from '@/ui/input/components/TextInput';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { SettingsObjectFieldPreview } from '../SettingsObjectFieldPreview';
import { SettingsObjectFieldPreviewCard } from '../SettingsObjectFieldPreviewCard';
import { SettingsObjectFieldTypeCard } from '../SettingsObjectFieldTypeCard';
const meta: Meta<typeof SettingsObjectFieldPreviewCard> = {
title: 'Modules/Settings/DataModel/SettingsObjectFieldPreviewCard',
component: SettingsObjectFieldPreviewCard,
const meta: Meta<typeof SettingsObjectFieldTypeCard> = {
title: 'Modules/Settings/DataModel/SettingsObjectFieldTypeCard',
component: SettingsObjectFieldTypeCard,
decorators: [ComponentDecorator],
args: {
preview: (
<SettingsObjectFieldPreview
objectIconKey="IconUser"
objectLabelPlural="People"
isObjectCustom={false}
fieldIconKey="IconNotes"
fieldLabel="Description"
fieldValue="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum magna enim, dapibus non enim in, lacinia faucibus nunc. Sed interdum ante sed felis facilisis, eget ultricies neque molestie. Mauris auctor, justo eu volutpat cursus, libero erat tempus nulla, non sodales lorem lacus a est."
fieldType="TEXT"
isObjectCustom={false}
objectIconKey="IconUser"
objectLabelPlural="People"
objectNamePlural="people"
/>
),
},
};
export default meta;
type Story = StoryObj<typeof SettingsObjectFieldPreviewCard>;
type Story = StoryObj<typeof SettingsObjectFieldTypeCard>;
export const Default: Story = {};

View File

@ -9,7 +9,7 @@ const meta: Meta<typeof SettingsObjectFieldTypeSelectSection> = {
title: 'Modules/Settings/DataModel/SettingsObjectFieldTypeSelectSection',
component: SettingsObjectFieldTypeSelectSection,
decorators: [ComponentDecorator],
args: { type: 'NUMBER' },
args: { fieldType: 'NUMBER' },
};
export default meta;

View File

@ -11,10 +11,15 @@ import { MetadataFieldDataType } from '../types/ObjectFieldDataType';
export const dataTypes: Record<
MetadataFieldDataType,
{ label: string; Icon: IconComponent }
{ label: string; Icon: IconComponent; defaultValue?: unknown }
> = {
NUMBER: { label: 'Number', Icon: IconNumbers },
TEXT: { label: 'Text', Icon: IconTextSize },
TEXT: {
label: 'Text',
Icon: IconTextSize,
defaultValue:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum magna enim, dapibus non enim in, lacinia faucibus nunc. Sed interdum ante sed felis facilisis, eget ultricies neque molestie. Mauris auctor, justo eu volutpat cursus, libero erat tempus nulla, non sodales lorem lacus a est.',
},
URL: { label: 'Link', Icon: IconLink },
BOOLEAN: { label: 'True/False', Icon: IconCheck },
RELATION: { label: 'Relation', Icon: IconPlug },

View File

@ -6,9 +6,9 @@ import { FieldMetadata } from '../types/FieldMetadata';
export type GenericFieldContextType = {
fieldDefinition: FieldDefinition<FieldMetadata>;
// TODO: add better typing for mutation web-hook
useUpdateEntityMutation: () => [(params: any) => void, any];
useUpdateEntityMutation?: () => [(params: any) => void, any];
entityId: string;
recoilScopeId: string;
recoilScopeId?: string;
hotkeyScope: string;
};

View File

@ -35,8 +35,11 @@ import { isFieldURLV2Value } from '../types/guards/isFieldURLV2Value';
import { isFieldURLValue } from '../types/guards/isFieldURLValue';
export const usePersistField = () => {
const { entityId, fieldDefinition, useUpdateEntityMutation } =
useContext(FieldContext);
const {
entityId,
fieldDefinition,
useUpdateEntityMutation = () => [],
} = useContext(FieldContext);
const [updateEntity] = useUpdateEntityMutation();
@ -102,7 +105,7 @@ export const usePersistField = () => {
valueToPersist,
);
updateEntity({
updateEntity?.({
variables: {
where: { id: entityId },
data: {
@ -120,7 +123,7 @@ export const usePersistField = () => {
valueToPersist,
);
updateEntity({
updateEntity?.({
variables: {
where: { id: entityId },
data: {
@ -145,7 +148,7 @@ export const usePersistField = () => {
valueToPersist.secondValue,
);
updateEntity({
updateEntity?.({
variables: {
where: { id: entityId },
data: {
@ -176,7 +179,7 @@ export const usePersistField = () => {
valueToPersist,
);
updateEntity({
updateEntity?.({
variables: {
where: { id: entityId },
data: {

View File

@ -6,8 +6,11 @@ import { entityFieldsFamilySelector } from '../states/selectors/entityFieldsFami
import { isFieldBoolean } from '../types/guards/isFieldBoolean';
export const useToggleEditOnlyInput = () => {
const { entityId, fieldDefinition, useUpdateEntityMutation } =
useContext(FieldContext);
const {
entityId,
fieldDefinition,
useUpdateEntityMutation = () => [],
} = useContext(FieldContext);
const [updateEntity] = useUpdateEntityMutation();
@ -27,7 +30,7 @@ export const useToggleEditOnlyInput = () => {
valueToPersist,
);
updateEntity({
updateEntity?.({
variables: {
where: { id: entityId },
data: {

View File

@ -21,12 +21,7 @@ export const FieldContextProvider = ({
recoilScopeId: '1',
hotkeyScope: 'hotkey-scope',
fieldDefinition,
useUpdateEntityMutation: () => [
() => {
return;
},
{},
],
useUpdateEntityMutation: () => [() => undefined, {}],
}}
>
{children}

View File

@ -3,11 +3,11 @@ import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { FieldContext } from '../../../../contexts/FieldContext';
import { useDateField } from '../../../hooks/useDateField';
import { DateFieldDisplay } from '../DateFieldDisplay';
const formattedDate = new Date();
const formattedDate = new Date('2023-04-01');
const DateFieldValueSetterEffect = ({ value }: { value: string }) => {
const { setFieldValue } = useDateField();
@ -16,62 +16,48 @@ const DateFieldValueSetterEffect = ({ value }: { value: string }) => {
setFieldValue(value);
}, [setFieldValue, value]);
return <></>;
};
type DateFieldDisplayWithContextProps = {
value: string;
entityId?: string;
};
const DateFieldDisplayWithContext = ({
value,
entityId,
}: DateFieldDisplayWithContextProps) => {
return (
<FieldContextProvider
fieldDefinition={{
fieldId: 'date',
label: 'Date',
type: 'DATE',
metadata: {
fieldName: 'Date',
},
}}
entityId={entityId}
>
<DateFieldValueSetterEffect value={value} />
<DateFieldDisplay />
</FieldContextProvider>
);
return null;
};
const meta: Meta = {
title: 'UI/Data/Field/Display/DateFieldDisplay',
component: DateFieldDisplayWithContext,
decorators: [
(Story, { args }) => (
<FieldContext.Provider
value={{
entityId: '',
fieldDefinition: {
fieldId: 'date',
label: 'Date',
type: 'DATE',
metadata: {
fieldName: 'Date',
},
},
hotkeyScope: 'hotkey-scope',
}}
>
<DateFieldValueSetterEffect value={args.value} />
<Story />
</FieldContext.Provider>
),
ComponentDecorator,
],
component: DateFieldDisplay,
argTypes: { value: { control: 'date' } },
args: {
value: formattedDate,
},
};
export default meta;
type Story = StoryObj<typeof DateFieldDisplayWithContext>;
type Story = StoryObj<typeof DateFieldDisplay>;
export const Default: Story = {
args: {
value: formattedDate.toISOString(),
},
};
export const Default: Story = {};
export const Elipsis: Story = {
args: {
value: formattedDate.toISOString(),
},
argTypes: {
value: { control: false },
},
parameters: {
container: {
width: 50,
},
container: { width: 50 },
},
decorators: [ComponentDecorator],
};

View File

@ -1,9 +1,9 @@
import React, { useEffect } from 'react';
import { useEffect } from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { FieldContext } from '../../../../contexts/FieldContext';
import { useDoubleTextField } from '../../../hooks/useDoubleTextField';
import { DoubleTextFieldDisplay } from '../DoubleTextFieldDisplay'; // Import your component
@ -21,66 +21,51 @@ const DoubleTextFieldDisplayValueSetterEffect = ({
setSecondValue(secondValue);
}, [setFirstValue, setSecondValue, firstValue, secondValue]);
return <></>;
};
type DoubleTextFieldDisplayWithContextProps = {
firstValue: string;
secondValue: string;
entityId?: string;
};
const DoubleTextFieldDisplayWithContext = ({
firstValue,
secondValue,
entityId,
}: DoubleTextFieldDisplayWithContextProps) => {
return (
<FieldContextProvider
fieldDefinition={{
fieldId: 'double-text',
label: 'Double-Text',
type: 'DOUBLE_TEXT',
metadata: {
firstValueFieldName: 'First-text',
firstValuePlaceholder: 'First-text',
secondValueFieldName: 'Second-text',
secondValuePlaceholder: 'Second-text',
},
}}
entityId={entityId}
>
<DoubleTextFieldDisplayValueSetterEffect
firstValue={firstValue}
secondValue={secondValue}
/>
<DoubleTextFieldDisplay />
</FieldContextProvider>
);
return null;
};
const meta: Meta = {
title: 'UI/Data/Field/Display/DoubleTextFieldDisplay',
component: DoubleTextFieldDisplayWithContext,
decorators: [
(Story, { args }) => (
<FieldContext.Provider
value={{
entityId: '',
fieldDefinition: {
fieldId: 'double-text',
label: 'Double-Text',
type: 'DOUBLE_TEXT',
metadata: {
firstValueFieldName: 'First-text',
firstValuePlaceholder: 'First-text',
secondValueFieldName: 'Second-text',
secondValuePlaceholder: 'Second-text',
},
},
hotkeyScope: 'hotkey-scope',
}}
>
<DoubleTextFieldDisplayValueSetterEffect
firstValue={args.firstValue}
secondValue={args.secondValue}
/>
<Story />
</FieldContext.Provider>
),
ComponentDecorator,
],
component: DoubleTextFieldDisplay,
args: {
firstValue: 'Lorem',
secondValue: 'ipsum',
},
};
export default meta;
type Story = StoryObj<typeof DoubleTextFieldDisplayWithContext>;
type Story = StoryObj<typeof DoubleTextFieldDisplay>;
export const Default: Story = {
args: {
firstValue: 'Lorem',
secondValue: 'ipsum',
},
};
export const CustomValues: Story = {
args: {
firstValue: 'Lorem',
secondValue: 'ipsum',
},
};
export const Default: Story = {};
export const Elipsis: Story = {
args: {
@ -88,14 +73,7 @@ export const Elipsis: Story = {
'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
secondValue: 'ipsum dolor sit amet, consectetur adipiscing elit.',
},
argTypes: {
firstValue: { control: true },
secondValue: { control: true },
},
parameters: {
container: {
width: 100,
},
container: { width: 100 },
},
decorators: [ComponentDecorator],
};

View File

@ -4,7 +4,7 @@ import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { FieldContext } from '../../../../contexts/FieldContext';
import { useEmailField } from '../../../hooks/useEmailField';
import { EmailFieldDisplay } from '../EmailFieldDisplay';
@ -15,65 +15,50 @@ const EmailFieldValueSetterEffect = ({ value }: { value: string }) => {
setFieldValue(value);
}, [setFieldValue, value]);
return <></>;
};
type EmailFieldDisplayWithContextProps = {
value: string;
entityId?: string;
};
const EmailFieldDisplayWithContext = ({
value,
entityId,
}: EmailFieldDisplayWithContextProps) => {
return (
<FieldContextProvider
fieldDefinition={{
fieldId: 'email',
label: 'Email',
type: 'EMAIL',
metadata: {
fieldName: 'Email',
placeHolder: 'Email',
},
}}
entityId={entityId}
>
<MemoryRouter>
<EmailFieldValueSetterEffect value={value} />
<EmailFieldDisplay />
</MemoryRouter>
</FieldContextProvider>
);
return null;
};
const meta: Meta = {
title: 'UI/Data/Field/Display/EmailFieldDisplay',
component: EmailFieldDisplayWithContext,
decorators: [
(Story, { args }) => (
<FieldContext.Provider
value={{
entityId: '',
fieldDefinition: {
fieldId: 'email',
label: 'Email',
type: 'EMAIL',
metadata: {
fieldName: 'Email',
placeHolder: 'Email',
},
},
hotkeyScope: 'hotkey-scope',
}}
>
<MemoryRouter>
<EmailFieldValueSetterEffect value={args.value} />
<Story />
</MemoryRouter>
</FieldContext.Provider>
),
ComponentDecorator,
],
component: EmailFieldDisplay,
args: {
value: 'Test@Test.test',
},
};
export default meta;
type Story = StoryObj<typeof EmailFieldDisplayWithContext>;
type Story = StoryObj<typeof EmailFieldDisplay>;
export const Default: Story = {
args: {
value: 'Test@Test.test',
},
};
export const Default: Story = {};
export const Elipsis: Story = {
args: {
value: 'Test@Test.test',
},
argTypes: {
value: { control: false },
},
parameters: {
container: {
width: 50,
},
container: { width: 50 },
},
decorators: [ComponentDecorator],
};

View File

@ -1,12 +1,9 @@
import { useEffect } from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { v4 } from 'uuid';
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { CatalogStory } from '~/testing/types';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { FieldContext } from '../../../../contexts/FieldContext';
import { useMoneyField } from '../../../hooks/useMoneyField';
import { MoneyFieldDisplay } from '../MoneyFieldDisplay';
@ -17,95 +14,53 @@ const MoneyFieldValueSetterEffect = ({ value }: { value: number }) => {
setFieldValue(value);
}, [setFieldValue, value]);
return <></>;
};
type MoneyFieldDisplayWithContextProps = {
value: number;
entityId?: string;
};
const MoneyFieldDisplayWithContext = ({
value,
entityId,
}: MoneyFieldDisplayWithContextProps) => {
return (
<FieldContextProvider
fieldDefinition={{
fieldId: 'money',
label: 'Money',
type: 'MONEY_AMOUNT',
metadata: {
fieldName: 'Amount',
placeHolder: 'Amount',
isPositive: true,
},
}}
entityId={entityId}
>
<MoneyFieldValueSetterEffect value={value} />
<MoneyFieldDisplay />
</FieldContextProvider>
);
return null;
};
const meta: Meta = {
title: 'UI/Data/Field/Display/MoneyFieldDisplay',
component: MoneyFieldDisplayWithContext,
};
export default meta;
type Story = StoryObj<typeof MoneyFieldDisplayWithContext>;
export const Default: Story = {
decorators: [
(Story, { args }) => (
<FieldContext.Provider
value={{
entityId: '',
fieldDefinition: {
fieldId: 'money',
label: 'Money',
type: 'MONEY_AMOUNT',
metadata: {
fieldName: 'Amount',
placeHolder: 'Amount',
isPositive: true,
},
},
hotkeyScope: 'hotkey-scope',
useUpdateEntityMutation: () => [() => undefined, undefined],
}}
>
<MoneyFieldValueSetterEffect value={args.value} />
<Story />
</FieldContext.Provider>
),
ComponentDecorator,
],
component: MoneyFieldDisplay,
args: {
value: 100,
},
};
export default meta;
type Story = StoryObj<typeof MoneyFieldDisplay>;
export const Default: Story = {};
export const Elipsis: Story = {
args: {
value: 1e100,
},
argTypes: {
value: { control: false },
},
parameters: {
container: {
width: 100,
},
container: { width: 100 },
},
decorators: [ComponentDecorator],
};
export const Catalog: CatalogStory<Story, typeof MoneyFieldDisplayWithContext> =
{
argTypes: {
value: { control: false },
},
parameters: {
catalog: {
dimensions: [
{
name: 'currency',
values: ['$'] satisfies string[],
props: (_value: string) => ({}),
},
{
name: 'value',
values: [
100, 1000, -1000, 1e10, 1.357802, -1.283, 0,
] satisfies number[],
props: (value: number) => ({ value, entityId: v4() }),
},
],
options: {
elementContainer: {
width: 100,
},
},
},
},
decorators: [CatalogDecorator],
};

View File

@ -1,12 +1,9 @@
import { useEffect } from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { v4 } from 'uuid';
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { CatalogStory } from '~/testing/types';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { FieldContext } from '../../../../contexts/FieldContext';
import { useNumberField } from '../../../hooks/useNumberField';
import { NumberFieldDisplay } from '../NumberFieldDisplay';
@ -17,46 +14,42 @@ const NumberFieldValueSetterEffect = ({ value }: { value: number }) => {
setFieldValue(value);
}, [setFieldValue, value]);
return <></>;
};
type NumberFieldDisplayWithContextProps = {
value: number;
entityId?: string;
};
const NumberFieldDisplayWithContext = ({
value,
entityId,
}: NumberFieldDisplayWithContextProps) => {
return (
<FieldContextProvider
fieldDefinition={{
fieldId: 'number',
label: 'Number',
type: 'NUMBER',
metadata: {
fieldName: 'Number',
placeHolder: 'Number',
isPositive: true,
},
}}
entityId={entityId}
>
<NumberFieldValueSetterEffect value={value} />
<NumberFieldDisplay />
</FieldContextProvider>
);
return null;
};
const meta: Meta = {
title: 'UI/Data/Field/Display/NumberFieldDisplay',
component: NumberFieldDisplayWithContext,
decorators: [
(Story, { args }) => (
<FieldContext.Provider
value={{
entityId: '',
fieldDefinition: {
fieldId: 'number',
label: 'Number',
type: 'NUMBER',
metadata: {
fieldName: 'Number',
placeHolder: 'Number',
isPositive: true,
},
},
hotkeyScope: 'hotkey-scope',
useUpdateEntityMutation: () => [() => undefined, undefined],
}}
>
<NumberFieldValueSetterEffect value={args.value} />
<Story />
</FieldContext.Provider>
),
ComponentDecorator,
],
component: NumberFieldDisplay,
};
export default meta;
type Story = StoryObj<typeof NumberFieldDisplayWithContext>;
type Story = StoryObj<typeof NumberFieldDisplay>;
export const Default: Story = {
args: {
@ -68,41 +61,19 @@ export const Elipsis: Story = {
args: {
value: 1e100,
},
argTypes: {
value: { control: false },
},
parameters: {
container: {
width: 100,
},
container: { width: 100 },
},
decorators: [ComponentDecorator],
};
export const Catalog: CatalogStory<
Story,
typeof NumberFieldDisplayWithContext
> = {
argTypes: {
value: { control: false },
export const Negative: Story = {
args: {
value: -1000,
},
};
export const Float: Story = {
args: {
value: 1.357802,
},
parameters: {
catalog: {
dimensions: [
{
name: 'value',
values: [
100, 1000, -1000, 1e10, 1.357802, -1.283, 0,
] satisfies number[],
props: (value: number) => ({ value, entityId: v4() }),
},
],
options: {
elementContainer: {
width: 100,
},
},
},
},
decorators: [CatalogDecorator],
};

View File

@ -4,7 +4,7 @@ import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { FieldContext } from '../../../../contexts/FieldContext';
import { usePhoneField } from '../../../hooks/usePhoneField';
import { PhoneFieldDisplay } from '../PhoneFieldDisplay';
@ -15,65 +15,51 @@ const PhoneFieldValueSetterEffect = ({ value }: { value: string }) => {
setFieldValue(value);
}, [setFieldValue, value]);
return <></>;
};
type PhoneFieldDisplayWithContextProps = {
value: string;
entityId?: string;
};
const PhoneFieldDisplayWithContext = ({
value,
entityId,
}: PhoneFieldDisplayWithContextProps) => {
return (
<FieldContextProvider
fieldDefinition={{
fieldId: 'phone',
label: 'Phone',
type: 'PHONE',
metadata: {
fieldName: 'Phone',
placeHolder: 'Phone',
},
}}
entityId={entityId}
>
<MemoryRouter>
<PhoneFieldValueSetterEffect value={value} />
<PhoneFieldDisplay />
</MemoryRouter>
</FieldContextProvider>
);
return null;
};
const meta: Meta = {
title: 'UI/Data/Field/Display/PhoneFieldDisplay',
component: PhoneFieldDisplayWithContext,
decorators: [
(Story, { args }) => (
<FieldContext.Provider
value={{
entityId: '',
fieldDefinition: {
fieldId: 'phone',
label: 'Phone',
type: 'PHONE',
metadata: {
fieldName: 'Phone',
placeHolder: 'Phone',
},
},
hotkeyScope: 'hotkey-scope',
useUpdateEntityMutation: () => [() => undefined, undefined],
}}
>
<MemoryRouter>
<PhoneFieldValueSetterEffect value={args.value} />
<Story />
</MemoryRouter>
</FieldContext.Provider>
),
ComponentDecorator,
],
component: PhoneFieldDisplay,
args: {
value: '362763872687362',
},
};
export default meta;
type Story = StoryObj<typeof PhoneFieldDisplayWithContext>;
type Story = StoryObj<typeof PhoneFieldDisplay>;
export const Default: Story = {
args: {
value: '362763872687362',
},
};
export const Default: Story = {};
export const Elipsis: Story = {
args: {
value: '362763872687362',
},
argTypes: {
value: { control: false },
},
parameters: {
container: {
width: 50,
},
container: { width: 50 },
},
decorators: [ComponentDecorator],
};

View File

@ -1,12 +1,9 @@
import { useEffect } from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { v4 } from 'uuid';
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { CatalogStory } from '~/testing/types';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { FieldContext } from '../../../../contexts/FieldContext';
import { useTextField } from '../../../hooks/useTextField';
import { TextFieldDisplay } from '../TextFieldDisplay';
@ -17,94 +14,52 @@ const TextFieldValueSetterEffect = ({ value }: { value: string }) => {
setFieldValue(value);
}, [setFieldValue, value]);
return <></>;
};
type TextFieldDisplayWithContextProps = {
value: string;
entityId?: string;
};
const TextFieldDisplayWithContext = ({
value,
entityId,
}: TextFieldDisplayWithContextProps) => {
return (
<FieldContextProvider
fieldDefinition={{
fieldId: 'text',
label: 'Text',
type: 'TEXT',
metadata: {
fieldName: 'Text',
placeHolder: 'Text',
},
}}
entityId={entityId}
>
<TextFieldValueSetterEffect value={value} />
<TextFieldDisplay />
</FieldContextProvider>
);
return null;
};
const meta: Meta = {
title: 'UI/Data/Field/Display/TextFieldDisplay',
component: TextFieldDisplayWithContext,
};
export default meta;
type Story = StoryObj<typeof TextFieldDisplayWithContext>;
export const Default: Story = {
decorators: [
(Story, { args }) => (
<FieldContext.Provider
value={{
entityId: '',
fieldDefinition: {
fieldId: 'text',
label: 'Text',
type: 'TEXT',
metadata: {
fieldName: 'Text',
placeHolder: 'Text',
},
},
hotkeyScope: 'hotkey-scope',
}}
>
<TextFieldValueSetterEffect value={args.value} />
<Story />
</FieldContext.Provider>
),
ComponentDecorator,
],
component: TextFieldDisplay,
args: {
value: 'Lorem ipsum',
},
};
export default meta;
type Story = StoryObj<typeof TextFieldDisplay>;
export const Default: Story = {};
export const Elipsis: Story = {
args: {
value:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Recusandae rerum fugiat veniam illum accusantium saepe, voluptate inventore libero doloribus doloremque distinctio blanditiis amet quis dolor a nulla? Placeat nam itaque rerum esse quidem animi, temporibus saepe debitis commodi quia eius eos minus inventore. Voluptates fugit optio sit ab consectetur ipsum, neque eius atque blanditiis. Ullam provident at porro minima, nobis vero dicta consequatur maxime laboriosam fugit repudiandae repellat tempore voluptas non voluptatibus neque aliquam ducimus doloribus ipsa? Sapiente suscipit unde modi commodi possimus doloribus eum voluptatibus, architecto laudantium, magnam, eos numquam exercitationem est maxime explicabo odio nemo qui distinctio temporibus.',
},
argTypes: {
value: { control: false },
},
parameters: {
container: {
width: 100,
},
container: { width: 100 },
},
decorators: [ComponentDecorator],
};
export const Catalog: CatalogStory<Story, typeof TextFieldDisplayWithContext> =
{
argTypes: {
value: { control: false },
},
parameters: {
catalog: {
dimensions: [
{
name: 'value',
values: [
'Hello world',
'Test',
'1234567890',
' ',
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Recusandae rerum fugiat veniam illum accusantium saepe, voluptate inventore libero doloribus doloremque distinctio blanditiis amet quis dolor a nulla? Placeat nam itaque rerum esse quidem animi, temporibus saepe debitis commodi quia eius eos minus inventore. Voluptates fugit optio sit ab consectetur ipsum, neque eius atque blanditiis. Ullam provident at porro minima, nobis vero dicta consequatur maxime laboriosam fugit repudiandae repellat tempore voluptas non voluptatibus neque aliquam ducimus doloribus ipsa? Sapiente suscipit unde modi commodi possimus doloribus eum voluptatibus, architecto laudantium, magnam, eos numquam exercitationem est maxime explicabo odio nemo qui distinctio temporibus.',
] satisfies string[],
props: (value: string) => ({ value, entityId: v4() }),
},
],
options: {
elementContainer: {
width: 100,
},
},
},
},
decorators: [CatalogDecorator],
};

View File

@ -4,7 +4,7 @@ import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { FieldContext } from '../../../../contexts/FieldContext';
import { useURLField } from '../../../hooks/useURLField';
import { URLFieldDisplay } from '../URLFieldDisplay';
@ -15,65 +15,50 @@ const URLFieldValueSetterEffect = ({ value }: { value: string }) => {
setFieldValue(value);
}, [setFieldValue, value]);
return <></>;
};
type URLFieldDisplayWithContextProps = {
value: string;
entityId?: string;
};
const URLFieldDisplayWithContext = ({
value,
entityId,
}: URLFieldDisplayWithContextProps) => {
return (
<FieldContextProvider
fieldDefinition={{
fieldId: 'URL',
label: 'URL',
type: 'URL',
metadata: {
fieldName: 'URL',
placeHolder: 'URL',
},
}}
entityId={entityId}
>
<MemoryRouter>
<URLFieldValueSetterEffect value={value} />
<URLFieldDisplay />
</MemoryRouter>
</FieldContextProvider>
);
return null;
};
const meta: Meta = {
title: 'UI/Data/Field/Display/URLFieldDisplay',
component: URLFieldDisplayWithContext,
decorators: [
(Story, { args }) => (
<FieldContext.Provider
value={{
entityId: '',
fieldDefinition: {
fieldId: 'URL',
label: 'URL',
type: 'URL',
metadata: {
fieldName: 'URL',
placeHolder: 'URL',
},
},
hotkeyScope: 'hotkey-scope',
}}
>
<MemoryRouter>
<URLFieldValueSetterEffect value={args.value} />
<Story />
</MemoryRouter>
</FieldContext.Provider>
),
ComponentDecorator,
],
component: URLFieldDisplay,
args: {
value: 'https://github.com/twentyhq',
},
};
export default meta;
type Story = StoryObj<typeof URLFieldDisplayWithContext>;
type Story = StoryObj<typeof URLFieldDisplay>;
export const Default: Story = {
args: {
value: 'https://github.com/GitStartHQ',
},
};
export const Default: Story = {};
export const Elipsis: Story = {
args: {
value: 'https://www.instagram.com/gitstart/',
},
argTypes: {
value: { control: true },
},
parameters: {
container: {
width: 200,
},
container: { width: 200 },
},
decorators: [ComponentDecorator],
};

View File

@ -9,7 +9,7 @@ import { isInlineCellInEditModeScopedState } from '../states/isInlineCellInEditM
import { InlineCellHotkeyScope } from '../types/InlineCellHotkeyScope';
export const useInlineCell = () => {
const { recoilScopeId } = useContext(FieldContext);
const { recoilScopeId = '' } = useContext(FieldContext);
const [isInlineCellInEditMode, setIsInlineCellInEditMode] = useRecoilState(
isInlineCellInEditModeScopedState(recoilScopeId),

View File

@ -125,7 +125,14 @@ export const SettingsObjectFieldEdit = () => {
/>
<SettingsObjectFieldTypeSelectSection
disabled
type={activeMetadataField.type as MetadataFieldDataType}
fieldIconKey={formValues.icon}
fieldLabel={formValues.label || 'Employees'}
fieldName={activeMetadataField.name}
fieldType={activeMetadataField.type as MetadataFieldDataType}
isObjectCustom={activeObjectMetadataItem.isCustom}
objectIconKey={activeObjectMetadataItem.icon}
objectLabelPlural={activeObjectMetadataItem.labelPlural}
objectNamePlural={activeObjectMetadataItem.namePlural}
/>
<Section>
<H2Title title="Danger zone" description="Disable this field" />

View File

@ -118,7 +118,13 @@ export const SettingsObjectNewFieldStep2 = () => {
}
/>
<SettingsObjectFieldTypeSelectSection
type={formValues.type}
fieldIconKey={formValues.icon}
fieldLabel={formValues.label || 'Employees'}
fieldType={formValues.type}
isObjectCustom={activeObjectMetadataItem.isCustom}
objectIconKey={activeObjectMetadataItem.icon}
objectLabelPlural={activeObjectMetadataItem.labelPlural}
objectNamePlural={activeObjectMetadataItem.namePlural}
onChange={(type) =>
setFormValues((previousValues) => ({ ...previousValues, type }))
}

View File

@ -17,4 +17,10 @@ export const mockedViewsData = [
objectId: 'company',
type: 'kanban',
},
{
id: '5c307222-1dd5-4ff3-ab06-8d990e9b3c74',
name: 'All companies (v2)',
objectId: 'a3195559-cc20-4749-9565-572a2f506581',
type: 'table',
},
];