From c13e56779ab8df4b21660866b98d0f42677fabc9 Mon Sep 17 00:00:00 2001
From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com>
Date: Tue, 9 Jan 2024 08:51:48 +0100
Subject: [PATCH 01/10] feat(reusable-styled-form-component): create base files
for new react example
This commit adds the base files needed to start developing an example of a reusable styled form component.
---
.../.eslintrc.cjs | 15 +
.../reusable-styled-form-component/.gitignore | 27 ++
.../reusable-styled-form-component/README.md | 6 +
.../reusable-styled-form-component/index.html | 14 +
.../package.json | 26 ++
.../postcss.config.js | 6 +
.../src/index.css | 3 +
.../src/index.tsx | 13 +
.../src/utils.ts | 10 +
.../tailwind.config.js | 8 +
.../tsconfig.json | 9 +
pnpm-lock.yaml | 285 +++++++++++++++++-
12 files changed, 408 insertions(+), 14 deletions(-)
create mode 100644 examples/react/reusable-styled-form-component/.eslintrc.cjs
create mode 100644 examples/react/reusable-styled-form-component/.gitignore
create mode 100644 examples/react/reusable-styled-form-component/README.md
create mode 100644 examples/react/reusable-styled-form-component/index.html
create mode 100644 examples/react/reusable-styled-form-component/package.json
create mode 100644 examples/react/reusable-styled-form-component/postcss.config.js
create mode 100644 examples/react/reusable-styled-form-component/src/index.css
create mode 100644 examples/react/reusable-styled-form-component/src/index.tsx
create mode 100644 examples/react/reusable-styled-form-component/src/utils.ts
create mode 100644 examples/react/reusable-styled-form-component/tailwind.config.js
create mode 100644 examples/react/reusable-styled-form-component/tsconfig.json
diff --git a/examples/react/reusable-styled-form-component/.eslintrc.cjs b/examples/react/reusable-styled-form-component/.eslintrc.cjs
new file mode 100644
index 000000000..75ee331c1
--- /dev/null
+++ b/examples/react/reusable-styled-form-component/.eslintrc.cjs
@@ -0,0 +1,15 @@
+// @ts-check
+
+/** @type {import('eslint').Linter.Config} */
+const config = {
+ extends: ['plugin:react/recommended', 'plugin:react-hooks/recommended'],
+ parserOptions: {
+ tsconfigRootDir: __dirname,
+ project: './tsconfig.json',
+ },
+ rules: {
+ 'react/no-children-prop': 'off',
+ },
+}
+
+module.exports = config
diff --git a/examples/react/reusable-styled-form-component/.gitignore b/examples/react/reusable-styled-form-component/.gitignore
new file mode 100644
index 000000000..4673b022e
--- /dev/null
+++ b/examples/react/reusable-styled-form-component/.gitignore
@@ -0,0 +1,27 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+pnpm-lock.yaml
+yarn.lock
+package-lock.json
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/examples/react/reusable-styled-form-component/README.md b/examples/react/reusable-styled-form-component/README.md
new file mode 100644
index 000000000..1cf889265
--- /dev/null
+++ b/examples/react/reusable-styled-form-component/README.md
@@ -0,0 +1,6 @@
+# Example
+
+To run this example:
+
+- `npm install`
+- `npm run dev`
diff --git a/examples/react/reusable-styled-form-component/index.html b/examples/react/reusable-styled-form-component/index.html
new file mode 100644
index 000000000..87450a047
--- /dev/null
+++ b/examples/react/reusable-styled-form-component/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+ TanStack Reusable Styled Form Component
+
+
+
+
+
+
+
+
diff --git a/examples/react/reusable-styled-form-component/package.json b/examples/react/reusable-styled-form-component/package.json
new file mode 100644
index 000000000..f4ad65d66
--- /dev/null
+++ b/examples/react/reusable-styled-form-component/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "@tanstack/reusable-styled-form-component",
+ "version": "0.0.1",
+ "main": "src/index.tsx",
+ "scripts": {
+ "dev": "vite --port=3001",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@tanstack/react-form": "<1.0.0",
+ "@tanstack/zod-form-adapter": "<1.0.0",
+ "cva": "1.0.0-beta.1",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0",
+ "zod": "^3.21.4"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-react": "^4.2.1",
+ "autoprefixer": "^10.4.16",
+ "postcss": "8.4.32",
+ "tailwind-merge": "^2.2.0",
+ "tailwindcss": "^3.4.1",
+ "vite": "^5.0.10"
+ }
+}
diff --git a/examples/react/reusable-styled-form-component/postcss.config.js b/examples/react/reusable-styled-form-component/postcss.config.js
new file mode 100644
index 000000000..33ad091d2
--- /dev/null
+++ b/examples/react/reusable-styled-form-component/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/examples/react/reusable-styled-form-component/src/index.css b/examples/react/reusable-styled-form-component/src/index.css
new file mode 100644
index 000000000..b5c61c956
--- /dev/null
+++ b/examples/react/reusable-styled-form-component/src/index.css
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/examples/react/reusable-styled-form-component/src/index.tsx b/examples/react/reusable-styled-form-component/src/index.tsx
new file mode 100644
index 000000000..abe414dc5
--- /dev/null
+++ b/examples/react/reusable-styled-form-component/src/index.tsx
@@ -0,0 +1,13 @@
+import * as React from 'react'
+import { createRoot } from 'react-dom/client'
+import { z } from 'zod'
+
+export default function App() {
+ return (
+
+ )
+}
+
+const rootElement = document.getElementById('root')!
+
+createRoot(rootElement).render()
diff --git a/examples/react/reusable-styled-form-component/src/utils.ts b/examples/react/reusable-styled-form-component/src/utils.ts
new file mode 100644
index 000000000..cf0dec80d
--- /dev/null
+++ b/examples/react/reusable-styled-form-component/src/utils.ts
@@ -0,0 +1,10 @@
+import { type VariantProps, defineConfig } from 'cva';
+import { twMerge } from 'tailwind-merge';
+
+const { cva, cx, compose } = defineConfig({
+ hooks: {
+ onComplete: (className) => twMerge(className),
+ },
+});
+
+export { cva, cx, compose, type VariantProps };
diff --git a/examples/react/reusable-styled-form-component/tailwind.config.js b/examples/react/reusable-styled-form-component/tailwind.config.js
new file mode 100644
index 000000000..7038f8440
--- /dev/null
+++ b/examples/react/reusable-styled-form-component/tailwind.config.js
@@ -0,0 +1,8 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: ['./index.html', './src/**/*.{tsx,ts}'],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+}
diff --git a/examples/react/reusable-styled-form-component/tsconfig.json b/examples/react/reusable-styled-form-component/tsconfig.json
new file mode 100644
index 000000000..666a0ea71
--- /dev/null
+++ b/examples/react/reusable-styled-form-component/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "jsx": "react",
+ "noEmit": true,
+ "strict": true,
+ "esModuleInterop": true,
+ "lib": ["DOM", "DOM.Iterable", "ES2020"]
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c1ac2f11d..d8b927997 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -61,7 +61,7 @@ importers:
version: 3.6.1(@typescript-eslint/parser@6.4.1)(eslint-plugin-import@2.29.1)(eslint@8.56.0)
eslint-plugin-import:
specifier: ^2.29.1
- version: 2.29.1(@typescript-eslint/parser@6.4.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
+ version: 2.29.1(@typescript-eslint/parser@6.4.1)(eslint-import-resolver-typescript@3.6.0)(eslint@8.56.0)
eslint-plugin-react:
specifier: ^7.33.2
version: 7.33.2(eslint@8.56.0)
@@ -148,6 +148,46 @@ importers:
specifier: ^5
version: 5.2.2
+ examples/react/reusable-styled-form-component:
+ dependencies:
+ '@tanstack/react-form':
+ specifier: <1.0.0
+ version: link:../../../packages/react-form
+ '@tanstack/zod-form-adapter':
+ specifier: <1.0.0
+ version: link:../../../packages/zod-form-adapter
+ cva:
+ specifier: 1.0.0-beta.1
+ version: 1.0.0-beta.1(typescript@5.2.2)
+ react:
+ specifier: ^18.0.0
+ version: 18.2.0
+ react-dom:
+ specifier: ^18.0.0
+ version: 18.2.0(react@18.2.0)
+ zod:
+ specifier: ^3.21.4
+ version: 3.22.4
+ devDependencies:
+ '@vitejs/plugin-react':
+ specifier: ^4.2.1
+ version: 4.2.1(vite@5.0.10)
+ autoprefixer:
+ specifier: ^10.4.16
+ version: 10.4.16(postcss@8.4.32)
+ postcss:
+ specifier: 8.4.32
+ version: 8.4.32
+ tailwind-merge:
+ specifier: ^2.2.0
+ version: 2.2.0
+ tailwindcss:
+ specifier: ^3.4.1
+ version: 3.4.1
+ vite:
+ specifier: ^5.0.10
+ version: 5.0.10(@types/node@18.19.4)
+
examples/react/simple:
dependencies:
'@tanstack/react-form':
@@ -634,6 +674,11 @@ packages:
resolution: {integrity: sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==}
dev: true
+ /@alloc/quick-lru@5.2.0:
+ resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+ engines: {node: '>=10'}
+ dev: true
+
/@ampproject/remapping@2.2.1:
resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
engines: {node: '>=6.0.0'}
@@ -4828,12 +4873,15 @@ packages:
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
- dev: false
/appdirsjs@1.2.7:
resolution: {integrity: sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==}
dev: false
+ /arg@5.0.2:
+ resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+ dev: true
+
/argparse@1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
dependencies:
@@ -5031,6 +5079,22 @@ packages:
when-exit: 2.1.2
dev: true
+ /autoprefixer@10.4.16(postcss@8.4.32):
+ resolution: {integrity: sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==}
+ engines: {node: ^10 || ^12 || >=14}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.1.0
+ dependencies:
+ browserslist: 4.22.2
+ caniuse-lite: 1.0.30001572
+ fraction.js: 4.3.7
+ normalize-range: 0.1.2
+ picocolors: 1.0.0
+ postcss: 8.4.32
+ postcss-value-parser: 4.2.0
+ dev: true
+
/available-typed-arrays@1.0.5:
resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
engines: {node: '>= 0.4'}
@@ -5342,7 +5406,6 @@ packages:
/camelcase-css@2.0.1:
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
engines: {node: '>= 6'}
- dev: false
/camelcase-keys@8.0.2:
resolution: {integrity: sha512-qMKdlOfsjlezMqxkUGGMaWWs17i2HoL15tM+wtx8ld4nLrUwU58TFdvyGOz/piNP842KeO8yXvggVQSdQ828NA==}
@@ -5423,6 +5486,21 @@ packages:
get-func-name: 2.0.2
dev: true
+ /chokidar@3.5.3:
+ resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
+ engines: {node: '>= 8.10.0'}
+ dependencies:
+ anymatch: 3.1.3
+ braces: 3.0.2
+ glob-parent: 5.1.2
+ is-binary-path: 2.1.0
+ is-glob: 4.0.3
+ normalize-path: 3.0.0
+ readdirp: 3.6.0
+ optionalDependencies:
+ fsevents: 2.3.3
+ dev: true
+
/ci-info@2.0.0:
resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==}
dev: false
@@ -5541,6 +5619,11 @@ packages:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
dev: false
+ /commander@4.1.1:
+ resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
+ engines: {node: '>= 6'}
+ dev: true
+
/commander@9.5.0:
resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
engines: {node: ^12.20.0 || >=14}
@@ -5683,7 +5766,6 @@ packages:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
hasBin: true
- dev: false
/cssstyle@3.0.0:
resolution: {integrity: sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==}
@@ -5703,6 +5785,18 @@ packages:
is-git-repository: 1.1.1
dev: true
+ /cva@1.0.0-beta.1(typescript@5.2.2):
+ resolution: {integrity: sha512-gznFqTgERU9q4wg7jfgqtt34+RUt9S5t0xDAAEuDwQEAXEgjdDkKXpLLNjwSxsB4Ln/sqWJEH7yhE8Ny0mxd0w==}
+ peerDependencies:
+ typescript: '>= 4.5.5 < 6'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ clsx: 2.0.0
+ typescript: 5.2.2
+ dev: false
+
/damerau-levenshtein@1.0.8:
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
dev: true
@@ -5905,6 +5999,10 @@ packages:
resolution: {integrity: sha512-/oD3At60ZfhgzpofJtyClNTrIACyMdRe+ih0YiHzAniN0IZnLdLpEzgR6RtGs3kowxUkTnvV/4t1FBxXMUdusQ==}
dev: true
+ /didyoumean@1.2.2:
+ resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+ dev: true
+
/diff-sequences@26.6.2:
resolution: {integrity: sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==}
engines: {node: '>= 10.14.2'}
@@ -5922,6 +6020,10 @@ packages:
path-type: 4.0.0
dev: true
+ /dlv@1.1.3:
+ resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+ dev: true
+
/doctrine@2.1.0:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
@@ -6243,7 +6345,7 @@ packages:
eslint: 8.56.0
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.0(@typescript-eslint/parser@6.4.1)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.56.0)
- eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.4.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.4.1)(eslint-import-resolver-typescript@3.6.0)(eslint@8.56.0)
eslint-plugin-jsx-a11y: 6.8.0(eslint@8.56.0)
eslint-plugin-react: 7.33.2(eslint@8.56.0)
eslint-plugin-react-hooks: 4.6.0(eslint@8.56.0)
@@ -6283,7 +6385,7 @@ packages:
enhanced-resolve: 5.15.0
eslint: 8.56.0
eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.4.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.0)(eslint@8.56.0)
- eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.4.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.4.1)(eslint-import-resolver-typescript@3.6.0)(eslint@8.56.0)
fast-glob: 3.3.1
get-tsconfig: 4.7.0
is-core-module: 2.13.1
@@ -6306,7 +6408,7 @@ packages:
enhanced-resolve: 5.15.0
eslint: 8.56.0
eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.4.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
- eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.4.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.4.1)(eslint-import-resolver-typescript@3.6.0)(eslint@8.56.0)
fast-glob: 3.3.1
get-tsconfig: 4.7.0
is-core-module: 2.13.1
@@ -6378,7 +6480,7 @@ packages:
- supports-color
dev: true
- /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.4.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0):
+ /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.4.1)(eslint-import-resolver-typescript@3.6.0)(eslint@8.56.0):
resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
engines: {node: '>=4'}
peerDependencies:
@@ -6870,6 +6972,10 @@ packages:
mime-types: 2.1.35
dev: true
+ /fraction.js@4.3.7:
+ resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
+ dev: true
+
/fresh@0.5.2:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'}
@@ -7917,6 +8023,11 @@ packages:
supports-color: 8.1.1
dev: false
+ /jiti@1.21.0:
+ resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==}
+ hasBin: true
+ dev: true
+
/jju@1.4.0:
resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==}
dev: true
@@ -8171,6 +8282,16 @@ packages:
resolve: 1.22.4
dev: true
+ /lilconfig@2.1.0:
+ resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /lilconfig@3.0.0:
+ resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==}
+ engines: {node: '>=14'}
+ dev: true
+
/lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
@@ -8842,6 +8963,14 @@ packages:
resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==}
dev: true
+ /mz@2.7.0:
+ resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
+ dependencies:
+ any-promise: 1.3.0
+ object-assign: 4.1.1
+ thenify-all: 1.6.0
+ dev: true
+
/nanoid@3.3.7:
resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -8979,7 +9108,11 @@ packages:
/normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
- dev: false
+
+ /normalize-range@0.1.2:
+ resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
+ engines: {node: '>=0.10.0'}
+ dev: true
/npm-bundled@2.0.1:
resolution: {integrity: sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw==}
@@ -9103,6 +9236,11 @@ packages:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
+ /object-hash@3.0.0:
+ resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
+ engines: {node: '>= 6'}
+ dev: true
+
/object-inspect@1.12.3:
resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
dev: true
@@ -9468,6 +9606,11 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
+ /pify@2.3.0:
+ resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
/pify@4.0.1:
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
engines: {node: '>=6'}
@@ -9483,7 +9626,6 @@ packages:
/pirates@4.0.6:
resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
engines: {node: '>= 6'}
- dev: false
/pkg-dir@3.0.0:
resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==}
@@ -9500,6 +9642,18 @@ packages:
pathe: 1.1.1
dev: true
+ /postcss-import@15.1.0(postcss@8.4.32):
+ resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ postcss: ^8.0.0
+ dependencies:
+ postcss: 8.4.32
+ postcss-value-parser: 4.2.0
+ read-cache: 1.0.0
+ resolve: 1.22.4
+ dev: true
+
/postcss-js@4.0.1(postcss@8.4.32):
resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
engines: {node: ^12 || ^14 || >= 16}
@@ -9508,7 +9662,23 @@ packages:
dependencies:
camelcase-css: 2.0.1
postcss: 8.4.32
- dev: false
+
+ /postcss-load-config@4.0.2(postcss@8.4.32):
+ resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
+ engines: {node: '>= 14'}
+ peerDependencies:
+ postcss: '>=8.0.9'
+ ts-node: '>=9.0.0'
+ peerDependenciesMeta:
+ postcss:
+ optional: true
+ ts-node:
+ optional: true
+ dependencies:
+ lilconfig: 3.0.0
+ postcss: 8.4.32
+ yaml: 2.3.4
+ dev: true
/postcss-mixins@9.0.4(postcss@8.4.32):
resolution: {integrity: sha512-XVq5jwQJDRu5M1XGkdpgASqLk37OqkH4JCFDXl/Dn7janOJjCTEKL+36cnRVy7bMtoBzALfO7bV7nTIsFnUWLA==}
@@ -9531,7 +9701,6 @@ packages:
dependencies:
postcss: 8.4.32
postcss-selector-parser: 6.0.15
- dev: false
/postcss-preset-mantine@1.12.2(postcss@8.4.32):
resolution: {integrity: sha512-a7W/lDSeMg/LeOKb/PNKp+pVzYSSxxWFHGihnwRBEGzeY0qCZnPluH5KsJMfxqbElcf5zoiXZIyElzI/0KmgjA==}
@@ -9549,7 +9718,6 @@ packages:
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
- dev: false
/postcss-simple-vars@7.0.1(postcss@8.4.32):
resolution: {integrity: sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A==}
@@ -9560,6 +9728,10 @@ packages:
postcss: 8.4.32
dev: false
+ /postcss-value-parser@4.2.0:
+ resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+ dev: true
+
/postcss@8.4.31:
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
engines: {node: ^10 || ^12 || >=14}
@@ -9904,6 +10076,12 @@ packages:
dependencies:
loose-envify: 1.4.0
+ /read-cache@1.0.0:
+ resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
+ dependencies:
+ pify: 2.3.0
+ dev: true
+
/read-pkg-up@9.1.0:
resolution: {integrity: sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -9942,6 +10120,13 @@ packages:
string_decoder: 1.3.0
util-deprecate: 1.0.2
+ /readdirp@3.6.0:
+ resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+ engines: {node: '>=8.10.0'}
+ dependencies:
+ picomatch: 2.3.1
+ dev: true
+
/readline@1.3.0:
resolution: {integrity: sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==}
dev: false
@@ -10741,6 +10926,20 @@ packages:
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
dev: false
+ /sucrase@3.35.0:
+ resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ hasBin: true
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.3
+ commander: 4.1.1
+ glob: 10.3.10
+ lines-and-columns: 1.2.4
+ mz: 2.7.0
+ pirates: 4.0.6
+ ts-interface-checker: 0.1.13
+ dev: true
+
/sudo-prompt@9.2.1:
resolution: {integrity: sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==}
dev: false
@@ -10785,6 +10984,43 @@ packages:
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
dev: false
+ /tailwind-merge@2.2.0:
+ resolution: {integrity: sha512-SqqhhaL0T06SW59+JVNfAqKdqLs0497esifRrZ7jOaefP3o64fdFNDMrAQWZFMxTLJPiHVjRLUywT8uFz1xNWQ==}
+ dependencies:
+ '@babel/runtime': 7.23.7
+ dev: true
+
+ /tailwindcss@3.4.1:
+ resolution: {integrity: sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ arg: 5.0.2
+ chokidar: 3.5.3
+ didyoumean: 1.2.2
+ dlv: 1.1.3
+ fast-glob: 3.3.1
+ glob-parent: 6.0.2
+ is-glob: 4.0.3
+ jiti: 1.21.0
+ lilconfig: 2.1.0
+ micromatch: 4.0.5
+ normalize-path: 3.0.0
+ object-hash: 3.0.0
+ picocolors: 1.0.0
+ postcss: 8.4.32
+ postcss-import: 15.1.0(postcss@8.4.32)
+ postcss-js: 4.0.1(postcss@8.4.32)
+ postcss-load-config: 4.0.2(postcss@8.4.32)
+ postcss-nested: 6.0.1(postcss@8.4.32)
+ postcss-selector-parser: 6.0.15
+ resolve: 1.22.4
+ sucrase: 3.35.0
+ transitivePeerDependencies:
+ - ts-node
+ dev: true
+
/tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'}
@@ -10837,6 +11073,19 @@ packages:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
dev: true
+ /thenify-all@1.6.0:
+ resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
+ engines: {node: '>=0.8'}
+ dependencies:
+ thenify: 3.3.1
+ dev: true
+
+ /thenify@3.3.1:
+ resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+ dependencies:
+ any-promise: 1.3.0
+ dev: true
+
/throat@5.0.0:
resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==}
dev: false
@@ -11021,6 +11270,10 @@ packages:
typescript: 5.3.3
dev: true
+ /ts-interface-checker@0.1.13:
+ resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+ dev: true
+
/tsconfig-paths@3.15.0:
resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
dependencies:
@@ -11137,7 +11390,6 @@ packages:
resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==}
engines: {node: '>=14.17'}
hasBin: true
- dev: true
/typescript@5.3.3:
resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
@@ -11902,6 +12154,11 @@ packages:
engines: {node: '>= 14'}
dev: false
+ /yaml@2.3.4:
+ resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==}
+ engines: {node: '>= 14'}
+ dev: true
+
/yargs-parser@18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
From 303cabf25af35050132a7da4b3e34cc877459a16 Mon Sep 17 00:00:00 2001
From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com>
Date: Tue, 9 Jan 2024 11:39:03 +0100
Subject: [PATCH 02/10] chore: update text field component
---
.../reusable-styled-form-component/index.html | 2 +-
.../package.json | 1 +
.../src/Form.tsx | 127 ++++++++++++++++++
.../src/Input.tsx | 28 ++++
.../src/Label.tsx | 23 ++++
.../src/index.tsx | 38 +++++-
.../src/variants.ts | 23 ++++
pnpm-lock.yaml | 11 ++
8 files changed, 251 insertions(+), 2 deletions(-)
create mode 100644 examples/react/reusable-styled-form-component/src/Form.tsx
create mode 100644 examples/react/reusable-styled-form-component/src/Input.tsx
create mode 100644 examples/react/reusable-styled-form-component/src/Label.tsx
create mode 100644 examples/react/reusable-styled-form-component/src/variants.ts
diff --git a/examples/react/reusable-styled-form-component/index.html b/examples/react/reusable-styled-form-component/index.html
index 87450a047..fa8314fd9 100644
--- a/examples/react/reusable-styled-form-component/index.html
+++ b/examples/react/reusable-styled-form-component/index.html
@@ -6,7 +6,7 @@
TanStack Reusable Styled Form Component
-
+
diff --git a/examples/react/reusable-styled-form-component/package.json b/examples/react/reusable-styled-form-component/package.json
index f4ad65d66..b3f5386bf 100644
--- a/examples/react/reusable-styled-form-component/package.json
+++ b/examples/react/reusable-styled-form-component/package.json
@@ -8,6 +8,7 @@
"preview": "vite preview"
},
"dependencies": {
+ "@heroicons/react": "^2.1.1",
"@tanstack/react-form": "<1.0.0",
"@tanstack/zod-form-adapter": "<1.0.0",
"cva": "1.0.0-beta.1",
diff --git a/examples/react/reusable-styled-form-component/src/Form.tsx b/examples/react/reusable-styled-form-component/src/Form.tsx
new file mode 100644
index 000000000..f9b74a0c5
--- /dev/null
+++ b/examples/react/reusable-styled-form-component/src/Form.tsx
@@ -0,0 +1,127 @@
+import * as React from 'react'
+import { type FormApi, type FormOptions, useForm } from '@tanstack/react-form'
+import { ExclamationCircleIcon } from '@heroicons/react/20/solid'
+import { zodValidator } from '@tanstack/zod-form-adapter'
+import { cx } from './utils'
+import { Input } from './Input'
+import { Label } from './Label'
+
+type FormProps = any
+
+type FormFieldProps = any
+
+type FormFieldTextProps = any
+
+type FormFieldCheckboxProps = any
+
+type FormFieldSelectProps = any
+
+type FormComponentProps = React.ForwardRefExoticComponent & {
+ Field: FormFieldProps & {
+ Text: React.ForwardRefExoticComponent
+ Checkbox: React.ForwardRefExoticComponent
+ Select: React.ForwardRefExoticComponent
+ }
+}
+
+type FormContextProps = FormApi, typeof zodValidator>
+
+const FormContext = React.createContext(
+ {} as FormContextProps,
+)
+
+const Form = React.forwardRef(
+ ({ className, formOptions, ...props }, ref) => {
+ const HeadlessForm = useForm({
+ validatorAdapter: zodValidator,
+ ...formOptions,
+ })
+
+ return (
+
+
+
+
+ )
+ },
+) as FormComponentProps
+
+Form.displayName = 'Form'
+
+Form.Field = ({ ...props }: FormFieldProps) => {
+ const HeadlessForm = React.useContext(FormContext)
+ return
+}
+
+Form.Field.displayName = 'Form.Field'
+
+Form.Field.Text = React.forwardRef(
+ ({ className, field, label, ...props }, ref) => {
+ const formMessageId = `${field.name}-message`
+ return (
+
+ 0}
+ type="text"
+ id={field.name}
+ name={field.name}
+ placeholder={label}
+ value={field.state.value}
+ onBlur={field.handleBlur}
+ onChange={(e) => field.handleChange(e.target.value)}
+ aria-describedby={
+ field.state.meta.errors.length > 0 ? `${formMessageId}` : ''
+ }
+ aria-invalid={field.state.meta.errors.length > 0}
+ ref={ref}
+ {...props}
+ />
+
+ 0 &&
+ 'peer-focus:opacity-100 text-red-500',
+ )}
+ />
+ 0 &&
+ 'peer-focus:opacity-100 text-red-500',
+ )}
+ aria-hidden={field.state.meta.errors.length > 0 ? 'false' : 'true'}
+ >
+ {field.state.meta.touchedErrors}
+
+
+ )
+ },
+)
+
+Form.Field.Text.displayName = 'Form.Field.Text'
+
+export { Form, type FormProps, type FormFieldProps }
diff --git a/examples/react/reusable-styled-form-component/src/Input.tsx b/examples/react/reusable-styled-form-component/src/Input.tsx
new file mode 100644
index 000000000..3389dad32
--- /dev/null
+++ b/examples/react/reusable-styled-form-component/src/Input.tsx
@@ -0,0 +1,28 @@
+import * as React from 'react'
+import { type inputVariantProps, inputVariants } from './variants'
+
+type InputProps = React.InputHTMLAttributes &
+ Omit & {
+ invalid?: boolean
+ }
+
+const Input = React.forwardRef(
+ ({ className, type, invalid = false, ...props }, ref) => {
+ return (
+
+ )
+ },
+)
+
+Input.displayName = 'Input'
+
+export { Input, inputVariants, type InputProps }
diff --git a/examples/react/reusable-styled-form-component/src/Label.tsx b/examples/react/reusable-styled-form-component/src/Label.tsx
new file mode 100644
index 000000000..0a5deecb3
--- /dev/null
+++ b/examples/react/reusable-styled-form-component/src/Label.tsx
@@ -0,0 +1,23 @@
+import * as React from 'react'
+import { cx } from './utils'
+
+type LabelProps = React.LabelHTMLAttributes
+
+const Label = React.forwardRef(
+ ({ className, ...props }, ref) => {
+ return (
+
+ )
+ },
+)
+
+Label.displayName = 'Label'
+
+export { Label, type LabelProps }
diff --git a/examples/react/reusable-styled-form-component/src/index.tsx b/examples/react/reusable-styled-form-component/src/index.tsx
index abe414dc5..46c6aa6ac 100644
--- a/examples/react/reusable-styled-form-component/src/index.tsx
+++ b/examples/react/reusable-styled-form-component/src/index.tsx
@@ -1,10 +1,46 @@
import * as React from 'react'
import { createRoot } from 'react-dom/client'
+import { Form } from './Form'
import { z } from 'zod'
export default function App() {
return (
-
+
+
{
+ return
+ }}
+ />
+ /[A-Z]/.test(value), {
+ message: 'Password must include a capital letter',
+ })
+ .refine((value) => /\d/.test(value), {
+ message: 'Password must include a number',
+ }),
+ }}
+ children={(field) => {
+ return
+ }}
+ />
+
+
)
}
diff --git a/examples/react/reusable-styled-form-component/src/variants.ts b/examples/react/reusable-styled-form-component/src/variants.ts
new file mode 100644
index 000000000..d445382e0
--- /dev/null
+++ b/examples/react/reusable-styled-form-component/src/variants.ts
@@ -0,0 +1,23 @@
+import { type VariantProps, cva } from './utils'
+
+const inputVariants = cva({
+ base: 'peer select-none appearance-none border outline-none border-gray-300 transition-colors focus-visible:border-blue-500 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-700 focus-visible:dark:border-blue-500',
+ variants: {
+ type: {
+ text: 'flex h-10 w-full rounded-md bg-white dark:bg-black px-3 py-2 text-sm placeholder:text-gray-500',
+ password:
+ 'flex h-10 w-full rounded-md bg-white dark:bg-black px-3 py-2 text-sm placeholder:text-gray-500',
+ checkbox:
+ 'h-4 w-4 cursor-pointer rounded-sm bg-white dark:bg-black transition-colors checked:border-blue-500 checked:bg-blue-500 checked:hover:bg-blue-500 checked:focus:bg-blue-500 checked:dark:border-blue-500',
+ number:
+ 'flex h-10 w-full rounded-md bg-white dark:bg-black px-3 py-2 text-sm [appearance:textfield] placeholder:text-gray-500 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none',
+ },
+ invalid: {
+ true: 'border-red-500 focus-visible:border-red-500 dark:border-red-500 focus-visible:dark:border-red-500',
+ },
+ },
+})
+
+type inputVariantProps = VariantProps
+
+export { inputVariants, type inputVariantProps }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d8b927997..3c7eb84fd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -150,6 +150,9 @@ importers:
examples/react/reusable-styled-form-component:
dependencies:
+ '@heroicons/react':
+ specifier: ^2.1.1
+ version: 2.1.1(react@18.2.0)
'@tanstack/react-form':
specifier: <1.0.0
version: link:../../../packages/react-form
@@ -2628,6 +2631,14 @@ packages:
'@hapi/hoek': 9.3.0
dev: false
+ /@heroicons/react@2.1.1(react@18.2.0):
+ resolution: {integrity: sha512-JyyN9Lo66kirbCMuMMRPtJxtKJoIsXKS569ebHGGRKbl8s4CtUfLnyKJxteA+vIKySocO4s1SkTkGS4xtG/yEA==}
+ peerDependencies:
+ react: '>= 16'
+ dependencies:
+ react: 18.2.0
+ dev: false
+
/@humanwhocodes/config-array@0.11.13:
resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==}
engines: {node: '>=10.10.0'}
From fce47e8b04041306651753eefad1ca92c5b707cc Mon Sep 17 00:00:00 2001
From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com>
Date: Wed, 10 Jan 2024 18:38:33 +0100
Subject: [PATCH 03/10] feat: add checkbox field sub-component and seperated
out error component
This commit adds a new checkbox input component and takes out the error message span and icon from the Text component into it's own component.
---
.../package.json | 2 +-
.../src/Form.tsx | 86 +++++++++++++++----
.../src/Label.tsx | 2 +-
.../src/index.tsx | 33 +++++--
pnpm-lock.yaml | 12 +--
5 files changed, 102 insertions(+), 33 deletions(-)
diff --git a/examples/react/reusable-styled-form-component/package.json b/examples/react/reusable-styled-form-component/package.json
index b3f5386bf..bd528d656 100644
--- a/examples/react/reusable-styled-form-component/package.json
+++ b/examples/react/reusable-styled-form-component/package.json
@@ -10,7 +10,7 @@
"dependencies": {
"@heroicons/react": "^2.1.1",
"@tanstack/react-form": "<1.0.0",
- "@tanstack/zod-form-adapter": "<1.0.0",
+ "@tanstack/zod-form-adapter": "workspace:^",
"cva": "1.0.0-beta.1",
"react": "^18.0.0",
"react-dom": "^18.0.0",
diff --git a/examples/react/reusable-styled-form-component/src/Form.tsx b/examples/react/reusable-styled-form-component/src/Form.tsx
index f9b74a0c5..43ed3bf41 100644
--- a/examples/react/reusable-styled-form-component/src/Form.tsx
+++ b/examples/react/reusable-styled-form-component/src/Form.tsx
@@ -12,12 +12,15 @@ type FormFieldProps = any
type FormFieldTextProps = any
+type FormFieldErrorProps = any
+
type FormFieldCheckboxProps = any
type FormFieldSelectProps = any
type FormComponentProps = React.ForwardRefExoticComponent & {
Field: FormFieldProps & {
+ Error: React.ForwardRefExoticComponent
Text: React.ForwardRefExoticComponent
Checkbox: React.ForwardRefExoticComponent
Select: React.ForwardRefExoticComponent
@@ -66,16 +69,20 @@ Form.Field = ({ ...props }: FormFieldProps) => {
Form.Field.displayName = 'Form.Field'
Form.Field.Text = React.forwardRef(
- ({ className, field, label, ...props }, ref) => {
- const formMessageId = `${field.name}-message`
+ ({ className, children, field, label, ...props }, ref) => {
+ const childWithField = React.Children.map(children, (child) =>
+ React.isValidElement(child)
+ ? React.cloneElement(child, { field })
+ : child,
+ )
return (
-
+
0}
+ invalid={field.state.meta.errors.length !== 0}
type="text"
id={field.name}
name={field.name}
@@ -84,44 +91,89 @@ Form.Field.Text = React.forwardRef(
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
aria-describedby={
- field.state.meta.errors.length > 0 ? `${formMessageId}` : ''
+ field.state.meta.errors.length !== 0 && children
+ ? `${field.name}-error`
+ : ''
}
- aria-invalid={field.state.meta.errors.length > 0}
+ aria-invalid={field.state.meta.errors.length !== 0}
ref={ref}
{...props}
/>
+ {childWithField}
+
+ )
+ },
+)
+
+Form.Field.Text.displayName = 'Form.Field.Text'
+
+Form.Field.Error = React.forwardRef
(
+ ({ className, field, ...props }, ref) => {
+ return (
+ <>
0 &&
- 'peer-focus:opacity-100 text-red-500',
+ 'absolute h-4 w-4 translate-x-[3px] translate-y-[42px] text-red-500 opacity-0 transition-opacity',
+ field.state.meta.errors.length !== 0 && 'peer-focus:opacity-100',
)}
/>
0 &&
- 'peer-focus:opacity-100 text-red-500',
+ 'absolute translate-x-5 leading-none translate-y-[42px] text-xs text-red-500 opacity-0 transition-opacity',
+ field.state.meta.errors.length !== 0 && 'peer-focus:opacity-100',
)}
- aria-hidden={field.state.meta.errors.length > 0 ? 'false' : 'true'}
+ id={`${field.name}-error`}
+ aria-hidden={field.state.meta.errors.length !== 0 ? 'false' : 'true'}
+ ref={ref}
+ {...props}
>
- {field.state.meta.touchedErrors}
+ {field.state.meta.errors.length !== 0 &&
+ field.state.meta.errors[0].split(', ')[0]}
-
+ >
)
},
)
-Form.Field.Text.displayName = 'Form.Field.Text'
+Form.Field.Error.displayName = 'Form.Field.Error'
+
+Form.Field.Checkbox = React.forwardRef<
+ HTMLInputElement,
+ FormFieldCheckboxProps
+>(({ className, children, field, label, ...props }, ref) => {
+ const childWithField = React.Children.map(children, (child) =>
+ React.isValidElement(child) ? React.cloneElement(child, { field }) : child,
+ )
+ return (
+
+ field.handleChange(e.target.checked)}
+ ref={ref}
+ {...props}
+ />
+
+ {childWithField}
+
+ )
+})
export { Form, type FormProps, type FormFieldProps }
diff --git a/examples/react/reusable-styled-form-component/src/Label.tsx b/examples/react/reusable-styled-form-component/src/Label.tsx
index 0a5deecb3..0e641c625 100644
--- a/examples/react/reusable-styled-form-component/src/Label.tsx
+++ b/examples/react/reusable-styled-form-component/src/Label.tsx
@@ -8,7 +8,7 @@ const Label = React.forwardRef(
return (