From deb52e1759b581b7bfb5d517f600953e3b634e72 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Fri, 10 Jan 2025 16:05:21 +1100 Subject: [PATCH] fix: use react change events + remove text field wrapper (#295) --- .../components/form/use-form-group.hook.ts | 22 ++++--- .../react/src/components/input/InputPhone.tsx | 59 ++++++++++--------- .../react/src/components/input/InputText.tsx | 37 ++++-------- .../src/components/input/InputTextArea.tsx | 37 ++++-------- .../react/src/components/select/Select.tsx | 17 +----- .../src/components/select/use-select.hook.ts | 25 ++------ packages/react/src/utilities/types.ts | 4 +- packages/theme/src/components/input.ts | 1 - packages/theme/src/components/navigation.ts | 1 + 9 files changed, 75 insertions(+), 128 deletions(-) diff --git a/packages/react/src/components/form/use-form-group.hook.ts b/packages/react/src/components/form/use-form-group.hook.ts index 867b82f5..739885d0 100644 --- a/packages/react/src/components/form/use-form-group.hook.ts +++ b/packages/react/src/components/form/use-form-group.hook.ts @@ -5,22 +5,23 @@ import type { LabelAria } from 'react-aria' import React from 'react' import { form } from '@giantnodes/theme' -import type { ChangeHandler } from '~/utilities/types' import { create } from '~/utilities/create-context' export type FeedbackType = 'success' | 'info' | 'warning' | 'error' -type UseFormGroupProps = LabelAria & { - ref?: React.RefObject +type UseFormElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement + +type UseFormGroupProps = LabelAria & { + ref?: React.RefObject name?: string color?: FormVariantProps['color'] - onChange?: ChangeHandler - onBlur?: ChangeHandler + onChange?: React.ChangeEventHandler + onBlur?: React.FocusEventHandler } -type FormGroupContextType = ReturnType +type FormGroupContextType = ReturnType> -export const useFormGroupValue = (props: UseFormGroupProps) => { +export const useFormGroupValue = (props: UseFormGroupProps) => { const { ref, name, fieldProps, labelProps, onChange, onBlur } = props const [feedback, setFeedback] = React.useState(null) @@ -56,8 +57,13 @@ export const useFormGroupValue = (props: UseFormGroupProps) => { } } -export const [FormGroupContext, useFormGroup] = create({ +export const [FormGroupContext, useFormGroupBase] = create({ name: 'FormGroupContext', strict: false, errorMessage: 'useFormGroup: `context` is undefined. Seems you forgot to wrap component within ', }) + +export const useFormGroup = () => { + const context = useFormGroupBase() + return context as FormGroupContextType | undefined +} diff --git a/packages/react/src/components/input/InputPhone.tsx b/packages/react/src/components/input/InputPhone.tsx index 25070a22..fa5de20b 100644 --- a/packages/react/src/components/input/InputPhone.tsx +++ b/packages/react/src/components/input/InputPhone.tsx @@ -2,11 +2,11 @@ import type { InputVariantProps } from '@giantnodes/theme' import type { CountryCode } from 'libphonenumber-js' -import type { InputProps, TextFieldProps } from 'react-aria-components' +import type { InputProps } from 'react-aria-components' import React from 'react' import { getExampleNumber, parsePhoneNumber } from 'libphonenumber-js/min' import examples from 'libphonenumber-js/mobile/examples' -import { Input, TextField } from 'react-aria-components' +import { Input } from 'react-aria-components' import type * as Polymorphic from '~/utilities/polymorphic' import { useFormGroup } from '~/components/form/use-form-group.hook' @@ -17,7 +17,7 @@ import { cn } from '~/utilities' const __ELEMENT_TYPE__ = 'input' type ComponentOwnProps = InputVariantProps & - Omit & { + Omit & { country: CountryCode onTemplateChange?: (template: string) => void } @@ -34,12 +34,13 @@ type ComponentType = (, ComponentOwnProps>( (props: ComponentProps, ref: Polymorphic.Ref) => { - const { as, className, country, color, size, shape, variant, onTemplateChange, ...rest } = props + const { className, country, color, size, shape, variant, onChange, onBlur, onTemplateChange, ...rest } = props - const Element = as ?? TextField - const group = useFormGroup() + const group = useFormGroup() const context = useInput() + const [value, setValue] = React.useState('') + const { slots } = useInputValue({ color: color ?? group?.status ?? context?.color, size: size ?? context?.size, @@ -101,32 +102,36 @@ const Component: ComponentType = React.forwardRef - group?.onChange?.({ - target: { value: format(value) }, - type: 'change', - }), - [format, group] - ) + const onInputChange = React.useCallback( + (event: React.ChangeEvent) => { + const formatted = format(event.target.value) - const component = React.useMemo( - () => ({ - name: group?.name, - onChange: onChange, - onBlur: group?.onBlur, - className: slots.field(), - ...group?.fieldProps, - }), - [group?.fieldProps, group?.name, group?.onBlur, onChange, slots] + const synthetic = { + ...event, + target: { + ...event.target, + value: formatted, + }, + } + + setValue(formatted) + + return (group?.onChange ?? onChange)?.(synthetic) + }, + [format, group?.onChange, onChange] ) - const input = React.useMemo( + const component = React.useMemo( () => ({ + name: group?.name, + onChange: onInputChange, + onBlur: group?.onBlur ?? onBlur, className: slots.input({ className: cn(className) }), + value, + ...group?.fieldProps, ...rest, }), - [className, rest, slots] + [className, group?.fieldProps, group?.name, group?.onBlur, onBlur, onInputChange, rest, slots, value] ) React.useEffect(() => { @@ -139,9 +144,7 @@ const Component: ComponentType = React.forwardRef - - | undefined) ?? ref} /> - + ) } diff --git a/packages/react/src/components/input/InputText.tsx b/packages/react/src/components/input/InputText.tsx index 519af500..00658b9b 100644 --- a/packages/react/src/components/input/InputText.tsx +++ b/packages/react/src/components/input/InputText.tsx @@ -1,9 +1,9 @@ 'use client' import type { InputVariantProps } from '@giantnodes/theme' -import type { InputProps, TextFieldProps } from 'react-aria-components' +import type { InputProps } from 'react-aria-components' import React from 'react' -import { Input, TextField } from 'react-aria-components' +import { Input } from 'react-aria-components' import type * as Polymorphic from '~/utilities/polymorphic' import { useFormGroup } from '~/components/form/use-form-group.hook' @@ -13,7 +13,7 @@ import { cn } from '~/utilities' const __ELEMENT_TYPE__ = 'input' type ComponentOwnProps = InputVariantProps & - Omit & { + Omit & { directory?: boolean } @@ -29,11 +29,9 @@ type ComponentType = (, ComponentOwnProps>( (props: ComponentProps, ref: Polymorphic.Ref) => { - const { as, className, color, size, shape, variant, directory, ...rest } = props + const { className, color, size, shape, variant, directory, ...rest } = props - const Element = as ?? TextField - - const group = useFormGroup() + const group = useFormGroup() const context = useInput() const { slots } = useInputValue({ @@ -43,36 +41,21 @@ const Component: ComponentType = React.forwardRef( + const component = React.useMemo( () => ({ name: group?.name, - onChange: (value: string) => - group?.onChange?.({ - target: { value }, - type: 'change', - }), + onChange: group?.onChange, onBlur: group?.onBlur, - className: slots.field(), - ...group?.fieldProps, - }), - [group, slots] - ) - - const input = React.useMemo( - () => ({ className: slots.input({ className: cn(className) }), directory: directory ? 'true' : undefined, webkitdirectory: directory ? 'true' : undefined, + ...group?.fieldProps, ...rest, }), - [className, directory, rest, slots] + [className, directory, group?.fieldProps, group?.name, group?.onBlur, group?.onChange, rest, slots] ) - return ( - - | undefined) ?? ref} /> - - ) + return } ) diff --git a/packages/react/src/components/input/InputTextArea.tsx b/packages/react/src/components/input/InputTextArea.tsx index 1c4eba0b..07a749d7 100644 --- a/packages/react/src/components/input/InputTextArea.tsx +++ b/packages/react/src/components/input/InputTextArea.tsx @@ -1,9 +1,9 @@ 'use client' import type { InputVariantProps } from '@giantnodes/theme' -import type { TextAreaProps, TextFieldProps } from 'react-aria-components' +import type { TextAreaProps } from 'react-aria-components' import React from 'react' -import { TextArea, TextField } from 'react-aria-components' +import { TextArea } from 'react-aria-components' import type * as Polymorphic from '~/utilities/polymorphic' import { useFormGroup } from '~/components/form/use-form-group.hook' @@ -26,11 +26,11 @@ type ComponentType = (, ComponentOwnProps>( (props: ComponentProps, ref: Polymorphic.Ref) => { - const { as, className, color, size, shape, variant, ...rest } = props - - const Element = as ?? TextField + const { className, color, size, shape, variant, ...rest } = props + const group = useFormGroup() const context = useInput() + const { slots } = useInputValue({ color: color ?? context?.color, size: size ?? context?.size, @@ -38,36 +38,19 @@ const Component: ComponentType = React.forwardRef( + const input = React.useMemo( () => ({ name: group?.name, - onChange: (value: string) => - group?.onChange?.({ - target: { value }, - type: 'change', - }), + onChange: group?.onChange, onBlur: group?.onBlur, - className: slots.field(), - ...group?.fieldProps, - }), - [group, slots] - ) - - const input = React.useMemo( - () => ({ className: slots.input({ className: cn(className) }), + ...group?.fieldProps, ...rest, }), - [className, rest, slots] + [className, group?.fieldProps, group?.name, group?.onBlur, group?.onChange, rest, slots] ) - return ( - -