Files
sui/src/routes/+page.svelte
2025-07-15 19:03:21 -07:00

455 lines
11 KiB
Svelte

<script lang="ts">
import { CalendarDate, today, getLocalTimeZone } from '@internationalized/date';
import Button from '$lib/Button.svelte';
import ActionSelect from '$lib/ActionSelect.svelte';
import Checkbox, { type CheckboxState } from '$lib/Checkbox.svelte';
import Combobox from '$lib/Combobox.svelte';
import DateInput from '$lib/DateInput.svelte';
import Dialog from '$lib/Dialog.svelte';
import FramelessButton from '$lib/FramelessButton.svelte';
import InputGroup from '$lib/InputGroup.svelte';
import Link from '$lib/Link.svelte';
import PhoneInput from '$lib/PhoneInput.svelte';
import PinInput from '$lib/PinInput.svelte';
import RadioGroup from '$lib/RadioGroup.svelte';
import StyledRawInput from '$lib/StyledRawInput.svelte';
import TextInput from '$lib/TextInput.svelte';
import TimeInput from '$lib/TimeInput.svelte';
import TimezoneInput from '$lib/TimezoneInput.svelte';
import ToggleGroup from '$lib/ToggleGroup.svelte';
import ToggleSelect from '$lib/ToggleSelect.svelte';
import { Toolbar } from '$lib/Toolbar';
import {
ArrowUUpLeft,
ArrowUUpRight,
DotsThreeOutlineVertical,
Plus,
TextB,
TextItalic,
TextStrikethrough,
TextUnderline
} from 'phosphor-svelte';
import type { ComboboxOption, Option } from '$lib';
import Tabs from '$lib/Tabs.svelte';
const comboboxOptions = [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
{ value: 'option3', label: 'Option 3', disabled: true }
];
let lazyOptions: ComboboxOption[] = $state([]);
let dateInputValue = $state<CalendarDate | undefined>(undefined);
let checkboxValue = $state<CheckboxState>('indeterminate');
let dialogOpen = $state(false);
let toggleOptions: Option[] = $state([
'item one',
'item two',
{ value: 'complex', label: 'Complex item' }
]);
const toolbar = new Toolbar();
const fontGroup = toolbar.group();
const boldToggle = fontGroup.toggle();
const boldStore = boldToggle.store;
</script>
<title>sui</title>
<h1 class="mb-4 text-3xl font-bold">sui — Opinionated Svelte 5 UI toolkit</h1>
<p class="mb-4">
Welcome to the Svelte UI toolkit! This is a collection of components and utilities designed to
help you build Svelte applications quickly and efficiently.
</p>
<h2 class="mb-2 text-2xl font-semibold">Component Library</h2>
<div class="component">
<p class="title">Button, Frameless Button, & Link</p>
<div class="flex gap-4">
<Button icon={{ component: Plus }} loading={false} onclick={() => alert('Button clicked!')}
>Click Me</Button
>
<Button icon={{ component: Plus }} loading={true} onclick={() => alert('Button clicked!')}
>Loading Button</Button
>
<FramelessButton icon={{ component: Plus }}>Click Me</FramelessButton>
<Link href="https://svelte.dev">Visit Svelte</Link>
<Button onclick={() => (dialogOpen = true)}>Open Dialog</Button>
</div>
</div>
<div class="component">
<p class="title">Action Select</p>
<div class="flex gap-2">
<ActionSelect
class="basis-1/2"
stateless
label="Stateless Action"
options={[
{ label: 'Yeet' },
{ label: 'Yote' },
{ label: 'Yote and Yeet' },
{ label: 'Disabled Action', disabled: true }
]}
/>
<ActionSelect
class="basis-1/2"
label="Stateful Action"
value={{ label: 'Initial Action', onchoose: (value) => console.log('Chosen:', value) }}
options={[
{ label: 'Action 1', onchoose: (value) => console.log('Action 1 chosen:', value) },
{ label: 'Action 2', onchoose: (value) => console.log('Action 2 chosen:', value) },
{
label: 'Disabled Action',
disabled: true,
onchoose: (value) => console.log('Disabled action chosen:', value)
}
]}
/>
<ActionSelect
class="mt-7"
options={[{ label: 'Option 1' }, { label: 'Option 2' }, { label: 'Option 3' }]}
sameWidth={false}
frameless={true}
stateless={true}
>
<DotsThreeOutlineVertical class="text-sui-text" size="1.2em" />
</ActionSelect>
</div>
</div>
<div class="component">
<p class="title">Combobox</p>
<InputGroup>
<Combobox
name="example-combobox"
label="Select an option"
placeholder="Choose..."
options={comboboxOptions}
onchange={(e) => console.log('Selected:', e.value)}
onvalidate={(e) => console.log('Validation:', e.detail)}
/>
<Combobox
loading
label="Loading stateless combobox"
stateless
placeholder="Choose..."
options={[
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
{ value: 'option3', label: 'Option 3' }
]}
onchange={(e) => console.log('Selected:', e.value)}
onvalidate={(e) => console.log('Validation:', e.detail)}
/>
<Combobox
icon={{
component: Plus,
props: {
size: '1.2em'
}
}}
label="Lazy combobox"
placeholder="Choose..."
options={lazyOptions}
lazy
onlazy={() => {
setTimeout(() => {
lazyOptions = [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
{ value: 'option3', label: 'Option 3' }
];
}, 2500);
}}
/>
<Combobox
label="Compact combobox"
placeholder="Choose..."
lazy
compact
options={comboboxOptions}
/>
</InputGroup>
</div>
<div class="component">
<p class="title">Checkbox</p>
<div class="flex items-center gap-4">
<Checkbox
name="example-checkbox"
bind:value={checkboxValue}
onchange={(value) => console.log('Checkbox value:', value)}
>
Agree to terms and conditions
</Checkbox>
<span>Checkbox is {checkboxValue}</span>
<Button onclick={() => (checkboxValue = 'indeterminate')}>Set Indeterminate</Button>
</div>
</div>
<div class="component">
<p class="title">Dateinput</p>
<div class="flex items-start gap-4">
<DateInput bind:value={dateInputValue} />
<div class="shrink-0">
<Button
icon={{ component: Plus }}
onclick={() => {
dateInputValue = today(getLocalTimeZone());
console.log('Dateinput value set to:', dateInputValue);
}}
>
Set Current Date
</Button>
</div>
<span>Selected date is {dateInputValue}</span>
</div>
</div>
<div class="component">
<p class="title">Phone Input</p>
<PhoneInput label="Phone Number" name="phone" defaultISO="CA" />
</div>
<div class="component">
<p class="title">Pin Input</p>
<PinInput label="Please enter your 6-digit PIN" length={6} />
</div>
<div class="component">
<p class="title">Radio Group</p>
<RadioGroup
name="example-radio-group"
label="Choose an option"
options={[
'male',
'female',
'other',
{ value: 'prefer_not_to_say', label: 'Prefer not to say' }
]}
/>
</div>
<div class="component">
<p class="title">Styled Raw Input, Text Input, Disabled Input, Compact Input</p>
<InputGroup>
<StyledRawInput placeholder="Type here..." class="basis-1/2" />
<TextInput label="Write something here" placeholder="Enter text..." class="basis-1/2" />
</InputGroup>
<InputGroup>
<TextInput label="Disabled input" placeholder="You can't enter text" disabled />
<TextInput label="Compact Input" placeholder="Small input field" compact />
</InputGroup>
</div>
<div class="component">
<p class="title">Time Input</p>
<TimeInput name="example-time-input" label="Enter a time" />
</div>
<div class="component">
<p class="title">TimezoneInput</p>
<TimezoneInput name="example-timezone" label="Pick your timezone" />
</div>
<div class="component">
<p class="title">Toggle</p>
<ToggleSelect name="example-toggle-select" class="mb-4">Toggle Me!</ToggleSelect>
<ToggleGroup label="Toggle Group" options={toggleOptions} />
<Button
onclick={() => {
toggleOptions.push({ value: 'new', label: 'New Option' });
}}>Add Option</Button
>
</div>
<div class="component">
<p class="title">Toolbar</p>
<div class="my-2">
<p>Bold is enabled: {$boldStore}</p>
</div>
<div
class="border-sui-text flex w-full min-w-max items-center gap-4 border-b
bg-white px-3 py-3 text-neutral-700 shadow-xs"
>
<div class="flex items-center gap-1">
<button type="button" class="item" title="Undo" aria-label="undo">
<ArrowUUpLeft size="1.25em" />
</button>
<button type="button" class="item" title="Redo" aria-label="redo">
<ArrowUUpRight size="1.25em" />
</button>
</div>
<div class="bg-sui-text/50 w-[1px] self-stretch"></div>
<div class="flex items-center gap-1">
<button class="item" title="Toggle Bold" aria-label="bold" {@attach boldToggle.attachment}>
<TextB size="1.25em" />
</button>
<button
class="item"
title="Toggle Italic"
aria-label="italic"
{@attach fontGroup.toggle().attachment}
>
<TextItalic size="1.25em" />
</button>
<button
class="item"
title="Toggle Underline"
aria-label="underline"
{@attach fontGroup.toggle().attachment}
>
<TextUnderline size="1.25em" />
</button>
<button
class="item"
title="Toggle Strikethrough"
aria-label="strikethrough"
{...fontGroup.toggle().props}
>
<TextStrikethrough size="1.25em" />
</button>
</div>
</div>
</div>
<div class="component">
<p class="title">Tabs</p>
<Tabs
pages={[
{
title: 'Dashboard',
content: tab1
},
{
title: 'Activity',
content: tab2
},
{
title: 'Settings',
content: tab3
}
]}
/>
{#snippet tab1()}
<h3 class="mb-4 text-2xl font-bold">Dashboard Overview</h3>
<div class="grid grid-cols-2 gap-4">
<div class="rounded bg-blue-100 p-4">
<h4 class="font-semibold">Total Users</h4>
<p class="text-3xl font-bold text-blue-600">1,247</p>
</div>
<div class="rounded bg-green-100 p-4">
<h4 class="font-semibold">Revenue</h4>
<p class="text-3xl font-bold text-green-600">$12,450</p>
</div>
</div>
{/snippet}
{#snippet tab2()}
<h3 class="mb-3 text-xl font-semibold">Recent Activity</h3>
<ul class="space-y-2">
<li class="flex items-center gap-2">
<span class="h-2 w-2 rounded-full bg-green-500"></span>
<span>User John Doe logged in</span>
</li>
<li class="flex items-center gap-2">
<span class="h-2 w-2 rounded-full bg-blue-500"></span>
<span>New order #1234 received</span>
</li>
<li class="flex items-center gap-2">
<span class="h-2 w-2 rounded-full bg-yellow-500"></span>
<span>System backup completed</span>
</li>
</ul>
{/snippet}
{#snippet tab3()}
<h3 class="mb-3 text-xl font-semibold">Settings</h3>
<form class="space-y-3">
<div class="flex items-center gap-2">
<input type="checkbox" id="notifications" checked />
<label for="notifications">Enable notifications</label>
</div>
<div class="flex items-center gap-2">
<input type="checkbox" id="darkmode" />
<label for="darkmode">Dark mode</label>
</div>
<button class="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
Save Changes
</button>
</form>
{/snippet}
</div>
<Dialog
bind:open={dialogOpen}
title="Dialog Title"
size="sm"
onsubmit={() => {
dialogOpen = false;
alert('Dialog submitted!');
}}
>
<p>This is a dialog content area.</p>
</Dialog>
<style lang="postcss">
@reference '$lib/styles/tailwind.css';
.component .title {
@apply mb-2 text-lg font-semibold;
}
.component {
@apply mb-6 rounded-lg border p-4;
}
.item {
@apply flex items-center;
padding: theme('spacing.1');
border-radius: theme('borderRadius.md');
&:hover {
background-color: theme('colors.sui-secondary.100');
}
&[data-state='on'] {
background-color: theme('colors.sui-secondary.200');
color: theme('colors.sui-accent.900');
}
/* &:focus {
@apply ring-accent-400 ring-2;
} */
}
.separator {
width: 1px;
background-color: theme('colors.neutral.300');
align-self: stretch;
}
</style>