Fix/form component

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7124
GitOrigin-RevId: fbaaa9f2e4377ebefccacc399472ead6a64927c6
This commit is contained in:
Nicolas Inchauspe 2022-12-14 16:11:28 +01:00 committed by hasura-bot
parent 9006ddd2d7
commit 1579e334cb
78 changed files with 3129 additions and 3815 deletions

View File

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { Connect } from 'react-redux';
import { Analytics, REDACT_EVERYTHING } from '@/features/Analytics';
import { Button } from '@/new-components/Button';
import { Form, InputField, Checkbox } from '@/new-components/Form';
import { Checkbox, InputField, SimpleForm } from '@/new-components/Form';
import { push } from 'react-router-redux';
import { z } from 'zod';
import Helmet from 'react-helmet';
@ -69,55 +69,53 @@ const Login: React.FC<ConnectInjectedProps> = ({ dispatch, children }) => {
};
return (
<Form
<SimpleForm
schema={validationSchema}
options={{
defaultValues: undefined,
}}
onSubmit={onSubmit}
>
{() => (
<Analytics name="Login" {...REDACT_EVERYTHING}>
<div className="flex flex-col bg-white p-4">
{!!children && <div>{children}</div>}
<div>
<div className="w-full">
<InputField
name="password"
type="password"
size="full"
placeholder="Enter admin-secret"
/>
</div>
</div>
<Analytics name="Login" {...REDACT_EVERYTHING}>
<div className="flex flex-col bg-white p-4">
{!!children && <div>{children}</div>}
<div>
<div className="w-full">
<Button
full
type="submit"
mode="primary"
size="md"
disabled={loading}
>
{getLoginButtonText()}
</Button>
</div>
<div>
<label className="cursor-pointer flex items-center pt-sm">
<Checkbox
name="savePassword"
options={[
{
value: 'checked',
label: 'Remember in this browser',
},
]}
/>
</label>
<InputField
name="password"
type="password"
size="full"
placeholder="Enter admin-secret"
/>
</div>
</div>
</Analytics>
)}
</Form>
<div className="w-full">
<Button
full
type="submit"
mode="primary"
size="md"
disabled={loading}
>
{getLoginButtonText()}
</Button>
</div>
<div>
<label className="cursor-pointer flex items-center pt-sm">
<Checkbox
name="savePassword"
options={[
{
value: 'checked',
label: 'Remember in this browser',
},
]}
/>
</label>
</div>
</div>
</Analytics>
</SimpleForm>
);
};

View File

@ -1,7 +1,7 @@
import KnowMoreLink from '@/components/Common/KnowMoreLink/KnowMoreLink';
import { Badge } from '@/new-components/Badge';
import { Dialog } from '@/new-components/Dialog';
import { Form } from '@/new-components/Form';
import { SimpleForm } from '@/new-components/Form';
import React from 'react';
import z from 'zod';
import { ImportTypesForm } from './ImportTypesForm';
@ -66,7 +66,7 @@ ${values.typeDef}`);
text="Know More"
/>
</p>
<Form
<SimpleForm
options={{
defaultValues: values,
}}
@ -74,8 +74,8 @@ ${values.typeDef}`);
schema={schema}
onSubmit={() => {}}
>
{() => <ImportTypesForm setValues={setValues} />}
</Form>
<ImportTypesForm setValues={setValues} />
</SimpleForm>
</div>
</Dialog>
);

View File

@ -1,5 +1,5 @@
import z from 'zod';
import { Form, InputField } from '@/new-components/Form';
import { InputField, SimpleForm } from '@/new-components/Form';
import React, { useEffect } from 'react';
import { useFormContext } from 'react-hook-form';
import { FaSearch } from 'react-icons/fa';
@ -32,12 +32,12 @@ export const TypeSearchForm: React.FC<TypeSearchFormProps> = ({
setSearch,
}) => {
return (
<Form
<SimpleForm
schema={schema}
onSubmit={() => {}}
className="pr-0 pt-0 pb-0 relative"
>
{() => <SearchInput setSearch={setSearch} />}
</Form>
<SearchInput setSearch={setSearch} />
</SimpleForm>
);
};

View File

@ -1,5 +1,5 @@
import { Dialog } from '@/new-components/Dialog';
import { Form } from '@/new-components/Form';
import { SimpleForm } from '@/new-components/Form';
import { SchemaType } from './types';
import React from 'react';
import { schema, TypeGeneratorForm } from './TypeGeneratorForm';
@ -57,7 +57,7 @@ export const TypeGeneratorModal = (props: TypeGeneratorModalProps) => {
Generate your GraphQL Types from a sample of your request and response
body.
</p>
<Form
<SimpleForm
options={{
defaultValues: values,
}}
@ -65,8 +65,8 @@ export const TypeGeneratorModal = (props: TypeGeneratorModalProps) => {
schema={schema}
onSubmit={() => {}}
>
{() => <TypeGeneratorForm setValues={setValues} />}
</Form>
<TypeGeneratorForm setValues={setValues} />
</SimpleForm>
</div>
</Dialog>
);

View File

@ -4,14 +4,13 @@ import { z } from 'zod';
import { AllowedRESTMethods } from '@/metadata/types';
import { isQueryValid } from '@/components/Services/ApiExplorer/Rest/utils';
import {
Form,
Checkbox,
CodeEditorField,
InputField,
Textarea,
CodeEditorField,
Checkbox,
useConsoleForm,
} from '@/new-components/Form';
import { Button } from '@/new-components/Button';
import { UseFormReturn } from 'react-hook-form';
type RestEndpointFormProps = {
/**
@ -76,72 +75,70 @@ export const RestEndpointForm: React.FC<RestEndpointFormProps> = ({
onSubmit = () => {},
onCancel = () => {},
}) => {
const restEndpointFormRef = React.useRef<UseFormReturn>(null);
setTimeout(() => {
restEndpointFormRef.current?.setFocus('name');
}, 100);
const {
methods: { setFocus },
Form,
} = useConsoleForm({
schema: validationSchema,
options: {
defaultValues: formState,
},
});
React.useEffect(() => {
setFocus('name');
}, []);
return (
<Form
ref={restEndpointFormRef}
schema={validationSchema}
className="p-9"
options={{
defaultValues: formState,
}}
onSubmit={onSubmit}
>
{() => (
<div className="space-y-2 w-full max-w-xl">
<h1 className="text-xl font-semibold mb-sm">
{{ create: 'Create', edit: 'Edit' }[mode]} Endpoint
</h1>
<InputField name="name" label="Name *" placeholder="Name" />
<Textarea
name="comment"
label="Description"
placeholder="Description"
/>
<InputField
name="url"
label="Location *"
placeholder="Location"
description="This is the location of your endpoint (must be unique). Any parameterized variables (eg. http://localhost:8080/api/rest/example/:id will be made available to your request."
prependLabel="http://localhost:8080/api/rest/"
/>
<Checkbox
name="methods"
label="Methods *"
options={[
{ value: 'GET', label: 'GET' },
{ value: 'POST', label: 'POST' },
{ value: 'PUT', label: 'PUT' },
{ value: 'PATCH', label: 'PATCH' },
{ value: 'DELETE', label: 'DELETE' },
]}
orientation="horizontal"
/>
<CodeEditorField
name="request"
label="GraphQL Request *"
tooltip="The request your endpoint will run. All variables will be mapped to REST endpoint variables."
/>
<div className="flex gap-4">
<Button type="button" onClick={onCancel} disabled={loading}>
Cancel
</Button>
<Button
type="submit"
mode="primary"
isLoading={loading}
loadingText={
{ create: 'Creating...', edit: 'Modifying ..' }[mode]
}
>
{{ create: 'Create', edit: 'Modify' }[mode]}
</Button>
</div>
<Form onSubmit={onSubmit} className="p-9">
<div className="space-y-2 w-full max-w-xl">
<h1 className="text-xl font-semibold mb-sm">
{{ create: 'Create', edit: 'Edit' }[mode]} Endpoint
</h1>
<InputField name="name" label="Name *" placeholder="Name" />
<Textarea
name="comment"
label="Description"
placeholder="Description"
/>
<InputField
name="url"
label="Location *"
placeholder="Location"
description="This is the location of your endpoint (must be unique). Any parameterized variables (eg. http://localhost:8080/api/rest/example/:id will be made available to your request."
prependLabel="http://localhost:8080/api/rest/"
/>
<Checkbox
name="methods"
label="Methods *"
options={[
{ value: 'GET', label: 'GET' },
{ value: 'POST', label: 'POST' },
{ value: 'PUT', label: 'PUT' },
{ value: 'PATCH', label: 'PATCH' },
{ value: 'DELETE', label: 'DELETE' },
]}
orientation="horizontal"
/>
<CodeEditorField
name="request"
label="GraphQL Request *"
tooltip="The request your endpoint will run. All variables will be mapped to REST endpoint variables."
/>
<div className="flex gap-4">
<Button type="button" onClick={onCancel} disabled={loading}>
Cancel
</Button>
<Button
type="submit"
mode="primary"
isLoading={loading}
loadingText={{ create: 'Creating...', edit: 'Modifying ..' }[mode]}
>
{{ create: 'Create', edit: 'Modify' }[mode]}
</Button>
</div>
)}
</div>
</Form>
);
};

View File

@ -1,4 +1,5 @@
import { Meta } from '@storybook/addon-docs';
import { action } from '@storybook/addon-actions';
<Meta title="Dev/Testing/5. Components-Driven Development - The WorkfLow" />
@ -154,7 +155,7 @@ Let's fix this:
import React from 'react';
import { Story, Meta } from '@storybook/react';
import { z } from 'zod';
import { Form } from '@/new-components/Form';
import { SimpleForm } from '@/new-components/Form';
import { UserInfo } from './UserInfo';
@ -163,9 +164,9 @@ export default {
component: UserInfo,
decorators: [
(S: React.FC) => (
<Form schema={z.any()} onSubmit={() => {}}>
{() => <S />}
</Form>
<SimpleForm schema={z.any()} onSubmit={action('onSubmit')}>
<S />
</SimpleForm>
),
],
} as Meta;
@ -272,7 +273,7 @@ export const CompanyInfo = () => {
import React from 'react';
import { Story, Meta } from '@storybook/react';
import { z } from 'zod';
import { Form } from '@/new-components/Form';
import { SimpleForm } from '@/new-components/Form';
import { CompanyInfo } from './CompanyInfo';
@ -281,9 +282,9 @@ export default {
component: CompanyInfo,
decorators: [
(S: React.FC) => (
<Form schema={z.any()} onSubmit={() => {}}>
{() => <S />}
</Form>
<SimpleForm schema={z.any()} onSubmit={action('onSubmit')}>
<S />
</SimpleForm>
),
],
} as Meta;
@ -305,7 +306,7 @@ The component will be responsible for its entire data lifecycle and we will use
import React from 'react';
import { z } from 'zod';
import { Form } from '@/new-components/Form';
import { SimpleForm } from '@/new-components/Form';
import { CompanyInfo } from './CompanyInfo';
import { UserInfo } from './UserInfo';
@ -330,19 +331,17 @@ export const ExampleForm = () => {
};
return (
<Form schema={schema} onSubmit={handleSubmit}>
{() => (
<>
<p className="font-bold">User Info</p>
<UserInfo />
<p className="font-bold">Company Info</p>
<CompanyInfo />
<Button type="submit" mode="primary">
Submit
</Button>
</>
)}
</Form>
<SimpleForm schema={schema} onSubmit={handleSubmit}>
<>
<p className="font-bold">User Info</p>
<UserInfo />
<p className="font-bold">Company Info</p>
<CompanyInfo />
<Button type="submit" mode="primary">
Submit
</Button>
</>
</SimpleForm>
);
};
```
@ -399,7 +398,7 @@ import { z } from 'zod';
import { Api } from '@/hooks/apiUtils';
import { Button } from '@/new-components/Button';
import { Form } from '@/new-components/Form';
import { SimpleForm } from '@/new-components/Form';
import { CompanyInfo } from './CompanyInfo';
import { UserInfo } from './UserInfo';
@ -439,19 +438,17 @@ export const ExampleForm = () => {
};
return (
<Form schema={schema} onSubmit={handleSubmit}>
{() => (
<>
<p className="font-bold">User Info</p>
<UserInfo />
<p className="font-bold">Company Info</p>
<CompanyInfo />
<Button type="submit" mode="primary">
Submit
</Button>
</>
)}
</Form>
<SimpleForm schema={schema} onSubmit={handleSubmit}>
<>
<p className="font-bold">User Info</p>
<UserInfo />
<p className="font-bold">Company Info</p>
<CompanyInfo />
<Button type="submit" mode="primary">
Submit
</Button>
</>
</SimpleForm>
);
};
```

View File

@ -1,6 +1,6 @@
import React from 'react';
import { Dialog } from '@/new-components/Dialog';
import { Form } from '@/new-components/Form';
import { SimpleForm } from '@/new-components/Form';
import { formSchema, OasGeneratorForm } from './OASGeneratorForm';
import { GeneratedAction } from './types';
@ -41,9 +41,13 @@ export const OasGeneratorModal = (props: OasGeneratorModalProps) => {
<p className="text-muted mb-6">
Generate your action from a Open API spec.
</p>
<Form className="pl-0 pr-0" schema={formSchema} onSubmit={() => {}}>
{() => <OasGeneratorForm setValues={setValues} />}
</Form>
<SimpleForm
className="pl-0 pr-0"
schema={formSchema}
onSubmit={() => {}}
>
<OasGeneratorForm setValues={setValues} />
</SimpleForm>
</div>
</Dialog>
);

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Form, InputField } from '@/new-components/Form';
import { InputField, SimpleForm } from '@/new-components/Form';
import { Button } from '@/new-components/Button';
import { schema, Schema } from './schema';
import { useDefaultValues } from './hooks';
@ -31,50 +31,48 @@ const OneOffScheduledEventForm = (props: Props) => {
};
return (
<Form
<SimpleForm
schema={schema}
onSubmit={onSubmit}
options={{ defaultValues }}
className="overflow-y-hidden p-4"
>
{() => (
<>
<div className="mb-md">
<InputField
name="comment"
label="Comment / Description"
placeholder="Comment / Description..."
tooltip="A statement to help describe the scheduled event in brief"
/>
</div>
<div className="mb-md">
<ScheduledTime />
</div>
<div className="mb-md">
<InputField
name="webhook"
label="Webhook URL"
placeholder="https://httpbin.com/post"
tooltip="The HTTP URL that should be triggered."
/>
</div>
<div className="mb-md">
<ScheduleEventPayloadInput />
</div>
<div className="mb-md">
<AdvancedSettings />
</div>
<div className="mb-md">
<RetryConfiguration />
</div>
<div className="flex items-center mb-lg">
<Button type="submit" mode="primary" isLoading={mutation.isLoading}>
Create scheduled event
</Button>
</div>
</>
)}
</Form>
<>
<div className="mb-md">
<InputField
name="comment"
label="Comment / Description"
placeholder="Comment / Description..."
tooltip="A statement to help describe the scheduled event in brief"
/>
</div>
<div className="mb-md">
<ScheduledTime />
</div>
<div className="mb-md">
<InputField
name="webhook"
label="Webhook URL"
placeholder="https://httpbin.com/post"
tooltip="The HTTP URL that should be triggered."
/>
</div>
<div className="mb-md">
<ScheduleEventPayloadInput />
</div>
<div className="mb-md">
<AdvancedSettings />
</div>
<div className="mb-md">
<RetryConfiguration />
</div>
<div className="flex items-center mb-lg">
<Button type="submit" mode="primary" isLoading={mutation.isLoading}>
Create scheduled event
</Button>
</div>
</>
</SimpleForm>
);
};

View File

@ -1,44 +1,39 @@
import z from 'zod';
import { Form, InputField } from '@/new-components/Form';
import { InputField, useConsoleForm } from '@/new-components/Form';
import React, { useEffect } from 'react';
import { useFormContext } from 'react-hook-form';
import { FaSearch } from 'react-icons/fa';
const schema = z.object({
search: z.string(),
});
interface AllowListSidebarSearchFormProps {
setSearch: (search: string) => void;
}
const SearchInput: React.FC<AllowListSidebarSearchFormProps> = ({
setSearch,
}) => {
const { watch } = useFormContext();
const search = watch('search');
useEffect(() => {
setSearch(search);
}, [search]);
return (
<InputField
id="search"
placeholder="Search Collections..."
icon={<FaSearch />}
name="search"
/>
);
};
export const AllowListSidebarSearchForm: React.FC<AllowListSidebarSearchFormProps> =
({ setSearch }) => {
const schema = z.object({
search: z.string(),
});
const {
methods: { watch },
Form,
} = useConsoleForm({
schema,
});
const search = watch('search');
useEffect(() => {
setSearch(search);
}, [search]);
return (
<Form
schema={schema}
onSubmit={() => {}}
className="pl-0 pr-0 !p-0 bg-transparent"
>
{() => <SearchInput setSearch={setSearch} />}
<Form onSubmit={() => {}} className="pl-0 pr-0 !p-0 bg-transparent">
<InputField
id="search"
placeholder="Search Collections..."
icon={<FaSearch />}
name="search"
/>
</Form>
);
};

View File

@ -1,7 +1,7 @@
import React from 'react';
import z from 'zod';
import { Dialog } from '@/new-components/Dialog';
import { Form, InputField } from '@/new-components/Form';
import { InputField, useConsoleForm } from '@/new-components/Form';
import { Analytics, REDACT_EVERYTHING } from '@/features/Analytics';
import { useFireNotification } from '@/new-components/Notifications';
import { useCreateQueryCollection } from '../../../QueryCollections/hooks/useCreateQueryCollection';
@ -20,61 +20,64 @@ export const QueryCollectionCreateDialog: React.FC<QueryCollectionCreateDialogPr
const { createQueryCollection, isLoading } = useCreateQueryCollection();
const { fireNotification } = useFireNotification();
const {
methods: { trigger, watch, setError },
Form,
} = useConsoleForm({
schema,
});
const name = watch('name');
return (
<Form schema={schema} onSubmit={() => {}} className="p-4">
{({ watch, setError, trigger }) => {
const name = watch('name');
return (
<Dialog hasBackdrop title="Create Collection" onClose={onClose}>
<>
<Analytics name="AllowList" {...REDACT_EVERYTHING}>
<div className="p-4">
<InputField
id="name"
name="name"
label="New Collection Name"
placeholder="New Collection Name..."
/>
</div>
</Analytics>
<Dialog.Footer
callToDeny="Cancel"
callToAction="Create Collection"
onClose={onClose}
onSubmit={async () => {
if (await trigger()) {
// TODO: remove as when proper form types will be available
createQueryCollection(name as string, {
addToAllowList: true,
onSuccess: () => {
onClose();
onCreate(name as string);
fireNotification({
type: 'success',
title: 'Collection created',
message: `Collection ${name} was created successfully`,
});
},
onError: error => {
setError('name', {
type: 'manual',
message: (error as Error).message,
});
fireNotification({
type: 'error',
title: 'Collection creation failed',
message: (error as Error).message,
});
},
});
}
}}
isLoading={isLoading}
<Form onSubmit={() => {}}>
<Dialog hasBackdrop title="Create Collection" onClose={onClose}>
<>
<Analytics name="AllowList" {...REDACT_EVERYTHING}>
<div className="p-4">
<InputField
id="name"
name="name"
label="New Collection Name"
placeholder="New Collection Name..."
/>
</>
</Dialog>
);
}}
</div>
</Analytics>
<Dialog.Footer
callToDeny="Cancel"
callToAction="Create Collection"
onClose={onClose}
onSubmit={async () => {
if (await trigger()) {
// TODO: remove as when proper form types will be available
createQueryCollection(name as string, {
addToAllowList: true,
onSuccess: () => {
onClose();
onCreate(name as string);
fireNotification({
type: 'success',
title: 'Collection created',
message: `Collection ${name} was created successfully`,
});
},
onError: error => {
setError('name', {
type: 'manual',
message: (error as Error).message,
});
fireNotification({
type: 'error',
title: 'Collection creation failed',
message: (error as Error).message,
});
},
});
}
}}
isLoading={isLoading}
/>
</>
</Dialog>
</Form>
);
};

View File

@ -1,7 +1,7 @@
import { OrderBy, TableColumn } from '@/features/DataSource';
import { Table } from '@/features/hasura-metadata-types';
import { Dialog } from '@/new-components/Dialog';
import { UpdatedForm } from '@/new-components/Form';
import { useConsoleForm } from '@/new-components/Form';
import React from 'react';
import { UseFormTrigger } from 'react-hook-form';
import { z } from 'zod';
@ -80,6 +80,19 @@ export const QueryDialog = ({
}: QueryDialogProps) => {
const { data, isLoading } = useTableColumns({ table, dataSourceName });
const {
methods: { trigger, watch },
Form,
} = useConsoleForm({
schema,
options: {
defaultValues: {
sorts: existingSorts,
filters: existingFilters as any,
},
},
});
if (isLoading) return <>Loading...</>;
if (!data) return <>Data not found!</>;
@ -88,10 +101,10 @@ export const QueryDialog = ({
const handleSubmitQuery = async (
filters: Schema['filters'],
trigger: UseFormTrigger<Schema>,
triggerValidation: UseFormTrigger<Schema>,
sorts: Schema['sorts']
) => {
if (await trigger()) {
if (await triggerValidation()) {
onSubmit({
filters: (filters ?? []).map(f => transformFilterValues(columns, f)),
sorts: sorts ?? [],
@ -99,54 +112,40 @@ export const QueryDialog = ({
}
};
const filters = watch('filters');
const sorts = watch('sorts');
const onSubmitHandler = () => handleSubmitQuery(filters, trigger, sorts);
return (
<div className="m-4">
<Dialog hasBackdrop title="Query Data" onClose={onClose}>
<>
<UpdatedForm
schema={schema}
options={{
defaultValues: {
sorts: existingSorts,
filters: existingFilters as any,
},
}}
onSubmit={() => {}}
>
{({ trigger, watch }) => {
const filters = watch('filters');
const sorts = watch('sorts');
<Form onSubmit={() => {}}>
<>
<div className="p-4">
<FilterRows
name="filters"
columns={columns}
operators={supportedOperators}
onRemove={() => onSubmitHandler()}
/>
const onSubmitHandler = () =>
handleSubmitQuery(filters, trigger, sorts);
<hr className="my-4" />
return (
<>
<div className="p-4">
<FilterRows
name="filters"
columns={columns}
operators={supportedOperators}
onRemove={() => onSubmitHandler()}
/>
<hr className="my-4" />
<SortRows
name="sorts"
columns={columns}
onRemove={() => onSubmitHandler()}
/>
</div>
<Dialog.Footer
callToAction="Run Query"
onClose={onClose}
onSubmit={() => onSubmitHandler()}
/>
</>
);
}}
</UpdatedForm>
<SortRows
name="sorts"
columns={columns}
onRemove={() => onSubmitHandler()}
/>
</div>
<Dialog.Footer
callToAction="Run Query"
onClose={onClose}
onSubmit={() => onSubmitHandler()}
/>
</>
</Form>
</>
</Dialog>
</div>

View File

@ -1,6 +1,6 @@
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { UpdatedForm } from '@/new-components/Form';
import { useConsoleForm } from '@/new-components/Form';
import { userEvent, within } from '@storybook/testing-library';
import { expect } from '@storybook/jest';
import { action } from '@storybook/addon-actions';
@ -59,48 +59,45 @@ const operators = [
];
export const Primary: ComponentStory<typeof FilterRows> = () => {
return (
<UpdatedForm
schema={z.object({
filters: z
.array(
z.object({
column: z.string(),
operator: z.string(),
value: z.string(),
})
)
.optional(),
})}
options={{
defaultValues: {
filters: [
{ column: 'FirstName', operator: '_eq', value: 'John Doe' },
],
},
}}
onSubmit={data => {
console.log(data);
}}
>
{({ watch }) => {
const formValues = watch('filters');
return (
<>
<FilterRows
columns={columns}
operators={operators}
name="filters"
onRemove={action('onRemove')}
/>
const {
methods: { watch },
Form,
} = useConsoleForm({
schema: z.object({
filters: z
.array(
z.object({
column: z.string(),
operator: z.string(),
value: z.string(),
})
)
.optional(),
}),
options: {
defaultValues: {
filters: [{ column: 'FirstName', operator: '_eq', value: 'John Doe' }],
},
},
});
<div className="py-4" data-testid="output">
Output: {JSON.stringify(formValues)}
</div>
</>
);
}}
</UpdatedForm>
const formValues = watch('filters');
return (
<Form onSubmit={action('onSubmit')}>
<>
<FilterRows
columns={columns}
operators={operators}
name="filters"
onRemove={action('onRemove')}
/>
<div className="py-4" data-testid="output">
Output: {JSON.stringify(formValues)}
</div>
</>
</Form>
);
};

View File

@ -1,11 +1,11 @@
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { UpdatedForm } from '@/new-components/Form';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { z } from 'zod';
import { FormDecorator } from '@/storybook/decorators/react-hook-form';
import { userEvent, within } from '@storybook/testing-library';
import { expect } from '@storybook/jest';
import { action } from '@storybook/addon-actions';
import { useConsoleForm } from './../../../../../new-components/Form';
import { SortRows } from './SortRows';
export default {
@ -42,44 +42,43 @@ const columns = [
];
export const Primary: ComponentStory<typeof SortRows> = () => {
return (
<UpdatedForm
schema={z.object({
sorts: z
.array(
z.object({
column: z.string(),
type: z.literal('asc').or(z.literal('desc')),
})
)
.optional(),
})}
options={{
defaultValues: {
sorts: [{ column: 'FirstName', type: 'asc' }],
},
}}
onSubmit={data => {
console.log(data);
}}
>
{({ watch }) => {
const formValues = watch('sorts');
return (
<div className="w-1/2">
<SortRows
columns={columns}
name="sorts"
onRemove={action('onRemove')}
/>
const {
methods: { watch },
Form,
} = useConsoleForm({
schema: z.object({
sorts: z
.array(
z.object({
column: z.string(),
type: z.literal('asc').or(z.literal('desc')),
})
)
.optional(),
}),
options: {
defaultValues: {
sorts: [{ column: 'FirstName', type: 'asc' }],
},
},
});
<div className="py-4" data-testid="output">
Output: {JSON.stringify(formValues)}
</div>
</div>
);
}}
</UpdatedForm>
const formValues = watch('sorts');
return (
<Form onSubmit={action('onSubmit')}>
<div className="w-1/2">
<SortRows
columns={columns}
name="sorts"
onRemove={action('onRemove')}
/>
<div className="py-4" data-testid="output">
Output: {JSON.stringify(formValues)}
</div>
</div>
</Form>
);
};

View File

@ -1,6 +1,6 @@
import { CustomizationForm } from '@/features/ConnectDB';
import { Button } from '@/new-components/Button';
import { Forms, InputField } from '@/new-components/Form';
import { useConsoleForm, InputField } from '@/new-components/Form';
import { IndicatorCard } from '@/new-components/IndicatorCard';
import React from 'react';
import { Configuration } from './components/Configuration';
@ -26,6 +26,14 @@ const CreateConnection = ({ name, driver, onDriverChange }: Props) => {
const { submit, isLoading: submitIsLoading } = useSubmit();
const {
methods: { formState },
Form,
} = useConsoleForm({
schema,
options: { defaultValues },
});
if (isError) {
return (
<IndicatorCard status="negative">
@ -51,47 +59,39 @@ const CreateConnection = ({ name, driver, onDriverChange }: Props) => {
}
return (
<Forms.New
<Form
key={`${defaultValues.name}-${defaultValues.driver}` || 'new-connection'}
schema={schema}
onSubmit={submit}
options={{
defaultValues,
}}
className="pl-sm"
>
{options => {
return (
<div>
<InputField type="text" name="name" label="Database Display Name" />
<div>
<InputField type="text" name="name" label="Database Display Name" />
<Driver onDriverChange={onDriverChange} />
<Driver onDriverChange={onDriverChange} />
<div className="max-w-xl">
<Configuration name="configuration" />
</div>
<div className="my-4">
<CustomizationForm />
</div>
<Button
type="submit"
className="mt-4"
mode="primary"
isLoading={submitIsLoading}
>
Connect Database
</Button>
{!!Object(options.formState.errors)?.keys?.length && (
<div className="mt-6 max-w-xl">
<IndicatorCard status="negative">
Error submitting form, see error messages above
</IndicatorCard>
</div>
)}
<div className="max-w-xl">
<Configuration name="configuration" />
</div>
<div className="my-4">
<CustomizationForm />
</div>
<Button
type="submit"
className="mt-4"
mode="primary"
isLoading={submitIsLoading}
>
Connect Database
</Button>
{!!Object(formState.errors)?.keys?.length && (
<div className="mt-6 max-w-xl">
<IndicatorCard status="negative">
Error submitting form, see error messages above
</IndicatorCard>
</div>
);
}}
</Forms.New>
)}
</div>
</Form>
);
};

View File

@ -1,9 +1,10 @@
import { CustomizationForm } from '@/features/ConnectDB';
import { Button } from '@/new-components/Button';
import { Form, InputField, Select } from '@/new-components/Form';
import { InputField, Select, useConsoleForm } from '@/new-components/Form';
import { IndicatorCard } from '@/new-components/IndicatorCard';
import React from 'react';
import { useQuery } from 'react-query';
import { z } from 'zod';
import { useTableDefinition } from '../Data';
import { DataSource, exportMetadata } from '../DataSource';
import { useHttpClient } from '../Network';
@ -47,67 +48,68 @@ const useEditDataSourceConnectionInfo = () => {
export const EditConnection = () => {
const { data, isLoading } = useEditDataSourceConnectionInfo();
const {
schema = z.any(),
name,
driver,
configuration,
customization,
} = data || {};
const { submit, isLoading: submitIsLoading } = useEditDataSourceConnection();
const {
methods: { formState },
Form,
} = useConsoleForm({
schema,
options: {
defaultValues: {
name,
driver,
configuration: (configuration as any)?.value,
replace_configuration: true,
customization,
},
},
});
if (isLoading) return <>Loading...</>;
if (!data) return <>Error</>;
const { schema, name, driver, configuration, customization } = data;
if (!schema) return <>Could not find schema</>;
return (
<Form
schema={schema}
onSubmit={values => {
submit(values);
}}
options={{
defaultValues: {
name,
driver,
configuration: (configuration as any).value,
replace_configuration: true,
customization,
},
}}
className="p-0 pl-sm"
>
{options => {
return (
<div className="max-w-5xl">
<InputField type="text" name="name" label="Database Display Name" />
<Form onSubmit={submit} className="p-0 pl-sm">
<div className="max-w-5xl">
<InputField type="text" name="name" label="Database Display Name" />
<Select
options={[{ label: driver, value: driver }]}
name="driver"
label="Data Source Driver"
disabled
/>
<Select
options={[{ label: driver || '', value: driver }]}
name="driver"
label="Data Source Driver"
disabled
/>
<div className="max-w-xl">
<Configuration name="configuration" />
</div>
<div className="mt-4">
<CustomizationForm />
</div>
<div className="mt-4">
<Button type="submit" mode="primary" isLoading={submitIsLoading}>
Edit Connection
</Button>
</div>
<div className="max-w-xl">
<Configuration name="configuration" />
</div>
<div className="mt-4">
<CustomizationForm />
</div>
<div className="mt-4">
<Button type="submit" mode="primary" isLoading={submitIsLoading}>
Edit Connection
</Button>
</div>
{!!Object(options.formState.errors)?.keys?.length && (
<div className="mt-6 max-w-xl">
<IndicatorCard status="negative">
Error submitting form, see error messages above
</IndicatorCard>
</div>
)}
{!!Object(formState.errors)?.keys?.length && (
<div className="mt-6 max-w-xl">
<IndicatorCard status="negative">
Error submitting form, see error messages above
</IndicatorCard>
</div>
);
}}
)}
</div>
</Form>
);
};

View File

@ -1,11 +1,12 @@
import { CustomizationForm } from '@/features/ConnectDB';
import { Forms } from '@/new-components/Form';
import { SimpleForm } from '@/new-components/Form';
import { expect } from '@storybook/jest';
import { ComponentStory, Meta } from '@storybook/react';
import { userEvent, waitFor, within } from '@storybook/testing-library';
import { screen } from '@testing-library/dom';
import React from 'react';
import { z } from 'zod';
import { action } from '@storybook/addon-actions';
const schema = z.object({
customization: z
@ -29,14 +30,9 @@ export default {
decorators: [
s => {
return (
<Forms.New
schema={schema}
onSubmit={d => {
console.log(d);
}}
>
<SimpleForm schema={schema} onSubmit={action('onSubmit')}>
{s}
</Forms.New>
</SimpleForm>
);
},
],

View File

@ -1,4 +1,5 @@
import { useQueries } from 'react-query';
import { z } from 'zod';
import { useHttpClient } from '@/features/Network';
import { DataSource } from '@/features/DataSource';
import { useDefaultValues } from './useDefaultValues';
@ -39,7 +40,7 @@ export const useLoadSchema = ({ name, driver }: Args) => {
const [schemaResult, driversResult] = results;
const schema = schemaResult.data;
const schema = schemaResult.data || z.any();
const drivers = driversResult.data;
const error = results.some(result => result.error) || defaultValuesError;

View File

@ -1,12 +1,12 @@
import React from 'react';
import { Form, InputField } from '@/new-components/Form';
import { InputField, SimpleForm } from '@/new-components/Form';
import { Button } from '@/new-components/Button';
import { schema, Schema } from './schema';
import {
CronScheduleSelector,
CronPayloadInput,
IncludeInMetadataSwitch,
AdvancedSettings,
CronPayloadInput,
CronScheduleSelector,
IncludeInMetadataSwitch,
RetryConfiguration,
} from './components';
import { getCronTriggerCreateQuery, getCronTriggerUpdateQuery } from './utils';
@ -54,63 +54,60 @@ const CronTriggersForm = (props: Props) => {
return <div>Something went wrong while loading cron trigger data</div>;
}
// TODO: type casting defaultValues as any as we need to fix the <Form /> component to accept nullable fields
return (
<Form
<SimpleForm
schema={schema}
onSubmit={onSubmit}
options={{ defaultValues: defaultValues as any }}
options={{ defaultValues }}
className="overflow-y-hidden p-4"
>
{() => (
<>
<div className="mb-md">
<InputField
name="name"
label="Name"
placeholder="Name..."
tooltip="Give this cron trigger a friendly name"
/>
</div>
<div className="mb-md">
<InputField
name="comment"
label="Comment / Description"
placeholder="Comment / Description..."
tooltip="A statement to help describe the cron trigger in brief"
/>
</div>
<div className="mb-md">
<InputField
name="webhook"
label="Webhook URL"
placeholder="https://httpbin.com/post"
tooltip="The HTTP URL that should be triggered. You can also provide the URL from environment variables, e.g. {{MY_WEBHOOK_URL}}"
/>
</div>
<div className="mb-md">
<CronScheduleSelector />
</div>
<div className="mb-md">
<CronPayloadInput />
</div>
<div className="mb-md">
<IncludeInMetadataSwitch />
</div>
<div className="mb-md">
<AdvancedSettings />
</div>
<div className="mb-md">
<RetryConfiguration />
</div>
<div className="flex items-center mb-lg">
<Button type="submit" mode="primary" isLoading={mutation.isLoading}>
{cronTriggerName ? 'Update Cron Trigger' : 'Add Cron Trigger'}
</Button>
</div>
</>
)}
</Form>
<>
<div className="mb-md">
<InputField
name="name"
label="Name"
placeholder="Name..."
tooltip="Give this cron trigger a friendly name"
/>
</div>
<div className="mb-md">
<InputField
name="comment"
label="Comment / Description"
placeholder="Comment / Description..."
tooltip="A statement to help describe the cron trigger in brief"
/>
</div>
<div className="mb-md">
<InputField
name="webhook"
label="Webhook URL"
placeholder="https://httpbin.com/post"
tooltip="The HTTP URL that should be triggered. You can also provide the URL from environment variables, e.g. {{MY_WEBHOOK_URL}}"
/>
</div>
<div className="mb-md">
<CronScheduleSelector />
</div>
<div className="mb-md">
<CronPayloadInput />
</div>
<div className="mb-md">
<IncludeInMetadataSwitch />
</div>
<div className="mb-md">
<AdvancedSettings />
</div>
<div className="mb-md">
<RetryConfiguration />
</div>
<div className="flex items-center mb-lg">
<Button type="submit" mode="primary" isLoading={mutation.isLoading}>
{cronTriggerName ? 'Update Cron Trigger' : 'Add Cron Trigger'}
</Button>
</div>
</>
</SimpleForm>
);
};

View File

@ -1,7 +1,7 @@
import { MetadataSelectors, useMetadata } from '@/features/hasura-metadata-api';
import { Button } from '@/new-components/Button';
import { Dialog } from '@/new-components/Dialog';
import { InputField, UpdatedForm } from '@/new-components/Form';
import { InputField, SimpleForm } from '@/new-components/Form';
import { sanitizeGraphQLFieldNames } from '@/utils';
import { SanitizeTips } from '@/utils/sanitizeGraphQLFieldNames';
import React from 'react';
@ -26,7 +26,7 @@ export const EditTableColumnDialog = (props: EditTableColumnDialogProps) => {
useUpdateTableConfiguration(dataSourceName, table);
return (
<UpdatedForm
<SimpleForm
schema={schema}
onSubmit={(data: Schema) => {
updateTableConfiguration({
@ -49,39 +49,37 @@ export const EditTableColumnDialog = (props: EditTableColumnDialogProps) => {
},
}}
>
{() => (
<Dialog
size="md"
titleTooltip={`Edit ${column.name} column settings`}
title={`[${column.name}]`}
hasBackdrop
onClose={onClose}
footer={
<div className="bg-white p-2 justify-end border flex">
<Button type="submit" isLoading={isSaveInProgress}>
Submit
</Button>
</div>
}
>
<div className="m-4">
<SanitizeTips />
<InputField
label="Custom GraphQL Field Name"
name="custom_name"
placeholder="Enter GraphQL Field Name"
tooltip="Add a custom GQL field name for table column"
inputTransform={val => sanitizeGraphQLFieldNames(val)}
/>
<InputField
tooltip="Add a comment for your table column"
label="Comment"
name="comment"
placeholder="Add a comment"
/>
<Dialog
size="md"
titleTooltip={`Edit ${column.name} column settings`}
title={`[${column.name}]`}
hasBackdrop
onClose={onClose}
footer={
<div className="bg-white p-2 justify-end border flex">
<Button type="submit" isLoading={isSaveInProgress}>
Submit
</Button>
</div>
</Dialog>
)}
</UpdatedForm>
}
>
<div className="m-4">
<SanitizeTips />
<InputField
label="Custom GraphQL Field Name"
name="custom_name"
placeholder="Enter GraphQL Field Name"
tooltip="Add a custom GQL field name for table column"
inputTransform={val => sanitizeGraphQLFieldNames(val)}
/>
<InputField
tooltip="Add a comment for your table column"
label="Comment"
name="comment"
placeholder="Add a comment"
/>
</div>
</Dialog>
</SimpleForm>
);
};

View File

@ -1,5 +1,5 @@
import { Button } from '@/new-components/Button';
import { Form } from '@/new-components/Form';
import { SimpleForm } from '@/new-components/Form';
import { userEvent, within } from '@storybook/testing-library';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import { ComponentMeta, Story } from '@storybook/react';
@ -7,6 +7,7 @@ import React from 'react';
import { expect } from '@storybook/jest';
import { Controller, useFormContext } from 'react-hook-form';
import { z } from 'zod';
import { action } from '@storybook/addon-actions';
import { DatabaseSelector } from '@/features/Data';
import { handlers } from './mocks/handlers.mock';
@ -23,7 +24,7 @@ export const Playground: Story = () => {
return (
<DatabaseSelector
value={{ database: 'chinook', schema: 'public', table: 'Album' }}
onChange={v => console.log(v)}
onChange={action('onChange')}
name="source"
className="border-l-4 border-l-green-600"
labels={{
@ -40,7 +41,7 @@ export const WithDisabledInputs: Story = () => {
return (
<DatabaseSelector
value={{ database: 'chinook', schema: 'public', table: 'Album' }}
onChange={v => console.log(v)}
onChange={action('onChange')}
name="source"
className="border-l-4 border-l-green-600"
disabledKeys={['database', 'schema', 'table']}
@ -52,7 +53,7 @@ export const BqWithDisabledInputs: Story = () => {
return (
<DatabaseSelector
value={{ database: 'bigquery_test', dataset: 'sensei', table: 'table1' }}
onChange={v => console.log(v)}
onChange={action('onChange')}
name="source"
className="border-l-4 border-l-green-600"
disabledKeys={['database', 'schema', 'table']}
@ -64,7 +65,7 @@ export const WithHiddenInputs: Story = () => {
return (
<DatabaseSelector
value={{ database: 'bigquery_test', dataset: 'sensei', table: '' }}
onChange={v => console.log(v)}
onChange={action('onChange')}
name="source"
className="border-l-4 border-l-green-600"
hiddenKeys={['database']}
@ -107,12 +108,8 @@ const FormElements = () => {
};
export const WithReactFormHookNested: Story = () => {
const submit = (values: Record<string, unknown>) => {
console.log(JSON.stringify(values));
};
return (
<Form
<SimpleForm
options={{
defaultValues: {
destination: {
@ -122,21 +119,15 @@ export const WithReactFormHookNested: Story = () => {
},
},
}}
onSubmit={submit}
onSubmit={action('onSubmit')}
schema={schema}
className="p-4"
>
{() => {
return (
<>
<FormElements />
<Button type="submit" data-testid="submit">
Submit
</Button>
</>
);
}}
</Form>
<FormElements />
<Button type="submit" data-testid="submit">
Submit
</Button>
</SimpleForm>
);
};

View File

@ -6,7 +6,7 @@ import {
} from '@/features/MetadataAPI';
import { useFireNotification } from '@/new-components/Notifications';
import { DataTarget } from '@/features/Datasources';
import { Form } from '@/new-components/Form';
import { useConsoleForm } from '@/new-components/Form';
import { Button } from '@/new-components/Button';
import { IndicatorCard } from '@/new-components/IndicatorCard';
import { getMetadataQuery, MetadataQueryType } from '@/metadata/queryUtils';
@ -50,6 +50,16 @@ export const LocalRelationshipWidget = ({
const { data: defaultValues, isLoading, isError } = useValues;
const {
methods: { formState },
Form,
} = useConsoleForm({
schema,
options: {
defaultValues,
},
});
const { fireNotification } = useFireNotification();
const mutation = useMetadataMigration({
onSuccess: () => {
@ -184,37 +194,30 @@ export const LocalRelationshipWidget = ({
}
return (
<Form
schema={schema}
onSubmit={submit}
options={{ defaultValues }}
className="p-4"
>
{options => (
<>
<div>
<FormElements
existingRelationshipName={existingRelationshipName || ''}
/>
<Form onSubmit={submit} className="p-4">
<>
<div>
<FormElements
existingRelationshipName={existingRelationshipName || ''}
/>
<Button
mode="primary"
type="submit"
isLoading={mutation.isLoading}
loadingText="Saving relationship"
data-test="add-local-db-relationship"
>
Save Relationship
</Button>
</div>
<Button
mode="primary"
type="submit"
isLoading={mutation.isLoading}
loadingText="Saving relationship"
data-test="add-local-db-relationship"
>
Save Relationship
</Button>
</div>
{!!Object.keys(options.formState.errors).length && (
<IndicatorCard status="negative">
Error saving relationship
</IndicatorCard>
)}
</>
)}
{!!Object.keys(formState.errors).length && (
<IndicatorCard status="negative">
Error saving relationship
</IndicatorCard>
)}
</>
</Form>
);
};

View File

@ -1,7 +1,7 @@
import React from 'react';
import { Story, Meta } from '@storybook/react';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import { within, userEvent } from '@storybook/testing-library';
import { within, userEvent, waitFor } from '@storybook/testing-library';
import { expect } from '@storybook/jest';
import {
RemoteDBRelationshipWidget,
@ -37,20 +37,23 @@ WithExistingRelationship.args = {
existingRelationshipName: 'AlbumToResident',
};
let callbackResponse = {};
let callbackResponsePrimaryWithTest = {};
export const PrimaryWithTest: Story<RemoteDBRelationshipWidgetProps> = args => (
<RemoteDBRelationshipWidget
{...args}
onComplete={d => {
callbackResponse = d;
callbackResponsePrimaryWithTest = d;
}}
/>
);
PrimaryWithTest.args = { ...Primary.args };
PrimaryWithTest.parameters = {
chromatic: { disableSnapshot: true },
};
PrimaryWithTest.play = async ({ canvasElement }) => {
const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
callbackResponsePrimaryWithTest = {};
const canvas = within(canvasElement);
const submitButton = await canvas.findByText('Save Relationship');
@ -83,20 +86,28 @@ PrimaryWithTest.play = async ({ canvasElement }) => {
userEvent.selectOptions(schemaLabel, 'public');
userEvent.selectOptions(tableLabel, 'resident');
userEvent.click(submitButton);
await waitFor(() => {
expect(canvas.queryByText('Saving relationship')).toBeInTheDocument();
});
await delay(1000);
expect(JSON.stringify(callbackResponse)).toContain('Success');
expect(JSON.stringify(callbackResponse)).toContain(
'Relationship saved successfully'
);
await waitFor(() => {
expect(JSON.stringify(callbackResponsePrimaryWithTest)).toContain(
'Success'
);
expect(JSON.stringify(callbackResponsePrimaryWithTest)).toContain(
'Relationship saved successfully'
);
});
};
let callbackResponseExistingRelationshipWithTest = {};
export const ExistingRelationshipWithTest: Story<RemoteDBRelationshipWidgetProps> =
args => (
<RemoteDBRelationshipWidget
{...args}
onComplete={d => {
callbackResponse = d;
callbackResponseExistingRelationshipWithTest = d;
}}
/>
);
@ -104,20 +115,28 @@ ExistingRelationshipWithTest.args = {
...Primary.args,
existingRelationshipName: 'AlbumToResident',
};
ExistingRelationshipWithTest.parameters = {
chromatic: { disableSnapshot: true },
};
ExistingRelationshipWithTest.play = async ({ canvasElement }) => {
const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
callbackResponseExistingRelationshipWithTest = {};
const canvas = within(canvasElement);
const submitButton = await canvas.findByText('Save Relationship');
const relationshipType = await canvas.findByLabelText('Type');
userEvent.selectOptions(relationshipType, 'Array Relationship');
userEvent.click(submitButton);
await waitFor(() => {
expect(canvas.queryByText('Saving relationship')).toBeInTheDocument();
});
await delay(1000);
expect(JSON.stringify(callbackResponse)).toContain('Success');
expect(JSON.stringify(callbackResponse)).toContain(
'Relationship saved successfully'
);
await waitFor(() => {
expect(
JSON.stringify(callbackResponseExistingRelationshipWithTest)
).toContain('Success');
expect(
JSON.stringify(callbackResponseExistingRelationshipWithTest)
).toContain('Relationship saved successfully');
});
};

View File

@ -6,7 +6,7 @@ import {
} from '@/features/MetadataAPI';
import { useFireNotification } from '@/new-components/Notifications';
import { DataTarget } from '@/features/Datasources';
import { InputField, Select, Form } from '@/new-components/Form';
import { InputField, Select, useConsoleForm } from '@/new-components/Form';
import { Button } from '@/new-components/Button';
import { IndicatorCard } from '@/new-components/IndicatorCard';
import { getMetadataQuery, MetadataQueryType } from '@/metadata/queryUtils';
@ -62,6 +62,16 @@ export const RemoteDBRelationshipWidget = ({
existingRelationshipName,
});
const {
methods: { formState },
Form,
} = useConsoleForm({
schema,
options: {
defaultValues,
},
});
const { fireNotification } = useFireNotification();
const mutation = useMetadataMigration({
onSuccess: () => {
@ -144,65 +154,58 @@ export const RemoteDBRelationshipWidget = ({
}
return (
<Form
schema={schema}
onSubmit={submit}
options={{ defaultValues }}
className="p-4"
>
{options => (
<>
<div>
<div className="w-full sm:w-6/12 mb-md">
<div className="mb-md">
<InputField
name="relationshipName"
label="Name"
placeholder="Relationship name"
dataTest="local-db-to-db-rel-name"
disabled={!!existingRelationshipName}
/>
</div>
<div className="mb-md">
<Select
name="relationshipType"
label="Type"
dataTest="local-db-to-db-select-rel-type"
placeholder="Select a relationship type..."
options={[
{
label: 'Object Relationship',
value: 'object',
},
{
label: 'Array Relationship',
value: 'array',
},
]}
/>
</div>
<Form onSubmit={submit}>
<>
<div>
<div className="w-full sm:w-6/12 mb-md">
<div className="mb-md">
<InputField
name="relationshipName"
label="Name"
placeholder="Relationship name"
dataTest="local-db-to-db-rel-name"
disabled={!!existingRelationshipName}
/>
</div>
<FormElements />
<Button
mode="primary"
type="submit"
isLoading={mutation.isLoading}
loadingText="Saving relationship"
data-test="add-local-db-relationship"
>
Save Relationship
</Button>
<div className="mb-md">
<Select
name="relationshipType"
label="Type"
dataTest="local-db-to-db-select-rel-type"
placeholder="Select a relationship type..."
options={[
{
label: 'Object Relationship',
value: 'object',
},
{
label: 'Array Relationship',
value: 'array',
},
]}
/>
</div>
</div>
<FormElements />
{!!Object.keys(options.formState.errors).length && (
<IndicatorCard status="negative">
Error saving relationship
</IndicatorCard>
)}
</>
)}
<Button
mode="primary"
type="submit"
isLoading={mutation.isLoading}
loadingText="Saving relationship"
data-test="add-local-db-relationship"
>
Save Relationship
</Button>
</div>
{!!Object.keys(formState.errors).length && (
<IndicatorCard status="negative">
Error saving relationship
</IndicatorCard>
)}
</>
</Form>
);
};

View File

@ -1,6 +1,6 @@
import { Button } from '@/new-components/Button';
import { z } from 'zod';
import { InputField, UpdatedForm } from '@/new-components/Form';
import { InputField, SimpleForm } from '@/new-components/Form';
import React from 'react';
import { Relationship } from '../DatabaseRelationshipsTable/types';
import { useRenameRelationship } from './useRenameRelationship';
@ -18,7 +18,7 @@ export const RenameRelationship = ({
return (
<div>
<UpdatedForm
<SimpleForm
schema={z.object({
name: z.string(),
})}
@ -40,15 +40,13 @@ export const RenameRelationship = ({
},
}}
>
{() => (
<div>
<InputField name="name" type="text" label="Relationship Name" />
<Button type="submit" mode="primary" isLoading={isLoading}>
Rename
</Button>
</div>
)}
</UpdatedForm>
<div>
<InputField name="name" type="text" label="Relationship Name" />
<Button type="submit" mode="primary" isLoading={isLoading}>
Rename
</Button>
</div>
</SimpleForm>
</div>
);
};

View File

@ -1,6 +1,6 @@
import React from 'react';
import { useConsoleForm } from '@/new-components/Form';
import { Button } from '@/new-components/Button';
import { Form } from '@/new-components/Form';
import { DataTarget } from '@/features/Datasources';
import { TableRelationship } from '@/features/MetadataAPI';
@ -38,9 +38,7 @@ export const SuggestedRelationshipForm = ({
}: SuggestedRelationshipFormProps) => {
const { submit, isLoading } = useSubmit();
const handleSubmit = async ({
relationshipName,
}: Record<string, unknown>) => {
const onSubmit = async ({ relationshipName }: Record<string, unknown>) => {
try {
await submit({
relationshipName: relationshipName as string,
@ -53,36 +51,38 @@ export const SuggestedRelationshipForm = ({
}
};
const {
methods: { register },
Form,
} = useConsoleForm({
schema,
options: {
defaultValues: {
relationshipName: relationship.to.table,
},
},
});
return (
<Form
onSubmit={handleSubmit}
schema={schema}
options={{
defaultValues: {
relationshipName: relationship.to.table,
},
}}
>
{options => (
<div className="flex items-center space-x-1.5 bg-white">
<label htmlFor="relationshipName" className="sr-only">
Relationship Name
</label>
<input
id="relationshipName"
type="text"
className="block w-full h-input shadow-sm rounded border border-gray-300 hover:border-gray-400 focus:outline-0 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="Relationship Name..."
{...options.register('relationshipName')}
/>
<Button type="submit" mode="primary" isLoading={isLoading}>
Add Relationship
</Button>
<button onClick={close} aria-label="close">
<CloseIcon />
</button>
</div>
)}
<Form onSubmit={onSubmit}>
<div className="flex items-center space-x-1.5 bg-white">
<label htmlFor="relationshipName" className="sr-only">
Relationship Name
</label>
<input
id="relationshipName"
type="text"
className="block w-full h-input shadow-sm rounded border border-gray-300 hover:border-gray-400 focus:outline-0 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="Relationship Name..."
{...register('relationshipName')}
/>
<Button type="submit" mode="primary" isLoading={isLoading}>
Add Relationship
</Button>
<button onClick={close} aria-label="close">
<CloseIcon />
</button>
</div>
</Form>
);
};

View File

@ -1,6 +1,6 @@
import { Table } from '@/features/hasura-metadata-types';
import { Button } from '@/new-components/Button';
import { InputField, Select, UpdatedForm } from '@/new-components/Form';
import { InputField, Select, SimpleForm } from '@/new-components/Form';
import React from 'react';
import { useManageLocalRelationship } from '../../hooks/useManageLocalRelationship';
import { LocalRelationship } from '../../types';
@ -49,7 +49,7 @@ export const Widget = (props: WidgetProps) => {
};
return (
<UpdatedForm
<SimpleForm
schema={schema}
options={{
defaultValues: {
@ -66,78 +66,76 @@ export const Widget = (props: WidgetProps) => {
}}
onSubmit={handleFormSubmit}
>
{() => (
<div id="create-local-rel" className="mt-4">
<InputField
name="name"
label="Name"
placeholder="Relationship name"
dataTest="local-db-to-db-rel-name"
/>
<div id="create-local-rel" className="mt-4">
<InputField
name="name"
label="Name"
placeholder="Relationship name"
dataTest="local-db-to-db-rel-name"
/>
<Select
name="relationship_type"
label="Relationship Type"
dataTest="local-db-to-db-select-rel-type"
placeholder="Select a relationship type..."
options={[
{
label: 'Object Relationship',
value: 'Object',
},
{
label: 'Array Relationship',
value: 'Array',
},
]}
/>
<Select
name="relationship_type"
label="Relationship Type"
dataTest="local-db-to-db-select-rel-type"
placeholder="Select a relationship type..."
options={[
{
label: 'Object Relationship',
value: 'Object',
},
{
label: 'Array Relationship',
value: 'Array',
},
]}
/>
<div>
<div className="grid grid-cols-12">
<div className="col-span-5">
<div className="rounded bg-gray-50 border border-gray-300 p-md gap-y-4 border-l-4 border-l-green-600">
<TablePicker
name="fromSource"
options={{
dataSource: { disabled: true },
table: { disabled: true },
}}
/>
</div>
</div>
<LinkBlockHorizontal />
<div className="col-span-5">
<div className="rounded bg-gray-50 border border-gray-300 p-md gap-y-4 border-l-4 border-l-indigo-600">
<TablePicker
name="toSource"
options={{
dataSource: { disabled: true },
}}
/>
</div>
<div>
<div className="grid grid-cols-12">
<div className="col-span-5">
<div className="rounded bg-gray-50 border border-gray-300 p-md gap-y-4 border-l-4 border-l-green-600">
<TablePicker
name="fromSource"
options={{
dataSource: { disabled: true },
table: { disabled: true },
}}
/>
</div>
</div>
<LinkBlockVertical title="Columns Mapped To" />
<LinkBlockHorizontal />
<MapColumns />
<div className="flex justify-end gap-2 mb-md">
<Button onClick={onCancel}>Close</Button>
<Button
type="submit"
mode="primary"
isLoading={isLoading}
loadingText="Creating"
>
Create
</Button>
<div className="col-span-5">
<div className="rounded bg-gray-50 border border-gray-300 p-md gap-y-4 border-l-4 border-l-indigo-600">
<TablePicker
name="toSource"
options={{
dataSource: { disabled: true },
}}
/>
</div>
</div>
</div>
<LinkBlockVertical title="Columns Mapped To" />
<MapColumns />
<div className="flex justify-end gap-2 mb-md">
<Button onClick={onCancel}>Close</Button>
<Button
type="submit"
mode="primary"
isLoading={isLoading}
loadingText="Creating"
>
Create
</Button>
</div>
</div>
)}
</UpdatedForm>
</div>
</SimpleForm>
);
};

View File

@ -1,5 +1,5 @@
import { Dialog } from '@/new-components/Dialog';
import { InputField, UpdatedForm } from '@/new-components/Form';
import { InputField, SimpleForm } from '@/new-components/Form';
import { IndicatorCard } from '@/new-components/IndicatorCard';
import React from 'react';
import { z } from 'zod';
@ -45,7 +45,7 @@ export const RenameRelationship = (props: RenameRelationshipProps) => {
description="Rename your current relationship. "
onClose={onCancel}
>
<UpdatedForm
<SimpleForm
schema={z.object({
updatedName: z.string().min(1, 'Updated name cannot be empty!'),
})}
@ -53,25 +53,23 @@ export const RenameRelationship = (props: RenameRelationshipProps) => {
renameRelationship(relationship, data.updatedName);
}}
>
{() => (
<>
<div className="m-4">
<InputField
name="updatedName"
label="New name"
placeholder="Enter a new name"
tooltip="New name of the relationship. Remember relationship names are unique."
/>
</div>
<Dialog.Footer
callToDeny="Cancel"
callToAction="Rename"
onClose={onCancel}
isLoading={false}
<>
<div className="m-4">
<InputField
name="updatedName"
label="New name"
placeholder="Enter a new name"
tooltip="New name of the relationship. Remember relationship names are unique."
/>
</>
)}
</UpdatedForm>
</div>
<Dialog.Footer
callToDeny="Cancel"
callToAction="Rename"
onClose={onCancel}
isLoading={false}
/>
</>
</SimpleForm>
</Dialog>
);
};

View File

@ -1,8 +1,9 @@
import React from 'react';
import { UpdatedForm } from '@/new-components/Form';
import { SimpleForm } from '@/new-components/Form';
import { z } from 'zod';
import { action } from '@storybook/addon-actions';
import { Button } from '@/new-components/Button';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import { TablePicker } from './TablePicker';
@ -13,16 +14,14 @@ export default {
} as ComponentMeta<typeof TablePicker>;
export const Basic: ComponentStory<typeof TablePicker> = () => (
<UpdatedForm
<SimpleForm
schema={z.object({
from: z.object({
dataSourceName: z.string(),
table: z.unknown(),
}),
})}
onSubmit={data => {
console.log(data);
}}
onSubmit={action('onSubmit')}
options={{
defaultValues: {
from: {
@ -35,11 +34,9 @@ export const Basic: ComponentStory<typeof TablePicker> = () => (
},
}}
>
{() => (
<>
<TablePicker name="from" />
<Button type="submit">Submit</Button>
</>
)}
</UpdatedForm>
<>
<TablePicker name="from" />
<Button type="submit">Submit</Button>
</>
</SimpleForm>
);

View File

@ -1,5 +1,5 @@
import { Button } from '@/new-components/Button';
import { Form, InputField } from '@/new-components/Form';
import { InputField, SimpleForm } from '@/new-components/Form';
import React from 'react';
import { z } from 'zod';
import { useAddAgent } from '../hooks/useAddAgent';
@ -27,54 +27,50 @@ export const AddAgentForm = (props: CreateAgentFormProps) => {
};
return (
<Form
<SimpleForm
schema={schema}
// something is wrong with type inference with react-hook-form form wrapper. temp until the issue is resolved
onSubmit={handleSubmit as any}
options={{ defaultValues: { url: '', name: '' } }}
className="py-4"
>
{() => {
return (
<>
<div className="bg-white p-6 border border-gray-300 rounded space-y-4 mb-6 max-w-xl">
<p className="text-lg text-gray-600 font-bold">
Connect a Data Connector Agent
</p>
<hr />
<>
<div className="bg-white p-6 border border-gray-300 rounded space-y-4 mb-6 max-w-xl">
<p className="text-lg text-gray-600 font-bold">
Connect a Data Connector Agent
</p>
<hr />
<InputField
label="Name"
name="name"
type="text"
tooltip="This value will be used as the source kind in metadata"
placeholder="Enter the name of the agent"
/>
<InputField
label="Name"
name="name"
type="text"
tooltip="This value will be used as the source kind in metadata"
placeholder="Enter the name of the agent"
/>
<InputField
label="URL"
name="url"
type="text"
tooltip="The URL of the data connector agent"
placeholder="Enter the URI of the agent"
/>
<div className="flex gap-4 justify-end">
<Button type="submit" mode="primary" isLoading={isLoading}>
Connect
</Button>
<Button
onClick={() => {
props.onClose();
}}
>
Close
</Button>
</div>
</div>
</>
);
}}
</Form>
<InputField
label="URL"
name="url"
type="text"
tooltip="The URL of the data connector agent"
placeholder="Enter the URI of the agent"
/>
<div className="flex gap-4 justify-end">
<Button type="submit" mode="primary" isLoading={isLoading}>
Connect
</Button>
<Button
onClick={() => {
props.onClose();
}}
>
Close
</Button>
</div>
</div>
</>
</SimpleForm>
);
};

View File

@ -1,5 +1,5 @@
import { useConsoleForm } from '@/new-components/Form';
import { Button } from '@/new-components/Button';
import { Form } from '@/new-components/Form';
import { OpenApiSchema } from '@hasura/dc-api-types';
import React, { useState } from 'react';
import ReactJson from 'react-json-view';
@ -26,42 +26,43 @@ export const RenderOpenApi3Form = ({
configSchema,
otherSchemas,
});
const {
methods: { formState },
Form,
} = useConsoleForm({
schema: z.object(schema ? { [name]: schema } : {}),
options: {
defaultValues,
},
});
if (!schema || isLoading) return <>Loading...</>;
return (
<Form
schema={z.object({ [name]: schema })}
options={{
defaultValues,
}}
onSubmit={values => {
setSubmittedValues(values as any);
}}
>
{options => {
return (
<>
<OpenApi3Form
schemaObject={configSchema}
references={otherSchemas}
name={name}
/>
<Button type="submit" data-testid="submit-form-btn">
Submit
</Button>
<div>Submitted Values:</div>
<div data-testid="output">
{rawOutput ? (
JSON.stringify(submittedValues)
) : (
<ReactJson src={submittedValues} name={false} />
)}
</div>
{console.log(options.formState.errors)}
</>
);
}}
<>
<OpenApi3Form
schemaObject={configSchema}
references={otherSchemas}
name={name}
/>
<Button type="submit" data-testid="submit-form-btn">
Submit
</Button>
<div>Submitted Values:</div>
<div data-testid="output">
{rawOutput ? (
JSON.stringify(submittedValues)
) : (
<ReactJson src={submittedValues} name={false} />
)}
</div>
{console.log(formState.errors)}
</>
</Form>
);
};

View File

@ -1,6 +1,6 @@
import React from 'react';
import { UpdatedForm as Form } from '@/new-components/Form';
import { useConsoleForm } from '@/new-components/Form';
import { Button } from '@/new-components/Button';
import { IndicatorCard } from '@/new-components/IndicatorCard';
@ -53,7 +53,7 @@ export const PermissionsForm = (props: PermissionsFormProps) => {
accessType,
});
const handleSubmit = async (formData: PermissionsSchema) => {
const onSubmit = async (formData: PermissionsSchema) => {
await updatePermissions.submit(formData);
handleClose();
};
@ -69,6 +69,18 @@ export const PermissionsForm = (props: PermissionsFormProps) => {
// for update it is possible to set pre update and post update row checks
const rowPermissions = queryType === 'update' ? ['pre', 'post'] : [queryType];
const { formData, defaultValues } = data || {};
const {
methods: { getValues },
Form,
} = useConsoleForm({
schema,
options: {
defaultValues,
},
});
if (isSubmittingError) {
return (
<IndicatorCard status="negative">Error submitting form</IndicatorCard>
@ -87,95 +99,82 @@ export const PermissionsForm = (props: PermissionsFormProps) => {
return <IndicatorCard status="info">Loading...</IndicatorCard>;
}
const { formData, defaultValues } = data;
// allRowChecks relates to other queries and is for duplicating from others
const allRowChecks = defaultValues?.allRowChecks;
const key = `${JSON.stringify(table)}-${queryType}-${roleName}`;
const filterType = getValues('filterType');
return (
<Form
key={key}
onSubmit={handleSubmit}
schema={schema}
options={{ defaultValues }}
>
{options => {
const filterType = options.getValues('filterType');
if (Object.keys(options.formState.errors)?.length) {
console.error('form errors:', options.formState.errors);
}
return (
<div className="bg-white rounded p-md border border-gray-300">
<div className="pb-4 flex items-center gap-4">
<Button type="button" onClick={handleClose}>
Close
</Button>
<h3 data-testid="form-title">
<strong>Role:</strong> {roleName} <strong>Action:</strong>{' '}
{queryType}
</h3>
</div>
<Form onSubmit={onSubmit} key={key}>
<div className="bg-white rounded p-md border border-gray-300">
<div className="pb-4 flex items-center gap-4">
<Button type="button" onClick={handleClose}>
Close
</Button>
<h3 data-testid="form-title">
<strong>Role:</strong> {roleName} <strong>Action:</strong>{' '}
{queryType}
</h3>
</div>
<RowPermissionsSectionWrapper
roleName={roleName}
queryType={queryType}
defaultOpen
>
{rowPermissions.map(permissionName => (
<React.Fragment key={permissionName}>
{queryType === 'update' && (
<p className="my-2">
<strong>
{permissionName === 'pre'
? 'Pre-update'
: 'Post-update'}
&nbsp; check
</strong>
&nbsp;
{permissionName === 'Post-update' && '(optional)'}
</p>
)}
<RowPermissionsSection
table={table}
queryType={queryType}
subQueryType={
queryType === 'update' ? permissionName : undefined
}
allRowChecks={allRowChecks || []}
dataSourceName={dataSourceName}
/>
</React.Fragment>
))}
</RowPermissionsSectionWrapper>
{queryType !== 'delete' && (
<ColumnPermissionsSection
roleName={roleName}
<RowPermissionsSectionWrapper
roleName={roleName}
queryType={queryType}
defaultOpen
>
{rowPermissions.map(permissionName => (
<React.Fragment key={permissionName}>
{queryType === 'update' && (
<p className="my-2">
<strong>
{permissionName === 'pre' ? 'Pre-update' : 'Post-update'}
&nbsp; check
</strong>
&nbsp;
{permissionName === 'Post-update' && '(optional)'}
</p>
)}
<RowPermissionsSection
table={table}
queryType={queryType}
columns={formData?.columns}
subQueryType={
queryType === 'update' ? permissionName : undefined
}
allRowChecks={allRowChecks || []}
dataSourceName={dataSourceName}
/>
)}
</React.Fragment>
))}
</RowPermissionsSectionWrapper>
{['insert', 'update'].includes(queryType) && (
<ColumnPresetsSection
queryType={queryType}
columns={formData?.columns}
/>
)}
{queryType !== 'delete' && (
<ColumnPermissionsSection
roleName={roleName}
queryType={queryType}
columns={formData?.columns}
/>
)}
{queryType === 'select' && (
<AggregationSection queryType={queryType} roleName={roleName} />
)}
{['insert', 'update'].includes(queryType) && (
<ColumnPresetsSection
queryType={queryType}
columns={formData?.columns}
/>
)}
{['insert', 'update', 'delete'].includes(queryType) && (
<BackendOnlySection queryType={queryType} />
)}
{queryType === 'select' && (
<AggregationSection queryType={queryType} roleName={roleName} />
)}
<hr className="my-4" />
{['insert', 'update', 'delete'].includes(queryType) && (
<BackendOnlySection queryType={queryType} />
)}
{/* {!!tableNames?.length && (
<hr className="my-4" />
{/* {!!tableNames?.length && (
<ClonePermissionsSection
queryType={queryType}
tables={tableNames}
@ -184,34 +183,32 @@ export const PermissionsForm = (props: PermissionsFormProps) => {
/>
)} */}
<div className="pt-2 flex gap-2">
<Button
type="submit"
mode="primary"
title={
filterType === 'none'
? 'You must select an option for row permissions'
: 'Submit'
}
disabled={filterType === 'none'}
isLoading={updatePermissions.isLoading}
>
Save Permissions
</Button>
<div className="pt-2 flex gap-2">
<Button
type="submit"
mode="primary"
title={
filterType === 'none'
? 'You must select an option for row permissions'
: 'Submit'
}
disabled={filterType === 'none'}
isLoading={updatePermissions.isLoading}
>
Save Permissions
</Button>
<Button
type="button"
disabled={accessType === 'noAccess'}
mode="destructive"
isLoading={deletePermissions.isLoading}
onClick={handleDelete}
>
Delete Permissions
</Button>
</div>
</div>
);
}}
<Button
type="button"
disabled={accessType === 'noAccess'}
mode="destructive"
isLoading={deletePermissions.isLoading}
onClick={handleDelete}
>
Delete Permissions
</Button>
</div>
</div>
</Form>
);
};

View File

@ -1,9 +1,10 @@
import React from 'react';
import { Story, Meta } from '@storybook/react';
import { Meta, Story } from '@storybook/react';
import { z } from 'zod';
import { Form } from '@/new-components/Form';
import { SimpleForm } from '@/new-components/Form';
import { action } from '@storybook/addon-actions';
import { AggregationSection, AggregationProps } from './Aggregation';
import { AggregationProps, AggregationSection } from './Aggregation';
export default {
title:
@ -27,14 +28,14 @@ AggregationEnabled.args = {
};
AggregationEnabled.decorators = [
(StoryComponent: React.FC) => (
<Form
<SimpleForm
schema={schema}
onSubmit={() => {}}
onSubmit={action('onSubmit')}
options={{ defaultValues: { enableAggregation: true } }}
className="p-4"
>
{() => <StoryComponent />}
</Form>
<StoryComponent />
</SimpleForm>
),
];
@ -46,14 +47,14 @@ AggregationDisabled.args = {
};
AggregationDisabled.decorators = [
(StoryComponent: React.FC) => (
<Form
<SimpleForm
schema={schema}
onSubmit={() => {}}
onSubmit={action('onSubmit')}
options={{ defaultValues: { enableAggregation: false } }}
className="p-4"
>
{() => <StoryComponent />}
</Form>
<StoryComponent />
</SimpleForm>
),
];

View File

@ -1,7 +1,7 @@
import React from 'react';
import { Story, Meta } from '@storybook/react';
import { Meta, Story } from '@storybook/react';
import { z } from 'zod';
import { Form } from '@/new-components/Form';
import { SimpleForm } from '@/new-components/Form';
import { BackendOnlySection, BackEndOnlySectionProps } from './BackendOnly';
@ -27,14 +27,14 @@ BackendOnlyEnabled.args = {
};
BackendOnlyEnabled.decorators = [
(StoryComponent: React.FC) => (
<Form
<SimpleForm
schema={schema}
onSubmit={() => {}}
options={{ defaultValues: { backendOnly: true } }}
className="p-4"
>
{() => <StoryComponent />}
</Form>
<StoryComponent />
</SimpleForm>
),
];
@ -46,14 +46,14 @@ BackendOnlyDisabled.args = {
};
BackendOnlyDisabled.decorators = [
(StoryComponent: React.FC) => (
<Form
<SimpleForm
schema={schema}
onSubmit={() => {}}
options={{ defaultValues: { backendOnly: false } }}
className="p-4"
>
{() => <StoryComponent />}
</Form>
<StoryComponent />
</SimpleForm>
),
];

View File

@ -1,8 +1,8 @@
import React from 'react';
import { Story, Meta } from '@storybook/react';
import { Meta, Story } from '@storybook/react';
import * as z from 'zod';
import { Form } from '@/new-components/Form';
import { SimpleForm } from '@/new-components/Form';
import {
ClonePermissionsSection,
@ -15,9 +15,9 @@ export default {
component: ClonePermissionsSection,
decorators: [
(StoryComponent: React.FC) => (
<Form schema={z.any()} onSubmit={() => {}} className="p-4">
{() => <StoryComponent />}
</Form>
<SimpleForm schema={z.any()} onSubmit={() => {}} className="p-4">
<StoryComponent />
</SimpleForm>
),
],
} as Meta;

View File

@ -1,7 +1,7 @@
import React from 'react';
import { Story, Meta } from '@storybook/react';
import { Meta, Story } from '@storybook/react';
import { z } from 'zod';
import { Form } from '@/new-components/Form';
import { SimpleForm } from '@/new-components/Form';
import {
ColumnPermissionsSection,
@ -15,7 +15,7 @@ export default {
component: ColumnPermissionsSection,
decorators: [
(StoryComponent: React.FC) => (
<Form
<SimpleForm
schema={schema}
onSubmit={() => {}}
options={{
@ -25,8 +25,8 @@ export default {
}}
className="p-4"
>
{() => <StoryComponent />}
</Form>
<StoryComponent />
</SimpleForm>
),
],
parameters: {
@ -79,7 +79,7 @@ PartiallySelected.args = {
};
PartiallySelected.decorators = [
(S: React.FC) => (
<Form
<SimpleForm
schema={schema}
onSubmit={() => {}}
options={{
@ -89,8 +89,8 @@ PartiallySelected.decorators = [
}}
className="p-4"
>
{() => <S />}
</Form>
<S />
</SimpleForm>
),
];
@ -102,7 +102,7 @@ AllSelected.args = {
};
AllSelected.decorators = [
(S: React.FC) => (
<Form
<SimpleForm
schema={schema}
onSubmit={() => {}}
options={{
@ -112,8 +112,8 @@ AllSelected.decorators = [
}}
className="p-4"
>
{() => <S />}
</Form>
<S />
</SimpleForm>
),
];

View File

@ -1,7 +1,7 @@
import React from 'react';
import { Story, Meta } from '@storybook/react';
import { Meta, Story } from '@storybook/react';
import { z } from 'zod';
import { Form } from '@/new-components/Form';
import { SimpleForm } from '@/new-components/Form';
import {
ColumnPresetsSection,
@ -13,7 +13,7 @@ export default {
component: ColumnPresetsSection,
decorators: [
(StoryComponent: React.FC) => (
<Form
<SimpleForm
schema={z.any()}
options={{
defaultValues: {
@ -24,8 +24,8 @@ export default {
onSubmit={() => {}}
className="p-4"
>
{() => <StoryComponent />}
</Form>
<StoryComponent />
</SimpleForm>
),
],
parameters: { chromatic: { disableSnapshot: true } },
@ -57,7 +57,7 @@ WithPartialPresets.args = {
};
WithPartialPresets.decorators = [
(S: React.FC) => (
<Form
<SimpleForm
schema={z.object({
presets: z.any(),
rowPermissionsCheckType: z.string(),
@ -78,8 +78,8 @@ WithPartialPresets.decorators = [
onSubmit={() => {}}
className="p-4"
>
{() => <S />}
</Form>
<S />
</SimpleForm>
),
];
@ -91,7 +91,7 @@ WithAllPresets.args = {
};
WithAllPresets.decorators = [
(S: React.FC) => (
<Form
<SimpleForm
schema={z.object({
presets: z.array(
z.object({
@ -131,8 +131,8 @@ WithAllPresets.decorators = [
onSubmit={() => {}}
className="p-4"
>
{() => <S />}
</Form>
<S />
</SimpleForm>
),
];

View File

@ -1,11 +1,11 @@
import React from 'react';
import { Story, Meta } from '@storybook/react';
import { Form } from '@/new-components/Form';
import { Meta, Story } from '@storybook/react';
import { SimpleForm } from '@/new-components/Form';
import { z } from 'zod';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import {
RowPermissionsSection,
RowPermissionsProps,
RowPermissionsSection,
RowPermissionsSectionWrapper,
RowPermissionsWrapperProps,
} from './RowPermissions';
@ -17,9 +17,9 @@ export default {
component: RowPermissionsSection,
decorators: [
(StoryComponent: React.FC) => (
<Form schema={z.any()} onSubmit={() => {}}>
{() => <StoryComponent />}
</Form>
<SimpleForm schema={z.any()} onSubmit={() => {}}>
<StoryComponent />
</SimpleForm>
),
ReactQueryDecorator(),
],

View File

@ -2,17 +2,17 @@ import React from 'react';
import { z } from 'zod';
import { ComponentStory, Meta } from '@storybook/react';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import { UpdatedForm } from '@/new-components/Form';
import { SimpleForm } from '@/new-components/Form';
import { RowPermissionBuilder } from './RowPermissionBuilder';
import { createDefaultValues } from './utils';
import {
complicatedExample,
exampleWithBoolOperator,
exampleWithRelationship,
handlers,
schema,
simpleExample,
exampleWithBoolOperator,
exampleWithRelationship,
complicatedExample,
} from './mocks';
export default {
@ -36,11 +36,9 @@ Primary.decorators = [
Component => {
return (
<div style={{ width: 800 }}>
<UpdatedForm schema={z.any()} onSubmit={console.log}>
{() => {
return <Component />;
}}
</UpdatedForm>
<SimpleForm schema={z.any()} onSubmit={console.log}>
<Component />
</SimpleForm>
</div>
);
},
@ -56,7 +54,7 @@ WithDefaults.decorators = [
Component => {
return (
<div style={{ width: 800 }}>
<UpdatedForm
<SimpleForm
schema={z.any()}
options={{
defaultValues: createDefaultValues({
@ -68,10 +66,8 @@ WithDefaults.decorators = [
}}
onSubmit={console.log}
>
{() => {
return <Component />;
}}
</UpdatedForm>
<Component />
</SimpleForm>
</div>
);
},
@ -89,7 +85,7 @@ WithDefaultsBool.decorators = [
Component => {
return (
<div style={{ width: 800 }}>
<UpdatedForm
<SimpleForm
schema={z.any()}
options={{
defaultValues: createDefaultValues({
@ -101,10 +97,8 @@ WithDefaultsBool.decorators = [
}}
onSubmit={console.log}
>
{() => {
return <Component />;
}}
</UpdatedForm>
<Component />
</SimpleForm>
</div>
);
},
@ -122,7 +116,7 @@ WithDefaultsRelationship.decorators = [
Component => {
return (
<div style={{ width: 800 }}>
<UpdatedForm
<SimpleForm
schema={z.any()}
options={{
defaultValues: createDefaultValues({
@ -134,10 +128,8 @@ WithDefaultsRelationship.decorators = [
}}
onSubmit={console.log}
>
{() => {
return <Component />;
}}
</UpdatedForm>
<Component />;
</SimpleForm>
</div>
);
},
@ -156,7 +148,7 @@ WithPointlesslyComplicatedRelationship.decorators = [
Component => {
return (
<div style={{ width: 800 }}>
<UpdatedForm
<SimpleForm
schema={z.any()}
options={{
defaultValues: createDefaultValues({
@ -168,10 +160,8 @@ WithPointlesslyComplicatedRelationship.decorators = [
}}
onSubmit={console.log}
>
{() => {
return <Component />;
}}
</UpdatedForm>
<Component />
</SimpleForm>
</div>
);
},

View File

@ -1,14 +1,14 @@
import React from 'react';
import { Story, Meta } from '@storybook/react';
import { Meta, Story } from '@storybook/react';
import { z } from 'zod';
import { Form } from '@/new-components/Form';
import { SimpleForm } from '@/new-components/Form';
import { useTableMachine } from '../hooks/useTableMachine';
import {
InputCell,
InputCellProps,
EditableCell,
EditableCellProps,
InputCell,
InputCellProps,
} from './Cells';
export default {
@ -16,9 +16,9 @@ export default {
component: InputCell,
decorators: [
(StoryComponent: React.FC) => (
<Form schema={z.any()} onSubmit={() => {}}>
{() => <StoryComponent />}
</Form>
<SimpleForm schema={z.any()} onSubmit={() => {}}>
<StoryComponent />
</SimpleForm>
),
],
parameters: { chromatic: { disableSnapshot: true } },

View File

@ -3,14 +3,14 @@ import { z } from 'zod';
import Skeleton from 'react-loading-skeleton';
import 'ace-builds/src-noconflict/mode-yaml';
import { Form, InputField, CodeEditorField } from '@/new-components/Form';
import { CodeEditorField, InputField, SimpleForm } from '@/new-components/Form';
import { Button } from '@/new-components/Button';
import { Badge } from '@/new-components/Badge';
import { Card } from '@/new-components/Card';
import {
FaCheckCircle,
FaTimesCircle,
FaExclamationTriangle,
FaTimesCircle,
} from 'react-icons/fa';
import { PrometheusAnimation } from './PrometheusAnimation';
@ -60,7 +60,7 @@ const PrometheusFormFields = ({
prometheusUrl,
prometheusConfig,
}: PrometheidFormFieldsProps) => (
<Form
<SimpleForm
schema={z.object({})}
onSubmit={() => {}}
options={{
@ -70,51 +70,49 @@ const PrometheusFormFields = ({
},
}}
>
{() => (
<>
<InputField
name="prometheusUrl"
label="Prometheus URL"
placeholder="URL"
tooltip="This is the URL from which Hasura exposes its metrics in the Prometheus format."
loading={loading}
size="full"
disabled
/>
<CodeEditorField
name="prometheusConfig"
label="Example Prometheus Configuration (.yml)"
tooltip={
<span>
This is a{' '}
<span className="font-mono text-sm w-max text-red-600 bg-red-50 px-1.5 py-0.5 rounded">
scrape_config
</span>{' '}
section of{' '}
<a
href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config"
target="_blank"
rel="noreferrer"
className="text-cloud italic"
>
a Prometheus configuration file
</a>{' '}
used for scraping metrics from this Hasura instance.
</span>
}
mode="yaml"
theme="eclipse"
editorOptions={{
minLines: 17,
maxLines: 20,
}}
loading={loading}
size="full"
disabled
/>
</>
)}
</Form>
<>
<InputField
name="prometheusUrl"
label="Prometheus URL"
placeholder="URL"
tooltip="This is the URL from which Hasura exposes its metrics in the Prometheus format."
loading={loading}
size="full"
disabled
/>
<CodeEditorField
name="prometheusConfig"
label="Example Prometheus Configuration (.yml)"
tooltip={
<span>
This is a{' '}
<span className="font-mono text-sm w-max text-red-600 bg-red-50 px-1.5 py-0.5 rounded">
scrape_config
</span>{' '}
section of{' '}
<a
href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config"
target="_blank"
rel="noreferrer"
className="text-cloud italic"
>
a Prometheus configuration file
</a>{' '}
used for scraping metrics from this Hasura instance.
</span>
}
mode="yaml"
theme="eclipse"
editorOptions={{
minLines: 17,
maxLines: 20,
}}
loading={loading}
size="full"
disabled
/>
</>
</SimpleForm>
);
PrometheusFormFields.defaultProps = {

View File

@ -1,7 +1,7 @@
import React from 'react';
import z from 'zod';
import { Dialog } from '@/new-components/Dialog';
import { Form, InputField } from '@/new-components/Form';
import { InputField, useConsoleForm } from '@/new-components/Form';
import { useFireNotification } from '@/new-components/Notifications';
import { Analytics, REDACT_EVERYTHING } from '@/features/Analytics';
import { useRenameQueryCollection } from '../../../QueryCollections/hooks/useRenameQueryCollection';
@ -21,63 +21,66 @@ export const QueryCollectionRenameDialog: React.FC<QueryCollectionCreateDialogPr
const { renameQueryCollection, isLoading } = useRenameQueryCollection();
const { fireNotification } = useFireNotification();
const {
methods: { watch, setError, trigger },
Form,
} = useConsoleForm({
schema,
});
const name = watch('name');
return (
<Form schema={schema} onSubmit={() => {}} className="p-4">
{({ watch, setError, trigger }) => {
const name = watch('name');
return (
<Dialog hasBackdrop title="Rename Collection" onClose={onClose}>
<>
<Analytics
name="QueryCollectionRenameDialog"
{...REDACT_EVERYTHING}
>
<div className="p-4">
<InputField
id="name"
name="name"
label="New Collection Name"
placeholder="New Collection Name..."
/>
</div>
</Analytics>
<Dialog.Footer
callToDeny="Cancel"
callToAction="Rename Collection"
onClose={onClose}
onSubmit={async () => {
if (await trigger()) {
// TODO: remove as when proper form types will be available
renameQueryCollection(currentName, name as string, {
onSuccess: () => {
onClose();
onRename(currentName, name as string);
fireNotification({
type: 'success',
title: 'Collection renamed',
message: `Collection ${currentName} was renamed to ${name}`,
});
},
onError: error => {
setError('name', {
type: 'manual',
message: (error as Error).message,
});
fireNotification({
type: 'error',
title: 'Error renaming collection',
message: (error as Error).message,
});
},
});
}
}}
isLoading={isLoading}
<Form onSubmit={() => {}}>
<Dialog hasBackdrop title="Rename Collection" onClose={onClose}>
<>
<Analytics
name="QueryCollectionRenameDialog"
{...REDACT_EVERYTHING}
>
<div className="p-4">
<InputField
id="name"
name="name"
label="New Collection Name"
placeholder="New Collection Name..."
/>
</>
</Dialog>
);
}}
</div>
</Analytics>
<Dialog.Footer
callToDeny="Cancel"
callToAction="Rename Collection"
onClose={onClose}
onSubmit={async () => {
if (await trigger()) {
// TODO: remove as when proper form types will be available
renameQueryCollection(currentName, name as string, {
onSuccess: () => {
onClose();
onRename(currentName, name as string);
fireNotification({
type: 'success',
title: 'Collection renamed',
message: `Collection ${currentName} was renamed to ${name}`,
});
},
onError: error => {
setError('name', {
type: 'manual',
message: (error as Error).message,
});
fireNotification({
type: 'error',
title: 'Error renaming collection',
message: (error as Error).message,
});
},
});
}
}}
isLoading={isLoading}
/>
</>
</Dialog>
</Form>
);
};

View File

@ -1,75 +0,0 @@
import React, { useEffect } from 'react';
import clsx from 'clsx';
import get from 'lodash.get';
import { FieldError, useFormContext } from 'react-hook-form';
import {
FieldWrapper,
FieldWrapperPassThroughProps,
} from '@/new-components/Form';
import { parseQueryString, readFile } from './utils';
export type GraphQLFileUploadProps = FieldWrapperPassThroughProps & {
/**
* The input field name used by react-hook-form (to store bulk data && validation errors
*/
name: string;
/**
* The input field classes
*/
className?: string;
};
/**
* GraphQLFileUpload
* GraphQLFileUpload is a custom implementation that parses the uploaded file and set it to existing react hook form context
*
*/
export const GraphQLFileUpload: React.FC<GraphQLFileUploadProps> = ({
name,
dataTest,
...wrapperProps
}: GraphQLFileUploadProps) => {
const {
setValue,
reset,
setError,
formState: { errors },
} = useFormContext();
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = e.target.files;
readFile(files![0], data => {
try {
const parsedData = parseQueryString(data);
setValue(name, parsedData);
} catch (error) {
setError(name, { type: 'custom', message: 'Invalid GraphQL query' });
}
});
};
const maybeError = get(errors, name) as FieldError | undefined;
useEffect(() => {
return () => {
// reset the form when the component is unmounted
reset();
};
}, [reset]);
return (
<FieldWrapper id={name} {...wrapperProps} error={maybeError}>
<div className={clsx('relative flex max-w-xl')}>
<input
type="file"
id={name}
aria-invalid={maybeError ? 'true' : 'false'}
aria-label={wrapperProps.label}
data-test={dataTest}
data-testid={name}
onChange={handleFileUpload}
/>
</div>
</FieldWrapper>
);
};

View File

@ -2,13 +2,21 @@ import React from 'react';
import z from 'zod';
import { Dialog } from '@/new-components/Dialog';
import { CodeEditorField, Form, InputField } from '@/new-components/Form';
import {
CodeEditorField,
InputField,
useConsoleForm,
} from '@/new-components/Form';
import { SubmitHandler } from 'react-hook-form';
import { QueryCollection } from '@/metadata/types';
import { Tabs } from '@/new-components/Tabs';
import ToolTip from '@/components/Common/Tooltip/Tooltip';
import { GraphQLFileUpload } from './GraphQLFileUpload';
import { QuickAdd } from './QuickAdd';
import { parseQueryString, readFileAsync } from './utils';
type UploadedQueryPayload = {
gqlFile?: any;
option: 'upload operation';
};
const schema = z.discriminatedUnion('option', [
z.object({
@ -18,12 +26,27 @@ const schema = z.discriminatedUnion('option', [
}),
z.object({
option: z.literal('upload operation'),
gqlFile: z.array(
z.object({
name: z.string(),
query: z.string(),
})
),
gqlFile: z.union([
z
.any()
.refine(files => files?.length === 1, 'GraphQL file is required.')
.refine(async files => {
let validQuery = true;
const data = await readFileAsync(files![0]);
try {
parseQueryString(data);
} catch (error) {
validQuery = false;
}
return validQuery;
}, 'Invalid GraphQL query'),
z.array(
z.object({
name: z.string(),
query: z.string(),
})
),
]),
}),
]);
@ -52,120 +75,138 @@ export const QueryCollectionOperationDialog = (
: setTabValue('write operation');
};
return (
<Form
// '@ts-expect-error' remove this when new form types are available
onSubmit={onSubmit as SubmitHandler<Record<string, unknown>>}
schema={schema}
options={{
// '@ts-expect-error' remove this when new form types are available
defaultValues,
}}
>
{options => {
return (
<Dialog hasBackdrop title={title} onClose={onClose}>
<>
{title === 'Add Operation' ? (
<Tabs
value={tabValue}
onValueChange={value => {
handleTab();
options.setValue('option', value);
}}
items={[
{
value: 'write operation',
label: 'Write Operation',
content: (
<div className="p-4">
<InputField
size="full"
id="name"
name="name"
className="max-w-full"
label="Operation Name"
/>
<QuickAdd
onAdd={operation => {
if (operation.name !== 'unnamed') {
options.setValue('name', operation.name);
}
options.setValue('query', operation.query);
}}
/>
<CodeEditorField
id="query"
name="query"
label="Operation"
editorOptions={{
minLines: 10,
maxLines: 10,
showLineNumbers: true,
}}
/>
</div>
),
},
{
value: 'upload operation',
label: 'Upload Operation',
content: (
<div className="p-sm overflow-y-auto max-h-[calc(100vh-14rem)]">
<div>
<label className="flex items-center font-semibold text-muted mb-xs">
Upload GraphQL File
<ToolTip message=".graphql file with operations" />
</label>
<GraphQLFileUpload name="gqlFile" />
</div>
</div>
),
},
]}
/>
) : (
<Tabs
value={tabValue}
items={[
{
value: 'write operation',
label: 'Write Operation',
content: (
<div className="p-4">
<InputField
size="full"
id="name"
name="name"
label="Operation Name"
/>
<CodeEditorField
id="query"
name="query"
label="Operation"
editorOptions={{
minLines: 10,
maxLines: 10,
showLineNumbers: true,
}}
/>
</div>
),
},
]}
/>
)}
const {
methods: { setValue },
Form,
} = useConsoleForm({
schema,
options: {
defaultValues,
},
});
<Dialog.Footer
callToDeny="Cancel"
callToAction={callToAction}
onClose={onClose}
isLoading={isLoading}
/>
</>
</Dialog>
const handleOnSubmit: SubmitHandler<QueryCollectionOperation> =
async values => {
const data = { ...values };
if ((values as UploadedQueryPayload).gqlFile) {
const gqlFileValue = (values as UploadedQueryPayload).gqlFile;
(data as UploadedQueryPayload).gqlFile = parseQueryString(
await readFileAsync(gqlFileValue[0])
);
}}
}
onSubmit(data);
};
return (
<Form onSubmit={handleOnSubmit}>
<Dialog hasBackdrop title={title} onClose={onClose}>
<>
{title === 'Add Operation' ? (
<Tabs
value={tabValue}
onValueChange={value => {
handleTab();
if (
value === 'write operation' ||
value === 'upload operation'
) {
setValue('option', value);
}
}}
items={[
{
value: 'write operation',
label: 'Write Operation',
content: (
<div className="p-4">
<InputField
size="full"
id="name"
name="name"
className="max-w-full"
label="Operation Name"
/>
<QuickAdd
onAdd={operation => {
if (operation.name !== 'unnamed') {
setValue('name', operation.name);
}
setValue('query', operation.query);
}}
/>
<CodeEditorField
id="query"
name="query"
label="Operation"
editorOptions={{
minLines: 10,
maxLines: 10,
showLineNumbers: true,
}}
/>
</div>
),
},
{
value: 'upload operation',
label: 'Upload Operation',
content: (
<div className="p-sm overflow-y-auto max-h-[calc(100vh-14rem)]">
<div>
<InputField
size="full"
type="file"
id="gqlFile"
name="gqlFile"
label="Upload GraphQL File"
tooltip=".graphql file with operations"
/>
</div>
</div>
),
},
]}
/>
) : (
<Tabs
value={tabValue}
items={[
{
value: 'write operation',
label: 'Write Operation',
content: (
<div className="p-4">
<InputField
size="full"
id="name"
name="name"
label="Operation Name"
/>
<CodeEditorField
id="query"
name="query"
label="Operation"
editorOptions={{
minLines: 10,
maxLines: 10,
showLineNumbers: true,
}}
/>
</div>
),
},
]}
/>
)}
<Dialog.Footer
callToDeny="Cancel"
callToAction={callToAction}
onClose={onClose}
isLoading={isLoading}
/>
</>
</Dialog>
</Form>
);
};

View File

@ -126,7 +126,7 @@ export const QuickAdd = (props: QuickAddProps) => {
}, []);
return (
<div className="max-w-xl flex justify-end">
<div className="flex justify-end">
<DropdownMenu
options={{
content: {

View File

@ -8,6 +8,24 @@ export type NewDefinitionNode = DefinitionNode & {
};
};
export const readFileAsync = async (file: File | null): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = event => {
const content = event.target!.result as string;
resolve(content);
};
reader.onerror = event => {
reject(
Error(`File could not be read! Code ${event.target!.error!.code}`)
);
};
if (file) reader.readAsText(file);
});
};
export const readFile = (
file: File | null,
callback: (content: string) => void

View File

@ -1,5 +1,5 @@
import z from 'zod';
import { Form, InputField } from '@/new-components/Form';
import { SimpleForm, InputField } from '@/new-components/Form';
import React, { useEffect } from 'react';
import { useFormContext } from 'react-hook-form';
import { FaSearch } from 'react-icons/fa';
@ -33,12 +33,12 @@ const SearchInput: React.FC<QueryCollectionsOperationsSearchFormProps> = ({
export const QueryCollectionsOperationsSearchForm: React.FC<QueryCollectionsOperationsSearchFormProps> =
({ setSearch }) => {
return (
<Form
<SimpleForm
schema={schema}
onSubmit={() => {}}
className="pr-0 pt-0 pb-0 relative top-2"
>
{() => <SearchInput setSearch={setSearch} />}
</Form>
<SearchInput setSearch={setSearch} />
</SimpleForm>
);
};

View File

@ -5,7 +5,7 @@ import {
} from '@/features/MetadataAPI';
import { useFireNotification } from '@/new-components/Notifications';
import { DataTarget } from '@/features/Datasources';
import { InputField, Form } from '@/new-components/Form';
import { InputField, SimpleForm } from '@/new-components/Form';
import { Button } from '@/new-components/Button';
import { getMetadataQuery, MetadataQueryType } from '@/metadata/queryUtils';
import { FormElementDbToRs } from './FormElementDbToRs';
@ -114,42 +114,40 @@ export const DbToRsForm = ({
};
return (
<Form
<SimpleForm
schema={schema}
onSubmit={submit}
options={{ defaultValues }}
className="p-4"
>
{() => (
<div>
<div className="w-full sm:w-6/12 mb-md">
<div className="mb-md">
<InputField
id="relationshipName"
name="relationshipName"
label="Name"
placeholder="Relationship name"
dataTest="local-db-to-db-rel-name"
disabled={!!selectedRelationship}
/>
</div>
<div>
<div className="w-full sm:w-6/12 mb-md">
<div className="mb-md">
<InputField
id="relationshipName"
name="relationshipName"
label="Name"
placeholder="Relationship name"
dataTest="local-db-to-db-rel-name"
disabled={!!selectedRelationship}
/>
</div>
<FormElementDbToRs
sourceTableInfo={sourceTableInfo}
selectedRelationship={selectedRelationship}
/>
<Button
mode="primary"
type="submit"
isLoading={mutation.isLoading}
loadingText="Saving relationship"
data-test="add-local-db-relationship"
>
Save Relationship
</Button>
</div>
)}
</Form>
<FormElementDbToRs
sourceTableInfo={sourceTableInfo}
selectedRelationship={selectedRelationship}
/>
<Button
mode="primary"
type="submit"
isLoading={mutation.isLoading}
loadingText="Saving relationship"
data-test="add-local-db-relationship"
>
Save Relationship
</Button>
</div>
</SimpleForm>
);
};

View File

@ -1,11 +1,12 @@
import React from 'react';
import * as z from 'zod';
import { Story, Meta } from '@storybook/react';
import { Form } from '@/new-components/Form';
import { Meta, Story } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { SimpleForm } from '@/new-components/Form';
import {
refRemoteSchemaSelectorKey,
RefRsSelector,
RefRsSelectorProps,
refRemoteSchemaSelectorKey,
} from './RefRsSelector';
const defaultValues = {
@ -18,14 +19,14 @@ export default {
component: RefRsSelector,
decorators: [
StoryComponent => (
<Form
<SimpleForm
schema={z.any()}
onSubmit={o => console.log(o)}
onSubmit={action('onSubmit')}
options={{ defaultValues }}
className="p-4"
>
{() => <StoryComponent />}
</Form>
<StoryComponent />
</SimpleForm>
),
],
} as Meta;

View File

@ -2,7 +2,8 @@ import React from 'react';
import * as z from 'zod';
import { Story, Meta } from '@storybook/react';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import { Form } from '@/new-components/Form';
import { action } from '@storybook/addon-actions';
import { SimpleForm } from '@/new-components/Form';
import { Button } from '@/new-components/Button';
import { handlers } from '../../__mocks__';
import { RemoteDatabaseWidget } from './RemoteDatabaseWidget';
@ -20,19 +21,17 @@ export default {
decorators: [
ReactQueryDecorator(),
StoryComponent => (
<Form
<SimpleForm
schema={z.any()}
onSubmit={o => console.log(o)}
onSubmit={action('onSubmit')}
options={{ defaultValues }}
className="p-4"
>
{() => (
<div>
<StoryComponent />
<Button type="submit">Submit</Button>
</div>
)}
</Form>
<div>
<StoryComponent />
<Button type="submit">Submit</Button>
</div>
</SimpleForm>
),
],
parameters: {

View File

@ -1,11 +1,10 @@
import React from 'react';
import { Form } from '@/new-components/Form';
import { useFormContext } from 'react-hook-form';
import { useConsoleForm } from '@/new-components/Form';
import {
allowedMetadataTypes,
useMetadataMigration,
} from '@/features/MetadataAPI';
import { useFireNotification } from '@/new-components/Notifications';
import { IndicatorCard } from '@/new-components/IndicatorCard';
import { Button } from '@/new-components/Button';
@ -129,70 +128,70 @@ export const RemoteSchemaToDbForm = ({
});
};
const {
methods: { formState },
Form,
} = useConsoleForm({
schema,
});
return (
<Form
schema={schema}
options={{ defaultValues: {} }}
onSubmit={submit}
className="p-4"
>
{options => (
<>
<SetDefaults
sourceRemoteSchema={sourceRemoteSchema}
typeName={typeName}
existingRelationshipName={existingRelationshipName}
/>
<div className="grid border border-gray-300 rounded shadow-sm p-4">
<div className="flex items-center mb-md">
<Button type="button" size="sm" onClick={closeHandler}>
Cancel
</Button>
<span className="font-semibold ml-sm">
{existingRelationshipName
? 'Edit Relationship'
: 'Create New Relationship'}
</span>
</div>
<hr className="mb-md border-gray-300" />
{/* relationship meta */}
{existingRelationshipName ? null : (
<RelationshipTypeCardRadioGroup
value="remoteDB"
onChange={relModeHandler}
/>
)}
<FormElements
sourceRemoteSchema={sourceRemoteSchema}
existingRelationshipName={existingRelationshipName ?? ''}
/>
{/* submit */}
<div>
<Button
iconPosition="start"
mode="primary"
size="md"
type="submit"
isLoading={mutation.isLoading}
loadingText="Saving relationship"
data-test="add-rs-relationship"
>
{existingRelationshipName
? 'Edit Relationship'
: 'Add Relationship'}
</Button>
</div>
<Form onSubmit={submit} className="p-4">
<>
<SetDefaults
sourceRemoteSchema={sourceRemoteSchema}
typeName={typeName}
existingRelationshipName={existingRelationshipName}
/>
<div className="grid border border-gray-300 rounded shadow-sm p-4">
<div className="flex items-center mb-md">
<Button type="button" size="sm" onClick={closeHandler}>
Cancel
</Button>
<span className="font-semibold ml-sm">
{existingRelationshipName
? 'Edit Relationship'
: 'Create New Relationship'}
</span>
</div>
<hr className="mb-md border-gray-300" />
{!!Object.keys(options.formState.errors).length && (
<IndicatorCard status="negative">
Error saving relationship
</IndicatorCard>
{/* relationship meta */}
{existingRelationshipName ? null : (
<RelationshipTypeCardRadioGroup
value="remoteDB"
onChange={relModeHandler}
/>
)}
</>
)}
<FormElements
sourceRemoteSchema={sourceRemoteSchema}
existingRelationshipName={existingRelationshipName ?? ''}
/>
{/* submit */}
<div>
<Button
iconPosition="start"
mode="primary"
size="md"
type="submit"
isLoading={mutation.isLoading}
loadingText="Saving relationship"
data-test="add-rs-relationship"
>
{existingRelationshipName
? 'Edit Relationship'
: 'Add Relationship'}
</Button>
</div>
</div>
{!!Object.keys(formState.errors).length && (
<IndicatorCard status="negative">
Error saving relationship
</IndicatorCard>
)}
</>
</Form>
);
};

View File

@ -1,6 +1,6 @@
import React from 'react';
import { Form } from '@/new-components/Form';
import { useConsoleForm } from '@/new-components/Form';
import { IndicatorCard } from '@/new-components/IndicatorCard';
import { Button } from '@/new-components/Button';
import {
@ -11,8 +11,8 @@ import {
import { useFireNotification } from '@/new-components/Notifications';
import { useFormContext } from 'react-hook-form';
import {
RemoteRelOption,
RelationshipTypeCardRadioGroup,
RemoteRelOption,
} from './RelationshipTypeCardRadioGroup';
import { FormElements } from './FormElements';
@ -123,74 +123,74 @@ export const RemoteSchemaToRemoteSchemaForm = (
});
};
const {
methods: { formState },
Form,
} = useConsoleForm({
schema: rsToRsFormSchema,
});
return (
<Form
schema={rsToRsFormSchema}
options={{ defaultValues: {} }}
onSubmit={submit}
className="p-4"
>
{options => (
<>
<SetDefaults
<Form onSubmit={submit} className="p-4">
<>
<SetDefaults
sourceRemoteSchema={sourceRemoteSchema}
typeName={typeName}
existingRelationshipName={existingRelationshipName}
/>
<div className="grid border border-gray-300 rounded shadow-sm p-4 w-full">
<div className="flex items-center gap-4 w-full mb-md">
<Button type="button" size="sm" onClick={closeHandler}>
Cancel
</Button>
<p className="font-semibold m-0">
{existingRelationshipName
? 'Edit Relationship'
: 'Create New Relationship'}
</p>
</div>
<hr className="mb-md border-gray-300" />
{existingRelationshipName ? null : (
<RelationshipTypeCardRadioGroup
value="remoteSchema"
onChange={relModeHandler}
/>
)}
<FormElements
sourceRemoteSchema={sourceRemoteSchema}
typeName={typeName}
existingRelationshipName={existingRelationshipName}
/>
<div className="grid border border-gray-300 rounded shadow-sm p-4 w-full">
<div className="flex items-center gap-4 w-full mb-md">
<Button type="button" size="sm" onClick={closeHandler}>
Cancel
</Button>
<p className="font-semibold m-0">
{existingRelationshipName
? 'Edit Relationship'
: 'Create New Relationship'}
</p>
</div>
<hr className="mb-md border-gray-300" />
{existingRelationshipName ? null : (
<RelationshipTypeCardRadioGroup
value="remoteSchema"
onChange={relModeHandler}
/>
)}
<FormElements
sourceRemoteSchema={sourceRemoteSchema}
existingRelationshipName={existingRelationshipName}
/>
{/* submit */}
<div>
<Button
mode="primary"
size="md"
type="submit"
isLoading={mutation.isLoading}
loadingText={
existingRelationshipName
? 'Updating relationship'
: 'Creating relationship'
}
data-test="add-rs-relationship"
>
{existingRelationshipName
? 'Edit Relationship'
: 'Add Relationship'}
</Button>
</div>
{!!Object.keys(options.formState.errors).length && (
<IndicatorCard status="negative">
Error saving relationship
</IndicatorCard>
)}
{/* submit */}
<div>
<Button
mode="primary"
size="md"
type="submit"
isLoading={mutation.isLoading}
loadingText={
existingRelationshipName
? 'Updating relationship'
: 'Creating relationship'
}
data-test="add-rs-relationship"
>
{existingRelationshipName
? 'Edit Relationship'
: 'Add Relationship'}
</Button>
</div>
</>
)}
{!!Object.keys(formState.errors).length && (
<IndicatorCard status="negative">
Error saving relationship
</IndicatorCard>
)}
</div>
</>
</Form>
);
};

View File

@ -1,11 +1,12 @@
import React from 'react';
import * as z from 'zod';
import { Story, Meta } from '@storybook/react';
import { Meta, Story } from '@storybook/react';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import { Form } from '@/new-components/Form';
import { action } from '@storybook/addon-actions';
import { SimpleForm } from '@/new-components/Form';
import {
handlers,
customer_columns,
handlers,
remote_rel_definition,
} from '../../__mocks__';
@ -30,14 +31,14 @@ export default {
decorators: [
ReactQueryDecorator(),
StoryComponent => (
<Form
<SimpleForm
schema={z.any()}
onSubmit={() => {}}
onSubmit={action('onSubmit')}
options={{ defaultValues }}
className="p-4"
>
{() => <StoryComponent />}
</Form>
<StoryComponent />
</SimpleForm>
),
],
parameters: {

View File

@ -1,12 +1,13 @@
import React from 'react';
import * as z from 'zod';
import { Story, Meta } from '@storybook/react';
import { Form } from '@/new-components/Form';
import { action } from '@storybook/addon-actions';
import { Meta, Story } from '@storybook/react';
import { SimpleForm } from '@/new-components/Form';
import { Button } from '@/new-components/Button';
import {
remoteSchemaSelectorKey,
RsSourceTypeSelector,
RsSourceTypeSelectorProps,
remoteSchemaSelectorKey,
} from './RsSourceTypeSelector';
const defaultValues = {
@ -19,19 +20,17 @@ export default {
component: RsSourceTypeSelector,
decorators: [
StoryComponent => (
<Form
<SimpleForm
schema={z.any()}
onSubmit={o => console.log(o)}
onSubmit={action('onSubmit')}
options={{ defaultValues }}
className="p-4"
>
{() => (
<div>
<StoryComponent />
<Button type="submit">Submit</Button>
</div>
)}
</Form>
<div>
<StoryComponent />
<Button type="submit">Submit</Button>
</div>
</SimpleForm>
),
],
} as Meta;

View File

@ -1,7 +1,7 @@
import { useMetadataMigration } from '@/features/MetadataAPI';
import { Button } from '@/new-components/Button';
import { FieldError } from 'react-hook-form';
import { Form, InputField } from '@/new-components/Form';
import { InputField, useConsoleForm } from '@/new-components/Form';
import { useFireNotification } from '@/new-components/Notifications';
import { IconTooltip } from '@/new-components/Tooltip';
import get from 'lodash.get';
@ -74,356 +74,340 @@ export const Create = ({ onSuccess }: Props) => {
const [openCustomizationWidget, setOpenCustomizationWidget] = useState(false);
const {
methods: { formState, register },
Form,
} = useConsoleForm({
schema,
options: {
defaultValues,
},
});
const queryRootError = get(formState.errors, 'customization.query_root') as
| FieldError
| undefined;
const mutationRootError = get(
formState.errors,
'customization.mutation_root'
) as FieldError | undefined;
return (
<Form
schema={schema}
onSubmit={onSubmit}
options={{ defaultValues }}
className="overflow-y-hidden p-4"
>
{options => {
const queryRootError = get(
options.formState.errors,
'customization.query_root'
) as FieldError | undefined;
const mutationRootError = get(
options.formState.errors,
'customization.mutation_root'
) as FieldError | undefined;
return (
<Analytics name="AddRemoteSchema" {...REDACT_EVERYTHING}>
<div className="max-w-6xl">
<h1 className="text-xl leading-6 font-semibold mb-lg">
Add Remote Schema
</h1>
<div className="mb-md w-6/12">
<InputField
name="name"
label="Remote Schema Name"
placeholder="Name..."
tooltip="give this GraphQL schema a friendly name"
/>
</div>
<div className="mb-md w-6/12">
<InputField
name="comment"
label="Comment / Description"
placeholder="Comment / Description..."
tooltip="A statement to help describe the remote schema in brief"
/>
</div>
<GraphQLServiceUrl />
<div className="mb-lg w-4/12">
<label className="block flex items-center text-gray-600 font-semibold mb-xs">
GraphQL Server Timeout
<IconTooltip message="Configure timeout for your remote GraphQL server. Defaults to 60 seconds." />
</label>
<div className="relative w-full">
<input
type="text"
className="font-normal block w-full shadow-sm rounded pr-10 border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="60"
{...options.register('timeout_seconds')}
data-testid="timeout_seconds"
/>
<div className="absolute inset-y-0 right-0 pr-3 flex text-gray-400 items-center pointer-events-none">
Seconds
</div>
</div>
</div>
<div className="mb-lg w-8/12">
<h2 className="text-lg font-semibold text-gray-600 ">
Headers
</h2>
<div className="items-center mr-sm mb-sm my-sm flex">
<input
{...options.register('forward_client_headers')}
type="checkbox"
className="mr-sm border-gray-400 rounded focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-400"
value="true"
data-testid="forward_client_headers"
/>
<label className="pl-3 flex items-center mt-2">
Forward all headers from client
<IconTooltip
message="Toggle forwarding headers sent by the client app in the request to
your remote GraphQL server"
/>
</label>
</div>
<div>
<label className="flex items-center mb-xs">
Additional headers:
<IconTooltip message="Custom headers to be sent to the remote GraphQL server" />
</label>
</div>
<RequestHeadersSelector
name="headers"
addButtonText="Add additional headers"
/>
</div>
<div className="mb-lg w-8/12">
<h2 className="text-lg font-semibold flex items-center">
GraphQL Customizations
</h2>
<p className="text-sm text-gray-600 mb-sm">
Individual Types and Fields will be editable after saving.
<br />
<a href="https://spec.graphql.org/June2018/#example-e2969">
Read more
</a>{' '}
about Type and Field naming conventions in the official
GraphQL spec
</p>
{openCustomizationWidget ? (
<div className="w-full rounded border bg-white border-gray-300 p-4">
<Button
type="button"
size="sm"
onClick={() => setOpenCustomizationWidget(false)}
>
Close
</Button>
<div className="grid gap-3 grid-cols-12 mb-md">
<div className="flex items-center col-span-4">
<label className="flex items-center text-gray-600 font-medium">
Root Field Namespace
<IconTooltip message="Root field type names will be prefixed by this name." />
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="namespace_"
{...options.register(
'customization.root_fields_namespace'
)}
data-testid="customization.root_fields_namespace"
/>
</div>
</div>
<h2 className="text-lg font-semibold mb-xs items-center flex">
Types
<IconTooltip message="add a prefix / suffix to all types of the remote schema" />
</h2>
<div className="grid gap-3 grid-cols-12 mb-md">
<div className="flex items-center col-span-4">
<label className="block text-gray-600 font-medium">
Prefix
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="prefix_"
{...options.register('customization.type_prefix')}
data-testid="customization.type_prefix"
/>
</div>
</div>
<div className="grid gap-3 grid-cols-12 mb-md">
<div className="flex items-center col-span-4">
<label className="block text-gray-600 font-medium">
Suffix
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="_suffix"
{...options.register('customization.type_suffix')}
data-testid="customization.type_suffix"
/>
</div>
</div>
<h2 className="text-lg font-semibold mb-xs flex items-center">
Fields
<IconTooltip message="add a prefix / suffix to the fields of the query / mutation root fields" />
</h2>
<h3 className="font-semibold mb-xs text-gray-600 text-lg">
Query root
</h3>
{queryRootError?.message && (
<div
role="alert"
aria-label={queryRootError.message}
className="mt-xs text-red-600 flex items-center"
>
<FaExclamationCircle className="fill-current h-4 mr-xs" />
{queryRootError.message}
</div>
)}
<div className="grid gap-3 grid-cols-12 mb-md">
<div className="flex items-center col-span-4">
<label className="block text-gray-600 font-medium">
Type Name
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="Query/query_root"
{...options.register(
'customization.query_root.parent_type'
)}
data-testid="customization.query_root.parent_type"
/>
</div>
</div>
<div className="grid gap-3 grid-cols-12 mb-md">
<div className="flex items-center col-span-4">
<label className="block text-gray-600 font-medium">
Prefix
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="prefix_"
{...options.register(
'customization.query_root.prefix'
)}
data-testid="customization.query_root.prefix"
/>
</div>
</div>
<div className="grid gap-3 grid-cols-12 mb-md">
<div className="flex items-center col-span-4">
<label className="block text-gray-600 font-medium">
Suffix
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="_suffix"
{...options.register(
'customization.query_root.suffix'
)}
data-testid="customization.query_root.suffix"
/>
</div>
</div>
<h3 className="font-semibold mb-xs text-gray-600 text-lg">
Mutation root
</h3>
{mutationRootError?.message && (
<div
role="alert"
aria-label={mutationRootError.message}
className="mt-xs text-red-600 flex items-center"
>
<FaExclamationCircle className="fill-current h-4 mr-xs" />
{mutationRootError.message}
</div>
)}
<div className="grid gap-3 grid-cols-12 mb-md">
<div className="flex items-center col-span-4">
<label className="block text-gray-600 font-medium">
Type Name
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="Mutation/mutation_root"
{...options.register(
'customization.mutation_root.parent_type'
)}
data-testid="customization.mutation_root.parent_type"
/>
</div>
</div>
<div className="grid gap-3 grid-cols-12 mb-md">
<div className="flex items-center col-span-4">
<label className="block text-gray-600 font-medium">
Prefix
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="prefix_"
{...options.register(
'customization.mutation_root.prefix'
)}
data-testid="customization.mutation_root.prefix"
/>
</div>
</div>
<div className="grid gap-3 grid-cols-12">
<div className="flex items-center col-span-4">
<label className="block text-gray-600 font-medium">
Suffix
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="_suffix"
{...options.register(
'customization.mutation_root.suffix'
)}
data-testid="customization.mutation_root.suffix"
/>
</div>
</div>
</div>
) : (
<Button
icon={<FaPlusCircle />}
type="button"
size="sm"
onClick={() => setOpenCustomizationWidget(true)}
data-testid="open_customization"
>
Add GQL Customization
</Button>
)}
</div>
<div className="flex items-center mb-lg">
<Analytics
name="remote-schema-tab-button-create-remote-schema"
passHtmlAttributesToChildren
>
<Button
type="submit"
data-testid="submit"
mode="primary"
isLoading={mutation.isLoading}
>
Add Remote Schema
</Button>
</Analytics>
<Form onSubmit={onSubmit} className="overflow-y-hidden p-4">
<Analytics name="AddRemoteSchema" {...REDACT_EVERYTHING}>
<div className="max-w-6xl">
<h1 className="text-xl leading-6 font-semibold mb-lg">
Add Remote Schema
</h1>
<div className="mb-md w-6/12">
<InputField
name="name"
label="Remote Schema Name"
placeholder="Name..."
tooltip="give this GraphQL schema a friendly name"
/>
</div>
<div className="mb-md w-6/12">
<InputField
name="comment"
label="Comment / Description"
placeholder="Comment / Description..."
tooltip="A statement to help describe the remote schema in brief"
/>
</div>
<GraphQLServiceUrl />
<div className="mb-lg w-4/12">
<label className="block flex items-center text-gray-600 font-semibold mb-xs">
GraphQL Server Timeout
<IconTooltip message="Configure timeout for your remote GraphQL server. Defaults to 60 seconds." />
</label>
<div className="relative w-full">
<input
type="text"
className="font-normal block w-full shadow-sm rounded pr-10 border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="60"
{...register('timeout_seconds')}
data-testid="timeout_seconds"
/>
<div className="absolute inset-y-0 right-0 pr-3 flex text-gray-400 items-center pointer-events-none">
Seconds
</div>
</div>
</Analytics>
);
}}
</div>
<div className="mb-lg w-8/12">
<h2 className="text-lg font-semibold text-gray-600 ">Headers</h2>
<div className="items-center mr-sm mb-sm my-sm flex">
<input
{...register('forward_client_headers')}
type="checkbox"
className="mr-sm border-gray-400 rounded focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-400"
value="true"
data-testid="forward_client_headers"
/>
<label className="pl-3 flex items-center mt-2">
Forward all headers from client
<IconTooltip
message="Toggle forwarding headers sent by the client app in the request to
your remote GraphQL server"
/>
</label>
</div>
<div>
<label className="flex items-center mb-xs">
Additional headers:
<IconTooltip message="Custom headers to be sent to the remote GraphQL server" />
</label>
</div>
<RequestHeadersSelector
name="headers"
addButtonText="Add additional headers"
/>
</div>
<div className="mb-lg w-8/12">
<h2 className="text-lg font-semibold flex items-center">
GraphQL Customizations
</h2>
<p className="text-sm text-gray-600 mb-sm">
Individual Types and Fields will be editable after saving.
<br />
<a href="https://spec.graphql.org/June2018/#example-e2969">
Read more
</a>{' '}
about Type and Field naming conventions in the official GraphQL
spec
</p>
{openCustomizationWidget ? (
<div className="w-full rounded border bg-white border-gray-300 p-4">
<Button
type="button"
size="sm"
onClick={() => setOpenCustomizationWidget(false)}
>
Close
</Button>
<div className="grid gap-3 grid-cols-12 mb-md">
<div className="flex items-center col-span-4">
<label className="flex items-center text-gray-600 font-medium">
Root Field Namespace
<IconTooltip message="Root field type names will be prefixed by this name." />
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="namespace_"
{...register('customization.root_fields_namespace')}
data-testid="customization.root_fields_namespace"
/>
</div>
</div>
<h2 className="text-lg font-semibold mb-xs items-center flex">
Types
<IconTooltip message="add a prefix / suffix to all types of the remote schema" />
</h2>
<div className="grid gap-3 grid-cols-12 mb-md">
<div className="flex items-center col-span-4">
<label className="block text-gray-600 font-medium">
Prefix
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="prefix_"
{...register('customization.type_prefix')}
data-testid="customization.type_prefix"
/>
</div>
</div>
<div className="grid gap-3 grid-cols-12 mb-md">
<div className="flex items-center col-span-4">
<label className="block text-gray-600 font-medium">
Suffix
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="_suffix"
{...register('customization.type_suffix')}
data-testid="customization.type_suffix"
/>
</div>
</div>
<h2 className="text-lg font-semibold mb-xs flex items-center">
Fields
<IconTooltip message="add a prefix / suffix to the fields of the query / mutation root fields" />
</h2>
<h3 className="font-semibold mb-xs text-gray-600 text-lg">
Query root
</h3>
{queryRootError?.message && (
<div
role="alert"
aria-label={queryRootError.message}
className="mt-xs text-red-600 flex items-center"
>
<FaExclamationCircle className="fill-current h-4 mr-xs" />
{queryRootError.message}
</div>
)}
<div className="grid gap-3 grid-cols-12 mb-md">
<div className="flex items-center col-span-4">
<label className="block text-gray-600 font-medium">
Type Name
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="Query/query_root"
{...register('customization.query_root.parent_type')}
data-testid="customization.query_root.parent_type"
/>
</div>
</div>
<div className="grid gap-3 grid-cols-12 mb-md">
<div className="flex items-center col-span-4">
<label className="block text-gray-600 font-medium">
Prefix
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="prefix_"
{...register('customization.query_root.prefix')}
data-testid="customization.query_root.prefix"
/>
</div>
</div>
<div className="grid gap-3 grid-cols-12 mb-md">
<div className="flex items-center col-span-4">
<label className="block text-gray-600 font-medium">
Suffix
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="_suffix"
{...register('customization.query_root.suffix')}
data-testid="customization.query_root.suffix"
/>
</div>
</div>
<h3 className="font-semibold mb-xs text-gray-600 text-lg">
Mutation root
</h3>
{mutationRootError?.message && (
<div
role="alert"
aria-label={mutationRootError.message}
className="mt-xs text-red-600 flex items-center"
>
<FaExclamationCircle className="fill-current h-4 mr-xs" />
{mutationRootError.message}
</div>
)}
<div className="grid gap-3 grid-cols-12 mb-md">
<div className="flex items-center col-span-4">
<label className="block text-gray-600 font-medium">
Type Name
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="Mutation/mutation_root"
{...register('customization.mutation_root.parent_type')}
data-testid="customization.mutation_root.parent_type"
/>
</div>
</div>
<div className="grid gap-3 grid-cols-12 mb-md">
<div className="flex items-center col-span-4">
<label className="block text-gray-600 font-medium">
Prefix
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="prefix_"
{...register('customization.mutation_root.prefix')}
data-testid="customization.mutation_root.prefix"
/>
</div>
</div>
<div className="grid gap-3 grid-cols-12">
<div className="flex items-center col-span-4">
<label className="block text-gray-600 font-medium">
Suffix
</label>
</div>
<div className="col-span-8">
<input
type="text"
className="font-normal w-full block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="_suffix"
{...register('customization.mutation_root.suffix')}
data-testid="customization.mutation_root.suffix"
/>
</div>
</div>
</div>
) : (
<Button
icon={<FaPlusCircle />}
type="button"
size="sm"
onClick={() => setOpenCustomizationWidget(true)}
data-testid="open_customization"
>
Add GQL Customization
</Button>
)}
</div>
<div className="flex items-center mb-lg">
<Analytics
name="remote-schema-tab-button-create-remote-schema"
passHtmlAttributesToChildren
>
<Button
type="submit"
data-testid="submit"
mode="primary"
isLoading={mutation.isLoading}
>
Add Remote Schema
</Button>
</Analytics>
</div>
</div>
</Analytics>
</Form>
);
};

View File

@ -2,9 +2,8 @@ import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { UseFormReturn } from 'react-hook-form';
import { z } from 'zod';
import { Form, Checkbox } from '@/new-components/Form';
import { SimpleForm, Checkbox, useConsoleForm } from '@/new-components/Form';
export default {
title: 'components/Forms 📁/Checkbox 🧬',
@ -14,7 +13,7 @@ export default {
description: {
component: `A component wrapping native \`<checkbox>\` element ([see MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/checkbox)),
its description, hint and error message.<br>
Default CSS display is \`block\`, provided without padding and margin (displayed here with the \`<Form>\` padding).`,
Default CSS display is \`block\`, provided without padding and margin (displayed here with the \`<SimpleForm>\` padding).`,
},
source: { type: 'code' },
},
@ -25,9 +24,9 @@ export const ApiPlayground: ComponentStory<typeof Checkbox> = args => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => <Checkbox {...args} />}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Checkbox {...args} />
</SimpleForm>
);
};
ApiPlayground.storyName = '⚙️ API';
@ -51,15 +50,13 @@ export const Basic: ComponentStory<typeof Checkbox> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Checkbox
name="checkboxNames"
label="The checkbox label"
options={options}
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Checkbox
name="checkboxNames"
label="The checkbox label"
options={options}
/>
</SimpleForm>
);
};
Basic.storyName = '🧰 Basic';
@ -79,23 +76,21 @@ export const VariantOrientation: ComponentStory<typeof Checkbox> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<>
<Checkbox
name="checkboxNames"
label="The checkbox label"
options={options}
/>
<Checkbox
name="checkboxNames"
label="The checkbox label"
options={options}
orientation="horizontal"
/>
</>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<>
<Checkbox
name="checkboxNames"
label="The checkbox label"
options={options}
/>
<Checkbox
name="checkboxNames"
label="The checkbox label"
options={options}
orientation="horizontal"
/>
</>
</SimpleForm>
);
};
VariantOrientation.storyName = '🎭 Variant - Orientation';
@ -115,16 +110,14 @@ export const VariantWithDescription: ComponentStory<typeof Checkbox> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Checkbox
name="checkboxNames"
label="The checkbox label"
description="Checkbox description"
options={options}
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Checkbox
name="checkboxNames"
label="The checkbox label"
description="Checkbox description"
options={options}
/>
</SimpleForm>
);
};
VariantWithDescription.storyName = '🎭 Variant - With description';
@ -144,16 +137,14 @@ export const VariantWithTooltip: ComponentStory<typeof Checkbox> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Checkbox
name="checkboxNames"
label="The checkbox label"
tooltip="Checkbox tooltip"
options={options}
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Checkbox
name="checkboxNames"
label="The checkbox label"
tooltip="Checkbox tooltip"
options={options}
/>
</SimpleForm>
);
};
VariantWithTooltip.storyName = '🎭 Variant - With tooltip';
@ -175,26 +166,24 @@ export const StateWithDefaultValue: ComponentStory<typeof Checkbox> = () => {
const validationSchema = z.object({});
return (
<Form
<SimpleForm
schema={validationSchema}
options={{ defaultValues }}
onSubmit={action('onSubmit')}
>
{() => (
<Checkbox
name="checkboxNames"
label="The checkbox label"
options={options}
/>
)}
</Form>
<Checkbox
name="checkboxNames"
label="The checkbox label"
options={options}
/>
</SimpleForm>
);
};
StateWithDefaultValue.storyName = '🔁 State - With default value';
StateWithDefaultValue.parameters = {
docs: {
description: {
story: `Use \`<Form>\` options to set default value.`,
story: `Use \`<SimpleForm>\` options to set default value.`,
},
source: { state: 'open' },
},
@ -210,19 +199,22 @@ export const StateLoading: ComponentStory<typeof Checkbox> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Checkbox
name="checkboxNames"
label="The checkbox label"
options={options}
loading
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Checkbox
name="checkboxNames"
label="The checkbox label"
options={options}
loading
/>
</SimpleForm>
);
};
StateLoading.storyName = '🔁 State - Loading';
StateLoading.parameters = {
docs: {
source: { state: 'open' },
},
};
export const StateDisabled: ComponentStory<typeof Checkbox> = () => {
const options = [
@ -234,46 +226,53 @@ export const StateDisabled: ComponentStory<typeof Checkbox> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Checkbox
name="checkboxNames"
label="The checkbox label"
options={options}
disabled
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Checkbox
name="checkboxNames"
label="The checkbox label"
options={options}
disabled
/>
</SimpleForm>
);
};
StateDisabled.storyName = '🔁 State - Disabled';
StateDisabled.parameters = {
docs: {
source: { state: 'open' },
},
};
export const StateWithErrorMessage: ComponentStory<typeof Checkbox> = () => {
const formRef = React.useRef<UseFormReturn>();
React.useEffect(() => {
formRef?.current?.trigger();
});
const options = [
{ value: 'value0', label: 'Value 0' },
{ value: 'value1', label: 'Value 1', disabled: true },
{ value: 'value2', label: 'Value 2' },
];
const validationSchema = z.object({
const schema = z.object({
checkboxNames: z.enum(['value0', 'value1']),
});
const {
methods: { trigger },
Form,
} = useConsoleForm({
schema,
});
React.useEffect(() => {
// Use useEffect hook to wait for the form to be rendered before triggering validation
trigger();
});
return (
<Form ref={formRef} schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Checkbox
name="checkboxNames"
label="The checkbox label"
options={options}
/>
)}
<Form onSubmit={action('onSubmit')}>
<Checkbox
name="checkboxNames"
label="The checkbox label"
options={options}
/>
</Form>
);
};
@ -281,7 +280,7 @@ StateWithErrorMessage.storyName = '🔁 State - With error message';
StateWithErrorMessage.parameters = {
docs: {
description: {
story: `Incorrect value is set then \`<Form>\` validation is automatically triggered.`,
story: `Incorrect value is set then \`<SimpleForm>\` validation is automatically triggered.`,
},
source: { state: 'open' },
},
@ -302,17 +301,15 @@ export const TestingScalability: ComponentStory<typeof Checkbox> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Checkbox
name="checkboxNames"
label="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
tooltip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
options={options}
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Checkbox
name="checkboxNames"
label="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
tooltip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
options={options}
/>
</SimpleForm>
);
};
TestingScalability.storyName = '🧪 Testing - Scalability';

View File

@ -2,9 +2,12 @@ import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { UseFormReturn } from 'react-hook-form';
import { z } from 'zod';
import { Form, CodeEditorField } from '@/new-components/Form';
import {
SimpleForm,
CodeEditorField,
useConsoleForm,
} from '@/new-components/Form';
export default {
title: 'components/Forms 📁/CodeEditorField 🧬',
@ -13,7 +16,7 @@ export default {
docs: {
description: {
component: `A component wrapping an Ace editor ([see Docs](https://ace.c9.io/)),<br>
Default CSS display is \`block\`, provided without padding and margin (displayed here with the \`<Form>\` padding).`,
Default CSS display is \`block\`, provided without padding and margin (displayed here with the \`<SimpleForm>\` padding).`,
},
source: { type: 'code' },
},
@ -24,9 +27,9 @@ export const ApiPlayground: ComponentStory<typeof CodeEditorField> = args => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => <CodeEditorField {...args} />}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<CodeEditorField {...args} />
</SimpleForm>
);
};
ApiPlayground.storyName = '⚙️ API';
@ -39,14 +42,12 @@ export const Basic: ComponentStory<typeof CodeEditorField> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
/>
</SimpleForm>
);
};
Basic.storyName = '🧰 Basic';
@ -61,15 +62,13 @@ export const VariantWithDescription: ComponentStory<typeof CodeEditorField> =
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
description="CodeEditorField description"
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
description="CodeEditorField description"
/>
</SimpleForm>
);
};
VariantWithDescription.storyName = '🎭 Variant - With description';
@ -84,15 +83,13 @@ export const VariantWithTooltip: ComponentStory<typeof CodeEditorField> =
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
tooltip="CodeEditorField tooltip"
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
tooltip="CodeEditorField tooltip"
/>
</SimpleForm>
);
};
VariantWithTooltip.storyName = '🎭 Variant - With tooltip';
@ -106,15 +103,13 @@ export const VariantSizeFull: ComponentStory<typeof CodeEditorField> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
size="full"
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
size="full"
/>
</SimpleForm>
);
};
VariantSizeFull.storyName = '🎭 Variant - Size full';
@ -128,15 +123,13 @@ export const VariantSizeMedium: ComponentStory<typeof CodeEditorField> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
size="medium"
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
size="medium"
/>
</SimpleForm>
);
};
VariantSizeMedium.storyName = '🎭 Variant - Size medium';
@ -153,25 +146,23 @@ export const StateWithDefaultValue: ComponentStory<typeof CodeEditorField> =
const validationSchema = z.object({});
return (
<Form
<SimpleForm
schema={validationSchema}
options={{ defaultValues }}
onSubmit={action('onSubmit')}
>
{() => (
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
/>
)}
</Form>
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
/>
</SimpleForm>
);
};
StateWithDefaultValue.storyName = '🔁 State - With default value';
StateWithDefaultValue.parameters = {
docs: {
description: {
story: `Use \`<Form>\` options to set default value.`,
story: `Use \`<SimpleForm>\` options to set default value.`,
},
source: { state: 'open' },
},
@ -183,19 +174,17 @@ export const StateLoading: ComponentStory<typeof CodeEditorField> = () => {
const validationSchema = z.object({});
return (
<Form
<SimpleForm
schema={validationSchema}
options={{ defaultValues }}
onSubmit={action('onSubmit')}
>
{() => (
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
loading
/>
)}
</Form>
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
loading
/>
</SimpleForm>
);
};
StateLoading.storyName = '🔁 State - Loading';
@ -211,19 +200,17 @@ export const StateDisabled: ComponentStory<typeof CodeEditorField> = () => {
const validationSchema = z.object({});
return (
<Form
<SimpleForm
schema={validationSchema}
options={{ defaultValues }}
onSubmit={action('onSubmit')}
>
{() => (
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
disabled
/>
)}
</Form>
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
disabled
/>
</SimpleForm>
);
};
StateDisabled.storyName = '🔁 State - Disabled';
@ -235,28 +222,28 @@ StateDisabled.parameters = {
export const StateWithErrorMessage: ComponentStory<typeof CodeEditorField> =
() => {
const formRef = React.useRef<UseFormReturn>();
React.useEffect(() => {
formRef?.current?.trigger();
});
const validationSchema = z.object({
const schema = z.object({
codeEditorFieldName: z.enum(['value0', 'value1']),
});
const {
methods: { trigger },
Form,
} = useConsoleForm({
schema,
});
React.useEffect(() => {
// Use useEffect hook to wait for the form to be rendered before triggering validation
trigger();
});
return (
<Form
ref={formRef}
schema={validationSchema}
onSubmit={action('onSubmit')}
>
{() => (
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
/>
)}
<Form onSubmit={action('onSubmit')}>
<CodeEditorField
name="codeEditorFieldName"
label="The codeEditor label"
/>
</Form>
);
};
@ -264,7 +251,7 @@ StateWithErrorMessage.storyName = '🔁 State - With error message';
StateWithErrorMessage.parameters = {
docs: {
description: {
story: `Incorrect value is set then \`<Form>\` validation is automatically triggered.`,
story: `Incorrect value is set then \`<SimpleForm>\` validation is automatically triggered.`,
},
source: { state: 'open' },
},
@ -275,16 +262,14 @@ export const TestingScalability: ComponentStory<typeof CodeEditorField> =
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<CodeEditorField
name="codeEditorFieldName"
label="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
tooltip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<CodeEditorField
name="codeEditorFieldName"
label="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
tooltip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
/>
</SimpleForm>
);
};
TestingScalability.storyName = '🧪 Testing - Scalability';

View File

@ -186,14 +186,8 @@ export const CodeEditorField: React.FC<CodeEditorFieldProps> = ({
);
}}
/>
{/* This is to let form errors to appear below the tip */}
{tipState === 'ANY' && (
<div className="bg-legacybg top-full left-1 text-gray-600 text-sm mt-1">
{' '}
</div>
)}
{tipState === 'ESC' && (
<div className="bg-legacybg top-full left-1 text-gray-600 text-sm mt-1">
<div className="absolute bg-legacybg top-full pl-1 text-gray-600 text-sm mt-1">
Tip:{' '}
<strong>
Press <em>Esc</em> key
@ -202,7 +196,7 @@ export const CodeEditorField: React.FC<CodeEditorFieldProps> = ({
</div>
)}
{tipState === 'TAB' && (
<div className="bg-legacybg top-full left-1 text-gray-600 text-sm mt-1">
<div className="absolute bg-legacybg top-full pl-1 text-gray-600 text-sm mt-1">
Tip: Press <em>Esc</em> key then{' '}
<strong>
navigate with <em>Tab</em>

View File

@ -21,7 +21,7 @@ export default {
component: `A utility component wrapping all needed elements to build form fields: **label**, **description** and **error message**.
The wrapped field is added in a dedicated **slot** used to build form fields components usable in forms .
Default CSS display is \`block\`, provided without padding and margin (displayed here with the \`<Form>\` padding).`,
Default CSS display is \`block\`, provided without padding and margin (displayed here with the \`<SimpleForm>\` padding).`,
},
source: { type: 'code' },
},
@ -104,62 +104,6 @@ VariantWithDescriptionAndTooltip.parameters = {
},
};
export const VariantHorizontalWithDescription: ComponentStory<
typeof FieldWrapper
> = () => (
<FieldWrapper
label="The field wrapper label"
description="The field wrapper description"
horizontal
>
<ChildrenExample />
</FieldWrapper>
);
VariantHorizontalWithDescription.storyName =
'🎭 Variant - Horizontal with description';
VariantHorizontalWithDescription.parameters = {
docs: {
source: { state: 'open' },
},
};
export const VariantHorizontalWithTooltip: ComponentStory<typeof FieldWrapper> =
() => (
<FieldWrapper
label="The field wrapper label"
tooltip="The field wrapper tooltip"
horizontal
>
<ChildrenExample />
</FieldWrapper>
);
VariantHorizontalWithTooltip.storyName = '🎭 Variant - Horizontal with tooltip';
VariantHorizontalWithTooltip.parameters = {
docs: {
source: { state: 'open' },
},
};
export const VariantHorizontalWithDescriptionAndTooltip: ComponentStory<
typeof FieldWrapper
> = () => (
<FieldWrapper
label="The field wrapper label"
description="The field wrapper description"
tooltip="The field wrapper tooltip"
horizontal
>
<ChildrenExample />
</FieldWrapper>
);
VariantHorizontalWithDescriptionAndTooltip.storyName =
'🎭 Variant - Horizontal with description and tooltip';
VariantHorizontalWithDescriptionAndTooltip.parameters = {
docs: {
source: { state: 'open' },
},
};
export const StateLoading: ComponentStory<typeof FieldWrapper> = () => (
<FieldWrapper
label="The field wrapper label"
@ -192,30 +136,7 @@ StateWithErrorMessage.storyName = '🔁 State - With error message';
StateWithErrorMessage.parameters = {
docs: {
description: {
story: `Incorrect value is set then \`<Form>\` validation is automatically triggered.`,
},
source: { state: 'open' },
},
};
export const StateHorizontalWithErrorMessage: ComponentStory<
typeof FieldWrapper
> = () => (
<FieldWrapper
label="The field wrapper label"
description="The field wrapper description"
tooltip="The field wrapper tooltip"
error={{ message: 'The error message', type: 'error' }}
>
<ChildrenExample />
</FieldWrapper>
);
StateHorizontalWithErrorMessage.storyName =
'🔁 State - Horizontal with error message';
StateHorizontalWithErrorMessage.parameters = {
docs: {
description: {
story: `Incorrect value is set then \`<Form>\` validation is automatically triggered.`,
story: `Incorrect value is set then \`<SimpleForm>\` validation is automatically triggered.`,
},
source: { state: 'open' },
},

View File

@ -44,10 +44,6 @@ type FieldWrapperProps = {
* The field tooltip label
*/
tooltip?: React.ReactNode;
/**
* Flag indicating wheteher the field is horizontally aligned
*/
horizontal?: boolean;
/**
* The field data test id for testing
*/
@ -107,7 +103,6 @@ export const FieldWrapper = (props: FieldWrapperProps) => {
children,
description,
tooltip,
horizontal,
loading,
noErrorPlaceholder = false,
renderDescriptionLineBreaks = false,
@ -146,19 +141,8 @@ export const FieldWrapper = (props: FieldWrapperProps) => {
if (label) {
FieldLabel = () => (
<label
htmlFor={id}
className={clsx(
'block pt-1 text-gray-600 mb-xs',
horizontal && 'pr-8 flex-grow220px'
)}
>
<span
className={clsx(
'flex items-center',
horizontal ? 'text-muted' : 'font-semibold'
)}
>
<label htmlFor={id} className={clsx('block pt-1 text-gray-600 mb-xs')}>
<span className={clsx('flex items-center font-semibold')}>
<span className={loading ? 'relative' : ''}>
<FieldLabelIcon />
{label}
@ -191,15 +175,11 @@ export const FieldWrapper = (props: FieldWrapperProps) => {
className={clsx(
className,
size === 'medium' ? 'w-1/2' : 'w-full',
horizontal
? 'flex flex-row flex-wrap w-full max-w-screen-md justify-between'
: size === 'full'
? ''
: 'max-w-xl'
size === 'full' ? '' : 'max-w-xl'
)}
>
<FieldLabel />
<div className={clsx(horizontal && 'flex-grow320px')}>
<div>
{/*
Remove line height to prevent skeleton bug
*/}

View File

@ -1,477 +0,0 @@
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { UseFormReturn } from 'react-hook-form';
import { DevTool } from '@hookform/devtools';
import { z } from 'zod';
import {
Form,
InputField,
Textarea,
Select,
Checkbox,
Radio,
CodeEditorField,
} from '@/new-components/Form';
import { Button } from '@/new-components/Button';
export default {
title: 'components/Forms 📁/Form 🦠',
component: Form,
parameters: {
docs: {
description: {
component: `A component wrapping native \`<checkbox>\` element ([see MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/checkbox)),
its description, hint and error message.<br>
Default CSS display is \`block\`, provided without padding and margin (displayed here with the \`<Form>\` padding).`,
},
source: { type: 'code', state: 'open' },
},
},
} as ComponentMeta<typeof Form>;
export const Basic: ComponentStory<typeof Form> = () => {
const validationSchema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
return (
<Form
// Apply validation schema to the form
schema={validationSchema}
onSubmit={action('onSubmit')}
>
{/* 💡 The `control` prop is provided by [**React Hook Form**](https://react-hook-form.com/api/useform)
and is used to access the form state. */}
{({ control }) => (
<div className="space-y-xs">
<h1 className="text-xl font-semibold mb-xs">Basic form</h1>
<InputField
name="inputFieldName"
label="The input field label"
placeholder="Input field placeholder"
/>
<Button type="submit" mode="primary">
Submit
</Button>
{/* Debug form state */}
<DevTool control={control} />
</div>
)}
</Form>
);
};
Basic.storyName = '💠 Basic usage';
Basic.parameters = {
docs: {
description: {
story: `\`<Form>\` component eases the task of forms creation.
It uses [**React Hook Form**](https://react-hook-form.com/) to
handle form validation and submission, validation schema is provided by [**Zod**](https://zod.dev/).
- 💡 Use the [\`<DevTool>\` component](https://react-hook-form.com/dev-tools) to debug the form state.
- 💡 Use the [**Storybook addon 'Actions'**](https://storybook.js.org/docs/react/essentials/actions) to see submitted values.`,
},
},
};
export const FormInputDefaultValue: ComponentStory<typeof Form> = () => {
const validationSchema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
return (
<Form
schema={validationSchema}
options={{
defaultValues: {
inputFieldName: 'Hello world !',
},
}}
onSubmit={action('onSubmit')}
>
{({ control }) => (
<div className="space-y-xs">
<h1 className="text-xl font-semibold mb-xs">Default value</h1>
<InputField
name="inputFieldName"
label="The input field label"
placeholder="Input field placeholder"
/>
<Button type="submit" mode="primary">
Submit
</Button>
<DevTool control={control} />
</div>
)}
</Form>
);
};
FormInputDefaultValue.storyName = '💠 Form input default value';
FormInputDefaultValue.parameters = {
docs: {
description: {
story: `In this example, the form is automatically filled with the \`Hello world !\`
value for the \`inputFieldName\` input.`,
},
},
};
export const ManuallyTriggerFormValidation: ComponentStory<typeof Form> =
() => {
const validationSchema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
const formRef = React.useRef<UseFormReturn>();
React.useEffect(() => {
// Use useEffect hook to wait for the form to be rendered before triggering validation
formRef?.current?.trigger();
});
return (
<Form
id="formId"
ref={formRef}
schema={validationSchema}
onSubmit={action('onSubmit')}
>
{({ control }) => (
<div className="space-y-xs">
<h1 className="text-xl font-semibold mb-xs">
Manually trigger form validation
</h1>
<InputField
name="inputFieldName"
label="The input field label"
placeholder="Input field placeholder"
/>
<Button type="submit" mode="primary">
Submit
</Button>
{/* Debug form state */}
<DevTool control={control} />
</div>
)}
</Form>
);
};
ManuallyTriggerFormValidation.storyName = '💠 Manually trigger form validation';
ManuallyTriggerFormValidation.parameters = {
docs: {
description: {
story: `In this example, the form is automatically validated thanks to a \`useImperativeHandle\` hook.`,
},
},
};
export const ManuallyFocusField: ComponentStory<typeof Form> = () => {
const validationSchema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
const formRef = React.useRef<UseFormReturn>();
React.useEffect(() => {
// Use useEffect hook to wait for the form to be rendered before triggering focus
formRef?.current?.setFocus('codeEditorFieldName');
});
return (
<Form
id="formId"
ref={formRef}
schema={validationSchema}
onSubmit={action('onSubmit')}
>
{({ control }) => (
<div className="space-y-xs">
<h1 className="text-xl font-semibold mb-xs">Manually focus field</h1>
<CodeEditorField
name="codeEditorFieldName"
label="The code editor field label"
/>
<Button type="submit" mode="primary">
Submit
</Button>
{/* Debug form state */}
<DevTool control={control} />
</div>
)}
</Form>
);
};
ManuallyFocusField.storyName = '💠 Manually focus a field';
ManuallyFocusField.parameters = {
docs: {
description: {
story: `In this example, the form \`codeEditorFieldName\` field is automatically focused thanks to a \`useImperativeHandle\` hook.`,
},
},
};
export const AllInputs: ComponentStory<typeof Form> = () => {
const validationSchema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
textareaName: z.string().min(1, { message: 'Mandatory field' }),
selectName: z.string().min(1, { message: 'Mandatory field' }),
checkboxNames: z
// When nothing is selected, the value is a false boolean
.union([z.string().array(), z.boolean()])
.refine(
value => Array.isArray(value) && value.length > 0,
'Choose at least one option'
),
radioName: z
// When nothing is selected, the value is null
.union([z.string(), z.null()])
.refine(
value => typeof value === 'string' && value.length > 0,
'Choose one option'
),
codeEditorFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
const formRef = React.useRef<UseFormReturn>();
React.useEffect(() => {
// Use useEffect hook to wait for the form to be rendered before triggering validation
formRef?.current?.trigger();
}, []);
return (
<Form
id="formId"
ref={formRef}
options={{
mode: 'onSubmit',
reValidateMode: 'onChange',
}}
schema={validationSchema}
onSubmit={action('onSubmit')}
>
{({ control, reset }) => (
<div className="space-y-xs">
<h1 className="text-xl font-semibold mb-xs">Form title</h1>
<InputField
name="inputFieldName"
label="The input field label"
description="The input field description"
tooltip="The input field tooltip"
placeholder="Input field placeholder"
/>
<Textarea
name="textareaName"
label="The textarea label"
description="The textarea description"
tooltip="The textarea tooltip"
placeholder="Textarea field placeholder"
/>
<Select
name="selectName"
options={[
{ value: 'selectValue0', label: 'Select value 0' },
{
value: 'selectValue1',
label: 'Select value 1',
disabled: true,
},
{ value: 'selectValue2', label: 'Select value 2' },
]}
label="The select label *"
description="The select description"
tooltip="The select tooltip"
placeholder="--Select placeholder--"
/>
<Checkbox
name="checkboxNames"
label="The checkbox label *"
description="The checkbox description"
tooltip="The checkbox tooltip"
options={[
{ value: 'checkboxValue0', label: 'Checkbox value 0' },
{
value: 'checkboxValue1',
label: 'Checkbox value 1',
disabled: true,
},
{ value: 'checkboxValue2', label: 'Checkbox value 2' },
]}
orientation="horizontal"
/>
<Radio
name="radioName"
label="The radio label *"
description="The radio description"
tooltip="The radio tooltip"
options={[
{ value: 'radioValue0', label: 'Radio value 0' },
{
value: 'radioValue1',
label: 'Radio value 1',
disabled: true,
},
{ value: 'radioValue2', label: 'Radio value 2' },
]}
orientation="horizontal"
/>
<CodeEditorField
name="codeEditorFieldName"
label="The code editor label *"
description="The code editor description"
tooltip="The code editor tooltip"
/>
<div className="flex gap-4">
<Button type="button" onClick={() => reset({})}>
Reset
</Button>
<Button type="submit" mode="primary">
Submit
</Button>
</div>
<DevTool control={control} />
</div>
)}
</Form>
);
};
AllInputs.storyName = '💠 Demo with all inputs and form reset';
AllInputs.parameters = {
docs: {
description: {
story: `Validation schema with all inputs mandatory.`,
},
},
};
export const AllInputsHorizontal: ComponentStory<typeof Form> = () => {
const validationSchema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
textareaName: z.string().min(1, { message: 'Mandatory field' }),
selectName: z.string().min(1, { message: 'Mandatory field' }),
checkboxNames: z
// When nothing is selected, the value is a false boolean
.union([z.string().array(), z.boolean()])
.refine(
value => Array.isArray(value) && value.length > 0,
'Choose at least one option'
),
radioName: z
// When nothing is selected, the value is null
.union([z.string(), z.null()])
.refine(
value => typeof value === 'string' && value.length > 0,
'Choose one option'
),
codeEditorFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
const formRef = React.useRef<UseFormReturn>();
React.useEffect(() => {
// Use useEffect hook to wait for the form to be rendered before triggering validation
formRef?.current?.trigger();
});
return (
<Form
id="formId"
ref={formRef}
options={{
mode: 'onSubmit',
reValidateMode: 'onChange',
}}
schema={validationSchema}
onSubmit={action('onSubmit')}
>
{({ control, reset }) => (
<div className="space-y-xs">
<h1 className="text-xl font-semibold mb-xs">Form title</h1>
<InputField
name="inputFieldName"
label="The input field label"
description="The input field description"
tooltip="The input field tooltip"
placeholder="Input field placeholder"
horizontal
/>
<Textarea
name="textareaName"
label="The textarea label"
description="The textarea description"
tooltip="The textarea tooltip"
placeholder="Textarea field placeholder"
horizontal
/>
<Select
name="selectName"
options={[
{ value: 'selectValue0', label: 'Select value 0' },
{
value: 'selectValue1',
label: 'Select value 1',
disabled: true,
},
{ value: 'selectValue2', label: 'Select value 2' },
]}
label="The select label *"
description="The select description"
tooltip="The select tooltip"
placeholder="--Select placeholder--"
horizontal
/>
<Checkbox
name="checkboxNames"
label="The checkbox label *"
description="The checkbox description"
tooltip="The checkbox tooltip"
options={[
{ value: 'checkboxValue0', label: 'Checkbox value 0' },
{
value: 'checkboxValue1',
label: 'Checkbox value 1',
disabled: true,
},
{ value: 'checkboxValue2', label: 'Checkbox value 2' },
]}
orientation="vertical"
horizontal
/>
<Radio
name="radioName"
label="The radio label *"
description="The radio description"
tooltip="The radio tooltip"
options={[
{ value: 'radioValue0', label: 'Radio value 0' },
{
value: 'radioValue1',
label: 'Radio value 1',
disabled: true,
},
{ value: 'radioValue2', label: 'Radio value 2' },
]}
orientation="vertical"
horizontal
/>
<CodeEditorField
name="codeEditorFieldName"
label="The code editor label *"
description="The code editor description"
tooltip="The code editor tooltip"
horizontal
/>
<div className="flex gap-4">
<Button type="button" onClick={() => reset({})}>
Reset
</Button>
<Button type="submit" mode="primary">
Submit
</Button>
</div>
<DevTool control={control} />
</div>
)}
</Form>
);
};
AllInputsHorizontal.storyName = ' 💠 Demo with horizontal fields';

View File

@ -1,144 +0,0 @@
import { zodResolver } from '@hookform/resolvers/zod';
import * as React from 'react';
import {
useForm,
UseFormReturn,
SubmitHandler,
UseFormProps,
FormProvider,
Path,
} from 'react-hook-form';
import { ZodType, ZodTypeDef, infer as zodInfer } from 'zod';
type FormProps<TFormValues, Schema> = {
/**
* Classes to apply to the wrapped <form> element
*/
className?: string;
/**
* On submit handler
*/
onSubmit: SubmitHandler<TFormValues>;
/**
* The component children
* @param methods
*/
children: (methods: UseFormReturn<TFormValues>) => React.ReactNode;
/**
* The form options
*/
options?: UseFormProps<TFormValues>;
/**
* The form ID
*/
id?: string;
/**
* The form validation schema
*/
schema: Schema;
};
export const Form = React.forwardRef(
<
TFormValues extends Record<string, unknown> = Record<string, unknown>,
Schema extends ZodType<TFormValues, ZodTypeDef, TFormValues> = ZodType<
TFormValues,
ZodTypeDef,
TFormValues
>
>(
{
onSubmit,
children,
className,
options,
id,
schema,
}: FormProps<zodInfer<Schema>, Schema>,
ref: React.Ref<unknown>
) => {
const methods = useForm<zodInfer<Schema>>({
...options,
resolver: schema && zodResolver(schema),
});
React.useImperativeHandle(ref, () => ({
trigger: async () => {
return methods.trigger();
},
setFocus: (name: string) => {
return methods.setFocus(name as Path<unknown>);
},
}));
return (
<FormProvider {...methods}>
<form
id={id}
className={`space-y-xs bg-legacybg ${className || ''}`}
onSubmit={methods.handleSubmit(onSubmit)}
>
{children(methods)}
</form>
</FormProvider>
);
}
);
type TFormValues = Record<string, unknown>;
type Schema = ZodType<TFormValues, ZodTypeDef, TFormValues>;
export const UpdatedForm = <FormSchema extends Schema>(
props: FormProps<zodInfer<FormSchema>, FormSchema> & {
autoFocus?: Path<zodInfer<FormSchema>>;
trigger?: boolean;
}
) => {
const {
id,
options,
schema,
onSubmit,
className,
children,
autoFocus,
trigger,
...rest
} = props;
const methods = useForm<zodInfer<FormSchema>>({
...options,
resolver: schema && zodResolver(schema),
});
React.useEffect(() => {
if (autoFocus) {
methods.setFocus(autoFocus);
}
if (trigger) {
methods.trigger();
}
}, [trigger, autoFocus, methods]);
return (
<FormProvider {...methods}>
<form
id={id}
className={className}
onSubmit={methods.handleSubmit(onSubmit)}
{...rest}
>
{children(methods)}
</form>
</FormProvider>
);
};
UpdatedForm.defaultProps = {
autoFocus: undefined,
trigger: false,
};
export const Forms = {
Old: Form,
New: UpdatedForm,
};

View File

@ -3,7 +3,7 @@ import { ComponentMeta, ComponentStory } from '@storybook/react';
import React from 'react';
import {
Form,
SimpleForm,
GraphQLSanitizedInputField as InputField,
} from '@/new-components/Form';
import { z } from 'zod';
@ -27,9 +27,9 @@ export const ApiPlayground: StoryType = args => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => <InputField {...args} />}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<InputField {...args} />
</SimpleForm>
);
};
@ -46,22 +46,20 @@ export const Examples: StoryType = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<div className="max-w-xs">
<InputField
name="sanitized-input"
label="With tips in description"
placeholder="Try typing spaces and other stuff!"
/>
<InputField
name="sanitized-input-no-tips"
label="No tips in description"
placeholder="Try typing spaces and other stuff!"
hideTips
/>
</div>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<div className="max-w-xs">
<InputField
name="sanitized-input"
label="With tips in description"
placeholder="Try typing spaces and other stuff!"
/>
<InputField
name="sanitized-input-no-tips"
label="No tips in description"
placeholder="Try typing spaces and other stuff!"
hideTips
/>
</div>
</SimpleForm>
);
};

View File

@ -2,8 +2,7 @@ import { action } from '@storybook/addon-actions';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import React from 'react';
import { Form, InputField } from '@/new-components/Form';
import { UseFormReturn } from 'react-hook-form';
import { SimpleForm, InputField, useConsoleForm } from '@/new-components/Form';
import { FiSearch } from 'react-icons/fi';
import { z } from 'zod';
@ -15,7 +14,7 @@ export default {
description: {
component: `A component wrapping native \`<input>\` element ([see MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input)),
its description, hint and error message.<br>
Default CSS display is \`block\`, provided without padding and margin (displayed here with the \`<Form>\` padding).`,
Default CSS display is \`block\`, provided without padding and margin (displayed here with the \`<SimpleForm>\` padding).`,
},
source: { type: 'code' },
},
@ -26,9 +25,9 @@ export const ApiPlayground: ComponentStory<typeof InputField> = args => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => <InputField {...args} />}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<InputField {...args} />
</SimpleForm>
);
};
ApiPlayground.storyName = '⚙️ API';
@ -42,9 +41,9 @@ export const Basic: ComponentStory<typeof InputField> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => <InputField name="inputFieldName" label="The inputField label" />}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<InputField name="inputFieldName" label="The inputField label" />
</SimpleForm>
);
};
Basic.storyName = '🧰 Basic';
@ -54,20 +53,39 @@ Basic.parameters = {
},
};
export const VariantClearButton: ComponentStory<typeof InputField> = () => {
const validationSchema = z.object({});
return (
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
clearButton
/>
</SimpleForm>
);
};
VariantClearButton.storyName = '🎭 Variant - Clear button';
VariantClearButton.parameters = {
docs: {
source: { state: 'open' },
},
};
export const VariantEmailType: ComponentStory<typeof InputField> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
type="email"
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
type="email"
/>
</SimpleForm>
);
};
VariantEmailType.storyName = '🎭 Variant - Type email';
@ -81,16 +99,14 @@ export const VariantPasswordType: ComponentStory<typeof InputField> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
type="password"
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
type="password"
/>
</SimpleForm>
);
};
VariantPasswordType.storyName = '🎭 Variant - Type password';
@ -100,20 +116,39 @@ VariantPasswordType.parameters = {
},
};
export const VariantFileType: ComponentStory<typeof InputField> = () => {
const validationSchema = z.object({});
return (
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
type="file"
/>
</SimpleForm>
);
};
VariantFileType.storyName = '🎭 Variant - Type file';
VariantFileType.parameters = {
docs: {
source: { state: 'open' },
},
};
export const VariantWithDescription: ComponentStory<typeof InputField> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
description="InputField description"
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
description="InputField description"
/>
</SimpleForm>
);
};
VariantWithDescription.storyName = '🎭 Variant - With description';
@ -127,7 +162,7 @@ export const VariantWithTooltip: ComponentStory<typeof InputField> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<InputField
name="inputFieldName"
@ -136,7 +171,7 @@ export const VariantWithTooltip: ComponentStory<typeof InputField> = () => {
tooltip="InputField tooltip"
/>
)}
</Form>
</SimpleForm>
);
};
VariantWithTooltip.storyName = '🎭 Variant - With tooltip';
@ -150,16 +185,14 @@ export const VariantSizeFull: ComponentStory<typeof InputField> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
size="full"
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
size="full"
/>
</SimpleForm>
);
};
VariantSizeFull.storyName = '🎭 Variant - Size full';
@ -173,16 +206,14 @@ export const VariantSizeMedium: ComponentStory<typeof InputField> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
size="medium"
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
size="medium"
/>
</SimpleForm>
);
};
VariantSizeMedium.storyName = '🎭 Variant - Size medium';
@ -196,15 +227,13 @@ export const VariantIconStart: ComponentStory<typeof InputField> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<InputField
name="inputFieldName"
label="The inputField label"
icon={<FiSearch />}
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<InputField
name="inputFieldName"
label="The inputField label"
icon={<FiSearch />}
/>
</SimpleForm>
);
};
VariantIconStart.storyName = '🎭 Variant - Icon start';
@ -218,17 +247,15 @@ export const VariantIconEnd: ComponentStory<typeof InputField> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
icon={<FiSearch />}
iconPosition="end"
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
icon={<FiSearch />}
iconPosition="end"
/>
</SimpleForm>
);
};
VariantIconEnd.storyName = '🎭 Variant - Icon end';
@ -242,16 +269,14 @@ export const VariantPrependLabel: ComponentStory<typeof InputField> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
prependLabel="Prepend label"
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
prependLabel="Prepend label"
/>
</SimpleForm>
);
};
VariantPrependLabel.storyName = '🎭 Variant - Prepend label';
@ -265,16 +290,14 @@ export const VariantAppendLabel: ComponentStory<typeof InputField> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
appendLabel="Append label"
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
appendLabel="Append label"
/>
</SimpleForm>
);
};
VariantAppendLabel.storyName = '🎭 Variant - Append label';
@ -290,26 +313,24 @@ export const StateWithDefaultValue: ComponentStory<typeof InputField> = () => {
const validationSchema = z.object({});
return (
<Form
<SimpleForm
schema={validationSchema}
options={{ defaultValues }}
onSubmit={action('onSubmit')}
>
{() => (
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
/>
)}
</Form>
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
/>
</SimpleForm>
);
};
StateWithDefaultValue.storyName = '🔁 State - With default value';
StateWithDefaultValue.parameters = {
docs: {
description: {
story: `Use \`<Form>\` options to set default value.`,
story: `Use \`<SimpleForm>\` options to set default value.`,
},
source: { state: 'open' },
},
@ -319,16 +340,14 @@ export const StateLoading: ComponentStory<typeof InputField> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
loading
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
loading
/>
</SimpleForm>
);
};
StateLoading.storyName = '🔁 State - Loading';
@ -342,16 +361,14 @@ export const StateDisabled: ComponentStory<typeof InputField> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
disabled
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
disabled
/>
</SimpleForm>
);
};
StateDisabled.storyName = '🔁 State - Disabled';
@ -362,25 +379,29 @@ StateDisabled.parameters = {
};
export const StateWithErrorMessage: ComponentStory<typeof InputField> = () => {
const formRef = React.useRef<UseFormReturn>();
React.useEffect(() => {
formRef?.current?.trigger();
});
const validationSchema = z.object({
const schema = z.object({
inputFieldName: z.enum(['value0', 'value1']),
});
const {
methods: { trigger },
Form,
} = useConsoleForm({
schema,
});
React.useEffect(() => {
// Use useEffect hook to wait for the form to be rendered before triggering validation
trigger();
});
return (
<Form ref={formRef} schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
/>
)}
<Form onSubmit={action('onSubmit')}>
<InputField
name="inputFieldName"
label="The inputField label"
placeholder="The inputField placeholder"
/>
</Form>
);
};
@ -388,8 +409,9 @@ StateWithErrorMessage.storyName = '🔁 State - With error message';
StateWithErrorMessage.parameters = {
docs: {
description: {
story: `Incorrect value is set then \`<Form>\` validation is automatically triggered.`,
story: `Incorrect value is set then \`<SimpleForm>\` validation is automatically triggered.`,
},
source: { state: 'open' },
},
};
@ -397,17 +419,15 @@ export const TestingScalability: ComponentStory<typeof InputField> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<InputField
name="inputFieldName"
label="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
placeholder="--Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.--"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
tooltip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<InputField
name="inputFieldName"
label="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
placeholder="--Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.--"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
tooltip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
/>
</SimpleForm>
);
};
TestingScalability.storyName = '🧪 Testing - Scalability';

View File

@ -52,7 +52,7 @@ export type InputFieldProps<T extends z.infer<Schema>> =
/**
* The input field type
*/
type?: 'text' | 'email' | 'password' | 'number';
type?: 'text' | 'email' | 'password' | 'number' | 'file';
/**
* The input field classes
*/
@ -119,6 +119,17 @@ export const InputField = <T extends z.infer<Schema>>({
const { onChange, ...regReturn } = register(name);
const showInputEndContainer = clearButton || (iconPosition === 'end' && icon);
const onInputChange = React.useCallback(
async event => {
console.log('event', event);
if (event.target.files?.[0]) {
onChange(event);
}
},
[onChange]
);
return (
<FieldWrapper
id={name}
@ -159,7 +170,8 @@ export const InputField = <T extends z.infer<Schema>>({
{
'pl-10': iconPosition === 'start' && icon,
'pr-10': iconPosition === 'end' && icon,
}
},
type === 'file' && 'h-auto'
)}
placeholder={placeholder}
{...regReturn}
@ -167,7 +179,11 @@ export const InputField = <T extends z.infer<Schema>>({
if (inputTransform) {
e.target.value = inputTransform(e.target.value);
}
onChange(e);
if (type === 'file') {
onInputChange(e);
} else {
onChange(e);
}
}}
disabled={disabled}
data-testid={name}

View File

@ -2,9 +2,8 @@ import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { UseFormReturn } from 'react-hook-form';
import { z } from 'zod';
import { Form, Radio } from '@/new-components/Form';
import { SimpleForm, Radio, useConsoleForm } from '@/new-components/Form';
export default {
title: 'components/Forms 📁/Radio 🧬',
@ -14,7 +13,7 @@ export default {
description: {
component: `A component wrapping native \`<radio>\` element ([see MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio)),
its description, hint and error message.<br>
Default CSS display is \`block\`, provided without padding and margin (displayed here with the \`<Form>\` padding).`,
Default CSS display is \`block\`, provided without padding and margin (displayed here with the \`<SimpleForm>\` padding).`,
},
source: { type: 'code' },
},
@ -25,9 +24,9 @@ export const ApiPlayground: ComponentStory<typeof Radio> = args => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => <Radio {...args} />}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Radio {...args} />
</SimpleForm>
);
};
ApiPlayground.storyName = '⚙️ API';
@ -51,11 +50,9 @@ export const Basic: ComponentStory<typeof Radio> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Radio name="radioNames" label="The radio label" options={options} />
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Radio name="radioNames" label="The radio label" options={options} />
</SimpleForm>
);
};
Basic.storyName = '🧰 Basic';
@ -75,19 +72,17 @@ export const VariantOrientation: ComponentStory<typeof Radio> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<>
<Radio name="radioNames" label="The radio label" options={options} />
<Radio
name="radioNames"
label="The radio label"
options={options}
orientation="horizontal"
/>
</>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<>
<Radio name="radioNames" label="The radio label" options={options} />
<Radio
name="radioNames"
label="The radio label"
options={options}
orientation="horizontal"
/>
</>
</SimpleForm>
);
};
VariantOrientation.storyName = '🎭 Variant - Orientation';
@ -107,16 +102,14 @@ export const VariantWithDescription: ComponentStory<typeof Radio> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Radio
name="radioNames"
label="The radio label"
description="Radio description"
options={options}
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Radio
name="radioNames"
label="The radio label"
description="Radio description"
options={options}
/>
</SimpleForm>
);
};
VariantWithDescription.storyName = '🎭 Variant - With description';
@ -136,16 +129,14 @@ export const VariantWithTooltip: ComponentStory<typeof Radio> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Radio
name="radioNames"
label="The radio label"
tooltip="Radio tooltip"
options={options}
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Radio
name="radioNames"
label="The radio label"
tooltip="Radio tooltip"
options={options}
/>
</SimpleForm>
);
};
VariantWithTooltip.storyName = '🎭 Variant - With tooltip';
@ -167,22 +158,20 @@ export const StateWithDefaultValue: ComponentStory<typeof Radio> = () => {
const validationSchema = z.object({});
return (
<Form
<SimpleForm
schema={validationSchema}
options={{ defaultValues }}
onSubmit={action('onSubmit')}
>
{() => (
<Radio name="radioNames" label="The radio label" options={options} />
)}
</Form>
<Radio name="radioNames" label="The radio label" options={options} />
</SimpleForm>
);
};
StateWithDefaultValue.storyName = '🔁 State - With default value';
StateWithDefaultValue.parameters = {
docs: {
description: {
story: `Use \`<Form>\` options to set default value.`,
story: `Use \`<SimpleForm>\` options to set default value.`,
},
source: { state: 'open' },
},
@ -198,16 +187,14 @@ export const StateLoading: ComponentStory<typeof Radio> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Radio
name="radioNames"
label="The radio label"
options={options}
disabled
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Radio
name="radioNames"
label="The radio label"
options={options}
disabled
/>
</SimpleForm>
);
};
StateLoading.storyName = '🔁 State - Loading';
@ -227,16 +214,14 @@ export const StateDisabled: ComponentStory<typeof Radio> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Radio
name="radioNames"
label="The radio label"
options={options}
disabled
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Radio
name="radioNames"
label="The radio label"
options={options}
disabled
/>
</SimpleForm>
);
};
StateDisabled.storyName = '🔁 State - Disabled';
@ -247,27 +232,31 @@ StateDisabled.parameters = {
};
export const StateWithErrorMessage: ComponentStory<typeof Radio> = () => {
const formRef = React.useRef<UseFormReturn>();
React.useEffect(() => {
formRef?.current?.trigger();
});
const options = [
{ value: 'value0', label: 'Value 0' },
{ value: 'value1', label: 'Value 1', loading: true },
{ value: 'value2', label: 'Value 2' },
];
const validationSchema = z.object({
const schema = z.object({
radioNames: z.enum(['value0', 'value1']),
});
const {
methods: { trigger },
Form,
} = useConsoleForm({
schema,
});
React.useEffect(() => {
// Use useEffect hook to wait for the form to be rendered before triggering validation
trigger();
});
return (
<Form ref={formRef} schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Radio name="radioNames" label="The radio label" options={options} />
)}
<Form onSubmit={action('onSubmit')}>
<Radio name="radioNames" label="The radio label" options={options} />
</Form>
);
};
@ -276,7 +265,7 @@ StateWithErrorMessage.storyName = '🔁 State - With error message';
StateWithErrorMessage.parameters = {
docs: {
description: {
story: `Incorrect value is set then \`<Form>\` validation is automatically triggered.`,
story: `Incorrect value is set then \`<SimpleForm>\` validation is automatically triggered.`,
},
source: { state: 'open' },
},
@ -297,17 +286,15 @@ export const TestingScalability: ComponentStory<typeof Radio> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Radio
name="radioNames"
label="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
tooltip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
options={options}
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Radio
name="radioNames"
label="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
tooltip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
options={options}
/>
</SimpleForm>
);
};
TestingScalability.storyName = '🧪 Testing - Scalability';

View File

@ -2,9 +2,8 @@ import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { UseFormReturn } from 'react-hook-form';
import { z } from 'zod';
import { Form, Select } from '@/new-components/Form';
import { SimpleForm, Select, useConsoleForm } from '@/new-components/Form';
export default {
title: 'components/Forms 📁/Select 🧬',
@ -14,7 +13,7 @@ export default {
description: {
component: `A component wrapping native \`<select>\` element ([see MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/select)),
its description, hint and error message.<br>
Default CSS display is \`block\`, provided without padding and margin (displayed here with the \`<Form>\` padding).`,
Default CSS display is \`block\`, provided without padding and margin (displayed here with the \`<SimpleForm>\` padding).`,
},
source: { type: 'code' },
},
@ -25,9 +24,9 @@ export const ApiPlayground: ComponentStory<typeof Select> = args => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => <Select {...args} />}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Select {...args} />
</SimpleForm>
);
};
ApiPlayground.storyName = '⚙️ API';
@ -51,11 +50,9 @@ export const Basic: ComponentStory<typeof Select> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Select name="selectNames" label="The select label" options={options} />
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Select name="selectNames" label="The select label" options={options} />
</SimpleForm>
);
};
Basic.storyName = '🧰 Basic';
@ -75,16 +72,14 @@ export const VariantWithDescription: ComponentStory<typeof Select> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Select
name="selectNames"
label="The select label"
description="Select description"
options={options}
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Select
name="selectNames"
label="The select label"
description="Select description"
options={options}
/>
</SimpleForm>
);
};
VariantWithDescription.storyName = '🎭 Variant - With description';
@ -104,16 +99,14 @@ export const VariantWithTooltip: ComponentStory<typeof Select> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Select
name="selectNames"
label="The select label"
tooltip="Select tooltip"
options={options}
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Select
name="selectNames"
label="The select label"
tooltip="Select tooltip"
options={options}
/>
</SimpleForm>
);
};
VariantWithTooltip.storyName = '🎭 Variant - With tooltip';
@ -135,22 +128,20 @@ export const StateWithDefaultValue: ComponentStory<typeof Select> = () => {
const validationSchema = z.object({});
return (
<Form
<SimpleForm
schema={validationSchema}
options={{ defaultValues }}
onSubmit={action('onSubmit')}
>
{() => (
<Select name="selectNames" label="The select label" options={options} />
)}
</Form>
<Select name="selectNames" label="The select label" options={options} />
</SimpleForm>
);
};
StateWithDefaultValue.storyName = '🔁 State - With default value';
StateWithDefaultValue.parameters = {
docs: {
description: {
story: `Use \`<Form>\` options to set default value.`,
story: `Use \`<SimpleForm>\` options to set default value.`,
},
source: { state: 'open' },
},
@ -166,7 +157,7 @@ export const StateLoading: ComponentStory<typeof Select> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Select
name="selectNames"
@ -175,7 +166,7 @@ export const StateLoading: ComponentStory<typeof Select> = () => {
loading
/>
)}
</Form>
</SimpleForm>
);
};
StateLoading.storyName = '🔁 State - Loading';
@ -195,16 +186,14 @@ export const StateDisabled: ComponentStory<typeof Select> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Select
name="selectNames"
label="The select label"
options={options}
disabled
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Select
name="selectNames"
label="The select label"
options={options}
disabled
/>
</SimpleForm>
);
};
StateDisabled.storyName = '🔁 State - Disabled';
@ -215,27 +204,31 @@ StateDisabled.parameters = {
};
export const StateWithErrorMessage: ComponentStory<typeof Select> = () => {
const formRef = React.useRef<UseFormReturn>();
React.useEffect(() => {
formRef?.current?.trigger();
});
const options = [
{ value: 'value0', label: 'Value 0' },
{ value: 'value1', label: 'Value 1', disabled: true },
{ value: 'value2', label: 'Value 2' },
];
const validationSchema = z.object({
const schema = z.object({
selectNames: z.enum(['value0', 'value1']),
});
const {
methods: { trigger },
Form,
} = useConsoleForm({
schema,
});
React.useEffect(() => {
// Use useEffect hook to wait for the form to be rendered before triggering validation
trigger();
});
return (
<Form ref={formRef} schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Select name="selectNames" label="The select label" options={options} />
)}
<Form onSubmit={action('onSubmit')}>
<Select name="selectNames" label="The select label" options={options} />
</Form>
);
};
@ -243,7 +236,7 @@ StateWithErrorMessage.storyName = '🔁 State - With error message';
StateWithErrorMessage.parameters = {
docs: {
description: {
story: `Incorrect value is set then \`<Form>\` validation is automatically triggered.`,
story: `Incorrect value is set then \`<SimpleForm>\` validation is automatically triggered.`,
},
source: { state: 'open' },
},
@ -264,17 +257,15 @@ export const TestingScalability: ComponentStory<typeof Select> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Select
name="selectNames"
label="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
tooltip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
options={options}
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Select
name="selectNames"
label="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
tooltip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
options={options}
/>
</SimpleForm>
);
};
TestingScalability.storyName = '🧪 Testing - Scalability';

View File

@ -0,0 +1,101 @@
import React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { z } from 'zod';
import { InputField, SimpleForm } from '@/new-components/Form';
import { Button } from '@/new-components/Button';
export default {
title: 'components/Forms 📁/Form 📁/Simple forms 🧬',
component: SimpleForm,
parameters: {
docs: {
description: {
component: `A simple form component. For most advanced usage (set focus, trigeer validation, ...), use \`useConsoleForm\` hook instead.`,
},
source: { type: 'code', state: 'open' },
},
},
decorators: [Story => <div className="p-4 ">{Story()}</div>],
} as ComponentMeta<typeof SimpleForm>;
export const Basic: ComponentStory<typeof SimpleForm> = () => {
const validationSchema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
return (
<SimpleForm
// Apply validation schema to the form
schema={validationSchema}
onSubmit={action('onSubmit')}
>
<div className="space-y-xs">
<h1 className="text-xl font-semibold mb-xs">Basic form</h1>
<InputField
name="inputFieldName"
label="The input field label"
placeholder="Input field placeholder"
clearButton
/>
<Button type="submit" mode="primary">
Submit
</Button>
</div>
</SimpleForm>
);
};
Basic.storyName = '💠 Basic usage';
Basic.parameters = {
docs: {
description: {
story: `\`<SimpleForm>\` component eases the task of forms creation.
It uses [**React Hook Form**](https://react-hook-form.com/) to
handle form validation and submission, validation schema is provided by [**Zod**](https://zod.dev/).
- 💡 Use the [**Storybook addon 'Actions'**](https://storybook.js.org/docs/react/essentials/actions) to see submitted values.`,
},
},
};
export const FormInputDefaultValue: ComponentStory<typeof SimpleForm> = () => {
const validationSchema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
return (
<SimpleForm
schema={validationSchema}
options={{
defaultValues: {
inputFieldName: 'Field defaut value',
},
}}
onSubmit={action('onSubmit')}
>
<div className="space-y-xs">
<h1 className="text-xl font-semibold mb-xs">Default value</h1>
<InputField
name="inputFieldName"
label="The input field label"
placeholder="Input field placeholder"
clearButton
/>
<Button type="submit" mode="primary">
Submit
</Button>
</div>
</SimpleForm>
);
};
FormInputDefaultValue.storyName = '💠 Form input default value';
FormInputDefaultValue.parameters = {
docs: {
description: {
story: `In this example, the form is automatically filled with the \`Hello world !\`
value for the \`inputFieldName\` input.`,
},
},
};

View File

@ -0,0 +1,19 @@
import * as React from 'react';
import { v4 as uuid } from 'uuid';
import { infer as zodInfer } from 'zod';
import { useConsoleForm } from './hooks/useConsoleForm';
import { FormProps, UseConsoleFormProps, Schema } from './hooks/form.types';
export const SimpleForm = <FormSchema extends Schema>(
props: FormProps<zodInfer<FormSchema>> &
UseConsoleFormProps<zodInfer<FormSchema>, FormSchema>
) => {
const { Form } = useConsoleForm<FormSchema>(props);
return <Form {...props} />;
};
SimpleForm.defaultProps = {
className: '',
id: uuid(),
options: {},
};

View File

@ -2,9 +2,8 @@ import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { UseFormReturn } from 'react-hook-form';
import { z } from 'zod';
import { Form, Textarea } from '@/new-components/Form';
import { SimpleForm, Textarea, useConsoleForm } from '@/new-components/Form';
export default {
title: 'components/Forms 📁/Textarea 🧬',
@ -14,7 +13,7 @@ export default {
description: {
component: `A component wrapping native \`<textarea>\` element ([see MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea)),
its description, hint and error message.<br>
Default CSS display is \`block\`, provided without padding and margin (displayed here with the \`<Form>\` padding).`,
Default CSS display is \`block\`, provided without padding and margin (displayed here with the \`<SimpleForm>\` padding).`,
},
source: { type: 'code' },
},
@ -25,9 +24,9 @@ export const ApiPlayground: ComponentStory<typeof Textarea> = args => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => <Textarea {...args} />}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Textarea {...args} />
</SimpleForm>
);
};
ApiPlayground.storyName = '⚙️ API';
@ -40,9 +39,9 @@ export const Basic: ComponentStory<typeof Textarea> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => <Textarea name="textareaName" label="The textarea label" />}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Textarea name="textareaName" label="The textarea label" />
</SimpleForm>
);
};
Basic.storyName = '🧰 Basic';
@ -56,15 +55,13 @@ export const VariantWithDescription: ComponentStory<typeof Textarea> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Textarea
name="textareaName"
label="The textarea label"
description="Textarea description"
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Textarea
name="textareaName"
label="The textarea label"
description="Textarea description"
/>
</SimpleForm>
);
};
VariantWithDescription.storyName = '🎭 Variant - With description';
@ -78,7 +75,7 @@ export const VariantWithTooltip: ComponentStory<typeof Textarea> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Textarea
name="textareaName"
@ -86,7 +83,7 @@ export const VariantWithTooltip: ComponentStory<typeof Textarea> = () => {
tooltip="Textarea tooltip"
/>
)}
</Form>
</SimpleForm>
);
};
VariantWithTooltip.storyName = '🎭 Variant - With tooltip';
@ -100,11 +97,9 @@ export const VariantSizeFull: ComponentStory<typeof Textarea> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Textarea name="textareaName" label="The textarea label" size="full" />
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Textarea name="textareaName" label="The textarea label" size="full" />
</SimpleForm>
);
};
VariantSizeFull.storyName = '🎭 Variant - Size full';
@ -118,15 +113,9 @@ export const VariantSizeMedium: ComponentStory<typeof Textarea> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Textarea
name="textareaName"
label="The textarea label"
size="medium"
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Textarea name="textareaName" label="The textarea label" size="medium" />
</SimpleForm>
);
};
VariantSizeMedium.storyName = '🎭 Variant - Size medium';
@ -142,20 +131,20 @@ export const StateWithDefaultValue: ComponentStory<typeof Textarea> = () => {
const validationSchema = z.object({});
return (
<Form
<SimpleForm
schema={validationSchema}
options={{ defaultValues }}
onSubmit={action('onSubmit')}
>
{() => <Textarea name="textareaName" label="The textarea label" />}
</Form>
<Textarea name="textareaName" label="The textarea label" />
</SimpleForm>
);
};
StateWithDefaultValue.storyName = '🔁 State - With default value';
StateWithDefaultValue.parameters = {
docs: {
description: {
story: `Use \`<Form>\` options to set default value.`,
story: `Use \`<SimpleForm>\` options to set default value.`,
},
source: { state: 'open' },
},
@ -165,11 +154,9 @@ export const StateLoading: ComponentStory<typeof Textarea> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Textarea name="textareaName" label="The textarea label" loading />
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Textarea name="textareaName" label="The textarea label" loading />
</SimpleForm>
);
};
StateLoading.storyName = '🔁 State - Loading';
@ -183,11 +170,9 @@ export const StateDisabled: ComponentStory<typeof Textarea> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Textarea name="textareaName" label="The textarea label" disabled />
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Textarea name="textareaName" label="The textarea label" disabled />
</SimpleForm>
);
};
StateDisabled.storyName = '🔁 State - Disabled';
@ -198,19 +183,25 @@ StateDisabled.parameters = {
};
export const StateWithErrorMessage: ComponentStory<typeof Textarea> = () => {
const formRef = React.useRef<UseFormReturn>();
React.useEffect(() => {
formRef?.current?.trigger();
});
const validationSchema = z.object({
const schema = z.object({
textareaName: z.enum(['value0', 'value1']),
});
const {
methods: { trigger },
Form,
} = useConsoleForm({
schema,
});
React.useEffect(() => {
// Use useEffect hook to wait for the form to be rendered before triggering validation
trigger();
});
return (
<Form ref={formRef} schema={validationSchema} onSubmit={action('onSubmit')}>
{() => <Textarea name="textareaName" label="The textarea label" />}
<Form onSubmit={action('onSubmit')}>
<Textarea name="textareaName" label="The textarea label" />
</Form>
);
};
@ -218,7 +209,7 @@ StateWithErrorMessage.storyName = '🔁 State - With error message';
StateWithErrorMessage.parameters = {
docs: {
description: {
story: `Incorrect value is set then \`<Form>\` validation is automatically triggered.`,
story: `Incorrect value is set then \`<SimpleForm>\` validation is automatically triggered.`,
},
source: { state: 'open' },
},
@ -228,17 +219,15 @@ export const TestingScalability: ComponentStory<typeof Textarea> = () => {
const validationSchema = z.object({});
return (
<Form schema={validationSchema} onSubmit={action('onSubmit')}>
{() => (
<Textarea
name="textareaName"
label="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
placeholder="--Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.--"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
tooltip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
/>
)}
</Form>
<SimpleForm schema={validationSchema} onSubmit={action('onSubmit')}>
<Textarea
name="textareaName"
label="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
placeholder="--Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.--"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
tooltip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
/>
</SimpleForm>
);
};
TestingScalability.storyName = '🧪 Testing - Scalability';

View File

@ -1,445 +0,0 @@
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { DevTool } from '@hookform/devtools';
import { z } from 'zod';
import {
UpdatedForm as Form,
InputField,
Textarea,
Select,
Checkbox,
Radio,
CodeEditorField,
} from '@/new-components/Form';
import { Button } from '@/new-components/Button';
export default {
title: 'components/Forms 📁/Updated Form🦠',
component: Form,
parameters: {
docs: {
description: {
component: ``,
},
source: { type: 'code', state: 'open' },
},
},
} as ComponentMeta<typeof Form>;
export const Primary = () => {
const validationSchema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
const onSubmit = (output: z.infer<typeof validationSchema>) => {
action('onSubmit')(output);
};
type Schema = z.infer<typeof validationSchema>;
return (
<Form
// Apply validation schema to the form
schema={validationSchema}
onSubmit={onSubmit}
>
{/* 💡 The `control` prop is provided by [**React Hook Form**](https://react-hook-form.com/api/useform)
and is used to access the form state. */}
{({ control }) => (
<div className="space-y-2">
<h1 className="text-xl font-semibold mb-xs">Basic form</h1>
<InputField<Schema>
name="inputFieldName"
label="The input field label"
placeholder="Input field placeholder"
/>
<Button type="submit" mode="primary">
Submit
</Button>
{/* Debug form state */}
<DevTool control={control} />
</div>
)}
</Form>
);
};
export const FormInputDefaultValue: ComponentStory<typeof Form> = () => {
const validationSchema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
const onSubmit = (output: z.infer<typeof validationSchema>) => {
action('onSubmit')(output);
};
return (
<Form
schema={validationSchema}
options={{
defaultValues: {
inputFieldName: 'Hello world !',
},
}}
onSubmit={onSubmit}
>
{({ control }) => (
<div className="space-y-2">
<h1 className="text-xl font-semibold mb-xs">Default value</h1>
<InputField
name="inputFieldName"
label="The input field label"
placeholder="Input field placeholder"
/>
<Button type="submit" mode="primary">
Submit
</Button>
<DevTool control={control} />
</div>
)}
</Form>
);
};
FormInputDefaultValue.storyName = '💠 Form input default value';
FormInputDefaultValue.parameters = {
docs: {
description: {
story: `In this example, the form is automatically filled with the \`Hello world !\`
value for the \`inputFieldName\` input.`,
},
},
};
export const ManuallyTriggerFormValidation: ComponentStory<typeof Form> =
() => {
const validationSchema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
return (
<Form
id="formId"
schema={validationSchema}
trigger
onSubmit={action('onSubmit')}
>
{options => {
return (
<div className="space-y-2">
<h1 className="text-xl font-semibold mb-xs">
Manually trigger form validation
</h1>
<InputField
name="inputFieldName"
label="The input field label"
placeholder="Input field placeholder"
/>
<Button type="submit" mode="primary">
Submit
</Button>
{/* Debug form state */}
<DevTool control={options.control} />
</div>
);
}}
</Form>
);
};
ManuallyTriggerFormValidation.storyName = '💠 Manually trigger form validation';
ManuallyTriggerFormValidation.parameters = {
docs: {
description: {
story: `In this example, the form is automatically validated thanks to a \`useImperativeHandle\` hook.`,
},
},
};
export const ManuallyFocusField: ComponentStory<typeof Form> = () => {
const validationSchema = z.object({
codeEditorFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
return (
<Form
id="formId"
schema={validationSchema}
autoFocus="codeEditorFieldName"
onSubmit={action('onSubmit')}
>
{({ control }) => (
<div className="space-y-2">
<h1 className="text-xl font-semibold mb-xs">Manually focus field</h1>
<CodeEditorField
name="codeEditorFieldName"
label="The code editor field label"
/>
<Button type="submit" mode="primary">
Submit
</Button>
{/* Debug form state */}
<DevTool control={control} />
</div>
)}
</Form>
);
};
ManuallyFocusField.storyName = '💠 Manually focus a field';
ManuallyFocusField.parameters = {
docs: {
description: {
story: `In this example, the form \`codeEditorFieldName\` field is automatically focused thanks to a \`useImperativeHandle\` hook.`,
},
},
};
export const AllInputs: ComponentStory<typeof Form> = () => {
const validationSchema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
textareaName: z.string().min(1, { message: 'Mandatory field' }),
selectName: z.string().min(1, { message: 'Mandatory field' }),
checkboxNames: z
// When nothing is selected, the value is a false boolean
.union([z.string().array(), z.boolean()])
.refine(
value => Array.isArray(value) && value.length > 0,
'Choose at least one option'
),
radioName: z
// When nothing is selected, the value is null
.union([z.string(), z.null()])
.refine(
value => typeof value === 'string' && value.length > 0,
'Choose one option'
),
codeEditorFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
return (
<Form
id="formId"
options={{
mode: 'onSubmit',
reValidateMode: 'onChange',
}}
schema={validationSchema}
onSubmit={action('onSubmit')}
>
{({ control, reset }) => (
<div className="space-y-2">
<h1 className="text-xl font-semibold mb-xs">Form title</h1>
<InputField
name="inputFieldName"
label="The input field label"
description="The input field description"
tooltip="The input field tooltip"
placeholder="Input field placeholder"
/>
<Textarea
name="textareaName"
label="The textarea label"
description="The textarea description"
tooltip="The textarea tooltip"
placeholder="Textarea field placeholder"
/>
<Select
name="selectName"
options={[
{ value: 'selectValue0', label: 'Select value 0' },
{
value: 'selectValue1',
label: 'Select value 1',
disabled: true,
},
{ value: 'selectValue2', label: 'Select value 2' },
]}
label="The select label *"
description="The select description"
tooltip="The select tooltip"
placeholder="--Select placeholder--"
/>
<Checkbox
name="checkboxNames"
label="The checkbox label *"
description="The checkbox description"
tooltip="The checkbox tooltip"
options={[
{ value: 'checkboxValue0', label: 'Checkbox value 0' },
{
value: 'checkboxValue1',
label: 'Checkbox value 1',
disabled: true,
},
{ value: 'checkboxValue2', label: 'Checkbox value 2' },
]}
orientation="horizontal"
/>
<Radio
name="radioName"
label="The radio label *"
description="The radio description"
tooltip="The radio tooltip"
options={[
{ value: 'radioValue0', label: 'Radio value 0' },
{
value: 'radioValue1',
label: 'Radio value 1',
disabled: true,
},
{ value: 'radioValue2', label: 'Radio value 2' },
]}
orientation="horizontal"
/>
<CodeEditorField
name="codeEditorFieldName"
label="The code editor label *"
description="The code editor description"
tooltip="The code editor tooltip"
/>
<div className="flex gap-4">
<Button type="button" onClick={() => reset({})}>
Reset
</Button>
<Button type="submit" mode="primary">
Submit
</Button>
</div>
<DevTool control={control} />
</div>
)}
</Form>
);
};
AllInputs.storyName = '💠 Demo with all inputs and form reset';
AllInputs.parameters = {
docs: {
description: {
story: `Validation schema with all inputs mandatory.`,
},
},
};
export const AllInputsHorizontal: ComponentStory<typeof Form> = () => {
const validationSchema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
textareaName: z.string().min(1, { message: 'Mandatory field' }),
selectName: z.string().min(1, { message: 'Mandatory field' }),
checkboxNames: z
// When nothing is selected, the value is a false boolean
.union([z.string().array(), z.boolean()])
.refine(
value => Array.isArray(value) && value.length > 0,
'Choose at least one option'
),
radioName: z
// When nothing is selected, the value is null
.union([z.string(), z.null()])
.refine(
value => typeof value === 'string' && value.length > 0,
'Choose one option'
),
codeEditorFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
return (
<Form
id="formId"
options={{
mode: 'onSubmit',
reValidateMode: 'onChange',
}}
schema={validationSchema}
onSubmit={action('onSubmit')}
>
{({ control, reset }) => (
<div className="space-y-2">
<h1 className="text-xl font-semibold mb-xs">Form title</h1>
<InputField
name="inputFieldName"
label="The input field label"
description="The input field description"
tooltip="The input field tooltip"
placeholder="Input field placeholder"
horizontal
/>
<Textarea
name="textareaName"
label="The textarea label"
description="The textarea description"
tooltip="The textarea tooltip"
placeholder="Textarea field placeholder"
horizontal
/>
<Select
name="selectName"
options={[
{ value: 'selectValue0', label: 'Select value 0' },
{
value: 'selectValue1',
label: 'Select value 1',
disabled: true,
},
{ value: 'selectValue2', label: 'Select value 2' },
]}
label="The select label *"
description="The select description"
tooltip="The select tooltip"
placeholder="--Select placeholder--"
horizontal
/>
<Checkbox
name="checkboxNames"
label="The checkbox label *"
description="The checkbox description"
tooltip="The checkbox tooltip"
options={[
{ value: 'checkboxValue0', label: 'Checkbox value 0' },
{
value: 'checkboxValue1',
label: 'Checkbox value 1',
disabled: true,
},
{ value: 'checkboxValue2', label: 'Checkbox value 2' },
]}
orientation="vertical"
horizontal
/>
<Radio
name="radioName"
label="The radio label *"
description="The radio description"
tooltip="The radio tooltip"
options={[
{ value: 'radioValue0', label: 'Radio value 0' },
{
value: 'radioValue1',
label: 'Radio value 1',
disabled: true,
},
{ value: 'radioValue2', label: 'Radio value 2' },
]}
orientation="vertical"
horizontal
/>
<CodeEditorField
name="codeEditorFieldName"
label="The code editor label *"
description="The code editor description"
tooltip="The code editor tooltip"
horizontal
/>
<div className="flex gap-4">
<Button type="button" onClick={() => reset({})}>
Reset
</Button>
<Button type="submit" mode="primary">
Submit
</Button>
</div>
<DevTool control={control} />
</div>
)}
</Form>
);
};
AllInputsHorizontal.storyName = ' 💠 Demo with horizontal fields';

View File

@ -1,7 +1,7 @@
import React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { z } from 'zod';
import { Form, InputField, InputFieldProps } from '..';
import { SimpleForm, InputField, InputFieldProps } from '..';
import { Button } from '../../Button';
const renderInputField = (
@ -10,20 +10,18 @@ const renderInputField = (
) => {
const onSubmit = jest.fn();
render(
<Form
<SimpleForm
onSubmit={args => {
console.log('AAAA', args);
onSubmit(args);
}}
schema={schema}
>
{() => (
<>
<InputField name="title" {...props} />
<Button type="submit">Submit</Button>
</>
)}
</Form>
<>
<InputField name="title" {...props} />
<Button type="submit">Submit</Button>
</>
</SimpleForm>
);
return {
onSubmit,

View File

@ -0,0 +1,56 @@
import React from 'react';
import {
FieldValues,
FormProviderProps,
SubmitHandler,
UseFormProps,
} from 'react-hook-form';
import { ZodType, ZodTypeDef, infer as ZodInfer } from 'zod';
// hook props:
export type UseConsoleFormProps<
TFielValues extends FieldValues,
TSchema extends ZodType<any, any, any>
> = {
/**
* The form options
*/
options?: UseFormProps<TFielValues>;
/**
* The form validation schema
*/
schema: TSchema;
};
type TFormValues = Record<string, unknown>;
export type Schema = ZodType<TFormValues, ZodTypeDef, TFormValues>;
// form component props:
export type FormProps<TForm extends ZodInfer<Schema>> = {
/**
* Classes to apply to the wrapped <form> element
*/
className?: string;
/**
* On submit handler
*/
onSubmit: SubmitHandler<TForm>;
/**
* The component children
*/
children: React.ReactNode;
/**
* The form ID
*/
id?: string;
};
// form wrapper props (combo of form component and FormProvider)
export interface FormWrapperProps<
TFieldValues extends FieldValues,
TSchema extends ZodInfer<Schema>,
// eslint-disable-next-line
TContext extends object = object
> extends FormProviderProps<TFieldValues, TContext>,
FormProps<TSchema> {}

View File

@ -0,0 +1,332 @@
import React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { z } from 'zod';
import { DevTool } from '@hookform/devtools';
import {
Checkbox,
CodeEditorField,
InputField,
Radio,
Select,
Textarea,
useConsoleForm,
} from '@/new-components/Form';
import { Button } from '@/new-components/Button';
export default {
title: 'components/Forms 📁/Form 📁/Advanced forms with hook 🪝',
parameters: {
docs: {
description: {
component: `A hook exposing React Form Hook with Console form fields.
This is the advocated way to create forms with advanced features as it allows to access all the React Hook Form data and methods.
For simple use cases, use the \`<SimpleForm />\` component instead.`,
},
source: { type: 'code', state: 'open' },
},
},
decorators: [Story => <div className="p-4 ">{Story()}</div>],
} as ComponentMeta<any>;
export const Basic: ComponentStory<any> = () => {
const schema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
const {
methods: { control },
Form,
} = useConsoleForm({
schema,
});
return (
<Form onSubmit={action('onSubmit')}>
<div className="space-y-xs">
<h1 className="text-xl font-semibold mb-xs">Basic form</h1>
<InputField
name="inputFieldName"
label="The input field label"
placeholder="Input field placeholder"
clearButton
/>
<Button type="submit" mode="primary">
Submit
</Button>
{/* Debug form state */}
<DevTool control={control} />
</div>
</Form>
);
};
Basic.storyName = '💠 Basic usage';
Basic.parameters = {
docs: {},
};
export const FormInputDefaultValue: ComponentStory<any> = () => {
const schema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
const {
methods: { control },
Form,
} = useConsoleForm({
schema,
options: {
defaultValues: {
inputFieldName: 'Input field default value',
},
},
});
return (
<Form onSubmit={action('onSubmit')}>
<div className="space-y-xs">
<h1 className="text-xl font-semibold mb-xs">Basic form</h1>
<InputField
name="inputFieldName"
label="The input field label"
placeholder="Input field placeholder"
clearButton
/>
<Button type="submit" mode="primary">
Submit
</Button>
{/* Debug form state */}
<DevTool control={control} />
</div>
</Form>
);
};
FormInputDefaultValue.storyName = '💠 With default value';
FormInputDefaultValue.parameters = {
docs: {},
};
export const ManuallyTriggerFormValidation: ComponentStory<any> = () => {
const schema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
const {
methods: { trigger, control },
Form,
} = useConsoleForm({
schema,
});
React.useEffect(() => {
// Use useEffect hook to wait for the form to be rendered before triggering validation
trigger();
}, []);
return (
<Form onSubmit={action('onSubmit')}>
<div className="space-y-xs">
<h1 className="text-xl font-semibold mb-xs">Basic form</h1>
<InputField
name="inputFieldName"
label="The input field label"
placeholder="Input field placeholder"
clearButton
/>
<Button type="submit" mode="primary">
Submit
</Button>
{/* Debug form state */}
<DevTool control={control} />
</div>
</Form>
);
};
ManuallyTriggerFormValidation.storyName = '💠 Manually trigger form validation';
ManuallyTriggerFormValidation.parameters = {
docs: {},
};
export const ManuallyFocusField: ComponentStory<any> = () => {
const schema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
});
const {
methods: { setFocus, control },
Form,
} = useConsoleForm({
schema,
});
React.useEffect(() => {
setFocus('inputFieldName');
}, []);
return (
<Form onSubmit={action('onSubmit')}>
<div className="space-y-xs">
<h1 className="text-xl font-semibold mb-xs">Basic form</h1>
<InputField
name="inputFieldName"
label="The input field label"
placeholder="Input field placeholder"
clearButton
/>
<Button type="submit" mode="primary">
Submit
</Button>
{/* Debug form state */}
<DevTool control={control} />
</div>
</Form>
);
};
ManuallyFocusField.storyName = '💠 Manually trigger focus';
ManuallyFocusField.parameters = {
docs: {},
};
export const AllInputs: ComponentStory<any> = () => {
const schema = z.object({
inputFieldName: z.string().min(1, { message: 'Mandatory field' }),
textareaName: z.string().min(1, { message: 'Mandatory field' }),
selectName: z.string().min(1, { message: 'Mandatory field' }),
checkboxNames: z
// When nothing is selected, the value is a false boolean
.union([z.string().array(), z.boolean()])
.refine(
value => Array.isArray(value) && value.length > 0,
'Choose at least one option'
),
radioName: z
// When nothing is selected, the value is null
.union([z.string(), z.null()])
.refine(
value => typeof value === 'string' && value.length > 0,
'Choose one option'
),
codeEditorFieldName: z.string().min(1, { message: 'Mandatory field' }),
inputFieldUploadName: z
.any()
.refine(files => files?.length === 1, 'File is required.'),
});
const {
methods: { setFocus, reset, control },
Form,
} = useConsoleForm({
schema,
options: {
defaultValues: {
inputFieldName: 'Input field default value',
},
},
});
React.useEffect(() => {
// Use useEffect hook to wait for the form to be rendered before triggering focus
setFocus('textareaName');
}, []);
return (
<Form onSubmit={action('onSubmit')}>
<div className="space-y-xs">
<h1 className="text-xl font-semibold mb-xs">Form title</h1>
<InputField
name="inputFieldName"
label="The input field label"
description="The input field description"
tooltip="The input field tooltip"
placeholder="Input field placeholder"
/>
<Textarea
name="textareaName"
label="The textarea label"
description="The textarea description"
tooltip="The textarea tooltip"
placeholder="Textarea field placeholder"
/>
<Select
name="selectName"
options={[
{ value: 'selectValue0', label: 'Select value 0' },
{
value: 'selectValue1',
label: 'Select value 1',
disabled: true,
},
{ value: 'selectValue2', label: 'Select value 2' },
]}
label="The select label *"
description="The select description"
tooltip="The select tooltip"
placeholder="--Select placeholder--"
/>
<Checkbox
name="checkboxNames"
label="The checkbox label *"
description="The checkbox description"
tooltip="The checkbox tooltip"
options={[
{ value: 'checkboxValue0', label: 'Checkbox value 0' },
{
value: 'checkboxValue1',
label: 'Checkbox value 1',
disabled: true,
},
{ value: 'checkboxValue2', label: 'Checkbox value 2' },
]}
orientation="horizontal"
/>
<Radio
name="radioName"
label="The radio label *"
description="The radio description"
tooltip="The radio tooltip"
options={[
{ value: 'radioValue0', label: 'Radio value 0' },
{
value: 'radioValue1',
label: 'Radio value 1',
disabled: true,
},
{ value: 'radioValue2', label: 'Radio value 2' },
]}
orientation="horizontal"
/>
<CodeEditorField
name="codeEditorFieldName"
label="The code editor label *"
description="The code editor description"
tooltip="The code editor tooltip"
/>
<InputField
name="inputFieldUploadName"
label="The upload input field label"
description="The upload input field description"
tooltip="The input upload field tooltip"
placeholder="Input upload field placeholder"
type="file"
/>
<div className="flex gap-4">
<Button type="button" onClick={() => reset({})}>
Reset
</Button>
<Button type="submit" mode="primary">
Submit
</Button>
</div>
{/* Debug form state */}
<DevTool control={control} />
</div>
</Form>
);
};
AllInputs.storyName = '💠 Demo with all inputs and form reset';
AllInputs.parameters = {
docs: {},
};

View File

@ -0,0 +1,67 @@
import { zodResolver } from '@hookform/resolvers/zod';
import * as React from 'react';
import {
FieldValues,
FormProvider,
useForm as useReactHookForm,
} from 'react-hook-form';
import { infer as zodInfer } from 'zod';
import {
FormProps,
FormWrapperProps,
Schema,
UseConsoleFormProps,
} from './form.types';
// available as a standlone if needed for advanced usage
const ConsoleFormWrapper = <
TFieldValues extends FieldValues,
TSchema extends zodInfer<Schema>,
// eslint-disable-next-line
TContext extends object = object
>(
props: FormWrapperProps<TFieldValues, TSchema, TContext>
) => {
const { id, className, onSubmit, children, ...methods } = props;
return (
<FormProvider {...methods}>
<form
id={id}
className={className}
onSubmit={methods.handleSubmit(onSubmit)}
data-non-regression="new-form-pattern"
>
{children}
</form>
</FormProvider>
);
};
export const useConsoleForm = <FormSchema extends Schema>(
hookProps: UseConsoleFormProps<zodInfer<FormSchema>, FormSchema>
) => {
const { options = {}, schema } = hookProps;
const methods = useReactHookForm<zodInfer<FormSchema>>({
...options,
resolver: schema && zodResolver(schema),
});
const BoundWrapper = React.useMemo(
() =>
<TFieldValues extends zodInfer<typeof schema>>(
props: FormProps<TFieldValues>
) =>
(
<ConsoleFormWrapper {...methods} {...props}>
{props.children}
</ConsoleFormWrapper>
),
[methods]
);
return {
methods,
Form: BoundWrapper,
};
};

View File

@ -1,9 +1,10 @@
export * from './Checkbox';
export * from './CodeEditorField';
export * from './Form';
export * from './SimpleForm';
export { InputField, InputFieldProps } from './InputField';
export * from './GraphQLSanitizedInputField';
export * from './Radio';
export * from './Select';
export * from './Textarea';
export * from './FieldWrapper';
export * from './hooks/useConsoleForm';

View File

@ -2,16 +2,13 @@ import { ComponentMeta, Story } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import React from 'react';
import { expect } from '@storybook/jest';
import { action } from '@storybook/addon-actions';
import { GiCarrot, GiShinyApple } from 'react-icons/gi';
import { FaCircle } from 'react-icons/fa';
import { z } from 'zod';
import { ListMap } from '.';
import { Button } from '../Button';
import { Form } from '../Form';
const submit = (values: Record<string, unknown>) => {
console.log(JSON.stringify(values));
};
import { SimpleForm } from '../Form';
const schema = z.object({
mapping: z.record(z.string()),
@ -23,17 +20,17 @@ export default {
decorators: [
StoryComponent => {
return (
<Form
<SimpleForm
options={{
defaultValues: {
mapping: { apple: 'tomato', cherry: 'chilli' },
},
}}
onSubmit={submit}
onSubmit={action('onSubmit')}
schema={schema}
>
{() => <StoryComponent />}
</Form>
<StoryComponent />
</SimpleForm>
);
},
],

View File

@ -1,7 +1,7 @@
import React from 'react';
import { ComponentMeta, Story } from '@storybook/react';
import { expect } from '@storybook/jest';
import { userEvent, within, waitFor } from '@storybook/testing-library';
import { userEvent, waitFor, within } from '@storybook/testing-library';
import { action } from '@storybook/addon-actions';
import { z } from 'zod';
import {
@ -10,7 +10,7 @@ import {
requestHeadersSelectorSchema,
} from '.';
import { Button } from '../Button';
import { Form } from '../Form';
import { SimpleForm } from '../Form';
export default {
title: 'components/Request Headers Selector',
@ -30,7 +30,7 @@ interface Props extends RequestHeadersSelectorProps {
export const Primary: Story<Props> = args => {
return (
<Form
<SimpleForm
options={{
defaultValues: {
headers: [
@ -42,15 +42,11 @@ export const Primary: Story<Props> = args => {
onSubmit={args.onSubmit}
schema={schema}
>
{() => {
return (
<>
<RequestHeadersSelector name={args.name} />
<Button type="submit">Submit</Button>
</>
);
}}
</Form>
<>
<RequestHeadersSelector name={args.name} />
<Button type="submit">Submit</Button>
</>
</SimpleForm>
);
};

View File

@ -10,10 +10,6 @@ module.exports = {
fontFamily: {
sans: ['Gudea', 'ui-sans-serif', 'system-ui'],
},
flex: {
grow220px: '1 0 220px',
grow320px: '1 0 320px',
},
extend: {
colors: {
current: 'currentColor',