Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
867 changes: 867 additions & 0 deletions .claude/rules/semantic-tokens.md

Large diffs are not rendered by default.

168 changes: 163 additions & 5 deletions apps/sim/app/playground/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
AvatarFallback,
AvatarImage,
Badge,
Banner,
Breadcrumb,
BubbleChatClose,
BubbleChatPreview,
Expand All @@ -30,6 +31,7 @@ import {
Eye,
FolderCode,
FolderPlus,
FormField,
Hand,
HexSimple,
Input,
Expand Down Expand Up @@ -64,6 +66,7 @@ import {
PopoverTrigger,
Redo,
Rocket,
Skeleton,
Slider,
SModal,
SModalContent,
Expand Down Expand Up @@ -228,6 +231,9 @@ export default function PlaygroundPage() {
<VariantRow label='ghost'>
<Button variant='ghost'>Ghost</Button>
</VariantRow>
<VariantRow label='subtle'>
<Button variant='subtle'>Subtle</Button>
</VariantRow>
<VariantRow label='ghost-secondary'>
<Button variant='ghost-secondary'>Ghost Secondary</Button>
</VariantRow>
Expand Down Expand Up @@ -337,6 +343,9 @@ export default function PlaygroundPage() {
<VariantRow label='gray'>
<Badge variant='gray'>Gray</Badge>
</VariantRow>
<VariantRow label='pink'>
<Badge variant='pink'>Pink</Badge>
</VariantRow>
<VariantRow label='gray-secondary'>
<Badge variant='gray-secondary'>Gray Secondary</Badge>
</VariantRow>
Expand All @@ -352,6 +361,18 @@ export default function PlaygroundPage() {
<VariantRow label='default'>
<Input placeholder='Enter text...' className='max-w-xs' />
</VariantRow>
<VariantRow label='error'>
<Input variant='error' placeholder='Invalid value' className='max-w-xs' />
</VariantRow>
<VariantRow label='ghost'>
<Input variant='ghost' placeholder='Inline edit...' className='max-w-xs' />
</VariantRow>
<VariantRow label='size sm'>
<Input size='sm' placeholder='Small input' className='max-w-xs' />
</VariantRow>
<VariantRow label='size md'>
<Input size='md' placeholder='Medium input' className='max-w-xs' />
</VariantRow>
<VariantRow label='disabled'>
<Input placeholder='Disabled' disabled className='max-w-xs' />
</VariantRow>
Expand Down Expand Up @@ -417,15 +438,85 @@ export default function PlaygroundPage() {

{/* Textarea */}
<Section title='Textarea'>
<Textarea placeholder='Enter your message...' className='max-w-md' rows={4} />
<VariantRow label='default'>
<Textarea placeholder='Enter your message...' className='max-w-md' rows={3} />
</VariantRow>
<VariantRow label='error'>
<Textarea
variant='error'
placeholder='Invalid content'
className='max-w-md'
rows={3}
/>
</VariantRow>
<VariantRow label='ghost'>
<Textarea
variant='ghost'
placeholder='Inline edit...'
className='max-w-md'
rows={3}
/>
</VariantRow>
</Section>

{/* Label */}
<Section title='Label'>
<div className='flex flex-col gap-2'>
<Label htmlFor='demo-input'>Label Text</Label>
<Input id='demo-input' placeholder='Input with label' className='max-w-xs' />
</div>
<VariantRow label='default'>
<Label>Label Text</Label>
</VariantRow>
<VariantRow label='required'>
<Label required>Required Field</Label>
</VariantRow>
<VariantRow label='size sm'>
<Label size='sm'>Small Label</Label>
</VariantRow>
<VariantRow label='size md'>
<Label size='md'>Medium Label</Label>
</VariantRow>
<VariantRow label='size lg'>
<Label size='lg'>Large Label</Label>
</VariantRow>
<VariantRow label='with input'>
<div className='flex flex-col gap-2'>
<Label htmlFor='demo-input' required>
Email Address
</Label>
<Input id='demo-input' placeholder='Input with label' className='max-w-xs' />
</div>
</VariantRow>
</Section>

{/* FormField */}
<Section title='FormField'>
<VariantRow label='default'>
<FormField label='Username' htmlFor='ff-user'>
<Input id='ff-user' placeholder='Enter username' className='max-w-xs' />
</FormField>
</VariantRow>
<VariantRow label='optional'>
<FormField label='Bio' htmlFor='ff-bio' optional>
<Textarea
id='ff-bio'
placeholder='Tell us about yourself'
className='max-w-xs'
rows={2}
/>
</FormField>
</VariantRow>
<VariantRow label='with error'>
<FormField
label='Email'
htmlFor='ff-email'
error='Please enter a valid email address'
>
<Input
id='ff-email'
variant='error'
placeholder='Enter email'
className='max-w-xs'
/>
</FormField>
</VariantRow>
</Section>

{/* Switch */}
Expand All @@ -436,6 +527,22 @@ export default function PlaygroundPage() {
{switchValue ? 'On' : 'Off'}
</span>
</VariantRow>
<VariantRow label='size sm'>
<Switch size='sm' />
<span className='text-[var(--text-secondary)] text-sm'>Small</span>
</VariantRow>
<VariantRow label='size md'>
<Switch size='md' />
<span className='text-[var(--text-secondary)] text-sm'>Medium (default)</span>
</VariantRow>
<VariantRow label='size lg'>
<Switch size='lg' />
<span className='text-[var(--text-secondary)] text-sm'>Large</span>
</VariantRow>
<VariantRow label='disabled'>
<Switch disabled />
<Switch disabled checked />
</VariantRow>
</Section>

{/* Checkbox */}
Expand Down Expand Up @@ -472,6 +579,18 @@ export default function PlaygroundPage() {
</div>
<span className='text-[var(--text-secondary)] text-sm'>{sliderValue[0]}</span>
</VariantRow>
<VariantRow label='size sm'>
<div className='w-48'>
<Slider size='sm' value={[50]} max={100} step={1} />
</div>
<span className='text-[var(--text-secondary)] text-sm'>Small</span>
</VariantRow>
<VariantRow label='size lg'>
<div className='w-48'>
<Slider size='lg' value={[70]} max={100} step={1} />
</div>
<span className='text-[var(--text-secondary)] text-sm'>Large</span>
</VariantRow>
<VariantRow label='disabled'>
<div className='w-48'>
<Slider value={[30]} disabled max={100} step={1} />
Expand Down Expand Up @@ -995,6 +1114,45 @@ export default function PlaygroundPage() {
</VariantRow>
</Section>

{/* Banner */}
<Section title='Banner'>
<Banner text='Default informational banner' variant='default' />
<Banner text='Error: Something went wrong' variant='destructive' />
<Banner text='Warning: Please review before continuing' variant='warning' />
<Banner text='Info: A new version is available' variant='info' />
<Banner text='Success: Your changes have been saved' variant='success' />
<Banner
text='Banner with action'
variant='info'
actionLabel='Update now'
onAction={() => {}}
/>
</Section>

{/* Skeleton */}
<Section title='Skeleton'>
<VariantRow label='line (default)'>
<Skeleton className='h-4 w-48' />
<Skeleton className='h-4 w-32' />
</VariantRow>
<VariantRow label='circle'>
<Skeleton variant='circle' className='h-8 w-8' />
<Skeleton variant='circle' className='h-10 w-10' />
</VariantRow>
<VariantRow label='rectangle'>
<Skeleton variant='rectangle' className='h-20 w-32' />
</VariantRow>
<VariantRow label='composition'>
<div className='flex items-center gap-3'>
<Skeleton variant='circle' className='h-10 w-10' />
<div className='space-y-2'>
<Skeleton className='h-4 w-32' />
<Skeleton className='h-3 w-48' />
</div>
</div>
</VariantRow>
</Section>

{/* Icons */}
<Section title='Icons'>
<div className='grid grid-cols-6 gap-4 sm:grid-cols-8 md:grid-cols-10'>
Expand Down
16 changes: 16 additions & 0 deletions apps/sim/components/emcn/components/banner/banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,27 @@ import { cva, type VariantProps } from 'class-variance-authority'
import { Button, type ButtonProps } from '@/components/emcn/components/button/button'
import { cn } from '@/lib/core/utils/cn'

/**
* Variant styles for the Banner component.
*
* @remarks
* Supports semantic variants:
* - **default** - Neutral surface background for informational banners
* - **destructive** - Red background for error/danger messages
* - **warning** - Amber/orange background for caution messages
* - **info** - Blue background for informational highlights
* - **success** - Green background for positive confirmations
*/
// TODO: Replace raw Tailwind palette colors with semantic tokens once
// muted banner background tokens (e.g. --banner-destructive-bg) are added to globals.css.
const bannerVariants = cva('shrink-0 px-6 py-2.5', {
variants: {
variant: {
default: 'bg-[var(--surface-active)]',
destructive: 'bg-red-50 dark:bg-red-950/30',
warning: 'bg-amber-50 dark:bg-amber-950/30',
info: 'bg-blue-50 dark:bg-blue-950/30',
success: 'bg-green-50 dark:bg-green-950/30',
},
},
defaultVariants: {
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/components/emcn/components/combobox/combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export interface ComboboxProps
/** Additional input props for editable mode */
inputProps?: Omit<
React.InputHTMLAttributes<HTMLInputElement>,
'value' | 'onChange' | 'disabled' | 'placeholder'
'value' | 'onChange' | 'disabled' | 'placeholder' | 'size'
>
/** Ref for the input element in editable mode */
inputRef?: React.RefObject<HTMLInputElement | null>
Expand Down
8 changes: 4 additions & 4 deletions apps/sim/components/emcn/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export { Expandable, ExpandableContent } from './expandable/expandable'
export { FormField, type FormFieldProps } from './form-field/form-field'
export { Input, type InputProps, inputVariants } from './input/input'
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from './input-otp/input-otp'
export { Label } from './label/label'
export { Label, type LabelProps, labelVariants } from './label/label'
export {
MODAL_SIZES,
Modal,
Expand Down Expand Up @@ -122,9 +122,9 @@ export {
SModalTabsTrigger,
SModalTrigger,
} from './s-modal/s-modal'
export { Skeleton } from './skeleton/skeleton'
export { Skeleton, type SkeletonProps, skeletonVariants } from './skeleton/skeleton'
export { Slider, type SliderProps } from './slider/slider'
export { Switch } from './switch/switch'
export { Switch, type SwitchProps, switchThumbVariants, switchVariants } from './switch/switch'
export {
Table,
TableBody,
Expand All @@ -145,7 +145,7 @@ export {
tagInputVariants,
tagVariants,
} from './tag-input/tag-input'
export { Textarea } from './textarea/textarea'
export { Textarea, type TextareaProps, textareaVariants } from './textarea/textarea'
export { TimePicker, type TimePickerProps, timePickerVariants } from './time-picker/time-picker'
export { CountdownRing } from './toast/countdown-ring'
export { ToastProvider, toast, useToast } from './toast/toast'
Expand Down
39 changes: 33 additions & 6 deletions apps/sim/components/emcn/components/input/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,33 @@ import { cn } from '@/lib/core/utils/cn'

/**
* Variant styles for the Input component.
* Currently supports a 'default' variant.
*
* @remarks
* Supports visual variants for different contexts:
* - **default** - Standard bordered input with surface background
* - **error** - Red-tinted border and focus ring for validation errors
* - **ghost** - Transparent background, border only on focus/hover
*/
const inputVariants = cva(
'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',
'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',
{
variants: {
variant: {
default: '',
default:
'border-[var(--border-1)] bg-[var(--surface-5)] focus-visible:border-[var(--text-muted)]',
error:
'border-[var(--text-error)] bg-[var(--surface-5)] focus-visible:border-[var(--text-error)]',
ghost:
'border-transparent bg-transparent hover-hover:bg-[var(--surface-4)] focus-visible:border-[var(--border-1)] focus-visible:bg-[var(--surface-5)]',
},
size: {
sm: 'px-1.5 py-1 text-caption',
md: 'px-2 py-1.5 text-sm',
},
},
defaultVariants: {
variant: 'default',
size: 'md',
},
}
)
Expand All @@ -44,19 +59,31 @@ const inputVariants = cva(
* Extends native input attributes with variant support.
*/
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement>,
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>,
VariantProps<typeof inputVariants> {}

/**
* Minimal input component matching the textarea styling.
* Uses consistent emcn design patterns.
*
* @example
* ```tsx
* // Error state
* <Input variant="error" placeholder="Invalid value" />
*
* // Ghost variant (transparent until focused)
* <Input variant="ghost" placeholder="Inline edit..." />
*
* // Small size
* <Input size="sm" placeholder="Compact input" />
* ```
*/
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, variant, type = 'text', ...props }, ref) => {
({ className, variant, size, type = 'text', ...props }, ref) => {
return (
<input
type={type}
className={cn(inputVariants({ variant }), className)}
className={cn(inputVariants({ variant, size }), className)}
ref={ref}
{...props}
/>
Expand Down
Loading
Loading