Implemented 8 production-ready form components with full accessibility: Components Added: - Textarea: Multi-line text input with character counter - Checkbox: Custom styled with indeterminate state support - Radio & RadioGroup: Context-based radio button groups - Switch: Toggle with animated thumb and loading state - Select: Native select with custom styling and option groups - Slider: Range input with marks, value display, cross-browser support - FormField: Wrapper component with label, error, and helper text Features: - Full accessibility (ARIA attributes, semantic HTML) - Controlled/uncontrolled patterns via useControlled hook - Dark mode support with semantic tokens - Design tokens and shared variant system - Comprehensive test coverage (856 tests passing) - Form-specific design tokens (formTokens) - Shared form variant base classes (formVariants) Playground Updates: - Added comprehensive Forms section showcasing all components - Interactive examples with state management - Complete form composition example - All components fully functional and themed Test Coverage: - 10+ describe blocks per component - All edge cases covered - Accessibility testing - Cross-browser compatibility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
153 lines
3.2 KiB
TypeScript
153 lines
3.2 KiB
TypeScript
import { createVariants } from '../system/variants';
|
|
|
|
/**
|
|
* Switch button variant definitions
|
|
*/
|
|
export const switchVariants = createVariants({
|
|
base: [
|
|
// Reset button styles
|
|
'inline-flex items-center justify-start',
|
|
'cursor-pointer',
|
|
'border-0 p-0',
|
|
// Track styling
|
|
'rounded-full',
|
|
'transition-all duration-200',
|
|
// Focus
|
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/20 focus-visible:ring-offset-2',
|
|
// Disabled
|
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
].join(' '),
|
|
|
|
variants: {
|
|
state: {
|
|
default: [
|
|
// Unchecked background
|
|
'bg-border',
|
|
// Checked background
|
|
'data-[state=checked]:bg-primary',
|
|
// Hover
|
|
'hover:bg-border-strong',
|
|
'data-[state=checked]:hover:bg-primary',
|
|
].join(' '),
|
|
|
|
error: [
|
|
'bg-border',
|
|
'data-[state=checked]:bg-error',
|
|
'hover:bg-border-strong',
|
|
'data-[state=checked]:hover:bg-error',
|
|
'focus-visible:ring-error/20',
|
|
].join(' '),
|
|
|
|
success: [
|
|
'bg-border',
|
|
'data-[state=checked]:bg-success',
|
|
'hover:bg-border-strong',
|
|
'data-[state=checked]:hover:bg-success',
|
|
'focus-visible:ring-success/20',
|
|
].join(' '),
|
|
},
|
|
|
|
size: {
|
|
sm: 'h-5 w-9',
|
|
md: 'h-6 w-11',
|
|
lg: 'h-7 w-13',
|
|
},
|
|
|
|
loading: {
|
|
true: 'cursor-wait pointer-events-none',
|
|
false: '',
|
|
},
|
|
},
|
|
|
|
compoundVariants: [],
|
|
|
|
defaultVariants: {
|
|
state: 'default',
|
|
size: 'md',
|
|
loading: 'false',
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Switch thumb (sliding circle) variants
|
|
*/
|
|
export const switchThumbVariants = createVariants({
|
|
base: [
|
|
// Layout
|
|
'inline-block rounded-full',
|
|
'bg-background',
|
|
'shadow-sm',
|
|
// Transition with spring-like easing
|
|
'transition-transform duration-200 ease-in-out',
|
|
// Transform origin for smooth animation
|
|
'will-change-transform',
|
|
].join(' '),
|
|
|
|
variants: {
|
|
size: {
|
|
sm: 'h-4 w-4 data-[state=unchecked]:translate-x-0.5 data-[state=checked]:translate-x-[18px]',
|
|
md: 'h-5 w-5 data-[state=unchecked]:translate-x-0.5 data-[state=checked]:translate-x-[22px]',
|
|
lg: 'h-6 w-6 data-[state=unchecked]:translate-x-0.5 data-[state=checked]:translate-x-[26px]',
|
|
},
|
|
},
|
|
|
|
compoundVariants: [],
|
|
|
|
defaultVariants: {
|
|
size: 'md',
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Loading spinner variants
|
|
*/
|
|
export const switchSpinnerVariants = createVariants({
|
|
base: [
|
|
'absolute inset-0 flex items-center justify-center',
|
|
'pointer-events-none',
|
|
].join(' '),
|
|
|
|
variants: {
|
|
size: {
|
|
sm: '',
|
|
md: '',
|
|
lg: '',
|
|
},
|
|
},
|
|
|
|
compoundVariants: [],
|
|
|
|
defaultVariants: {
|
|
size: 'md',
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Label variants
|
|
*/
|
|
export const switchLabelVariants = createVariants({
|
|
base: [
|
|
// Typography
|
|
'text-sm font-medium text-foreground',
|
|
// Layout
|
|
'select-none',
|
|
// Cursor
|
|
'cursor-pointer',
|
|
// Disabled
|
|
'peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
|
|
].join(' '),
|
|
|
|
variants: {
|
|
size: {
|
|
sm: 'text-xs',
|
|
md: 'text-sm',
|
|
lg: 'text-base',
|
|
},
|
|
},
|
|
|
|
compoundVariants: [],
|
|
|
|
defaultVariants: {
|
|
size: 'md',
|
|
},
|
|
});
|