Skip to content

Commit

Permalink
refactor: migrate form components to a new structure and update imports
Browse files Browse the repository at this point in the history
- Renamed and exported ButtonGroup as a standalone component.
- Removed deprecated FormErrorMessage and FormLegend components.
- Introduced new form components: Caption, Checkbox, ControlGroup, Fieldset, Input, Label, Legend, Message, Option, Radio, Select, Separator, Textarea.
- Updated imports across various components to use the new form structure.
- Adjusted styling and class names for consistency.
- Refactored error handling to utilize the new Message component.
  • Loading branch information
iatopilskii committed Dec 4, 2024
1 parent 7ef40bd commit 23c59c4
Show file tree
Hide file tree
Showing 32 changed files with 497 additions and 278 deletions.
10 changes: 7 additions & 3 deletions packages/ui/src/components/button-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ interface ButtonGroupProps {
verticalAlign?: string
}

function Root({ children, direction = 'horizontal', className, spacing = '4', verticalAlign }: ButtonGroupProps) {
export function ButtonGroup({
children,
direction = 'horizontal',
className,
spacing = '4',
verticalAlign
}: ButtonGroupProps) {
const gapClass = `gap-${spacing}`
const verticalAlignClass = verticalAlign ? `items-${verticalAlign}` : ''

Expand All @@ -20,5 +26,3 @@ function Root({ children, direction = 'horizontal', className, spacing = '4', ve
</div>
)
}

export { Root }
2 changes: 1 addition & 1 deletion packages/ui/src/components/filters/filter-trigger.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@components/dropdown-menu'
import { Input } from '@components/form'
import { Icon } from '@components/icon'
import { Input } from '@components/input'
import { TFunction } from 'i18next'

import { FilterOption, SortOption } from './types'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState } from 'react'

import { Calendar as UICalendar, type CalendarDateRange } from '@components/calendar'
import { Input } from '@components/input'
import { Input } from '@components/form'
import { cn } from '@utils/cn'

import { FilterValue } from '../../types'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DropdownMenuCheckboxItem } from '@components/dropdown-menu'
import { Input } from '@components/form'
import { Icon } from '@components/icon'
import { Input } from '@components/input'

import { CheckboxFilterOption, FilterValue } from '../../types'
import { UseFiltersReturn } from '../../use-filters'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState } from 'react'

import { Input } from '@components/form'
import { Icon } from '@components/icon'
import { Input } from '@components/input'

import { FilterValue } from '../../types'
import { UseFiltersReturn } from '../../use-filters'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useState } from 'react'

import { DropdownMenuItem } from '@components/dropdown-menu'
import { Input } from '@components/form'
import { Icon } from '@components/icon'
import { Input } from '@components/input'

import { FilterValue } from '../../types'
import { UseFiltersReturn } from '../../use-filters'
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/filters/filters-bar/sorts.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useEffect, useState } from 'react'

import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@components/dropdown-menu'
import { Input } from '@components/form'
import { Icon } from '@components/icon'
import { Input } from '@components/input'
import { closestCenter, DndContext } from '@dnd-kit/core'
import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
Expand Down
37 changes: 0 additions & 37 deletions packages/ui/src/components/form-error-message.tsx

This file was deleted.

24 changes: 0 additions & 24 deletions packages/ui/src/components/form-legend.tsx

This file was deleted.

25 changes: 25 additions & 0 deletions packages/ui/src/components/form/caption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { PropsWithChildren } from 'react'

import { Text } from '@/components'
import { cn } from '@utils/cn'

interface CaptionProps extends PropsWithChildren {
className?: string
}

/**
* Caption component that renders supplementary text below form inputs.
* Used to provide additional context or hints for form fields.
*
* @param {CaptionProps} props - The properties for the Caption component.
* @param {React.ReactNode} props.children - The content to be displayed as the caption text.
* @param {string} [props.className] - Optional additional class names for styling.
* @returns {JSX.Element} The rendered Caption component.
*/
export function Caption({ children, className }: CaptionProps) {
return (
<Text className={cn('text-foreground-4 mt-1 leading-snug', className)} size={2}>
{children}
</Text>
)
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { ComponentPropsWithoutRef, ElementRef, forwardRef } from 'react'

import { Icon } from '@/components'
import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
import { cn } from '@utils/cn'

import { Icon } from './icon'
import { Label } from './label'

interface CheckboxProps extends ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> {
label?: string
}
interface CheckboxProps extends ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> {}

/**
* Checkbox component that provides a customizable, accessible checkbox input.
* Built on top of Radix UI Checkbox primitive with additional styling.
*
* @param {CheckboxProps} props - The properties for the Checkbox component.
* @param {string} [props.className] - Optional additional class names for styling.
* @param {React.Ref<HTMLButtonElement>} ref - Forward ref for the checkbox element.
* @returns {JSX.Element} The rendered Checkbox component.
*/
const Checkbox = forwardRef<ElementRef<typeof CheckboxPrimitive.Root>, CheckboxProps>(
({ className, label, ...props }, ref) => (
({ className, ...props }, ref) => (
<div className={cn('flex gap-x-2.5', className)}>
<CheckboxPrimitive.Root
ref={ref}
Expand All @@ -22,11 +27,6 @@ const Checkbox = forwardRef<ElementRef<typeof CheckboxPrimitive.Root>, CheckboxP
<Icon className="h-1.5 w-2" name="checkbox" width={8} height={6} />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
{label && (
<Label className="leading-tight" color="foreground-1" htmlFor={props.id}>
{label}
</Label>
)}
</div>
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ interface ControlGroupProps extends HTMLAttributes<HTMLDivElement> {
type?: 'button' | 'input'
}

/**
* A container component that groups form control elements together.
*
* @param props.type - Specifies the type of control group ('button' or 'input').
* Affects spacing and ARIA labels.
* @param props.children - The form control elements to be grouped (Label, Input/Button,
* ErrorMessage, Caption, etc.).
* @param props.className - Additional CSS classes to apply to the control group.
*/
export function ControlGroup({ children, type, className, ...props }: ControlGroupProps) {
return (
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ interface FieldsetProps extends HTMLAttributes<HTMLFieldSetElement> {
shaded?: boolean
}

/**
* A form fieldset component that groups related form elements.
* @component
* @param {FieldsetProps} props - The component props
* @param {ReactNode} props.children - The content to be rendered inside the fieldset
* @param {boolean} [props.box] - When true, adds border and padding to create a box around the fieldset
* @param {boolean} [props.shaded] - When true, adds a subtle background color to the fieldset
* @param {string} [props.className] - Additional CSS classes to apply to the fieldset
* @returns {JSX.Element} A styled fieldset element
*/
export function Fieldset({ children, box, shaded, className, ...props }: FieldsetProps) {
return (
<fieldset
Expand Down
13 changes: 13 additions & 0 deletions packages/ui/src/components/form/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export * from './caption'
export * from './checkbox'
export * from './control-group'
export * from './fieldset'
export * from './input'
export * from './label'
export * from './legend'
export * from './message'
export * from './option'
export * from './radio'
export * from './select'
export * from './textarea'
export * from './separator'
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { forwardRef, InputHTMLAttributes, ReactNode } from 'react'
import { forwardRef, Fragment, InputHTMLAttributes, ReactNode } from 'react'

import { Caption, ControlGroup, Label, Message, MessageTheme } from '@/components'
import { cn } from '@utils/cn'
import { cva, type VariantProps } from 'class-variance-authority'

import { cn } from '../utils/cn'
import { ControlGroup } from './control-group'
import { ErrorMessageTheme, FormErrorMessage } from './form-error-message'
import { Label } from './label'
import { Text } from './text'

export interface BaseInputProps
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'>,
VariantProps<typeof inputVariants> {}
Expand All @@ -20,8 +16,8 @@ const inputVariants = cva('bg-transparent px-2.5 py-1 text-foreground-1 disabled
extended: 'grow border-none focus-visible:outline-none'
},
size: {
32: 'h-8',
36: 'h-9'
sm: 'h-8',
md: 'h-9'
},
theme: {
default:
Expand All @@ -32,7 +28,7 @@ const inputVariants = cva('bg-transparent px-2.5 py-1 text-foreground-1 disabled
defaultVariants: {
variant: 'default',
theme: 'default',
size: 32
size: 'sm'
}
})

Expand All @@ -45,7 +41,7 @@ const BaseInput = forwardRef<HTMLInputElement, BaseInputProps>(
BaseInput.displayName = 'BaseInput'

interface InputError {
theme: ErrorMessageTheme
theme: MessageTheme
message?: string
}

Expand All @@ -56,27 +52,49 @@ interface InputProps extends BaseInputProps {
optional?: boolean
}

/**
* A form input component with support for labels, captions, and error messages.
*
* @component
* @param {Object} props - The component props
* @param {string} [props.label] - Optional label text displayed above the input
* @param {ReactNode} [props.caption] - Optional caption text displayed below the input
* @param {InputError} [props.error] - Error configuration object containing theme and message
* @param {string} [props.id] - Input element ID, used for label association
* @param {MessageTheme} [props.theme] - Visual theme of the input
* @param {boolean} [props.disabled] - Whether the input is disabled
* @param {boolean} [props.optional] - Indicates if the field is optional, displays "(Optional)" in the label
* @param {BaseInputProps} props.rest - All other props are passed to the underlying input element
*
* @example
* <Input
* label="Email"
* id="email"
* type="email"
* placeholder="Enter your email"
* caption="We'll never share your email"
* error={{ theme: 'danger', message: 'Invalid email format' }}
* />
*/
const Input = forwardRef<HTMLInputElement, InputProps>(
({ label, caption, error, id, theme, disabled, optional, ...props }, ref) => {
const InputWrapper = error || caption || label ? ControlGroup : Fragment

return (
<ControlGroup>
<InputWrapper>
{label && (
<Label className="mb-2.5" color={disabled ? 'foreground-9' : 'foreground-2'} optional={optional} htmlFor={id}>
<Label className="mb-2.5" color={disabled ? 'disabled-dark' : 'secondary'} optional={optional} htmlFor={id}>
{label}
</Label>
)}
<BaseInput id={id} ref={ref} theme={error ? 'danger' : theme} disabled={disabled} {...props} />
{error && (
<FormErrorMessage className={cn(caption ? 'mt-1' : 'absolute top-full translate-y-1')} theme={error.theme}>
<Message className={cn(caption ? 'mt-1' : 'absolute top-full translate-y-1')} theme={error.theme}>
{error.message}
</FormErrorMessage>
)}
{caption && (
<Text className="mt-1 leading-snug text-foreground-4" size={2}>
{caption}
</Text>
</Message>
)}
</ControlGroup>
{caption && <Caption>{caption}</Caption>}
</InputWrapper>
)
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ const labelVariants = cva('peer-disabled:cursor-not-allowed peer-disabled:opacit
default: 'text-sm font-normal leading-none'
},
color: {
'foreground-1': 'text-foreground-1',
'foreground-2': 'text-foreground-2',
'foreground-5': 'text-foreground-5',
'foreground-9': 'text-foreground-9'
primary: 'text-foreground-1',
secondary: 'text-foreground-2',
disabled: 'text-foreground-5',
'disabled-dark': 'text-foreground-9'
}
},
defaultVariants: {
variant: 'default',
color: 'foreground-1'
color: 'primary'
}
})

Expand All @@ -38,6 +38,19 @@ interface LabelProps extends VariantProps<typeof labelVariants>, PropsWithChildr
className?: string
}

/**
* A Label component that wraps the Radix UI LabelPrimitive.Root component.
* It supports variant and color styling through class-variance-authority.
*
* @param {Object} props - The properties object.
* @param {string} [props.htmlFor] - The id of the element this label is associated with.
* @param {boolean} [props.optional] - If true, renders "(optional)" next to the label text.
* @param {string} [props.color] - The color variant of the label.
* @param {string} [props.variant] - The style variant of the label.
* @param {React.ReactNode} props.children - The content of the label.
* @param {string} [props.className] - Additional class names to apply to the label.
* @returns {JSX.Element} The rendered label component.
*/
const Label = ({ htmlFor, optional, color, variant, children, className }: LabelProps) => {
return (
<LabelRoot htmlFor={htmlFor} variant={variant} color={color} className={className}>
Expand Down
Loading

0 comments on commit 23c59c4

Please sign in to comment.