mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-10-05 14:28:08 +03:00
Fix/form component
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7124 GitOrigin-RevId: fbaaa9f2e4377ebefccacc399472ead6a64927c6
This commit is contained in:
parent
9006ddd2d7
commit
1579e334cb
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
},
|
||||
],
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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');
|
||||
});
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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'}
|
||||
check
|
||||
</strong>
|
||||
|
||||
{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'}
|
||||
check
|
||||
</strong>
|
||||
|
||||
{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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
),
|
||||
];
|
||||
|
||||
|
@ -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>
|
||||
),
|
||||
];
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
),
|
||||
];
|
||||
|
||||
|
@ -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>
|
||||
),
|
||||
];
|
||||
|
||||
|
@ -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(),
|
||||
],
|
||||
|
@ -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>
|
||||
);
|
||||
},
|
||||
|
@ -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 } },
|
||||
|
@ -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 = {
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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: {
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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: {
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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: {
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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>
|
||||
|
@ -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' },
|
||||
},
|
||||
|
@ -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
|
||||
*/}
|
||||
|
@ -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';
|
@ -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,
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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';
|
||||
|
@ -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}
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
101
console/src/new-components/Form/SimpleForm.stories.tsx
Normal file
101
console/src/new-components/Form/SimpleForm.stories.tsx
Normal 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.`,
|
||||
},
|
||||
},
|
||||
};
|
19
console/src/new-components/Form/SimpleForm.tsx
Normal file
19
console/src/new-components/Form/SimpleForm.tsx
Normal 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: {},
|
||||
};
|
@ -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';
|
||||
|
@ -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';
|
@ -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,
|
||||
|
56
console/src/new-components/Form/hooks/form.types.ts
Normal file
56
console/src/new-components/Form/hooks/form.types.ts
Normal 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> {}
|
332
console/src/new-components/Form/hooks/useConsoleForm.stories.tsx
Normal file
332
console/src/new-components/Form/hooks/useConsoleForm.stories.tsx
Normal 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: {},
|
||||
};
|
67
console/src/new-components/Form/hooks/useConsoleForm.tsx
Normal file
67
console/src/new-components/Form/hooks/useConsoleForm.tsx
Normal 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,
|
||||
};
|
||||
};
|
@ -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';
|
||||
|
@ -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>
|
||||
);
|
||||
},
|
||||
],
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user