update
This commit is contained in:
@@ -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<ControlContextProps>({})
|
||||
|
||||
interface ControlProviderProps extends ControlContextProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
function ControlProvider({ children, ...props }: ControlProviderProps) {
|
||||
return (
|
||||
<ControlContext.Provider value={props}>{children}</ControlContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
function useControl(props) {
|
||||
function useControl<T extends ControlContextProps>(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<HTMLElement, ControlProps>(function Control(
|
||||
{ as = 'div', children, ...props },
|
||||
ref,
|
||||
) {
|
||||
@@ -26,23 +48,29 @@ export const Control = forwardRef(function Control(
|
||||
|
||||
return (
|
||||
<ControlProvider id={props?.id || id} {...props_}>
|
||||
{React.createElement(as, { ref, ...props }, children)}
|
||||
{createElement(as, { ref, ...props }, children)}
|
||||
</ControlProvider>
|
||||
)
|
||||
})
|
||||
|
||||
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<HTMLElement>,
|
||||
<>
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-green-secondary rounded-xl">
|
||||
<Loader className="w-5 text-white" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{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<ComponentPropsWithoutRef<'input'>, 'size'> {
|
||||
as?: ElementType
|
||||
size?: 'base'
|
||||
children?: ReactNode
|
||||
}
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(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<HTMLInputElement>,
|
||||
children,
|
||||
)
|
||||
})
|
||||
|
||||
export const Checkbox = forwardRef(function Checkbox(
|
||||
{ className, ...props },
|
||||
ref,
|
||||
) {
|
||||
const { className: _, ...field } = useControl(props)
|
||||
interface CheckboxProps extends ComponentPropsWithoutRef<'input'> {
|
||||
className?: string
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
type="checkbox"
|
||||
className={clsx(
|
||||
'text-green-secondary border border-gray-300',
|
||||
'focus:ring-2 focus:border-green-secondary focus:ring-green-secondary focus:ring-offset-0 focus:ring-opacity-30',
|
||||
'dark:border-gray-700 dark:bg-gray-800 focus:dark:border-security dark:checked:bg-green-secondary dark:checked:border-security dark:disabled:bg-gray-700',
|
||||
'disabled:bg-gray-200 outline-none rounded transition',
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...field}
|
||||
/>
|
||||
)
|
||||
})
|
||||
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
||||
function Checkbox({ className, ...props }, ref) {
|
||||
const { className: _, ...field } = useControl(props)
|
||||
|
||||
export function Error({ children }) {
|
||||
return (
|
||||
<input
|
||||
type="checkbox"
|
||||
className={clsx(
|
||||
'text-green-secondary border border-gray-300',
|
||||
'focus:ring-2 focus:border-green-secondary focus:ring-green-secondary focus:ring-offset-0 focus:ring-opacity-30',
|
||||
'dark:border-gray-700 dark:bg-gray-800 focus:dark:border-security dark:checked:bg-green-secondary dark:checked:border-security dark:disabled:bg-gray-700',
|
||||
'disabled:bg-gray-200 outline-none rounded transition',
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...field}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
interface ErrorProps {
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
export function Error({ children }: ErrorProps) {
|
||||
return (
|
||||
<div className="text-sm text-red-500 flex items-start gap-0.5">
|
||||
<ExclamationCircleIcon className="w-4 mt-[1.5px] flex-shrink-0" />
|
||||
@@ -1,6 +1,6 @@
|
||||
import clsx from 'clsx'
|
||||
|
||||
export default function Loader({ className, ...props }) {
|
||||
export function Loader({ className, ...props }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
className={clsx('animate-spin', className)}
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from 'react'
|
||||
|
||||
export function Regular({ ...props }) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
Reference in New Issue
Block a user