Files
simstudioai--sim/apps/sim/app/playground/page.tsx
T
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

1076 lines
40 KiB
TypeScript

'use client'
import { useState, useSyncExternalStore } from 'react'
import {
Avatar,
AvatarFallback,
AvatarImage,
Badge,
BubbleChatClose,
BubbleChatPreview,
Button,
ButtonGroup,
ButtonGroupItem,
Checkbox,
ChevronDown,
ChipDatePicker,
Code,
Combobox,
Connections,
Cursor,
DocumentAttachment,
Download,
Duplicate,
Expand,
Eye,
FolderCode,
FolderPlus,
Hand,
HexSimple,
Input,
Key as KeyIcon,
Label,
Layout,
Library,
Loader,
Modal,
ModalBody,
ModalContent,
ModalDescription,
ModalFooter,
ModalHeader,
ModalTabs,
ModalTabsContent,
ModalTabsList,
ModalTabsTrigger,
ModalTrigger,
MoreHorizontal,
NoWrap,
PanelLeft,
Play,
PlayOutline,
Popover,
PopoverBackButton,
PopoverContent,
PopoverFolder,
PopoverItem,
PopoverScrollArea,
PopoverSearch,
PopoverSection,
PopoverTrigger,
Redo,
Rocket,
Slider,
Switch,
Table,
TableBody,
TableCaption,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
TagInput,
type TagItem,
Textarea,
TimePicker,
ToastProvider,
Tooltip,
Trash,
Trash2,
toast,
Undo,
Wrap,
ZoomIn,
ZoomOut,
} from '@sim/emcn'
import { ArrowLeft, Folder, Moon, Sun } from 'lucide-react'
import { notFound, useRouter } from 'next/navigation'
import { env, isTruthy } from '@/lib/core/config/env'
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
<section className='space-y-4'>
<h2 className='border-[var(--border)] border-b pb-2 font-medium text-[var(--text-primary)] text-lg'>
{title}
</h2>
<div className='space-y-4'>{children}</div>
</section>
)
}
function VariantRow({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div className='flex items-center gap-4'>
<span className='w-32 shrink-0 text-[var(--text-secondary)] text-sm'>{label}</span>
<div className='flex flex-wrap items-center gap-2'>{children}</div>
</div>
)
}
const SAMPLE_CODE = `function greet(name) {
console.log("Hello, " + name);
return { success: true };
}`
const SAMPLE_PYTHON = `def greet(name):
print(f"Hello, {name}")
return {"success": True}`
const COMBOBOX_OPTIONS = [
{ label: 'Option 1', value: 'opt1' },
{ label: 'Option 2', value: 'opt2' },
{ label: 'Option 3', value: 'opt3' },
]
const DARK_MODE_EVENT = 'playground:dark-mode-change'
const subscribeToDarkMode = (onStoreChange: () => void) => {
window.addEventListener(DARK_MODE_EVENT, onStoreChange)
return () => window.removeEventListener(DARK_MODE_EVENT, onStoreChange)
}
const getDarkModeSnapshot = () => document.documentElement.classList.contains('dark')
const getServerDarkModeSnapshot = () => false
export default function PlaygroundPage() {
const router = useRouter()
const [comboboxValue, setComboboxValue] = useState('')
const [switchValue, setSwitchValue] = useState(false)
const [checkboxValue, setCheckboxValue] = useState(false)
const [sliderValue, setSliderValue] = useState([50])
const [timeValue, setTimeValue] = useState('09:30')
const isDarkMode = useSyncExternalStore(
subscribeToDarkMode,
getDarkModeSnapshot,
getServerDarkModeSnapshot
)
const [buttonGroupValue, setButtonGroupValue] = useState('curl')
const [dateValue, setDateValue] = useState('')
const [dateRangeStart, setDateRangeStart] = useState('')
const [dateRangeEnd, setDateRangeEnd] = useState('')
const [tagItems, setTagItems] = useState<TagItem[]>([
{ value: 'user@example.com', isValid: true },
{ value: 'invalid-email', isValid: false, error: 'Invalid email format' },
])
const toggleDarkMode = () => {
document.documentElement.classList.toggle('dark')
window.dispatchEvent(new Event(DARK_MODE_EVENT))
}
if (!isTruthy(env.NEXT_PUBLIC_ENABLE_PLAYGROUND)) {
notFound()
}
return (
<ToastProvider>
<Tooltip.Provider>
<div className='relative min-h-screen bg-[var(--bg)] p-8'>
<div className='absolute top-8 left-8 flex items-center gap-2'>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button variant='ghost' onClick={() => router.back()} className='size-8 p-0'>
<ArrowLeft className='size-4' />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>Go back</Tooltip.Content>
</Tooltip.Root>
</div>
<div className='absolute top-8 right-8'>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button variant='default' onClick={toggleDarkMode} className='size-8 p-0'>
{isDarkMode ? <Sun className='size-4' /> : <Moon className='size-4' />}
</Button>
</Tooltip.Trigger>
<Tooltip.Content>{isDarkMode ? 'Light mode' : 'Dark mode'}</Tooltip.Content>
</Tooltip.Root>
</div>
<div className='mx-auto max-w-4xl space-y-12'>
<div>
<h1 className='font-semibold text-2xl text-[var(--text-primary)]'>
EMCN Component Playground
</h1>
<p className='mt-2 text-[var(--text-secondary)]'>
All emcn UI components and their variants
</p>
</div>
{/* Toast */}
<Section title='Toast'>
<VariantRow label='default'>
<Button variant='default' onClick={() => toast({ message: 'Workflow saved' })}>
Show
</Button>
</VariantRow>
<VariantRow label='info'>
<Button variant='default' onClick={() => toast.info('Sync in progress')}>
Show
</Button>
</VariantRow>
<VariantRow label='success'>
<Button variant='default' onClick={() => toast.success('Imported 1,240 rows')}>
Show
</Button>
</VariantRow>
<VariantRow label='warning'>
<Button
variant='default'
onClick={() =>
toast.warning('Usage nearing limit', {
description: '$4.20 used of $5.00 limit.',
})
}
>
Show
</Button>
</VariantRow>
<VariantRow label='error'>
<Button
variant='default'
onClick={() =>
toast.error('Jira 1: Read Issue failed', {
description: 'Domain and account are required.',
})
}
>
Show
</Button>
</VariantRow>
<VariantRow label='error + action'>
<Button
variant='default'
onClick={() =>
toast.error('Workflow Validation', {
description:
'Usage limit exceeded: $0.00 used of $5.00 limit. Please upgrade your plan to continue running this workflow.',
action: { label: 'Fix in Chat', onClick: () => {} },
})
}
>
Show
</Button>
</VariantRow>
<VariantRow label='success + action'>
<Button
variant='default'
onClick={() =>
toast.success('Deployed v3', {
action: { label: 'View deployment', onClick: () => {} },
})
}
>
Show
</Button>
</VariantRow>
<VariantRow label='stacking'>
<Button
variant='default'
onClick={() => {
toast.info('First notification')
toast.warning('Second notification')
toast.error('Third notification')
}}
>
Show 3
</Button>
<Button variant='ghost' onClick={() => toast.dismissAll()}>
Dismiss all
</Button>
</VariantRow>
</Section>
{/* Button */}
<Section title='Button'>
<VariantRow label='default'>
<Button variant='default'>Default</Button>
</VariantRow>
<VariantRow label='active'>
<Button variant='active'>Active</Button>
</VariantRow>
<VariantRow label='3d'>
<Button variant='3d'>3D</Button>
</VariantRow>
<VariantRow label='outline'>
<Button variant='outline'>Outline</Button>
</VariantRow>
<VariantRow label='primary'>
<Button variant='primary'>Primary</Button>
</VariantRow>
<VariantRow label='destructive'>
<Button variant='destructive'>Destructive</Button>
</VariantRow>
<VariantRow label='secondary'>
<Button variant='secondary'>Secondary</Button>
</VariantRow>
<VariantRow label='tertiary'>
<Button variant='tertiary'>Tertiary</Button>
</VariantRow>
<VariantRow label='ghost'>
<Button variant='ghost'>Ghost</Button>
</VariantRow>
<VariantRow label='ghost-secondary'>
<Button variant='ghost-secondary'>Ghost Secondary</Button>
</VariantRow>
<VariantRow label='disabled'>
<Button disabled>Disabled</Button>
</VariantRow>
<VariantRow label='size sm'>
<Button size='sm'>Small</Button>
<Button size='sm' variant='primary'>
Small Primary
</Button>
</VariantRow>
<VariantRow label='size md'>
<Button size='md'>Medium</Button>
<Button size='md' variant='primary'>
Medium Primary
</Button>
</VariantRow>
</Section>
{/* ButtonGroup */}
<Section title='ButtonGroup'>
<VariantRow label='default'>
<ButtonGroup value={buttonGroupValue} onValueChange={setButtonGroupValue}>
<ButtonGroupItem value='curl'>cURL</ButtonGroupItem>
<ButtonGroupItem value='python'>Python</ButtonGroupItem>
<ButtonGroupItem value='javascript'>JavaScript</ButtonGroupItem>
</ButtonGroup>
</VariantRow>
<VariantRow label='gap none'>
<ButtonGroup value='opt1' gap='none'>
<ButtonGroupItem value='opt1'>Option 1</ButtonGroupItem>
<ButtonGroupItem value='opt2'>Option 2</ButtonGroupItem>
</ButtonGroup>
</VariantRow>
<VariantRow label='gap sm'>
<ButtonGroup value='opt1' gap='sm'>
<ButtonGroupItem value='opt1'>Option 1</ButtonGroupItem>
<ButtonGroupItem value='opt2'>Option 2</ButtonGroupItem>
</ButtonGroup>
</VariantRow>
<VariantRow label='disabled'>
<ButtonGroup value='opt1' disabled>
<ButtonGroupItem value='opt1'>Option 1</ButtonGroupItem>
<ButtonGroupItem value='opt2'>Option 2</ButtonGroupItem>
</ButtonGroup>
</VariantRow>
<VariantRow label='single item'>
<ButtonGroup value='only'>
<ButtonGroupItem value='only'>Only Option</ButtonGroupItem>
</ButtonGroup>
</VariantRow>
</Section>
{/* Badge */}
<Section title='Badge'>
<VariantRow label='default'>
<Badge variant='default'>Default</Badge>
</VariantRow>
<VariantRow label='outline'>
<Badge variant='outline'>Outline</Badge>
</VariantRow>
<VariantRow label='type'>
<Badge variant='type'>Type</Badge>
</VariantRow>
<VariantRow label='green'>
<Badge variant='green'>Green</Badge>
<Badge variant='green' dot>
With Dot
</Badge>
</VariantRow>
<VariantRow label='red'>
<Badge variant='red'>Red</Badge>
<Badge variant='red' dot>
With Dot
</Badge>
</VariantRow>
<VariantRow label='blue'>
<Badge variant='blue'>Blue</Badge>
<Badge variant='blue' dot>
With Dot
</Badge>
</VariantRow>
<VariantRow label='blue-secondary'>
<Badge variant='blue-secondary'>Blue Secondary</Badge>
</VariantRow>
<VariantRow label='purple'>
<Badge variant='purple'>Purple</Badge>
</VariantRow>
<VariantRow label='orange'>
<Badge variant='orange'>Orange</Badge>
</VariantRow>
<VariantRow label='amber'>
<Badge variant='amber'>Amber</Badge>
</VariantRow>
<VariantRow label='teal'>
<Badge variant='teal'>Teal</Badge>
</VariantRow>
<VariantRow label='cyan'>
<Badge variant='cyan'>Cyan</Badge>
</VariantRow>
<VariantRow label='gray'>
<Badge variant='gray'>Gray</Badge>
</VariantRow>
<VariantRow label='gray-secondary'>
<Badge variant='gray-secondary'>Gray Secondary</Badge>
</VariantRow>
<VariantRow label='sizes'>
<Badge size='sm'>Small</Badge>
<Badge size='md'>Medium</Badge>
<Badge size='lg'>Large</Badge>
</VariantRow>
</Section>
{/* Input */}
<Section title='Input'>
<VariantRow label='default'>
<Input placeholder='Enter text...' className='max-w-xs' />
</VariantRow>
<VariantRow label='disabled'>
<Input placeholder='Disabled' disabled className='max-w-xs' />
</VariantRow>
</Section>
{/* TagInput */}
<Section title='TagInput'>
<VariantRow label='default'>
<div className='w-80'>
<TagInput
items={tagItems}
onAdd={(value) => {
const isValid = value.includes('@') && value.includes('.')
setTagItems((prev) => [...prev, { value, isValid }])
return isValid
}}
onRemove={(_, index) => {
setTagItems((prev) => prev.filter((_, i) => i !== index))
}}
placeholder='Enter emails...'
placeholderWithTags='Add another'
/>
</div>
</VariantRow>
<VariantRow label='secondary variant'>
<div className='w-80'>
<TagInput
items={[
{ value: 'workflow', isValid: true },
{ value: 'automation', isValid: true },
]}
onAdd={() => true}
onRemove={() => {}}
placeholder='Add tags'
placeholderWithTags='Add another'
triggerKeys={['Enter', ',']}
/>
</div>
</VariantRow>
<VariantRow label='disabled'>
<div className='w-80'>
<TagInput
items={[{ value: 'disabled@email.com', isValid: true }]}
onAdd={() => false}
onRemove={() => {}}
placeholder='Disabled input'
disabled
/>
</div>
</VariantRow>
</Section>
{/* Textarea */}
<Section title='Textarea'>
<Textarea placeholder='Enter your message...' className='max-w-md' rows={4} />
</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>
</Section>
{/* Switch */}
<Section title='Switch'>
<VariantRow label='default'>
<Switch checked={switchValue} onCheckedChange={setSwitchValue} />
<span className='text-[var(--text-secondary)] text-sm'>
{switchValue ? 'On' : 'Off'}
</span>
</VariantRow>
</Section>
{/* Checkbox */}
<Section title='Checkbox'>
<VariantRow label='default'>
<Checkbox checked={checkboxValue} onCheckedChange={(c) => setCheckboxValue(!!c)} />
<span className='text-[var(--text-secondary)] text-sm'>
{checkboxValue ? 'Checked' : 'Unchecked'}
</span>
</VariantRow>
<VariantRow label='size sm'>
<Checkbox size='sm' />
<span className='text-[var(--text-secondary)] text-sm'>Small (14px)</span>
</VariantRow>
<VariantRow label='size md'>
<Checkbox size='md' />
<span className='text-[var(--text-secondary)] text-sm'>Medium (16px)</span>
</VariantRow>
<VariantRow label='size lg'>
<Checkbox size='lg' />
<span className='text-[var(--text-secondary)] text-sm'>Large (20px)</span>
</VariantRow>
<VariantRow label='disabled'>
<Checkbox disabled />
<Checkbox disabled checked />
</VariantRow>
</Section>
{/* Slider */}
<Section title='Slider'>
<VariantRow label='default'>
<div className='w-48'>
<Slider value={sliderValue} onValueChange={setSliderValue} max={100} step={1} />
</div>
<span className='text-[var(--text-secondary)] text-sm'>{sliderValue[0]}</span>
</VariantRow>
<VariantRow label='disabled'>
<div className='w-48'>
<Slider value={[30]} disabled max={100} step={1} />
</div>
</VariantRow>
</Section>
{/* Avatar */}
<Section title='Avatar'>
<VariantRow label='sizes'>
<Avatar size='xs'>
<AvatarFallback>XS</AvatarFallback>
</Avatar>
<Avatar size='sm'>
<AvatarFallback>SM</AvatarFallback>
</Avatar>
<Avatar size='md'>
<AvatarFallback>MD</AvatarFallback>
</Avatar>
<Avatar size='lg'>
<AvatarFallback>LG</AvatarFallback>
</Avatar>
</VariantRow>
<VariantRow label='with image'>
<Avatar size='md'>
<AvatarImage src='https://github.com/shadcn.png' alt='User' />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</VariantRow>
<VariantRow label='status online'>
<Avatar size='md' status='online'>
<AvatarFallback>JD</AvatarFallback>
</Avatar>
</VariantRow>
<VariantRow label='status offline'>
<Avatar size='md' status='offline'>
<AvatarFallback>JD</AvatarFallback>
</Avatar>
</VariantRow>
<VariantRow label='status busy'>
<Avatar size='md' status='busy'>
<AvatarFallback>JD</AvatarFallback>
</Avatar>
</VariantRow>
<VariantRow label='status away'>
<Avatar size='md' status='away'>
<AvatarFallback>JD</AvatarFallback>
</Avatar>
</VariantRow>
<VariantRow label='all sizes with status'>
<Avatar size='xs' status='online'>
<AvatarFallback>XS</AvatarFallback>
</Avatar>
<Avatar size='sm' status='online'>
<AvatarFallback>SM</AvatarFallback>
</Avatar>
<Avatar size='md' status='online'>
<AvatarFallback>MD</AvatarFallback>
</Avatar>
<Avatar size='lg' status='online'>
<AvatarFallback>LG</AvatarFallback>
</Avatar>
</VariantRow>
</Section>
{/* Table */}
<Section title='Table'>
<VariantRow label='default'>
<Table className='max-w-md'>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Status</TableHead>
<TableHead>Role</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow className='hover:bg-[var(--surface-2)]'>
<TableCell>Alice</TableCell>
<TableCell>Active</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
<TableRow className='hover:bg-[var(--surface-2)]'>
<TableCell>Bob</TableCell>
<TableCell>Pending</TableCell>
<TableCell>User</TableCell>
</TableRow>
<TableRow className='hover:bg-[var(--surface-2)]'>
<TableCell>Charlie</TableCell>
<TableCell>Active</TableCell>
<TableCell>User</TableCell>
</TableRow>
</TableBody>
</Table>
</VariantRow>
<VariantRow label='with footer'>
<Table className='max-w-md'>
<TableHeader>
<TableRow>
<TableHead>Item</TableHead>
<TableHead className='text-right'>Price</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Product A</TableCell>
<TableCell className='text-right'>$10.00</TableCell>
</TableRow>
<TableRow>
<TableCell>Product B</TableCell>
<TableCell className='text-right'>$20.00</TableCell>
</TableRow>
</TableBody>
<TableFooter>
<TableRow>
<TableCell>Total</TableCell>
<TableCell className='text-right'>$30.00</TableCell>
</TableRow>
</TableFooter>
</Table>
</VariantRow>
<VariantRow label='with caption'>
<Table className='max-w-md'>
<TableCaption>A list of team members</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Department</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Alice</TableCell>
<TableCell>Engineering</TableCell>
</TableRow>
<TableRow>
<TableCell>Bob</TableCell>
<TableCell>Design</TableCell>
</TableRow>
</TableBody>
</Table>
</VariantRow>
</Section>
{/* Combobox */}
<Section title='Combobox'>
<VariantRow label='default'>
<div className='w-48'>
<Combobox
options={COMBOBOX_OPTIONS}
value={comboboxValue}
onChange={setComboboxValue}
placeholder='Select option...'
/>
</div>
</VariantRow>
<VariantRow label='size sm'>
<div className='w-48'>
<Combobox
options={COMBOBOX_OPTIONS}
value=''
onChange={() => {}}
placeholder='Small size'
size='sm'
/>
</div>
</VariantRow>
<VariantRow label='searchable'>
<div className='w-48'>
<Combobox
options={COMBOBOX_OPTIONS}
value=''
onChange={() => {}}
placeholder='With search'
searchable
/>
</div>
</VariantRow>
<VariantRow label='editable'>
<div className='w-48'>
<Combobox
options={COMBOBOX_OPTIONS}
value=''
onChange={() => {}}
placeholder='Type or select...'
editable
/>
</div>
</VariantRow>
<VariantRow label='multiSelect'>
<div className='w-48'>
<Combobox
options={COMBOBOX_OPTIONS}
multiSelectValues={[]}
onMultiSelectChange={() => {}}
placeholder='Select multiple...'
multiSelect
searchable
/>
</div>
</VariantRow>
</Section>
{/* TimePicker */}
<Section title='TimePicker'>
<VariantRow label='default'>
<div className='w-48'>
<TimePicker value={timeValue} onChange={setTimeValue} placeholder='Select time' />
</div>
<span className='text-[var(--text-secondary)] text-sm'>{timeValue}</span>
</VariantRow>
<VariantRow label='size sm'>
<div className='w-48'>
<TimePicker
value='14:00'
onChange={() => {}}
placeholder='Small size'
size='sm'
/>
</div>
</VariantRow>
<VariantRow label='no value'>
<div className='w-48'>
<TimePicker placeholder='Select time...' onChange={() => {}} />
</div>
</VariantRow>
<VariantRow label='disabled'>
<div className='w-48'>
<TimePicker value='09:00' disabled />
</div>
</VariantRow>
</Section>
{/* ChipDatePicker */}
<Section title='ChipDatePicker'>
<VariantRow label='single date'>
<div className='w-56'>
<ChipDatePicker
value={dateValue}
onChange={setDateValue}
placeholder='Select date'
fullWidth
/>
</div>
<span className='text-[var(--text-secondary)] text-sm'>
{dateValue || 'No date'}
</span>
</VariantRow>
<VariantRow label='range mode'>
<div className='w-72'>
<ChipDatePicker
mode='range'
startDate={dateRangeStart}
endDate={dateRangeEnd}
onRangeChange={(start, end) => {
setDateRangeStart(start)
setDateRangeEnd(end)
}}
placeholder='Select date range'
fullWidth
/>
</div>
</VariantRow>
<VariantRow label='range mode (with time)'>
<div className='w-72'>
<ChipDatePicker
mode='range'
showTime
startDate={dateRangeStart}
endDate={dateRangeEnd}
onRangeChange={(start, end) => {
setDateRangeStart(start)
setDateRangeEnd(end)
}}
placeholder='Select date range'
fullWidth
/>
</div>
</VariantRow>
<VariantRow label='disabled'>
<div className='w-56'>
<ChipDatePicker value='2025-01-15' disabled fullWidth />
</div>
</VariantRow>
</Section>
{/* Tooltip */}
<Section title='Tooltip'>
<VariantRow label='default'>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button variant='default'>Hover me</Button>
</Tooltip.Trigger>
<Tooltip.Content>Tooltip content</Tooltip.Content>
</Tooltip.Root>
</VariantRow>
<VariantRow label='with shortcut'>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button variant='default'>Clear console</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<Tooltip.Shortcut keys='⌘D'>Clear console</Tooltip.Shortcut>
</Tooltip.Content>
</Tooltip.Root>
</VariantRow>
<VariantRow label='shortcut only'>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button variant='default'>Save</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<Tooltip.Shortcut keys='⌘S' />
</Tooltip.Content>
</Tooltip.Root>
</VariantRow>
</Section>
{/* Popover */}
<Section title='Popover'>
<VariantRow label='default'>
<Popover>
<PopoverTrigger asChild>
<Button variant='default'>Open Popover</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverSection>Section Title</PopoverSection>
<PopoverItem>Item 1</PopoverItem>
<PopoverItem>Item 2</PopoverItem>
<PopoverItem active>Active Item</PopoverItem>
</PopoverContent>
</Popover>
</VariantRow>
<VariantRow label='secondary variant'>
<Popover variant='secondary'>
<PopoverTrigger asChild>
<Button variant='secondary'>Secondary Popover</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverItem>Item 1</PopoverItem>
<PopoverItem active>Active Item</PopoverItem>
</PopoverContent>
</Popover>
</VariantRow>
<VariantRow label='with search'>
<Popover>
<PopoverTrigger asChild>
<Button variant='default'>Searchable Popover</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverSearch placeholder='Search items...' />
<PopoverScrollArea className='max-h-40'>
<PopoverItem>Apple</PopoverItem>
<PopoverItem>Banana</PopoverItem>
<PopoverItem>Cherry</PopoverItem>
<PopoverItem>Date</PopoverItem>
<PopoverItem>Elderberry</PopoverItem>
</PopoverScrollArea>
</PopoverContent>
</Popover>
</VariantRow>
<VariantRow label='with folders'>
<Popover>
<PopoverTrigger asChild>
<Button variant='default'>Folder Navigation</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverBackButton />
<PopoverItem rootOnly>Root Item</PopoverItem>
<PopoverFolder
id='folder1'
title='Folder 1'
icon={<Folder className='size-3' />}
>
<PopoverItem>Nested Item 1</PopoverItem>
<PopoverItem>Nested Item 2</PopoverItem>
</PopoverFolder>
<PopoverFolder
id='folder2'
title='Folder 2'
icon={<Folder className='size-3' />}
>
<PopoverItem>Another Nested Item</PopoverItem>
</PopoverFolder>
</PopoverContent>
</Popover>
</VariantRow>
</Section>
{/* Modal */}
<Section title='Modal'>
<VariantRow label='sizes'>
{(['sm', 'md', 'lg', 'xl', 'full'] as const).map((size) => (
<Modal key={size}>
<ModalTrigger asChild>
<Button variant='default'>{size}</Button>
</ModalTrigger>
<ModalContent size={size}>
<ModalHeader>Modal {size.toUpperCase()}</ModalHeader>
<ModalBody>
<ModalDescription className='text-[var(--text-secondary)]'>
This is a {size} sized modal.
</ModalDescription>
</ModalBody>
<ModalFooter>
<Button variant='ghost'>Cancel</Button>
<Button variant='primary'>Save</Button>
</ModalFooter>
</ModalContent>
</Modal>
))}
</VariantRow>
<VariantRow label='with tabs'>
<Modal>
<ModalTrigger asChild>
<Button variant='default'>Modal with Tabs</Button>
</ModalTrigger>
<ModalContent>
<ModalHeader>Settings</ModalHeader>
<ModalTabs defaultValue='tab1'>
<ModalTabsList>
<ModalTabsTrigger value='tab1'>General</ModalTabsTrigger>
<ModalTabsTrigger value='tab2'>Advanced</ModalTabsTrigger>
</ModalTabsList>
<ModalBody>
<ModalDescription className='sr-only'>
Modal settings with general and advanced tabs
</ModalDescription>
<ModalTabsContent value='tab1'>
<p className='text-[var(--text-secondary)]'>General settings content</p>
</ModalTabsContent>
<ModalTabsContent value='tab2'>
<p className='text-[var(--text-secondary)]'>Advanced settings content</p>
</ModalTabsContent>
</ModalBody>
</ModalTabs>
<ModalFooter>
<Button variant='primary'>Save</Button>
</ModalFooter>
</ModalContent>
</Modal>
</VariantRow>
</Section>
{/* Code */}
<Section title='Code'>
<VariantRow label='javascript'>
<div className='w-full max-w-lg'>
<Code.Viewer code={SAMPLE_CODE} language='javascript' showGutter />
</div>
</VariantRow>
<VariantRow label='json'>
<div className='w-full max-w-lg'>
<Code.Viewer
code={JSON.stringify({ name: 'Sim', version: '1.0' }, null, 2)}
language='json'
showGutter
/>
</div>
</VariantRow>
<VariantRow label='python'>
<div className='w-full max-w-lg'>
<Code.Viewer code={SAMPLE_PYTHON} language='python' showGutter />
</div>
</VariantRow>
<VariantRow label='no gutter'>
<div className='w-full max-w-lg'>
<Code.Viewer code={SAMPLE_CODE} language='javascript' />
</div>
</VariantRow>
<VariantRow label='wrap text'>
<div className='w-full max-w-lg'>
<Code.Viewer
code="const longLine = 'This is a very long line that should wrap when wrapText is enabled to demonstrate the text wrapping functionality';"
language='javascript'
showGutter
wrapText
/>
</div>
</VariantRow>
</Section>
{/* Icons */}
<Section title='Icons'>
<div className='grid grid-cols-6 gap-4 sm:grid-cols-8 md:grid-cols-10'>
{[
{ Icon: BubbleChatClose, name: 'BubbleChatClose' },
{ Icon: BubbleChatPreview, name: 'BubbleChatPreview' },
{ Icon: ChevronDown, name: 'ChevronDown' },
{ Icon: Connections, name: 'Connections' },
{ Icon: Cursor, name: 'Cursor' },
{ Icon: DocumentAttachment, name: 'DocumentAttachment' },
{ Icon: Download, name: 'Download' },
{ Icon: Duplicate, name: 'Duplicate' },
{ Icon: Expand, name: 'Expand' },
{ Icon: Eye, name: 'Eye' },
{ Icon: FolderCode, name: 'FolderCode' },
{ Icon: FolderPlus, name: 'FolderPlus' },
{ Icon: Hand, name: 'Hand' },
{ Icon: HexSimple, name: 'HexSimple' },
{ Icon: KeyIcon, name: 'Key' },
{ Icon: Layout, name: 'Layout' },
{ Icon: Library, name: 'Library' },
{ Icon: Loader, name: 'Loader' },
{ Icon: MoreHorizontal, name: 'MoreHorizontal' },
{ Icon: NoWrap, name: 'NoWrap' },
{ Icon: PanelLeft, name: 'PanelLeft' },
{ Icon: Play, name: 'Play' },
{ Icon: PlayOutline, name: 'PlayOutline' },
{ Icon: Redo, name: 'Redo' },
{ Icon: Rocket, name: 'Rocket' },
{ Icon: Trash, name: 'Trash' },
{ Icon: Trash2, name: 'Trash2' },
{ Icon: Undo, name: 'Undo' },
{ Icon: Wrap, name: 'Wrap' },
{ Icon: ZoomIn, name: 'ZoomIn' },
{ Icon: ZoomOut, name: 'ZoomOut' },
].map(({ Icon, name }) => (
<Tooltip.Root key={name}>
<Tooltip.Trigger asChild>
<div className='flex size-10 cursor-pointer items-center justify-center rounded-md border border-[var(--border)] bg-[var(--surface-2)] transition-colors hover:bg-[var(--surface-4)]'>
<Icon className='size-5 text-[var(--text-secondary)]' />
</div>
</Tooltip.Trigger>
<Tooltip.Content>{name}</Tooltip.Content>
</Tooltip.Root>
))}
</div>
</Section>
</div>
</div>
</Tooltip.Provider>
</ToastProvider>
)
}