setIsOpen(false)}
25 | onMovePrevRequest={() =>
26 | setIndex(
27 | (prevIndex) => (prevIndex + images.length - 1) % images.length
28 | )
29 | }
30 | onMoveNextRequest={() =>
31 | setIndex((prevIndex) => (prevIndex + 1) % images.length)
32 | }
33 | />
34 | )}
35 | >
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs-tailwind-starter",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "prepare": "husky install",
10 | "lint": "next lint"
11 | },
12 | "dependencies": {
13 | "@hookform/resolvers": "^2.6.0",
14 | "@tailwindcss/forms": "^0.3.3",
15 | "autoprefixer": "^10.2.1",
16 | "next": "^11.0.0",
17 | "postcss": "^8.2.4",
18 | "react": "^17.0.2",
19 | "react-datepicker": "^4.1.1",
20 | "react-dom": "^17.0.2",
21 | "react-dropzone": "^11.3.4",
22 | "react-hook-form": "^7.10.1",
23 | "react-icons": "^4.2.0",
24 | "react-image-lightbox": "^5.1.4",
25 | "react-select": "^4.3.1",
26 | "yup": "^0.32.9"
27 | },
28 | "devDependencies": {
29 | "@commitlint/cli": "^12.1.4",
30 | "@commitlint/config-conventional": "^12.1.4",
31 | "eslint": "^7.29.0",
32 | "eslint-config-next": "^11.0.1",
33 | "husky": "^6.0.0",
34 | "lint-staged": "^11.0.0",
35 | "prettier": "^2.3.0",
36 | "tailwindcss": "^2.2.2"
37 | },
38 | "lint-staged": {
39 | "**/*.{js,jsx,ts,tsx}": [
40 | "yarn prettier --write"
41 | ]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/pages/index.jsx:
--------------------------------------------------------------------------------
1 | import Seo from '@/components/Seo';
2 | import CustomLink from '@/components/CustomLink';
3 |
4 | const pageList = [
5 | { label: 'Using Yup as schema validator', route: '/yup' },
6 | { label: 'Collection of inputs', route: '/inputs' },
7 | { label: 'Multi Step Form', route: 'https://clarence.link/stepform' },
8 | ];
9 |
10 | export default function Home() {
11 | return (
12 | <>
13 |
14 |
15 |
16 |
17 |
18 |
React Hook Form Default Inputs
19 |
23 | See the repository
24 |
25 |
26 |
27 |
List
28 |
29 | {pageList.map(({ label, route }) => (
30 | -
31 |
32 | {label}
33 |
34 |
35 | ))}
36 |
37 |
38 |
39 |
40 |
50 |
51 | >
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/components/Input.jsx:
--------------------------------------------------------------------------------
1 | import { useFormContext } from 'react-hook-form';
2 | import { HiExclamationCircle } from 'react-icons/hi';
3 |
4 | import { classNames } from '@/lib/helper';
5 |
6 | export default function Input({
7 | label,
8 | placeholder = '',
9 | helperText = '',
10 | id,
11 | type = 'text',
12 | readOnly = false,
13 | validation,
14 | ...rest
15 | }) {
16 | const {
17 | register,
18 | formState: { errors },
19 | } = useFormContext();
20 |
21 | return (
22 |
23 |
26 |
27 |
45 |
46 | {errors[id] && (
47 |
48 |
49 |
50 | )}
51 |
52 |
53 | {helperText !== '' && (
54 |
{helperText}
55 | )}
56 | {errors[id] && (
57 |
{errors[id].message}
58 | )}
59 |
60 |
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/components/TextArea.jsx:
--------------------------------------------------------------------------------
1 | import { useFormContext } from 'react-hook-form';
2 | import { HiExclamationCircle } from 'react-icons/hi';
3 |
4 | import { classNames } from '@/lib/helper';
5 |
6 | export default function TextArea({
7 | label,
8 | placeholder = '',
9 | helperText = '',
10 | id,
11 | type = 'text',
12 | readOnly = false,
13 | validation,
14 | ...rest
15 | }) {
16 | const {
17 | register,
18 | formState: { errors },
19 | } = useFormContext();
20 |
21 | return (
22 |
23 |
26 |
27 |
46 | {errors[id] && (
47 |
48 |
49 |
50 | )}
51 |
52 |
53 | {helperText !== '' && (
54 |
{helperText}
55 | )}
56 | {errors[id] && (
57 |
{errors[id].message}
58 | )}
59 |
60 |
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/components/Seo.jsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import { useRouter } from 'next/router';
3 |
4 | export default function Seo(props) {
5 | const router = useRouter();
6 | const meta = {
7 | title: 'React Hook Form Default Input',
8 | description:
9 | 'A default input list for react-hook-form by Theodorus Clarence',
10 | image: 'https://theodorusclarence.com/favicon/large-og.jpg',
11 | type: 'website',
12 | robots: 'follow, index',
13 | ...props,
14 | };
15 |
16 | return (
17 |
18 | {meta.title}
19 |
20 |
21 |
25 |
29 | {/* Open Graph */}
30 |
31 |
32 |
33 |
34 |
35 | {/* Twitter */}
36 |
37 |
38 |
39 |
40 |
41 | {meta.date && (
42 | <>
43 |
44 |
49 |
54 | >
55 | )}
56 |
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/components/PasswordInput.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { useFormContext } from 'react-hook-form';
3 | import { HiEye, HiEyeOff } from 'react-icons/hi';
4 |
5 | import { classNames } from '@/lib/helper';
6 |
7 | export default function PasswordInput({
8 | label,
9 | placeholder = '',
10 | helperText = '',
11 | id,
12 | readOnly = false,
13 | validation,
14 | ...rest
15 | }) {
16 | const {
17 | register,
18 | formState: { errors },
19 | } = useFormContext();
20 |
21 | const [showPassword, setShowPassword] = useState(false);
22 | const togglePassword = () => setShowPassword((prev) => !prev);
23 |
24 | return (
25 |
26 |
29 |
30 |
48 |
49 |
62 |
63 |
64 | {helperText !== '' && (
65 |
{helperText}
66 | )}
67 | {errors[id] && (
68 |
{errors[id].message}
69 | )}
70 |
71 |
72 | );
73 | }
74 |
--------------------------------------------------------------------------------
/components/Select.jsx:
--------------------------------------------------------------------------------
1 | import { Children, cloneElement } from 'react';
2 | import { useFormContext } from 'react-hook-form';
3 | import { HiExclamationCircle } from 'react-icons/hi';
4 |
5 | import { classNames } from '@/lib/helper';
6 |
7 | export default function Select({
8 | label,
9 | helperText,
10 | id,
11 | placeholder,
12 | readOnly = false,
13 | children,
14 | validation,
15 | ...rest
16 | }) {
17 | const {
18 | register,
19 | formState: { errors },
20 | } = useFormContext();
21 |
22 | // Add disabled and selected attribute to option, will be used if readonly
23 | const readOnlyChildren = Children.map(children, (child) => {
24 | return cloneElement(child, {
25 | disabled: child.props.value !== rest?.defaultValue,
26 | selected: child.props.value === rest?.defaultValue,
27 | });
28 | });
29 |
30 | return (
31 |
32 |
35 |
36 |
60 |
61 | {errors[id] && (
62 |
63 |
64 |
65 | )}
66 |
67 |
68 | {helperText &&
{helperText}
}
69 | {errors[id] && (
70 |
{errors[id].message}
71 | )}
72 |
73 |
74 | );
75 | }
76 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a NextJs and Tailwind project bootstrapped using nextjs-tailwind-starter created by [Theodorus Clarence](https://github.com/theodorusclarence/nextjs-tailwind-starter).
2 |
3 | See the deployment on [https://nextjs-tailwind-starter.theodorusclarence.com/](https://nextjs-tailwind-starter.theodorusclarence.com/)
4 |
5 | 
6 |
7 | ## Getting Started
8 |
9 | To use this starter, you can use create-next-app to do it by:
10 | ```bash
11 | npx create-next-app -e https://github.com/theodorusclarence/nextjs-tailwind-starter project-name
12 | ```
13 |
14 | or
15 |
16 | [](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Ftheodorusclarence%2Fnextjs-tailwind-starter)
17 |
18 | First, run the development server:
19 |
20 | ```bash
21 | npm run dev
22 | # or
23 | yarn dev
24 | ```
25 |
26 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
27 |
28 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
29 |
30 | ## What's Inside
31 |
32 | ### Inter Fonts
33 |
34 | Inter fonts is self hosted. The default weights are `400, 600, 700`. To add more, use fontsquirrel.
35 |
36 | ### UnstyledLink Component
37 | Used as a component for Next.js Link. Will render out Next/Link if the href started with `/` or `#`, else will render an `a` tag with `target='_blank'`.
38 | ### CustomLink Component
39 | An extension of UnstyledLink Component, you can add your default styling for a button/link.
40 | ```jsx
41 |
45 | {props.children}
46 |
47 | ```
48 |
49 | ### Default Favicon Declaration
50 | Use [Favicon Generator](https://www.favicon-generator.org/) and then overwrite the files in `/public/favicon`
51 |
52 | ### Just-In-Time Tailwindcss
53 | Defaulted to true, you can uncomment the `mode='jit'` in `/tailwind.config.js`
54 |
55 | ### Default Styles
56 | There are default styles for responsive heading sizes, and `.layout` to support a max-width for larger screen size.
57 |
58 | ### Seo Component
59 | Configure the default in `/components/Seo.jsx`. If you want to use the default, just add `` on top of your page.
60 |
61 | You can also customize it per page by overriding the title, description as props
62 |
63 | ```jsx
64 |
68 | ```
--------------------------------------------------------------------------------
/components/ReactSelect.jsx:
--------------------------------------------------------------------------------
1 | import { Controller, useFormContext } from 'react-hook-form';
2 | import Select from 'react-select';
3 |
4 | export default function ReactSelect({
5 | disabled,
6 | label,
7 | helperText,
8 | id,
9 | placeholder,
10 | validation,
11 | options,
12 | defaultValue,
13 | }) {
14 | const customStyles = {
15 | control: (styles, state) => ({
16 | ...styles,
17 | border: state.isFocused ? '0' : '1px solid rgb(209, 213, 219)',
18 | boxShadow: state.isFocused ? '0 0 0 0.1rem rgb(0, 196, 253)' : 0,
19 | '*': {
20 | boxShadow: 'none !important',
21 | },
22 | }),
23 | option: (styles, state) => ({
24 | ...styles,
25 | color: 'black',
26 | background: state.isSelected ? '#D1D5DB' : 'white',
27 | ':hover': {
28 | background: '#E5E7EB',
29 | },
30 | }),
31 | };
32 |
33 | const errorStyles = {
34 | control: (styles) => ({
35 | ...styles,
36 | border: 'none',
37 | boxShadow: '0 0 0 0.04rem #EF4444',
38 | '*': {
39 | boxShadow: 'none !important',
40 | },
41 | }),
42 | option: (styles, state) => ({
43 | ...styles,
44 | color: 'black',
45 | background: state.isSelected ? '#D1D5DB' : 'white',
46 | ':hover': {
47 | background: '#E5E7EB',
48 | },
49 | }),
50 | };
51 |
52 | const optionsObject = options.map((option) => {
53 | return {
54 | value: option,
55 | label: option,
56 | };
57 | });
58 |
59 | const {
60 | control,
61 | formState: { errors },
62 | } = useFormContext();
63 |
64 | return (
65 |
66 |
69 |
70 |
{
80 | const styles = errors[id] ? errorStyles : customStyles;
81 | return (
82 |
89 | );
90 | }}
91 | />
92 |
93 | {helperText &&
{helperText}
}
94 | {errors[id] && (
95 |
{errors[id].message}
96 | )}
97 |
98 |
99 |
100 | );
101 | }
102 |
--------------------------------------------------------------------------------
/components/DatePicker.jsx:
--------------------------------------------------------------------------------
1 | import ReactDatePicker from 'react-datepicker';
2 | import 'react-datepicker/dist/react-datepicker.css';
3 | import { Controller, useFormContext } from 'react-hook-form';
4 | import { HiOutlineCalendar } from 'react-icons/hi';
5 |
6 | import { classNames } from '@/lib/helper';
7 |
8 | export default function DatePicker({
9 | validation,
10 | label,
11 | id,
12 | placeholder,
13 | defaultYear,
14 | defaultMonth,
15 | defaultValue,
16 | helperText,
17 | readOnly = false,
18 | ...rest
19 | }) {
20 | const {
21 | formState: { errors },
22 | control,
23 | } = useFormContext();
24 |
25 | // If there is a year default, then change the year to the props
26 | const defaultDate = new Date();
27 | if (defaultYear) defaultDate.setFullYear(defaultYear);
28 | if (defaultMonth) defaultDate.setMonth(defaultMonth);
29 |
30 | return (
31 |
32 |
35 |
36 |
(
42 | <>
43 |
44 |
67 |
68 |
69 |
70 | {helperText !== '' && (
71 |
{helperText}
72 | )}
73 | {errors[id] && (
74 |
75 | {errors[id].message}
76 |
77 | )}
78 |
79 | >
80 | )}
81 | />
82 |
83 | );
84 | }
85 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | html {
7 | scroll-behavior: smooth;
8 | scroll-padding: 50px;
9 | }
10 |
11 | /* inter var - latin */
12 | @font-face {
13 | font-family: 'Inter';
14 | font-style: normal;
15 | font-weight: 100 900;
16 | font-display: optional;
17 | src: url('/fonts/inter-var-latin.woff2') format('woff2');
18 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
19 | U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
20 | U+2215, U+FEFF, U+FFFD;
21 | }
22 |
23 | /* Write your own custom base styles here */
24 | h1 {
25 | @apply text-3xl font-bold md:text-5xl font-primary;
26 | }
27 |
28 | h2 {
29 | @apply text-2xl font-bold md:text-4xl font-primary;
30 | }
31 |
32 | h3 {
33 | @apply text-xl font-bold md:text-3xl font-primary;
34 | }
35 |
36 | h4 {
37 | @apply text-lg font-bold font-primary;
38 | }
39 |
40 | body {
41 | @apply text-sm font-primary md:text-base;
42 | }
43 |
44 | .layout {
45 | /* 750px */
46 | /* max-width: 43.75rem; */
47 |
48 | /* 1100px */
49 | max-width: 68.75rem;
50 | @apply w-11/12 mx-auto;
51 | }
52 | }
53 |
54 | @layer utilities {
55 | .animated-underline {
56 | background-image: linear-gradient(#33333300, #33333300),
57 | linear-gradient(to right, #00e0f3, #00c4fd);
58 | background-size: 100% 2px, 0 2px;
59 | background-position: 100% 100%, 0 100%;
60 | background-repeat: no-repeat;
61 | transition: background-size 0.3s ease;
62 | }
63 | .animated-underline:hover,
64 | .animated-underline:focus {
65 | background-size: 0 2px, 100% 2px;
66 | }
67 |
68 | .hash-anchor {
69 | @apply relative;
70 | border-bottom: 1px dotted transparent;
71 | }
72 |
73 | .hash-anchor:hover,
74 | .hash-anchor:focus {
75 | border-bottom: 1px dotted currentcolor;
76 | }
77 |
78 | .hash-anchor:hover:after,
79 | .hash-anchor:focus:after {
80 | visibility: visible;
81 | }
82 |
83 | .hash-anchor:after {
84 | @apply absolute invisible text-lg -translate-y-1/2 -right-5 text-primary-500 top-1/2;
85 | content: '#';
86 | }
87 | }
88 |
89 | /* React Datepicker Reset Style */
90 | .react-datepicker-wrapper {
91 | display: block;
92 | width: 100%;
93 | }
94 |
95 | .react-datepicker__navigation.react-datepicker__navigation--previous,
96 | .react-datepicker__navigation.react-datepicker__navigation--next {
97 | top: 6px;
98 | }
99 |
100 | .react-datepicker__header__dropdown.react-datepicker__header__dropdown--select {
101 | padding: 0 5px;
102 | }
103 |
104 | .react-datepicker__header__dropdown {
105 | margin-top: 0.5rem;
106 | }
107 |
108 | .react-datepicker__year-select,
109 | .react-datepicker__month-select {
110 | padding-top: 0.2rem;
111 | padding-bottom: 0.2rem;
112 | padding-left: 0.7rem;
113 | border-radius: 0.25rem;
114 | }
115 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript } from 'next/document';
2 |
3 | class MyDocument extends Document {
4 | static async getInitialProps(ctx) {
5 | const initialProps = await Document.getInitialProps(ctx);
6 | return { ...initialProps };
7 | }
8 |
9 | render() {
10 | return (
11 |
12 |
13 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
71 |
77 |
83 |
89 |
90 |
91 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | );
103 | }
104 | }
105 |
106 | export default MyDocument;
107 |
--------------------------------------------------------------------------------
/components/Dropzone.jsx:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react';
2 | import { useFormContext } from 'react-hook-form';
3 | import { useDropzone } from 'react-dropzone';
4 |
5 | import { HiX } from 'react-icons/hi';
6 |
7 | import ImageLightbox from './ImageLightbox';
8 |
9 | import { classNames } from '@/lib/helper';
10 |
11 | const FilePreview = ({ file, deleteFile }) => {
12 | const imagesType = ['image/png', 'image/jpg', 'image/jpeg'];
13 |
14 | return imagesType.includes(file.type) ? (
15 |
16 |
21 |
27 |
28 | ) : (
29 |
30 |
36 | {file.name}
37 |
38 | );
39 | };
40 |
41 | export default function DragnDropInput({
42 | accept,
43 | id,
44 | label,
45 | helperText = '',
46 | maxFiles = 1,
47 | validation,
48 | }) {
49 | const {
50 | register,
51 | setValue,
52 | setError,
53 | clearErrors,
54 | watch,
55 | formState: { errors },
56 | } = useFormContext();
57 |
58 | const files = watch(id);
59 | const onDrop = useCallback(
60 | (acceptedFiles, rejectedFiles) => {
61 | if (rejectedFiles && rejectedFiles.length > 0) {
62 | setValue(id, []);
63 | setError(id, {
64 | type: 'manual',
65 | message: rejectedFiles && rejectedFiles[0].errors[0].message,
66 | });
67 | } else {
68 | setValue(id, acceptedFiles, { shouldValidate: true });
69 | clearErrors(id);
70 | }
71 | },
72 | [id, setValue, setError, clearErrors]
73 | );
74 |
75 | const deleteFile = (e, file) => {
76 | e.preventDefault();
77 | const newFiles = [...files];
78 |
79 | newFiles.splice(newFiles.indexOf(file), 1);
80 |
81 | if (newFiles.length > 0) {
82 | setValue(id, newFiles);
83 | } else {
84 | setValue(id, []);
85 | }
86 | };
87 |
88 | const { getRootProps, getInputProps } = useDropzone({
89 | onDrop,
90 | accept,
91 | maxFiles,
92 | });
93 |
94 | return (
95 | <>
96 |
99 |
100 | {files?.length >= maxFiles ? (
101 |
102 | {files.map((file) => (
103 |
104 | ))}
105 |
106 | ) : (
107 | <>
108 |
109 |
110 |
118 |
119 | Drag 'n' drop some files here, or click to select
120 | files
121 |
122 |
123 | {!!files?.length && (
124 |
125 | {files.map((file) => (
126 |
131 | ))}
132 |
133 | )}
134 |
135 |
136 |
137 | {helperText !== '' && (
138 |
{helperText}
139 | )}
140 | {errors[id] && (
141 |
{errors[id].message}
142 | )}
143 |
144 | >
145 | )}
146 | >
147 | );
148 | }
149 |
--------------------------------------------------------------------------------
/pages/yup.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-useless-escape */
2 | import * as yup from 'yup';
3 | import { useState } from 'react';
4 | import { FormProvider, useForm } from 'react-hook-form';
5 | import { yupResolver } from '@hookform/resolvers/yup';
6 |
7 | import Seo from '@/components/Seo';
8 | import Input from '@/components/Input';
9 | import Button from '@/components/Button';
10 | import CustomLink from '@/components/CustomLink';
11 | import DatePicker from '@/components/DatePicker';
12 | import PasswordInput from '@/components/PasswordInput';
13 |
14 | const schema = yup.object().shape({
15 | name: yup.string().required('Name is required'),
16 | email: yup
17 | .string()
18 | .email('Need to be a valid email')
19 | .required('Email is required'),
20 | password: yup
21 | .string()
22 | .required('Password is required')
23 | .min(8, 'Password must be at least 8 characters long'),
24 | age: yup
25 | .number('Must be a number')
26 | .typeError('Must be a number')
27 | .positive('Must be a positive value')
28 | .integer('Must be a number')
29 | .required('Age is required'),
30 | phone: yup
31 | .string()
32 | .matches(/^\+628[1-9][0-9]{8,11}$/, 'Must use +62 format')
33 | .required('Phone is required'),
34 | personalsite: yup
35 | .string()
36 | .url('Must be a url')
37 | .required('Personal Site is required'),
38 | date: yup
39 | .date()
40 | .min(new Date('2020-08-15'), 'Date must be greater than 15/08/2020')
41 | .required('Date is required'),
42 | });
43 |
44 | export default function YupPage() {
45 | const methods = useForm({ mode: 'onTouched', resolver: yupResolver(schema) });
46 | const { handleSubmit } = methods;
47 |
48 | const [formData, setFormData] = useState(null);
49 | const onSubmit = (data) => {
50 | setFormData(data);
51 | };
52 |
53 | return (
54 | <>
55 |
56 |
57 |
58 |
59 |
60 |
61 | ← Back to Home
62 |
63 |
Form Using Yup Validation
64 |
68 | See the source code
69 |
70 |
71 |
88 |
89 |
90 |
Yup Schema:
91 |
{schemaString}
92 |
93 |
94 |
Result:
95 |
96 | {JSON.stringify(formData, null, 2)}
97 |
98 |
99 |
100 |
101 |
102 | >
103 | );
104 | }
105 |
106 | const schemaString = `
107 | {
108 | name: yup.string().required('Name is required'),
109 | email: yup
110 | .string()
111 | .email('Need to be a valid email')
112 | .required('Email is required'),
113 | password: yup
114 | .string()
115 | .required('Password is required')
116 | .min(8, 'Password must be at least 8 characters long'),
117 | age: yup
118 | .number('Must be a number')
119 | .typeError('Must be a number')
120 | .positive('Must be a positive value')
121 | .integer('Must be a number')
122 | .required('Age is required'),
123 | phone: yup
124 | .string()
125 | .matches(/^\+628[1-9][0-9]{8,11}$/, 'Must use +62 format')
126 | .required('Phone is required'),
127 | personalsite: yup
128 | .string()
129 | .url('Must be a url')
130 | .required('Personal Site is required'),
131 | date: yup
132 | .date()
133 | .min(new Date('2020-08-15'), 'Date must be greater than 15/08/2020')
134 | .required('Date is required'),
135 | }
136 | `;
137 |
--------------------------------------------------------------------------------
/pages/inputs.jsx:
--------------------------------------------------------------------------------
1 | import { FormProvider } from 'react-hook-form';
2 | import { useForm } from 'react-hook-form';
3 | import id from 'date-fns/locale/id';
4 |
5 | import Seo from '@/components/Seo';
6 | import Input from '@/components/Input';
7 | import Button from '@/components/Button';
8 | import HashLink from '@/components/HashLink';
9 | import TextArea from '@/components/TextArea';
10 | import DatePicker from '@/components/DatePicker';
11 | import CustomLink from '@/components/CustomLink';
12 | import Select from '@/components/Select';
13 | import PasswordInput from '@/components/PasswordInput';
14 | import Dropzone from '@/components/Dropzone';
15 | import ReactSelect from '@/components/ReactSelect';
16 |
17 | export default function InputsPage() {
18 | const methods = useForm({ mode: 'onTouched' });
19 | const { handleSubmit } = methods;
20 | const onSubmit = (data) => {
21 | console.log(data);
22 | };
23 |
24 | return (
25 | <>
26 |
27 |
28 |
29 |
30 |
31 |
32 | ← Back to Home
33 |
34 |
List of Inputs
35 |
39 | See the source code
40 |
41 |
42 |
43 |
63 |
64 |
65 |
66 |
67 | >
68 | );
69 | }
70 |
71 | function InputSection() {
72 | return (
73 |
122 | );
123 | }
124 |
125 | function PasswordInputSection() {
126 | return (
127 |
174 | );
175 | }
176 |
177 | function TextAreaSection() {
178 | return (
179 |
180 |
195 |
196 |
197 | Default Text Area
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 | Text Area with required validation (onTouched)
206 |
207 |
208 |
214 |
215 |
216 |
217 |
218 | Text Area Read Only
219 |
220 |
227 |
228 |
229 | );
230 | }
231 |
232 | function DatePickerSection() {
233 | return (
234 |
235 |
250 |
251 |
252 |
253 | Default DatePicker
254 |
255 |
261 |
262 |
263 |
264 |
265 | DatePicker with defaultYear to 2001
266 |
267 |
273 |
274 |
275 |
276 |
277 | DatePicker with defaultMonth to May
278 |
279 | {/* Month starts from 0 */}
280 |
286 |
287 |
288 |
289 |
290 | DatePicker with ID Locale
291 |
292 | {/* import id from date-fns/locale/id */}
293 |
299 |
300 |
301 |
302 |
303 | DatePicker with required validation
304 |
305 |
311 |
312 |
313 |
314 |
315 | DatePicker Read Only
316 |
317 |
324 |
325 |
326 | );
327 | }
328 |
329 | function SelectSection() {
330 | return (
331 |
332 |
347 |
348 |
349 |
350 | Normal Select
351 |
352 |
360 |
361 |
362 |
363 | Select with required validation
364 |
365 |
376 |
377 |
378 |
379 | Select with default value
380 |
381 |
392 |
393 |
394 |
395 | Select Read Only
396 |
397 |
408 |
409 |
410 | );
411 | }
412 |
413 | function DropzoneSection() {
414 | return (
415 |
474 | );
475 | }
476 |
477 | function ReactSelectSection() {
478 | return (
479 |
480 |
495 |
496 |
497 |
498 | Normal Select
499 |
500 |
506 |
507 |
508 |
509 | Select with required validation
510 |
511 |
519 |
520 |
521 |
522 | Select with default value
523 |
524 |
532 |
533 |
534 | );
535 | }
536 |
--------------------------------------------------------------------------------