├── .github
├── stale.yml
└── workflows
│ ├── ci.yml
│ └── lock-issue.yml
├── .gitignore
├── .husky
└── pre-commit
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── contentlayer.config.ts
├── eslint.config.mjs
├── next-env.d.ts
├── next.config.ts
├── package.json
├── pnpm-lock.yaml
├── public
└── images
│ ├── Logo.svg
│ ├── casinoreviews.png
│ ├── dev-tool.png
│ ├── follower24.png
│ ├── formik.min.png
│ ├── gatsby-icon.png
│ ├── hookForm.png
│ ├── hookform.min.png
│ ├── logo
│ ├── react-hook-form-logo-grey.png
│ ├── react-hook-form-logo-grey.svg
│ ├── react-hook-form-logo-only-grey.png
│ ├── react-hook-form-logo-only-grey.svg
│ ├── react-hook-form-logo-only.png
│ ├── react-hook-form-logo-only.svg
│ ├── react-hook-form-logo.png
│ └── react-hook-form-logo.svg
│ ├── open-link.svg
│ ├── react-hook-form-og.png
│ ├── reduxform.min.png
│ ├── route4me.png
│ ├── sanity.png
│ └── twicsy.png
├── src
├── components
│ ├── Admonition.module.css
│ ├── Admonition.tsx
│ ├── ApiFormState.tsx
│ ├── ApiGallery.module.css
│ ├── ApiGallery.tsx
│ ├── ApiPage.module.css
│ ├── Bday.module.css
│ ├── BeekaiBuilderPage.module.css
│ ├── BeekaiBuilderPage.tsx
│ ├── BuilderPage.module.css
│ ├── BuilderPage.tsx
│ ├── ClipBoard.tsx
│ ├── CodeArea.module.css
│ ├── CodeArea.tsx
│ ├── CodeCompareSection.module.css
│ ├── CodeCompareSection.tsx
│ ├── CodePerfCompareSection.module.css
│ ├── CodePerfCompareSection.tsx
│ ├── CodeSandbox.tsx
│ ├── DevToolFeaturesList.module.css
│ ├── DevToolFeaturesList.tsx
│ ├── DevTools.module.css
│ ├── DevTools.tsx
│ ├── FeatureList.module.css
│ ├── FeaturesList.tsx
│ ├── Footer.module.css
│ ├── Footer.tsx
│ ├── Form.module.css
│ ├── Form.tsx
│ ├── FormFields.module.css
│ ├── FormFields.tsx
│ ├── FormStateApi.tsx
│ ├── FormStateTable.tsx
│ ├── GetStarted.module.css
│ ├── Header.module.css
│ ├── Header.tsx
│ ├── HomePage.module.css
│ ├── HomePage.tsx
│ ├── IsolateRender.module.css
│ ├── IsolateRender.tsx
│ ├── Menu
│ │ ├── Menu.tsx
│ │ ├── MenuLinks.ts
│ │ ├── SideMenu.module.css
│ │ └── index.ts
│ ├── Nav.module.css
│ ├── Nav.tsx
│ ├── Popup.module.css
│ ├── Popup.tsx
│ ├── ResourceList.tsx
│ ├── ResourcePage.module.css
│ ├── ResourcePageArticles.tsx
│ ├── ResourcePageBindings.tsx
│ ├── ResourcePageNewsletter.tsx
│ ├── ResourcePageVideos.tsx
│ ├── Search.module.css
│ ├── Search.tsx
│ ├── SortableContainer.module.css
│ ├── SortableContainer.tsx
│ ├── StarRepo.tsx
│ ├── TabGroup.module.css
│ ├── TabGroup.tsx
│ ├── Toggle.module.css
│ ├── Toggle.tsx
│ ├── TypeText.tsx
│ ├── UseController.tsx
│ ├── UseControllerContent.tsx
│ ├── UseControllerMethods.tsx
│ ├── UseFieldArray.tsx
│ ├── UseFieldArrayContent.tsx
│ ├── Watcher.module.css
│ ├── Watcher.tsx
│ ├── codeExamples
│ │ ├── dependantFieldsTS.tsx
│ │ ├── devTool.tsx
│ │ ├── formState.ts
│ │ ├── formStateTs.ts
│ │ ├── formStateUseEffect.ts
│ │ ├── formStateUseEffectTs.ts
│ │ ├── reactHookFormCode.ts
│ │ ├── useController.ts
│ │ ├── useControllerCheckboxes.ts
│ │ ├── useControllerTs.ts
│ │ ├── useFieldArray.ts
│ │ ├── useFieldArrayArgument.ts
│ │ ├── useFieldArrayConditional.ts
│ │ ├── useFieldArrayFocus.ts
│ │ ├── useFieldArrayPreview.ts
│ │ ├── useFieldArrayTS.ts
│ │ └── useFormState.ts
│ ├── general-observer.tsx
│ ├── layout.css
│ ├── layout.tsx
│ ├── learnMore.tsx
│ ├── logic
│ │ ├── generateCode.ts
│ │ └── getEditLink.tsx
│ ├── mdx
│ │ ├── code.tsx
│ │ ├── mdx.tsx
│ │ ├── pre.tsx
│ │ ├── theme.ts
│ │ └── youtube.tsx
│ ├── selectNav.module.css
│ ├── selectNav.tsx
│ ├── seo.tsx
│ ├── sponsorsList.module.css
│ ├── sponsorsList.tsx
│ └── utils
│ │ ├── copyClipBoard.ts
│ │ ├── goToBuilder.ts
│ │ └── useWindowSize.ts
├── content
│ ├── advanced-usage.mdx
│ ├── docs
│ │ ├── createFormControl.mdx
│ │ ├── formprovider.mdx
│ │ ├── usecontroller
│ │ │ └── controller.mdx
│ │ ├── useform.mdx
│ │ ├── useform
│ │ │ ├── clearerrors.mdx
│ │ │ ├── control.mdx
│ │ │ ├── form.mdx
│ │ │ ├── formstate.mdx
│ │ │ ├── getfieldstate.mdx
│ │ │ ├── getvalues.mdx
│ │ │ ├── handlesubmit.mdx
│ │ │ ├── register.mdx
│ │ │ ├── reset.mdx
│ │ │ ├── resetfield.mdx
│ │ │ ├── seterror.mdx
│ │ │ ├── setfocus.mdx
│ │ │ ├── setvalue.mdx
│ │ │ ├── subscribe.mdx
│ │ │ ├── trigger.mdx
│ │ │ ├── unregister.mdx
│ │ │ └── watch.mdx
│ │ ├── useformcontext.mdx
│ │ ├── useformstate.mdx
│ │ ├── useformstate
│ │ │ └── errormessage.mdx
│ │ └── usewatch.mdx
│ ├── faqs.mdx
│ ├── get-started.mdx
│ └── ts.mdx
├── data
│ ├── api.tsx
│ ├── builder.tsx
│ ├── devtools.tsx
│ ├── generic.tsx
│ ├── home.tsx
│ ├── nav.tsx
│ └── resources.tsx
├── pages
│ ├── 404.module.css
│ ├── 404.tsx
│ ├── [...slug].tsx
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── about-us.tsx
│ ├── dev-tools.tsx
│ ├── docs.tsx
│ ├── docs
│ │ ├── usecontroller.tsx
│ │ └── usefieldarray.tsx
│ ├── form-builder.tsx
│ ├── index.tsx
│ ├── media.module.css
│ ├── media.tsx
│ ├── migrate-v6-to-v7.tsx
│ └── resources
│ │ ├── 3rd-party-bindings.tsx
│ │ ├── articles.tsx
│ │ ├── newsletters.tsx
│ │ └── videos.tsx
├── state
│ └── formData.ts
├── styles
│ ├── breakpoints.ts
│ ├── button.module.css
│ ├── colors.ts
│ ├── container.module.css
│ ├── table.module.css
│ └── typography.module.css
└── types
│ ├── global.d.ts
│ ├── little-state-machine.d.ts
│ └── types.ts
└── tsconfig.json
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 20
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 3
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pinned
8 | - security
9 | # Label to use when marking an issue as stale
10 | staleLabel:
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: >
13 | This issue has been automatically marked as stale because it has not had
14 | recent activity. It will be closed if no further activity occurs. Thank you
15 | for your contributions.
16 | # Comment to post when closing a stale issue. Set to `false` to disable
17 | closeComment: false
18 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - "master"
7 | pull_request:
8 | branches:
9 | - "master"
10 |
11 | env:
12 | HUSKY: 0
13 |
14 | jobs:
15 | check:
16 | name: Run ${{ matrix.script }} code check
17 | runs-on: ubuntu-latest
18 | timeout-minutes: 15
19 |
20 | strategy:
21 | fail-fast: true
22 | matrix:
23 | script: ["format", "lint:ci", "typecheck:ci"]
24 |
25 | steps:
26 | - name: Checkout repo
27 | uses: actions/checkout@v4
28 |
29 | - name: Setup pnpm
30 | uses: pnpm/action-setup@v4
31 |
32 | - name: Setup Node
33 | uses: actions/setup-node@v4
34 | with:
35 | node-version-file: ".nvmrc"
36 | cache: "pnpm"
37 |
38 | - name: Install Dependencies
39 | shell: bash
40 | run: pnpm install --frozen-lockfile
41 |
42 | - name: Run ${{ matrix.script }}
43 | run: |
44 | pnpm run ${{ matrix.script }}
45 |
--------------------------------------------------------------------------------
/.github/workflows/lock-issue.yml:
--------------------------------------------------------------------------------
1 | name: "Lock Issues"
2 |
3 | on:
4 | workflow_dispatch:
5 | schedule:
6 | - cron: "0 0,6,12,18 * * *"
7 |
8 | permissions:
9 | issues: write
10 |
11 | concurrency:
12 | group: lock
13 |
14 | jobs:
15 | action:
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: dessant/lock-threads@v3
19 | with:
20 | github-token: ${{ secrets.GITHUB_TOKEN }}
21 | issue-inactive-days: 60
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.*
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 | /dist
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 | .idea/
26 | .idea
27 | .cache/
28 | package-lock.json
29 | .vscode
30 | tsconfig.tsbuildinfo
31 | .next/
32 |
33 | # contentlayer
34 | .contentlayer
35 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 20.18.1
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | pnpm-lock.yaml
2 | .cache/
3 | .contentlayer/
4 | .next/
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/prettierrc",
3 | "endOfLine": "lf",
4 | "semi": false,
5 | "singleQuote": false,
6 | "tabWidth": 2,
7 | "trailingComma": "es5"
8 | }
9 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | - Using welcoming and inclusive language
18 | - Being respectful of differing viewpoints and experiences
19 | - Gracefully accepting constructive criticism
20 | - Focusing on what is best for the community
21 | - Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | - The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | - Trolling, insulting/derogatory comments, and personal or political attacks
28 | - Public or private harassment
29 | - Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | - Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at bluebill1049@hotmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to `React Hook Form` (The Docs)
2 |
3 | As the creators and maintainers of this project, we want to ensure that `react-hook-form` lives and continues to grow and evolve. We would like to encourage everyone to help and support this library by contributing.
4 |
5 | ## Code contributions
6 |
7 | Here is a quick guide to doing code contributions to the library.
8 |
9 | 1. Fork and clone the repo to your local machine `git clone https://github.com/YOUR_GITHUB_USERNAME/website.git`
10 |
11 | 2. Create a new branch from `master` with a meaningful name for a new feature or an issue you want to work on: `git checkout -b your-meaningful-branch-name`
12 |
13 | 3. Install packages by running:
14 |
15 | ```shellscript
16 | pnpm install
17 | ```
18 |
19 | 4. Startup a local version of the docs
20 |
21 | ```shellscript
22 | pnpm run dev
23 | ```
24 |
25 | 5. Ensure your code is formatted properly
26 |
27 | ```shellscript
28 | pnpm run format
29 | ```
30 |
31 | 6. Push your branch: `git push -u origin your-meaningful-branch-name`
32 |
33 | 7. Submit a pull request to the upstream react-hook-form repository.
34 |
35 | 8. Choose a descriptive title and describe your changes briefly.
36 |
37 | ## Testing production build
38 |
39 | To test the documentation production site on your local machine,
40 | first execute the build script:
41 |
42 | ```shellscript
43 | pnpm run build
44 | ```
45 |
46 | Then start a local server which serves the file created by executing
47 |
48 | ```shellscript
49 | pnpm run start
50 | ```
51 |
52 | ## Coding style
53 |
54 | Please follow the coding style of the project.
55 | React Hook Form uses prettier.
56 | If possible, enable the prettier plugin in your editor to get real-time feedback. The formatting can be run manually with the following command:
57 |
58 | ```shellscript
59 | pnpm run format:fix
60 | ```
61 |
62 | ## License
63 |
64 | By contributing your code to the react-hook-form GitHub repository, you agree to license your contribution under the MIT license.
65 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019-present Beier(Bill) Luo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
8 |
9 | Performant, flexible and extensible forms with easy to use validation.
10 |
11 | ## Install
12 |
13 | ```shellscript
14 | pnpm install && pnpm dev
15 | ```
16 |
17 | ## Backers
18 |
19 | Thanks goes to all our backers! [[Become a backer](https://opencollective.com/react-hook-form#backer)].
20 |
21 |
22 |
23 |
24 |
25 | ## Contributors
26 |
27 | Thanks goes to these wonderful people. [[Become a contributor](https://github.com/react-hook-form/documentation/blob/master/CONTRIBUTING.md)].
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/contentlayer.config.ts:
--------------------------------------------------------------------------------
1 | import { defineDocumentType, makeSource } from "contentlayer/source-files"
2 | import { remarkHeadingId } from "remark-custom-heading-id"
3 | import remarkGfm from "remark-gfm"
4 | import rehypeMdxCodeProps from "rehype-mdx-code-props"
5 | import emoji from "remark-emoji"
6 | import * as sidebar from "./src/components/Menu/MenuLinks"
7 | import type { Pages } from "./src/types/types"
8 |
9 | export const Doc = defineDocumentType(() => ({
10 | name: "Doc",
11 | contentType: "mdx",
12 | filePathPattern: "**/*.mdx",
13 | fields: {
14 | title: { type: "string", required: true },
15 | description: { type: "string", required: true },
16 | sidebar: {
17 | type: "enum",
18 | options: [
19 | "apiLinks",
20 | "advancedLinks",
21 | "tsLinks",
22 | "faqLinks",
23 | "getStartedLinks",
24 | ],
25 | required: true,
26 | },
27 | },
28 | computedFields: {
29 | slug: {
30 | type: "string",
31 | resolve: (doc) => `/${doc._raw.flattenedPath}`,
32 | },
33 | slugAsParams: {
34 | type: "string",
35 | resolve: (doc) => doc._raw.flattenedPath.split("/").slice(1).join("/"),
36 | },
37 |
38 | /**
39 | * Can't define value different from primitives, so hardcoding the correct type
40 | * @see https://github.com/contentlayerdev/contentlayer/issues/149
41 | */
42 | segment: {
43 | type: "string[]" as "list",
44 | resolve: (doc) => doc._raw.flattenedPath.split("/"),
45 | },
46 | pages: {
47 | type: "json",
48 | resolve: (doc) => {
49 | // added explicit type casting to keep track of this data structure
50 | // in case in the future contentlayer will support values different than primitives
51 | return sidebar[doc.sidebar] as unknown as Pages
52 | },
53 | },
54 | },
55 | }))
56 |
57 | export default makeSource({
58 | contentDirPath: "src/content",
59 | documentTypes: [Doc],
60 | mdx: {
61 | remarkPlugins: [remarkGfm, remarkHeadingId, emoji],
62 | rehypePlugins: [rehypeMdxCodeProps],
63 | },
64 | })
65 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | import tseslint from "typescript-eslint"
3 | import { FlatCompat } from "@eslint/eslintrc"
4 | import eslintPluginJsxA11y from "eslint-plugin-jsx-a11y"
5 | import eslintPluginReact from "eslint-plugin-react"
6 |
7 | const compat = new FlatCompat({
8 | baseDirectory: import.meta.dirname,
9 | })
10 |
11 | export default tseslint.config(
12 | tseslint.configs.recommendedTypeChecked,
13 |
14 | ...compat.config({
15 | extends: ["next"],
16 | rules: {
17 | // react
18 | "react/prop-types": "off",
19 | "react/no-unescaped-entities": "off",
20 | "react/jsx-curly-brace-presence": "warn",
21 |
22 | // jsx-ally is already included in next-js so
23 | // we are adding only recommended rules directly here
24 | ...eslintPluginJsxA11y.flatConfigs.recommended.rules,
25 | "jsx-a11y/no-onchange": "warn",
26 |
27 | // import
28 | "import/no-anonymous-default-export": "off",
29 |
30 | // next
31 | "@next/next/no-img-element": "off",
32 | },
33 | }),
34 |
35 | // @ts-expect-error eslintPluginReact.configs.flat, but runtime is always defined
36 | eslintPluginReact.configs.flat["jsx-runtime"],
37 |
38 | {
39 | linterOptions: {
40 | reportUnusedDisableDirectives: "error",
41 | },
42 | languageOptions: {
43 | parserOptions: {
44 | projectService: true,
45 | tsconfigRootDir: import.meta.dirname,
46 | },
47 | },
48 | rules: {
49 | // typescript
50 | "@typescript-eslint/explicit-function-return-type": "off",
51 | "@typescript-eslint/explicit-module-boundary-types": "off",
52 | "@typescript-eslint/interface-name-prefix": "off",
53 | "@typescript-eslint/no-floating-promises": "off",
54 | "@typescript-eslint/restrict-template-expressions": [
55 | "error",
56 | {
57 | allowAny: false,
58 | allowBoolean: false,
59 | allowNever: false,
60 | allowNullish: false,
61 | allowNumber: true,
62 | allowRegExp: false,
63 | },
64 | ],
65 | },
66 | }
67 | )
68 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
6 |
--------------------------------------------------------------------------------
/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from "next"
2 | import { withContentlayer } from "next-contentlayer"
3 | import withBundleAnalyzer from "@next/bundle-analyzer"
4 |
5 | const nextConfig: NextConfig = {
6 | eslint: {
7 | /**
8 | * Now eslint requires typecheck so we have to run build before executing lint
9 | * These check are performed via `.github/workflows/ci.yml` action
10 | *
11 | * @see https://github.com/react-hook-form/documentation/pull/1107
12 | */
13 | ignoreDuringBuilds: true,
14 | },
15 | typescript: {
16 | /** @see `eslint.ignoreDuringBuilds` comment */
17 | ignoreBuildErrors: true,
18 | },
19 | reactStrictMode: true,
20 | pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"],
21 | }
22 |
23 | const bundleAnalyzer = withBundleAnalyzer({
24 | enabled: process.env.ANALYZE === "true",
25 | })
26 |
27 | export default bundleAnalyzer(withContentlayer(nextConfig))
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-hook-form-website",
3 | "description": "React hooks for form validation without the hassle.",
4 | "version": "6.0.1",
5 | "author": "beier luo",
6 | "dependencies": {
7 | "@hookform/devtools": "4.3.1",
8 | "@mdx-js/loader": "^2.3.0",
9 | "@mdx-js/react": "^2.3.0",
10 | "@next/mdx": "15.1.0",
11 | "class-variance-authority": "^0.6.1",
12 | "clsx": "^1.2.1",
13 | "contentlayer": "^0.3.4",
14 | "little-state-machine": "^4.8.1",
15 | "next": "^15.1.2",
16 | "next-contentlayer": "^0.3.4",
17 | "next-themes": "^0.2.1",
18 | "prism-react-renderer": "^2.4.1",
19 | "prismjs": "^1.30.0",
20 | "react": "18.3.1",
21 | "react-dom": "18.3.1",
22 | "react-github-btn": "1.4.0",
23 | "react-hook-form": "7.44.3",
24 | "react-simple-animate": "^3.5.3",
25 | "react-simple-img": "3.0.0",
26 | "react-sortablejs": "1.5.1",
27 | "rehype-mdx-code-props": "^1.0.0",
28 | "remark-custom-heading-id": "^1.0.1",
29 | "remark-emoji": "^3.1.2",
30 | "remark-gfm": "^3.0.1",
31 | "sortablejs": "1.15.0"
32 | },
33 | "devDependencies": {
34 | "@next/bundle-analyzer": "15.1.0",
35 | "@types/eslint-plugin-jsx-a11y": "6.10.0",
36 | "@types/eslint__eslintrc": "2.1.2",
37 | "@types/node": "20.17.10",
38 | "@types/react": "18.3.17",
39 | "@types/react-dom": "18.3.5",
40 | "@types/react-helmet": "^6.1.11",
41 | "cross-env": "^7.0.3",
42 | "eslint": "9.17.0",
43 | "eslint-config-next": "15.1.0",
44 | "eslint-plugin-jsx-a11y": "6.10.2",
45 | "eslint-plugin-react": "7.37.2",
46 | "eslint-plugin-react-hooks": "5.1.0",
47 | "husky": "^8.0.3",
48 | "lint-staged": "^13.3.0",
49 | "prettier": "^3.5.3",
50 | "typescript": "^5.8.3",
51 | "typescript-eslint": "8.18.1"
52 | },
53 | "keywords": [
54 | "react-hook-form",
55 | "form-validation"
56 | ],
57 | "license": "MIT",
58 | "scripts": {
59 | "analyze": "cross-env ANALYZE=true next build",
60 | "build": "next build",
61 | "dev": "next dev",
62 | "format": "prettier . --check",
63 | "format:fix": "prettier . --write",
64 | "lint": "next lint",
65 | "lint:fix": "next lint --fix",
66 | "lint:ci": "next build --no-lint && next lint",
67 | "now-build": "pnpm run build",
68 | "start": "next start",
69 | "typecheck": "tsc --noEmit",
70 | "typecheck:ci": "next build --no-lint && tsc --noEmit",
71 | "prepare": "husky install"
72 | },
73 | "lint-staged": {
74 | "*.{ts,tsx,mdx,css,json}": [
75 | "pnpm run format:fix"
76 | ]
77 | },
78 | "packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c",
79 | "pnpm": {
80 | "overrides": {
81 | "@types/react": "18.3.17",
82 | "@types/react-dom": "18.3.5"
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/public/images/Logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Logo
5 | Created with Sketch.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/public/images/casinoreviews.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-hook-form/documentation/9e17128086b6422e6ecaaa6d94b87b9e83161448/public/images/casinoreviews.png
--------------------------------------------------------------------------------
/public/images/dev-tool.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-hook-form/documentation/9e17128086b6422e6ecaaa6d94b87b9e83161448/public/images/dev-tool.png
--------------------------------------------------------------------------------
/public/images/follower24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-hook-form/documentation/9e17128086b6422e6ecaaa6d94b87b9e83161448/public/images/follower24.png
--------------------------------------------------------------------------------
/public/images/formik.min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-hook-form/documentation/9e17128086b6422e6ecaaa6d94b87b9e83161448/public/images/formik.min.png
--------------------------------------------------------------------------------
/public/images/gatsby-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-hook-form/documentation/9e17128086b6422e6ecaaa6d94b87b9e83161448/public/images/gatsby-icon.png
--------------------------------------------------------------------------------
/public/images/hookForm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-hook-form/documentation/9e17128086b6422e6ecaaa6d94b87b9e83161448/public/images/hookForm.png
--------------------------------------------------------------------------------
/public/images/hookform.min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-hook-form/documentation/9e17128086b6422e6ecaaa6d94b87b9e83161448/public/images/hookform.min.png
--------------------------------------------------------------------------------
/public/images/logo/react-hook-form-logo-grey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-hook-form/documentation/9e17128086b6422e6ecaaa6d94b87b9e83161448/public/images/logo/react-hook-form-logo-grey.png
--------------------------------------------------------------------------------
/public/images/logo/react-hook-form-logo-only-grey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-hook-form/documentation/9e17128086b6422e6ecaaa6d94b87b9e83161448/public/images/logo/react-hook-form-logo-only-grey.png
--------------------------------------------------------------------------------
/public/images/logo/react-hook-form-logo-only-grey.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/logo/react-hook-form-logo-only.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-hook-form/documentation/9e17128086b6422e6ecaaa6d94b87b9e83161448/public/images/logo/react-hook-form-logo-only.png
--------------------------------------------------------------------------------
/public/images/logo/react-hook-form-logo-only.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/logo/react-hook-form-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-hook-form/documentation/9e17128086b6422e6ecaaa6d94b87b9e83161448/public/images/logo/react-hook-form-logo.png
--------------------------------------------------------------------------------
/public/images/open-link.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/images/react-hook-form-og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-hook-form/documentation/9e17128086b6422e6ecaaa6d94b87b9e83161448/public/images/react-hook-form-og.png
--------------------------------------------------------------------------------
/public/images/reduxform.min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-hook-form/documentation/9e17128086b6422e6ecaaa6d94b87b9e83161448/public/images/reduxform.min.png
--------------------------------------------------------------------------------
/public/images/route4me.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-hook-form/documentation/9e17128086b6422e6ecaaa6d94b87b9e83161448/public/images/route4me.png
--------------------------------------------------------------------------------
/public/images/sanity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-hook-form/documentation/9e17128086b6422e6ecaaa6d94b87b9e83161448/public/images/sanity.png
--------------------------------------------------------------------------------
/public/images/twicsy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-hook-form/documentation/9e17128086b6422e6ecaaa6d94b87b9e83161448/public/images/twicsy.png
--------------------------------------------------------------------------------
/src/components/Admonition.module.css:
--------------------------------------------------------------------------------
1 | .admonition {
2 | margin-top: 1rem;
3 | margin-bottom: 1em;
4 | padding: 15px 30px 15px 15px;
5 | border-radius: 0.4rem;
6 | }
7 |
8 | .admonitionIcon {
9 | display: inline-block;
10 | vertical-align: middle;
11 | margin-right: 0.2em;
12 | }
13 |
14 | .admonitionIcon svg {
15 | display: inline-block;
16 | width: 22px;
17 | height: 22px;
18 | stroke-width: 0;
19 | }
20 |
21 | .admonitionIcon svg {
22 | stroke: var(--color-black);
23 | fill: var(--color-black);
24 | }
25 |
26 | :global(.dark) .admonitionIcon svg {
27 | stroke: rgb(253, 253, 254);
28 | fill: rgb(253, 253, 254);
29 | }
30 |
31 | .admonitionContent > :last-child {
32 | margin-bottom: 0;
33 | }
34 |
35 | /** Customization */
36 | .admonitionWarning {
37 | background-color: rgba(230, 126, 34, 0.1);
38 | border-left: 8px solid var(--color-orange);
39 | }
40 |
41 | .admonitionTip {
42 | background-color: rgba(46, 204, 113, 0.1);
43 | border-left: 8px solid var(--color-green);
44 | }
45 |
46 | .admonitionCaution {
47 | background-color: rgba(231, 76, 60, 0.1);
48 | border-left: 8px solid var(--color-secondary);
49 | }
50 |
51 | .admonitionImportant {
52 | background-color: rgb(25, 60, 71, 0.1);
53 | border-left: 8px solid var(--color-blue);
54 | }
55 |
56 | .admonitionNote {
57 | background-color: rgb(71, 71, 72, 0.1);
58 | border-left: 8px solid var(--color-text);
59 | }
60 |
61 | .admonitionQuestion {
62 | background-color: rgba(8, 61, 119, 0.1);
63 | border-left: 8px solid var(--color-light-blue);
64 | }
65 |
--------------------------------------------------------------------------------
/src/components/ApiFormState.tsx:
--------------------------------------------------------------------------------
1 | import { memo } from "react"
2 | import API from "../data/api"
3 | import typographyStyles from "../styles/typography.module.css"
4 | import FormStateTable from "./FormStateTable"
5 | import TabGroup from "./TabGroup"
6 | import CodeArea from "./CodeArea"
7 | import formStateUseEffect from "./codeExamples/formStateUseEffect"
8 | import formStateUseEffectTs from "./codeExamples/formStateUseEffectTs"
9 |
10 | function ApiFormState({ api }: { api: typeof API }) {
11 | return (
12 | <>
13 |
14 |
15 | formState: Object
16 |
17 |
18 | {api.formState.description}
19 |
20 |
21 |
22 |
23 | Rules
24 |
25 |
26 |
27 |
28 |
29 | formState
is wrapped with a{" "}
30 |
35 | Proxy
36 | {" "}
37 | to improve render performance and skip extra logic if specific state
38 | is not subscribed to. Therefore make sure you invoke or read it
39 | before a render
in order to enable the state update.
40 |
41 |
42 |
43 |
44 | formState
is updated in batch. If you want to subscribe
45 | to formState
via useEffect
, make sure that
46 | you place the entire formState
in the optional array.
47 |
48 |
49 | {
51 | if (formState.errors.firstName) {
52 | // do the your logic here
53 | }
54 | }, [formState]); // ✅
55 | // ❌ formState.errors will not trigger the useEffect
56 | `}
57 | />
58 |
62 |
63 |
64 |
65 |
66 | Pay attention to the logical operator when subscription to{" "}
67 | formState
.
68 |
69 |
70 | ;
74 |
75 | // ✅ read all formState values to subscribe to changes
76 | const { isDirty, isValid } = formState;
77 | return ;
78 | `}
79 | />
80 |
81 |
82 | >
83 | )
84 | }
85 |
86 | export default memo(ApiFormState)
87 |
--------------------------------------------------------------------------------
/src/components/ApiGallery.module.css:
--------------------------------------------------------------------------------
1 | .root {
2 | max-width: 768px;
3 | margin: 60px auto 0;
4 | }
5 |
6 | @media (min-width: 768px) {
7 | .root {
8 | max-width: 840px;
9 | }
10 | }
11 |
12 | .gallery {
13 | display: grid;
14 | list-style: none;
15 | margin: 20px;
16 | grid-gap: 25px;
17 | grid-template-columns: 1fr;
18 | padding: 0;
19 | }
20 |
21 | @media (min-width: 768px) {
22 | .gallery {
23 | margin: 60px auto 60px;
24 | padding: 0 20px;
25 | grid-gap: 25px;
26 | grid-template-columns: repeat(3, 1fr);
27 | }
28 | }
29 |
30 | @media (min-width: 1024px) {
31 | .gallery {
32 | max-width: 1024px;
33 | }
34 | }
35 |
36 | .gallery li {
37 | border: 1px solid var(--color-light-blue);
38 | position: relative;
39 | padding-bottom: 30px;
40 | transition: transform 0.3s ease;
41 | }
42 |
43 | .gallery li:hover {
44 | border: 1px solid var(--color-secondary);
45 | outline-width: 0;
46 | box-shadow: 2px 2px 0 4px #000;
47 | transform: translate(-2px, -2px);
48 | }
49 |
50 | .gallery li a {
51 | position: absolute;
52 | right: 0;
53 | bottom: 0;
54 | top: 0;
55 | left: 0;
56 | display: flex;
57 | justify-content: flex-end;
58 | align-items: flex-end;
59 | padding: 12px 20px 12px 80px;
60 | font-size: 14px;
61 | }
62 |
63 | .gallery h3 {
64 | color: white;
65 | margin: 0;
66 | font-weight: normal;
67 | padding: 13px 20px;
68 | font-size: 18px;
69 | background: var(--color-button-blue);
70 | border-bottom: 1px solid var(--color-light-blue);
71 | letter-spacing: 1px;
72 | }
73 |
74 | .gallery h3 code {
75 | margin-right: 5px;
76 | letter-spacing: -1px;
77 | }
78 |
79 | .gallery p {
80 | margin: 20px;
81 | font-size: 14px;
82 | }
83 |
84 | .versionControl {
85 | text-align: right;
86 | padding-right: 20px;
87 | }
88 |
89 | .versionControl > div {
90 | display: inline-block;
91 | }
92 |
93 | .versionControl > p {
94 | font-size: 14px;
95 | color: var(--color-light-grey);
96 | display: inline-block;
97 | margin-right: 20px;
98 | }
99 |
100 | p.beta {
101 | margin-bottom: -1rem;
102 | font-size: 0.7rem;
103 | color: var(--color-light-grey);
104 | }
105 |
--------------------------------------------------------------------------------
/src/components/ApiPage.module.css:
--------------------------------------------------------------------------------
1 | .mobileTypeText {
2 | font-weight: 400;
3 | font-size: 15px;
4 | font-family: monospace;
5 | font-variant-ligatures: none;
6 | color: var(--color-light-pink);
7 | margin-top: 10px;
8 | display: block;
9 | }
10 |
11 | .quickSelect {
12 | position: relative;
13 | max-width: 320px;
14 | margin: 0 auto;
15 | }
16 |
17 | .quickSelect:after {
18 | content: "▼";
19 | font-size: 15px;
20 | right: 17%;
21 | top: 12px;
22 | position: absolute;
23 | pointer-events: none;
24 | }
25 |
26 | .quickSelect > select {
27 | border-radius: 4px;
28 | border: 1px solid var(--color-light-blue);
29 | appearance: none;
30 | background: none;
31 | color: white;
32 | margin: 0.67em auto 20px;
33 | display: block;
34 | text-align: center;
35 | text-align-last: center;
36 | font-size: 2rem;
37 | font-weight: lighter;
38 | position: relative;
39 | padding: 10px 30px;
40 | max-width: 240px;
41 | }
42 |
43 | .versionToggle {
44 | position: absolute;
45 | right: 20px;
46 | }
47 |
48 | @media (min-width: 768px) {
49 | .hiddenMenu > h1 {
50 | display: block;
51 | }
52 |
53 | .hiddenMenu > div {
54 | display: none;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/Bday.module.css:
--------------------------------------------------------------------------------
1 | .root {
2 | display: none;
3 | }
4 |
5 | .root h1 > span {
6 | font-size: 30px;
7 | display: inline;
8 | font-weight: 400;
9 | margin-left: -15px;
10 | }
11 |
12 | @media (min-width: 768px) {
13 | .root {
14 | width: 630px;
15 | margin: 0 auto 0px;
16 | align-items: inherit;
17 | grid-template-areas: "d e";
18 | border: 1px solid var(--color-light-blue);
19 | padding: 0 40px 40px;
20 | border-radius: 7px;
21 | box-shadow: 0 0 20px var(--color-primary);
22 | display: grid;
23 | }
24 | }
25 |
26 | .root span {
27 | display: block;
28 | }
29 |
30 | .happy {
31 | align-self: center;
32 | grid-area: a;
33 | text-align: right;
34 | font-size: 50px;
35 | font-weight: 600;
36 | color: var(--color-light-pink);
37 | }
38 |
39 | .birthday {
40 | align-self: center;
41 | grid-area: c;
42 | text-align: right;
43 | font-size: 50px;
44 | font-weight: 400;
45 | position: relative;
46 | margin-top: -10px;
47 | }
48 |
49 | .birthday p {
50 | bottom: -35px;
51 | right: 0;
52 | position: absolute;
53 | font-size: 12px;
54 | margin: 0;
55 | color: var(--color-light-blue);
56 | padding: 0;
57 | }
58 |
59 | .first {
60 | margin-top: 0;
61 | font-size: 130px;
62 | line-height: 120px;
63 | font-weight: bold;
64 | padding-left: 10px;
65 | display: block;
66 | grid-area: b;
67 | }
68 |
69 | .first sup {
70 | font-size: 30px;
71 | font-weight: 400;
72 | top: -4.1rem;
73 | }
74 |
75 | .cake {
76 | grid-area: d;
77 | text-align: center;
78 | font-size: 80px;
79 | line-height: 100px;
80 | }
81 |
82 | .cake svg {
83 | fill: white;
84 | height: 180px;
85 | }
86 |
87 | .achievement {
88 | grid-area: e;
89 | align-self: end;
90 | }
91 |
92 | .achievementList {
93 | display: grid;
94 | padding-left: 10px;
95 | }
96 |
97 | .achievementList h4 {
98 | margin-left: 12px;
99 | font-size: 30px;
100 | font-weight: 400;
101 | margin-bottom: 20px;
102 | margin-top: 40px;
103 | }
104 |
105 | .achievementList h4 span {
106 | border-bottom: 1px solid var(--color-light-blue);
107 | padding-bottom: 5px;
108 | display: inline-block;
109 | }
110 |
111 | .achievementList ul {
112 | list-style: none;
113 | margin: 0;
114 | }
115 |
116 | .achievementList p {
117 | padding: 5px 0;
118 | margin: 0;
119 | font-size: 14px;
120 | }
121 |
122 | .achievement strong {
123 | color: var(--color-light-pink);
124 | font-weight: 500;
125 | }
126 |
127 | .link {
128 | border: 1px solid white;
129 | margin-left: 40px;
130 | text-decoration: none;
131 | text-align: center;
132 | padding: 5px 0;
133 | margin-top: 20px;
134 | display: inline-block;
135 | border-radius: 25px;
136 | font-size: 14px;
137 | color: white;
138 | width: 150px;
139 | }
140 |
141 | .link:hover {
142 | background-color: white;
143 | color: var(--color-primary);
144 | border: 1px solid var(--color-primary);
145 | }
146 |
147 | .avatars {
148 | margin-right: 0;
149 | display: grid;
150 | grid-template-columns: 1fr 1fr 1fr 1fr;
151 | }
152 |
153 | .avatars img {
154 | height: 60px;
155 | margin: 2px;
156 | }
157 |
--------------------------------------------------------------------------------
/src/components/BeekaiBuilderPage.module.css:
--------------------------------------------------------------------------------
1 | .root {
2 | background: #0f111b;
3 | border-top: 1px solid #1e2a4a;
4 | border-bottom: 1px solid #1e2a4a;
5 | padding: 20px;
6 | }
7 |
8 | .logo {
9 | height: 25px;
10 | width: 100px;
11 | }
12 |
13 | .container {
14 | max-width: 910px;
15 | margin: 40px auto 20px;
16 | display: flex;
17 | padding: 0 20px;
18 | flex-direction: column;
19 | }
20 |
21 | .container > div {
22 | flex: 1;
23 | }
24 |
25 | .container a {
26 | width: 100%;
27 | text-align: center;
28 | text-decoration: none;
29 | margin-top: 20px;
30 | }
31 |
32 | .heading {
33 | font-size: 1.5rem;
34 | line-height: 1.3;
35 | font-weight: 600;
36 | margin-top: 20px;
37 | }
38 |
39 | .features {
40 | margin-top: 30px;
41 | }
42 |
43 | .product {
44 | position: relative;
45 | }
46 |
47 | .productImg {
48 | background: url("https://www.beekai.com/marketing/features/form/form-builder-hero.png")
49 | no-repeat;
50 | border-radius: 10px;
51 | display: block;
52 | background-size: 800px;
53 | background-position-x: -40px;
54 | max-height: 550px;
55 | min-height: 200px;
56 | z-index: 1;
57 | position: relative;
58 | }
59 |
60 | .product::after {
61 | content: "";
62 | position: absolute;
63 | top: -0.5rem;
64 | bottom: -0.5rem;
65 | left: -0.5rem;
66 | right: -0.5rem;
67 | filter: blur(3rem);
68 | transform: scale(0.96);
69 | background-image: linear-gradient(230deg, #b367fe, #ec5990, #bf1650);
70 | opacity: 1;
71 | transition: 0.3s;
72 | animation: scale 3s alternate-reverse ease infinite;
73 | }
74 |
75 | .content {
76 | z-index: 1;
77 | }
78 |
79 | @keyframes scale {
80 | 0% {
81 | transform: scale(0.9);
82 | }
83 | 50% {
84 | transform: scale(0.85);
85 | }
86 | 100% {
87 | transform: scale(0.88);
88 | }
89 | }
90 |
91 | @media (min-width: 768px) {
92 | .container {
93 | flex-direction: row;
94 | gap: 40px;
95 | }
96 |
97 | .heading {
98 | font-size: 1.8rem;
99 | font-weight: 700;
100 | margin-top: 0;
101 | }
102 |
103 | .productImg {
104 | background-size: 700px;
105 | height: 100%;
106 | background-position-x: -35px;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/components/BeekaiBuilderPage.tsx:
--------------------------------------------------------------------------------
1 | import styles from "./BeekaiBuilderPage.module.css"
2 | import buttonStyles from "../styles/button.module.css"
3 |
4 | export function BeekaiBuilderPage() {
5 | return (
6 |
7 |
8 |
11 |
12 |
13 |
18 |
Next-gen form builder
19 |
20 | Build the next-generation forms with modern technology and best in
21 | class user experience and accessibility.
22 |
23 |
24 |
25 |
26 | Generate code for React/Vanilla JS
27 |
28 |
29 | GUI with drag and drop
30 |
31 |
32 | Improved accessibility by default
33 |
34 |
35 | Support dynamic field array
36 |
37 |
38 | End-to-end integration with submission
39 |
40 |
41 | User behaviour analytic
42 |
43 |
44 | and many more features
45 |
46 |
47 |
48 |
54 | Try it now
55 |
56 |
57 |
58 |
59 | )
60 | }
61 |
--------------------------------------------------------------------------------
/src/components/BuilderPage.module.css:
--------------------------------------------------------------------------------
1 | .root {
2 | position: fixed;
3 | top: 0;
4 | left: 0;
5 | width: 100%;
6 | height: 100vh;
7 | z-index: 11;
8 | box-sizing: border-box;
9 | -webkit-overflow-scrolling: touch;
10 | }
11 |
12 | .builder {
13 | overflow: auto;
14 | height: 100vh;
15 | background: var(--color-background);
16 | }
17 |
18 | .pageWrapper {
19 | display: grid;
20 | grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
21 | grid-column-gap: 60px;
22 | overflow: hidden;
23 | max-width: 2000px;
24 | margin: 0 auto 20px;
25 | padding: 0 20px 20px 20px;
26 | }
27 |
28 | .pageWrapper > section:first-child {
29 | margin-top: 50px;
30 | order: 3;
31 | }
32 |
33 | .pageWrapper > form:nth-child(2) {
34 | order: 1;
35 | }
36 |
37 | .pageWrapper > section:nth-child(3) {
38 | order: 2;
39 | }
40 |
41 | .form select,
42 | .form input {
43 | display: block;
44 | box-sizing: border-box;
45 | width: 100%;
46 | border-radius: 4px;
47 | padding: 6px 10px;
48 | margin-bottom: 10px;
49 | font-size: 16px;
50 | }
51 |
52 | .form select:hover,
53 | .form input:hover {
54 | border: 1px solid var(--color-light-pink);
55 | }
56 |
57 | .form select:not([multiple]) {
58 | height: 40px;
59 | }
60 |
61 | .form input.form-error {
62 | border: 1px solid #bf1650;
63 | }
64 |
65 | .form input[type="checkbox"] {
66 | display: inline-block;
67 | width: auto;
68 | margin-right: 10px;
69 | }
70 |
71 | .form label {
72 | line-height: 2;
73 | text-align: left;
74 | display: block;
75 | margin-bottom: 13px;
76 | margin-top: 20px;
77 | }
78 |
79 | .form fieldset {
80 | border: 1px solid var(--color-light-blue);
81 | border-radius: 4px;
82 | }
83 |
84 | .closeButton {
85 | font-size: 25px;
86 | position: absolute;
87 | cursor: pointer;
88 | z-index: 5;
89 | border-radius: 4px;
90 | color: white;
91 | top: 20px;
92 | right: 30px;
93 | width: 50px;
94 | height: 50px;
95 | display: flex;
96 | justify-content: center;
97 | background: var(--color-primary);
98 | border: 1px solid white;
99 | }
100 |
101 | .closeButton:hover {
102 | border: 1px solid var(--color-secondary);
103 | }
104 |
105 | @media (min-width: 768px) {
106 | .pageWrapper > section:first-child {
107 | margin-top: 0;
108 | order: 1;
109 | }
110 |
111 | .pageWrapper > form:nth-child(2) {
112 | order: 2;
113 | }
114 |
115 | .pageWrapper > section:nth-child(3) {
116 | order: 3;
117 | }
118 |
119 | .closeButton {
120 | align-items: center;
121 | justify-content: center;
122 | display: flex;
123 | }
124 | }
125 |
126 | .buttonWrapper {
127 | display: flex;
128 | position: absolute;
129 | top: 10px;
130 | right: 5px;
131 | }
132 |
133 | .button {
134 | border: none;
135 | color: white;
136 | border-radius: 0;
137 | font-size: 13px;
138 | padding: 0 10px;
139 | right: 20px;
140 | z-index: 1;
141 | top: 10px;
142 | display: none;
143 | cursor: pointer;
144 | text-transform: uppercase;
145 | height: 34px;
146 | align-items: center;
147 | margin: 0 3px;
148 | }
149 |
150 | .button:hover {
151 | background: var(--color-secondary);
152 | color: white;
153 | }
154 |
155 | @media (min-width: 768px) {
156 | .button {
157 | display: flex;
158 | }
159 | }
160 |
161 | .copyButton {
162 | background: var(--color-light-blue);
163 | border: 1px solid transparent;
164 | }
165 |
166 | .active,
167 | .copyButton:hover {
168 | background: none;
169 | border: 1px solid var(--color-secondary);
170 | color: white;
171 | }
172 |
173 | .active,
174 | .copyButton:hover span {
175 | background: var(--color-primary);
176 | }
177 |
178 | .wrapper pre {
179 | line-height: 1.5 !important;
180 | }
181 |
182 | .wrapper pre code {
183 | display: none;
184 | }
185 |
186 | .wrapper pre code.showCode {
187 | display: block;
188 | }
189 |
--------------------------------------------------------------------------------
/src/components/ClipBoard.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react"
2 | import generic from "../data/generic"
3 |
4 | const ClipBoard = ({
5 | className,
6 | onClick,
7 | }: {
8 | onClick: () => void
9 | className?: string
10 | }) => {
11 | const [copiedCode, setCopiedCode] = useState(false)
12 |
13 | useEffect(() => {
14 | if (!copiedCode) {
15 | return
16 | }
17 |
18 | const timerId = setTimeout(() => {
19 | setCopiedCode(false)
20 | }, 3000)
21 |
22 | return () => {
23 | clearTimeout(timerId)
24 | }
25 | }, [copiedCode])
26 |
27 | return (
28 | {
31 | onClick()
32 | setCopiedCode(true)
33 | }}
34 | aria-label={generic.copied}
35 | >
36 | {copiedCode ? generic.codeCopied : generic.copy}
37 |
38 | )
39 | }
40 |
41 | export default ClipBoard
42 |
--------------------------------------------------------------------------------
/src/components/CodeArea.module.css:
--------------------------------------------------------------------------------
1 | .buttonWrapper {
2 | display: flex;
3 | position: absolute;
4 | top: 10px;
5 | right: 5px;
6 | }
7 |
8 | .button {
9 | border: none;
10 | color: white;
11 | border-radius: 0;
12 | font-size: 13px;
13 | padding: 0 10px;
14 | right: 20px;
15 | z-index: 1;
16 | top: 10px;
17 | display: none;
18 | cursor: pointer;
19 | text-transform: uppercase;
20 | height: 34px;
21 | align-items: center;
22 | margin: 0 3px;
23 | }
24 |
25 | .codeLink {
26 | background: var(--color-light-blue);
27 | border: 1px solid transparent;
28 | }
29 |
30 | .button:hover {
31 | background: var(--color-secondary);
32 | color: white;
33 | }
34 |
35 | @media (min-width: 768px) {
36 | .button {
37 | display: flex;
38 | }
39 | }
40 |
41 | .copyButton {
42 | background: none;
43 | border: 1px solid transparent;
44 | color: currentColor;
45 | }
46 |
47 | .active,
48 | .copyButton:hover {
49 | background: none;
50 | border: 1px solid var(--color-secondary);
51 | color: white;
52 | }
53 |
54 | .active,
55 | .copyButton:hover span {
56 | background: var(--color-primary);
57 | }
58 |
59 | .linkToSandBox {
60 | text-decoration: none;
61 | line-height: 2;
62 | right: 115px;
63 | color: inherit;
64 | }
65 |
66 | .linkToSandBox > svg {
67 | display: inline-block;
68 | height: 18px;
69 | position: relative;
70 | margin-right: 8px;
71 | }
72 |
--------------------------------------------------------------------------------
/src/components/CodeArea.tsx:
--------------------------------------------------------------------------------
1 | import copyClipBoard from "./utils/copyClipBoard"
2 | import ClipBoard from "./ClipBoard"
3 | import styles from "./CodeArea.module.css"
4 | import { useRef, useState } from "react"
5 | import { CodeSandBoxLink } from "./CodeSandbox"
6 | import { PrismSyntaxHighlight } from "./mdx/code"
7 |
8 | const ToggleTypes = {
9 | js: "JS",
10 | ts: "TS",
11 | types: "TYPES",
12 | } as const
13 |
14 | export default function CodeArea({
15 | rawData,
16 | tsRawData,
17 | rawTypes,
18 | url,
19 | tsUrl,
20 | withOutCopy,
21 | isExpo,
22 | }: {
23 | rawData?: string
24 | tsRawData?: string
25 | rawTypes?: string
26 | url?: string
27 | tsUrl?: string
28 | withOutCopy?: boolean
29 | isExpo?: boolean
30 | }) {
31 | const [currentType, setType] = useState<
32 | (typeof ToggleTypes)[keyof typeof ToggleTypes]
33 | >(() => {
34 | if (rawData) return ToggleTypes.js
35 | if (tsRawData) return ToggleTypes.ts
36 | return ToggleTypes.types
37 | })
38 |
39 | const codeAreaRef = useRef(null)
40 |
41 | return (
42 |
47 |
48 | {((rawData && tsRawData) || (rawData && rawTypes)) && (
49 | {
51 | setType(ToggleTypes.js)
52 | }}
53 | className={`${styles.button} ${styles.codeLink} ${
54 | currentType === ToggleTypes.js ? styles.active : ""
55 | }`}
56 | >
57 | JS
58 |
59 | )}
60 | {((tsRawData && rawData) || (tsRawData && rawTypes)) && (
61 | {
63 | setType(ToggleTypes.ts)
64 | }}
65 | className={`${styles.button} ${styles.codeLink} ${
66 | currentType === ToggleTypes.ts ? styles.active : ""
67 | }`}
68 | >
69 | TS
70 |
71 | )}
72 | {((rawTypes && rawData) || (rawTypes && tsRawData)) && (
73 | {
75 | setType(ToggleTypes.types)
76 | }}
77 | className={`${styles.button} ${styles.codeLink} ${
78 | currentType === ToggleTypes.types ? styles.active : ""
79 | }`}
80 | >
81 | Types
82 |
83 | )}
84 | {!withOutCopy && (
85 | {
88 | copyClipBoard(codeAreaRef.current?.innerText || "")
89 | }}
90 | />
91 | )}
92 |
93 | {((url && currentType === ToggleTypes.js) ||
94 | (tsUrl && currentType === ToggleTypes.ts) ||
95 | (tsUrl && currentType === ToggleTypes.types)) && (
96 |
101 | )}
102 |
103 |
104 |
105 | {currentType === ToggleTypes.js && (
106 |
107 | {rawData}
108 |
109 | )}
110 | {currentType === ToggleTypes.ts && (
111 |
112 | {tsRawData}
113 |
114 | )}
115 | {currentType === ToggleTypes.types && (
116 |
117 | {rawTypes}
118 |
119 | )}
120 |
121 |
122 | )
123 | }
124 |
--------------------------------------------------------------------------------
/src/components/CodeCompareSection.module.css:
--------------------------------------------------------------------------------
1 | .gridView {
2 | display: flex;
3 | flex-direction: column;
4 | }
5 |
6 | .gridView > div:first-child {
7 | order: 1;
8 | }
9 |
10 | .gridView iframe {
11 | display: none;
12 | box-shadow: 0 0 20px #010817;
13 | }
14 |
15 | .fullScreen {
16 | background: none;
17 | color: white;
18 | position: absolute;
19 | z-index: 1;
20 | right: 0;
21 | font-size: 12px;
22 | border-top: none;
23 | border-right: none;
24 | border-color: var(--color-secondary);
25 | border-bottom-left-radius: 4px;
26 | display: none;
27 | }
28 |
29 | .fullScreen:hover {
30 | background: var(--color-light-pink);
31 | }
32 |
33 | @media (min-width: 1000px) {
34 | .gridView {
35 | display: grid;
36 | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
37 | grid-column-gap: 40px;
38 | max-width: 1024px;
39 | margin: 0 auto;
40 | }
41 |
42 | .gridView iframe {
43 | display: block;
44 | }
45 |
46 | .gridView > div:first-child {
47 | order: 0;
48 | }
49 |
50 | .fullScreen {
51 | display: block;
52 | }
53 |
54 | .display {
55 | display: none;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/components/CodeCompareSection.tsx:
--------------------------------------------------------------------------------
1 | import { memo } from "react"
2 | import reactHookFormCode from "./codeExamples/reactHookFormCode"
3 | import CodeArea from "./CodeArea"
4 | import { AnimateGroup, Animate } from "react-simple-animate"
5 | import home from "../data/home"
6 | import typographyStyles from "../styles/typography.module.css"
7 | import containerStyles from "../styles/container.module.css"
8 | import styles from "./CodeCompareSection.module.css"
9 |
10 | const props = {
11 | start: { transform: "translateY(100px)" },
12 | end: { transform: "translateY(0)" },
13 | easeType: "ease-in",
14 | }
15 |
16 | function CodeCompareSection({
17 | isPlayCodeCompare,
18 | }: {
19 | isPlayCodeCompare: boolean
20 | }) {
21 | return (
22 |
23 |
28 |
29 |
{home.codeComparison.title}
30 |
31 | {home.codeComparison.description}
32 |
33 |
34 |
40 | {isPlayCodeCompare && (
41 |
57 | )}
58 |
59 | (
63 |
69 |
React Hook Form
70 |
71 |
72 | )}
73 | />
74 |
75 |
76 |
77 | )
78 | }
79 |
80 | export default memo(CodeCompareSection)
81 |
--------------------------------------------------------------------------------
/src/components/CodePerfCompareSection.module.css:
--------------------------------------------------------------------------------
1 | .imgSection {
2 | display: flex;
3 | flex-direction: column;
4 | }
5 |
6 | .imgSection > img {
7 | border-radius: 4px;
8 | max-width: 100%;
9 | margin: 20px 0;
10 | box-shadow: 0 0 8px #000000;
11 | object-fit: cover;
12 | }
13 |
14 | .imgSection ul {
15 | min-width: 250px;
16 | padding-left: 0;
17 | margin: 0 15px 0 20px;
18 | margin-left: 0;
19 | list-style-type: none;
20 | }
21 |
22 | .imgSection ul > li {
23 | padding: 2px 0;
24 | font-size: 16px;
25 | margin-left: 0;
26 | }
27 |
28 | .videoWrapper {
29 | width: 100%;
30 | height: 450px;
31 | display: flex;
32 | overflow-x: auto;
33 | -webkit-overflow-scrolling: touch;
34 | scroll-snap-type: x mandatory;
35 | margin: 20px 0 40px;
36 | }
37 |
38 | .videoWrapper p {
39 | text-align: center;
40 | }
41 |
42 | .videoWrapper > section:first-child {
43 | order: 1;
44 | }
45 |
46 | .videoWrapper > section {
47 | width: 100%;
48 | height: 100%;
49 | scroll-snap-align: start;
50 | flex-shrink: 0;
51 | overflow-y: hidden;
52 | }
53 | .videoWrapper > section > video {
54 | width: 100%;
55 | height: 100%;
56 | border-radius: 10px;
57 | }
58 |
59 | @media (min-width: 768px) {
60 | .imgSection {
61 | flex-direction: row;
62 | max-width: 80%;
63 | justify-content: center;
64 | }
65 |
66 | .imgSection ul {
67 | margin-left: 250px;
68 | }
69 |
70 | .videoWrapper > section:first-child {
71 | order: 0;
72 | }
73 |
74 | .videoWrapper {
75 | height: auto;
76 | display: grid;
77 | grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
78 | grid-column-gap: 60px;
79 | max-width: 768px;
80 | margin: 40px auto;
81 | overflow-y: hidden;
82 | }
83 |
84 | .videoWrapper > section > video {
85 | height: 400px;
86 | border-radius: 10px;
87 | margin-top: -44px;
88 | }
89 | }
90 |
91 | @media (min-width: 1024px) {
92 | .videoWrapper > section > video {
93 | height: 450px;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/components/CodePerfCompareSection.tsx:
--------------------------------------------------------------------------------
1 | import { memo } from "react"
2 | import { SimpleImg } from "react-simple-img"
3 | import { AnimateGroup } from "react-simple-animate"
4 | import home from "../data/home"
5 | import containerStyles from "../styles/container.module.css"
6 | import typographyStyles from "../styles/typography.module.css"
7 | import styles from "./CodePerfCompareSection.module.css"
8 |
9 | function CodePerfCompareSection({ isPlayRender }: { isPlayRender: boolean }) {
10 | return (
11 |
12 |
13 |
{home.mount.title}
14 |
15 | {home.mount.description}
16 |
17 |
18 |
24 | React Hook Form
25 |
26 |
27 |
28 | {home.mount.totalMount}: 1
29 | {home.mount.totalChange}: 1
30 |
31 | {home.mount.totalTime}:{" "}
32 | 1800ms
33 |
34 |
35 |
41 |
42 |
43 | Others
44 |
45 |
46 | {home.mount.totalMount}: 6
47 | {home.mount.totalChange}: 1
48 |
49 | {home.mount.totalTime}:{" "}
50 | 2070ms
51 |
52 |
53 |
59 |
60 |
61 |
62 |
63 | {home.mount.totalMount}: 17
64 | {home.mount.totalChange}: 2
65 |
66 | {home.mount.totalTime}:{" "}
67 | 2380ms
68 |
69 |
70 |
76 |
77 |
78 | )
79 | }
80 |
81 | export default memo(CodePerfCompareSection)
82 |
--------------------------------------------------------------------------------
/src/components/CodeSandbox.tsx:
--------------------------------------------------------------------------------
1 | import styles from "./CodeArea.module.css"
2 |
3 | export const CodeSandBoxLink = ({
4 | url,
5 | isJS,
6 | isExpo,
7 | }: {
8 | url?: string
9 | isExpo?: boolean
10 | isJS?: boolean
11 | }) => (
12 |
18 | {!isExpo && (
19 |
20 |
21 |
25 |
26 |
27 | )}{" "}
28 | {isExpo ? "Expo" : "CodeSandbox"}{" "}
29 | {typeof isJS === "boolean" && (
30 |
36 | {isJS ? "JS" : "TS"}
37 |
38 | )}
39 |
40 | )
41 |
--------------------------------------------------------------------------------
/src/components/DevToolFeaturesList.module.css:
--------------------------------------------------------------------------------
1 | .featuresContent {
2 | text-align: center;
3 | }
4 |
5 | .featuresContent h3 {
6 | font-weight: 400;
7 | font-size: 20px;
8 | margin-top: 10px;
9 | }
10 |
11 | .featuresContent svg {
12 | fill: var(--color-text);
13 | width: 50px;
14 | display: block;
15 | margin: 0 auto;
16 | height: 60px;
17 | }
18 |
19 | .featuresContent > article {
20 | padding-bottom: 30px;
21 | }
22 |
23 | .featuresContent > article > div {
24 | transform: scale(0);
25 | }
26 |
27 | .features {
28 | margin-top: -60px;
29 | }
30 |
31 | .features > h2 {
32 | margin-bottom: 30px;
33 | }
34 |
35 | @media (min-width: 768px) {
36 | .featuresContent h3 {
37 | font-size: 22px;
38 | }
39 |
40 | .features > h2 {
41 | max-width: 450px;
42 | margin: 0 auto 20px;
43 | }
44 |
45 | .features {
46 | margin-top: 60px;
47 | }
48 |
49 | .featuresContent {
50 | display: grid;
51 | grid-template-columns: repeat(3, 1fr);
52 | grid-column-gap: 40px;
53 | max-width: 800px;
54 | margin: 20px auto 30px;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/DevTools.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: grid;
3 | }
4 |
5 | .devToolImg {
6 | height: 230px;
7 | margin: 30px auto 80px;
8 | display: block;
9 | cursor: not-allowed;
10 | border-radius: 5px;
11 | }
12 |
13 | .devTool ::-webkit-scrollbar {
14 | }
15 |
16 | .devTool ::-webkit-scrollbar-track {
17 | background: inherit;
18 | }
19 |
20 | .devTool ::-webkit-scrollbar-thumb {
21 | background: grey;
22 | }
23 |
24 | .devTool ::-webkit-scrollbar-thumb:hover {
25 | background: black;
26 | }
27 |
28 | .demo {
29 | display: grid;
30 | grid-gap: 30px;
31 | max-width: 768px;
32 | margin: 0 auto;
33 | }
34 |
35 | .demo > div:first-child {
36 | order: 2;
37 | }
38 |
39 | @media (min-width: 768px) {
40 | .devToolImg {
41 | max-width: 600px;
42 | margin: 50px auto 0;
43 | display: block;
44 | height: auto;
45 | border-radius: 8px;
46 | min-height: 420px;
47 | }
48 |
49 | .demo {
50 | display: grid;
51 | grid-gap: 30px;
52 | grid-template-columns: 1fr 1fr;
53 | max-width: 768px;
54 | margin: 0 auto;
55 | }
56 |
57 | .demo > div:first-child {
58 | order: 0;
59 | }
60 | }
61 |
62 | @media (min-width: 1024px) {
63 | .devTool {
64 | display: block;
65 | }
66 |
67 | .devToolImg {
68 | max-width: 800px;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/components/FeatureList.module.css:
--------------------------------------------------------------------------------
1 | .featuresContent {
2 | text-align: center;
3 | }
4 |
5 | .featuresContent h3 {
6 | font-weight: 500;
7 | font-size: 20px;
8 | margin-top: 10px;
9 | }
10 |
11 | .featuresContent svg {
12 | width: 50px;
13 | display: block;
14 | margin: 0 auto;
15 | height: 60px;
16 | }
17 |
18 | .featuresContent > article {
19 | padding-bottom: 30px;
20 | }
21 |
22 | .featuresContent > article > div {
23 | transform: scale(0);
24 | }
25 |
26 | .features {
27 | margin-top: -60px;
28 | }
29 |
30 | .features > h2 {
31 | margin-bottom: 30px;
32 | }
33 |
34 | @media (min-width: 768px) {
35 | .featuresContent h3 {
36 | font-size: 22px;
37 | }
38 |
39 | .features > h2 {
40 | max-width: 450px;
41 | margin: 0 auto 20px;
42 | }
43 |
44 | .features {
45 | margin-top: 60px;
46 | margin-bottom: 60px;
47 | }
48 |
49 | .featuresContent {
50 | display: grid;
51 | grid-template-columns: repeat(3, 1fr);
52 | grid-column-gap: 30px;
53 | max-width: 1024px;
54 | margin: 40px auto 30px;
55 | }
56 | }
57 |
58 | @media (min-width: 1280px) {
59 | .featuresContent {
60 | grid-template-columns: repeat(6, 1fr);
61 | grid-column-gap: 25px;
62 | max-width: 1480px;
63 | }
64 |
65 | .featuresContent > article {
66 | padding-bottom: 0;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/components/Footer.module.css:
--------------------------------------------------------------------------------
1 | .footer {
2 | padding: 40px 0;
3 | font-size: 0.8rem;
4 | font-weight: 400;
5 | text-align: center;
6 | }
7 |
8 | .footer a {
9 | color: var(--color-footer);
10 | text-decoration: none;
11 | }
12 |
13 | .footer a:hover {
14 | color: var(--color-light-pink);
15 | text-decoration: none;
16 | }
17 |
18 | .footer > p {
19 | font-size: 13px;
20 | }
21 |
22 | .links {
23 | border-bottom: 1px solid var(--color-light-pink);
24 | max-width: 900px;
25 | margin: 0 auto 20px;
26 | padding: 0 0 10px 0;
27 | display: block;
28 | }
29 |
30 | .links > li {
31 | display: inline-flex;
32 | }
33 |
34 | .links > li > a {
35 | text-decoration: none;
36 | color: var(--color-text);
37 | padding: 10px 8px;
38 | min-width: 48px;
39 | min-height: 48px;
40 | }
41 |
42 | a.link {
43 | color: var(--color-primary);
44 | }
45 |
46 | .logoGroup {
47 | display: flex;
48 | justify-content: center;
49 | align-items: center;
50 | gap: 1rem;
51 | }
52 |
53 | .logoGroup img {
54 | width: 100px;
55 | }
56 |
57 | .heading {
58 | font-size: 0.6rem;
59 | font-weight: 600;
60 | margin-bottom: -0.2rem;
61 | }
62 |
--------------------------------------------------------------------------------
/src/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link"
2 | import nav from "../data/nav"
3 | import styles from "./Footer.module.css"
4 |
5 | export default function Footer() {
6 | return (
7 |
96 | )
97 | }
98 |
--------------------------------------------------------------------------------
/src/components/Form.module.css:
--------------------------------------------------------------------------------
1 | .code {
2 | padding: 0 20px;
3 | white-space: pre-wrap;
4 | font-size: 0.7rem;
5 | line-height: 1.6;
6 | }
7 |
8 | .wrapper {
9 | display: grid;
10 | min-height: 700px;
11 | transition: 1s all;
12 | grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
13 | grid-column-gap: 40px;
14 | max-width: 1440px;
15 | margin: 20px auto 0;
16 | }
17 |
18 | .demoForm {
19 | flex: 1;
20 | }
21 |
22 | .demoForm > select,
23 | .demoForm > input,
24 | .input {
25 | display: block;
26 | box-sizing: border-box;
27 | width: 100%;
28 | border-radius: 4px;
29 | padding: 6px 10px;
30 | margin-bottom: 10px;
31 | font-size: 0.9rem;
32 | }
33 |
34 | .demoForm > select:not([multiple]) {
35 | height: 43px;
36 | }
37 |
--------------------------------------------------------------------------------
/src/components/FormFields.module.css:
--------------------------------------------------------------------------------
1 | .radioGroup {
2 | display: flex;
3 | margin-bottom: 20px;
4 | }
5 |
6 | .radioGroup > label:not(:last-child) {
7 | margin-right: 20px;
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/FormStateTable.tsx:
--------------------------------------------------------------------------------
1 | import generic from "../data/generic"
2 | import API from "../data/api"
3 | import typographyStyles from "../styles/typography.module.css"
4 | import tableStyles from "../styles/table.module.css"
5 | import { FormStateApi } from "./FormStateApi"
6 |
7 | export default function FormStateTable({ api }: { api: typeof API }) {
8 | return (
9 | <>
10 |
11 | Return
12 |
13 |
14 |
15 |
16 |
17 | {generic.name}
18 | {generic.type}
19 | {generic.description}
20 |
21 |
22 |
23 |
24 |
25 | >
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/GetStarted.module.css:
--------------------------------------------------------------------------------
1 | .copyButton {
2 | display: none;
3 | background: var(--color-light-blue);
4 | color: white;
5 | font-size: 13px;
6 | float: right;
7 | text-transform: uppercase;
8 | border: 1px solid transparent;
9 | margin-top: -2px;
10 | cursor: pointer;
11 | }
12 |
13 | .copyButton:hover {
14 | background: none;
15 | border: 1px solid var(--color-secondary);
16 | color: white;
17 | }
18 |
19 | .copyButton:hover span {
20 | background: var(--color-primary);
21 | }
22 |
23 | @media (min-width: 768px) {
24 | .copyButton {
25 | display: inline-block;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/Header.module.css:
--------------------------------------------------------------------------------
1 | .logo {
2 | height: 80px;
3 | fill: white;
4 | padding: 15px;
5 | border: 8px solid white;
6 | border-radius: 20px;
7 | background: var(--color-light-pink);
8 | margin: -50px auto 0;
9 | }
10 |
11 | .desktopLogo {
12 | display: none;
13 | position: relative;
14 | height: 60px;
15 | padding: 8px;
16 | border-radius: 12px;
17 | background: var(--color-light-pink);
18 | border: 4px solid white;
19 | top: 10px;
20 | margin-right: 5px;
21 | }
22 |
23 | .head {
24 | display: flex;
25 | justify-content: center;
26 | align-items: center;
27 | height: 100vh;
28 | flex-direction: column;
29 | }
30 |
31 | .videoHeading {
32 | text-align: center;
33 | font-weight: normal;
34 | line-height: 2;
35 | border-bottom: 2px solid var(--color-secondary);
36 | display: block;
37 | margin-bottom: 0;
38 | }
39 |
40 | .toggleGroup {
41 | text-align: center;
42 | display: none;
43 | border: 1px solid var(--color-light-blue);
44 | border-radius: 4px;
45 | }
46 |
47 | .toggleGroup > button {
48 | width: 155px;
49 | color: white;
50 | padding: 10px 25px;
51 | border: 1px solid transparent;
52 | cursor: pointer;
53 | background: black;
54 | }
55 |
56 | .toggleGroup > button:active {
57 | transform: none;
58 | }
59 |
60 | .toggleGroup.smallToggleGroup > button {
61 | width: 70px;
62 | color: white;
63 | padding: 5px 15px;
64 | border: 1px solid transparent;
65 | cursor: pointer;
66 | background: black;
67 | font-size: 14px;
68 | }
69 |
70 | .toggleGroup > button:hover {
71 | background: var(--color-secondary);
72 | }
73 |
74 | .toggleGroup > button:first-child {
75 | border-right: 0;
76 | border-top-left-radius: 4px;
77 | border-bottom-left-radius: 4px;
78 | border-right: 1px solid var(--color-light-blue);
79 | }
80 |
81 | .toggleGroup > button:disabled {
82 | cursor: default;
83 | color: currentColor;
84 | background: transparent;
85 | }
86 |
87 | .toggleGroup > button:last-child {
88 | border-left: 0 !important;
89 | border-top-right-radius: 4px;
90 | border-bottom-right-radius: 4px;
91 | }
92 |
93 | .video {
94 | width: 100%;
95 | border-radius: 10px;
96 | display: block;
97 | box-shadow: 0px 0 9px 0px #010817;
98 | border: 1px solid transparent;
99 | cursor: pointer;
100 | transition: 0.3s all;
101 | }
102 |
103 | .video:hover {
104 | border: 1px solid var(--color-secondary);
105 | }
106 |
107 | .videoWrapperShow {
108 | margin-bottom: 100px;
109 | }
110 |
111 | .videoWrapperHide {
112 | margin-bottom: 100px;
113 | }
114 |
115 | @media (min-width: 320px) {
116 | .logo {
117 | height: 120px;
118 | }
119 | }
120 |
121 | @media (min-width: 768px) {
122 | .logo {
123 | display: none;
124 | }
125 |
126 | .head {
127 | height: auto;
128 | }
129 |
130 | .videoHeading {
131 | display: none;
132 | }
133 |
134 | .desktopLogo {
135 | fill: white;
136 | display: inline-block;
137 | }
138 |
139 | .toggleGroup {
140 | display: inline-block;
141 | margin: 0 auto 50px;
142 | }
143 |
144 | .video {
145 | width: 700px;
146 | height: 400px;
147 | margin: 0 auto 20px;
148 | }
149 |
150 | .videoWrapperShow {
151 | display: block;
152 | margin-bottom: 0;
153 | }
154 |
155 | .videoWrapperHide {
156 | display: none;
157 | margin-bottom: 0;
158 | }
159 |
160 | .logoHeading {
161 | margin-top: 70px;
162 | }
163 | }
164 |
165 | @media (min-width: 1024px) {
166 | .video {
167 | width: 800px;
168 | height: 480px;
169 | }
170 |
171 | .logoHeading {
172 | margin-top: 50px;
173 | }
174 | }
175 |
176 | @media (min-width: 1280px) {
177 | .video {
178 | width: 980px;
179 | height: 600px;
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/src/components/HomePage.module.css:
--------------------------------------------------------------------------------
1 | .root {
2 | padding: 0 20px 50px;
3 | position: relative;
4 | }
5 |
6 | .feedback {
7 | margin-top: 40px;
8 | }
9 |
10 | .feedback > div {
11 | border-radius: 15px;
12 | margin-bottom: 20px;
13 | }
14 |
15 | .feedback svg {
16 | margin: 0 auto 10px;
17 | width: 45px;
18 | }
19 |
20 | .feedback > div > p {
21 | font-size: 15px;
22 | text-align: left;
23 | padding: 20px 0;
24 | }
25 |
26 | @media (min-width: 768px) {
27 | .feedback {
28 | display: grid;
29 | grid-template-columns: repeat(3, 1fr);
30 | grid-gap: 50px;
31 | }
32 |
33 | .feedback > div {
34 | margin-bottom: 0;
35 | }
36 | }
37 |
38 | @media (min-width: 1024px) {
39 | .root {
40 | padding: 0 50px;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/IsolateRender.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | display: grid;
3 | grid-gap: 20px;
4 | margin-top: 20px;
5 | grid-template-columns: 1fr 1fr;
6 | position: relative;
7 | }
8 |
9 | .wrapper > div {
10 | display: none;
11 | }
12 |
13 | .wrapper p {
14 | font-size: 45px;
15 | font-weight: 800;
16 | margin-top: 160px;
17 | line-height: 1.4;
18 | }
19 |
20 | .wrapper h2 {
21 | font-size: 14px;
22 | }
23 |
24 | .externalComponent {
25 | font-size: 14px;
26 | border: 1px solid var(--color-secondary);
27 | padding: 10px 0;
28 | border-radius: 4px;
29 | margin: 20px 0;
30 | }
31 |
32 | .line {
33 | position: absolute;
34 | width: 1px;
35 | background: var(--color-blue);
36 | height: 44%;
37 | left: 50%;
38 | top: 30%;
39 | z-index: -1;
40 | }
41 |
42 | @media (min-width: 768px) {
43 | .wrapper {
44 | grid-gap: 40px;
45 | grid-template-columns: 1fr 65px 1fr;
46 | }
47 |
48 | .wrapper > div {
49 | display: block;
50 | }
51 |
52 | .wrapper h2 {
53 | font-size: 24px;
54 | font-weight: 400;
55 | padding-bottom: 10px;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/components/Menu/Menu.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link"
2 | import colors from "../../styles/colors"
3 | import styles from "./SideMenu.module.css"
4 | import typographyStyles from "../../styles/typography.module.css"
5 | import { useRouter } from "next/router"
6 | import { Pages } from "../../types/types"
7 |
8 | function Menu({ pages = [] }: { pages: Pages }) {
9 | const router = useRouter()
10 | const { asPath: pathname } = router
11 |
12 | return (
13 |
14 |
15 |
16 |
23 | Menu
24 |
25 |
26 |
27 |
28 | {pages.map((page) => {
29 | const isActive = pathname === page.pathname
30 |
31 | return (
32 |
39 | {`>`}
40 |
44 | {page.name}
45 |
46 |
47 | {page.pages && (
48 |
49 | {page.pages.map((page) => {
50 | const isActive = pathname === page.pathname
51 |
52 | return (
53 |
54 | {`>`}
{" "}
58 |
62 | {page.name}
63 |
64 |
65 | )
66 | })}
67 |
68 | )}
69 |
70 | )
71 | })}
72 |
73 |
74 |
75 |
76 |
77 | )
78 | }
79 |
80 | export default Menu
81 |
--------------------------------------------------------------------------------
/src/components/Menu/SideMenu.module.css:
--------------------------------------------------------------------------------
1 | .menu {
2 | display: none;
3 | position: relative;
4 | }
5 |
6 | .arrow {
7 | position: relative;
8 | color: var(--color-light-pink);
9 | }
10 |
11 | .arrowLast:before {
12 | content: "";
13 | position: absolute;
14 | top: 0;
15 | left: 0;
16 | height: 43%;
17 | border-left: 1px solid #ec5990;
18 | }
19 |
20 | .size {
21 | font-size: 10px;
22 | margin-left: 5px;
23 | color: currentColor;
24 | }
25 |
26 | @media (min-width: 768px) {
27 | .menu {
28 | display: block;
29 | }
30 |
31 | .menu > div {
32 | position: fixed;
33 | margin-top: -10px;
34 | }
35 |
36 | .menu > div > ul {
37 | margin-top: 0;
38 | max-width: 230px;
39 | padding: 0;
40 | overflow-y: auto;
41 | height: calc(100vh - 190px);
42 | overflow-y: auto;
43 | }
44 |
45 | .menu > div > ul > li {
46 | line-height: 22px;
47 | padding-bottom: 8px;
48 | font-size: 15px;
49 | display: flex;
50 | }
51 |
52 | .menu > div > ul > li > a {
53 | text-decoration: none;
54 | padding-left: 6px;
55 | line-height: 20px;
56 | }
57 |
58 | .menu > div > ul > li > a,
59 | .menu > div > ul > li > button {
60 | color: var(--color-text);
61 |
62 | transition: 0.3s all;
63 | background: none;
64 | border: none;
65 | cursor: pointer;
66 | border-bottom: 1px solid transparent;
67 | text-align: left;
68 | padding: 0;
69 | margin: 0 7px;
70 | }
71 |
72 | .menu > div > ul > li > a:hover,
73 | .menu > div > ul > li > button:hover {
74 | border-bottom: 1px solid var(--color-light-pink);
75 | }
76 |
77 | :global(.light) > .menu > div > ul > li > a,
78 | :global(.light) > .menu > div > ul > li > button {
79 | transition: 0.3s all;
80 | background: none;
81 | border: none;
82 | cursor: pointer;
83 | border-bottom: 1px solid transparent;
84 | text-align: left;
85 | }
86 |
87 | :global(.light) > .menu > div > ul > li > a:hover,
88 | :global(.light) > .menu > div > ul > li > button:hover {
89 | background: none;
90 | border-bottom: 1px solid var(--color-light-pink) !important;
91 | }
92 | }
93 |
94 | @media (min-height: 920px) {
95 | .menu > div > ul > li {
96 | padding-bottom: 12px;
97 | }
98 | }
99 |
100 | .titleList {
101 | width: 200px;
102 | }
103 |
104 | .code {
105 | color: var(--color-light-pink);
106 | position: relative;
107 | font-size: 12px;
108 | top: 2px;
109 | display: inline-table;
110 | flex-shrink: 0;
111 | }
112 |
113 | @media (min-width: 1024px) {
114 | .menu {
115 | margin-top: -130px;
116 | }
117 |
118 | .menu > div > ul {
119 | margin-top: 0;
120 | max-width: 260px;
121 | }
122 |
123 | .menu > ul {
124 | max-width: 250px;
125 | height: calc(100vh - 200px);
126 | }
127 |
128 | .titleList {
129 | width: 250px;
130 | margin-bottom: 20px;
131 | }
132 |
133 | .titleList > h2 {
134 | padding: 0;
135 | }
136 | }
137 |
138 | @media (min-width: 1280px) {
139 | .menu > ul {
140 | max-width: 270px;
141 | }
142 | }
143 |
144 | .menu li.menuItem {
145 | display: block;
146 | }
147 |
148 | .menuItem code {
149 | position: relative;
150 | }
151 |
152 | .menuItem li {
153 | padding: 3px 0;
154 | }
155 |
156 | .menu li.menuItem > ul {
157 | padding-left: 10px;
158 | }
159 |
160 | .menu li.menuItem > ul > li {
161 | padding-left: 20px;
162 | border-left: 1px solid var(--color-light-pink);
163 | position: relative;
164 | }
165 |
166 | .menu li.menuItem > ul > li:last-child {
167 | border-left: none;
168 | position: relative;
169 | }
170 |
171 | .menu li.menuItem > ul > li:last-child:after {
172 | content: "";
173 | position: absolute;
174 | bottom: 0px;
175 | top: 0;
176 | left: 0;
177 | height: 58%;
178 | border-left: 1px solid var(--color-light-pink);
179 | }
180 |
181 | .menu li.menuItem > ul > li:before {
182 | content: "";
183 | position: absolute;
184 | bottom: 12px;
185 | left: 0;
186 | width: 10px;
187 | border-bottom: 1px solid var(--color-light-pink);
188 | }
189 |
190 | .menu li.menuItem > ul a {
191 | color: currentColor;
192 | text-decoration: none;
193 | border-bottom: 1px solid transparent;
194 | }
195 |
196 | .menu li.menuItem > ul a:hover {
197 | border-bottom: 1px solid var(--color-light-pink);
198 | }
199 |
200 | .menu li.menuItem > ul > li {
201 | list-style: none;
202 | }
203 |
204 | .menu ul li a.isActive {
205 | border-bottom: 1px solid var(--color-secondary);
206 | }
207 |
--------------------------------------------------------------------------------
/src/components/Menu/index.ts:
--------------------------------------------------------------------------------
1 | import Menu from "./Menu"
2 | import {
3 | apiLinks,
4 | faqLinks,
5 | advancedLinks,
6 | tsLinks,
7 | getStartedLinks,
8 | } from "./MenuLinks"
9 |
10 | const links = {
11 | apiLinks,
12 | faqLinks,
13 | advancedLinks,
14 | tsLinks,
15 | getStartedLinks,
16 | }
17 |
18 | export {
19 | Menu,
20 | apiLinks,
21 | faqLinks,
22 | advancedLinks,
23 | tsLinks,
24 | getStartedLinks,
25 | links,
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/Popup.module.css:
--------------------------------------------------------------------------------
1 | .iconStyle,
2 | .icon,
3 | .button {
4 | border-radius: 50%;
5 | border: none;
6 | height: 18px;
7 | width: 18px;
8 | font-size: 15px;
9 | display: inline-flex;
10 | margin-left: 10px;
11 | justify-content: center;
12 | align-items: center;
13 | line-height: 1;
14 | }
15 |
16 | .icon {
17 | border: 1px solid var(--color-text);
18 | margin-left: 0;
19 | margin-right: 5px;
20 | }
21 |
22 | .root {
23 | font-weight: bold;
24 | position: relative;
25 | }
26 |
27 | .root > span {
28 | font-size: 14px !important;
29 | margin-left: 10px;
30 | font-weight: 400;
31 | display: inline-block;
32 | overflow: hidden;
33 | position: relative;
34 | top: 5px;
35 | }
36 |
37 | .root > span > span {
38 | display: inline-block;
39 | position: relative;
40 | font-family: sans-serif;
41 | }
42 |
43 | .button {
44 | cursor: pointer;
45 | }
46 |
47 | .button:hover {
48 | background: var(--color-light-pink);
49 | color: white;
50 | }
51 |
--------------------------------------------------------------------------------
/src/components/Popup.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { Animate } from "react-simple-animate"
3 | import styles from "./Popup.module.css"
4 |
5 | function Popup({
6 | message,
7 | top,
8 | iconOnly,
9 | }: {
10 | iconOnly?: boolean
11 | message?: string
12 | top?: number
13 | }) {
14 | const [tipShow, setTipShow] = useState(false)
15 |
16 | if (iconOnly) {
17 | return !
18 | }
19 |
20 | return (
21 |
22 | {
25 | setTipShow(!tipShow)
26 | }}
27 | >
28 | !
29 |
30 |
31 | (
40 |
41 | {message || <>React Native: compatible with Controller>}
42 |
43 | )}
44 | />
45 |
46 |
47 | )
48 | }
49 |
50 | export default Popup
51 |
--------------------------------------------------------------------------------
/src/components/ResourceList.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { useForm } from "react-hook-form"
3 | import headerStyles from "./Header.module.css"
4 | import styles from "./ResourcePage.module.css"
5 | import typographyStyles from "../styles/typography.module.css"
6 |
7 | interface Resource {
8 | title: string
9 | description?: string
10 | version?: string
11 | author?: string
12 | url: string
13 | authorUrl?: string
14 | }
15 |
16 | interface ResourceListFormData {
17 | filterResources: string
18 | }
19 |
20 | export default function ResourceList({
21 | title,
22 | resources,
23 | }: {
24 | title: string
25 | resources: Resource[]
26 | }) {
27 | const { register, watch } = useForm({
28 | mode: "onChange",
29 | })
30 | const [layout, setLayout] = useState("grid")
31 |
32 | const isGridLayout = layout === "grid"
33 |
34 | const filtered = resources.reduce((acc, cur) => {
35 | const { title, author, description, version } = cur
36 | // case insensitive filter
37 | if (
38 | `${title} ${author ?? ""} ${description ?? ""} ${
39 | version ? `v${version}` : ""
40 | }`.match(new RegExp(watch("filterResources"), "i"))
41 | ) {
42 | acc.push(cur)
43 | }
44 | return acc
45 | }, [])
46 |
47 | return (
48 |
49 |
50 | {title}
51 |
52 |
53 |
58 |
59 |
60 |
65 | {
70 | setLayout("grid")
71 | }}
72 | >
73 | Grid
74 |
75 | {
80 | setLayout("list")
81 | }}
82 | >
83 | List
84 |
85 |
86 |
87 |
88 |
91 | {filtered.map(
92 | ({ url, title, author, authorUrl, version, description }) => (
93 |
94 | {version && (
95 |
96 |
v{version || "6"}
97 |
98 | )}
99 |
100 |
101 | {title}
102 |
103 |
104 | {author && (
105 |
106 |
111 | {author}
112 |
113 |
114 | )}
115 |
116 | {description && {description}
}
117 |
118 |
119 | )
120 | )}
121 |
122 |
123 | )
124 | }
125 |
--------------------------------------------------------------------------------
/src/components/ResourcePageArticles.tsx:
--------------------------------------------------------------------------------
1 | import containerStyle from "../styles/container.module.css"
2 | import styles from "./ResourcePage.module.css"
3 | import Footer from "./Footer"
4 | import generic from "../data/generic"
5 | import ResourceList from "./ResourceList"
6 | import resources from "../data/resources"
7 | import StarRepo from "./StarRepo"
8 |
9 | export default function ResourcePageCondensed() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/ResourcePageBindings.tsx:
--------------------------------------------------------------------------------
1 | import containerStyle from "../styles/container.module.css"
2 | import styles from "./ResourcePage.module.css"
3 | import Footer from "./Footer"
4 | import generic from "../data/generic"
5 | import ResourceList from "./ResourceList"
6 | import resources from "../data/resources"
7 | import StarRepo from "./StarRepo"
8 |
9 | export default function ResourcePageBinding() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/ResourcePageNewsletter.tsx:
--------------------------------------------------------------------------------
1 | import containerStyle from "../styles/container.module.css"
2 | import styles from "./ResourcePage.module.css"
3 | import Footer from "./Footer"
4 | import generic from "../data/generic"
5 | import ResourceList from "./ResourceList"
6 | import resources from "../data/resources"
7 | import StarRepo from "./StarRepo"
8 |
9 | export default function ResourcePageNewsletter() {
10 | return (
11 |
12 |
13 |
17 |
18 |
19 |
20 |
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/ResourcePageVideos.tsx:
--------------------------------------------------------------------------------
1 | import containerStyle from "../styles/container.module.css"
2 | import styles from "./ResourcePage.module.css"
3 | import Footer from "./Footer"
4 | import generic from "../data/generic"
5 | import ResourceList from "./ResourceList"
6 | import resources from "../data/resources"
7 | import StarRepo from "./StarRepo"
8 |
9 | export default function ResourcePageVideos() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/Search.module.css:
--------------------------------------------------------------------------------
1 | .searchForm {
2 | margin-right: 12px;
3 | position: relative;
4 | }
5 |
6 | .whiteText {
7 | color: white !important;
8 | }
9 |
10 | .searchBar {
11 | padding: 0 10px;
12 | width: 26px;
13 | border-radius: 25px;
14 | font-size: 16px;
15 | line-height: 24px;
16 | height: 26px;
17 | z-index: 1;
18 | cursor: pointer;
19 | color: var(--color-primary) !important;
20 | }
21 |
22 | .searchBarOpen {
23 | padding: 3px 14px;
24 | width: 150px;
25 | }
26 |
27 | .searchBarOpen:focus {
28 | outline: none;
29 | border: 1px solid var(--color-light-pink);
30 | cursor: default;
31 | }
32 |
33 | .icon {
34 | color: black !important;
35 | position: absolute;
36 | left: 7px;
37 | z-index: 22;
38 | top: 5px;
39 | pointer-events: none;
40 | }
41 |
42 | @media (min-width: 1600px) {
43 | .searchBar {
44 | width: 150px;
45 | padding: 3px 14px;
46 | }
47 |
48 | .icon {
49 | display: none;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/components/Search.tsx:
--------------------------------------------------------------------------------
1 | import { useRef, useEffect } from "react"
2 | import clsx from "clsx"
3 | import searchStyles from "./Search.module.css"
4 | import useWindowSize from "./utils/useWindowSize"
5 | import { LARGE_SCREEN } from "../styles/breakpoints"
6 |
7 | const Search = ({
8 | focus,
9 | setFocus,
10 | }: {
11 | focus: boolean
12 | setFocus: (value: boolean) => void
13 | }) => {
14 | const { width } = useWindowSize()
15 | const searchRef = useRef(null)
16 |
17 | useEffect(() => {
18 | window.docsearch?.({
19 | appId: "Z2CKVVT2QA",
20 | apiKey: "c56a3f265ffbf85c2c654f865cb09164",
21 | indexName: "react-hook-form",
22 | inputSelector: "#algolia-doc-search",
23 | })
24 |
25 | return () => {
26 | setFocus(false)
27 | }
28 | }, [setFocus])
29 |
30 | useEffect(() => {
31 | if (LARGE_SCREEN <= width) {
32 | setFocus(true)
33 | } else {
34 | setFocus(false)
35 | searchRef.current?.blur()
36 | }
37 | }, [setFocus, width])
38 |
39 | return (
40 | <>
41 |
63 |
64 | >
65 | )
66 | }
67 |
68 | export default Search
69 |
--------------------------------------------------------------------------------
/src/components/SortableContainer.module.css:
--------------------------------------------------------------------------------
1 | .list {
2 | border: 1px solid var(--color-light-blue);
3 | padding: 14px 14px 14px 50px;
4 | border-radius: 4px;
5 | margin-bottom: 10px;
6 | background: var(--color-primary);
7 | cursor: move;
8 | position: relative;
9 | list-style: none;
10 | color: white;
11 | }
12 |
13 | .list > svg {
14 | fill: white;
15 | display: inline-block;
16 | width: 20px;
17 | position: absolute;
18 | left: 15px;
19 | top: 17px;
20 | }
21 |
22 | .editPanel {
23 | float: right;
24 | }
25 |
26 | .editPanel > button {
27 | position: relative;
28 | color: white;
29 | top: -2px;
30 | font-size: 14px;
31 | cursor: pointer;
32 | padding: 1px 8px;
33 | background: var(--color-light-blue);
34 | border: 1px solid transparent;
35 | text-transform: uppercase;
36 | letter-spacing: 1px;
37 | }
38 |
39 | .editPanel > button:hover {
40 | background: var(--color-primary);
41 | border: 1px solid var(--color-secondary);
42 | }
43 |
44 | .editPanel > button:first-child {
45 | margin-right: 14px;
46 | }
47 |
48 | .sortableWrapper {
49 | margin-top: 30px;
50 | }
51 |
52 | .sortableWrapper > ul {
53 | margin-left: 0;
54 | padding-left: 0;
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/StarRepo.tsx:
--------------------------------------------------------------------------------
1 | import generic from "../data/generic"
2 | import buttonStyles from "../styles/button.module.css"
3 | import containerStyles from "../styles/container.module.css"
4 |
5 | export default function StarRepo() {
6 | return (
7 |
11 |
{generic.needYourSupport.title}
12 |
{generic.needYourSupport.description}
13 |
{
16 | window.open(
17 | "https://github.com/react-hook-form/react-hook-form/stargazers"
18 | )
19 | }}
20 | style={{ margin: "40px auto" }}
21 | >
22 | {generic.needYourSupport.buttonText}
23 |
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/TabGroup.module.css:
--------------------------------------------------------------------------------
1 | .buttonTabGroup {
2 | display: grid;
3 | grid-auto-flow: column;
4 | }
5 |
6 | .buttonTabGroup > button {
7 | color: var(--color-text);
8 | background: var(--color-background);
9 | padding: 5px 8px 5px;
10 | font-size: 12px;
11 | border: none;
12 | transition: 0.3s all;
13 | text-transform: uppercase;
14 | }
15 |
16 | .buttonTabGroup > button:nth-child(n + 2) {
17 | margin-left: 3px;
18 | }
19 |
20 | .buttonTabGroup > button:hover {
21 | background: var(--color-secondary);
22 | }
23 |
24 | .buttonTabGroup > button:disabled {
25 | border-top: 1px solid var(--color-secondary);
26 | background: var(--color-primary);
27 | color: white;
28 | cursor: not-allowed;
29 | }
30 |
31 | @media (min-width: 768px) {
32 | .buttonTabGroup > button {
33 | padding: 5px 20px 5px;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/TabGroup.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import type { ReactNode } from "react"
3 | import styles from "./TabGroup.module.css"
4 |
5 | const TabGroup = ({
6 | children,
7 | buttonLabels,
8 | }: {
9 | children: ReactNode[]
10 | buttonLabels: string[]
11 | }) => {
12 | const [index, setIndex] = useState(0)
13 |
14 | return (
15 |
16 |
20 | {buttonLabels.map((label, currentIndex) => (
21 | {
28 | setIndex(currentIndex)
29 | }}
30 | >
31 | {label}
32 |
33 | ))}
34 |
35 | {children.map((child, currentIndex) => {
36 | return (
37 |
43 | {child}
44 |
45 | )
46 | })}
47 |
48 | )
49 | }
50 |
51 | export default TabGroup
52 |
--------------------------------------------------------------------------------
/src/components/Toggle.module.css:
--------------------------------------------------------------------------------
1 | /* The toggle - the box around the slider */
2 | .toggle {
3 | position: relative;
4 | display: inline-block;
5 | width: 40px;
6 | height: 26px;
7 | z-index: 1;
8 | margin-right: 12px;
9 | }
10 |
11 | /* Hide default HTML checkbox */
12 | .toggle input {
13 | opacity: 0;
14 | width: 0;
15 | height: 0;
16 | }
17 |
18 | /* The slider */
19 | .slider {
20 | position: absolute;
21 | cursor: pointer;
22 | top: 0;
23 | left: 0;
24 | right: 0;
25 | bottom: 0;
26 | background-color: white;
27 | -webkit-transition: 0.2s;
28 | transition: 0.2s;
29 | border: 1px solid transparent;
30 | }
31 |
32 | .slider:before {
33 | position: absolute;
34 | content: "";
35 | top: 1px;
36 | height: 22px;
37 | width: 20px;
38 | left: 2px;
39 | bottom: 4px;
40 | background-color: var(--color-blue);
41 | -webkit-transition: 0.2s;
42 | transition: 0.2s;
43 | }
44 |
45 | input:checked + .slider {
46 | background-color: var(--color-light-blue);
47 | }
48 |
49 | input:checked + .slider:before {
50 | background: white;
51 | }
52 |
53 | input:focus + .slider {
54 | border: 1px solid var(--color-light-pink);
55 | }
56 |
57 | input:checked + .slider:before {
58 | -webkit-transform: translateX(14px);
59 | -ms-transform: translateX(14px);
60 | transform: translateX(14px);
61 | }
62 |
63 | /* Rounded sliders */
64 | .slider.round {
65 | border-radius: 30px;
66 | }
67 |
68 | .slider.round:before {
69 | border-radius: 50%;
70 | }
71 |
--------------------------------------------------------------------------------
/src/components/Toggle.tsx:
--------------------------------------------------------------------------------
1 | import { CSSProperties, useEffect, useState } from "react"
2 | import styles from "./Toggle.module.css"
3 | import { useTheme } from "next-themes"
4 |
5 | export default function Toggle({ style }: { style?: CSSProperties }) {
6 | const [mounted, setMounted] = useState(false)
7 |
8 | const { theme, setTheme } = useTheme()
9 | const lightMode = theme === "light"
10 |
11 | // useEffect only runs on the client, so now we can safely show the UI
12 | useEffect(() => {
13 | setMounted(true)
14 | }, [])
15 |
16 | if (!mounted) {
17 | return null
18 | }
19 |
20 | return (
21 | // eslint-disable-next-line jsx-a11y/label-has-associated-control
22 |
23 | {
27 | if (e.target.checked) {
28 | setTheme("light")
29 | } else {
30 | setTheme("dark")
31 | }
32 | }}
33 | checked={lightMode}
34 | />
35 |
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/TypeText.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from "react"
2 | import typographyStyles from "../styles/typography.module.css"
3 |
4 | const TypeText = ({
5 | children,
6 | pre = false,
7 | }: {
8 | children: ReactNode
9 | pre: boolean
10 | }) => {
11 | const Element = pre ? "pre" : "span"
12 | return {children}
13 | }
14 |
15 | export default TypeText
16 |
--------------------------------------------------------------------------------
/src/components/UseController.tsx:
--------------------------------------------------------------------------------
1 | import typographyStyles from "../styles/typography.module.css"
2 | import Footer from "./Footer"
3 | import containerStyles from "../styles/container.module.css"
4 | import UseControllerContent from "./UseControllerContent"
5 | import StarRepo from "./StarRepo"
6 | import { Menu, apiLinks } from "./Menu"
7 |
8 | export default function UseController() {
9 | return (
10 |
11 |
12 | useController
13 |
14 |
15 | React hooks for controlled component
16 |
17 |
18 |
19 |
20 |
21 |
22 |
27 |
28 |
29 |
30 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/UseControllerContent.tsx:
--------------------------------------------------------------------------------
1 | import api from "../data/api"
2 | import generic from "../data/generic"
3 | import CodeArea from "./CodeArea"
4 | import useController from "./codeExamples/useController"
5 | import useControllerTs from "./codeExamples/useControllerTs"
6 | import tableStyles from "../styles/table.module.css"
7 | import typographyStyles from "../styles/typography.module.css"
8 | import UseControllerMethods from "./UseControllerMethods"
9 | import TabGroup from "./TabGroup"
10 | import useControllerCheckboxes from "./codeExamples/useControllerCheckboxes"
11 | import { SelectNav } from "./selectNav"
12 |
13 | export default function UseControllerContent() {
14 | return (
15 | <>
16 |
24 |
25 |
26 | useController:
27 |
28 | {`(props?: UseControllerProps) => { field: object, fieldState: object, formState: object }`}
31 |
32 |
33 | {api.useController.description}
34 | Props
35 |
36 | The following table contains information about the arguments for{" "}
37 | useController
.
38 |
39 |
40 |
41 |
42 |
43 | {generic.name}
44 | {generic.type}
45 | {generic.required}
46 | {generic.description}
47 |
48 |
49 | {api.useController.table}
50 |
51 |
52 |
53 |
54 |
55 |
56 | Examples
57 |
58 |
59 |
60 |
66 |
67 |
71 |
72 |
73 | {api.useController.tips}
74 | >
75 | )
76 | }
77 |
--------------------------------------------------------------------------------
/src/components/UseFieldArray.tsx:
--------------------------------------------------------------------------------
1 | import typographyStyles from "../styles/typography.module.css"
2 | import Footer from "./Footer"
3 | import containerStyles from "../styles/container.module.css"
4 | import UseFieldArrayContent from "./UseFieldArrayContent"
5 | import StarRepo from "./StarRepo"
6 | import { Menu, apiLinks } from "./Menu"
7 |
8 | export default function UseFieldArray() {
9 | return (
10 |
11 |
12 | useFieldArray
13 |
14 |
React hooks for Field Array
15 |
16 |
17 |
18 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/Watcher.module.css:
--------------------------------------------------------------------------------
1 | .root {
2 | display: none;
3 | }
4 |
5 | .svgWrapper {
6 | width: 200px;
7 | }
8 |
9 | .watchGroup {
10 | display: flex;
11 | height: 50px;
12 | }
13 |
14 | .watchGroup p {
15 | margin-top: 5px;
16 | padding: 0 0 0 50px;
17 | }
18 |
19 | .watchGroup input[type="checkbox"] {
20 | width: 20px;
21 | margin-top: 8px;
22 | margin-left: -60px;
23 | height: 20px;
24 | background: var(--color-background);
25 | border: 1px solid var(--color-secondary);
26 | border-radius: 2px;
27 | }
28 |
29 | .watchGroup input[type="checkbox"]:checked {
30 | border: 1px solid white;
31 | background: var(--color-primary);
32 | min-width: 20px;
33 | }
34 |
35 | .svgWrapper svg {
36 | width: 100%;
37 | }
38 |
39 | .svgWrapper svg path {
40 | stroke-dasharray: 10;
41 | animation: dash 10s linear normal infinite;
42 | }
43 |
44 | .behind {
45 | background: var(--color-background);
46 | }
47 |
48 | .closed {
49 | color: var(--color-background);
50 | }
51 |
52 | @keyframes dash {
53 | from {
54 | stroke-dashoffset: 500;
55 | }
56 | to {
57 | stroke-dashoffset: 0;
58 | }
59 | }
60 |
61 | @media (min-width: 768px) {
62 | .watcher {
63 | display: block;
64 | }
65 |
66 | .root {
67 | display: grid;
68 | margin: 40px auto;
69 | max-width: 800px;
70 | grid-template-columns: 1fr 1fr 200px;
71 | }
72 |
73 | .svgWrapper {
74 | display: block;
75 | width: 300px;
76 | }
77 |
78 | .svgWrapper svg {
79 | height: 200px;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/components/codeExamples/dependantFieldsTS.tsx:
--------------------------------------------------------------------------------
1 | export default `import * as React from "react";
2 | import { useForm } from "react-hook-form";
3 |
4 | type FormValues = {
5 | a: string;
6 | b: string;
7 | c: string;
8 | };
9 |
10 | export default function App() {
11 | const { watch, register, handleSubmit, setValue, formState } = useForm<
12 | FormValues
13 | >({
14 | defaultValues: {
15 | a: "",
16 | b: "",
17 | c: ""
18 | }
19 | });
20 | const onSubmit = (data: FormValues) => console.log(data);
21 | const [a, b] = watch(["a", "b"]);
22 |
23 | React.useEffect(() => {
24 | if (formState.touchedFields.a && formState.touchedFields.b && a && b) {
25 | setValue("c", \`\${a} \${b}\`);
26 | }
27 | }, [setValue, a, b, formState]);
28 |
29 | return (
30 |
46 | );
47 | }
48 | `
49 |
--------------------------------------------------------------------------------
/src/components/codeExamples/devTool.tsx:
--------------------------------------------------------------------------------
1 | export default `import { useForm } from "react-hook-form";
2 | import { DevTool } from "@hookform/devtools";
3 |
4 | export default () => {
5 | const { register, control, handleSubmit } = useForm({
6 | mode: "onChange",
7 | });
8 |
9 | return (
10 | <>
11 |
19 |
20 | {/* set up the dev tool */}
21 | >
22 | );
23 | };
24 | `
25 |
--------------------------------------------------------------------------------
/src/components/codeExamples/formState.ts:
--------------------------------------------------------------------------------
1 | export default `import { useForm } from "react-hook-form";
2 |
3 | export default function App() {
4 | const {
5 | register,
6 | handleSubmit,
7 | // Read the formState before render to subscribe the form state through the Proxy
8 | formState: { errors, isDirty, isSubmitting, touchedFields, submitCount },
9 | } = useForm();
10 | const onSubmit = (data) => console.log(data);
11 |
12 | return (
13 |
17 | );
18 | }
19 | `
20 |
--------------------------------------------------------------------------------
/src/components/codeExamples/formStateTs.ts:
--------------------------------------------------------------------------------
1 | export default `import { useForm } from "react-hook-form";
2 |
3 | type FormInputs = {
4 | test: string
5 | }
6 |
7 | export default function App() {
8 | const {
9 | register,
10 | handleSubmit,
11 | // Read the formState before render to subscribe the form state through Proxy
12 | formState: { errors, isDirty, isSubmitting, touchedFields, submitCount },
13 | } = useForm();
14 | const onSubmit = (data: FormInputs) => console.log(data);
15 |
16 | return (
17 |
21 | );
22 | }
23 | `
24 |
--------------------------------------------------------------------------------
/src/components/codeExamples/formStateUseEffect.ts:
--------------------------------------------------------------------------------
1 | export default `import { useForm } from "react-hook-form";
2 |
3 | export default function App () {
4 | const {
5 | register,
6 | handleSubmit,
7 | formState
8 | } = useForm();
9 |
10 | const onSubmit = (data) => console.log(data);
11 |
12 | React.useEffect(() => {
13 | console.log("touchedFields", formState.touchedFields);
14 | },[formState]); // use entire formState object as optional array arg in useEffect, not individual properties of it
15 |
16 |
17 | return (
18 |
22 | );
23 | };
24 |
25 | `
26 |
--------------------------------------------------------------------------------
/src/components/codeExamples/formStateUseEffectTs.ts:
--------------------------------------------------------------------------------
1 | export default `import { useForm } from "react-hook-form";
2 | type FormInputs = {
3 | test: string
4 | }
5 | export default function App() {
6 | const {
7 | register,
8 | handleSubmit,
9 | formState
10 | } = useForm();
11 | const onSubmit = (data: FormInputs) => console.log(data);
12 |
13 | React.useEffect(() => {
14 | console.log("touchedFields", formState.touchedFields);
15 | }, [formState]);
16 |
17 | return (
18 |
22 | );
23 | }
24 | `
25 |
--------------------------------------------------------------------------------
/src/components/codeExamples/reactHookFormCode.ts:
--------------------------------------------------------------------------------
1 | export default `import { useForm } from "react-hook-form";
2 |
3 | const Example = () => {
4 | const { handleSubmit, register, formState: { errors } } = useForm();
5 | const onSubmit = values => console.log(values);
6 |
7 | return (
8 |
30 | );
31 | };
32 | `
33 |
--------------------------------------------------------------------------------
/src/components/codeExamples/useController.ts:
--------------------------------------------------------------------------------
1 | export default `import { TextField } from "@material-ui/core";
2 | import { useController, useForm } from "react-hook-form";
3 |
4 | function Input({ control, name }) {
5 | const {
6 | field,
7 | fieldState: { invalid, isTouched, isDirty },
8 | formState: { touchedFields, dirtyFields }
9 | } = useController({
10 | name,
11 | control,
12 | rules: { required: true },
13 | });
14 |
15 | return (
16 |
23 | );
24 | }
25 | `
26 |
--------------------------------------------------------------------------------
/src/components/codeExamples/useControllerCheckboxes.ts:
--------------------------------------------------------------------------------
1 | export default `import * as React from "react";
2 | import { useController, useForm } from "react-hook-form";
3 |
4 | const Checkboxes = ({ options, control, name }) => {
5 | const { field } = useController({
6 | control,
7 | name
8 | });
9 | const [value, setValue] = React.useState(field.value || []);
10 |
11 | return (
12 | <>
13 | {options.map((option, index) => (
14 | {
16 | const valueCopy = [...value];
17 |
18 | // update checkbox value
19 | valueCopy[index] = e.target.checked ? e.target.value : null;
20 |
21 | // send data to react hook form
22 | field.onChange(valueCopy);
23 |
24 | // update local state
25 | setValue(valueCopy);
26 | }}
27 | key={option}
28 | checked={value.includes(option)}
29 | type="checkbox"
30 | value={option}
31 | />
32 | ))}
33 | >
34 | );
35 | };
36 |
37 | export default function App() {
38 | const { register, handleSubmit, control } = useForm({
39 | defaultValues: {
40 | controlled: [],
41 | uncontrolled: []
42 | }
43 | });
44 | const onSubmit = (data) => console.log(data);
45 |
46 | return (
47 |
65 | );
66 | }
67 | `
68 |
--------------------------------------------------------------------------------
/src/components/codeExamples/useControllerTs.ts:
--------------------------------------------------------------------------------
1 | export default `import * as React from "react";
2 | import { useForm, useController, UseControllerProps } from "react-hook-form";
3 |
4 | type FormValues = {
5 | FirstName: string;
6 | };
7 |
8 | function Input(props: UseControllerProps) {
9 | const { field, fieldState } = useController(props);
10 |
11 | return (
12 |
13 |
14 |
{fieldState.isTouched && "Touched"}
15 |
{fieldState.isDirty && "Dirty"}
16 |
{fieldState.invalid ? "invalid" : "valid"}
17 |
18 | );
19 | }
20 |
21 | export default function App() {
22 | const { handleSubmit, control } = useForm({
23 | defaultValues: {
24 | FirstName: ""
25 | },
26 | mode: "onChange"
27 | });
28 | const onSubmit = (data: FormValues) => console.log(data);
29 |
30 | return (
31 |
35 | );
36 | }
37 | `
38 |
--------------------------------------------------------------------------------
/src/components/codeExamples/useFieldArray.ts:
--------------------------------------------------------------------------------
1 | export default `import { useForm, useFieldArray } from "react-hook-form";
2 |
3 | function App() {
4 | const { register, control, handleSubmit, reset, trigger, setError } = useForm({
5 | // defaultValues: {}; you can populate the fields by this attribute
6 | });
7 | const { fields, append, remove } = useFieldArray({
8 | control,
9 | name: "test"
10 | });
11 |
12 | return (
13 |
35 | );
36 | }
37 | `
38 |
--------------------------------------------------------------------------------
/src/components/codeExamples/useFieldArrayArgument.ts:
--------------------------------------------------------------------------------
1 | export default `function FieldArray() {
2 | const { control, register } = useForm();
3 | const { fields, append, prepend, remove, swap, move, insert } = useFieldArray({
4 | control, // control props comes from useForm (optional: if you are using FormProvider)
5 | name: "test", // unique name for your Field Array
6 | });
7 |
8 | return (
9 | {fields.map((field, index) => (
10 |
14 | ))}
15 | );
16 | }
17 |
18 | `
19 |
--------------------------------------------------------------------------------
/src/components/codeExamples/useFieldArrayConditional.ts:
--------------------------------------------------------------------------------
1 | export default `import React from 'react';
2 | import { useForm, useWatch, useFieldArray, Control } from 'react-hook-form';
3 |
4 | type FormValues = {
5 | data: { name: string }[];
6 | };
7 |
8 | const ConditionField = ({
9 | control,
10 | index,
11 | register,
12 | }: {
13 | control: Control;
14 | index: number;
15 | }) => {
16 | const output = useWatch({
17 | name: 'data',
18 | control,
19 | defaultValue: 'yay! I am watching you :)',
20 | });
21 |
22 | return (
23 | <>
24 | {output[index]?.name === "bill" && (
25 |
26 | )}
27 |
31 | >
32 | );
33 | };
34 |
35 | const UseFieldArrayUnregister: React.FC = () => {
36 | const { control, handleSubmit, register } = useForm({
37 | defaultValues: {
38 | data: [{ name: 'test' }, { name: 'test1' }, { name: 'test2' }],
39 | },
40 | mode: 'onSubmit',
41 | shouldUnregister: false,
42 | });
43 | const { fields } = useFieldArray({
44 | control,
45 | name: 'data',
46 | });
47 | const onSubmit = (data: FormValues) => console.log(data);
48 |
49 | return (
50 |
59 | );
60 | };
61 | `
62 |
--------------------------------------------------------------------------------
/src/components/codeExamples/useFieldArrayFocus.ts:
--------------------------------------------------------------------------------
1 | export default `import React from 'react';
2 | import { useForm, useFieldArray } from 'react-hook-form';
3 |
4 | const App = () => {
5 | const { register, control } = useForm<{
6 | test: { value: string }[];
7 | }>({
8 | defaultValues: {
9 | test: [{ value: '1' }, { value: '2' }],
10 | },
11 | });
12 | const { fields, prepend, append } = useFieldArray({
13 | name: 'test',
14 | control,
15 | });
16 |
17 | return (
18 |
35 | );
36 | };
37 | `
38 |
--------------------------------------------------------------------------------
/src/components/codeExamples/useFieldArrayPreview.ts:
--------------------------------------------------------------------------------
1 | export default `import * as React from "react";
2 | import { useForm, useFieldArray, useWatch } from "react-hook-form";
3 |
4 | export default function App() {
5 | const { control, handleSubmit } = useForm();
6 | const { fields, append, update } = useFieldArray({
7 | control,
8 | name: 'array'
9 | });
10 |
11 | return (
12 |
33 | );
34 | }
35 |
36 | const Display = ({ control, index }) => {
37 | const data = useWatch({
38 | control,
39 | name: \`array.\${index}\`
40 | });
41 | return {data?.firstName}
;
42 | };
43 |
44 | const Edit = ({ update, index, value, control }) => {
45 | const { register, handleSubmit } = useForm({
46 | defaultValues: value
47 | });
48 |
49 | return (
50 |
51 |
52 |
53 |
57 |
58 | update(index, data))}
61 | >
62 | Submit
63 |
64 |
65 | );
66 | };
67 |
68 | `
69 |
--------------------------------------------------------------------------------
/src/components/codeExamples/useFieldArrayTS.ts:
--------------------------------------------------------------------------------
1 | export default `
2 | import * as React from "react";
3 | import { useForm, useFieldArray, useWatch, Control } from "react-hook-form";
4 |
5 | type FormValues = {
6 | cart: {
7 | name: string;
8 | price: number;
9 | quantity: number;
10 | }[];
11 | };
12 |
13 | const Total = ({ control }: { control: Control }) => {
14 | const formValues = useWatch({
15 | name: "cart",
16 | control
17 | });
18 | const total = formValues.reduce(
19 | (acc, current) => acc + (current.price || 0) * (current.quantity || 0),
20 | 0
21 | );
22 | return Total Amount: {total}
;
23 | };
24 |
25 | export default function App() {
26 | const {
27 | register,
28 | control,
29 | handleSubmit,
30 | formState: { errors }
31 | } = useForm({
32 | defaultValues: {
33 | cart: [{ name: "test", quantity: 1, price: 23 }]
34 | },
35 | mode: "onBlur"
36 | });
37 | const { fields, append, remove } = useFieldArray({
38 | name: "cart",
39 | control
40 | });
41 | const onSubmit = (data: FormValues) => console.log(data);
42 |
43 | return (
44 |
100 | );
101 | }
102 | `
103 |
--------------------------------------------------------------------------------
/src/components/codeExamples/useFormState.ts:
--------------------------------------------------------------------------------
1 | export default `import * as React from "react";
2 | import { useForm, useFormState } from "react-hook-form";
3 |
4 | function Child({ control }) {
5 | const { dirtyFields } = useFormState({
6 | control
7 | });
8 |
9 | return dirtyFields.firstName ? Field is dirty.
: null;
10 | };
11 |
12 | export default function App() {
13 | const { register, handleSubmit, control } = useForm({
14 | defaultValues: {
15 | firstName: "firstName"
16 | }
17 | });
18 | const onSubmit = (data) => console.log(data);
19 |
20 | return (
21 |
27 | );
28 | }
29 | `
30 |
--------------------------------------------------------------------------------
/src/components/general-observer.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | FunctionComponent,
3 | RefObject,
4 | useEffect,
5 | useRef,
6 | useState,
7 | ReactNode,
8 | } from "react"
9 |
10 | interface IGeneralObserverProps {
11 | /** React Children */
12 | children: ReactNode
13 | placeholder?: ReactNode
14 | /** Fires when IntersectionObserver enters viewport */
15 | onEnter?: (id?: string) => void
16 | }
17 |
18 | export const GeneralObserver: FunctionComponent = ({
19 | children,
20 | onEnter,
21 | placeholder,
22 | }) => {
23 | const ref = useRef(null)
24 | const [isChildVisible, setIsChildVisible] = useState(false)
25 |
26 | useEffect(() => {
27 | const observer = new IntersectionObserver(
28 | ([entry]) => {
29 | if (entry.isIntersecting) {
30 | setIsChildVisible(true)
31 | onEnter?.()
32 | }
33 | },
34 | {
35 | rootMargin: "0px 0px",
36 | threshold: [1],
37 | }
38 | )
39 | if (ref.current) {
40 | observer.observe(ref.current)
41 | }
42 |
43 | return () => {
44 | observer.disconnect()
45 | }
46 | }, [ref, onEnter])
47 |
48 | let slot = children
49 |
50 | if (placeholder && isChildVisible) {
51 | slot = placeholder
52 | }
53 |
54 | return (
55 | } data-testid="general-observer">
56 | {slot}
57 |
58 | )
59 | }
60 |
--------------------------------------------------------------------------------
/src/components/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Animate } from "react-simple-animate"
2 | import { getEditLink } from "./logic/getEditLink"
3 | import Nav from "./Nav"
4 | import { useEffect, useState, ReactNode } from "react"
5 | import { useRouter } from "next/router"
6 |
7 | const Layout = ({ children }: { children: ReactNode }) => {
8 | const router = useRouter()
9 | const location = router
10 |
11 | const [show, setShow] = useState(false)
12 | const scrollHandler = () => {
13 | if (window.scrollY > 75) {
14 | setShow(true)
15 | } else {
16 | setShow(false)
17 | }
18 | }
19 | const editLink = getEditLink(location.pathname)
20 |
21 | useEffect(() => {
22 | window.addEventListener("scroll", scrollHandler)
23 |
24 | return () => {
25 | window.removeEventListener("scroll", scrollHandler)
26 | }
27 | }, [])
28 |
29 | return (
30 | <>
31 | {/**/}
44 |
45 | Skip to content
46 |
47 |
48 | {children}
49 |
54 | {editLink && (
55 |
62 | Edit
63 |
64 | )}
65 | {
69 | window.scrollTo({
70 | top: 0,
71 | left: 0,
72 | behavior: "smooth",
73 | })
74 | }}
75 | >
76 | ▲
77 |
78 |
79 | >
80 | )
81 | }
82 |
83 | export default Layout
84 |
--------------------------------------------------------------------------------
/src/components/learnMore.tsx:
--------------------------------------------------------------------------------
1 | import generic from "../data/generic"
2 | import containerStyles from "../styles/container.module.css"
3 | import buttonStyles from "../styles/button.module.css"
4 | import { useRouter } from "next/router"
5 |
6 | export default function LearnMore() {
7 | const router = useRouter()
8 | return (
9 |
10 |
{generic.learnMore.title}
11 |
{generic.learnMore.description}
12 |
13 |
{
16 | router.push("/docs")
17 | }}
18 | style={{ margin: "40px auto" }}
19 | >
20 | {generic.learnMore.buttonText}
21 |
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/logic/generateCode.ts:
--------------------------------------------------------------------------------
1 | import type { GlobalState } from "little-state-machine"
2 |
3 | export default (formData: GlobalState["formData"]) => {
4 | return `import React from 'react';
5 | import { useForm } from 'react-hook-form';
6 |
7 | export default function App() {
8 | const { register, handleSubmit, formState: { errors } } = useForm();
9 | const onSubmit = data => console.log(data);
10 | console.log(errors);
11 |
12 | return (
13 |
124 | );
125 | }`
126 | }
127 |
--------------------------------------------------------------------------------
/src/components/logic/getEditLink.tsx:
--------------------------------------------------------------------------------
1 | const preFix =
2 | "https://github.com/react-hook-form/documentation/edit/master/src/"
3 |
4 | const dataPreFix = "data/"
5 | const pagesPreFix = "pages/"
6 | const content = "content/"
7 |
8 | const filterApiPageURL = (pathname: string): string => {
9 | if (pathname.charAt(pathname.length - 1) === "/")
10 | return pathname.substring(0, pathname.length - 1).substring(1)
11 |
12 | return pathname.substring(1)
13 | }
14 |
15 | export const getEditLink = (pathname: string): string => {
16 | if (!pathname) return ""
17 |
18 | if (pathname === "/" || pathname === "") {
19 | return `${preFix}${dataPreFix}/home.tsx`
20 | } else if (pathname.includes("get-started")) {
21 | return `${preFix}${content}get-started.mdx`
22 | } else if (pathname.includes("api")) {
23 | const splitPath = pathname.split("/")
24 | if (splitPath.length === 2 || splitPath[2] === "") {
25 | return `${preFix}${dataPreFix}api.tsx`
26 | }
27 | return `${preFix}${pagesPreFix}${filterApiPageURL(pathname)}.tsx`
28 | } else if (pathname.includes("ts")) {
29 | return `${preFix}${content}ts.mdx`
30 | } else if (pathname.includes("advanced-usage")) {
31 | return `${preFix}${content}advanced.mdx`
32 | } else if (pathname.includes("faqs")) {
33 | return `${preFix}${content}faq.mdx`
34 | } else if (pathname.includes("dev-tools")) {
35 | return `${preFix}${dataPreFix}devtools.tsx`
36 | } else if (pathname.includes("form-builder")) {
37 | return `${preFix}${dataPreFix}builder.tsx`
38 | } else if (pathname.includes("resources")) {
39 | return `${preFix}${dataPreFix}resources.tsx`
40 | }
41 |
42 | return ""
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/mdx/code.tsx:
--------------------------------------------------------------------------------
1 | import { useMemo } from "react"
2 | import type { ReactNode } from "react"
3 | import { Highlight } from "prism-react-renderer"
4 | import { useTheme } from "next-themes"
5 | import { darkTheme, lightTheme } from "./theme"
6 |
7 | function usePrismTheme() {
8 | const { theme } = useTheme()
9 |
10 | return useMemo(() => {
11 | if (theme === "light") return lightTheme
12 | return darkTheme
13 | }, [theme])
14 | }
15 |
16 | export const PrismSyntaxHighlight = ({
17 | children,
18 | className,
19 | }: {
20 | children: ReactNode
21 | className: string
22 | }) => {
23 | const language = className.replace(/language-/gm, "")
24 |
25 | const currentTheme = usePrismTheme()
26 |
27 | return (
28 |
33 | {({ className, style, tokens, getLineProps, getTokenProps }) => (
34 |
35 | {tokens.map((line, lineIndex) => {
36 | const { key: lineKey, ...lineProps } = getLineProps({
37 | line,
38 | key: lineIndex,
39 | })
40 |
41 | return (
42 |
43 | {line.map((lineToken, linenTokenIndex) => {
44 | const { key: lineTokenKey, ...lineTokenProps } =
45 | getTokenProps({
46 | token: lineToken,
47 | key: linenTokenIndex,
48 | })
49 | return (
50 |
51 | )
52 | })}
53 |
54 | )
55 | })}
56 |
57 | )}
58 |
59 | )
60 | }
61 |
--------------------------------------------------------------------------------
/src/components/mdx/mdx.tsx:
--------------------------------------------------------------------------------
1 | import { PrismSyntaxHighlight } from "./code"
2 | import { Pre } from "./pre"
3 | import TabGroup from "../TabGroup"
4 | import { YouTube } from "./youtube"
5 | import TypeText from "../TypeText"
6 | import Popup from "../Popup"
7 | import { Components } from "@mdx-js/react/lib"
8 | import { Admonition } from "../Admonition"
9 | import { CodeSandBoxLink } from "../CodeSandbox"
10 | import tableStyles from "../../styles/table.module.css"
11 | import { SelectNav } from "@/components/selectNav"
12 | import CodeArea from "@/components/CodeArea"
13 |
14 | export const MDXComponents: Components = {
15 | // p: P,
16 | // strong: Strong,
17 | // blockquote: Blockquote,
18 | // ol: OL,
19 | // ul: UL,
20 | // li: LI,
21 | // h1: H1,
22 | // h2: H2,
23 | // h3: H3,
24 | // h4: H4,
25 | // hr: Divider,
26 | // a: Link,
27 | // img: ResponsiveImage,
28 | // Layout,
29 | SelectNav,
30 | CodeArea,
31 | table(props) {
32 | return (
33 |
36 | )
37 | },
38 | Admonition,
39 | Popup,
40 | TypeText,
41 | YouTube(props) {
42 | return
43 | },
44 | CodeSandbox: CodeSandBoxLink,
45 | pre(props) {
46 | return
47 | },
48 | code({ className, children, ...props }) {
49 | return className ? (
50 |
51 | {children}
52 |
53 | ) : (
54 | {children}
55 | )
56 | },
57 | TabGroup,
58 | PrettyObject({ value }: { value: Record }) {
59 | return JSON.stringify(value, null, 2).replace(/"/g, "")
60 | },
61 | }
62 |
--------------------------------------------------------------------------------
/src/components/mdx/pre.tsx:
--------------------------------------------------------------------------------
1 | import type { DetailedHTMLProps, HTMLAttributes } from "react"
2 | import { isValidElement, useRef } from "react"
3 | import ClipBoard from "../ClipBoard"
4 | import styles from "../CodeArea.module.css"
5 | import copyClipBoard from "../utils/copyClipBoard"
6 | import { CodeSandBoxLink } from "../CodeSandbox"
7 |
8 | export const Pre = (
9 | props: DetailedHTMLProps, HTMLPreElement> & {
10 | copy?: boolean
11 | sandbox?: string
12 | expo?: boolean
13 | }
14 | ) => {
15 | const preRef = useRef(null)
16 |
17 | const language = isValidElement<{ className?: string }>(props.children)
18 | ? props.children.props.className?.replace(/language-/gm, "") || ""
19 | : ""
20 |
21 | const isJs = language === "javascript"
22 |
23 | return (
24 |
29 |
30 | {props.copy && (
31 | {
34 | if (preRef.current?.innerText) {
35 | copyClipBoard(preRef.current.innerText)
36 | }
37 | }}
38 | />
39 | )}
40 | {props.sandbox && (
41 |
46 | )}
47 |
48 |
{props.children}
49 |
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/src/components/mdx/youtube.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent } from "react"
2 |
3 | import { GeneralObserver } from "../general-observer"
4 |
5 | interface YouTubeProps {
6 | /** YouTube id */
7 | youTubeId: string
8 |
9 | /** Auto play the video */
10 | autoPlay?: boolean
11 |
12 | /** No Cookie option */
13 | noCookie?: boolean
14 | }
15 |
16 | export const YouTube: FunctionComponent = (
17 | props: YouTubeProps
18 | ) => {
19 | const { youTubeId, autoPlay = false, noCookie = false } = props
20 |
21 | const provider = noCookie
22 | ? "https://www.youtube-nocookie.com"
23 | : "https://www.youtube.com"
24 |
25 | const src = `${provider}/embed/${youTubeId}?&autoplay=${autoPlay.toString()}`
26 |
27 | return (
28 |
29 |
39 |
40 | )
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/selectNav.module.css:
--------------------------------------------------------------------------------
1 | .root {
2 | width: 100%;
3 | margin-top: 1rem;
4 | background: none;
5 | appearance: none;
6 | color: var(--color-text);
7 | border: 1px solid var(--color-light-blue);
8 | border-radius: 3px;
9 | padding-left: 15px;
10 | padding-right: 15px;
11 | cursor: pointer;
12 | position: relative;
13 | }
14 |
15 | .root:hover {
16 | border: 1px solid var(--color-secondary);
17 | }
18 |
19 | .root > option {
20 | color: black;
21 | }
22 |
23 | @media (min-width: 768px) {
24 | .root {
25 | display: none;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/selectNav.tsx:
--------------------------------------------------------------------------------
1 | import styles from "./selectNav.module.css"
2 | import { useRouter } from "next/router"
3 |
4 | type Props = {
5 | options: {
6 | value: string
7 | label: string
8 | }[]
9 | }
10 |
11 | export function SelectNav({ options }: Props) {
12 | const router = useRouter()
13 |
14 | return (
15 | // eslint-disable-next-line jsx-a11y/no-onchange
16 | {
19 | router.push(e.target.value)
20 | }}
21 | >
22 | Select page...
23 | {options.map((option) => {
24 | return (
25 |
26 | {option.label}
27 |
28 | )
29 | })}
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/seo.tsx:
--------------------------------------------------------------------------------
1 | import Head from "next/head"
2 |
3 | const site = {
4 | siteMetadata: {
5 | title: `React Hook Form - Simple React forms validation`,
6 | description: `Performant, flexible and extensible forms with easy-to-use validation.`,
7 | author: `@bluebill1049`,
8 | siteUrl: "https://react-hook-form.com",
9 | languages: {
10 | langs: ["en", "es", "jp", "zh", "kr", "pt", "ru"],
11 | defaultLangKey: "en",
12 | },
13 | },
14 | }
15 |
16 | function SEO({ title, description }: { title: string; description?: string }) {
17 | const metaDescription = description || site.siteMetadata.description
18 |
19 | return (
20 |
21 | {title || site.siteMetadata.title}
22 |
27 |
31 |
32 |
36 |
37 |
38 |
42 |
46 |
47 | )
48 | }
49 |
50 | export default SEO
51 |
--------------------------------------------------------------------------------
/src/components/sponsorsList.module.css:
--------------------------------------------------------------------------------
1 | .root {
2 | text-align: center;
3 | margin: 1rem 0 1rem 0;
4 | font-size: 0.8rem;
5 | border-top: 1px solid var(--color-black);
6 | padding-top: 1.5rem;
7 | }
8 |
9 | .heading {
10 | margin-bottom: 2rem;
11 | opacity: 0.7;
12 | font-weight: 500;
13 | }
14 |
15 | .root a {
16 | color: white;
17 | text-decoration: none;
18 | display: flex;
19 | align-content: center;
20 | justify-content: center;
21 | }
22 |
23 | .root a img {
24 | width: 120px;
25 | height: auto;
26 | margin: auto 0;
27 | }
28 |
29 | .logoGroup {
30 | display: grid;
31 | grid-template-columns: repeat(2, 1fr);
32 | gap: 1rem;
33 | margin: 0 1rem;
34 | }
35 |
36 | .logoGroup > div {
37 | border-radius: 4px;
38 | padding: 0.5rem;
39 | }
40 |
41 | .placeholder {
42 | border: 1px dashed #ccc;
43 | border-radius: 4px;
44 | padding: 0.5rem;
45 | }
46 |
47 | .twicsy {
48 | border-radius: 50px;
49 | }
50 |
51 | @media (min-width: 768px) {
52 | .logoGroup {
53 | display: grid;
54 | max-width: 600px;
55 | margin: 0 auto;
56 | grid-template-columns: repeat(4, 1fr);
57 | gap: 1.5rem;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/components/sponsorsList.tsx:
--------------------------------------------------------------------------------
1 | import styles from "./sponsorsList.module.css"
2 |
3 | export function SponsorsList() {
4 | return (
5 |
6 |
SUPPORTED AND BACKED BY
7 |
8 |
49 |
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/src/components/utils/copyClipBoard.ts:
--------------------------------------------------------------------------------
1 | export default function copyToClipboard(text: string) {
2 | return new Promise((resolve, reject) => {
3 | if (navigator?.clipboard) {
4 | const cb = navigator.clipboard
5 | cb.writeText(text).then(resolve).catch(reject)
6 | } else {
7 | try {
8 | const body = document.querySelector("body")
9 |
10 | const textarea = document.createElement("textarea")
11 | body?.appendChild(textarea)
12 |
13 | textarea.value = text
14 | textarea.select()
15 |
16 | document.execCommand("copy")
17 |
18 | body?.removeChild(textarea)
19 |
20 | resolve(0)
21 | } catch (e) {
22 | reject(e as Error)
23 | }
24 | }
25 | })
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/utils/goToBuilder.ts:
--------------------------------------------------------------------------------
1 | export default function goToBuilder(toggle = true) {
2 | const title = " | React hook form - Simple React form validation"
3 |
4 | if (toggle) {
5 | document.title = `Form Builder${title}`
6 | window.history.pushState(
7 | { page: `Form Builder${title}` },
8 | `Form Builder${title}`,
9 | `/form-builder`
10 | )
11 | } else {
12 | document.title = `Home${title}`
13 | window.history.pushState({ page: `Home${title}` }, `Home${title}`, `/`)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/utils/useWindowSize.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react"
2 |
3 | interface Size {
4 | width: number
5 | height: number
6 | }
7 |
8 | export default function useWindowSize(): Size {
9 | const [windowSize, setWindowSize] = useState({
10 | width: 0,
11 | height: 0,
12 | })
13 |
14 | useEffect(() => {
15 | function handleResize() {
16 | setWindowSize({
17 | width: window.innerWidth,
18 | height: window.innerHeight,
19 | })
20 | }
21 |
22 | window.addEventListener("resize", handleResize)
23 |
24 | return () => {
25 | window.removeEventListener("resize", handleResize)
26 | }
27 | }, [])
28 |
29 | return windowSize
30 | }
31 |
--------------------------------------------------------------------------------
/src/content/docs/createFormControl.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: createFormControl
3 | description: Create form state and ready to be subscribed
4 | sidebar: apiLinks
5 | ---
6 |
7 | This function create the entire form state subscription and allow you to subscribe update with or without react component. You can use this function without the need of React Context api.
8 |
9 | ### Props
10 |
11 | ---
12 |
13 | | Name | Type | Description |
14 | | ---------- | --------------------------- | -------------- |
15 | | `...props` | Object | `UseFormProps` |
16 |
17 | ### Returns
18 |
19 | ---
20 |
21 | | Name | Type | Description |
22 | | ------------- | ------------------------------ | ------------------------------------------------------------------------------------- |
23 | | `formControl` | Object | control object for `useForm` hook |
24 | | `control` | Object | control object for `useController`, `useFormState`, `useWatch` |
25 | | `subscribe` | Function | function to [subscribe](/docs/useform/subscribe) for form state update without render |
26 | | `...returns` | Functions | `useForm` return methods |
27 |
28 |
29 | - This function is published at **v7.55.0** - This function is completely
30 | optional, you can consider to use this instead of `useFormContext` API. - You
31 | may find it useful if you would like to subscribe formsState by skipping react
32 | re-render.
33 |
34 |
35 |
36 | - You should either use this API or context API
37 | ```tsx
38 | const props = createFormControl()
39 |
40 | // ❌ You don't need provider
41 |
42 | // ✅ Direct use method from createFormControl
43 | ```
44 |
45 |
46 | **Examples:**
47 |
48 | ---
49 |
50 |
51 |
52 | ```javascript
53 | const { formControl, control, handleSubmit, register } = createFormControl({
54 | mode: 'onChange',
55 | defaultValues: {
56 | firstName: 'Bill'
57 | }
58 | }})
59 |
60 | function App() {
61 | useForm({
62 | formControl,
63 | })
64 |
65 | return (
66 | console.log)}>
67 |
68 |
69 |
70 |
71 | );
72 | }
73 |
74 | function FormState() {
75 | useFormState({
76 | control // no longer need context api
77 | })
78 | }
79 |
80 | function Controller() {
81 | useFormState({
82 | control // no longer need context api
83 | })
84 | }
85 | ```
86 |
87 | ```javascript
88 | const { formControl, register } = createFormControl(props)
89 |
90 | formControl.subscribe({
91 | formState: {
92 | isDirty: true,
93 | values: true,
94 | },
95 | callback: (formState) => {
96 | if (formState.isDirty) {
97 | // do something here
98 | }
99 |
100 | if (formState.values.test.length > 3) {
101 | // do something here
102 | }
103 | },
104 | })
105 |
106 | function App() {
107 | const { register } = useForm({
108 | formControl,
109 | })
110 |
111 | return (
112 |
113 |
114 |
115 | )
116 | }
117 | ```
118 |
119 |
120 |
--------------------------------------------------------------------------------
/src/content/docs/formprovider.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: FormProvider
3 | description: A component to provide React Context
4 | sidebar: apiLinks
5 | ---
6 |
7 | This component will host context object and allow consuming component to subscribe to context and use [useForm](/docs/useform) props and methods.
8 |
9 | ### Props
10 |
11 | ---
12 |
13 | This following table applied to `FormProvider`, `useFormContext` accepts no argument.
14 |
15 | | Name | Type | Description |
16 | | ---------- | --------------------------- | ---------------------------------------------- |
17 | | `...props` | Object | `FormProvider` requires all `useForm` methods. |
18 |
19 |
20 |
21 | - Avoid using nested FormProvider
22 |
23 |
24 |
25 | **Examples:**
26 |
27 | ---
28 |
29 | ```javascript copy sandbox="https://codesandbox.io/s/react-hook-form-v7-form-context-ytudi"
30 | import { useForm, FormProvider, useFormContext } from "react-hook-form"
31 |
32 | export default function App() {
33 | const methods = useForm()
34 |
35 | const onSubmit = (data) => console.log(data)
36 | const { register, reset } = methods
37 |
38 | useEffect(() => {
39 | reset({
40 | name: "data",
41 | })
42 | }, [reset]) // ❌ never put `methods` as the deps
43 |
44 | return (
45 |
46 | // pass all methods into the context
47 |
48 |
49 |
50 |
51 |
52 |
53 | )
54 | }
55 |
56 | function NestedInput() {
57 | const { register } = useFormContext() // retrieve all hook methods
58 |
59 | return
60 | }
61 | ```
62 |
--------------------------------------------------------------------------------
/src/content/docs/useform/clearerrors.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: clearErrors
3 | description: Clear form errors
4 | sidebar: apiLinks
5 | ---
6 |
7 | ## \> `clearErrors:` (name?: string | string[]) => void
8 |
9 | This function can manually clear errors in the form.
10 |
11 | ### Props
12 |
13 | ---
14 |
15 | | Type | Description | Example |
16 | | ------------------------------ | ----------------------- | --------------------------------------- |
17 | | undefined | Remove all errors. | `clearErrors()` |
18 | | string | Remove single error. | `clearErrors("yourDetails.firstName")` |
19 | | string[] | Remove multiple errors. | `clearErrors(["yourDetails.lastName"])` |
20 |
21 | - `undefined`: reset all errors
22 | - `string`: reset the error on a single field or by key name.
23 |
24 | ```javascript
25 | register("test.firstName", { required: true })
26 | register("test.lastName", { required: true })
27 | clearErrors("test") // will clear both errors from test.firstName and test.lastName
28 | clearErrors("test.firstName") // for clear single input error
29 | ```
30 |
31 | - `string[]`: reset errors on the given fields
32 |
33 |
34 |
35 | - This will not affect the validation rules attached to each inputs.
36 | - This method doesn't affect validation rules or `isValid` formState.
37 |
38 |
39 |
40 | **Examples**
41 |
42 | ---
43 |
44 |
45 |
46 | ```typescript sandbox="https://codesandbox.io/s/react-hook-form-v7-ts-clearerrors-w3ymx"
47 | import * as React from "react"
48 | import { useForm } from "react-hook-form"
49 |
50 | type FormInputs = {
51 | firstName: string
52 | lastName: string
53 | username: string
54 | }
55 |
56 | const App = () => {
57 | const {
58 | register,
59 | formState: { errors },
60 | handleSubmit,
61 | clearErrors,
62 | } = useForm()
63 |
64 | const onSubmit = (data: FormInputs) => {
65 | console.log(data)
66 | }
67 |
68 | return (
69 |
70 |
71 |
72 |
73 | clearErrors("firstName")}>
74 | Clear First Name Errors
75 |
76 | clearErrors(["firstName", "lastName"])}
79 | >
80 | Clear First and Last Name Errors
81 |
82 | clearErrors()}>
83 | Clear All Errors
84 |
85 |
86 |
87 | )
88 | }
89 | ```
90 |
91 | ```javascript sandbox="https://codesandbox.io/s/react-hook-form-v7-clearerrors-w5tl6"
92 | import * as React from "react"
93 | import { useForm } from "react-hook-form"
94 |
95 | const App = () => {
96 | const {
97 | register,
98 | formState: { errors },
99 | handleSubmit,
100 | clearErrors,
101 | } = useForm()
102 | const onSubmit = (data) => console.log(data)
103 |
104 | return (
105 |
106 |
107 |
108 |
109 | clearErrors("firstName")}>
110 | Clear First Name Errors
111 |
112 | clearErrors(["firstName", "lastName"])}
115 | >
116 | Clear First and Last Name Errors
117 |
118 | clearErrors()}>
119 | Clear All Errors
120 |
121 |
122 |
123 | )
124 | }
125 | ```
126 |
127 |
128 |
--------------------------------------------------------------------------------
/src/content/docs/useform/control.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: control
3 | description: Take control of the form
4 | sidebar: apiLinks
5 | ---
6 |
7 | ## \> `control:` Object
8 |
9 | This object contains methods for registering components into React Hook Form.
10 |
11 |
12 |
13 | **Important:** do not access any of the properties inside this object directly. It's for internal usage only.
14 |
15 |
16 |
17 | **Examples:**
18 |
19 | ---
20 |
21 |
22 |
23 | ```typescript copy sandbox="https://codesandbox.io/s/react-hook-form-v6-controller-ts-jwyzw"
24 | import { useForm, Controller } from "react-hook-form"
25 | import { TextField } from "@material-ui/core"
26 |
27 | type FormInputs = {
28 | firstName: string
29 | }
30 |
31 | function App() {
32 | const { control, handleSubmit } = useForm()
33 | const onSubmit = (data: FormInputs) => console.log(data)
34 |
35 | return (
36 |
37 |
43 |
44 |
45 |
46 | )
47 | }
48 | ```
49 |
50 | ```javascript copy sandbox="https://codesandbox.io/s/react-hook-form-v7-controller-5h1q5"
51 | import { useForm, Controller } from "react-hook-form"
52 |
53 | function App() {
54 | const { control } = useForm()
55 |
56 | return (
57 | }
59 | name="firstName"
60 | control={control}
61 | defaultValue=""
62 | />
63 | )
64 | }
65 | ```
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/content/docs/useform/handlesubmit.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: handleSubmit
3 | description: Ready to send to the server
4 | sidebar: apiLinks
5 | ---
6 |
7 | ## \> `handleSubmit:` `((data: Object, e?: Event) => Promise, (errors: Object, e?: Event) => Promise) => Promise`
8 |
9 | This function will receive the form data if form validation is successful.
10 |
11 | ### Props
12 |
13 | ---
14 |
15 | | Name | Type | Description |
16 | | ------------------ | ------------------------------------------------------------------- | ---------------------- |
17 | | SubmitHandler | `(data: Object, e?: Event) => Promise` | A successful callback. |
18 | | SubmitErrorHandler | `(errors: Object, e?: Event) => Promise` | An error callback. |
19 |
20 |
21 |
22 | - You can easily submit form asynchronously with handleSubmit.
23 |
24 | ```javascript copy
25 | handleSubmit(onSubmit)()
26 |
27 | // You can pass an async function for asynchronous validation.
28 | handleSubmit(async (data) => await fetchAPI(data))
29 | ```
30 |
31 | - `disabled` inputs will appear as `undefined` values in form values. If you want to prevent users from updating an input and wish to retain the form value, you can use `readOnly` or disable the entire <fieldset />. Here is an [example](https://codesandbox.io/s/react-hook-form-disabled-inputs-oihxx).
32 | - `handleSubmit` function will not swallow errors that occurred inside your onSubmit callback, so we recommend you to try and catch inside async request and handle those errors gracefully for your customers.
33 |
34 | ```javascript
35 | const onSubmit = async () => {
36 | // async request which may result error
37 | try {
38 | // await fetch()
39 | } catch (e) {
40 | // handle your error
41 | }
42 | }
43 |
44 | return
45 | ```
46 |
47 |
48 |
49 | **Examples:**
50 |
51 | ---
52 |
53 | **Sync**
54 |
55 |
56 |
57 | ```typescript copy sandbox="https://codesandbox.io/s/react-hook-form-handlesubmit-ts-v7-lcrtu"
58 | import { useForm, SubmitHandler, SubmitErrorHandler } from "react-hook-form"
59 |
60 | type FormValues = {
61 | firstName: string
62 | lastName: string
63 | email: string
64 | }
65 |
66 | export default function App() {
67 | const { register, handleSubmit } = useForm()
68 | const onSubmit: SubmitHandler = (data) => console.log(data)
69 | const onError: SubmitErrorHandler = (errors) => console.log(errors)
70 |
71 | return (
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | )
80 | }
81 | ```
82 |
83 | ```javascript copy sandbox="https://codesandbox.io/s/react-hook-form-handlesubmit-v7-uqmiy"
84 | import { useForm } from "react-hook-form"
85 |
86 | export default function App() {
87 | const { register, handleSubmit } = useForm()
88 | const onSubmit = (data, e) => console.log(data, e)
89 | const onError = (errors, e) => console.log(errors, e)
90 |
91 | return (
92 |
93 |
94 |
95 | Submit
96 |
97 | )
98 | }
99 | ```
100 |
101 |
102 |
103 | **Async**
104 |
105 | ```javascript copy sandbox="https://codesandbox.io/s/react-hook-form-async-submit-validation-kpx0o"
106 | import { useForm } from "react-hook-form";
107 |
108 | const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
109 |
110 | function App() {
111 | const { register, handleSubmit, formState: { errors } } = useForm();
112 | const onSubmit = async data => {
113 | await sleep(2000);
114 | if (data.username === "bill") {
115 | alert(JSON.stringify(data));
116 | } else {
117 | alert("There is an error");
118 | }
119 | };
120 |
121 | return (
122 |
123 | User Name
124 |
125 |
126 |
127 |
128 | );
129 | }
130 | ```
131 |
132 | ### Video
133 |
134 | ---
135 |
136 | The following video tutorial explains the `handleSubmit` API in detail.
137 |
138 |
139 |
--------------------------------------------------------------------------------
/src/content/docs/useform/setfocus.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: setFocus
3 | description: Manually set an input focus
4 | sidebar: apiLinks
5 | ---
6 |
7 | ## `setFocus:` (name: string, options: SetFocusOptions) => void
8 |
9 | This method will allow users to programmatically focus on input. Make sure input's ref is registered into the hook form.
10 |
11 | ### Props
12 |
13 | ---
14 |
15 | | Name | | Type | Description |
16 | | --------- | -------------- | ---------------------------- | --------------------------------------------- |
17 | | `name` | | string | A input field name to focus |
18 | | `options` | `shouldSelect` | boolean | Whether to select the input content on focus. |
19 |
20 |
21 |
22 | - This API will invoke focus method from the ref, so it's important to provide `ref` during `register`.
23 | - Avoid calling `setFocus` right after `reset` as all input references will be removed by `reset` API.
24 |
25 |
26 |
27 | ### Examples
28 |
29 | ---
30 |
31 |
32 |
33 | ```typescript copy sandbox="https://codesandbox.io/s/setfocus-rolus"
34 | import * as React from "react"
35 | import { useForm } from "react-hook-form"
36 |
37 | type FormValues = {
38 | firstName: string
39 | }
40 |
41 | export default function App() {
42 | const { register, handleSubmit, setFocus } = useForm()
43 | const onSubmit = (data: FormValues) => console.log(data)
44 | renderCount++
45 |
46 | React.useEffect(() => {
47 | setFocus("firstName")
48 | }, [setFocus])
49 |
50 | return (
51 |
52 |
53 |
54 |
55 | )
56 | }
57 | ```
58 |
59 | ```javascript copy sandbox="https://codesandbox.io/s/setfocus-rolus"
60 | import * as React from "react"
61 | import { useForm } from "react-hook-form"
62 |
63 | export default function App() {
64 | const { register, handleSubmit, setFocus } = useForm()
65 | const onSubmit = (data) => console.log(data)
66 | renderCount++
67 |
68 | React.useEffect(() => {
69 | setFocus("firstName")
70 | }, [setFocus])
71 |
72 | return (
73 |
74 |
75 |
76 |
77 | )
78 | }
79 | ```
80 |
81 |
82 |
--------------------------------------------------------------------------------
/src/content/docs/useformcontext.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: useFormContext
3 | description: React Context API for hook form
4 | sidebar: apiLinks
5 | ---
6 |
7 |
15 |
16 | ## \> `useFormContext:` Function
17 |
18 | This custom hook allows you to access the form context. `useFormContext` is intended to be used in deeply nested structures, where it would become inconvenient to pass the context as a prop.
19 |
20 | ### Return
21 |
22 | ---
23 |
24 | This hook will return all the useForm return methods and props.
25 |
26 | ```javascript
27 | const methods = useForm()
28 |
29 | // all the useForm return props
30 |
31 | const methods = useFormContext() // retrieve those props
32 | ```
33 |
34 |
35 | You need to wrap your form with the [`FormProvider`](/docs/formprovider)
36 | component for `useFormContext` to work properly.
37 |
38 |
39 | **Example:**
40 |
41 | ```javascript copy codesandbox="https://codesandbox.io/s/react-hook-form-v7-form-context-ytudi"
42 | import { useForm, FormProvider, useFormContext } from "react-hook-form"
43 |
44 | export default function App() {
45 | const methods = useForm()
46 | const onSubmit = (data) => console.log(data)
47 | const { register, reset } = methods
48 |
49 | useEffect(() => {
50 | reset({
51 | name: "data",
52 | })
53 | }, [reset]) // ❌ never put `methods` as the deps
54 |
55 | return (
56 |
57 | // pass all methods into the context
58 |
59 |
60 |
61 |
62 |
63 |
64 | )
65 | }
66 |
67 | function NestedInput() {
68 | const { register } = useFormContext() // retrieve all hook methods
69 | return
70 | }
71 | ```
72 |
--------------------------------------------------------------------------------
/src/data/builder.tsx:
--------------------------------------------------------------------------------
1 | export default {
2 | title: "Form Builder",
3 | description: "GUI for building forms with validation",
4 | builder: {
5 | title: "Form Builder",
6 | description: "Build your form with code and example.",
7 | },
8 | layout: {
9 | title: "Layout",
10 | message: "You can start adding fields with Input Creator.",
11 | },
12 | inputCreator: {
13 | title: "Input Creator",
14 | description: `This form allows you to create and update inputs. The Generate Form button will create a new form with the updates.`,
15 | message: "You can start adding fields with Input Creator.",
16 | options: "Options",
17 | validation: "Show validation",
18 | generate: "Generate Form",
19 | },
20 | code: {
21 | title: "Code",
22 | description: `As you are making changes over the form, the code section will be updated and you can copy the code as well.`,
23 | },
24 | }
25 |
--------------------------------------------------------------------------------
/src/data/devtools.tsx:
--------------------------------------------------------------------------------
1 | import typographyStyles from "../styles/typography.module.css"
2 |
3 | export default {
4 | title: "DevTools",
5 | description: "React Hook Form DevTools to help debug forms with validation.",
6 | install: "Installation",
7 | step1: (
8 | <>
9 | Step 1: install{" "}
10 | @hookform/devtools
as a dev dependency package.
11 | >
12 | ),
13 | step2: (
14 | <>
15 | Step 2: Integrate with your React
16 | App is as simple as import a Component into your App/Form render and pass{" "}
17 | control
prop into it.
18 | >
19 | ),
20 | demoDescription:
21 | "You can interact with the following demo to see DevTool in action.",
22 | function: "Functionality",
23 | functionDetail: (
24 | <>
25 |
26 | Read inputs and entire form state.
27 |
28 |
29 |
30 | Note: Because RHF is based on
31 | uncontrolled inputs, Update button will refresh the
32 | DevTool to read the latest input value and form state.
33 |
34 |
35 |
36 | Visual feedback when input or form is valid or invalid.
37 |
38 |
39 | Search registered input and also custom registered components.
40 |
41 |
42 |
43 | Note: you can easily locate
44 | an input by clicking the Native button. This will be
45 | useful when you are working on a large form.
46 |
47 |
48 | >
49 | ),
50 | features: [
51 | {
52 | title: "React Base",
53 | description:
54 | "DevTool itself is built by React, so you easily integrate with your React application.",
55 | },
56 | {
57 | description:
58 | "React Hook Form focuses on delivering a great experience in terms of building forms with React. This tool will help debug your forms.",
59 | },
60 | {
61 | title: "Open-Source",
62 | description:
63 | "DevTools is an open-source project, so we can all improve the experience together.",
64 | },
65 | ],
66 | }
67 |
--------------------------------------------------------------------------------
/src/data/generic.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link"
2 |
3 | export default {
4 | copy: "Copy",
5 | codeCopied: "Copied",
6 | required: "Required",
7 | learnMore: {
8 | title: "Want to learn more?",
9 | description:
10 | "Check out the React Hook Form documentation and learn all about the API.",
11 | buttonText: "Checkout API",
12 | },
13 | advanceUsage: {
14 | title: "Advanced Usage",
15 | description: `Learn how to build complex and accessible forms`,
16 | buttonText: `Learn Advanced Usage`,
17 | },
18 | needYourSupport: {
19 | title: "Thank you for your support",
20 | description: `If you find React Hook Form to be useful in your project, please consider to star and support it.`,
21 | buttonText: `Star us on GitHub`,
22 | },
23 | codeExample: "Code Examples",
24 | menu: "Menu",
25 | note: "Note",
26 | select: "Select",
27 | name: "Name",
28 | type: "Type",
29 | default: "Default",
30 | description: "Description",
31 | features: "Features",
32 | delete: "Delete",
33 | example: "Example",
34 | edit: "Edit",
35 | cancelEdit: "Cancel Edit",
36 | reset: "Reset",
37 | deleteAll: "Delete All",
38 | create: "Create",
39 | update: "Update",
40 | copied: "Copy code into your clipboard.",
41 | return: "Return",
42 | blog: "Articles/Blog",
43 | video: "Videos",
44 | newsletter: "Newsletter",
45 | binding: "3rd Party Bindings",
46 | liveDemo: "Live Demo",
47 | control: (
48 | <>
49 |
50 | control
51 | {" "}
52 | object provided by useForm
. It's optional if you are using
53 | FormProvider.
54 | >
55 | ),
56 | }
57 |
--------------------------------------------------------------------------------
/src/data/nav.tsx:
--------------------------------------------------------------------------------
1 | const Nav = {
2 | home: "Home",
3 | getStarted: "Get Started",
4 | advanced: "Advanced",
5 | tools: {
6 | nav: "Tools",
7 | devTools: "DevTools",
8 | formBuilder: "Form Builder",
9 | },
10 | builder: (
11 | <>
12 | Form Builder
13 | >
14 | ),
15 | faqs: "FAQs",
16 | releases: "Releases",
17 | resources: "Resources",
18 | }
19 |
20 | export default Nav
21 |
--------------------------------------------------------------------------------
/src/pages/404.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | padding: 2rem;
3 | }
4 |
5 | .root {
6 | text-align: center;
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/404.tsx:
--------------------------------------------------------------------------------
1 | import Layout from "../components/layout"
2 | import Seo from "../components/seo"
3 | import styles from "./404.module.css"
4 |
5 | const NotFoundPage = () => (
6 |
7 |
8 |
9 |
10 | NOT FOUND
11 | You just found a route that doesn't exist... the sadness.
12 |
13 |
14 |
15 | )
16 |
17 | export default NotFoundPage
18 |
--------------------------------------------------------------------------------
/src/pages/[...slug].tsx:
--------------------------------------------------------------------------------
1 | import { allDocs } from "contentlayer/generated"
2 | import { GetStaticPaths, InferGetStaticPropsType } from "next"
3 | import { useMDXComponent } from "next-contentlayer/hooks"
4 | import Layout from "@/components/layout"
5 | import { MDXComponents } from "@/components/mdx/mdx"
6 | import Seo from "@/components/seo"
7 | import Footer from "@/components/Footer"
8 | import StarRepo from "@/components/StarRepo"
9 | import containerStyles from "@/styles/container.module.css"
10 | import typographyStyles from "@/styles/typography.module.css"
11 | import Menu from "@/components/Menu/Menu"
12 | import { Pages } from "@/types/types"
13 |
14 | export const getStaticPaths: GetStaticPaths = () => {
15 | // Get a list of valid doc paths.
16 | const paths = allDocs.map((doc) => ({
17 | params: { slug: doc.segment },
18 | }))
19 |
20 | return { paths, fallback: false }
21 | }
22 |
23 | export const getStaticProps = ({
24 | params,
25 | }: {
26 | params?: { slug?: string[] }
27 | }) => {
28 | // Find the doc for the current page.
29 | const doc = allDocs.find(
30 | (doc) => doc._raw.flattenedPath === params?.slug?.join("/")
31 | )
32 |
33 | // Return notFound if the doc does not exist.
34 | if (!doc) {
35 | return { notFound: true }
36 | }
37 |
38 | // Return the doc as page props.
39 | return { props: { doc } }
40 | }
41 |
42 | export default function Page({
43 | doc,
44 | }: InferGetStaticPropsType) {
45 | // Parse the MDX file via the useMDXComponent hook.
46 | const MDXContent = useMDXComponent(doc.body.code)
47 |
48 | return (
49 |
50 |
51 |
52 |
53 |
54 | {doc.title}
55 |
56 |
{doc.description}
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | )
72 | }
73 |
--------------------------------------------------------------------------------
/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { StateMachineProvider, createStore } from "little-state-machine"
2 | import { type AppProps } from "next/app"
3 | import Head from "next/head"
4 | import formData from "../state/formData"
5 | import { ThemeProvider } from "next-themes"
6 | import { useEffect } from "react"
7 | import "../components/layout.css"
8 |
9 | createStore(
10 | {
11 | formData,
12 | },
13 | {
14 | storageType:
15 | typeof window !== "undefined" ? window.localStorage : undefined,
16 | }
17 | )
18 |
19 | function App({ Component, pageProps }: AppProps) {
20 | useEffect(() => {
21 | try {
22 | if ("serviceWorker" in window.navigator) {
23 | navigator.serviceWorker.getRegistrations().then((registrations) => {
24 | if (Array.isArray(registrations)) {
25 | for (const registration of registrations) {
26 | ;(registration as ServiceWorkerRegistration).unregister()
27 | }
28 | }
29 | })
30 | }
31 | } catch {}
32 | }, [])
33 |
34 | return (
35 | <>
36 |
37 | React Hook Form - Simple React forms validation
38 |
42 |
43 |
48 |
49 |
50 |
51 |
52 | >
53 | )
54 | }
55 |
56 | export default App
57 |
--------------------------------------------------------------------------------
/src/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from "next/document"
2 | import Script from "next/script"
3 |
4 | export default function Document() {
5 | return (
6 |
7 |
8 |
12 |
16 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/src/pages/dev-tools.tsx:
--------------------------------------------------------------------------------
1 | import Layout from "../components/layout"
2 | import Seo from "../components/seo"
3 | import DevTools from "../components/DevTools"
4 | import api from "../data/api"
5 |
6 | const Api = () => (
7 |
8 |
9 |
10 |
11 | )
12 |
13 | export default Api
14 |
--------------------------------------------------------------------------------
/src/pages/docs.tsx:
--------------------------------------------------------------------------------
1 | import Layout from "../components/layout"
2 | import Seo from "../components/seo"
3 | import api from "../data/api"
4 | import ApiGallery from "../components/ApiGallery"
5 |
6 | const Api = () => (
7 |
8 |
9 |
10 |
11 | )
12 |
13 | export default Api
14 |
--------------------------------------------------------------------------------
/src/pages/docs/usecontroller.tsx:
--------------------------------------------------------------------------------
1 | import Seo from "../../components/seo"
2 | import Layout from "../../components/layout"
3 | import UseControllerContent from "../../components/UseController"
4 |
5 | export default function Usecontroller() {
6 | return (
7 |
8 |
9 |
10 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/src/pages/docs/usefieldarray.tsx:
--------------------------------------------------------------------------------
1 | import Seo from "../../components/seo"
2 | import Layout from "../../components/layout"
3 | import UseFieldArrayContent from "../../components/UseFieldArray"
4 |
5 | export default function UseFieldArray() {
6 | return (
7 |
8 |
9 |
10 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/src/pages/form-builder.tsx:
--------------------------------------------------------------------------------
1 | import Layout from "../components/layout"
2 | import Seo from "../components/seo"
3 | import BuilderPage from "../components/BuilderPage"
4 | import builder from "../data/builder"
5 |
6 | const Api = () => {
7 | return (
8 |
9 |
10 |
11 |
12 | )
13 | }
14 |
15 | export default Api
16 |
--------------------------------------------------------------------------------
/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Layout from "../components/layout"
2 | import Seo from "../components/seo"
3 | import Home from "../components/HomePage"
4 | import home from "../data/home"
5 |
6 | const IndexPage = () => (
7 |
8 |
9 |
10 |
11 | )
12 |
13 | export default IndexPage
14 |
--------------------------------------------------------------------------------
/src/pages/media.module.css:
--------------------------------------------------------------------------------
1 | .media {
2 | max-width: 800px;
3 | margin: 3rem auto;
4 | display: flex;
5 | flex-direction: column;
6 | grid-template-columns: 1fr 1fr;
7 | gap: 2rem;
8 | }
9 |
10 | .media div {
11 | display: flex;
12 | flex-direction: column;
13 | align-items: center;
14 | }
15 |
16 | .media p {
17 | font-size: 12px;
18 | }
19 |
20 | .media img {
21 | width: 100%;
22 | border-radius: 10px;
23 | }
24 |
25 | img.logo {
26 | width: 200px;
27 | height: 200px;
28 | }
29 |
30 | @media (min-width: 768px) {
31 | .media {
32 | margin: 3rem auto;
33 | display: grid;
34 | grid-template-columns: 1fr 1fr;
35 | gap: 2rem;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/pages/media.tsx:
--------------------------------------------------------------------------------
1 | import Seo from "../components/seo"
2 | import Layout from "../components/layout"
3 | import containerStyle from "../styles/container.module.css"
4 | import styles from "../components/ResourcePage.module.css"
5 | import typographyStyles from "../styles/typography.module.css"
6 | import mediaStyles from "./media.module.css"
7 | import Footer from "../components/Footer"
8 |
9 | const Media = () => {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 | Media
17 |
18 |
19 | The following are assets that represent React Hook Form.
20 |
21 |
22 |
23 |
24 |
28 |
PNG
29 |
30 |
31 |
35 |
SVG
36 |
37 |
38 |
39 |
44 |
PNG
45 |
46 |
47 |
52 |
SVG
53 |
54 |
55 |
56 |
60 |
PNG
61 |
62 |
63 |
67 |
SVG
68 |
69 |
70 |
71 |
76 |
PNG
77 |
78 |
79 |
84 |
SVG
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | )
93 | }
94 |
95 | export default Media
96 |
--------------------------------------------------------------------------------
/src/pages/migrate-v6-to-v7.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react"
2 |
3 | export default function MigrateV6ToV7() {
4 | useEffect(() => {
5 | window.location.href = "https://legacy.react-hook-form.com/migrate-v6-to-v7"
6 | }, [])
7 |
8 | return null
9 | }
10 |
--------------------------------------------------------------------------------
/src/pages/resources/3rd-party-bindings.tsx:
--------------------------------------------------------------------------------
1 | import Layout from "../../components/layout"
2 | import Seo from "../../components/seo"
3 | import ResourcePageBindings from "../../components/ResourcePageBindings"
4 |
5 | const ResourcesBindings = () => {
6 | return (
7 |
8 |
9 |
10 |
11 | )
12 | }
13 |
14 | export default ResourcesBindings
15 |
--------------------------------------------------------------------------------
/src/pages/resources/articles.tsx:
--------------------------------------------------------------------------------
1 | import Layout from "../../components/layout"
2 | import Seo from "../../components/seo"
3 | import ResourcePageArticles from "../../components/ResourcePageArticles"
4 |
5 | const ResourcesArticles = () => {
6 | return (
7 |
8 |
9 |
10 |
11 | )
12 | }
13 |
14 | export default ResourcesArticles
15 |
--------------------------------------------------------------------------------
/src/pages/resources/newsletters.tsx:
--------------------------------------------------------------------------------
1 | import Layout from "../../components/layout"
2 | import Seo from "../../components/seo"
3 | import ResourcePageNewsletter from "../../components/ResourcePageNewsletter"
4 |
5 | const ResourcesNewsletter = () => {
6 | return (
7 |
8 |
9 |
10 |
11 | )
12 | }
13 |
14 | export default ResourcesNewsletter
15 |
--------------------------------------------------------------------------------
/src/pages/resources/videos.tsx:
--------------------------------------------------------------------------------
1 | import Layout from "../../components/layout"
2 | import Seo from "../../components/seo"
3 | import ResourcePageVideos from "../../components/ResourcePageVideos"
4 |
5 | const ResourcesVideos = () => {
6 | return (
7 |
8 |
9 |
10 |
11 | )
12 | }
13 |
14 | export default ResourcesVideos
15 |
--------------------------------------------------------------------------------
/src/state/formData.ts:
--------------------------------------------------------------------------------
1 | import type { GlobalState } from "little-state-machine"
2 |
3 | const formData: GlobalState["formData"] = [
4 | {
5 | name: "First name",
6 | type: "text",
7 | required: true,
8 | max: "",
9 | min: "",
10 | maxLength: "80",
11 | minLength: "",
12 | pattern: "",
13 | },
14 | {
15 | name: "Last name",
16 | type: "text",
17 | required: true,
18 | max: "",
19 | min: "",
20 | maxLength: "100",
21 | minLength: "",
22 | pattern: "",
23 | },
24 | {
25 | name: "Email",
26 | type: "text",
27 | required: true,
28 | max: "",
29 | min: "",
30 | maxLength: "",
31 | minLength: "",
32 | pattern: "^\\S+@\\S+$",
33 | },
34 | {
35 | name: "Mobile number",
36 | type: "tel",
37 | required: true,
38 | max: "",
39 | min: "",
40 | maxLength: "12",
41 | minLength: "6",
42 | pattern: "",
43 | },
44 | {
45 | name: "Title",
46 | type: "select",
47 | required: true,
48 | max: "",
49 | min: "",
50 | maxLength: "",
51 | minLength: "",
52 | pattern: "",
53 | options: "Mr;Mrs;Miss;Dr",
54 | },
55 | {
56 | name: "Developer",
57 | type: "radio",
58 | required: true,
59 | max: "",
60 | min: "",
61 | maxLength: "",
62 | minLength: "",
63 | pattern: "",
64 | options: "Yes;No",
65 | },
66 | ]
67 |
68 | export default formData
69 |
--------------------------------------------------------------------------------
/src/styles/breakpoints.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | fromXsmallScreen: "(min-width: 320px)",
3 | fromSmallScreen: "(min-width: 480px)",
4 | fromMediumScreen: "(min-width: 768px)",
5 | fromLargeScreen: "(min-width: 1024px)",
6 | fromXlargeScreen: "(min-width: 1280px)",
7 | fromXxlargeScreen: "(min-width: 1600px)",
8 | }
9 |
10 | export const LARGE_SCREEN = 1600
11 |
--------------------------------------------------------------------------------
/src/styles/button.module.css:
--------------------------------------------------------------------------------
1 | .links,
2 | .codeAsLink {
3 | color: var(--color-link);
4 | }
5 |
6 | .codeAsLink {
7 | cursor: pointer;
8 | text-decoration: underline;
9 | appearance: none;
10 | background: none;
11 | border: none;
12 | padding: 0;
13 | color: var(--color-link) !important;
14 | }
15 |
16 | .buttonsGroup {
17 | display: grid;
18 | grid-template-columns: repeat(2, 1fr);
19 | grid-column-gap: 20px;
20 | margin: 0 auto;
21 | }
22 |
23 | .button,
24 | .primaryButton,
25 | .pinkButton,
26 | .darkButton {
27 | border-radius: 4px;
28 | transition: 0.3s all;
29 | cursor: pointer;
30 | color: white;
31 | font-size: 16px;
32 | font-weight: 400;
33 | margin: 20px 0;
34 | -webkit-appearance: none;
35 | line-height: 1;
36 | display: inline-block;
37 | padding: 16px 10px;
38 | }
39 |
40 | .primaryButton {
41 | box-sizing: border-box;
42 | background: var(--color-primary);
43 | border: 1px solid var(--color-light-blue);
44 | }
45 |
46 | a.primaryButton {
47 | text-decoration: none;
48 | }
49 |
50 | a.primaryButton:hover {
51 | color: white;
52 | }
53 |
54 | .pinkButton,
55 | .darkButton {
56 | background: var(--color-light-pink);
57 | letter-spacing: 0.2rem;
58 | text-transform: uppercase;
59 | border: 1px solid var(--color-light-pink);
60 | width: 100%;
61 | }
62 |
63 | .pinkButton:hover {
64 | background: var(--color-secondary);
65 | }
66 |
67 | .darkButton {
68 | background: black;
69 | color: white;
70 | border: 1px solid var(--color-light-pink);
71 | }
72 |
73 | .darkButton:hover {
74 | background: var(--color-primary);
75 | border: 1px solid var(--color-secondary);
76 | }
77 |
78 | .darkButton:active {
79 | background: black;
80 | }
81 |
82 | @keyframes moving {
83 | from {
84 | transform: translateX(0px);
85 | }
86 |
87 | to {
88 | transform: translateX(5px);
89 | }
90 | }
91 |
92 | .primaryButton:hover > span {
93 | display: inline-block;
94 | animation: moving 0.4s linear infinite;
95 | animation-direction: alternate;
96 | }
97 |
98 | .primaryButton:active {
99 | background: black;
100 | }
101 |
102 | .primaryButton:hover {
103 | border: 1px solid var(--color-secondary);
104 | box-shadow: 0 0 5px #000;
105 | }
106 |
107 | @media (min-width: 768px) {
108 | .primaryButton {
109 | font-size: 18px;
110 | font-weight: 400;
111 | margin: 40px 0;
112 | padding: 15px 30px;
113 | }
114 |
115 | .buttonsGroup {
116 | grid-column-gap: 30px;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/styles/colors.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | primary: "#0e101c",
3 | secondary: "#bf1650",
4 | lightBlue: "#516391",
5 | blue: "#1e2a4a",
6 | lightPink: "#ec5990",
7 | errorPink: "#fbecf2",
8 | buttonBlue: "#191d3a",
9 | link: "#ff7aa8",
10 | black: "#000",
11 | }
12 |
--------------------------------------------------------------------------------
/src/styles/container.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | padding-top: 45px;
3 | }
4 |
5 | .subContainer {
6 | margin: 0 auto;
7 | max-width: 768px;
8 | }
9 |
10 | .centerContent {
11 | margin: 0 auto;
12 | text-align: center;
13 | max-width: 1024px;
14 | }
15 |
16 | .centerContent svg {
17 | display: none;
18 | }
19 |
20 | .wrapper {
21 | max-width: 1235px;
22 | margin: 0 auto;
23 | overflow: hidden;
24 | position: relative;
25 | padding: 0 15px 50px 20px;
26 | }
27 |
28 | .centerContent p {
29 | max-width: 730px;
30 | margin: 0 auto 0;
31 | }
32 |
33 | .centerContent h3 {
34 | margin: 0;
35 | }
36 |
37 | @media (min-width: 768px) {
38 | .container {
39 | padding-top: 0;
40 | }
41 |
42 | .centerContent svg {
43 | width: 85px;
44 | display: block;
45 | text-align: center;
46 | margin: 100px auto -30px;
47 | }
48 |
49 | .wrapper {
50 | display: grid;
51 | grid-template-columns: 250px minmax(0, 1fr);
52 | }
53 | }
54 |
55 | @media (min-width: 1024px) {
56 | .wrapper {
57 | display: grid;
58 | grid-template-columns: 300px minmax(0, 1fr);
59 | }
60 |
61 | .centerContent svg {
62 | margin: 100px auto -50px;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/styles/table.module.css:
--------------------------------------------------------------------------------
1 | .table {
2 | border-collapse: collapse;
3 | width: 100%;
4 | }
5 |
6 | .table td {
7 | padding: 6px 15px 6px 0;
8 | line-height: 1.4;
9 | font-size: 0.875rem;
10 | }
11 |
12 | .table td > h5:first-child,
13 | .table td > p:first-child,
14 | .table td > ul:first-child,
15 | .table td > ul li:first-child > p {
16 | margin-top: 0 !important;
17 | }
18 |
19 | .table td > ul li {
20 | margin: 0.5rem 0;
21 | }
22 |
23 | .table td > ul li:first-child {
24 | margin-top: 0;
25 | }
26 |
27 | .table td > ul li:last-child {
28 | margin-bottom: 0;
29 | }
30 |
31 | .table td:last-child {
32 | padding-right: 0;
33 | }
34 |
35 | .tableWrapper {
36 | -webkit-overflow-scrolling: touch;
37 | overflow-y: hidden;
38 | overflow-x: auto;
39 | }
40 |
41 | @media (min-width: 768px) {
42 | .mobileTypeText {
43 | margin-top: 0px;
44 | display: inline;
45 | }
46 |
47 | .tableWrapper::-webkit-scrollbar {
48 | height: 8px;
49 | }
50 |
51 | .tableWrapper::-webkit-scrollbar-track {
52 | background: var(--color-button-blue);
53 | border-radius: 10px;
54 | }
55 |
56 | .tableWrapper::-webkit-scrollbar-thumb {
57 | background: var(--color-medium-blue);
58 | border-radius: 10px;
59 | }
60 |
61 | .tableWrapper::-webkit-scrollbar-thumb:hover {
62 | background: var(--color-light-pink);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/styles/typography.module.css:
--------------------------------------------------------------------------------
1 | .heading,
2 | .headingWithTopMargin {
3 | text-align: center;
4 | margin-bottom: 10px;
5 | }
6 |
7 | .subHeading {
8 | font-size: 15px;
9 | padding: 0 20px;
10 | text-align: center;
11 | color: var(--color-light-pink);
12 | max-width: 330px;
13 | margin: 0 auto;
14 | }
15 |
16 | .title {
17 | text-align: center;
18 | font-weight: 400;
19 | font-size: 1.1rem;
20 | overflow: hidden;
21 | position: relative;
22 | line-height: 1;
23 | margin: 10px auto 20px;
24 | max-width: 600px;
25 | }
26 |
27 | .rulesTitle,
28 | .subTitle {
29 | border-bottom: 2px solid var(--color-light-blue);
30 | line-height: 1.8;
31 | font-size: 20px;
32 | font-weight: bold;
33 | margin-bottom: 0;
34 | }
35 |
36 | .rulesTitle:before,
37 | .subTitle:before {
38 | font-size: 15px;
39 | position: relative;
40 | margin-right: 8px;
41 | top: -2px;
42 | content: "ⓘ";
43 | }
44 |
45 | h3.rulesTitle,
46 | h3.subTitle {
47 | font-size: 14px;
48 | }
49 |
50 | h3.rulesTitle:before,
51 | h3.subTitle:before {
52 | top: 0;
53 | margin-right: 0.5rem;
54 | }
55 |
56 | .rulesTitle {
57 | line-height: 1;
58 | margin-top: 2rem;
59 | border: 1px solid var(--color-secondary);
60 | padding: 0.4rem 0.5rem 0.6rem 0.5rem;
61 | border-radius: 6px;
62 | }
63 |
64 | .title:before,
65 | .title:after {
66 | background-color: var(--color-light-blue);
67 | content: "";
68 | display: inline-block;
69 | height: 1px;
70 | position: relative;
71 | vertical-align: middle;
72 | width: 50%;
73 | }
74 |
75 | .title:before {
76 | right: 0.5em;
77 | margin-left: -50%;
78 | }
79 |
80 | .title:after {
81 | left: 0.5em;
82 | margin-right: -50%;
83 | }
84 |
85 | .codeHeading > h2:before {
86 | display: inline-block;
87 | content: "> ";
88 | }
89 |
90 | .h5 .videoLink,
91 | .codeHeading .videoLink {
92 | text-decoration: none;
93 | font-size: 14px;
94 | margin-bottom: -5px;
95 | margin-left: 15px;
96 | background: none;
97 | text-transform: uppercase;
98 | border: 1px solid var(--color-secondary);
99 | padding: 5px 10px;
100 | font-family:
101 | "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans",
102 | "Droid Sans", "Helvetica Neue", sans-serif;
103 | }
104 |
105 | .h1 {
106 | font-size: 24px;
107 | display: inline-block;
108 | margin: 40px auto;
109 | font-weight: 400;
110 | padding-bottom: 5px;
111 | border-bottom: 2px solid var(--color-secondary);
112 | }
113 |
114 | .h5 {
115 | font-size: 18px;
116 | text-align: left;
117 | font-weight: 500;
118 | padding-bottom: 15px;
119 | margin: 50px 0 10px;
120 | border-bottom: 1px solid var(--color-light-blue);
121 | }
122 |
123 | .h5 > code {
124 | font-size: 18px !important;
125 | }
126 |
127 | .typeText {
128 | font-weight: 400;
129 | font-size: 14px;
130 | font-family: monospace;
131 | font-variant-ligatures: none;
132 | color: var(--color-light-pink) !important;
133 | }
134 |
135 | pre.typeText {
136 | margin: 5px 0;
137 | line-height: 1.5;
138 | }
139 |
140 | .note {
141 | color: var(--color-light-pink);
142 | }
143 |
144 | .codeBlock {
145 | display: block;
146 | padding: 10px;
147 | }
148 |
149 | .error {
150 | font-size: 12px;
151 | color: var(--color-light-pink);
152 | }
153 |
154 | @media (min-width: 768px) {
155 | .h1 {
156 | font-size: 36px;
157 | font-weight: bold;
158 | }
159 |
160 | .heading,
161 | .headingWithTopMargin {
162 | font-size: 55px !important;
163 | margin: 80px auto 10px;
164 | }
165 |
166 | .subHeading {
167 | font-size: 18px;
168 | margin-top: 5px;
169 | margin-bottom: 20px;
170 | max-width: 575px;
171 | }
172 |
173 | .title {
174 | margin-top: 20px;
175 | font-size: 1.3rem;
176 | line-height: 1.5;
177 | }
178 |
179 | h2.title {
180 | margin-top: 40px;
181 | }
182 |
183 | .headingWithTopMargin {
184 | margin-top: 70px;
185 | }
186 |
187 | .questionTitle {
188 | padding-left: 10px;
189 | border-left: 5px solid var(--color-light-pink);
190 | line-height: 1;
191 | }
192 | }
193 |
194 | @media (min-width: 1024px) {
195 | .h1 {
196 | font-size: 50px;
197 | margin-top: 60px;
198 | line-height: 1.3;
199 | border-bottom: 3px solid var(--color-secondary);
200 | }
201 |
202 | .homeParagraph {
203 | font-size: 20px;
204 | line-height: 1.5;
205 | }
206 |
207 | .heading,
208 | .headingWithTopMargin {
209 | margin-top: 20px;
210 | }
211 |
212 | .headingWithTopMargin {
213 | margin-top: 70px;
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/src/types/global.d.ts:
--------------------------------------------------------------------------------
1 | interface Window {
2 | docsearch?: (options: {
3 | appId: string
4 | apiKey: string
5 | indexName: string
6 | inputSelector: string
7 | }) => vod
8 | }
9 |
10 | declare module "*.mdx" {
11 | export const meta: {
12 | title: string
13 | description: string
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/types/little-state-machine.d.ts:
--------------------------------------------------------------------------------
1 | import "little-state-machine"
2 |
3 | declare module "little-state-machine" {
4 | export interface FormDataItem {
5 | name: string
6 | type: string
7 | required: boolean
8 | max: string
9 | min: string
10 | maxLength: string
11 | minLength: string
12 | pattern: string
13 | /** Available when type is `select` or `radio` */
14 | options?: string
15 | }
16 |
17 | interface GlobalState {
18 | formData: FormDataItem[]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/types/types.ts:
--------------------------------------------------------------------------------
1 | export type Pages = {
2 | pathname: string
3 | name: string
4 | pages?: {
5 | pathname: string
6 | name: string
7 | }[]
8 | }[]
9 |
--------------------------------------------------------------------------------