diff --git a/package.json b/package.json index b8c44aff6..e67452f41 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "@better-upload/client": "3.0.12", "@better-upload/server": "3.0.12", "@fontsource-variable/inter": "5.2.8", - "@hookform/resolvers": "5.2.2", "@orpc/client": "1.13.4", "@orpc/openapi": "1.13.4", "@orpc/react-query": "1.13.4", @@ -78,6 +77,7 @@ "@t3-oss/env-core": "0.13.10", "@tanstack/devtools-vite": "0.5.2", "@tanstack/react-devtools": "0.9.7", + "@tanstack/react-form": "^1.33.0", "@tanstack/react-query": "5.90.21", "@tanstack/react-query-devtools": "5.91.3", "@tanstack/react-router": "1.163.3", @@ -105,7 +105,6 @@ "react-day-picker": "9.13.0", "react-dom": "19.2.4", "react-error-boundary": "6.1.0", - "react-hook-form": "7.71.1", "react-i18next": "16.5.4", "react-merge-refs": "3.0.2", "react-textarea-autosize": "8.5.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a4761e38..bbf5592c7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,9 +26,6 @@ importers: '@fontsource-variable/inter': specifier: 5.2.8 version: 5.2.8 - '@hookform/resolvers': - specifier: 5.2.2 - version: 5.2.2(react-hook-form@7.71.1(react@19.2.4)) '@orpc/client': specifier: 1.13.4 version: 1.13.4 @@ -62,6 +59,9 @@ importers: '@tanstack/react-devtools': specifier: 0.9.7 version: 0.9.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11) + '@tanstack/react-form': + specifier: ^1.33.0 + version: 1.33.0(@tanstack/react-start@1.166.1(crossws@0.4.4(srvx@0.11.7))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.5(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-query': specifier: 5.90.21 version: 5.90.21(react@19.2.4) @@ -143,9 +143,6 @@ importers: react-error-boundary: specifier: 6.1.0 version: 6.1.0(react@19.2.4) - react-hook-form: - specifier: 7.71.1 - version: 7.71.1(react@19.2.4) react-i18next: specifier: 16.5.4 version: 16.5.4(i18next@25.8.0(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) @@ -948,11 +945,6 @@ packages: '@fontsource-variable/inter@5.2.8': resolution: {integrity: sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==} - '@hookform/resolvers@5.2.2': - resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==} - peerDependencies: - react-hook-form: ^7.55.0 - '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -1184,56 +1176,48 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-arm64-musl@0.36.0': resolution: {integrity: sha512-SPGLJkOIHSIC6ABUQ5V8NqJpvYhMJueJv26NYqfCnwi/Mn6A61amkpJJ9Suy0Nmvs+OWESJpcebrBUbXPGZyQQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@oxfmt/binding-linux-ppc64-gnu@0.36.0': resolution: {integrity: sha512-3EuoyB8x9x8ysYJjbEO/M9fkSk72zQKnXCvpZMDHXlnY36/1qMp55Nm0PrCwjGO/1pen5hdOVkz9WmP3nAp2IQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-riscv64-gnu@0.36.0': resolution: {integrity: sha512-MpY3itLwpGh8dnywtrZtaZ604T1m715SydCKy0+qTxetv+IHzuA+aO/AGzrlzUNYZZmtWtmDBrChZGibvZxbRQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-riscv64-musl@0.36.0': resolution: {integrity: sha512-mmDhe4Vtx+XwQPRPn/V25+APnkApYgZ23q+6GVsNYY98pf3aU0aI3Me96pbRs/AfJ1jIiGC+/6q71FEu8dHcHw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [musl] '@oxfmt/binding-linux-s390x-gnu@0.36.0': resolution: {integrity: sha512-AYXhU+DmNWLSnvVwkHM92fuYhogtVHab7UQrPNaDf1sxadugg9gWVmcgJDlIwxJdpk5CVW/TFvwUKwI432zhhA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-x64-gnu@0.36.0': resolution: {integrity: sha512-H16QhhQ3usoakMleiAAQ2mg0NsBDAdyE9agUgfC8IHHh3jZEbr0rIKwjEqwbOHK5M0EmfhJmr+aGO/MgZPsneA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-x64-musl@0.36.0': resolution: {integrity: sha512-EFFGkixA39BcmHiCe2ECdrq02D6FCve5ka6ObbvrheXl4V+R0U/E+/uLyVx1X65LW8TA8QQHdnbdDallRekohw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@oxfmt/binding-openharmony-arm64@0.36.0': resolution: {integrity: sha512-zr/t369wZWFOj1qf06Z5gGNjFymfUNDrxKMmr7FKiDRVI1sNsdKRCuRL4XVjtcptKQ+ao3FfxLN1vrynivmCYg==} @@ -1306,56 +1290,48 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-arm64-musl@1.54.0': resolution: {integrity: sha512-WBNFe1foIFg6ipgzjzJfDLn7C5nZpLr/UHlnlT7GI3scCD2xpJiJTGMTTpJqA8p0Q1l2NsEnAOj9PtreF0Tkzg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@oxlint/binding-linux-ppc64-gnu@1.54.0': resolution: {integrity: sha512-M+UAW76rZHHiYKKy4e7Te5MNikANiFhYWl0qYF1MTIhQczYIqWVDQ+SX0SzW8ipOB/oK3+enOvlvJuhMoA968Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-riscv64-gnu@1.54.0': resolution: {integrity: sha512-sGassljr8TR5F1WCqOaacrp3mYi107MnjkqlSXOMHACps7U2bL4v7M3RY7P7NMcGkNhzRHF3VO5+XyPWhTVM6w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-riscv64-musl@1.54.0': resolution: {integrity: sha512-mW5Z6XTO8QWtlUjlf6yjntarjnbrmGVSK/V6XYy2rjH8xUfv9pn9G1vO92DBBBi0eUwnVpcOY3hMkP851WZNWg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [musl] '@oxlint/binding-linux-s390x-gnu@1.54.0': resolution: {integrity: sha512-nfPmEGW9BfWEUD0PVXGaYa2RS4wVblofNih1gsyUoLSWSyliNisIWd7F+QXrDSL5SJ78BPZjDW6FainV8qRiTg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@oxlint/binding-linux-x64-gnu@1.54.0': resolution: {integrity: sha512-gybxMQx4NN1T+pa8TLwgVS4u9H9Hxwm7Sl0qvnQJF2WRhNN1oTOCzkmmCBbW/29DYLeV99WU76zh7srCamK9yw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-x64-musl@1.54.0': resolution: {integrity: sha512-EAXMh2w3pzSj/aiB67kXzcHIoKxAywC1i1IgxA1sLY7iffEaZowDTOJgirY7WxeR/WiiKwak89gPeh9wUdLnIw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@oxlint/binding-openharmony-arm64@1.54.0': resolution: {integrity: sha512-Ih7CfITkbw86+LjgV5gDwNmNQlvz7X+51gdeLLUDwuA/9lojDxCmRQEzeMl44KStQlwzCuNIcgGMae0MllV5ww==} @@ -1602,28 +1578,24 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.5': resolution: {integrity: sha512-WFljyDkxtXRlWxMjxeegf7xMYXxUr8u7JdXlOEWKYgDqEgxUnSEsVDxBiNWQ1D5kQKwf8Wo4sVKEYPRhCdsjwA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.5': resolution: {integrity: sha512-CUlplTujmbDWp2gamvrqVKi2Or8lmngXT1WxsizJfts7JrvfGhZObciaY/+CbdbS9qNnskvwMZNEhTPrn7b+WA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.5': resolution: {integrity: sha512-wdf7g9NbVZCeAo2iGhsjJb7I8ZFfs6X8bumfrWg82VK+8P6AlLXwk48a1ASiJQDTS7Svq2xVzZg3sGO2aXpHRA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.5': resolution: {integrity: sha512-0CWY7ubu12nhzz+tkpHjoG3IRSTlWYe0wrfJRf4qqjqQSGtAYgoL9kwzdvlhaFdZ5ffVeyYw9qLsChcjUMEloQ==} @@ -1700,79 +1672,66 @@ packages: resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.60.1': resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.60.1': resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.60.1': resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.60.1': resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.60.1': resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} cpu: [loong64] os: [linux] - libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.60.1': resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.60.1': resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} cpu: [ppc64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.60.1': resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.60.1': resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.60.1': resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.1': resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.60.1': resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openbsd-x64@4.60.1': resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} @@ -1844,9 +1803,6 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@standard-schema/utils@0.3.0': - resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} - '@storybook/addon-a11y@10.2.14': resolution: {integrity: sha512-So/Pf8IHEV1XcCHjwPUWs55cB5PilqS3TEho1IXDWpQHBmxBHgX+Z2lQ/SrfqqtMWwy3YY95YO3fA2fn7To5Yg==} peerDependencies: @@ -2056,28 +2012,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.18': resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.18': resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.18': resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.18': resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} @@ -2122,6 +2074,11 @@ packages: resolution: {integrity: sha512-RPfGuk2bDZgcu9bAJodvO2lnZeHuz4/71HjZ0bGb/SPg8+lyTA+RLSKQvo7fSmPSi8/vcH3aKQ8EM9ywf1olaw==} engines: {node: '>=18'} + '@tanstack/devtools-event-client@0.4.4': + resolution: {integrity: sha512-6T5Yop/793YI+H+5J8Hsyj4kCih9sl4t3ElLgKioW5hk3ocn+ZdSJ94tT7vL7uabxSugWYBZlOTMPzEw2puvQw==} + engines: {node: '>=18'} + hasBin: true + '@tanstack/devtools-ui@0.4.4': resolution: {integrity: sha512-5xHXFyX3nom0UaNfiOM92o6ziaHjGo3mcSGe2HD5Xs8dWRZNpdZ0Smd0B9ddEhy0oB+gXyMzZgUJb9DmrZV0Mg==} engines: {node: '>=18'} @@ -2154,10 +2111,17 @@ packages: peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + '@tanstack/form-core@1.33.0': + resolution: {integrity: sha512-AV4Pw9Dk4orFsuPBcDssfWMJFs+yMYBae7zZ4oTqrCf4ftNGQKxvrQRZeqKHG6A4TkiLeSvf2kzIjcVkrW7E6w==} + '@tanstack/history@1.161.4': resolution: {integrity: sha512-Kp/WSt411ZWYvgXy6uiv5RmhHrz9cAml05AQPrtdAp7eUqvIDbMGPnML25OKbzR3RJ1q4wgENxDTvlGPa9+Mww==} engines: {node: '>=20.19'} + '@tanstack/pacer-lite@0.1.1': + resolution: {integrity: sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w==} + engines: {node: '>=18'} + '@tanstack/query-core@5.90.20': resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} @@ -2173,6 +2137,15 @@ packages: react: '>=16.8' react-dom: '>=16.8' + '@tanstack/react-form@1.33.0': + resolution: {integrity: sha512-unaee+VS4MvKo+s1dmgGUXI4902VeAhuaUbKsQbhFe3MceOpB3JpAUGCDpyzjQPXVFkFY0COKfLrUNX2XZYW4g==} + peerDependencies: + '@tanstack/react-start': '*' + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@tanstack/react-start': + optional: true + '@tanstack/react-query-devtools@5.91.3': resolution: {integrity: sha512-nlahjMtd/J1h7IzOOfqeyDh5LNfG0eULwlltPEonYy0QL+nqrBB+nyzJfULV+moL7sZyxc2sHdNJki+vLA9BSA==} peerDependencies: @@ -2225,6 +2198,12 @@ packages: react-dom: '>=18.0.0 || >=19.0.0' vite: '>=7.0.0' + '@tanstack/react-store@0.11.0': + resolution: {integrity: sha512-tX4YXh3PDkmpvGQWkWqKpzs/MSqbtuwY9dWdWhtV9Q50PmO+jOkUKIWIX4G85dwt7lxdHLXsiaEKPdKmC8F41w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@tanstack/react-store@0.9.1': resolution: {integrity: sha512-YzJLnRvy5lIEFTLWBAZmcOjK3+2AepnBv/sr6NZmiqJvq7zTQggyK99Gw8fqYdMdHPQWXjz0epFKJXC+9V2xDA==} peerDependencies: @@ -2296,6 +2275,9 @@ packages: resolution: {integrity: sha512-JJfY4Ikz9yg9gye+PCsMoUO765UBjiFmwLywUjr5W3hJ5uUImdOGClh5ovuxw5pkVaWhw5r7YDRlmAyOkZOCmA==} engines: {node: '>=22.12.0'} + '@tanstack/store@0.11.0': + resolution: {integrity: sha512-WlzzCt3xi0G6pCAJu1U+2jiECwabETDpQDi3hfkFZvJii9AuZqEKbOiVarX1/bWhTNjU486yQtJCCasi/0q+Cw==} + '@tanstack/store@0.9.1': resolution: {integrity: sha512-+qcNkOy0N1qSGsP7omVCW0SDrXtaDcycPqBDE726yryiA5eTDFpjBReaYjghVJwNf1pcPMyzIwTGlYjCSQR0Fg==} @@ -3924,28 +3906,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -4493,12 +4471,6 @@ packages: peerDependencies: react: ^18.0.0 || ^19.0.0 - react-hook-form@7.71.1: - resolution: {integrity: sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w==} - engines: {node: '>=18.0.0'} - peerDependencies: - react: ^16.8.0 || ^17 || ^18 || ^19 - react-i18next@16.5.4: resolution: {integrity: sha512-6yj+dcfMncEC21QPhOTsW8mOSO+pzFmT6uvU7XXdvM/Cp38zJkmTeMeKmTrmCMD5ToT79FmiE/mRWiYWcJYW4g==} peerDependencies: @@ -5950,11 +5922,6 @@ snapshots: '@fontsource-variable/inter@5.2.8': {} - '@hookform/resolvers@5.2.2(react-hook-form@7.71.1(react@19.2.4))': - dependencies: - '@standard-schema/utils': 0.3.0 - react-hook-form: 7.71.1(react@19.2.4) - '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -6663,8 +6630,6 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@standard-schema/utils@0.3.0': {} - '@storybook/addon-a11y@10.2.14(storybook@10.2.14(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': dependencies: '@storybook/global': 5.0.0 @@ -6944,6 +6909,8 @@ snapshots: '@tanstack/devtools-event-client@0.4.0': {} + '@tanstack/devtools-event-client@0.4.4': {} + '@tanstack/devtools-ui@0.4.4(csstype@3.2.3)(solid-js@1.9.11)': dependencies: clsx: 2.1.1 @@ -7003,8 +6970,16 @@ snapshots: - supports-color - typescript + '@tanstack/form-core@1.33.0': + dependencies: + '@tanstack/devtools-event-client': 0.4.4 + '@tanstack/pacer-lite': 0.1.1 + '@tanstack/store': 0.11.0 + '@tanstack/history@1.161.4': {} + '@tanstack/pacer-lite@0.1.1': {} + '@tanstack/query-core@5.90.20': {} '@tanstack/query-devtools@5.93.0': {} @@ -7022,6 +6997,16 @@ snapshots: - solid-js - utf-8-validate + '@tanstack/react-form@1.33.0(@tanstack/react-start@1.166.1(crossws@0.4.4(srvx@0.11.7))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.5(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@tanstack/form-core': 1.33.0 + '@tanstack/react-store': 0.11.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@tanstack/react-start': 1.166.1(crossws@0.4.4(srvx@0.11.7))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.5(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)) + transitivePeerDependencies: + - react-dom + '@tanstack/react-query-devtools@5.91.3(@tanstack/react-query@5.90.21(react@19.2.4))(react@19.2.4)': dependencies: '@tanstack/query-devtools': 5.93.0 @@ -7097,6 +7082,13 @@ snapshots: - vite-plugin-solid - webpack + '@tanstack/react-store@0.11.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@tanstack/store': 0.11.0 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + use-sync-external-store: 1.6.0(react@19.2.4) + '@tanstack/react-store@0.9.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@tanstack/store': 0.9.1 @@ -7230,6 +7222,8 @@ snapshots: dependencies: '@tanstack/router-core': 1.163.3 + '@tanstack/store@0.11.0': {} + '@tanstack/store@0.9.1': {} '@tanstack/virtual-file-routes@1.161.4': {} @@ -9448,10 +9442,6 @@ snapshots: dependencies: react: 19.2.4 - react-hook-form@7.71.1(react@19.2.4): - dependencies: - react: 19.2.4 - react-i18next@16.5.4(i18next@25.8.0(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): dependencies: '@babel/runtime': 7.28.6 diff --git a/src/components/form/docs.stories.tsx b/src/components/form/docs.stories.tsx index f4fc8e829..0aaaa6769 100644 --- a/src/components/form/docs.stories.tsx +++ b/src/components/form/docs.stories.tsx @@ -1,6 +1,4 @@ -import { zodResolver } from '@hookform/resolvers/zod'; import { Meta } from '@storybook/react-vite'; -import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zu } from '@/lib/zod/zod-utils'; @@ -12,6 +10,7 @@ import { FormFieldError, FormFieldHelper, FormFieldLabel, + useForm, } from '@/components/form'; import { onSubmit } from '@/components/form/docs.utils'; import { Button } from '@/components/ui/button'; @@ -34,26 +33,27 @@ const zFormSchema = () => export const Default = () => { const form = useForm({ - mode: 'onBlur', - resolver: zodResolver(zFormSchema()), + schema: zFormSchema(), + mode: 'blur', defaultValues: { name: '', other: '', }, + onSubmit, }); return ( -
+
Name - + This is an helper text Other (Custom) ( @@ -63,9 +63,11 @@ export const Default = () => { https:// field.handleChange(e.target.value)} + onBlur={field.handleBlur} + aria-invalid={!fieldState.meta.isValid ? true : undefined} /> @@ -83,31 +85,35 @@ export const Default = () => { export const NoHtmlForm = () => { const form = useForm({ - mode: 'onBlur', - resolver: zodResolver(zFormSchema()), + schema: zFormSchema(), + mode: 'blur', defaultValues: { name: '', other: '', }, + onSubmit, }); return ( - - + + { + e.preventDefault(); + e.stopPropagation(); + form.handleSubmit(); + }} + >
Name - + This is an helper text Other (Custom) ( @@ -117,9 +123,11 @@ export const NoHtmlForm = () => { https:// field.handleChange(e.target.value)} + onBlur={field.handleBlur} + aria-invalid={!fieldState.meta.isValid ? true : undefined} /> diff --git a/src/components/form/field-checkbox-group/docs.stories.tsx b/src/components/form/field-checkbox-group/docs.stories.tsx index bb01ec637..c82f16f1b 100644 --- a/src/components/form/field-checkbox-group/docs.stories.tsx +++ b/src/components/form/field-checkbox-group/docs.stories.tsx @@ -1,8 +1,6 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; import { z } from 'zod'; -import { FormFieldController } from '@/components/form'; +import { FormFieldController, useForm } from '@/components/form'; import { onSubmit } from '@/components/form/docs.utils'; import { Button } from '@/components/ui/button'; @@ -20,8 +18,8 @@ const zFormSchema = () => }); const formOptions = { - mode: 'onBlur', - resolver: zodResolver(zFormSchema()), + schema: zFormSchema(), + mode: 'blur', defaultValues: { bears: [], } as z.infer>, @@ -34,17 +32,17 @@ const options = [ ]; export const Default = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Bearstronaut Select your favorite bearstronaut @@ -63,16 +61,17 @@ export const DefaultValue = () => { defaultValues: { bears: ['pawdrin'], }, + onSubmit, }); return ( - +
Bearstronaut Select your favorite bearstronaut { defaultValues: { bears: ['pawdrin'], }, + onSubmit, }); return ( - +
Bearstronaut Select your favorite bearstronaut { }; export const Row = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Bearstronaut Select your favorite bearstronaut { }; export const WithDisabledOption = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); const optionsWithDisabled = [ { value: 'bearstrong', label: 'Bearstrong' }, @@ -151,13 +151,13 @@ export const WithDisabledOption = () => { ]; return ( - +
Bearstronaut Select your favorite bearstronaut { Bearstronaut @@ -67,7 +67,7 @@ test('should toggle checkbox on label click', async () => { Bearstronaut @@ -102,7 +102,7 @@ test('should allow selecting multiple checkboxes', async () => { Bearstronaut @@ -141,7 +141,7 @@ test('keyboard interaction: toggle with space', async () => { Bearstronaut @@ -179,7 +179,7 @@ test('default values', async () => { Bearstronaut @@ -212,7 +212,7 @@ test('disabled group', async () => { Bearstronaut { const cb = page.getByRole('checkbox', { name: 'Buzz Pawdrin' }); await expect.element(cb).toBeDisabled(); - try { - await user.click(page.getByRole('button', { name: 'Submit' }), { - timeout: FAILED_CLICK_TIMEOUT_MS, - }); - } catch { - expect(mockedSubmit).toHaveBeenCalledWith({ bears: undefined }); - } + await user.click(page.getByRole('button', { name: 'Submit' })); + expect(mockedSubmit).toHaveBeenCalledWith({ bears: ['pawdrin'] }); }); test('disabled option', async () => { @@ -249,7 +244,7 @@ test('disabled option', async () => { Bearstronaut diff --git a/src/components/form/field-checkbox-group/index.tsx b/src/components/form/field-checkbox-group/index.tsx index b9ccd65f9..94aa0ff2d 100644 --- a/src/components/form/field-checkbox-group/index.tsx +++ b/src/components/form/field-checkbox-group/index.tsx @@ -21,30 +21,28 @@ export const FieldCheckboxGroup = ( ) => { const { containerProps, options, ...rest } = props; const ctx = useFormField(); - const { - field: { value, onChange, ...field }, - fieldState, - } = useFormFieldController(); + const { field, fieldState, isInvalid } = useFormFieldController(); return ( { - onChange?.(value); + field.handleChange(value); rest.onValueChange?.(value, event); }} - {...rest} > {options.map(({ label, ...option }) => ( field.handleBlur()} {...option} > {label} diff --git a/src/components/form/field-checkbox/docs.stories.tsx b/src/components/form/field-checkbox/docs.stories.tsx index 091c56ac2..d0a948de1 100644 --- a/src/components/form/field-checkbox/docs.stories.tsx +++ b/src/components/form/field-checkbox/docs.stories.tsx @@ -1,7 +1,5 @@ -import { zodResolver } from '@hookform/resolvers/zod'; import { Meta } from '@storybook/react-vite'; import { CheckIcon } from 'lucide-react'; -import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { cn } from '@/lib/tailwind/utils'; @@ -11,6 +9,7 @@ import { FormField, FormFieldController, FormFieldHelper, + useForm, } from '@/components/form'; import { onSubmit } from '@/components/form/docs.utils'; import { FieldCheckbox } from '@/components/form/field-checkbox'; @@ -29,25 +28,21 @@ const zFormSchema = () => }); const formOptions = { - mode: 'onBlur', - resolver: zodResolver(zFormSchema()), + schema: zFormSchema(), + mode: 'blur', defaultValues: { lovesBears: false, }, } as const; export const Default = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
- + I love bears There is only one possible answer. @@ -66,17 +61,14 @@ export const DefaultValue = () => { defaultValues: { lovesBears: true, }, + onSubmit, }); return ( - +
- + I love bears There is only one possible answer. @@ -95,15 +87,16 @@ export const Disabled = () => { defaultValues: { lovesBears: true, }, + onSubmit, }); return ( - +
@@ -120,16 +113,16 @@ export const Disabled = () => { }; export const CustomCheckbox = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
{ > {({ form }) => ( - + I love bears @@ -65,11 +61,7 @@ test('should select checkbox on label click', async () => { > {({ form }) => ( - + I love bears @@ -101,11 +93,7 @@ test('default value', async () => { > {({ form }) => ( - + I love bears @@ -133,7 +121,7 @@ test('disabled', async () => { @@ -157,5 +145,5 @@ test('disabled', async () => { await expect.element(checkbox).not.toBeChecked(); } await user.click(page.getByRole('button', { name: 'Submit' })); - expect(mockedSubmit).toHaveBeenCalledWith({ lovesBears: undefined }); + expect(mockedSubmit).toHaveBeenCalledWith({ lovesBears: false }); }); diff --git a/src/components/form/field-checkbox/index.tsx b/src/components/form/field-checkbox/index.tsx index 442047d55..a9f552f27 100644 --- a/src/components/form/field-checkbox/index.tsx +++ b/src/components/form/field-checkbox/index.tsx @@ -17,21 +17,25 @@ export const FieldCheckbox = ( const { containerProps, ...rest } = props; const ctx = useFormField(); - const { - field: { value, onChange, ...field }, - fieldState, - } = useFormFieldController(); + const { field, fieldState, isInvalid } = useFormFieldController(); return ( { + field.handleChange(checked); + rest.onCheckedChange?.(checked, eventDetails); + }} + onBlur={(e) => { + field.handleBlur(); + rest.onBlur?.(e); + }} /> diff --git a/src/components/form/field-combobox-multiple/docs.stories.tsx b/src/components/form/field-combobox-multiple/docs.stories.tsx index 0c590cbd9..08501c5f9 100644 --- a/src/components/form/field-combobox-multiple/docs.stories.tsx +++ b/src/components/form/field-combobox-multiple/docs.stories.tsx @@ -1,5 +1,3 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { @@ -7,6 +5,7 @@ import { FormField, FormFieldController, FormFieldLabel, + useForm, } from '@/components/form'; import { onSubmit } from '@/components/form/docs.utils'; import { Avatar, AvatarFallback } from '@/components/ui/avatar'; @@ -57,20 +56,23 @@ const options = [ }[]; const formOptions = { - mode: 'onBlur', - resolver: zodResolver(zFormSchema()), -} as const; + schema: zFormSchema(), + mode: 'blur' as const, + defaultValues: { + bear: [], + }, +}; export const Default = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Bearstronaut { }; export const DefaultValue = () => { - const form = useForm>>({ + const form = useForm({ ...formOptions, defaultValues: { bear: ['1'], }, + onSubmit, }); return ( - +
Bearstronaut { }; export const Disabled = () => { - const form = useForm>>({ + const form = useForm({ ...formOptions, defaultValues: { bear: ['1'], }, + onSubmit, }); return ( - +
Bearstronaut { }; export const CustomOptions = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Bearstronaut { Bearstronaut @@ -69,7 +69,7 @@ test('should allow selecting multiple items', async () => { Bearstronaut @@ -107,7 +107,7 @@ test('default values', async () => { Bearstronaut @@ -137,7 +137,7 @@ test('disabled', async () => { Bearstronaut { await expect.element(input).toBeDisabled(); await user.click(page.getByRole('button', { name: 'Submit' })); - expect(mockedSubmit).toHaveBeenCalledWith({ bears: undefined }); + expect(mockedSubmit).toHaveBeenCalledWith({ bears: ['pawdrin'] }); }); test('disabled option', async () => { @@ -169,7 +169,7 @@ test('disabled option', async () => { Bearstronaut diff --git a/src/components/form/field-combobox-multiple/index.tsx b/src/components/form/field-combobox-multiple/index.tsx index f62b03aee..d8cb781d5 100644 --- a/src/components/form/field-combobox-multiple/index.tsx +++ b/src/components/form/field-combobox-multiple/index.tsx @@ -61,7 +61,7 @@ export const FieldComboboxMultiple = ( const { t } = useTranslation(['components']); const ctx = useFormField(); - const { field, fieldState } = useFormFieldController(); + const { field, fieldState, isInvalid } = useFormFieldController(); return ( @@ -69,18 +69,18 @@ export const FieldComboboxMultiple = ( {...rest} multiple items={items} - disabled={field.disabled} - value={items?.filter((item) => field.value?.includes(item.value)) ?? []} + value={ + items?.filter((item) => fieldState.value?.includes(item.value)) ?? [] + } isItemEqualToValue={(item: TItem, selectedValue: TItem) => item.value === selectedValue.value } itemToStringLabel={(item: TItem) => item.label?.toString() ?? ''} itemToStringValue={(item: TItem) => item.value} onValueChange={(items: TItem[], event) => { - field.onChange(items?.map((i) => i.value) ?? [], event); + field.handleChange(items?.map((i) => i.value) ?? []); rest.onValueChange?.(items?.map((i) => i.value) ?? [], event); }} - inputRef={field.ref} > @@ -91,9 +91,9 @@ export const FieldComboboxMultiple = ( ))} diff --git a/src/components/form/field-combobox/docs.stories.tsx b/src/components/form/field-combobox/docs.stories.tsx index 792388767..91acb5b82 100644 --- a/src/components/form/field-combobox/docs.stories.tsx +++ b/src/components/form/field-combobox/docs.stories.tsx @@ -1,5 +1,3 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { @@ -7,6 +5,7 @@ import { FormField, FormFieldController, FormFieldLabel, + useForm, } from '@/components/form'; import { onSubmit } from '@/components/form/docs.utils'; import { Avatar, AvatarFallback } from '@/components/ui/avatar'; @@ -55,20 +54,21 @@ const options = [ }[]; const formOptions = { - mode: 'onBlur', - resolver: zodResolver(zFormSchema()), + schema: zFormSchema(), + mode: 'blur', + defaultValues: {} as z.input>, } as const; export const Default = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Bearstronaut { }; export const DefaultValue = () => { - const form = useForm>>({ + const form = useForm({ ...formOptions, defaultValues: { bear: '1', }, + onSubmit, }); return ( - +
Bearstronaut { }; export const Disabled = () => { - const form = useForm>>({ + const form = useForm({ ...formOptions, defaultValues: { bear: '1', }, + onSubmit, }); return ( - +
Bearstronaut { }; export const CustomOptions = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Bearstronaut { Bearstronaut @@ -67,7 +67,7 @@ test('default value', async () => { Bearstronaut @@ -100,7 +100,7 @@ test('disabled', async () => { Bearstronaut { await expect.element(input).toBeDisabled(); await user.click(page.getByRole('button', { name: 'Submit' })); - expect(mockedSubmit).toHaveBeenCalledWith({ bear: undefined }); + expect(mockedSubmit).toHaveBeenCalledWith({ bear: 'pawdrin' }); }); test('disabled option', async () => { @@ -132,7 +132,7 @@ test('disabled option', async () => { Bearstronaut diff --git a/src/components/form/field-combobox/index.tsx b/src/components/form/field-combobox/index.tsx index 2e2fe351d..6d2dd08c9 100644 --- a/src/components/form/field-combobox/index.tsx +++ b/src/components/form/field-combobox/index.tsx @@ -52,34 +52,32 @@ export const FieldCombobox = ( const { t } = useTranslation(['components']); const ctx = useFormField(); - const { field, fieldState } = useFormFieldController(); + const { field, fieldState, isInvalid } = useFormFieldController(); return ( item.value === field.value) ?? null} + value={items.find((item) => item.value === fieldState.value) ?? null} isItemEqualToValue={(item: TItem, selectedValue: TItem) => item.value === selectedValue.value } itemToStringLabel={(item: TItem) => item.label?.toString() ?? ''} itemToStringValue={(item: TItem) => item.value} onValueChange={(item: TItem, event) => { - field.onChange(item?.value ?? null, event); + field.handleChange(item?.value ?? null); rest.onValueChange?.(item?.value ?? null, event); }} - inputRef={field.ref} > diff --git a/src/components/form/field-custom/docs.stories.tsx b/src/components/form/field-custom/docs.stories.tsx index 0073db7c9..3d530293e 100644 --- a/src/components/form/field-custom/docs.stories.tsx +++ b/src/components/form/field-custom/docs.stories.tsx @@ -1,5 +1,3 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zu } from '@/lib/zod/zod-utils'; @@ -11,6 +9,7 @@ import { FormFieldError, FormFieldHelper, FormFieldLabel, + useForm, } from '@/components/form'; import { onSubmit } from '@/components/form/docs.utils'; import { Button } from '@/components/ui/button'; @@ -31,23 +30,23 @@ const zFormSchema = () => }); const formOptions = { - mode: 'onBlur', - resolver: zodResolver(zFormSchema()), + schema: zFormSchema(), + mode: 'blur', defaultValues: { url: '', }, } as const; export const Default = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Website URL ( @@ -57,9 +56,11 @@ export const Default = () => { https:// field.handleChange(e.target.value)} + onBlur={field.handleBlur} + aria-invalid={!fieldState.meta.isValid ? true : undefined} placeholder="example.com" /> @@ -83,15 +84,16 @@ export const DefaultValue = () => { defaultValues: { url: 'example.com', }, + onSubmit, }); return ( - +
Website URL ( @@ -101,9 +103,11 @@ export const DefaultValue = () => { https:// field.handleChange(e.target.value)} + onBlur={field.handleBlur} + aria-invalid={!fieldState.meta.isValid ? true : undefined} placeholder="example.com" /> @@ -127,15 +131,16 @@ export const Disabled = () => { defaultValues: { url: 'example.com', }, + onSubmit, }); return ( - +
Website URL { https:// field.handleChange(e.target.value)} + onBlur={field.handleBlur} + aria-invalid={!fieldState.meta.isValid ? true : undefined} placeholder="example.com" disabled /> @@ -173,15 +180,16 @@ export const ReadOnly = () => { defaultValues: { url: 'example.com', }, + onSubmit, }); return ( - +
Website URL ( @@ -191,9 +199,11 @@ export const ReadOnly = () => { https:// field.handleChange(e.target.value)} + onBlur={field.handleBlur} + aria-invalid={!fieldState.meta.isValid ? true : undefined} placeholder="example.com" readOnly /> diff --git a/src/components/form/field-date/docs.stories.tsx b/src/components/form/field-date/docs.stories.tsx index 6d307acb7..136616331 100644 --- a/src/components/form/field-date/docs.stories.tsx +++ b/src/components/form/field-date/docs.stories.tsx @@ -1,8 +1,6 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; import { z } from 'zod'; -import { FormFieldController } from '@/components/form'; +import { FormFieldController, useForm } from '@/components/form'; import { onSubmit } from '@/components/form/docs.utils'; import { Button } from '@/components/ui/button'; @@ -18,24 +16,24 @@ const zFormSchema = () => }); const formOptions = { - mode: 'onBlur', - resolver: zodResolver(zFormSchema()), + schema: zFormSchema(), + mode: 'blur', defaultValues: { date: null as unknown as Date, }, } as const; export const Default = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Date @@ -50,16 +48,16 @@ export const Default = () => { }; export const CalendarCustomization = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Date { - field.onChange(e); - rest.onChange?.(e); + name={field.name} + value={fieldState.value ?? null} + onChange={(date) => { + field.handleChange(date); + rest.onChange?.(date); }} onBlur={(e) => { - field.onBlur(); + field.handleBlur(); rest.onBlur?.(e); }} /> diff --git a/src/components/form/field-number/docs.stories.tsx b/src/components/form/field-number/docs.stories.tsx index 6c4871826..ad93fd6c1 100644 --- a/src/components/form/field-number/docs.stories.tsx +++ b/src/components/form/field-number/docs.stories.tsx @@ -1,6 +1,4 @@ -import { zodResolver } from '@hookform/resolvers/zod'; import { Meta } from '@storybook/react-vite'; -import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { @@ -9,6 +7,7 @@ import { FormFieldController, FormFieldHelper, FormFieldLabel, + useForm, } from '@/components/form'; import { onSubmit } from '@/components/form/docs.utils'; import { FieldNumber } from '@/components/form/field-number'; @@ -24,21 +23,24 @@ const zFormSchema = () => }); const formOptions = { - mode: 'onBlur', - resolver: zodResolver(zFormSchema()), + schema: zFormSchema(), + mode: 'blur', + defaultValues: { + balance: null as unknown as number, + }, } as const; export const Default = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Balance @@ -58,16 +60,17 @@ export const DefaultValue = () => { defaultValues: { balance: 30, }, + onSubmit, }); return ( - +
Balance @@ -82,16 +85,16 @@ export const DefaultValue = () => { }; export const Currency = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Balance { }; export const Disabled = () => { - const form = useForm({ ...formOptions, defaultValues: { balance: 42 } }); + const form = useForm({ + ...formOptions, + defaultValues: { balance: 42 }, + onSubmit, + }); return ( - +
Balance @@ -136,17 +143,21 @@ export const Disabled = () => { }; export const ReadOnly = () => { - const form = useForm({ ...formOptions, defaultValues: { balance: 42 } }); + const form = useForm({ + ...formOptions, + defaultValues: { balance: 42 }, + onSubmit, + }); return ( - +
Balance diff --git a/src/components/form/field-number/index.tsx b/src/components/form/field-number/index.tsx index 972390936..76d99e8a6 100644 --- a/src/components/form/field-number/index.tsx +++ b/src/components/form/field-number/index.tsx @@ -18,7 +18,7 @@ export const FieldNumber = ( const { containerProps, inCents, ...rest } = props; const ctx = useFormField(); - const { field, fieldState } = useFormFieldController(); + const { field, fieldState, isInvalid } = useFormFieldController(); const formatValue = ( value: number | undefined | null, type: 'to-cents' | 'from-cents' @@ -30,22 +30,21 @@ export const FieldNumber = ( return null; }; - const { onChange, value, ...fieldProps } = field; return ( { - onChange(formatValue(value, 'to-cents')); + field.handleChange(formatValue(value, 'to-cents')); rest.onValueChange?.(value, event); }} onBlur={(e) => { - field.onBlur(); + field.handleBlur(); rest.onBlur?.(e); }} /> diff --git a/src/components/form/field-otp/docs.stories.tsx b/src/components/form/field-otp/docs.stories.tsx index 506743f75..f990010f5 100644 --- a/src/components/form/field-otp/docs.stories.tsx +++ b/src/components/form/field-otp/docs.stories.tsx @@ -1,5 +1,3 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zu } from '@/lib/zod/zod-utils'; @@ -9,6 +7,7 @@ import { FormField, FormFieldController, FormFieldLabel, + useForm, } from '@/components/form'; import { onSubmit } from '@/components/form/docs.utils'; import { Button } from '@/components/ui/button'; @@ -32,21 +31,22 @@ const zFormSchema = (options: { length?: number } = {}) => { }; const formOptions = { - mode: 'onBlur', - resolver: zodResolver(zFormSchema()), + schema: zFormSchema(), + mode: 'blur', + defaultValues: {} as z.input>, } as const; export const Default = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Code @@ -65,16 +65,17 @@ export const DefaultValue = () => { defaultValues: { code: '927342', }, + onSubmit, }); return ( - +
Code @@ -88,16 +89,16 @@ export const DefaultValue = () => { }; export const Disabled = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Code { export const CustomLength = () => { const form = useForm({ ...formOptions, - resolver: zodResolver(zFormSchema({ length: 4 })), + schema: zFormSchema({ length: 4 }), + onSubmit, }); return ( - +
Code @@ -138,16 +140,16 @@ export const CustomLength = () => { }; export const AutoSubmit = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Code { Code @@ -63,7 +63,7 @@ test('default value', async () => { Code @@ -90,7 +90,7 @@ test('auto submit', async () => { Code { Code { await user.click(page.getByRole('button', { name: 'Submit' })); - expect(mockedSubmit).toHaveBeenCalledWith({ code: undefined }); + expect(mockedSubmit).toHaveBeenCalledWith({ code: '000000' }); }); diff --git a/src/components/form/field-otp/index.tsx b/src/components/form/field-otp/index.tsx index 04499f7f9..9ecc6e4a2 100644 --- a/src/components/form/field-otp/index.tsx +++ b/src/components/form/field-otp/index.tsx @@ -1,5 +1,5 @@ +import { useStore } from '@tanstack/react-form'; import { useRef } from 'react'; -import { useFormState } from 'react-hook-form'; import { useFormField } from '@/components/form/form-field'; import { FormFieldContainer } from '@/components/form/form-field-container'; @@ -25,18 +25,18 @@ export const FieldOtp = ( const containerRef = useRef>(null); const ctx = useFormField(); - const formState = useFormState(); - const { field, fieldState } = useFormFieldController(); + const { field, fieldState, isInvalid } = useFormFieldController(); + const isSubmitted = useStore(field.form.store, (state) => state.isSubmitted); return ( { rest.onComplete?.(v); // Only auto submit on first try - if (!formState.isSubmitted && autoSubmit) { + if (!isSubmitted && autoSubmit) { const button = document.createElement('button'); button.type = 'submit'; button.style.display = 'none'; @@ -46,13 +46,14 @@ export const FieldOtp = ( } }} {...rest} - {...field} - onChange={(e) => { - field.onChange(e); - rest.onChange?.(e); + name={field.name} + value={fieldState.value ?? ''} + onChange={(value) => { + field.handleChange(value); + rest.onChange?.(value); }} onBlur={(e) => { - field.onBlur(); + field.handleBlur(); rest.onBlur?.(e); }} > diff --git a/src/components/form/field-radio-group/docs.stories.tsx b/src/components/form/field-radio-group/docs.stories.tsx index 2a2476c05..ff0438efd 100644 --- a/src/components/form/field-radio-group/docs.stories.tsx +++ b/src/components/form/field-radio-group/docs.stories.tsx @@ -1,12 +1,10 @@ -import { zodResolver } from '@hookform/resolvers/zod'; import { CheckIcon } from 'lucide-react'; -import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { cn } from '@/lib/tailwind/utils'; import { zu } from '@/lib/zod/zod-utils'; -import { FormFieldController } from '@/components/form'; +import { FormFieldController, useForm } from '@/components/form'; import { onSubmit } from '@/components/form/docs.utils'; import { Button } from '@/components/ui/button'; import { Radio } from '@/components/ui/radio-group'; @@ -25,8 +23,8 @@ const zFormSchema = () => }); const formOptions = { - mode: 'onBlur', - resolver: zodResolver(zFormSchema()), + schema: zFormSchema(), + mode: 'blur', defaultValues: { bear: '', }, @@ -39,17 +37,17 @@ const options = [ ]; export const Default = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Bearstronaut Select your favorite bearstronaut @@ -68,16 +66,17 @@ export const DefaultValue = () => { defaultValues: { bear: 'pawdrin', }, + onSubmit, }); return ( - +
Bearstronaut Select your favorite bearstronaut { defaultValues: { bear: 'pawdrin', }, + onSubmit, }); return ( - +
Bearstronaut Select your favorite bearstronaut { }; export const Row = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Bearstronaut Select your favorite bearstronaut { }; export const WithDisabledOption = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); const optionsWithDisabled = [ { value: 'bearstrong', label: 'Bearstrong' }, @@ -156,13 +156,13 @@ export const WithDisabledOption = () => { ]; return ( - +
Bearstronaut Select your favorite bearstronaut { }; export const WithCustomRadio = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Bearstronaut Select your favorite bearstronaut { Bearstronaut @@ -80,7 +80,7 @@ test('should select radio on label click', async () => { Bearstronaut @@ -117,7 +117,7 @@ test('should handle keyboard navigation', async () => { Bearstronaut @@ -167,7 +167,7 @@ test('default value', async () => { Bearstronaut @@ -201,7 +201,7 @@ test('disabled', async () => { Bearstronaut { await expect.element(radio).toBeDisabled(); await user.click(page.getByRole('button', { name: 'Submit' })); - expect(mockedSubmit).toHaveBeenCalledWith({ bear: undefined }); + expect(mockedSubmit).toHaveBeenCalledWith({ bear: 'pawdrin' }); }); test('disabled option', async () => { @@ -236,7 +236,7 @@ test('disabled option', async () => { Bearstronaut diff --git a/src/components/form/field-radio-group/index.tsx b/src/components/form/field-radio-group/index.tsx index fe6bcab1f..873506a38 100644 --- a/src/components/form/field-radio-group/index.tsx +++ b/src/components/form/field-radio-group/index.tsx @@ -22,20 +22,25 @@ export const FieldRadioGroup = ( ) => { const { containerProps, options, renderOption, ...rest } = props; const ctx = useFormField(); - const { - field: { value, onChange, ...field }, - fieldState, - } = useFormFieldController(); + const { field, fieldState, isInvalid } = useFormFieldController(); return ( { + field.handleChange(value); + rest.onValueChange?.(value, eventDetails); + }} + onBlur={(e) => { + field.handleBlur(); + rest.onBlur?.(e); + }} > {options.map(({ label, ...option }) => { const radioId = `${ctx.id}-${option.value}`; @@ -45,9 +50,8 @@ export const FieldRadioGroup = ( {renderOption({ label, - 'aria-invalid': fieldState.invalid, + 'aria-invalid': isInvalid, size: ctx.size, - ...field, ...option, })} @@ -57,9 +61,8 @@ export const FieldRadioGroup = ( return ( {label} diff --git a/src/components/form/field-select/docs.stories.tsx b/src/components/form/field-select/docs.stories.tsx index f0080c170..b9d4a05c5 100644 --- a/src/components/form/field-select/docs.stories.tsx +++ b/src/components/form/field-select/docs.stories.tsx @@ -1,5 +1,3 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { @@ -7,6 +5,7 @@ import { FormField, FormFieldController, FormFieldLabel, + useForm, } from '@/components/form'; import { onSubmit } from '@/components/form/docs.utils'; import { Avatar, AvatarFallback } from '@/components/ui/avatar'; @@ -55,20 +54,21 @@ const options = [ }[]; const formOptions = { - mode: 'onBlur', - resolver: zodResolver(zFormSchema()), + schema: zFormSchema(), + mode: 'blur', + defaultValues: {} as z.input>, } as const; export const Default = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Bearstronaut { }; export const CustomOptions = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Bearstronaut { }; export const DefaultValue = () => { - const form = useForm>>({ + const form = useForm({ ...formOptions, defaultValues: { bear: '1', }, + onSubmit, }); return ( - +
Bearstronaut { }; export const Disabled = () => { - const form = useForm>>({ + const form = useForm({ ...formOptions, defaultValues: { bear: '1', }, + onSubmit, }); return ( - +
Bearstronaut { Bearstronaut @@ -67,7 +67,7 @@ test('default value', async () => { Bearstronaut @@ -99,7 +99,7 @@ test('disabled', async () => { Bearstronaut { await expect.element(trigger).toBeDisabled(); await user.click(page.getByRole('button', { name: 'Submit' })); - expect(mockedSubmit).toHaveBeenCalledWith({ bear: undefined }); + expect(mockedSubmit).toHaveBeenCalledWith({ bear: 'pawdrin' }); }); test('disabled option', async () => { @@ -131,7 +131,7 @@ test('disabled option', async () => { Bearstronaut diff --git a/src/components/form/field-select/index.tsx b/src/components/form/field-select/index.tsx index 2410ef9cf..1e664655f 100644 --- a/src/components/form/field-select/index.tsx +++ b/src/components/form/field-select/index.tsx @@ -33,23 +33,22 @@ export const FieldSelect = ( const { containerProps, inputProps, children, placeholder, ...rest } = props; const ctx = useFormField(); - const { field, fieldState } = useFormFieldController(); + const { field, fieldState, isInvalid } = useFormFieldController(); return ( { - field.onChange(e); + field.handleChange(e.target.value); rest.onChange?.(e); }} onBlur={(e) => { - field.onBlur(); + field.handleBlur(); rest.onBlur?.(e); }} /> diff --git a/src/components/form/field-textarea/docs.stories.tsx b/src/components/form/field-textarea/docs.stories.tsx index f2e43980a..cf5b65055 100644 --- a/src/components/form/field-textarea/docs.stories.tsx +++ b/src/components/form/field-textarea/docs.stories.tsx @@ -1,10 +1,8 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zu } from '@/lib/zod/zod-utils'; -import { FormFieldController } from '@/components/form'; +import { FormFieldController, useForm } from '@/components/form'; import { onSubmit } from '@/components/form/docs.utils'; import { Button } from '@/components/ui/button'; @@ -20,24 +18,24 @@ const zFormSchema = () => }); const formOptions = { - mode: 'onBlur', - resolver: zodResolver(zFormSchema()), + schema: zFormSchema(), + mode: 'blur', defaultValues: { description: '', }, } as const; export const Default = () => { - const form = useForm(formOptions); + const form = useForm({ ...formOptions, onSubmit }); return ( - +
Description @@ -57,15 +55,16 @@ export const DefaultValue = () => { defaultValues: { description: 'Default description', }, + onSubmit, }); return ( - +
Description { defaultValues: { description: 'Default Value', }, + onSubmit, }); return ( - +
Description { defaultValues: { description: 'Default Value', }, + onSubmit, }); return ( - +
Description { {({ form }) => ( Name - + )} @@ -53,11 +49,7 @@ test('default value', async () => { {({ form }) => ( Name - + )} @@ -83,7 +75,7 @@ test('disabled', async () => { Name @@ -98,7 +90,7 @@ test('disabled', async () => { // Expected to fail since input is disabled } await user.click(page.getByRole('button', { name: 'Submit' })); - expect(mockedSubmit).toHaveBeenCalledWith({ name: undefined }); + expect(mockedSubmit).toHaveBeenCalledWith({ name: 'new value' }); }); test('readOnly', async () => { @@ -116,7 +108,7 @@ test('readOnly', async () => { Name diff --git a/src/components/form/field-textarea/index.tsx b/src/components/form/field-textarea/index.tsx index 37e8bd30f..46976e8b5 100644 --- a/src/components/form/field-textarea/index.tsx +++ b/src/components/form/field-textarea/index.tsx @@ -17,23 +17,22 @@ export const FieldTextarea = ( const { containerProps, ...rest } = props; const ctx = useFormField(); - const { field, fieldState } = useFormFieldController(); + const { field, fieldState, isInvalid } = useFormFieldController(); return (