From 5854cab1500c9db65805bf239ad9238c5cdefc6e Mon Sep 17 00:00:00 2001 From: andres Date: Mon, 15 Jan 2024 09:53:43 +0100 Subject: [PATCH] live lesson 13-01-24 --- package.json | 5 +- pnpm-lock.yaml | 30 +++++++ .../auth/login-form/login-form.stories.tsx | 14 ++++ src/components/auth/login-form/login-form.tsx | 52 ++++++++++++ src/components/ui/checkbox/checkbox.tsx | 82 ++++++++----------- .../ui/controlled/controlled-checkbox.tsx | 25 ++++++ .../ui/controlled/controlled-text-field.tsx | 23 ++++++ 7 files changed, 181 insertions(+), 50 deletions(-) create mode 100644 src/components/auth/login-form/login-form.stories.tsx create mode 100644 src/components/auth/login-form/login-form.tsx create mode 100644 src/components/ui/controlled/controlled-checkbox.tsx create mode 100644 src/components/ui/controlled/controlled-text-field.tsx diff --git a/package.json b/package.json index 9c67231..51640da 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,14 @@ }, "dependencies": { "@fontsource/roboto": "^5.0.5", + "@hookform/resolvers": "^3.3.4", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-label": "^2.0.2", "clsx": "^2.0.0", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-hook-form": "^7.49.3", + "zod": "^3.22.4" }, "devDependencies": { "@it-incubator/eslint-config": "^0.1.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9e14e9..18cdb2b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ dependencies: '@fontsource/roboto': specifier: ^5.0.5 version: 5.0.5 + '@hookform/resolvers': + specifier: ^3.3.4 + version: 3.3.4(react-hook-form@7.49.3) '@radix-ui/react-checkbox': specifier: ^1.0.4 version: 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0) @@ -23,6 +26,12 @@ dependencies: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + react-hook-form: + specifier: ^7.49.3 + version: 7.49.3(react@18.2.0) + zod: + specifier: ^3.22.4 + version: 3.22.4 devDependencies: '@it-incubator/eslint-config': @@ -1747,6 +1756,14 @@ packages: resolution: {integrity: sha512-IMXFq5AMgGx0sgNLfwWsmPuy3qa7lmDmQcXXihqwF4mT2UpD725cbxZj93ERY793OWon+6V1ANax02I3nt9+4w==} dev: false + /@hookform/resolvers@3.3.4(react-hook-form@7.49.3): + resolution: {integrity: sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==} + peerDependencies: + react-hook-form: ^7.0.0 + dependencies: + react-hook-form: 7.49.3(react@18.2.0) + dev: false + /@humanwhocodes/config-array@0.11.10: resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} engines: {node: '>=10.10.0'} @@ -7541,6 +7558,15 @@ packages: react-is: 18.1.0 dev: true + /react-hook-form@7.49.3(react@18.2.0): + resolution: {integrity: sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==} + engines: {node: '>=18', pnpm: '8'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + dependencies: + react: 18.2.0 + dev: false + /react-inspector@6.0.2(react@18.2.0): resolution: {integrity: sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==} peerDependencies: @@ -9139,3 +9165,7 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + /zod@3.22.4: + resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + dev: false diff --git a/src/components/auth/login-form/login-form.stories.tsx b/src/components/auth/login-form/login-form.stories.tsx new file mode 100644 index 0000000..a67bab6 --- /dev/null +++ b/src/components/auth/login-form/login-form.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import { LoginForm } from './login-form' + +const meta = { + title: 'Auth/LoginForm', + component: LoginForm, + tags: ['autodocs'], +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Primary: Story = {} diff --git a/src/components/auth/login-form/login-form.tsx b/src/components/auth/login-form/login-form.tsx new file mode 100644 index 0000000..dc6c70f --- /dev/null +++ b/src/components/auth/login-form/login-form.tsx @@ -0,0 +1,52 @@ +import { zodResolver } from '@hookform/resolvers/zod' +import { useForm } from 'react-hook-form' +import { z } from 'zod' + +import { Button } from '../../ui/button' + +import { ControlledCheckbox } from '@/components/ui/controlled/controlled-checkbox.tsx' +import { ControlledTextField } from '@/components/ui/controlled/controlled-text-field.tsx' + +const loginSchema = z.object({ + email: z.string().email(''), + password: z.string().min(3).max(30), + rememberMe: z.literal(true), +}) + +type FormValues = z.infer +export const LoginForm = () => { + const { + handleSubmit, + control, + formState: { errors }, + } = useForm({ + resolver: zodResolver(loginSchema), + }) + + const onSubmit = (data: FormValues) => { + console.log(data) + } + + return ( +
+ + + + + + ) +} diff --git a/src/components/ui/checkbox/checkbox.tsx b/src/components/ui/checkbox/checkbox.tsx index b833184..d0a4d6d 100644 --- a/src/components/ui/checkbox/checkbox.tsx +++ b/src/components/ui/checkbox/checkbox.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react' +import { ComponentPropsWithoutRef, forwardRef, ElementRef } from 'react' import * as CheckboxRadix from '@radix-ui/react-checkbox' import * as LabelRadix from '@radix-ui/react-label' @@ -9,58 +9,42 @@ import s from './checkbox.module.scss' import { Check } from '@/assets/icons' import { Typography } from '@/components' -export type CheckboxProps = { - className?: string - checked?: boolean - onChange?: (checked: boolean) => void - disabled?: boolean - required?: boolean +export type CheckboxProps = ComponentPropsWithoutRef & { label?: string - id?: string position?: 'left' } -export const Checkbox: FC = ({ - checked, - onChange, - position, - disabled, - required, - label, - id, - className, -}) => { - const classNames = { - container: clsx(s.container, className), - buttonWrapper: clsx(s.buttonWrapper, disabled && s.disabled, position === 'left' && s.left), - root: s.root, - indicator: s.indicator, - label: clsx(s.label, disabled && s.disabled), - } +export const Checkbox = forwardRef, CheckboxProps>( + ({ position, label, className, ...rest }, ref) => { + const classNames = { + container: clsx(s.container, className), + buttonWrapper: clsx( + s.buttonWrapper, + rest.disabled && s.disabled, + position === 'left' && s.left + ), + root: s.root, + indicator: s.indicator, + label: clsx(s.label, rest.disabled && s.disabled), + } - return ( -
- - -
- - {checked && ( - + return ( +
+ + +
+ + - )} - -
- {label} -
-
-
- ) -} +
+
+ {label} +
+
+
+ ) + } +) + +Checkbox.displayName = 'Checkbox' diff --git a/src/components/ui/controlled/controlled-checkbox.tsx b/src/components/ui/controlled/controlled-checkbox.tsx new file mode 100644 index 0000000..a974d63 --- /dev/null +++ b/src/components/ui/controlled/controlled-checkbox.tsx @@ -0,0 +1,25 @@ +import { FieldValues, useController, UseControllerProps } from 'react-hook-form' + +import { Checkbox, CheckboxProps } from '@/components' + +type Props = Omit< + UseControllerProps, + 'disabled' | 'rules' | 'defaultValue' +> & + Omit +export const ControlledCheckbox = ({ + control, + shouldUnregister, + ...rest +}: Props) => { + const { + field: { value, onChange, onBlur, ref }, + } = useController({ + name: rest.name, + control, + shouldUnregister, + disabled: rest.disabled, + }) + + return +} diff --git a/src/components/ui/controlled/controlled-text-field.tsx b/src/components/ui/controlled/controlled-text-field.tsx new file mode 100644 index 0000000..5d2835b --- /dev/null +++ b/src/components/ui/controlled/controlled-text-field.tsx @@ -0,0 +1,23 @@ +import { FieldValues, useController, UseControllerProps } from 'react-hook-form' + +import { TextField, TextFieldProps } from '@/components/ui/text-field' + +type Props = Omit< + UseControllerProps, + 'disabled' | 'rules' | 'defaultValue' +> & + Omit +export const ControlledTextField = ({ + control, + shouldUnregister, + ...rest +}: Props) => { + const { field } = useController({ + name: rest.name, + control, + shouldUnregister, + disabled: rest.disabled, + }) + + return +}