From e2ffcf8a153c6a7127abac6102cb592878395a23 Mon Sep 17 00:00:00 2001 From: Benji Grant Date: Fri, 14 Jul 2023 14:53:11 +1000 Subject: [PATCH 01/11] Restructure examples directory to support multiple examples --- example/App.tsx | 135 -------------------------------- examples/App.tsx | 47 +++++++++++ examples/components/Output.tsx | 14 ++++ {example => examples}/index.tsx | 0 examples/kitchen-sink/index.tsx | 133 +++++++++++++++++++++++++++++++ {example => examples}/style.css | 61 ++++++++++++--- index.html | 6 +- package.json | 1 + yarn.lock | 12 +++ 9 files changed, 259 insertions(+), 150 deletions(-) delete mode 100644 example/App.tsx create mode 100644 examples/App.tsx create mode 100644 examples/components/Output.tsx rename {example => examples}/index.tsx (100%) create mode 100644 examples/kitchen-sink/index.tsx rename {example => examples}/style.css (57%) diff --git a/example/App.tsx b/example/App.tsx deleted file mode 100644 index 4f136a7..0000000 --- a/example/App.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { SubmitHandler, useForm, ControlledField, controlled, fieldErrors, getValue, Errors } from '@stevent-team/react-zoom-form' -import { z } from 'zod' - -// Define the structure and validation of your form -const schema = z.object({ - requiredString: z.string().trim().min(1, 'This field is required').default(''), - optionalString: z.string().trim().nullish(), - defaultString: z.string().trim().default('Default value'), - number: z.coerce.number().min(3).max(10), - nested: z.object({ - inside: z.object({ - here: z.string().trim().min(1), - }), - }).optional(), - array: z.array( - z.object({ - prop: z.string().trim().min(1), - }), - ), - link: z.object({ - label: z.string(), - url: z.string().url(), - }).default({ label: 'default from zod', url: 'https://example.com' }), - condition: z.boolean(), - conditional: z.string(), - radio: z.enum(['option1', 'option2', 'option3']), -}) - -const issueMap = (issue: z.ZodIssue) => `${issue.message} (${issue.code})` - -const initialValues = { - defaultString: 'Default value', -} - -const App = () => { - const { fields, handleSubmit, isDirty, reset } = useForm({ schema, initialValues }) - - const onSubmit: SubmitHandler = values => { - console.log(values) - reset(values) - } - - return
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {getValue(fields.condition) && <> - - - - } - - - - - - - - - - -
isDirty: {isDirty ? 'true' : 'false'}
-
value: {JSON.stringify(getValue(fields), null, 2)}
-
errors: {JSON.stringify(fieldErrors(fields), null, 2)}
-
- -} - -interface Link { - label: string - url: string -} - -const LinkField = ({ field }: { field: ControlledField }) => { - const { value, onChange, errors } = field - - return <> -
- onChange({ ...value, label: e.currentTarget.value })} - /> - onChange({ ...value, url: e.currentTarget.value })} - /> -
- {errors.length > 0 && {errors.map(e => `${e.message} (${e.code})`).join(', ')}} - -} - -export default App diff --git a/examples/App.tsx b/examples/App.tsx new file mode 100644 index 0000000..fb03448 --- /dev/null +++ b/examples/App.tsx @@ -0,0 +1,47 @@ +import { Route, Switch, Link, useLocation } from 'wouter' +import KitchenSink from './kitchen-sink' + +interface Example { + name: string + path: string + component: () => JSX.Element +} + +const EXAMPLES: Example[] = [ + { + name: 'Kitchen Sink', + path: '/kitchen-sink', + component: KitchenSink, + }, +] + +const App = () => { + const [location] = useLocation() + + return <> + + +
+ + {EXAMPLES.map(example => )} + + Example not found + +
+ +} + +export default App diff --git a/examples/components/Output.tsx b/examples/components/Output.tsx new file mode 100644 index 0000000..b8f79bd --- /dev/null +++ b/examples/components/Output.tsx @@ -0,0 +1,14 @@ +import { Field, fieldErrors, getValue } from '@stevent-team/react-zoom-form' + +interface OutputProps { + isDirty: boolean + fields: Field +} + +const Output = ({ isDirty, fields }: OutputProps) => +
isDirty: {isDirty ? 'true' : 'false'}
+
value: {JSON.stringify(getValue(fields), null, 2)}
+
errors: {JSON.stringify(fieldErrors(fields), null, 2)}
+
+ +export default Output diff --git a/example/index.tsx b/examples/index.tsx similarity index 100% rename from example/index.tsx rename to examples/index.tsx diff --git a/examples/kitchen-sink/index.tsx b/examples/kitchen-sink/index.tsx new file mode 100644 index 0000000..3a390c6 --- /dev/null +++ b/examples/kitchen-sink/index.tsx @@ -0,0 +1,133 @@ +import { SubmitHandler, useForm, ControlledField, controlled, getValue, Errors } from '@stevent-team/react-zoom-form' +import { z } from 'zod' +import Output from '../components/Output' + +const schema = z.object({ + requiredString: z.string().trim().min(1, 'This field is required').default(''), + optionalString: z.string().trim().nullish(), + defaultString: z.string().trim().default('Default value'), + number: z.coerce.number().min(3).max(10), + nested: z.object({ + inside: z.object({ + here: z.string().trim().min(1), + }), + }).optional(), + array: z.array( + z.object({ + prop: z.string().trim().min(1), + }), + ), + link: z.object({ + label: z.string(), + url: z.string().url(), + }).default({ label: 'default from zod', url: 'https://example.com' }), + condition: z.boolean(), + conditional: z.string(), + radio: z.enum(['option1', 'option2', 'option3']), +}) + +const issueMap = (issue: z.ZodIssue) => `${issue.message} (${issue.code})` + +const initialValues = { + defaultString: 'Default value', +} + +const KitchenSink = () => { + const { fields, handleSubmit, isDirty, reset } = useForm({ schema, initialValues }) + + const onSubmit: SubmitHandler = values => { + console.log(values) + reset(values) + } + + return <> +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {getValue(fields.condition) && <> + + + + } + + + + + + + + + + + + +} + +interface Link { + label: string + url: string +} + +const LinkField = ({ field }: { field: ControlledField }) => { + const { value, onChange, errors } = field + + return <> +
+ onChange({ ...value, label: e.currentTarget.value })} + /> + onChange({ ...value, url: e.currentTarget.value })} + /> +
+ {errors.length > 0 && {errors.map(e => `${e.message} (${e.code})`).join(', ')}} + +} + +export default KitchenSink diff --git a/example/style.css b/examples/style.css similarity index 57% rename from example/style.css rename to examples/style.css index 0e6b1a1..38739be 100644 --- a/example/style.css +++ b/examples/style.css @@ -3,19 +3,48 @@ } body { - display: flex; margin: 0; + font-family: sans-serif; +} + +#app { + display: flex; padding: 2em; box-sizing: border-box; - flex-direction: column; - align-items: center; + align-items: flex-start; justify-content: center; min-height: 100vh; - font-family: sans-serif; + gap: 2em; + flex-wrap: wrap; +} + +nav h2 { + margin-block-start: .3rem; +} + +nav li { + line-height: 1.5; +} + +nav li.active span { + font-weight: 600; +} + +main { + flex: 1; + display: flex; + justify-content: center; + gap: 0 2em; + flex-wrap: wrap; } -form { - min-width: 300px; +h1 { + margin-block-start: 0; +} + +form, output { + width: 500px; + max-width: calc(100vw - 4em); } form > label { @@ -43,17 +72,25 @@ button { color: darkred; } -@media (prefers-color-scheme: dark) { - .error { - color: lightcoral; - } -} - output { font-family: monospace; white-space: pre; + overflow-x: auto; + background: #EEE; + padding: .5em 1em; + box-sizing: border-box; + border-radius: .3em; } output div { margin-block: .5em; } + +@media (prefers-color-scheme: dark) { + .error { + color: lightcoral; + } + output { + background: #222; + } +} diff --git a/index.html b/index.html index 472db98..3dafe6c 100644 --- a/index.html +++ b/index.html @@ -3,11 +3,11 @@ - - React Zoom Form Example + + React Zoom Form Examples
- + diff --git a/package.json b/package.json index a4507bb..a4a47a2 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "vite": "^4.4.2", "vite-plugin-dts": "^3.2.0", "vitest": "^0.33.0", + "wouter": "^2.11.0", "zod": "^3.21.4" } } diff --git a/yarn.lock b/yarn.lock index c51e770..e6cea2f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3920,6 +3920,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +use-sync-external-store@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + validate-npm-package-license@^3.0.1: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -4111,6 +4116,13 @@ why-is-node-running@^2.2.2: siginfo "^2.0.0" stackback "0.0.2" +wouter@^2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/wouter/-/wouter-2.11.0.tgz#3db485dec158115b67330821e7673bf3e2f78678" + integrity sha512-Y2CzNCwIN8kHjR2Q10D+UAgQND6TvBNmwXxgYw5ltXjjTlL7cLDUDpCip3a927Svxrmxr6vJMcPUysFxSvriCw== + dependencies: + use-sync-external-store "^1.0.0" + wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" From adab7d4670ce182af25ba82a81a4164e40762f5e Mon Sep 17 00:00:00 2001 From: Benji Grant Date: Fri, 14 Jul 2023 15:06:07 +1000 Subject: [PATCH 02/11] Add basic example --- examples/App.tsx | 20 +++++++++++----- examples/{components => }/Output.tsx | 0 examples/basic/index.tsx | 34 ++++++++++++++++++++++++++++ examples/kitchen-sink/index.tsx | 2 +- 4 files changed, 49 insertions(+), 7 deletions(-) rename examples/{components => }/Output.tsx (100%) create mode 100644 examples/basic/index.tsx diff --git a/examples/App.tsx b/examples/App.tsx index fb03448..f16adb6 100644 --- a/examples/App.tsx +++ b/examples/App.tsx @@ -1,5 +1,8 @@ import { Route, Switch, Link, useLocation } from 'wouter' import KitchenSink from './kitchen-sink' +import Basic from './basic' + +const GITHUB = 'https://github.com/stevent-team/react-zoom-form' interface Example { name: string @@ -8,6 +11,11 @@ interface Example { } const EXAMPLES: Example[] = [ + { + name: 'Basic', + path: '/basic', + component: Basic, + }, { name: 'Kitchen Sink', path: '/kitchen-sink', @@ -20,15 +28,15 @@ const App = () => { return <>