Skip to content

Commit 456a8ba

Browse files
feat: add new variants and create docs for tokens
1 parent 5aa0b4d commit 456a8ba

File tree

11 files changed

+1325
-80
lines changed

11 files changed

+1325
-80
lines changed

.claude/rules/semantic-tokens.md

Lines changed: 867 additions & 0 deletions
Large diffs are not rendered by default.

apps/sim/app/playground/page.tsx

Lines changed: 163 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
AvatarFallback,
99
AvatarImage,
1010
Badge,
11+
Banner,
1112
Breadcrumb,
1213
BubbleChatClose,
1314
BubbleChatPreview,
@@ -30,6 +31,7 @@ import {
3031
Eye,
3132
FolderCode,
3233
FolderPlus,
34+
FormField,
3335
Hand,
3436
HexSimple,
3537
Input,
@@ -64,6 +66,7 @@ import {
6466
PopoverTrigger,
6567
Redo,
6668
Rocket,
69+
Skeleton,
6770
Slider,
6871
SModal,
6972
SModalContent,
@@ -228,6 +231,9 @@ export default function PlaygroundPage() {
228231
<VariantRow label='ghost'>
229232
<Button variant='ghost'>Ghost</Button>
230233
</VariantRow>
234+
<VariantRow label='subtle'>
235+
<Button variant='subtle'>Subtle</Button>
236+
</VariantRow>
231237
<VariantRow label='ghost-secondary'>
232238
<Button variant='ghost-secondary'>Ghost Secondary</Button>
233239
</VariantRow>
@@ -337,6 +343,9 @@ export default function PlaygroundPage() {
337343
<VariantRow label='gray'>
338344
<Badge variant='gray'>Gray</Badge>
339345
</VariantRow>
346+
<VariantRow label='pink'>
347+
<Badge variant='pink'>Pink</Badge>
348+
</VariantRow>
340349
<VariantRow label='gray-secondary'>
341350
<Badge variant='gray-secondary'>Gray Secondary</Badge>
342351
</VariantRow>
@@ -352,6 +361,18 @@ export default function PlaygroundPage() {
352361
<VariantRow label='default'>
353362
<Input placeholder='Enter text...' className='max-w-xs' />
354363
</VariantRow>
364+
<VariantRow label='error'>
365+
<Input variant='error' placeholder='Invalid value' className='max-w-xs' />
366+
</VariantRow>
367+
<VariantRow label='ghost'>
368+
<Input variant='ghost' placeholder='Inline edit...' className='max-w-xs' />
369+
</VariantRow>
370+
<VariantRow label='size sm'>
371+
<Input size='sm' placeholder='Small input' className='max-w-xs' />
372+
</VariantRow>
373+
<VariantRow label='size md'>
374+
<Input size='md' placeholder='Medium input' className='max-w-xs' />
375+
</VariantRow>
355376
<VariantRow label='disabled'>
356377
<Input placeholder='Disabled' disabled className='max-w-xs' />
357378
</VariantRow>
@@ -417,15 +438,85 @@ export default function PlaygroundPage() {
417438

418439
{/* Textarea */}
419440
<Section title='Textarea'>
420-
<Textarea placeholder='Enter your message...' className='max-w-md' rows={4} />
441+
<VariantRow label='default'>
442+
<Textarea placeholder='Enter your message...' className='max-w-md' rows={3} />
443+
</VariantRow>
444+
<VariantRow label='error'>
445+
<Textarea
446+
variant='error'
447+
placeholder='Invalid content'
448+
className='max-w-md'
449+
rows={3}
450+
/>
451+
</VariantRow>
452+
<VariantRow label='ghost'>
453+
<Textarea
454+
variant='ghost'
455+
placeholder='Inline edit...'
456+
className='max-w-md'
457+
rows={3}
458+
/>
459+
</VariantRow>
421460
</Section>
422461

423462
{/* Label */}
424463
<Section title='Label'>
425-
<div className='flex flex-col gap-2'>
426-
<Label htmlFor='demo-input'>Label Text</Label>
427-
<Input id='demo-input' placeholder='Input with label' className='max-w-xs' />
428-
</div>
464+
<VariantRow label='default'>
465+
<Label>Label Text</Label>
466+
</VariantRow>
467+
<VariantRow label='required'>
468+
<Label required>Required Field</Label>
469+
</VariantRow>
470+
<VariantRow label='size sm'>
471+
<Label size='sm'>Small Label</Label>
472+
</VariantRow>
473+
<VariantRow label='size md'>
474+
<Label size='md'>Medium Label</Label>
475+
</VariantRow>
476+
<VariantRow label='size lg'>
477+
<Label size='lg'>Large Label</Label>
478+
</VariantRow>
479+
<VariantRow label='with input'>
480+
<div className='flex flex-col gap-2'>
481+
<Label htmlFor='demo-input' required>
482+
Email Address
483+
</Label>
484+
<Input id='demo-input' placeholder='Input with label' className='max-w-xs' />
485+
</div>
486+
</VariantRow>
487+
</Section>
488+
489+
{/* FormField */}
490+
<Section title='FormField'>
491+
<VariantRow label='default'>
492+
<FormField label='Username' htmlFor='ff-user'>
493+
<Input id='ff-user' placeholder='Enter username' className='max-w-xs' />
494+
</FormField>
495+
</VariantRow>
496+
<VariantRow label='optional'>
497+
<FormField label='Bio' htmlFor='ff-bio' optional>
498+
<Textarea
499+
id='ff-bio'
500+
placeholder='Tell us about yourself'
501+
className='max-w-xs'
502+
rows={2}
503+
/>
504+
</FormField>
505+
</VariantRow>
506+
<VariantRow label='with error'>
507+
<FormField
508+
label='Email'
509+
htmlFor='ff-email'
510+
error='Please enter a valid email address'
511+
>
512+
<Input
513+
id='ff-email'
514+
variant='error'
515+
placeholder='Enter email'
516+
className='max-w-xs'
517+
/>
518+
</FormField>
519+
</VariantRow>
429520
</Section>
430521

431522
{/* Switch */}
@@ -436,6 +527,22 @@ export default function PlaygroundPage() {
436527
{switchValue ? 'On' : 'Off'}
437528
</span>
438529
</VariantRow>
530+
<VariantRow label='size sm'>
531+
<Switch size='sm' />
532+
<span className='text-[var(--text-secondary)] text-sm'>Small</span>
533+
</VariantRow>
534+
<VariantRow label='size md'>
535+
<Switch size='md' />
536+
<span className='text-[var(--text-secondary)] text-sm'>Medium (default)</span>
537+
</VariantRow>
538+
<VariantRow label='size lg'>
539+
<Switch size='lg' />
540+
<span className='text-[var(--text-secondary)] text-sm'>Large</span>
541+
</VariantRow>
542+
<VariantRow label='disabled'>
543+
<Switch disabled />
544+
<Switch disabled checked />
545+
</VariantRow>
439546
</Section>
440547

441548
{/* Checkbox */}
@@ -472,6 +579,18 @@ export default function PlaygroundPage() {
472579
</div>
473580
<span className='text-[var(--text-secondary)] text-sm'>{sliderValue[0]}</span>
474581
</VariantRow>
582+
<VariantRow label='size sm'>
583+
<div className='w-48'>
584+
<Slider size='sm' value={[50]} max={100} step={1} />
585+
</div>
586+
<span className='text-[var(--text-secondary)] text-sm'>Small</span>
587+
</VariantRow>
588+
<VariantRow label='size lg'>
589+
<div className='w-48'>
590+
<Slider size='lg' value={[70]} max={100} step={1} />
591+
</div>
592+
<span className='text-[var(--text-secondary)] text-sm'>Large</span>
593+
</VariantRow>
475594
<VariantRow label='disabled'>
476595
<div className='w-48'>
477596
<Slider value={[30]} disabled max={100} step={1} />
@@ -995,6 +1114,45 @@ export default function PlaygroundPage() {
9951114
</VariantRow>
9961115
</Section>
9971116

1117+
{/* Banner */}
1118+
<Section title='Banner'>
1119+
<Banner text='Default informational banner' variant='default' />
1120+
<Banner text='Error: Something went wrong' variant='destructive' />
1121+
<Banner text='Warning: Please review before continuing' variant='warning' />
1122+
<Banner text='Info: A new version is available' variant='info' />
1123+
<Banner text='Success: Your changes have been saved' variant='success' />
1124+
<Banner
1125+
text='Banner with action'
1126+
variant='info'
1127+
actionLabel='Update now'
1128+
onAction={() => {}}
1129+
/>
1130+
</Section>
1131+
1132+
{/* Skeleton */}
1133+
<Section title='Skeleton'>
1134+
<VariantRow label='line (default)'>
1135+
<Skeleton className='h-4 w-48' />
1136+
<Skeleton className='h-4 w-32' />
1137+
</VariantRow>
1138+
<VariantRow label='circle'>
1139+
<Skeleton variant='circle' className='h-8 w-8' />
1140+
<Skeleton variant='circle' className='h-10 w-10' />
1141+
</VariantRow>
1142+
<VariantRow label='rectangle'>
1143+
<Skeleton variant='rectangle' className='h-20 w-32' />
1144+
</VariantRow>
1145+
<VariantRow label='composition'>
1146+
<div className='flex items-center gap-3'>
1147+
<Skeleton variant='circle' className='h-10 w-10' />
1148+
<div className='space-y-2'>
1149+
<Skeleton className='h-4 w-32' />
1150+
<Skeleton className='h-3 w-48' />
1151+
</div>
1152+
</div>
1153+
</VariantRow>
1154+
</Section>
1155+
9981156
{/* Icons */}
9991157
<Section title='Icons'>
10001158
<div className='grid grid-cols-6 gap-4 sm:grid-cols-8 md:grid-cols-10'>

apps/sim/components/emcn/components/banner/banner.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,25 @@ import { cva, type VariantProps } from 'class-variance-authority'
55
import { Button, type ButtonProps } from '@/components/emcn/components/button/button'
66
import { cn } from '@/lib/core/utils/cn'
77

8+
/**
9+
* Variant styles for the Banner component.
10+
*
11+
* @remarks
12+
* Supports semantic variants:
13+
* - **default** - Neutral surface background for informational banners
14+
* - **destructive** - Red background for error/danger messages
15+
* - **warning** - Amber/orange background for caution messages
16+
* - **info** - Blue background for informational highlights
17+
* - **success** - Green background for positive confirmations
18+
*/
819
const bannerVariants = cva('shrink-0 px-6 py-2.5', {
920
variants: {
1021
variant: {
1122
default: 'bg-[var(--surface-active)]',
1223
destructive: 'bg-red-50 dark:bg-red-950/30',
24+
warning: 'bg-amber-50 dark:bg-amber-950/30',
25+
info: 'bg-blue-50 dark:bg-blue-950/30',
26+
success: 'bg-green-50 dark:bg-green-950/30',
1327
},
1428
},
1529
defaultVariants: {

apps/sim/components/emcn/components/combobox/combobox.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export interface ComboboxProps
9797
/** Additional input props for editable mode */
9898
inputProps?: Omit<
9999
React.InputHTMLAttributes<HTMLInputElement>,
100-
'value' | 'onChange' | 'disabled' | 'placeholder'
100+
'value' | 'onChange' | 'disabled' | 'placeholder' | 'size'
101101
>
102102
/** Ref for the input element in editable mode */
103103
inputRef?: React.RefObject<HTMLInputElement | null>

apps/sim/components/emcn/components/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export { Expandable, ExpandableContent } from './expandable/expandable'
6060
export { FormField, type FormFieldProps } from './form-field/form-field'
6161
export { Input, type InputProps, inputVariants } from './input/input'
6262
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from './input-otp/input-otp'
63-
export { Label } from './label/label'
63+
export { Label, type LabelProps, labelVariants } from './label/label'
6464
export {
6565
MODAL_SIZES,
6666
Modal,
@@ -122,9 +122,9 @@ export {
122122
SModalTabsTrigger,
123123
SModalTrigger,
124124
} from './s-modal/s-modal'
125-
export { Skeleton } from './skeleton/skeleton'
125+
export { Skeleton, type SkeletonProps, skeletonVariants } from './skeleton/skeleton'
126126
export { Slider, type SliderProps } from './slider/slider'
127-
export { Switch } from './switch/switch'
127+
export { Switch, type SwitchProps, switchThumbVariants, switchVariants } from './switch/switch'
128128
export {
129129
Table,
130130
TableBody,
@@ -145,7 +145,7 @@ export {
145145
tagInputVariants,
146146
tagVariants,
147147
} from './tag-input/tag-input'
148-
export { Textarea } from './textarea/textarea'
148+
export { Textarea, type TextareaProps, textareaVariants } from './textarea/textarea'
149149
export { TimePicker, type TimePickerProps, timePickerVariants } from './time-picker/time-picker'
150150
export { CountdownRing } from './toast/countdown-ring'
151151
export { ToastProvider, toast, useToast } from './toast/toast'

apps/sim/components/emcn/components/input/input.tsx

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,33 @@ import { cn } from '@/lib/core/utils/cn'
2323

2424
/**
2525
* Variant styles for the Input component.
26-
* Currently supports a 'default' variant.
26+
*
27+
* @remarks
28+
* Supports visual variants for different contexts:
29+
* - **default** - Standard bordered input with surface background
30+
* - **error** - Red-tinted border and focus ring for validation errors
31+
* - **ghost** - Transparent background, border only on focus/hover
2732
*/
2833
const inputVariants = cva(
29-
'flex w-full touch-manipulation rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-1.5 font-medium font-sans text-sm text-[var(--text-primary)] transition-colors placeholder:text-[var(--text-muted)] outline-none focus-visible:border-[var(--text-muted)] disabled:cursor-not-allowed disabled:opacity-50',
34+
'flex w-full touch-manipulation rounded-sm border font-medium font-sans text-[var(--text-primary)] transition-colors placeholder:text-[var(--text-muted)] outline-none disabled:cursor-not-allowed disabled:opacity-50',
3035
{
3136
variants: {
3237
variant: {
33-
default: '',
38+
default:
39+
'border-[var(--border-1)] bg-[var(--surface-5)] focus-visible:border-[var(--text-muted)]',
40+
error:
41+
'border-[var(--text-error)] bg-[var(--surface-5)] focus-visible:border-[var(--text-error)]',
42+
ghost:
43+
'border-transparent bg-transparent hover-hover:bg-[var(--surface-4)] focus-visible:border-[var(--border-1)] focus-visible:bg-[var(--surface-5)]',
44+
},
45+
size: {
46+
sm: 'px-1.5 py-1 text-caption',
47+
md: 'px-2 py-1.5 text-sm',
3448
},
3549
},
3650
defaultVariants: {
3751
variant: 'default',
52+
size: 'md',
3853
},
3954
}
4055
)
@@ -44,19 +59,31 @@ const inputVariants = cva(
4459
* Extends native input attributes with variant support.
4560
*/
4661
export interface InputProps
47-
extends React.InputHTMLAttributes<HTMLInputElement>,
62+
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>,
4863
VariantProps<typeof inputVariants> {}
4964

5065
/**
5166
* Minimal input component matching the textarea styling.
5267
* Uses consistent emcn design patterns.
68+
*
69+
* @example
70+
* ```tsx
71+
* // Error state
72+
* <Input variant="error" placeholder="Invalid value" />
73+
*
74+
* // Ghost variant (transparent until focused)
75+
* <Input variant="ghost" placeholder="Inline edit..." />
76+
*
77+
* // Small size
78+
* <Input size="sm" placeholder="Compact input" />
79+
* ```
5380
*/
5481
const Input = React.forwardRef<HTMLInputElement, InputProps>(
55-
({ className, variant, type = 'text', ...props }, ref) => {
82+
({ className, variant, size, type = 'text', ...props }, ref) => {
5683
return (
5784
<input
5885
type={type}
59-
className={cn(inputVariants({ variant }), className)}
86+
className={cn(inputVariants({ variant, size }), className)}
6087
ref={ref}
6188
{...props}
6289
/>

0 commit comments

Comments
 (0)