refactor: use react-hook-form in Settings Data Model Object pages (#4271)

Related issue: #3836
This commit is contained in:
Thaïs 2024-03-05 07:52:19 -03:00 committed by GitHub
parent caa4dcf893
commit 91e5e7598b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 338 additions and 267 deletions

View File

@ -56,6 +56,7 @@
"@types/facepaint": "^1.2.5", "@types/facepaint": "^1.2.5",
"@types/lodash.camelcase": "^4.3.7", "@types/lodash.camelcase": "^4.3.7",
"@types/lodash.merge": "^4.6.7", "@types/lodash.merge": "^4.6.7",
"@types/lodash.pick": "^4.3.7",
"@types/mailparser": "^3.4.4", "@types/mailparser": "^3.4.4",
"@types/nodemailer": "^6.4.14", "@types/nodemailer": "^6.4.14",
"add": "^2.0.6", "add": "^2.0.6",
@ -112,6 +113,7 @@
"lodash.kebabcase": "^4.1.1", "lodash.kebabcase": "^4.1.1",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"lodash.omit": "^4.5.0", "lodash.omit": "^4.5.0",
"lodash.pick": "^4.4.0",
"lodash.snakecase": "^4.1.1", "lodash.snakecase": "^4.1.1",
"lodash.upperfirst": "^4.3.1", "lodash.upperfirst": "^4.3.1",
"luxon": "^3.3.0", "luxon": "^3.3.0",

View File

@ -1,103 +0,0 @@
import styled from '@emotion/styled';
import { validateMetadataLabel } from '@/object-metadata/utils/validateMetadataLabel';
import { H2Title } from '@/ui/display/typography/components/H2Title';
import { IconPicker } from '@/ui/input/components/IconPicker';
import { TextArea } from '@/ui/input/components/TextArea';
import { TextInput } from '@/ui/input/components/TextInput';
import { Section } from '@/ui/layout/section/components/Section';
type SettingsObjectFormSectionProps = {
disabled?: boolean;
icon?: string;
singularName?: string;
pluralName?: string;
description?: string;
onChange?: (
formValues: Partial<{
icon: string;
labelSingular: string;
labelPlural: string;
description: string;
}>,
) => void;
};
const StyledInputsContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
margin-bottom: ${({ theme }) => theme.spacing(2)};
width: 100%;
`;
const StyledLabel = styled.span`
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)};
text-transform: uppercase;
`;
const StyledInputContainer = styled.div`
display: flex;
flex-direction: column;
`;
export const SettingsObjectFormSection = ({
disabled,
icon = 'IconListNumbers',
singularName = '',
pluralName = '',
description = '',
onChange,
}: SettingsObjectFormSectionProps) => (
<Section>
<H2Title
title="About"
description="Name in both singular (e.g., 'Invoice') and plural (e.g., 'Invoices') forms."
/>
<StyledInputsContainer>
<StyledInputContainer>
<StyledLabel>Icon</StyledLabel>
<IconPicker
disabled={disabled}
selectedIconKey={icon}
onChange={(icon) => {
onChange?.({ icon: icon.iconKey });
}}
/>
</StyledInputContainer>
<TextInput
label="Singular"
placeholder="Listing"
value={singularName}
onChange={(value) => {
if (!value || validateMetadataLabel(value)) {
onChange?.({ labelSingular: value });
}
}}
disabled={disabled}
fullWidth
/>
<TextInput
label="Plural"
placeholder="Listings"
value={pluralName}
onChange={(value) => {
if (!value || validateMetadataLabel(value)) {
onChange?.({ labelPlural: value });
}
}}
disabled={disabled}
fullWidth
/>
</StyledInputsContainer>
<TextArea
placeholder="Write a description"
minRows={4}
value={description}
onChange={(value) => onChange?.({ description: value })}
disabled={disabled}
/>
</Section>
);

View File

@ -1,24 +0,0 @@
import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { SettingsObjectFormSection } from '../SettingsObjectFormSection';
const meta: Meta<typeof SettingsObjectFormSection> = {
title: 'Modules/Settings/DataModel/SettingsObjectFormSection',
component: SettingsObjectFormSection,
decorators: [ComponentDecorator],
};
export default meta;
type Story = StoryObj<typeof SettingsObjectFormSection>;
export const Default: Story = {};
export const WithDefaultValues: Story = {
args: {
singularName: 'Company',
pluralName: 'Companies',
description: 'Lorem ipsum',
},
};

View File

@ -0,0 +1,120 @@
import { Controller, useFormContext } from 'react-hook-form';
import styled from '@emotion/styled';
import { z } from 'zod';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema';
import { IconPicker } from '@/ui/input/components/IconPicker';
import { TextArea } from '@/ui/input/components/TextArea';
import { TextInput } from '@/ui/input/components/TextInput';
export const settingsDataModelObjectAboutFormSchema =
objectMetadataItemSchema.pick({
description: true,
icon: true,
labelPlural: true,
labelSingular: true,
});
type SettingsDataModelObjectAboutFormValues = z.infer<
typeof settingsDataModelObjectAboutFormSchema
>;
type SettingsDataModelObjectAboutFormProps = {
disabled?: boolean;
objectMetadataItem?: ObjectMetadataItem;
};
const StyledInputsContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
margin-bottom: ${({ theme }) => theme.spacing(2)};
width: 100%;
`;
const StyledLabel = styled.span`
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)};
text-transform: uppercase;
`;
const StyledInputContainer = styled.div`
display: flex;
flex-direction: column;
`;
export const SettingsDataModelObjectAboutForm = ({
disabled,
objectMetadataItem,
}: SettingsDataModelObjectAboutFormProps) => {
const { control } = useFormContext<SettingsDataModelObjectAboutFormValues>();
return (
<>
<StyledInputsContainer>
<StyledInputContainer>
<StyledLabel>Icon</StyledLabel>
<Controller
name="icon"
control={control}
defaultValue={objectMetadataItem?.icon ?? 'IconListNumbers'}
render={({ field: { onChange, value } }) => (
<IconPicker
disabled={disabled}
selectedIconKey={value}
onChange={({ iconKey }) => onChange(iconKey)}
/>
)}
/>
</StyledInputContainer>
{[
{
label: 'Singular',
fieldName: 'labelSingular' as const,
placeholder: 'Listing',
defaultValue: objectMetadataItem?.labelSingular,
},
{
label: 'Plural',
fieldName: 'labelPlural' as const,
placeholder: 'Listings',
defaultValue: objectMetadataItem?.labelPlural,
},
].map(({ defaultValue, fieldName, label, placeholder }) => (
<Controller
key={`object-${fieldName}-text-input`}
name={fieldName}
control={control}
defaultValue={defaultValue}
render={({ field: { onChange, value } }) => (
<TextInput
label={label}
placeholder={placeholder}
value={value}
onChange={onChange}
disabled={disabled}
fullWidth
/>
)}
/>
))}
</StyledInputsContainer>
<Controller
name="description"
control={control}
defaultValue={objectMetadataItem?.description ?? null}
render={({ field: { onChange, value } }) => (
<TextArea
placeholder="Write a description"
minRows={4}
value={value ?? undefined}
onChange={(nextValue) => onChange(nextValue ?? null)}
disabled={disabled}
/>
)}
/>
</>
);
};

View File

@ -0,0 +1,43 @@
import styled from '@emotion/styled';
import { Meta, StoryObj } from '@storybook/react';
import { mockedCompanyObjectMetadataItem } from '@/object-record/record-field/__mocks__/fieldDefinitions';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { FormProviderDecorator } from '~/testing/decorators/FormProviderDecorator';
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
import { SettingsDataModelObjectAboutForm } from '../SettingsDataModelObjectAboutForm';
const StyledContainer = styled.div`
flex: 1;
`;
const meta: Meta<typeof SettingsDataModelObjectAboutForm> = {
title:
'Modules/Settings/DataModel/Objects/Forms/SettingsDataModelObjectAboutForm',
component: SettingsDataModelObjectAboutForm,
decorators: [
(Story) => (
<StyledContainer>
<Story />
</StyledContainer>
),
FormProviderDecorator,
IconsProviderDecorator,
ComponentDecorator,
],
parameters: {
container: { width: 520 },
},
};
export default meta;
type Story = StoryObj<typeof SettingsDataModelObjectAboutForm>;
export const Default: Story = {};
export const WithDefaultValues: Story = {
args: {
objectMetadataItem: mockedCompanyObjectMetadataItem,
},
};

View File

@ -1,47 +1,60 @@
import { useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useCreateOneObjectMetadataItem } from '@/object-metadata/hooks/useCreateOneObjectMetadataItem'; import { useCreateOneObjectMetadataItem } from '@/object-metadata/hooks/useCreateOneObjectMetadataItem';
import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug'; import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer'; import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsObjectFormSection } from '@/settings/data-model/components/SettingsObjectFormSection'; import {
SettingsDataModelObjectAboutForm,
settingsDataModelObjectAboutFormSchema,
} from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm';
import { settingsCreateObjectInputSchema } from '@/settings/data-model/validation-schemas/settingsCreateObjectInputSchema'; import { settingsCreateObjectInputSchema } from '@/settings/data-model/validation-schemas/settingsCreateObjectInputSchema';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
import { IconSettings } from '@/ui/display/icon'; import { IconSettings } from '@/ui/display/icon';
import { H2Title } from '@/ui/display/typography/components/H2Title';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
const newObjectFormSchema = settingsDataModelObjectAboutFormSchema;
type SettingsDataModelNewObjectFormValues = z.infer<typeof newObjectFormSchema>;
export const SettingsNewObject = () => { export const SettingsNewObject = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { enqueueSnackBar } = useSnackBar(); const { enqueueSnackBar } = useSnackBar();
const { createOneObjectMetadataItem } = useCreateOneObjectMetadataItem(); const { createOneObjectMetadataItem } = useCreateOneObjectMetadataItem();
const [formValues, setFormValues] = useState<{ const settingsObjectsPagePath = getSettingsPagePath(SettingsPath.Objects);
description?: string;
icon: string;
labelPlural: string;
labelSingular: string;
}>({ icon: 'IconListNumbers', labelPlural: '', labelSingular: '' });
const canSave = !!formValues.labelPlural && !!formValues.labelSingular; const formConfig = useForm<SettingsDataModelNewObjectFormValues>({
mode: 'onTouched',
resolver: zodResolver(newObjectFormSchema),
});
const canSave = formConfig.formState.isValid;
const handleSave = async () => { const handleSave = async () => {
const formValues = formConfig.getValues();
try { try {
const createdObject = await createOneObjectMetadataItem( const { data: response } = await createOneObjectMetadataItem(
settingsCreateObjectInputSchema.parse(formValues), settingsCreateObjectInputSchema.parse(formValues),
); );
navigate( navigate(
createdObject.data?.createOneObject.isActive response
? `/settings/objects/${getObjectSlug( ? `${settingsObjectsPagePath}/${getObjectSlug(
createdObject.data.createOneObject, response.createOneObject,
)}` )}`
: '/settings/objects', : settingsObjectsPagePath,
); );
} catch (error) { } catch (error) {
enqueueSnackBar((error as Error).message, { enqueueSnackBar((error as Error).message, {
@ -51,37 +64,35 @@ export const SettingsNewObject = () => {
}; };
return ( return (
<SubMenuTopBarContainer Icon={IconSettings} title="Settings"> // eslint-disable-next-line react/jsx-props-no-spreading
<SettingsPageContainer> <FormProvider {...formConfig}>
<SettingsHeaderContainer> <SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<Breadcrumb <SettingsPageContainer>
links={[ <SettingsHeaderContainer>
{ <Breadcrumb
children: 'Objects', links={[
href: getSettingsPagePath(SettingsPath.Objects), {
}, children: 'Objects',
{ children: 'New' }, href: settingsObjectsPagePath,
]} },
/> { children: 'New' },
<SaveAndCancelButtons ]}
isSaveDisabled={!canSave} />
onCancel={() => navigate(getSettingsPagePath(SettingsPath.Objects))} <SaveAndCancelButtons
onSave={handleSave} isSaveDisabled={!canSave}
/> onCancel={() => navigate(settingsObjectsPagePath)}
</SettingsHeaderContainer> onSave={handleSave}
<SettingsObjectFormSection />
icon={formValues.icon} </SettingsHeaderContainer>
singularName={formValues.labelSingular} <Section>
pluralName={formValues.labelPlural} <H2Title
description={formValues.description} title="About"
onChange={(formValues) => { description="Name in both singular (e.g., 'Invoice') and plural (e.g., 'Invoices') forms."
setFormValues((previousValues) => ({ />
...previousValues, <SettingsDataModelObjectAboutForm />
...formValues, </Section>
})); </SettingsPageContainer>
}} </SubMenuTopBarContainer>
/> </FormProvider>
</SettingsPageContainer>
</SubMenuTopBarContainer>
); );
}; };

View File

@ -1,5 +1,9 @@
import { useEffect, useState } from 'react'; import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { zodResolver } from '@hookform/resolvers/zod';
import pick from 'lodash.pick';
import { z } from 'zod';
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings'; import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem'; import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
@ -7,7 +11,10 @@ import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer'; import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsObjectFormSection } from '@/settings/data-model/components/SettingsObjectFormSection'; import {
SettingsDataModelObjectAboutForm,
settingsDataModelObjectAboutFormSchema,
} from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm';
import { SettingsDataModelObjectSettingsFormCard } from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectSettingsFormCard'; import { SettingsDataModelObjectSettingsFormCard } from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectSettingsFormCard';
import { settingsUpdateObjectInputSchema } from '@/settings/data-model/validation-schemas/settingsUpdateObjectInputSchema'; import { settingsUpdateObjectInputSchema } from '@/settings/data-model/validation-schemas/settingsUpdateObjectInputSchema';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
@ -21,6 +28,12 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
const objectEditFormSchema = settingsDataModelObjectAboutFormSchema;
type SettingsDataModelObjectEditFormValues = z.infer<
typeof objectEditFormSchema
>;
export const SettingsObjectEdit = () => { export const SettingsObjectEdit = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { enqueueSnackBar } = useSnackBar(); const { enqueueSnackBar } = useSnackBar();
@ -33,57 +46,37 @@ export const SettingsObjectEdit = () => {
const activeObjectMetadataItem = const activeObjectMetadataItem =
findActiveObjectMetadataItemBySlug(objectSlug); findActiveObjectMetadataItemBySlug(objectSlug);
const [formValues, setFormValues] = useState< const settingsObjectsPagePath = getSettingsPagePath(SettingsPath.Objects);
Partial<{
icon: string; const formConfig = useForm<SettingsDataModelObjectEditFormValues>({
labelSingular: string; mode: 'onTouched',
labelPlural: string; resolver: zodResolver(objectEditFormSchema),
description: string; });
}>
>({});
useEffect(() => { useEffect(() => {
if (!activeObjectMetadataItem) { if (!activeObjectMetadataItem) navigate(AppPath.NotFound);
navigate(AppPath.NotFound); }, [activeObjectMetadataItem, navigate]);
return;
}
if (!Object.keys(formValues).length) {
setFormValues({
icon: activeObjectMetadataItem.icon ?? undefined,
labelSingular: activeObjectMetadataItem.labelSingular,
labelPlural: activeObjectMetadataItem.labelPlural,
description: activeObjectMetadataItem.description ?? undefined,
});
}
}, [activeObjectMetadataItem, formValues, navigate]);
if (!activeObjectMetadataItem) return null; if (!activeObjectMetadataItem) return null;
const areRequiredFieldsFilled = const { isDirty, isValid } = formConfig.formState;
!!formValues.labelSingular && !!formValues.labelPlural; const canSave = isDirty && isValid;
const hasChanges =
formValues.description !== activeObjectMetadataItem.description ||
formValues.icon !== activeObjectMetadataItem.icon ||
formValues.labelPlural !== activeObjectMetadataItem.labelPlural ||
formValues.labelSingular !== activeObjectMetadataItem.labelSingular;
const canSave = areRequiredFieldsFilled && hasChanges;
const handleSave = async () => { const handleSave = async () => {
const editedObjectMetadataItem = { const formValues = formConfig.getValues();
...activeObjectMetadataItem, const dirtyFieldKeys = Object.keys(
...formValues, formConfig.formState.dirtyFields,
}; ) as (keyof SettingsDataModelObjectEditFormValues)[];
try { try {
await updateOneObjectMetadataItem({ await updateOneObjectMetadataItem({
idToUpdate: activeObjectMetadataItem.id, idToUpdate: activeObjectMetadataItem.id,
updatePayload: settingsUpdateObjectInputSchema.parse(formValues), updatePayload: settingsUpdateObjectInputSchema.parse(
pick(formValues, dirtyFieldKeys),
),
}); });
navigate(`/settings/objects/${getObjectSlug(editedObjectMetadataItem)}`); navigate(`${settingsObjectsPagePath}/${getObjectSlug(formValues)}`);
} catch (error) { } catch (error) {
enqueueSnackBar((error as Error).message, { enqueueSnackBar((error as Error).message, {
variant: 'error', variant: 'error',
@ -96,63 +89,68 @@ export const SettingsObjectEdit = () => {
idToUpdate: activeObjectMetadataItem.id, idToUpdate: activeObjectMetadataItem.id,
updatePayload: { isActive: false }, updatePayload: { isActive: false },
}); });
navigate(getSettingsPagePath(SettingsPath.Objects)); navigate(settingsObjectsPagePath);
}; };
return ( return (
<SubMenuTopBarContainer Icon={IconSettings} title="Settings"> // eslint-disable-next-line react/jsx-props-no-spreading
<SettingsPageContainer> <FormProvider {...formConfig}>
<SettingsHeaderContainer> <SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<Breadcrumb <SettingsPageContainer>
links={[ <SettingsHeaderContainer>
{ children: 'Objects', href: '/settings/objects' }, <Breadcrumb
{ links={[
children: activeObjectMetadataItem.labelPlural, {
href: `/settings/objects/${objectSlug}`, children: 'Objects',
}, href: settingsObjectsPagePath,
{ children: 'Edit' }, },
]} {
/> children: activeObjectMetadataItem.labelPlural,
{activeObjectMetadataItem.isCustom && ( href: `${settingsObjectsPagePath}/${objectSlug}`,
<SaveAndCancelButtons },
isSaveDisabled={!canSave} { children: 'Edit' },
onCancel={() => navigate(`/settings/objects/${objectSlug}`)} ]}
onSave={handleSave}
/> />
)} {activeObjectMetadataItem.isCustom && (
</SettingsHeaderContainer> <SaveAndCancelButtons
<SettingsObjectFormSection isSaveDisabled={!canSave}
disabled={!activeObjectMetadataItem.isCustom} onCancel={() =>
icon={formValues.icon} navigate(`${settingsObjectsPagePath}/${objectSlug}`)
singularName={formValues.labelSingular} }
pluralName={formValues.labelPlural} onSave={handleSave}
description={formValues.description} />
onChange={(values) => )}
setFormValues((previousFormValues) => ({ </SettingsHeaderContainer>
...previousFormValues, <Section>
...values, <H2Title
})) title="About"
} description="Name in both singular (e.g., 'Invoice') and plural (e.g., 'Invoices') forms."
/> />
<Section> <SettingsDataModelObjectAboutForm
<H2Title disabled={!activeObjectMetadataItem.isCustom}
title="Settings" objectMetadataItem={activeObjectMetadataItem}
description="Choose the fields that will identify your records" />
/> </Section>
<SettingsDataModelObjectSettingsFormCard <Section>
objectMetadataItem={activeObjectMetadataItem} <H2Title
/> title="Settings"
</Section> description="Choose the fields that will identify your records"
<Section> />
<H2Title title="Danger zone" description="Disable object" /> <SettingsDataModelObjectSettingsFormCard
<Button objectMetadataItem={activeObjectMetadataItem}
Icon={IconArchive} />
title="Disable" </Section>
size="small" <Section>
onClick={handleDisable} <H2Title title="Danger zone" description="Disable object" />
/> <Button
</Section> Icon={IconArchive}
</SettingsPageContainer> title="Disable"
</SubMenuTopBarContainer> size="small"
onClick={handleDisable}
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</FormProvider>
); );
}; };

View File

@ -0,0 +1,13 @@
import { FormProvider, useForm } from 'react-hook-form';
import { Decorator } from '@storybook/react';
export const FormProviderDecorator: Decorator = (Story) => {
const formConfig = useForm();
return (
// eslint-disable-next-line react/jsx-props-no-spreading
<FormProvider {...formConfig}>
<Story />
</FormProvider>
);
};

View File

@ -15636,6 +15636,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/lodash.pick@npm:^4.3.7":
version: 4.4.9
resolution: "@types/lodash.pick@npm:4.4.9"
dependencies:
"@types/lodash": "npm:*"
checksum: 61ae2fb9fe817a2f398863d10dc57e3cb864ebadefb7f3554d8d0c189448e08a174d6bfdd6e0220988ea084a2d47c3377908cf037e2b3ce552aa79ce0f36bc9f
languageName: node
linkType: hard
"@types/lodash.snakecase@npm:^4.1.7": "@types/lodash.snakecase@npm:^4.1.7":
version: 4.1.9 version: 4.1.9
resolution: "@types/lodash.snakecase@npm:4.1.9" resolution: "@types/lodash.snakecase@npm:4.1.9"
@ -44428,6 +44437,7 @@ __metadata:
"@types/lodash.isobject": "npm:^3.0.7" "@types/lodash.isobject": "npm:^3.0.7"
"@types/lodash.kebabcase": "npm:^4.1.7" "@types/lodash.kebabcase": "npm:^4.1.7"
"@types/lodash.merge": "npm:^4.6.7" "@types/lodash.merge": "npm:^4.6.7"
"@types/lodash.pick": "npm:^4.3.7"
"@types/lodash.snakecase": "npm:^4.1.7" "@types/lodash.snakecase": "npm:^4.1.7"
"@types/lodash.upperfirst": "npm:^4.3.7" "@types/lodash.upperfirst": "npm:^4.3.7"
"@types/luxon": "npm:^3.3.0" "@types/luxon": "npm:^3.3.0"
@ -44525,6 +44535,7 @@ __metadata:
lodash.kebabcase: "npm:^4.1.1" lodash.kebabcase: "npm:^4.1.1"
lodash.merge: "npm:^4.6.2" lodash.merge: "npm:^4.6.2"
lodash.omit: "npm:^4.5.0" lodash.omit: "npm:^4.5.0"
lodash.pick: "npm:^4.4.0"
lodash.snakecase: "npm:^4.1.1" lodash.snakecase: "npm:^4.1.1"
lodash.upperfirst: "npm:^4.3.1" lodash.upperfirst: "npm:^4.3.1"
luxon: "npm:^3.3.0" luxon: "npm:^3.3.0"