From dbaaa3e4f845b85f965a7bc49b8822b99940bb75 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Fri, 3 May 2024 17:37:47 +1000 Subject: [PATCH] refactor: drop Input.Control component (#64) --- .../src/components/form/Form.stories.tsx | 9 +-- .../src/components/input/Input.stories.tsx | 27 ++++----- packages/react/src/components/input/Input.tsx | 58 +++++++++++------- .../react/src/components/input/InputAddon.tsx | 18 ++++-- .../src/components/input/InputControl.tsx | 50 ---------------- .../react/src/components/input/InputGroup.tsx | 53 ++++++++++++++++ packages/react/src/components/input/index.ts | 6 +- .../src/components/input/use-input.hook.tsx | 9 ++- packages/theme/src/components/input.ts | 60 ++++++++----------- 9 files changed, 154 insertions(+), 136 deletions(-) delete mode 100644 packages/react/src/components/input/InputControl.tsx create mode 100644 packages/react/src/components/input/InputGroup.tsx diff --git a/packages/react/src/components/form/Form.stories.tsx b/packages/react/src/components/form/Form.stories.tsx index 11a87df..25bc17e 100644 --- a/packages/react/src/components/form/Form.stories.tsx +++ b/packages/react/src/components/form/Form.stories.tsx @@ -20,12 +20,13 @@ export const Default: StoryFn = (args) => (
Email address - + $ - + USD - - invalid email address provided + + + A unique string of characters that identifies an email account
) diff --git a/packages/react/src/components/input/Input.stories.tsx b/packages/react/src/components/input/Input.stories.tsx index 2d7c0c0..f3183ef 100644 --- a/packages/react/src/components/input/Input.stories.tsx +++ b/packages/react/src/components/input/Input.stories.tsx @@ -1,20 +1,23 @@ -import type { InputProps } from '@/components/input' +import type { InputGroupProps, InputProps } from '@/components/input' import type { Meta, StoryFn } from '@storybook/react' import { input } from '@giantnodes/theme' import { Input } from '@/components/input' -const Component: Meta = { +type InputComponentProps = React.ComponentProps + +const Component: Meta = { title: 'Components/Input', component: Input, argTypes: { size: { control: { type: 'select' }, + options: ['xs', 'sm', 'md', 'lg', 'xl'], }, status: { control: { type: 'select' }, - options: ['neutral', 'success', 'info', 'warning', 'danger'], + options: ['neutral', 'brand', 'success', 'info', 'warning', 'danger'], }, }, } @@ -23,27 +26,21 @@ const defaultProps = { ...input.defaultVariants, } -export const Default: StoryFn = (args) => ( - - $ - - USD - -) +export const Default: StoryFn = (args) => Default.args = { ...defaultProps, } -export const Disabled: StoryFn = (args) => ( - +export const UsingGroup: StoryFn = (args) => ( + $ - + USD - + ) -Disabled.args = { +UsingGroup.args = { ...defaultProps, } diff --git a/packages/react/src/components/input/Input.tsx b/packages/react/src/components/input/Input.tsx index ae59303..36962ea 100644 --- a/packages/react/src/components/input/Input.tsx +++ b/packages/react/src/components/input/Input.tsx @@ -1,50 +1,68 @@ import type * as Polymophic from '@/utilities/polymorphic' import type { InputVariantProps } from '@giantnodes/theme' +import type { InputProps } from 'react-aria-components' import React from 'react' +import { Input } from 'react-aria-components' +import { useFormGroupContext } from '@/components/form/use-form-group.hook' import InputAddon from '@/components/input/InputAddon' -import InputControl from '@/components/input/InputControl' -import { InputContext, useInput } from '@/components/input/use-input.hook' +import InputGroup from '@/components/input/InputGroup' +import { useInput, useInputContext } from '@/components/input/use-input.hook' -const __ELEMENT_TYPE__ = 'div' +const __ELEMENT_TYPE__ = 'input' type ComponentOwnProps = InputVariantProps -type ComponentProps = Polymophic.ComponentPropsWithRef +type ComponentProps = Polymophic.ComponentPropsWithRef< + TElement, + ComponentOwnProps +> -type ComponentType = ( - props: ComponentProps +type ComponentType = ( + props: ComponentProps ) => React.ReactNode const Component: ComponentType = React.forwardRef( - (props: ComponentProps, ref: Polymophic.Ref) => { - const { as, children, className, status, size, variant, transparent, ...rest } = props + ( + props: ComponentProps, + ref: Polymophic.Ref + ) => { + const { as, children, className, status, size, variant, ...rest } = props - const Element = as ?? __ELEMENT_TYPE__ + const Element = as || Input - const context = useInput({ status, size, variant, transparent }) + const context = useInputContext() + const { slots } = useInput({ + status: status ?? context?.status, + size: size ?? context?.size, + variant: variant ?? context?.variant, + }) - const component = React.useMemo>( + const group = useFormGroupContext() + + const component = React.useMemo( () => ({ - className: context.slots.input({ className }), + name: group?.name, + onChange: group?.onChange, + onBlur: group?.onBlur, + className: slots.input({ className: className?.toString() }), + ...group?.fieldProps, ...rest, }), - [className, context.slots, rest] + [className, group?.fieldProps, group?.name, group?.onBlur, group?.onChange, rest, slots] ) return ( - - - {children} - - + ) ?? ref}> + {children} + ) } ) -export type { ComponentOwnProps as InputProps } +export type { ComponentOwnProps as InputOwnProps, ComponentProps as InputProps } export default Object.assign(Component, { Addon: InputAddon, - Control: InputControl, + Group: InputGroup, }) diff --git a/packages/react/src/components/input/InputAddon.tsx b/packages/react/src/components/input/InputAddon.tsx index 8e97d7d..01e4dfd 100644 --- a/packages/react/src/components/input/InputAddon.tsx +++ b/packages/react/src/components/input/InputAddon.tsx @@ -8,17 +8,23 @@ const __ELEMENT_TYPE__ = 'span' type ComponentOwnProps = {} -type ComponentProps = Polymophic.ComponentPropsWithRef +type ComponentProps = Polymophic.ComponentPropsWithRef< + TElement, + ComponentOwnProps +> -type ComponentType = ( - props: ComponentProps +type ComponentType = ( + props: ComponentProps ) => React.ReactNode const Component: ComponentType = React.forwardRef( - (props: ComponentProps, ref: Polymophic.Ref) => { + ( + props: ComponentProps, + ref: Polymophic.Ref + ) => { const { as, children, className, ...rest } = props - const Element = as ?? __ELEMENT_TYPE__ + const Element = as || __ELEMENT_TYPE__ const { slots } = useInputContext() @@ -38,5 +44,5 @@ const Component: ComponentType = React.forwardRef( } ) -export type { ComponentOwnProps as InputAddonProps } +export type { ComponentProps as InputAddonProps, ComponentOwnProps as InputAddonOwnProps } export default Component diff --git a/packages/react/src/components/input/InputControl.tsx b/packages/react/src/components/input/InputControl.tsx deleted file mode 100644 index 5a706ff..0000000 --- a/packages/react/src/components/input/InputControl.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import type * as Polymophic from '@/utilities/polymorphic' -import type { InputProps } from 'react-aria-components' - -import React from 'react' -import { Input } from 'react-aria-components' - -import { useFormGroupContext } from '@/components/form/use-form-group.hook' -import { useInputContext } from '@/components/input/use-input.hook' - -const __ELEMENT_TYPE__ = 'input' - -type ComponentOwnProps = InputProps - -type ComponentProps = Polymophic.ComponentPropsWithRef - -type ComponentType = ( - props: ComponentProps -) => React.ReactNode - -const Component: ComponentType = React.forwardRef( - (props: ComponentProps, ref: Polymophic.Ref) => { - const { as, children, className, ...rest } = props - - const Element = as ?? Input - - const { slots } = useInputContext() - const group = useFormGroupContext() - - const component = React.useMemo( - () => ({ - name: group?.name, - onChange: group?.onChange, - onBlur: group?.onBlur, - className: slots.control({ className: className?.toString() }), - ...group?.fieldProps, - ...rest, - }), - [className, group?.fieldProps, group?.name, group?.onBlur, group?.onChange, rest, slots] - ) - - return ( - ) ?? ref}> - {children} - - ) - } -) - -export type { ComponentOwnProps as InputControlProps } -export default Component diff --git a/packages/react/src/components/input/InputGroup.tsx b/packages/react/src/components/input/InputGroup.tsx new file mode 100644 index 0000000..bb37bf2 --- /dev/null +++ b/packages/react/src/components/input/InputGroup.tsx @@ -0,0 +1,53 @@ +import type * as Polymophic from '@/utilities/polymorphic' +import type { InputVariantProps } from '@giantnodes/theme' +import type { GroupProps } from 'react-aria-components' + +import React from 'react' +import { Group } from 'react-aria-components' + +import { InputContext, useInput } from '@/components/input/use-input.hook' + +const __ELEMENT_TYPE__ = 'div' + +type ComponentOwnProps = GroupProps & InputVariantProps + +type ComponentProps = Polymophic.ComponentPropsWithRef< + TElement, + ComponentOwnProps +> + +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + ( + props: ComponentProps, + ref: Polymophic.Ref + ) => { + const { as, children, className, status, size, variant, ...rest } = props + + const Element = as || Group + + const context = useInput({ status, size, variant }) + + const component = React.useMemo( + () => ({ + className: context.slots.group({ className: className?.toString() }), + ...rest, + }), + [className, context.slots, rest] + ) + + return ( + + + {children} + + + ) + } +) + +export type { ComponentProps as InputGroupProps, ComponentOwnProps as InputGroupOwnProps } +export default Component diff --git a/packages/react/src/components/input/index.ts b/packages/react/src/components/input/index.ts index b7dbdb3..cd02ffd 100644 --- a/packages/react/src/components/input/index.ts +++ b/packages/react/src/components/input/index.ts @@ -1,5 +1,5 @@ -export type { InputProps } from '@/components/input/Input' -export type { InputAddonProps } from '@/components/input/InputAddon' -export type { InputControlProps } from '@/components/input/InputControl' +export type * from '@/components/input/Input' +export type * from '@/components/input/InputAddon' +export type * from '@/components/input/InputGroup' export { default as Input } from '@/components/input/Input' diff --git a/packages/react/src/components/input/use-input.hook.tsx b/packages/react/src/components/input/use-input.hook.tsx index 952f29d..cdc041c 100644 --- a/packages/react/src/components/input/use-input.hook.tsx +++ b/packages/react/src/components/input/use-input.hook.tsx @@ -10,17 +10,20 @@ type UseInputProps = InputVariantProps type UseInputReturn = ReturnType export const useInput = (props: UseInputProps) => { - const { status, size, transparent, variant } = props + const { status, size, variant } = props - const slots = React.useMemo(() => input({ status, size, transparent, variant }), [status, size, transparent, variant]) + const slots = React.useMemo(() => input({ status, size, variant }), [status, size, variant]) return { slots, + status, + size, + variant, } } export const [InputContext, useInputContext] = createContext({ name: 'InputContext', - strict: true, + strict: false, errorMessage: 'useInputContext: `context` is undefined. Seems you forgot to wrap component within ', }) diff --git a/packages/theme/src/components/input.ts b/packages/theme/src/components/input.ts index 14d8773..d288356 100644 --- a/packages/theme/src/components/input.ts +++ b/packages/theme/src/components/input.ts @@ -4,66 +4,57 @@ import { tv } from 'tailwind-variants' export const input = tv({ slots: { - input: [ - 'group', - 'flex flex-row', - 'items-center', - 'w-full', - 'p-1.5', - 'rounded-md', - 'text-shark-400 dark:text-shark-200', - 'has-[:disabled]:opacity-50', - ], - control: [ - 'grow', - 'bg-transparent', - 'text-shark-400 dark:text-shark-200', - 'focus:outline-none', - 'disabled:cursor-not-allowed', - 'placeholder:font-normal placeholder:text-sm', - ], - addon: ['flex', 'pointer-events-none', 'px-1.5'], + group: ['flex gap-px', 'bg-foreground', 'rounded-md', 'overflow-hidden', 'has-[:disabled]:opacity-50'], + input: ['flex-1', 'bg-foreground', 'w-full', 'outline-none', 'placeholder-subtitle', 'py-2 px-3'], + addon: ['flex items-center', 'bg-foreground', 'px-3'], }, variants: { size: { + xs: { + group: ['text-xs'], + input: ['placeholder:text-xs'], + }, sm: { - input: ['text-xs'], + group: ['text-sm'], + input: ['placeholder:text-sm'], }, md: { - input: ['text-sm'], + group: ['text-md'], + input: ['placeholder:text-md'], }, lg: { - input: ['text-md'], + group: ['text-lg'], + input: ['placeholder:text-lg'], + }, + xl: { + group: ['text-xl'], + input: ['placeholder:text-xl'], }, }, status: { neutral: { - input: ['dark:border-shark-500'], + group: ['text-content', 'border-partition'], }, brand: { - input: ['border-brand-500'], + group: ['text-brand', 'border-brand'], }, success: { - input: ['border-green-500'], + group: ['text-success', 'border-success'], }, info: { - input: ['border-blue-500'], + group: ['text-info', 'border-info'], }, warning: { - input: ['border-yellow-600'], + group: ['text-warning', 'border-warning'], }, danger: { - input: ['border-red-500'], + group: ['text-danger', 'border-danger'], }, }, variant: { + none: {}, outlined: { - input: ['border', 'border-solid'], - }, - }, - transparent: { - false: { - input: ['dark:bg-shark-700'], + group: ['border', 'border-solid'], }, }, }, @@ -71,7 +62,6 @@ export const input = tv({ size: 'md', status: 'neutral', variant: 'outlined', - transparent: false, }, })