Skip to content

Commit

Permalink
feat: add form legend and fieldset components
Browse files Browse the repository at this point in the history
  • Loading branch information
PHILLIPS71 committed Jul 23, 2024
1 parent 48fcddc commit 2a8ef78
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 25 deletions.
6 changes: 3 additions & 3 deletions packages/react/src/components/form/Form.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ export const Default: StoryFn<FormProps> = (args) => (
<Form.Root {...args}>
<Form.Group name="email-address">
<Form.Label>Email address</Form.Label>
<Input.Group>
<Input.Root>
<Input.Addon>$</Input.Addon>
<Input.Root placeholder="Username" type="text" />
<Input.Text placeholder="Username" type="text" />
<Input.Addon>USD</Input.Addon>
</Input.Group>
</Input.Root>

<Form.Caption>A unique string of characters that identifies an email account</Form.Caption>
</Form.Group>
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/components/form/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ const Component: ComponentType = React.forwardRef(
props: ComponentProps<TElement>,
ref: Polymophic.Ref<TElement>
) => {
const { as, children, className, status, ...rest } = props
const { as, children, className, color, ...rest } = props

const Element = as ?? Form

const slots = React.useMemo(() => form({ status }), [status])
const slots = React.useMemo(() => form({ color }), [color])

const component = React.useMemo<FormProps>(
() => ({
Expand Down
7 changes: 3 additions & 4 deletions packages/react/src/components/form/FormFeedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

import React from 'react'

import type { FeedbackType } from '~/components/form/use-form-group.hook'
import type * as Polymophic from '~/utilities/polymorphic'
import { useFormGroupContext } from '~/components/form/use-form-group.hook'
import { cn } from '~/utilities'

const __ELEMENT_TYPE__ = 'span'

type FeedbackType = 'success' | 'warning' | 'error'

type ComponentOwnProps = {
type: FeedbackType
}
Expand Down Expand Up @@ -38,7 +37,7 @@ const Component: ComponentType = React.forwardRef(
() => ({
className: context?.slots.feedback({
class: cn(className, { hidden: type !== context.feedback }),
status: context.status,
color: context.status,
}),
...rest,
}),
Expand All @@ -53,5 +52,5 @@ const Component: ComponentType = React.forwardRef(
}
)

export type { ComponentOwnProps as FormFeedbackOwnProps, ComponentProps as FormFeedbackProps, FeedbackType }
export type { ComponentOwnProps as FormFeedbackOwnProps, ComponentProps as FormFeedbackProps }
export default Component
49 changes: 49 additions & 0 deletions packages/react/src/components/form/FormFieldSet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use client'

import React from 'react'
import { form } from '@giantnodes/theme'

import type * as Polymophic from '~/utilities/polymorphic'

const __ELEMENT_TYPE__ = 'fieldset'

type ComponentOwnProps = unknown

type ComponentProps<TElement extends React.ElementType = typeof __ELEMENT_TYPE__> = Polymophic.ComponentPropsWithRef<
TElement,
ComponentOwnProps
>

type ComponentType = <TElement extends React.ElementType = typeof __ELEMENT_TYPE__>(
props: ComponentProps<TElement>
) => React.ReactNode

const Component: ComponentType = React.forwardRef(
<TElement extends React.ElementType = typeof __ELEMENT_TYPE__>(
props: ComponentProps<TElement>,
ref: Polymophic.Ref<TElement>
) => {
const { as, children, className, ...rest } = props

const Element = as ?? __ELEMENT_TYPE__

const slots = React.useMemo(() => form({}), [])

const component = React.useMemo(
() => ({
className: slots.fieldset({ className }),
...rest,
}),
[className, slots, rest]
)

return (
<Element {...component} ref={ref}>
{children}
</Element>
)
}
)

export type { ComponentOwnProps as FormFieldSetOwnProps, ComponentProps as FormFieldSetProps }
export default Component
10 changes: 6 additions & 4 deletions packages/react/src/components/form/FormGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const Component: ComponentType = React.forwardRef(
props: ComponentProps<TElement>,
ref: Polymophic.Ref<TElement>
) => {
const { as, children, className, name, success, warning, error, onChange, onBlur, ...rest } = props
const { as, children, className, name, success, info, warning, error, onChange, onBlur, ...rest } = props

const Element = as ?? __ELEMENT_TYPE__

Expand All @@ -45,21 +45,23 @@ const Component: ComponentType = React.forwardRef(

const component = React.useMemo<React.ComponentPropsWithoutRef<typeof __ELEMENT_TYPE__>>(
() => ({
'data-error': error ?? undefined,
'data-success': success ?? undefined,
'data-info': info ?? undefined,
'data-warning': warning ?? undefined,
'data-error': error ?? undefined,
className: context.slots.group({ className }),
...rest,
}),
[className, context.slots, error, rest, success, warning]
[className, context.slots, error, info, rest, success, warning]
)

React.useEffect(() => {
if (success) context.setFeedback('success')
if (info) context.setFeedback('info')
if (warning) context.setFeedback('warning')
if (error) context.setFeedback('error')
else context.setFeedback(null)
}, [context, success, warning, error])
}, [context, error, info, success, warning])

return (
<FormGroupContext.Provider value={context}>
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/form/FormLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const Component: ComponentType = React.forwardRef(

const component = React.useMemo<LabelProps>(
() => ({
className: context?.slots.label({ className, status: context.status }),
className: context?.slots.label({ className, color: context.status }),
...context?.labelProps,
...rest,
}),
Expand Down
49 changes: 49 additions & 0 deletions packages/react/src/components/form/FormLegend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use client'

import React from 'react'
import { form } from '@giantnodes/theme'

import type * as Polymophic from '~/utilities/polymorphic'

const __ELEMENT_TYPE__ = 'legend'

type ComponentOwnProps = unknown

type ComponentProps<TElement extends React.ElementType = typeof __ELEMENT_TYPE__> = Polymophic.ComponentPropsWithRef<
TElement,
ComponentOwnProps
>

type ComponentType = <TElement extends React.ElementType = typeof __ELEMENT_TYPE__>(
props: ComponentProps<TElement>
) => React.ReactNode

const Component: ComponentType = React.forwardRef(
<TElement extends React.ElementType = typeof __ELEMENT_TYPE__>(
props: ComponentProps<TElement>,
ref: Polymophic.Ref<TElement>
) => {
const { as, children, className, ...rest } = props

const Element = as ?? __ELEMENT_TYPE__

const slots = React.useMemo(() => form({}), [])

const component = React.useMemo(
() => ({
className: slots.legend({ className }),
...rest,
}),
[className, slots, rest]
)

return (
<Element {...component} ref={ref}>
{children}
</Element>
)
}
)

export type { ComponentOwnProps as FormLegendOwnProps, ComponentProps as FormLegendSetProps }
export default Component
2 changes: 2 additions & 0 deletions packages/react/src/components/form/component.parts.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export { default as Root } from '~/components/form/Form'
export { default as Caption } from '~/components/form/FormCaption'
export { default as Feedback } from '~/components/form/FormFeedback'
export { default as FieldSet } from '~/components/form/FormFieldSet'
export { default as Group } from '~/components/form/FormGroup'
export { default as Label } from '~/components/form/FormLabel'
export { default as Legend } from '~/components/form/FormLegend'
2 changes: 2 additions & 0 deletions packages/react/src/components/form/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export type * from '~/components/form/Form'
export type * from '~/components/form/FormCaption'
export type * from '~/components/form/FormFeedback'
export type * from '~/components/form/FormFieldSet'
export type * from '~/components/form/FormGroup'
export type * from '~/components/form/FormLabel'
export type * from '~/components/form/FormLegend'

export * as Form from '~/components/form/component.parts'
10 changes: 7 additions & 3 deletions packages/react/src/components/form/use-form-group.hook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import type { LabelAria } from 'react-aria'
import React from 'react'
import { form } from '@giantnodes/theme'

import type { FeedbackType } from '~/components/form/FormFeedback'
import type { ChangeHandler } from '~/utilities/types'
import { createContext } from '~/utilities/context'

export type FeedbackType = 'success' | 'info' | 'warning' | 'error'

type UseFormGroupProps = LabelAria & {
ref?: React.RefObject<HTMLInputElement | HTMLLabelElement>
name?: string
color?: FormVariantProps['color']
onChange?: ChangeHandler
onBlur?: ChangeHandler
}
Expand All @@ -23,10 +25,12 @@ export const useFormGroup = (props: UseFormGroupProps) => {

const [feedback, setFeedback] = React.useState<FeedbackType | null>(null)

const status = React.useMemo<FormVariantProps['status']>(() => {
const status = React.useMemo<FormVariantProps['color']>(() => {
switch (feedback) {
case 'success':
return 'success'
case 'info':
return 'info'
case 'warning':
return 'warning'
case 'error':
Expand All @@ -36,7 +40,7 @@ export const useFormGroup = (props: UseFormGroupProps) => {
}
}, [feedback])

const slots = React.useMemo(() => form({ status }), [status])
const slots = React.useMemo(() => form({ color: status }), [status])

return {
ref,
Expand Down
30 changes: 22 additions & 8 deletions packages/theme/src/components/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,49 @@ import { tv } from 'tailwind-variants'
export const form = tv({
slots: {
form: [],
group: ['group', 'flex flex-col gap-2', 'w-full'],
label: ['text-sm', 'text-title'],
group: ['group', 'flex flex-col gap-1', 'w-full'],
label: ['text-sm'],
legend: ['w-full', 'mb-1'],
fieldset: ['flex flex-row gap-2', 'text-sm'],
caption: ['text-xs', 'text-subtitle'],
feedback: ['text-xs'],
},
variants: {
status: {
color: {
neutral: {
label: ['text-title'],
fieldset: ['text-title'],
feedback: ['text-content'],
},
brand: {
label: ['text-brand'],
fieldset: ['text-brand'],
feedback: ['text-brand'],
},
success: {
feedback: ['text-green'],
label: ['text-success'],
fieldset: ['text-success'],
feedback: ['text-success'],
},
info: {
feedback: ['text-blue'],
label: ['text-info'],
fieldset: ['text-info'],
feedback: ['text-info'],
},
warning: {
feedback: ['text-yellow'],
label: ['text-warning'],
fieldset: ['text-warning'],
feedback: ['text-warning'],
},
danger: {
feedback: ['text-red'],
label: ['text-danger'],
fieldset: ['text-danger'],
feedback: ['text-danger'],
},
},
},
defaultVariants: {
status: 'neutral',
color: 'neutral',
},
})

Expand Down

0 comments on commit 2a8ef78

Please sign in to comment.