Skip to content

Commit

Permalink
fix: formularios e login page
Browse files Browse the repository at this point in the history
  • Loading branch information
erikfig committed Sep 12, 2024
1 parent 3fe5ff1 commit 63d06c9
Show file tree
Hide file tree
Showing 20 changed files with 852 additions and 13 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"react-apexcharts": "^1.4.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.26.1",
"react-select": "^5.8.0",
"styled-components": "^6.1.13",
"tailwindcss": "^3.4.10",
"tsconfig-paths": "^4.2.0",
Expand Down
15 changes: 15 additions & 0 deletions src/components/form/form-container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { FormHTMLAttributes, ReactNode } from 'react'

type FormProps = {
children: ReactNode
} & FormHTMLAttributes<HTMLFormElement>

export const FormContainer = ({ children, className, ...props }: FormProps) => {
let localClassName = 'flex gap-6 flex-col'

if (className) {
localClassName += ` ${className}`
}

return <form className={localClassName} {...props}>{children}</form>
}
22 changes: 22 additions & 0 deletions src/components/form/input/adapter/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { InputProps } from '..'

export const Checkbox = ({ options, name, label, value, ...props }: InputProps) => {
return (
<div>
{options?.map((opt) => (
<div className="flex items-center mt-4 pl-4">
<input
id={`${name}-${opt.value}`}
name={name}
checked={Array.isArray(value) ? value.includes(opt.value) : opt.value === value}
type="checkbox"
value={opt.value}
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded dark:ring-offset-gray-800 dark:bg-gray-700 dark:border-gray-600"
{...props}
/>
<label htmlFor={`${name}-${opt.value}`} className="ms-2 text-sm font-medium text-gray-900 dark:text-gray-300">{opt.label}</label>
</div>
))}
</div>
)
}
19 changes: 19 additions & 0 deletions src/components/form/input/adapter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

import { Toggle } from './toggle'
import { Checkbox } from './checkbox'
import { Radio } from './radio'
import { Select } from './select'
import { Textarea } from './textarea'

type AdapterType = {
[key: string]: any
}

export const Adapters: AdapterType = {
'checkbox': Checkbox,
'select': Select,
'radio': Radio,
'textarea': Textarea,
'toggle': Toggle,
}
14 changes: 14 additions & 0 deletions src/components/form/input/adapter/radio.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { InputProps } from '..'

export const Radio = ({ options, name, label, value, ...props }: InputProps) => {
return (
<div>
{options?.map((opt) => (
<div className="flex items-center mt-4 pl-4">
<input id={`${name}-${opt.value}`} checked={value === opt.value} type="radio" value={opt.value} className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded dark:ring-offset-gray-800 dark:bg-gray-700 dark:border-gray-600" {...props} />
<label htmlFor={`${name}-${opt.value}`} className="ms-2 text-sm font-medium text-gray-900 dark:text-gray-300">{opt.label}</label>
</div>
))}
</div>
)
}
31 changes: 31 additions & 0 deletions src/components/form/input/adapter/select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import ReactSelect, { Props } from 'react-select'
import { InputProps } from '..'

type SelectProps = InputProps & Props

export const Select = ({ options, name, error, className, label, helpText, value, onChange, ...props }: SelectProps) => {
return (
<>
<ReactSelect
id={name}
options={options}
value={options ? options?.filter((opt) => Array.isArray(value) ? value.includes(opt.value) : opt.value === value) : []}
onChange={(e: any) => {
if (Array.isArray(e) && onChange) {
onChange({ target: { value: e.map((item: any) => item.value) } } as any)
return
}

if (onChange) {
onChange({ target: { value: e?.value } } as any)
}
}}
isSearchable
isClearable
className={`react-select-container${error ? ' error' : ''}`}
classNamePrefix="react-select"
{...props}
/>
</>
)
}
22 changes: 22 additions & 0 deletions src/components/form/input/adapter/textarea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ReactNode, TextareaHTMLAttributes } from 'react'
import { resolveClassName } from '../input-field'

type TextareaProps = {
children?: ReactNode
rows?: number
label?: string
helpText?: ReactNode
error?: boolean
} & TextareaHTMLAttributes<HTMLTextAreaElement>

export const Textarea = ({ children, rows, name, label, helpText, className, error, ...props }: TextareaProps) => {
const localClassName = resolveClassName({ error, className })

return (
<div>
<div className="flex items-center mt-4">
<textarea id={name} rows={rows} className={localClassName} {...props}>{children}</textarea>
</div>
</div>
)
}
19 changes: 19 additions & 0 deletions src/components/form/input/adapter/toggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { InputProps } from '..'
import { Toggle as ToggleComponent } from '../../toggle'

export const Toggle = ({ options, value, onChange }: InputProps) => {
return (
<div>
{options?.map((opt) => (
<div className="flex items-center mt-4 pl-4">
<ToggleComponent
checked={Array.isArray(value) ? value.includes(opt.value) : opt.value === value}
onClick={() => onChange ? onChange({ target: { value: opt.value } } as any) : ''}
>
{opt.label}
</ToggleComponent>
</div>
))}
</div>
)
}
46 changes: 46 additions & 0 deletions src/components/form/input/index.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>
)
}
37 changes: 37 additions & 0 deletions src/components/form/input/input-field.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { HTMLInputTypeAttribute, InputHTMLAttributes } from 'react'

export type InputFieldProps = {
label?: string
type?: HTMLInputTypeAttribute
error?: boolean
isLoading?: boolean
} & InputHTMLAttributes<HTMLInputElement>

const classNameTemplate = 'bg-gray-50 border-2 border-gray-300 text-gray-900 text-sm rounded-lg block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white'
const classNameErrorTemplate = '!border-red-500 !outline-red-500'

export const resolveClassName = ({ className, error }: Partial<InputFieldProps>): string => {
let localClassName = classNameTemplate

if (className) {
localClassName = `${localClassName} ${className}`
}

if (error) {
localClassName = `${localClassName} ${classNameErrorTemplate}`
}

return localClassName
}

export const InputField = ({ type, error, className, ...props }: InputFieldProps) => {
const localClassName = resolveClassName({ error, className })

return (
<input
type={type ?? 'text'}
className={localClassName}
{...props}
/>
)
}
7 changes: 7 additions & 0 deletions src/components/form/input/label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type LabelProps = {
label: string
name?: string
}

export const Label = ({ name, label }: LabelProps) =>
<label htmlFor={name} className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{label}</label>
4 changes: 2 additions & 2 deletions src/components/form/toggle/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { ReactNode } from 'react'

type ToggleProps = {
checked?: boolean,
onClick: () => void,
children: ReactNode,
onClick?: () => void,
children?: ReactNode,
}

export const Toggle = ({ checked, onClick, children }: ToggleProps) => (
Expand Down
8 changes: 5 additions & 3 deletions src/config/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { RouteObject } from 'react-router-dom'
import { Home } from '../pages/home'
import { ReactNode } from 'react'
import { NoTemplate } from '@pages/no-template'
import { Forms } from '@pages/forms'
import { LoginLayout } from '@layouts/default/login.layout'

type RouteConfigItemProps = {
title?: ReactNode,
Expand Down Expand Up @@ -37,17 +39,17 @@ export const RoutesConfig: RouteConfigItem[] = [
menuLabel: 'Formulários',
menuIcon: 'input',
},
element: <>Formulários</>,
element: <Forms />,
},
{
layout: DefaultLayout,
layout: LoginLayout,
path: '/login',
props: {
title: <><span className="material-symbols-outlined">passkey</span> Login</>,
menuLabel: 'Login',
menuIcon: 'passkey',
},
element: <>Login</>,
element: <></>,
},
{
path: '/empty',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MouseEventHandler, ReactNode, useEffect, useState } from 'react'
import { Card } from '../card'
import { Card } from '../../../../components/card'
import { useLocation } from 'react-router-dom'
import { RoutesConfig } from '@config/routes'

Expand Down
2 changes: 1 addition & 1 deletion src/layouts/default/default.layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useCallback } from 'react'
import { Rounded } from '../../components/rounded'
import { useDarkMode } from '../../contexts/dark-mode'
import './style.css'
import { Menu, MenuItem } from '../../components/menu'
import { Menu, MenuItem } from './components/menu'
import { Typography } from '../../components/Typography'
import { Toggle } from '../../components/form/toggle'
import { Outlet, useNavigate } from 'react-router-dom'
Expand Down
84 changes: 84 additions & 0 deletions src/layouts/default/login.layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { FormEvent, 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'

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="grid grid-cols-2 h-screen">

<div className="flex justify-center items-center bg-gray-300 relative">
<CardBackground src="/programmer.jpeg" className="bg-dark/80 rounded-full text-white flex flex-col gap-16 items-center justify-around">
<div className="flex items-center gap-3">
<Rounded><Img src="/logo.png" width={50} height={50} /></Rounded>
{' '}
<span>Erik <span className='text-primary'>Dashboard</span></span>
</div>
</CardBackground>
</div>

<div className="flex justify-center items-center bg-gray-200 dark:text-white dark:bg-gray-800 p-4">
<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>

</div>

<Typography type="display" className='absolute bottom-4 right-1 flex items-center gap-6 dark:text-white'>
<Toggle checked={isDarkMode} onClick={darkModeHandler}>
<span className="material-symbols-outlined">{isDarkMode ? 'brightness_4' : 'brightness_7'}</span> Dark Mode
</Toggle>
</Typography>
</div>
)
}
Loading

0 comments on commit 63d06c9

Please sign in to comment.