Skip to content

Commit

Permalink
feat: validações, react-hook-form e toast
Browse files Browse the repository at this point in the history
  • Loading branch information
erikfig committed Sep 16, 2024
1 parent efa87a2 commit c783e56
Show file tree
Hide file tree
Showing 13 changed files with 260 additions and 99 deletions.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,23 @@
"preview": "vite preview"
},
"dependencies": {
"@hookform/resolvers": "^3.9.0",
"@tanstack/react-query": "^5.53.3",
"apexcharts": "^3.52.0",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.44",
"react": "^18.3.1",
"react-apexcharts": "^1.4.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"react-router-dom": "^6.26.1",
"react-select": "^5.8.0",
"react-toastify": "^10.0.5",
"styled-components": "^6.1.13",
"tailwindcss": "^3.4.10",
"tsconfig-paths": "^4.2.0",
"vite-tsconfig-paths": "^5.0.0"
"vite-tsconfig-paths": "^5.0.0",
"yup": "^1.4.0"
},
"devDependencies": {
"@eslint/js": "^9.8.0",
Expand Down
6 changes: 5 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { ToastContainer } from 'react-toastify'
import { Router } from './router'

export const App = () => (
<Router />
<>
<Router />
<ToastContainer position="bottom-right" theme="colored" />
</>
)
7 changes: 5 additions & 2 deletions src/components/button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { ReactNode, HTMLAttributes, HTMLProps, createElement } from 'react'
export type ButtonProps = HTMLAttributes<HTMLButtonElement> & HTMLProps<HTMLAnchorElement> & {
children?: ReactNode,
type?: "button" | "submit" | "reset",
color?: string,
}

export const Button = ({ children, className, ...props }: ButtonProps) => {
export const Button = ({ children, className, color, ...props }: ButtonProps) => {
let tag = 'button'
let localClassName = 'text-white bg-primary hover:bg-dark font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 transition-all duration-300'
let localClassName = 'text-white disabled:bg-dark hover:bg-dark font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 transition-all duration-300'

if (props.href) {
tag = 'a'
Expand All @@ -17,5 +18,7 @@ export const Button = ({ children, className, ...props }: ButtonProps) => {
localClassName = `${localClassName} ${className}`
}

localClassName = `${localClassName} ${color ? color : `bg-primary`}`

return createElement(tag, { ...props, className: localClassName }, children)
}
48 changes: 2 additions & 46 deletions src/components/form/input/index.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,2 @@
import { ReactNode, useEffect, useState } from 'react'
import { InputField, InputFieldProps } from './input-field'
import { Typography } from '../../Typography'
import { Adapters } from './adapter'
import { Label } from './label'

export type Option = {
value: any
label: string
}

export type InputProps = InputFieldProps & {
name: string
label?: string
helpText?: ReactNode
options?: Option[]
rows?: number
isMulti?: boolean
}

export const Input = ({ label, helpText, type = 'text', options, onChange, ...props }: InputProps) => {
const [hasAdapter, setHasAdapter] = useState<boolean>(false)

useEffect(() => {
setHasAdapter(!!Adapters[type])
}, [type])

let localHelpTextClassName = 'mt-2 !mb-0 text-sm text-gray-500 dark:text-gray-400'

if (props?.error) {
localHelpTextClassName += ' !text-red-500'
}

return (
<div>
{label && <Label name={props.name} label={label} />}
<InputField type={hasAdapter ? 'hidden' : type} id={props.name} onChange={onChange} {...props} />
{
hasAdapter && Adapters[type]({ label, helpText, type, options, onChange, ...props })
}
{helpText &&
<Typography id="helper-text-explanation" className={localHelpTextClassName}>{helpText}</Typography>
}
</div>
)
}
export * from './input-controller'
export * from './input'
34 changes: 34 additions & 0 deletions src/components/form/input/input-controller.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

import {
Control, Controller, FieldErrors,
} from 'react-hook-form'
import { Input, InputProps } from './input'

type InputControllerProps = {
control: Control<any>
name: keyof FieldErrors
errors: FieldErrors<any>
size?: number
} & InputProps

export const InputController = ({
name, control, errors, size, ...props
}: InputControllerProps) => (
<Controller
name={name ?? ''}
control={control}
render={({ field: { value, onChange } }) => (
<Input
name={name}
value={value as unknown as string || ''}
error={!!errors?.[name]}
helpText={<>{errors?.[name]?.message}</>}
onChange={onChange}
width={size ? '100%' : props.width}
{...props}
/>
)}
/>
)

46 changes: 46 additions & 0 deletions src/components/form/input/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { ReactNode, useEffect, useState } from 'react'
import { InputField, InputFieldProps } from './input-field'
import { Typography } from '../../Typography'
import { Adapters } from './adapter'
import { Label } from './label'

export type Option = {
value: any
label: string
}

export type InputProps = InputFieldProps & {
name: string
label?: string
helpText?: ReactNode
options?: Option[]
rows?: number
isMulti?: boolean
}

export const Input = ({ label, helpText, type = 'text', options, onChange, ...props }: InputProps) => {
const [hasAdapter, setHasAdapter] = useState<boolean>(false)

useEffect(() => {
setHasAdapter(!!Adapters[type])
}, [type])

let localHelpTextClassName = 'mt-2 !mb-0 text-sm text-gray-500 dark:text-gray-400'

if (props?.error) {
localHelpTextClassName += ' !text-red-500'
}

return (
<div>
{label && <Label name={props.name} label={label} />}
<InputField type={hasAdapter ? 'hidden' : type} id={props.name} onChange={onChange} {...props} />
{
hasAdapter && Adapters[type]({ label, helpText, type, options, onChange, ...props })
}
{helpText &&
<Typography id="helper-text-explanation" className={localHelpTextClassName}>{helpText}</Typography>
}
</div>
)
}
1 change: 1 addition & 0 deletions src/components/toast/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from 'react-toastify'
3 changes: 2 additions & 1 deletion src/config/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ReactNode } from 'react'
import { NoTemplate } from '@pages/no-template'
import { Forms } from '@pages/forms'
import { LoginLayout } from '@layouts/default/login.layout'
import { LoginPage } from '@pages/login'

type RouteConfigItemProps = {
title?: ReactNode,
Expand Down Expand Up @@ -49,7 +50,7 @@ export const RoutesConfig: RouteConfigItem[] = [
menuLabel: 'Login',
menuIcon: 'passkey',
},
element: <></>,
element: <LoginPage />,
},
{
path: '/empty',
Expand Down
50 changes: 4 additions & 46 deletions src/layouts/default/login.layout.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,19 @@
import { FormEvent, useCallback } from 'react'
import { useCallback } from 'react'
import { useDarkMode } from '../../contexts/dark-mode'
import { Typography } from '../../components/Typography'
import { Toggle } from '../../components/form/toggle'
import { Rounded } from '../../components/rounded'
import { Img } from '../../components/img'
import { Card, CardBackground } from '../../components/card'
import { FormContainer } from '../../components/form/form-container'
import { Input } from '../../components/form/input'
import { Button } from '../../components/button'
import { useNavigate } from 'react-router-dom'
import { CardBackground } from '../../components/card'
import { Outlet } from 'react-router-dom'

export const LoginLayout = () => {
const { isDarkMode, toggleDarkMode } = useDarkMode()
const navigate = useNavigate()

const darkModeHandler = useCallback(() => {
toggleDarkMode()
}, [isDarkMode])

const submit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
navigate('/')
}

return (
<div className={isDarkMode ? 'dark' : ''}>
<div className="lg:grid lg:grid-cols-2 h-screen">
Expand All @@ -37,40 +28,7 @@ export const LoginLayout = () => {
</CardBackground>
</div>

<div className="flex justify-center items-center bg-gray-200 dark:text-white dark:bg-gray-800 p-4 h-screen">
<FormContainer style={{ maxWidth: 400, width: '100%' }} onSubmit={submit}>
<Card className='flex gap-6 flex-col'>
<Typography type="h2" className="flex items-center gap-3">
<Rounded><Img src="/logo.png" width={50} height={50} /></Rounded>
Sign in to your account
</Typography>

<Input
name="email"
label="Email"
type="email"
placeholder='Field with HTML props support'
/>

<Input
name="password"
label="Field text"
type="password"
placeholder='Field with HTML props support'
/>

<Input
name="input-text-checkbox"
type='checkbox'
options={[
{ label: 'Remember-me', value: 'remember' },
]}
/>

<Button>Login</Button>
</Card>
</FormContainer>
</div>
<Outlet />

</div>

Expand Down
1 change: 1 addition & 0 deletions src/layouts/default/style.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&family=Space+Grotesk:[email protected]&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0');
@import 'react-toastify/dist/ReactToastify.css';

@tailwind base;
@tailwind components;
Expand Down
15 changes: 13 additions & 2 deletions src/pages/forms.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react'
import { useCallback, useState } from 'react'
import { Button } from '../components/button'
import { Card } from '../components/card'
import { FormContainer } from '../components/form/form-container'
Expand All @@ -17,6 +17,17 @@ export const Forms = () => {

const { handleOnChange, handleOnChangeMultiple } = useOnChange<typeof formData>(formData, setFormData)

const reset = useCallback(() => {
setFormData({
'input-password': null,
'input-text-checkbox': null,
'input-text-radio': null,
'input-text-toggle': null,
'input-text-select': null,
'input-text-select-multiple': null,
})
}, [formData])

return (
<Card>
<FormContainer>
Expand Down Expand Up @@ -154,7 +165,7 @@ export const Forms = () => {

<div>
<Button>Confirm button</Button>
<Button type="reset" className="bg-gray-500 hover:bg-dark">Reset button</Button>
<Button type="reset" color="bg-gray-600" onClick={reset}>Reset button</Button>
</div>
</FormContainer>
</Card>
Expand Down
Loading

0 comments on commit c783e56

Please sign in to comment.