From df82a69af0a317711ab84f373038e05f78ba47bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Rafael=20Siqueira?= Date: Fri, 21 Feb 2025 15:25:49 -0300 Subject: [PATCH] update --- .../app/components/{form.jsx => form.tsx} | 118 ++++++++++++------ .../app/components/{loader.jsx => loader.tsx} | 2 +- dashboard_js/app/components/logo.tsx | 2 - dashboard_js/app/hooks/use-auth.tsx | 12 +- dashboard_js/app/routes/auth/signin/index.tsx | 13 +- dashboard_js/package-lock.json | 28 +++++ dashboard_js/package.json | 1 + 7 files changed, 129 insertions(+), 47 deletions(-) rename dashboard_js/app/components/{form.jsx => form.tsx} (51%) rename dashboard_js/app/components/{loader.jsx => loader.tsx} (88%) diff --git a/dashboard_js/app/components/form.jsx b/dashboard_js/app/components/form.tsx similarity index 51% rename from dashboard_js/app/components/form.jsx rename to dashboard_js/app/components/form.tsx index 16ea419..bad1596 100644 --- a/dashboard_js/app/components/form.jsx +++ b/dashboard_js/app/components/form.tsx @@ -1,23 +1,45 @@ -import React, { forwardRef, useContext, useId } from 'react' +import type { ReactNode, ComponentPropsWithoutRef, ElementType } from 'react' +import { + createElement, + createContext, + forwardRef, + useContext, + useId, +} from 'react' import { ExclamationCircleIcon } from '@heroicons/react/24/outline' import { omit } from 'ramda' -import Loader from './loader' +import { Loader } from './loader' import clsx from 'clsx' -const ControlContext = React.createContext({}) +interface ControlContextProps { + id?: string + className?: string + [key: string]: unknown +} -function ControlProvider({ children, ...props }) { +const ControlContext = createContext({}) + +interface ControlProviderProps extends ControlContextProps { + children: ReactNode +} + +function ControlProvider({ children, ...props }: ControlProviderProps) { return ( {children} ) } -function useControl(props) { +function useControl(props?: T) { const field = useContext(ControlContext) - return { ...field, ...props } + return { ...field, ...props } as T & ControlContextProps } -export const Control = forwardRef(function Control( +interface ControlProps extends ComponentPropsWithoutRef<'div'> { + as?: ElementType + children?: ReactNode +} + +export const Control = forwardRef(function Control( { as = 'div', children, ...props }, ref, ) { @@ -26,23 +48,29 @@ export const Control = forwardRef(function Control( return ( - {React.createElement(as, { ref, ...props }, children)} + {createElement(as, { ref, ...props }, children)} ) }) +interface ButtonProps extends ComponentPropsWithoutRef<'button'> { + as?: ElementType + isLoading?: boolean + children?: ReactNode +} + export function Button({ children, as = 'button', className, isLoading = false, ...props -}) { +}: ButtonProps) { if (isLoading) { props['disabled'] = isLoading } - return React.createElement( + return createElement( as, { className: clsx( @@ -53,20 +81,23 @@ export function Button({ className, ), ...props, - }, + } as React.HTMLAttributes, <> {isLoading && (
)} - {children} , ) } -export function Label({ children, className, ...props }) { +interface LabelProps extends ComponentPropsWithoutRef<'label'> { + children?: ReactNode +} + +export function Label({ children, className, ...props }: LabelProps) { const { id, htmlFor, className: _, ...field } = useControl(props) return ( @@ -83,21 +114,25 @@ export function Label({ children, className, ...props }) { ) } -export const Input = forwardRef(function Input( +interface InputProps extends Omit, 'size'> { + as?: ElementType + size?: 'base' + children?: ReactNode +} +export const Input = forwardRef(function Input( { as = 'input', size = 'base', className, children, ...props }, ref, ) { const { className: _, ...field } = useControl(props) const sizes = { base: 'h-12' } - return React.createElement( + return createElement( as, { className: clsx( 'bg-white outline-none px-4 rounded-lg transition', 'border border-green-light dark:border-gray-700 dark:bg-gray-800', 'focus:ring-1 focus:border-green-secondary focus:ring-green-secondary focus:placeholder:text-transparent', - // Tailwind's won't inherit focus behavior; you must define it explicitly for both modes. 'dark:focus:border-green-secondary', 'aria-[invalid=true]:border-red-400 aria-[invalid=true]:ring-red-400', 'dark:aria-[invalid=true]:border-red-500 dark:aria-[invalid=true]:ring-red-500', @@ -108,34 +143,41 @@ export const Input = forwardRef(function Input( ), ref, ...field, - }, + } as React.InputHTMLAttributes, children, ) }) -export const Checkbox = forwardRef(function Checkbox( - { className, ...props }, - ref, -) { - const { className: _, ...field } = useControl(props) +interface CheckboxProps extends ComponentPropsWithoutRef<'input'> { + className?: string +} - return ( - - ) -}) +export const Checkbox = forwardRef( + function Checkbox({ className, ...props }, ref) { + const { className: _, ...field } = useControl(props) -export function Error({ children }) { + return ( + + ) + }, +) + +interface ErrorProps { + children?: ReactNode +} + +export function Error({ children }: ErrorProps) { return (
diff --git a/dashboard_js/app/components/loader.jsx b/dashboard_js/app/components/loader.tsx similarity index 88% rename from dashboard_js/app/components/loader.jsx rename to dashboard_js/app/components/loader.tsx index 2942d94..e8184db 100644 --- a/dashboard_js/app/components/loader.jsx +++ b/dashboard_js/app/components/loader.tsx @@ -1,6 +1,6 @@ import clsx from 'clsx' -export default function Loader({ className, ...props }) { +export function Loader({ className, ...props }: { className?: string }) { return ( (null) -export function useAuth() { - return useContext(AuthContext) +export function useAuth(): AuthContextType { + const ctx = useContext(AuthContext) + + if (!ctx) { + throw new Error('useAuth must be used within an AuthProvider') + } + + return ctx } export function AuthProvider({ children }: { children: React.ReactNode }) { diff --git a/dashboard_js/app/routes/auth/signin/index.tsx b/dashboard_js/app/routes/auth/signin/index.tsx index d047f6e..92b67e8 100644 --- a/dashboard_js/app/routes/auth/signin/index.tsx +++ b/dashboard_js/app/routes/auth/signin/index.tsx @@ -1,16 +1,23 @@ +import type { SubmitHandler } from 'react-hook-form' +import type { AuthContextType } from '~/hooks/use-auth' import { useForm } from 'react-hook-form' import { useAuth } from '~/hooks/use-auth' import { Card } from '~/layouts/auth' import { Control, Label, Input, Button } from '~/components/form' import { useNavigate } from 'react-router' +type Input = { + username: string + password: string +} + export default function Signin() { const navigate = useNavigate() - const { register, handleSubmit, formState } = useForm() + const { register, handleSubmit, formState } = useForm