live lesson 13-01-24

This commit is contained in:
2024-01-15 09:53:43 +01:00
parent 06afed2826
commit 5854cab150
7 changed files with 181 additions and 50 deletions

View File

@@ -14,11 +14,14 @@
}, },
"dependencies": { "dependencies": {
"@fontsource/roboto": "^5.0.5", "@fontsource/roboto": "^5.0.5",
"@hookform/resolvers": "^3.3.4",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"react": "^18.2.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": { "devDependencies": {
"@it-incubator/eslint-config": "^0.1.3", "@it-incubator/eslint-config": "^0.1.3",

30
pnpm-lock.yaml generated
View File

@@ -8,6 +8,9 @@ dependencies:
'@fontsource/roboto': '@fontsource/roboto':
specifier: ^5.0.5 specifier: ^5.0.5
version: 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': '@radix-ui/react-checkbox':
specifier: ^1.0.4 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) 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: react-dom:
specifier: ^18.2.0 specifier: ^18.2.0
version: 18.2.0(react@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: devDependencies:
'@it-incubator/eslint-config': '@it-incubator/eslint-config':
@@ -1747,6 +1756,14 @@ packages:
resolution: {integrity: sha512-IMXFq5AMgGx0sgNLfwWsmPuy3qa7lmDmQcXXihqwF4mT2UpD725cbxZj93ERY793OWon+6V1ANax02I3nt9+4w==} resolution: {integrity: sha512-IMXFq5AMgGx0sgNLfwWsmPuy3qa7lmDmQcXXihqwF4mT2UpD725cbxZj93ERY793OWon+6V1ANax02I3nt9+4w==}
dev: false 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: /@humanwhocodes/config-array@0.11.10:
resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==}
engines: {node: '>=10.10.0'} engines: {node: '>=10.10.0'}
@@ -7541,6 +7558,15 @@ packages:
react-is: 18.1.0 react-is: 18.1.0
dev: true 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): /react-inspector@6.0.2(react@18.2.0):
resolution: {integrity: sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==} resolution: {integrity: sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==}
peerDependencies: peerDependencies:
@@ -9139,3 +9165,7 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
/zod@3.22.4:
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
dev: false

View File

@@ -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<typeof LoginForm>
export default meta
type Story = StoryObj<typeof meta>
export const Primary: Story = {}

View File

@@ -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<typeof loginSchema>
export const LoginForm = () => {
const {
handleSubmit,
control,
formState: { errors },
} = useForm<FormValues>({
resolver: zodResolver(loginSchema),
})
const onSubmit = (data: FormValues) => {
console.log(data)
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<ControlledTextField
control={control}
name={'email'}
label={'email'}
errorMessage={errors.email?.message}
/>
<ControlledTextField
control={control}
name={'password'}
label={'password'}
errorMessage={errors.password?.message}
/>
<ControlledCheckbox
control={control}
name={'rememberMe'}
label="I accept terms and conditions"
/>
<Button type="submit">Submit</Button>
</form>
)
}

View File

@@ -1,4 +1,4 @@
import { FC } from 'react' import { ComponentPropsWithoutRef, forwardRef, ElementRef } from 'react'
import * as CheckboxRadix from '@radix-ui/react-checkbox' import * as CheckboxRadix from '@radix-ui/react-checkbox'
import * as LabelRadix from '@radix-ui/react-label' import * as LabelRadix from '@radix-ui/react-label'
@@ -9,58 +9,42 @@ import s from './checkbox.module.scss'
import { Check } from '@/assets/icons' import { Check } from '@/assets/icons'
import { Typography } from '@/components' import { Typography } from '@/components'
export type CheckboxProps = { export type CheckboxProps = ComponentPropsWithoutRef<typeof CheckboxRadix.Root> & {
className?: string
checked?: boolean
onChange?: (checked: boolean) => void
disabled?: boolean
required?: boolean
label?: string label?: string
id?: string
position?: 'left' position?: 'left'
} }
export const Checkbox: FC<CheckboxProps> = ({ export const Checkbox = forwardRef<ElementRef<typeof CheckboxRadix.Root>, CheckboxProps>(
checked, ({ position, label, className, ...rest }, ref) => {
onChange, const classNames = {
position, container: clsx(s.container, className),
disabled, buttonWrapper: clsx(
required, s.buttonWrapper,
label, rest.disabled && s.disabled,
id, position === 'left' && s.left
className, ),
}) => { root: s.root,
const classNames = { indicator: s.indicator,
container: clsx(s.container, className), label: clsx(s.label, rest.disabled && s.disabled),
buttonWrapper: clsx(s.buttonWrapper, disabled && s.disabled, position === 'left' && s.left), }
root: s.root,
indicator: s.indicator,
label: clsx(s.label, disabled && s.disabled),
}
return ( return (
<div className={classNames.container}> <div className={classNames.container}>
<LabelRadix.Root asChild> <LabelRadix.Root asChild>
<Typography variant="body2" className={classNames.label} as={'label'}> <Typography variant="body2" className={classNames.label} as={'label'}>
<div className={classNames.buttonWrapper}> <div className={classNames.buttonWrapper}>
<CheckboxRadix.Root <CheckboxRadix.Root className={classNames.root} {...rest} ref={ref}>
className={classNames.root} <CheckboxRadix.Indicator className={classNames.indicator}>
checked={checked}
onCheckedChange={onChange}
disabled={disabled}
required={required}
id={id}
>
{checked && (
<CheckboxRadix.Indicator className={classNames.indicator} forceMount>
<Check /> <Check />
</CheckboxRadix.Indicator> </CheckboxRadix.Indicator>
)} </CheckboxRadix.Root>
</CheckboxRadix.Root> </div>
</div> {label}
{label} </Typography>
</Typography> </LabelRadix.Root>
</LabelRadix.Root> </div>
</div> )
) }
} )
Checkbox.displayName = 'Checkbox'

View File

@@ -0,0 +1,25 @@
import { FieldValues, useController, UseControllerProps } from 'react-hook-form'
import { Checkbox, CheckboxProps } from '@/components'
type Props<T extends FieldValues> = Omit<
UseControllerProps<T>,
'disabled' | 'rules' | 'defaultValue'
> &
Omit<CheckboxProps, 'checked' | 'onValueChange'>
export const ControlledCheckbox = <T extends FieldValues>({
control,
shouldUnregister,
...rest
}: Props<T>) => {
const {
field: { value, onChange, onBlur, ref },
} = useController({
name: rest.name,
control,
shouldUnregister,
disabled: rest.disabled,
})
return <Checkbox {...rest} checked={value} onCheckedChange={onChange} onBlur={onBlur} ref={ref} />
}

View File

@@ -0,0 +1,23 @@
import { FieldValues, useController, UseControllerProps } from 'react-hook-form'
import { TextField, TextFieldProps } from '@/components/ui/text-field'
type Props<T extends FieldValues> = Omit<
UseControllerProps<T>,
'disabled' | 'rules' | 'defaultValue'
> &
Omit<TextFieldProps, 'value' | 'onChange' | 'onValueChange'>
export const ControlledTextField = <T extends FieldValues>({
control,
shouldUnregister,
...rest
}: Props<T>) => {
const { field } = useController({
name: rest.name,
control,
shouldUnregister,
disabled: rest.disabled,
})
return <TextField {...rest} {...field} />
}