455 lines
11 KiB
Svelte
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>
|