feat: redo settings and all org pages. (#601)

* feat: new components, deploy storybook

* refactor: simplify component field definitions and enhance button loading state

- Removed unnecessary type imports and simplified the FIELDS array definitions across multiple components in Storybook.
- Updated button component to use a loading state instead of isLoading, improving clarity and consistency in props.
- Adjusted button stories to reflect the new loading prop and enhanced the button's loading visual feedback.
- Minor CSS adjustments for better layout and styling consistency.

* chore: add field, card, password components

* feat: improve page component & update AuthUI path

* chore: update bits-ui to version 2.14.4 and all org related UI's

- Updated bits-ui dependency in pnpm-lock.yaml and package.json to version 2.14.4.
- Refactored imports for RoleBasedSecurity and UpgradeBanner components to use the new ui feature directory.
- Removed unused Course components and related files to clean up the codebase.
- Enhanced various components to improve consistency and maintainability.

* chore: move all setting buttons to the top and update auth ui styles

* chore: remove svelte-loading-spinners and replace with @cio/ui/base/spinner

- Removed all instances of svelte-loading-spinners from the project.
- Updated components to use the new Spinner from @cio/ui/base/spinner.
- Cleaned up pnpm-lock.yaml and package.json to reflect the removal of svelte-loading-spinners.
- Added new CSS animations for rocket launch effects in app.css.
- Enhanced email change flow with updated verification messages in translations.

* chore: remove supabase file upload from dashboard and move to r2

* chore: update organization API and UI components

- Added new API methods for organization plan operations and team management.
- Introduced middleware for API key authentication and organization admin checks.
- Refactored organization-related components to utilize the new API structure.
- Updated various UI components to improve consistency and maintainability.
- Enhanced error handling and validation for organization-related actions.
- Removed deprecated services and streamlined the codebase.
This commit is contained in:
Rotimi Best
2025-12-02 09:54:38 +01:00
committed by GitHub
parent 531516a8f7
commit c7854efae3
350 changed files with 13928 additions and 3895 deletions
@@ -1,7 +1,4 @@
import { type ComponentProps } from 'svelte';
import { Accordion } from '@cio/ui/base/accordion';
export const FIELDS: (keyof ComponentProps<typeof Accordion>)[] = [
export const FIELDS = [
'type',
'value',
'onValueChange',
@@ -9,4 +6,4 @@ export const FIELDS: (keyof ComponentProps<typeof Accordion>)[] = [
'name',
'loop',
'orientation'
];
] as string[];
@@ -1,4 +1 @@
import { type ComponentProps } from 'svelte';
import { Avatar } from '@cio/ui/base/avatar';
export const FIELDS: (keyof ComponentProps<typeof Avatar>)[] = ['loadingStatus', 'onLoadingStatusChange', 'delayMs'];
export const FIELDS = ['loadingStatus', 'onLoadingStatusChange', 'delayMs'] as string[];
@@ -17,8 +17,8 @@
</script>
<Story name="Default">
{#snippet template()}
<Button>Default Button</Button>
{#snippet template(args)}
<Button {...args}>Default Button</Button>
{/snippet}
</Story>
@@ -72,7 +72,7 @@
<Story name="Loading">
{#snippet template(args)}
<Button {...args} isLoading>Loading Button</Button>
<Button {...args} loading>Loading Button</Button>
{/snippet}
</Story>
@@ -1,4 +1 @@
import { type ComponentProps } from 'svelte';
import { Button } from '@cio/ui/base/button';
export const FIELDS: (keyof ComponentProps<typeof Button>)[] = ['variant', 'size', 'disabled', 'class', 'type', 'href'];
export const FIELDS = ['variant', 'size', 'disabled', 'class', 'type', 'href', 'loading'] as string[];
@@ -1,11 +1,8 @@
import { type ComponentProps } from 'svelte';
import { Checkbox } from '@cio/ui/base/checkbox';
export const FIELDS: (keyof ComponentProps<typeof Checkbox>)[] = [
export const FIELDS = [
'checked',
'onCheckedChange',
'disabled',
'name',
'value',
'required'
];
] as string[];
@@ -1,9 +1,6 @@
import { type ComponentProps } from 'svelte';
import { Collapsible } from '@cio/ui/base/collapsible';
export const FIELDS: (keyof ComponentProps<typeof Collapsible>)[] = [
export const FIELDS = [
'open',
'onOpenChange',
'onOpenChangeComplete',
'disabled'
];
] as string[];
@@ -1,11 +1,8 @@
import { type ComponentProps } from 'svelte';
import { Command } from '@cio/ui/base/command';
export const FIELDS: (keyof ComponentProps<typeof Command>)[] = [
export const FIELDS = [
'value',
'onValueChange',
'label',
'filter',
'columns',
'disablePointerSelection'
];
] as string[];
@@ -1,4 +1 @@
import { type ComponentProps } from 'svelte';
import * as Dialog from '@cio/ui/base/dialog';
export const FIELDS: (keyof ComponentProps<typeof Dialog.Root>)[] = ['open', 'onOpenChange'];
export const FIELDS = ['open', 'onOpenChange'] as string[];
+2 -5
View File
@@ -1,7 +1,4 @@
import { type ComponentProps } from 'svelte';
import { Input } from '@cio/ui/base/input';
export const FIELDS: (keyof ComponentProps<typeof Input>)[] = [
export const FIELDS = [
'value',
'type',
'placeholder',
@@ -11,4 +8,4 @@ export const FIELDS: (keyof ComponentProps<typeof Input>)[] = [
'name',
'required',
'readonly'
];
] as string[];
@@ -0,0 +1,416 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import * as Item from '@cio/ui/base/item';
import { Button } from '@cio/ui/base/button';
import * as Avatar from '@cio/ui/base/avatar';
import * as DropdownMenu from '@cio/ui/base/dropdown-menu';
import BadgeCheckIcon from '@lucide/svelte/icons/badge-check';
import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
import ExternalLinkIcon from '@lucide/svelte/icons/external-link';
import ShieldAlertIcon from '@lucide/svelte/icons/shield-alert';
import PlusIcon from '@lucide/svelte/icons/plus';
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
const { Story } = defineMeta({
title: 'Atom/Item',
component: Item.Root,
parameters: {
layout: 'centered',
controls: {
include: []
}
},
tags: ['autodocs']
});
const music = [
{
title: 'Midnight City Lights',
artist: 'Neon Dreams',
album: 'Electric Nights',
duration: '3:45'
},
{
title: 'Coffee Shop Conversations',
artist: 'The Morning Brew',
album: 'Urban Stories',
duration: '4:05'
},
{
title: 'Digital Rain',
artist: 'Cyber Symphony',
album: 'Binary Beats',
duration: '3:30'
}
];
const people = [
{
username: 'shadcn',
avatar: 'https://github.com/shadcn.png',
email: 'shadcn@vercel.com'
},
{
username: 'maxleiter',
avatar: 'https://github.com/maxleiter.png',
email: 'maxleiter@vercel.com'
},
{
username: 'evilrabbit',
avatar: 'https://github.com/evilrabbit.png',
email: 'evilrabbit@vercel.com'
}
];
const models = [
{
name: 'v0-1.5-sm',
description: 'Everyday tasks and UI generation.',
image:
'https://images.unsplash.com/photo-1650804068570-7fb2e3dbf888?q=80&w=640&auto=format&fit=crop',
credit: 'Valeria Reverdo on Unsplash'
},
{
name: 'v0-1.5-lg',
description: 'Advanced thinking or reasoning.',
image:
'https://images.unsplash.com/photo-1610280777472-54133d004c8c?q=80&w=640&auto=format&fit=crop',
credit: 'Michael Oeser on Unsplash'
},
{
name: 'v0-2.0-mini',
description: 'Open Source model for everyone.',
image:
'https://images.unsplash.com/photo-1602146057681-08560aee8cde?q=80&w=640&auto=format&fit=crop',
credit: 'Cherry Laithang on Unsplash'
}
];
</script>
<Story name="Basic Item">
{#snippet template()}
<div class="flex w-full max-w-md flex-col gap-6">
<Item.Root variant="outline">
<Item.Content>
<Item.Title>Basic Item</Item.Title>
<Item.Description>A simple item with title and description.</Item.Description>
</Item.Content>
<Item.Actions>
<Button variant="outline" size="sm">Action</Button>
</Item.Actions>
</Item.Root>
<Item.Root variant="outline" size="sm">
{#snippet child({ props })}
<a href="#/" {...props}>
<Item.Media>
<BadgeCheckIcon class="size-5" />
</Item.Media>
<Item.Content>
<Item.Title>Your profile has been verified.</Item.Title>
</Item.Content>
<Item.Actions>
<ChevronRightIcon class="size-4" />
</Item.Actions>
</a>
{/snippet}
</Item.Root>
</div>
{/snippet}
</Story>
<Story name="Variants">
{#snippet template()}
<div class="flex flex-col gap-6">
<Item.Root>
<Item.Content>
<Item.Title>Default Variant</Item.Title>
<Item.Description>Standard styling with subtle background and borders.</Item.Description>
</Item.Content>
<Item.Actions>
<Button variant="outline" size="sm">Open</Button>
</Item.Actions>
</Item.Root>
<Item.Root variant="outline">
<Item.Content>
<Item.Title>Outline Variant</Item.Title>
<Item.Description>Outlined style with clear borders and transparent background.</Item.Description>
</Item.Content>
<Item.Actions>
<Button variant="outline" size="sm">Open</Button>
</Item.Actions>
</Item.Root>
<Item.Root variant="muted">
<Item.Content>
<Item.Title>Muted Variant</Item.Title>
<Item.Description>Subdued appearance with muted colors for secondary content.</Item.Description>
</Item.Content>
<Item.Actions>
<Button variant="outline" size="sm">Open</Button>
</Item.Actions>
</Item.Root>
</div>
{/snippet}
</Story>
<Story name="Size">
{#snippet template()}
<div class="flex w-full max-w-md flex-col gap-6">
<Item.Root variant="outline">
<Item.Content>
<Item.Title>Basic Item</Item.Title>
<Item.Description>A simple item with title and description.</Item.Description>
</Item.Content>
<Item.Actions>
<Button variant="outline" size="sm">Action</Button>
</Item.Actions>
</Item.Root>
<Item.Root variant="outline" size="sm">
{#snippet child({ props })}
<a href="#/" {...props}>
<Item.Media>
<BadgeCheckIcon class="size-5" />
</Item.Media>
<Item.Content>
<Item.Title>Your profile has been verified.</Item.Title>
</Item.Content>
<Item.Actions>
<ChevronRightIcon class="size-4" />
</Item.Actions>
</a>
{/snippet}
</Item.Root>
</div>
{/snippet}
</Story>
<Story name="Icon">
{#snippet template()}
<div class="flex w-full max-w-lg flex-col gap-6">
<Item.Root variant="outline">
<Item.Media variant="icon">
<ShieldAlertIcon />
</Item.Media>
<Item.Content>
<Item.Title>Security Alert</Item.Title>
<Item.Description>New login detected from unknown device.</Item.Description>
</Item.Content>
<Item.Actions>
<Button size="sm" variant="outline">Review</Button>
</Item.Actions>
</Item.Root>
</div>
{/snippet}
</Story>
<Story name="Avatar">
{#snippet template()}
<div class="flex w-full max-w-lg flex-col gap-6">
<Item.Root variant="outline">
<Item.Media>
<Avatar.Root class="size-10">
<Avatar.Image src="https://github.com/evilrabbit.png" />
<Avatar.Fallback>ER</Avatar.Fallback>
</Avatar.Root>
</Item.Media>
<Item.Content>
<Item.Title>Evil Rabbit</Item.Title>
<Item.Description>Last seen 5 months ago</Item.Description>
</Item.Content>
<Item.Actions>
<Button size="icon" variant="outline" class="rounded-full" aria-label="Invite">
<PlusIcon />
</Button>
</Item.Actions>
</Item.Root>
<Item.Root variant="outline">
<Item.Media>
<div
class="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale"
>
<Avatar.Root class="hidden sm:flex">
<Avatar.Image src="https://github.com/shadcn.png" alt="@shadcn" />
<Avatar.Fallback>CN</Avatar.Fallback>
</Avatar.Root>
<Avatar.Root class="hidden sm:flex">
<Avatar.Image src="https://github.com/maxleiter.png" alt="@maxleiter" />
<Avatar.Fallback>LR</Avatar.Fallback>
</Avatar.Root>
<Avatar.Root>
<Avatar.Image src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
<Avatar.Fallback>ER</Avatar.Fallback>
</Avatar.Root>
</div>
</Item.Media>
<Item.Content>
<Item.Title>No Team Members</Item.Title>
<Item.Description>Invite your team to collaborate on this project.</Item.Description>
</Item.Content>
<Item.Actions>
<Button size="sm" variant="outline">Invite</Button>
</Item.Actions>
</Item.Root>
</div>
{/snippet}
</Story>
<Story name="Image">
{#snippet template()}
<div class="flex w-full max-w-md flex-col gap-6">
<div class="flex w-full max-w-md flex-col gap-4">
{#each music as song (song)}
<Item.Root variant="outline">
{#snippet child({ props })}
<a href="#/" {...props}>
<Item.Media variant="image">
<img
src={`https://avatar.vercel.sh/${song.title}`}
alt={song.title}
width="32"
height="32"
class="size-8 rounded object-cover grayscale"
/>
</Item.Media>
<Item.Content>
<Item.Title class="line-clamp-1">
{song.title} - <span class="text-muted-foreground">{song.album}</span>
</Item.Title>
<Item.Description>{song.artist}</Item.Description>
</Item.Content>
<Item.Content class="flex-none text-center">
<Item.Description>{song.duration}</Item.Description>
</Item.Content>
</a>
{/snippet}
</Item.Root>
{/each}
</div>
</div>
{/snippet}
</Story>
<Story name="Group">
{#snippet template()}
<div class="flex w-full max-w-md flex-col gap-6">
<Item.Group>
{#each people as person, index (person.username)}
<Item.Root>
<Item.Media>
<Avatar.Root>
<Avatar.Image src={person.avatar} class="grayscale" />
<Avatar.Fallback>{person.username.charAt(0)}</Avatar.Fallback>
</Avatar.Root>
</Item.Media>
<Item.Content class="gap-1">
<Item.Title>{person.username}</Item.Title>
<Item.Description>{person.email}</Item.Description>
</Item.Content>
<Item.Actions>
<Button variant="ghost" size="icon" class="rounded-full">
<PlusIcon />
</Button>
</Item.Actions>
</Item.Root>
{#if index !== people.length - 1}
<Item.Separator />
{/if}
{/each}
</Item.Group>
</div>
{/snippet}
</Story>
<Story name="Header">
{#snippet template()}
<div class="flex w-full max-w-xl flex-col gap-6">
<Item.Group class="grid grid-cols-3 gap-4">
{#each models as model (model.name)}
<Item.Root variant="outline">
<Item.Header>
<img
src={model.image}
alt={model.name}
width="128"
height="128"
class="aspect-square w-full rounded-sm object-cover"
/>
</Item.Header>
<Item.Content>
<Item.Title>{model.name}</Item.Title>
<Item.Description>{model.description}</Item.Description>
</Item.Content>
</Item.Root>
{/each}
</Item.Group>
</div>
{/snippet}
</Story>
<Story name="Link">
{#snippet template()}
<div class="flex w-full max-w-md flex-col gap-4">
<Item.Root>
{#snippet child({ props })}
<a href="#/" {...props}>
<Item.Content>
<Item.Title>Visit our documentation</Item.Title>
<Item.Description>Learn how to get started with our components.</Item.Description>
</Item.Content>
<Item.Actions>
<ChevronRightIcon class="size-4" />
</Item.Actions>
</a>
{/snippet}
</Item.Root>
<Item.Root variant="outline">
{#snippet child({ props })}
<a href="#/" target="_blank" rel="noopener noreferrer" {...props}>
<Item.Content>
<Item.Title>External resource</Item.Title>
<Item.Description>Opens in a new tab with security attributes.</Item.Description>
</Item.Content>
<Item.Actions>
<ExternalLinkIcon class="size-4" />
</Item.Actions>
</a>
{/snippet}
</Item.Root>
</div>
{/snippet}
</Story>
<Story name="Dropdown">
{#snippet template()}
<div class="flex min-h-64 w-full max-w-md flex-col items-center gap-6">
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="outline" size="sm" class="w-fit">
Select <ChevronDownIcon />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content class="w-72 [--radius:0.65rem]" align="end">
{#each people as person (person.username)}
<DropdownMenu.Item class="p-0">
<Item.Root size="sm" class="w-full p-2">
<Item.Media>
<Avatar.Root class="size-8">
<Avatar.Image src={person.avatar} class="grayscale" />
<Avatar.Fallback>{person.username.charAt(0)}</Avatar.Fallback>
</Avatar.Root>
</Item.Media>
<Item.Content class="gap-0.5">
<Item.Title>{person.username}</Item.Title>
<Item.Description>{person.email}</Item.Description>
</Item.Content>
</Item.Root>
</DropdownMenu.Item>
{/each}
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
{/snippet}
</Story>
+1 -4
View File
@@ -1,4 +1 @@
import { type ComponentProps } from 'svelte';
import { Label } from '@cio/ui/base/label';
export const FIELDS: (keyof ComponentProps<typeof Label>)[] = ['children', 'child'];
export const FIELDS = ['children', 'child'] as string[];
@@ -1,10 +1,7 @@
import { type ComponentProps } from 'svelte';
import { Menubar } from '@cio/ui/base/menubar';
export const FIELDS: (keyof ComponentProps<typeof Menubar>)[] = [
export const FIELDS = [
'onValueChange',
'value',
'dir',
'onOpenChange',
'disabled'
];
] as string[];
@@ -1,10 +1,7 @@
import { type ComponentProps } from 'svelte';
import { Pagination } from '@cio/ui/base/pagination';
export const FIELDS: (keyof ComponentProps<typeof Pagination>)[] = [
export const FIELDS = [
'count',
'page',
'onPageChange',
'perPage',
'orientation'
];
] as string[];
@@ -1,4 +1 @@
import { type ComponentProps } from 'svelte';
import { Progress } from '@cio/ui/base/progress';
export const FIELDS: (keyof ComponentProps<typeof Progress>)[] = ['max', 'min', 'value'];
export const FIELDS = ['max', 'min', 'value'] as string[];
@@ -1,7 +1,4 @@
import { type ComponentProps } from 'svelte';
import { RadioGroup } from '@cio/ui/base/radio-group';
export const FIELDS: (keyof ComponentProps<typeof RadioGroup>)[] = [
export const FIELDS = [
'value',
'onValueChange',
'disabled',
@@ -9,4 +6,4 @@ export const FIELDS: (keyof ComponentProps<typeof RadioGroup>)[] = [
'name',
'loop',
'orientation'
];
] as string[];
@@ -1,7 +1,4 @@
import { type ComponentProps } from 'svelte';
import * as Select from '@cio/ui/base/select';
export const FIELDS: (keyof ComponentProps<typeof Select.Root>)[] = [
export const FIELDS = [
'value',
'onValueChange',
'disabled',
@@ -11,4 +8,4 @@ export const FIELDS: (keyof ComponentProps<typeof Select.Root>)[] = [
'required',
'autocomplete',
'allowDeselect'
];
] as string[];
@@ -1,4 +1 @@
import { type ComponentProps } from 'svelte';
import { Separator } from '@cio/ui/base/separator';
export const FIELDS: (keyof ComponentProps<typeof Separator>)[] = ['orientation', 'decorative'];
export const FIELDS = ['orientation', 'decorative'] as string[];
+1 -4
View File
@@ -1,4 +1 @@
import { type ComponentProps } from 'svelte';
import { Sheet } from '@cio/ui/base/sheet';
export const FIELDS: (keyof ComponentProps<typeof Sheet>)[] = ['open', 'onOpenChange'];
export const FIELDS = ['open', 'onOpenChange'] as string[];
@@ -39,7 +39,7 @@
>Make changes to your profile here. Click save when you're done.</Sheet.SheetDescription
>
</Sheet.SheetHeader>
<div class="grid gap-4 py-4">
<div class="grid gap-4 p-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label.Root for="name" class="text-right">Name</Label.Root>
<Input.Root id="name" value="Pedro Duarte" class="col-span-3" />
@@ -1,4 +1 @@
import { type ComponentProps } from 'svelte';
import { Skeleton } from '@cio/ui/base/skeleton';
export const FIELDS: (keyof ComponentProps<typeof Skeleton>)[] = ['class'];
export const FIELDS = ['class'] as string[];
@@ -1,4 +1 @@
import { type ComponentProps } from 'svelte';
import { Toaster as Sonner } from '@cio/ui/base/sonner';
export const FIELDS: (keyof ComponentProps<typeof Sonner>)[] = ['types', 'position', 'expand'];
export const FIELDS = ['types', 'position', 'expand'] as string[];
@@ -1,6 +1,13 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import { Spinner } from '@cio/ui/base/spinner';
import * as Item from '@cio/ui/base/item';
import { Button } from '@cio/ui/base/button';
import { Badge } from '@cio/ui/base/badge';
import * as InputGroup from '@cio/ui/base/input-group';
import * as Empty from '@cio/ui/base/empty';
import { Progress } from '@cio/ui/base/progress';
import ArrowUpIcon from '@lucide/svelte/icons/arrow-up';
const { Story } = defineMeta({
title: 'Atom/Spinner',
@@ -18,34 +25,144 @@
{/snippet}
</Story>
<Story name="Small">
<Story name="Size">
{#snippet template()}
<Spinner class="h-4 w-4" />
{/snippet}
</Story>
<Story name="Large">
{#snippet template()}
<Spinner class="h-12 w-12" />
{/snippet}
</Story>
<Story name="Custom Color">
{#snippet template()}
<div class="flex gap-4">
<Spinner class="text-blue-500" />
<Spinner class="text-green-500" />
<Spinner class="text-red-500" />
<Spinner class="text-purple-500" />
<div class="flex items-center gap-6">
<Spinner class="size-3" />
<Spinner class="size-4" />
<Spinner class="size-6" />
<Spinner class="size-8" />
</div>
{/snippet}
</Story>
<Story name="With Text">
<Story name="Color">
{#snippet template()}
<div class="flex items-center gap-2">
<Spinner />
<span>Loading...</span>
<div class="flex items-center gap-6">
<Spinner class="size-6 text-red-500" />
<Spinner class="size-6 text-green-500" />
<Spinner class="size-6 text-blue-500" />
<Spinner class="size-6 text-yellow-500" />
<Spinner class="size-6 text-purple-500" />
</div>
{/snippet}
</Story>
<Story name="Button">
{#snippet template()}
<div class="flex flex-col items-center gap-4">
<Button disabled size="sm">
<Spinner />
Loading...
</Button>
<Button variant="outline" disabled size="sm">
<Spinner />
Please wait
</Button>
<Button variant="secondary" disabled size="sm">
<Spinner />
Processing
</Button>
</div>
{/snippet}
</Story>
<Story name="Badge">
{#snippet template()}
<div class="flex items-center gap-4 [--radius:1.2rem]">
<Badge>
<Spinner />
Syncing
</Badge>
<Badge variant="secondary">
<Spinner />
Updating
</Badge>
<Badge variant="outline">
<Spinner />
Processing
</Badge>
</div>
{/snippet}
</Story>
<Story name="Input Group">
{#snippet template()}
<div class="flex w-full max-w-md flex-col gap-4">
<InputGroup.Root>
<InputGroup.Input placeholder="Send a message..." disabled />
<InputGroup.Addon align="inline-end">
<Spinner />
</InputGroup.Addon>
</InputGroup.Root>
<InputGroup.Root>
<InputGroup.Textarea placeholder="Send a message..." disabled />
<InputGroup.Addon align="block-end">
<Spinner />
Validating...
<InputGroup.Button class="ms-auto" variant="default">
<ArrowUpIcon />
<span class="sr-only">Send</span>
</InputGroup.Button>
</InputGroup.Addon>
</InputGroup.Root>
</div>
{/snippet}
</Story>
<Story name="Empty">
{#snippet template()}
<Empty.Root class="w-full">
<Empty.Header>
<Empty.Media variant="icon">
<Spinner />
</Empty.Media>
<Empty.Title>Processing your request</Empty.Title>
<Empty.Description>Please wait while we process your request. Do not refresh the page.</Empty.Description>
</Empty.Header>
<Empty.Content>
<Button variant="outline" size="sm">Cancel</Button>
</Empty.Content>
</Empty.Root>
{/snippet}
</Story>
<Story name="Item">
{#snippet template()}
<div class="flex w-full max-w-md flex-col gap-4 [--radius:1rem]">
<Item.Root variant="outline">
<Item.Media variant="icon">
<Spinner />
</Item.Media>
<Item.Content>
<Item.Title>Downloading...</Item.Title>
<Item.Description>129 MB / 1000 MB</Item.Description>
</Item.Content>
<Item.Actions class="hidden sm:flex">
<Button variant="outline" size="sm">Cancel</Button>
</Item.Actions>
<Item.Footer>
<Progress value={75} />
</Item.Footer>
</Item.Root>
</div>
{/snippet}
</Story>
<Story name="Item with Payment">
{#snippet template()}
<div class="flex w-full max-w-xs flex-col gap-4 [--radius:1rem]">
<Item.Root variant="muted">
<Item.Media>
<Spinner />
</Item.Media>
<Item.Content>
<Item.Title class="line-clamp-1">Processing payment...</Item.Title>
</Item.Content>
<Item.Content class="flex-none justify-end">
<span class="text-sm tabular-nums">$100.00</span>
</Item.Content>
</Item.Root>
</div>
{/snippet}
</Story>
@@ -1,11 +1,8 @@
import { type ComponentProps } from 'svelte';
import { Switch } from '@cio/ui/base/switch';
export const FIELDS: (keyof ComponentProps<typeof Switch>)[] = [
export const FIELDS = [
'checked',
'onCheckedChange',
'disabled',
'name',
'required',
'value'
];
] as string[];
+2 -5
View File
@@ -1,10 +1,7 @@
import { type ComponentProps } from 'svelte';
import { Tabs } from '@cio/ui/base/tabs';
export const FIELDS: (keyof ComponentProps<typeof Tabs>)[] = [
export const FIELDS = [
'value',
'onValueChange',
'disabled',
'activationMode',
'orientation'
];
] as string[];
@@ -1,9 +1,6 @@
import { type ComponentProps } from 'svelte';
import { Tooltip } from '@cio/ui/base/tooltip';
export const FIELDS: (keyof ComponentProps<typeof Tooltip>)[] = [
export const FIELDS = [
'disableHoverableContent',
'delayDuration',
'ignoreNonKeyboardFocus',
'disabled'
];
] as string[];
+2
View File
@@ -1,3 +1,5 @@
@import url('https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap');
@import 'tailwindcss';
@import '@cio/ui/output.css';
@@ -0,0 +1,400 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import Bot from '@lucide/svelte/icons/bot';
import AlertTriangle from '@lucide/svelte/icons/alert-triangle';
import Check from '@lucide/svelte/icons/check';
import ChevronDown from '@lucide/svelte/icons/chevron-down';
import Copy from '@lucide/svelte/icons/copy';
import Share from '@lucide/svelte/icons/share';
import Trash from '@lucide/svelte/icons/trash';
import UserRoundX from '@lucide/svelte/icons/user-round-x';
import VolumeOff from '@lucide/svelte/icons/volume-off';
import AudioLines from '@lucide/svelte/icons/audio-lines';
import ArrowLeft from '@lucide/svelte/icons/arrow-left';
import ArrowRight from '@lucide/svelte/icons/arrow-right';
import Search from '@lucide/svelte/icons/search';
import Archive from '@lucide/svelte/icons/archive';
import CalendarPlus from '@lucide/svelte/icons/calendar-plus';
import Clock from '@lucide/svelte/icons/clock';
import ListFilter from '@lucide/svelte/icons/list-filter';
import MailCheck from '@lucide/svelte/icons/mail-check';
import MoreHorizontal from '@lucide/svelte/icons/more-horizontal';
import Tag from '@lucide/svelte/icons/tag';
import Trash2 from '@lucide/svelte/icons/trash-2';
import Minus from '@lucide/svelte/icons/minus';
import Plus from '@lucide/svelte/icons/plus';
import { Button } from '@cio/ui/base/button';
import { Input } from '@cio/ui/base/input';
import { Separator } from '@cio/ui/base/separator';
import * as Popover from '@cio/ui/base/popover';
import * as Select from '@cio/ui/base/select';
import * as InputGroup from '@cio/ui/base/input-group';
import * as Tooltip from '@cio/ui/base/tooltip';
import * as ButtonGroup from '@cio/ui/base/button-group';
import * as DropdownMenu from '@cio/ui/base/dropdown-menu';
import { FIELDS } from './fields';
const { Story } = defineMeta({
title: 'Molecules/ButtonGroup',
component: ButtonGroup.Root,
parameters: {
layout: 'centered',
controls: {
include: FIELDS
}
},
tags: ['autodocs']
});
let label = $state('personal');
let voiceEnabled = $state(false);
const CURRENCIES = [
{
value: '$',
label: 'US Dollar'
},
{
value: '€',
label: 'Euro'
},
{
value: '£',
label: 'British Pound'
}
];
let currency = $state('$');
</script>
<Story name="Default">
{#snippet template()}
<ButtonGroup.Root>
<ButtonGroup.Root class="hidden sm:flex">
<Button variant="outline" size="icon" aria-label="Go Back">
<ArrowLeft />
</Button>
</ButtonGroup.Root>
<ButtonGroup.Root>
<Button variant="outline">Archive</Button>
<Button variant="outline">Report</Button>
</ButtonGroup.Root>
<ButtonGroup.Root>
<Button variant="outline">Snooze</Button>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="outline" size="icon" aria-label="More Options">
<MoreHorizontal />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end" class="w-52">
<DropdownMenu.Group>
<DropdownMenu.Item>
<MailCheck />
Mark as Read
</DropdownMenu.Item>
<DropdownMenu.Item>
<Archive />
Archive
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Group>
<DropdownMenu.Item>
<Clock />
Snooze
</DropdownMenu.Item>
<DropdownMenu.Item>
<CalendarPlus />
Add to Calendar
</DropdownMenu.Item>
<DropdownMenu.Item>
<ListFilter />
Add to List
</DropdownMenu.Item>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>
<Tag />
Label As...
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent>
<DropdownMenu.RadioGroup bind:value={label}>
<DropdownMenu.RadioItem value="personal">Personal</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="work">Work</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="other">Other</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Group>
<DropdownMenu.Item class="text-destructive focus:text-destructive">
<Trash2 />
Trash
</DropdownMenu.Item>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
</ButtonGroup.Root>
</ButtonGroup.Root>
{/snippet}
</Story>
<Story name="Orientation">
{#snippet template()}
<ButtonGroup.Root orientation="vertical" aria-label="Media controls" class="h-fit">
<Button variant="outline" size="icon">
<Plus />
</Button>
<Button variant="outline" size="icon">
<Minus />
</Button>
</ButtonGroup.Root>
{/snippet}
</Story>
<Story name="Sizes">
{#snippet template()}
<div class="flex flex-col items-start gap-8">
<ButtonGroup.Root>
<Button variant="outline" size="sm">Small</Button>
<Button variant="outline" size="sm">Button</Button>
<Button variant="outline" size="sm">Group</Button>
<Button variant="outline" size="icon-sm">
<Plus />
</Button>
</ButtonGroup.Root>
<ButtonGroup.Root>
<Button variant="outline">Default</Button>
<Button variant="outline">Button</Button>
<Button variant="outline">Group</Button>
<Button variant="outline" size="icon">
<Plus />
</Button>
</ButtonGroup.Root>
<ButtonGroup.Root>
<Button variant="outline" size="lg">Large</Button>
<Button variant="outline" size="lg">Button</Button>
<Button variant="outline" size="lg">Group</Button>
<Button variant="outline" size="icon-lg">
<Plus />
</Button>
</ButtonGroup.Root>
</div>
{/snippet}
</Story>
<Story name="Nested">
{#snippet template()}
<ButtonGroup.Root>
<ButtonGroup.Root>
<Button variant="outline" size="sm">1</Button>
<Button variant="outline" size="sm">2</Button>
<Button variant="outline" size="sm">3</Button>
<Button variant="outline" size="sm">4</Button>
<Button variant="outline" size="sm">5</Button>
</ButtonGroup.Root>
<ButtonGroup.Root>
<Button variant="outline" size="icon-sm" aria-label="Previous">
<ArrowLeft />
</Button>
<Button variant="outline" size="icon-sm" aria-label="Next">
<ArrowRight />
</Button>
</ButtonGroup.Root>
</ButtonGroup.Root>
{/snippet}
</Story>
<Story name="Separator">
{#snippet template()}
<ButtonGroup.Root>
<Button variant="secondary" size="sm">Copy</Button>
<ButtonGroup.Separator />
<Button variant="secondary" size="sm">Paste</Button>
</ButtonGroup.Root>
{/snippet}
</Story>
<Story name="Split">
{#snippet template()}
<ButtonGroup.Root>
<Button variant="secondary">Button</Button>
<ButtonGroup.Separator />
<Button variant="secondary" size="icon">
<Plus />
</Button>
</ButtonGroup.Root>
{/snippet}
</Story>
<Story name="Input">
{#snippet template()}
<ButtonGroup.Root>
<Input placeholder="Search..." />
<Button variant="outline" size="icon" aria-label="Search">
<Search />
</Button>
</ButtonGroup.Root>
{/snippet}
</Story>
<Story name="Input Group">
{#snippet template()}
<ButtonGroup.Root class="[--radius:9999rem]">
<ButtonGroup.Root>
<Button variant="outline" size="icon">
<Plus />
</Button>
</ButtonGroup.Root>
<ButtonGroup.Root>
<InputGroup.Root>
<InputGroup.Input
placeholder={voiceEnabled ? 'Record and send audio...' : 'Send a message...'}
disabled={voiceEnabled}
/>
<InputGroup.Addon align="inline-end">
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
{#snippet child({ props })}
<InputGroup.Button
{...props}
onclick={() => (voiceEnabled = !voiceEnabled)}
size="icon-xs"
data-active={voiceEnabled}
class="ui:data-[active=true]:bg-orange-100 ui:data-[active=true]:text-orange-700 ui:dark:data-[active=true]:bg-orange-800 ui:dark:data-[active=true]:text-orange-100"
aria-pressed={voiceEnabled}
>
<AudioLines />
</InputGroup.Button>
{/snippet}
</Tooltip.Trigger>
<Tooltip.Content>Voice Mode</Tooltip.Content>
</Tooltip.Root></Tooltip.Provider
>
</InputGroup.Addon>
</InputGroup.Root>
</ButtonGroup.Root>
</ButtonGroup.Root>
{/snippet}
</Story>
<Story name="Dropdown Menu">
{#snippet template()}
<ButtonGroup.Root>
<Button variant="outline">Follow</Button>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="outline" class="ps-2!">
<ChevronDown />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end" class="[--radius:1rem]">
<DropdownMenu.Group>
<DropdownMenu.Item>
<VolumeOff />
Mute Conversation
</DropdownMenu.Item>
<DropdownMenu.Item>
<Check />
Mark as Read
</DropdownMenu.Item>
<DropdownMenu.Item>
<AlertTriangle />
Report Conversation
</DropdownMenu.Item>
<DropdownMenu.Item>
<UserRoundX />
Block User
</DropdownMenu.Item>
<DropdownMenu.Item>
<Share />
Share Conversation
</DropdownMenu.Item>
<DropdownMenu.Item>
<Copy />
Copy Conversation
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Group>
<DropdownMenu.Item variant="destructive">
<Trash />
Delete Conversation
</DropdownMenu.Item>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
</ButtonGroup.Root>
{/snippet}
</Story>
<Story name="Select">
{#snippet template()}
<ButtonGroup.Root>
<ButtonGroup.Root>
<Select.Root type="single" bind:value={currency}>
<Select.Trigger class="font-mono">
{currency}
</Select.Trigger>
<Select.Content class="min-w-24">
{#each CURRENCIES as currencyOption (currencyOption.value)}
<Select.Item value={currencyOption.value}>
{currencyOption.value}
<span class="text-muted-foreground">{currencyOption.label}</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
<Input placeholder="10.00" pattern="[0-9]*" />
</ButtonGroup.Root>
<ButtonGroup.Root>
<Button aria-label="Send" size="icon" variant="outline">
<ArrowRight />
</Button>
</ButtonGroup.Root>
</ButtonGroup.Root>
{/snippet}
</Story>
<Story name="Popover">
{#snippet template()}
<ButtonGroup.Root>
<Button variant="outline">
<Bot />
Copilot
</Button>
<Popover.Root>
<Popover.Trigger>
{#snippet child({ props })}
<Button {...props} variant="outline" size="icon" aria-label="Open Popover">
<ChevronDown />
</Button>
{/snippet}
</Popover.Trigger>
<Popover.Content align="end" class="rounded-xl p-0 text-sm">
<div class="px-4 py-3">
<div class="text-sm font-medium">Agent Tasks</div>
</div>
<Separator />
<div class="*:[p:not(:last-child)]:mb-2 p-4 text-sm">
<Textarea placeholder="Describe your task in natural language." class="mb-4 resize-none" />
<p class="font-medium">Start a new task with Copilot</p>
<p class="text-muted-foreground">
Describe your task in natural language. Copilot will work in the background and open a pull request for
your review.
</p>
</div>
</Popover.Content>
</Popover.Root>
</ButtonGroup.Root>
{/snippet}
</Story>
@@ -0,0 +1 @@
export const FIELDS = ['class', 'orientation'] as string[];
@@ -0,0 +1,123 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import * as Card from '@cio/ui/base/card';
import { Button } from '@cio/ui/base/button';
import { Label } from '@cio/ui/base/label';
import { Input } from '@cio/ui/base/input';
import { FIELDS } from './fields';
const { Story } = defineMeta({
title: 'Molecules/Card',
component: Card.Root,
parameters: {
layout: 'centered',
controls: {
include: FIELDS
}
},
tags: ['autodocs']
});
let email = $state('');
let password = $state('');
</script>
<Story name="Default">
{#snippet template()}
<Card.Root class="w-full max-w-sm">
<Card.Header>
<Card.Title>Login to your account</Card.Title>
<Card.Description>Enter your email below to login to your account</Card.Description>
<Card.Action>
<Button variant="link">Sign Up</Button>
</Card.Action>
</Card.Header>
<Card.Content>
<form>
<div class="ui:flex ui:flex-col ui:gap-6">
<div class="ui:grid ui:gap-2">
<Label for="email">Email</Label>
<Input id="email" type="email" bind:value={email} placeholder="m@example.com" required />
</div>
<div class="ui:grid ui:gap-2">
<div class="ui:flex ui:items-center">
<Label for="password">Password</Label>
<a href="#" class="ui:ms-auto ui:inline-block ui:text-sm ui:underline-offset-4 hover:ui:underline">
Forgot your password?
</a>
</div>
<Input id="password" type="password" bind:value={password} required />
</div>
</div>
</form>
</Card.Content>
<Card.Footer class="ui:flex-col ui:gap-2">
<Button type="submit" class="ui:w-full">Login</Button>
<Button variant="outline" class="ui:w-full">Login with Google</Button>
</Card.Footer>
</Card.Root>
{/snippet}
</Story>
<Story name="Simple">
{#snippet template()}
<Card.Root class="ui:w-full ui:max-w-sm">
<Card.Header>
<Card.Title>Card Title</Card.Title>
<Card.Description>Card description goes here.</Card.Description>
</Card.Header>
<Card.Content>
<p>Card content goes here.</p>
</Card.Content>
</Card.Root>
{/snippet}
</Story>
<Story name="With Footer">
{#snippet template()}
<Card.Root class="ui:w-full ui:max-w-sm">
<Card.Header>
<Card.Title>Card Title</Card.Title>
<Card.Description>Card description goes here.</Card.Description>
</Card.Header>
<Card.Content>
<p>Card content goes here.</p>
</Card.Content>
<Card.Footer>
<Button size="sm">Action</Button>
</Card.Footer>
</Card.Root>
{/snippet}
</Story>
<Story name="With Action">
{#snippet template()}
<Card.Root class="ui:w-full ui:max-w-sm">
<Card.Header>
<Card.Title>Card Title</Card.Title>
<Card.Description>Card description goes here.</Card.Description>
<Card.Action>
<Button variant="ghost" size="sm">More</Button>
</Card.Action>
</Card.Header>
<Card.Content>
<p>Card content goes here.</p>
</Card.Content>
</Card.Root>
{/snippet}
</Story>
<Story name="No Header">
{#snippet template()}
<Card.Root class="ui:w-full ui:max-w-sm">
<Card.Content>
<p>Card content without header.</p>
</Card.Content>
<Card.Footer>
<Button size="sm">Action</Button>
</Card.Footer>
</Card.Root>
{/snippet}
</Story>
@@ -0,0 +1 @@
export const FIELDS = ['class'] as string[];
@@ -0,0 +1,102 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import { CopyButton } from '@cio/ui/base/copy-button';
import { Input } from '@cio/ui/base/input';
import { Button } from '@cio/ui/base/button';
import * as InputGroup from '@cio/ui/base/input-group';
import { FIELDS } from './fields';
const { Story } = defineMeta({
title: 'Molecules/CopyButton',
component: CopyButton,
parameters: {
layout: 'centered',
controls: {
include: FIELDS
}
},
tags: ['autodocs']
});
let textToCopy = $state('Hello, World!');
let copiedText = $state('');
</script>
<Story name="Default">
{#snippet template()}
<CopyButton text="Copy this text to clipboard" />
{/snippet}
</Story>
<Story name="With Text">
{#snippet template()}
<CopyButton text="npm install @cio/ui" variant="outline">Copy Command</CopyButton>
{/snippet}
</Story>
<Story name="Variants">
{#snippet template()}
<div class="ui:flex ui:flex-col ui:gap-4 ui:items-start">
<CopyButton text="Default variant" variant="default" />
<CopyButton text="Outline variant" variant="outline" />
<CopyButton text="Secondary variant" variant="secondary" />
<CopyButton text="Ghost variant" variant="ghost" />
<CopyButton text="Destructive variant" variant="destructive" />
</div>
{/snippet}
</Story>
<Story name="With Input Group">
{#snippet template()}
<InputGroup.Root class="ui:w-96">
<InputGroup.Input value="https://classroomio.com/courses/123" readonly />
<InputGroup.Addon align="inline-end">
<CopyButton text="https://classroomio.com/courses/123" size="icon-sm" />
</InputGroup.Addon>
</InputGroup.Root>
{/snippet}
</Story>
<Story name="Reactive Text">
{#snippet template()}
<div class="ui:flex ui:flex-col ui:gap-4 ui:w-96">
<InputGroup.Root>
<InputGroup.Input bind:value={textToCopy} placeholder="Enter text to copy" />
<InputGroup.Addon align="inline-end">
<CopyButton text={textToCopy} size="icon-sm" />
</InputGroup.Addon>
</InputGroup.Root>
<div class="ui:text-sm ui:text-muted-foreground">
<p>Text to copy: <code class="ui:bg-muted ui:px-2 ui:py-1 ui:rounded">{textToCopy}</code></p>
</div>
</div>
{/snippet}
</Story>
<Story name="Code Block">
{#snippet template()}
<div class="ui:rounded-lg ui:border ui:bg-muted/50 ui:p-4 ui:w-full ui:max-w-2xl">
<div class="ui:flex ui:items-center ui:justify-between ui:mb-2">
<span class="ui:text-sm ui:font-medium">Installation</span>
<CopyButton
text="pnpm add @cio/ui"
size="sm"
variant="ghost"
onCopy={(status) => {
if (status === 'success') {
copiedText = 'pnpm add @cio/ui';
}
}}
>
Copy
</CopyButton>
</div>
<pre class="ui:text-sm"><code>pnpm add @cio/ui</code></pre>
{#if copiedText}
<p class="ui:mt-2 ui:text-green-600">Last copied: {copiedText}</p>
{/if}
</div>
{/snippet}
</Story>
@@ -0,0 +1,11 @@
export const FIELDS = [
'ref',
'children',
'text',
'icon',
'animationDuration',
'variant',
'size',
'onCopy',
'class'
] as string[];
@@ -0,0 +1,146 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import FolderCodeIcon from '@lucide/svelte/icons/folder-code';
import CloudIcon from '@lucide/svelte/icons/cloud';
import BellIcon from '@lucide/svelte/icons/bell';
import PlusIcon from '@lucide/svelte/icons/plus';
import RefreshCcwIcon from '@lucide/svelte/icons/refresh-ccw';
import ArrowUpRightIcon from '@lucide/svelte/icons/arrow-up-right';
import * as Empty from '@cio/ui/base/empty';
import * as Avatar from '@cio/ui/base/avatar';
import { Button } from '@cio/ui/base/button';
import { FIELDS } from './fields';
const { Story } = defineMeta({
title: 'Molecules/Empty',
component: Empty.Root,
parameters: {
layout: 'centered',
controls: {
include: FIELDS
}
},
tags: ['autodocs']
});
</script>
<Story name="Default">
{#snippet template()}
<Empty.Root>
<Empty.Header>
<Empty.Media variant="icon">
<FolderCodeIcon />
</Empty.Media>
<Empty.Title>No Courses Yet</Empty.Title>
<Empty.Description>
You haven't created any courses yet. Get started by creating your first course.
</Empty.Description>
</Empty.Header>
<Empty.Content>
<div class="flex gap-2">
<Button>Create Course</Button>
<Button variant="outline">Import Project</Button>
</div>
</Empty.Content>
<Button variant="link" class="ui:text-muted-foreground" size="sm">
<a href="#/">
Learn More <ArrowUpRightIcon class="inline" />
</a>
</Button>
</Empty.Root>
{/snippet}
</Story>
<Story name="Outline">
{#snippet template()}
<Empty.Root class="border border-dashed">
<Empty.Header>
<Empty.Media variant="icon">
<CloudIcon />
</Empty.Media>
<Empty.Title>Cloud Storage Empty</Empty.Title>
<Empty.Description>Upload files to your cloud storage to access them anywhere.</Empty.Description>
</Empty.Header>
<Empty.Content>
<Button variant="outline" size="sm">Upload Files</Button>
</Empty.Content>
</Empty.Root>
{/snippet}
</Story>
<Story name="Background">
{#snippet template()}
<Empty.Root class="ui:from-muted/50 ui:to-background ui:h-full ui:bg-gradient-to-b ui:from-30%">
<Empty.Header>
<Empty.Media variant="icon">
<BellIcon />
</Empty.Media>
<Empty.Title>No Notifications</Empty.Title>
<Empty.Description>You're all caught up. New notifications will appear here.</Empty.Description>
</Empty.Header>
<Empty.Content>
<Button variant="outline" size="sm">
<RefreshCcwIcon />
Refresh
</Button>
</Empty.Content>
</Empty.Root>
{/snippet}
</Story>
<Story name="Avatar">
{#snippet template()}
<Empty.Root>
<Empty.Header>
<Empty.Media variant="default">
<Avatar.Root class="size-12">
<Avatar.Image src="https://github.com/shadcn.png" class="grayscale" />
<Avatar.Fallback>LR</Avatar.Fallback>
</Avatar.Root>
</Empty.Media>
<Empty.Title>User Offline</Empty.Title>
<Empty.Description>
This user is currently offline. You can leave a message to notify them or try again later.
</Empty.Description>
</Empty.Header>
<Empty.Content>
<Button size="sm">Leave Message</Button>
</Empty.Content>
</Empty.Root>
{/snippet}
</Story>
<Story name="Avatar Groups">
{#snippet template()}
<Empty.Root>
<Empty.Header>
<Empty.Media>
<div class="*:ring-background flex -space-x-2 *:size-12 *:ring-2 *:grayscale">
<Avatar.Root>
<Avatar.Image src="https://github.com/shadcn.png" alt="@shadcn" />
<Avatar.Fallback>CN</Avatar.Fallback>
</Avatar.Root>
<Avatar.Root>
<Avatar.Image src="https://github.com/maxleiter.png" alt="@maxleiter" />
<Avatar.Fallback>LR</Avatar.Fallback>
</Avatar.Root>
<Avatar.Root>
<Avatar.Image src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
<Avatar.Fallback>ER</Avatar.Fallback>
</Avatar.Root>
</div>
</Empty.Media>
<Empty.Title>No Team Members</Empty.Title>
<Empty.Description>Invite your team to collaborate on this project.</Empty.Description>
</Empty.Header>
<Empty.Content>
<Button size="sm">
<PlusIcon />
Invite Members
</Button>
</Empty.Content>
</Empty.Root>
{/snippet}
</Story>
@@ -0,0 +1 @@
export const FIELDS = ['class'] as string[];
@@ -0,0 +1,508 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import * as Field from '@cio/ui/base/field';
import { Input } from '@cio/ui/base/input';
import { Textarea } from '@cio/ui/base/textarea';
import { Button } from '@cio/ui/base/button';
import { Checkbox } from '@cio/ui/base/checkbox';
import * as Select from '@cio/ui/base/select';
import { Switch } from '@cio/ui/base/switch';
import * as RadioGroup from '@cio/ui/base/radio-group';
import { FIELDS } from './fields';
const { Story } = defineMeta({
title: 'Molecules/Field',
component: Field.Field,
parameters: {
layout: 'centered',
controls: {
include: FIELDS
}
},
tags: ['autodocs']
});
let courseType = $state();
let courseVersion = $state();
let assignmentType = $state();
let notificationPreference = $state('email');
let deliveryMethod = $state('live-class');
const courseTypes = [
{ value: 'live-class', label: 'Live Class' },
{ value: 'self-paced', label: 'Self-Paced' },
{ value: 'hybrid', label: 'Hybrid' }
];
const courseVersions = [
{ value: 'v1', label: 'Version 1' },
{ value: 'v2', label: 'Version 2' },
{ value: 'v3', label: 'Version 3' }
];
const assignmentTypes = [
{ value: 'essay', label: 'Essay' },
{ value: 'quiz', label: 'Quiz' },
{ value: 'project', label: 'Project' },
{ value: 'presentation', label: 'Presentation' }
];
const courseTypeLabel = $derived(courseTypes.find((t) => t.value === courseType)?.label ?? 'Select course type');
</script>
<Story name="Payment Form">
{#snippet template()}
<form class="w-lg">
<Field.Group>
<Field.Set>
<Field.Legend>Course Information</Field.Legend>
<Field.Description>Create a new course for your students</Field.Description>
<Field.Group>
<Field.Field>
<Field.Label for="course-title">Course Title</Field.Label>
<Input id="course-title" placeholder="Introduction to Web Development" required />
</Field.Field>
<Field.Field>
<Field.Label for="course-description">Description</Field.Label>
<Textarea
id="course-description"
placeholder="A comprehensive course covering HTML, CSS, and JavaScript fundamentals..."
rows={4}
class="resize-none"
/>
<Field.Description>Provide a brief overview of what students will learn</Field.Description>
</Field.Field>
<div class="grid grid-cols-2 gap-4">
<Field.Field>
<Field.Label for="course-type">Course Type</Field.Label>
<Select.Root type="single" bind:value={courseType}>
<Select.Trigger id="course-type">
<span>{courseTypeLabel}</span>
</Select.Trigger>
<Select.Content>
{#each courseTypes as type (type.value)}
<Select.Item value={type.value}>{type.label}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</Field.Field>
<Field.Field>
<Field.Label for="course-version">Version</Field.Label>
<Select.Root type="single" bind:value={courseVersion}>
<Select.Trigger id="course-version">
<span>{courseVersion || 'Select version'}</span>
</Select.Trigger>
<Select.Content>
{#each courseVersions as version (version.value)}
<Select.Item value={version.value}>{version.label}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</Field.Field>
</div>
</Field.Group>
</Field.Set>
<Field.Separator />
<Field.Set>
<Field.Legend>Course Settings</Field.Legend>
<Field.Description>Configure how your course will be delivered and accessed</Field.Description>
<Field.Group>
<Field.Field orientation="horizontal">
<Checkbox id="course-published" checked={false} />
<Field.Label for="course-published" class="font-normal">Publish course immediately</Field.Label>
</Field.Field>
<Field.Field orientation="horizontal">
<Checkbox id="course-certificate" checked={true} />
<Field.Label for="course-certificate" class="font-normal">
Enable certificate download upon completion
</Field.Label>
</Field.Field>
</Field.Group>
</Field.Set>
<Field.Separator />
<Field.Set>
<Field.Group>
<Field.Field>
<Field.Label for="course-overview">Course Overview</Field.Label>
<Textarea
id="course-overview"
placeholder="Welcome to this amazing course! In this course, you'll learn..."
class="resize-none"
rows={3}
/>
<Field.Description
>This message will be displayed to students when they first access the course</Field.Description
>
</Field.Field>
</Field.Group>
</Field.Set>
<Field.Field orientation="horizontal">
<Button type="submit">Create Course</Button>
<Button variant="outline" type="button">Cancel</Button>
</Field.Field>
</Field.Group>
</form>
{/snippet}
</Story>
<Story name="Basic Usage">
{#snippet template()}
<div class="w-lg">
<Field.Set>
<Field.Legend>Student Profile</Field.Legend>
<Field.Description>Update your profile information visible to teachers and classmates</Field.Description>
<Field.Group>
<Field.Field>
<Field.Label for="student-name">Full name</Field.Label>
<Input id="student-name" autoComplete="off" placeholder="John Doe" />
<Field.Description
>This will be displayed on your certificates and course completion records</Field.Description
>
</Field.Field>
<Field.Field>
<Field.Label for="student-email">Email</Field.Label>
<Input id="student-email" type="email" autoComplete="off" aria-invalid />
<Field.Error>This email is already registered. Please use a different email.</Field.Error>
</Field.Field>
<Field.Field orientation="horizontal">
<Checkbox id="email-notifications" checked={true} />
<Field.Label for="email-notifications">Receive email notifications about course updates</Field.Label>
</Field.Field>
</Field.Group>
</Field.Set>
</div>
{/snippet}
</Story>
<Story name="Input">
{#snippet template()}
<div class="w-lg">
<Field.Set>
<Field.Group>
<Field.Field>
<Field.Label for="login-email">Email</Field.Label>
<Input id="login-email" type="email" placeholder="student@example.com" />
<Field.Description>Enter the email address associated with your account</Field.Description>
</Field.Field>
<Field.Field>
<Field.Label for="login-password">Password</Field.Label>
<Field.Description>Must be at least 8 characters long</Field.Description>
<Input id="login-password" type="password" placeholder="********" />
</Field.Field>
</Field.Group>
</Field.Set>
</div>
{/snippet}
</Story>
<Story name="Textarea">
{#snippet template()}
<div class="w-lg">
<Field.Set>
<Field.Group>
<Field.Field>
<Field.Label for="assignment-answer">Assignment Answer</Field.Label>
<Textarea
id="assignment-answer"
placeholder="Write your response here. Be sure to address all parts of the question..."
rows={8}
/>
<Field.Description>
Submit your completed assignment. You can edit your submission before the deadline.
</Field.Description>
</Field.Field>
</Field.Group>
</Field.Set>
</div>
{/snippet}
</Story>
<Story name="Select">
{#snippet template()}
<div class="w-lg">
<Field.Field>
<Field.Label for="assignment-type">Assignment Type</Field.Label>
<Select.Root type="single" bind:value={assignmentType}>
<Select.Trigger id="assignment-type">
<span>{assignmentType || 'Select assignment type'}</span>
</Select.Trigger>
<Select.Content>
{#each assignmentTypes as type (type.value)}
<Select.Item value={type.value}>{type.label}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
<Field.Description>Choose the type of assignment you want to create for your students</Field.Description>
</Field.Field>
</div>
{/snippet}
</Story>
<Story name="Field Set">
{#snippet template()}
<div class="@container w-lg">
<!-- <div class="max-w-sm" style="width: 512px;"> -->
<Field.Set>
<Field.Legend>Enrollment Information</Field.Legend>
<Field.Description>Add a new student to your course</Field.Description>
<Field.Group>
<Field.Field>
<Field.Label for="student-email-enroll">Student Email</Field.Label>
<Input id="student-email-enroll" type="email" placeholder="student@example.com" />
</Field.Field>
<div class="flex justify-between gap-4">
<Field.Field>
<Field.Label for="enrollment-date">Enrollment Date</Field.Label>
<Input id="enrollment-date" type="date" />
</Field.Field>
<Field.Field>
<Field.Label for="access-level">Access Level</Field.Label>
<Select.Root type="single">
<Select.Trigger id="access-level">
<span>Full Access</span>
</Select.Trigger>
<Select.Content>
<Select.Item value="full">Full Access</Select.Item>
<Select.Item value="limited">Limited Access</Select.Item>
<Select.Item value="readonly">Read Only</Select.Item>
</Select.Content>
</Select.Root>
</Field.Field>
</div>
</Field.Group>
</Field.Set>
</div>
{/snippet}
</Story>
<Story name="Checkbox">
{#snippet template()}
<div class="w-lg">
<Field.Group>
<Field.Set>
<Field.Legend variant="label">Course Features</Field.Legend>
<Field.Description>Enable features available to students in this course</Field.Description>
<Field.Group class="gap-3">
<Field.Field orientation="horizontal">
<Checkbox id="course-forum" checked />
<Field.Label for="course-forum" class="font-normal">Enable course forum</Field.Label>
</Field.Field>
<Field.Field orientation="horizontal">
<Checkbox id="course-quiz" checked />
<Field.Label for="course-quiz" class="font-normal">Enable live quizzes</Field.Label>
</Field.Field>
<Field.Field orientation="horizontal">
<Checkbox id="course-assignments" checked />
<Field.Label for="course-assignments" class="font-normal">Enable assignments</Field.Label>
</Field.Field>
<Field.Field orientation="horizontal">
<Checkbox id="course-certificates" />
<Field.Label for="course-certificates" class="font-normal">Enable certificate generation</Field.Label>
</Field.Field>
</Field.Group>
</Field.Set>
<Field.Separator />
<Field.Field orientation="horizontal">
<Checkbox id="ai-assistance" checked />
<Field.Content>
<Field.Label for="ai-assistance">AI-Powered Course Creation</Field.Label>
<Field.Description>
Use AI to generate course content, lesson outlines, and assignments from your notes. This feature helps
you create comprehensive courses faster.
</Field.Description>
</Field.Content>
</Field.Field>
</Field.Group>
</div>
{/snippet}
</Story>
<Story name="Radio">
{#snippet template()}
<div class="w-lg">
<Field.Set>
<Field.Label>Course Delivery Method</Field.Label>
<Field.Description>Choose how your course will be delivered to students</Field.Description>
<RadioGroup.Root bind:value={deliveryMethod}>
<Field.Field orientation="horizontal">
<RadioGroup.Item value="live-class" id="type-live" />
<Field.Label for="type-live" class="font-normal">
Live Class - Scheduled sessions with real-time interaction
</Field.Label>
</Field.Field>
<Field.Field orientation="horizontal">
<RadioGroup.Item value="self-paced" id="type-self-paced" />
<Field.Label for="type-self-paced" class="font-normal">
Self-Paced - Students learn at their own speed
</Field.Label>
</Field.Field>
<Field.Field orientation="horizontal">
<RadioGroup.Item value="hybrid" id="type-hybrid" />
<Field.Label for="type-hybrid" class="font-normal">
Hybrid - Combination of live sessions and self-paced content
</Field.Label>
</Field.Field>
</RadioGroup.Root>
</Field.Set>
</div>
{/snippet}
</Story>
<Story name="Switch">
{#snippet template()}
<div class="w-lg">
<Field.Field orientation="horizontal">
<Field.Content>
<Field.Label for="publish-course">Publish Course</Field.Label>
<Field.Description>
Make this course visible to enrolled students. Unpublished courses are only visible to teachers and admins.
</Field.Description>
</Field.Content>
<Switch id="publish-course" />
</Field.Field>
</div>
{/snippet}
</Story>
<Story name="Choice Card">
{#snippet template()}
<div class="w-lg">
<Field.Group>
<Field.Set>
<Field.Label for="delivery-method-p8w">Course Delivery Method</Field.Label>
<Field.Description>Choose how students will access and interact with your course content</Field.Description>
<RadioGroup.Root bind:value={deliveryMethod}>
<Field.Label for="live-class-r2h">
<Field.Field orientation="horizontal">
<Field.Content>
<Field.Title>Live Class</Field.Title>
<Field.Description>
Scheduled sessions with real-time interaction. Perfect for bootcamps and structured learning
programs.
</Field.Description>
</Field.Content>
<RadioGroup.Item value="live-class" id="live-class-r2h" />
</Field.Field>
</Field.Label>
<Field.Label for="self-paced-z4k">
<Field.Field orientation="horizontal">
<Field.Content>
<Field.Title>Self-Paced</Field.Title>
<Field.Description>
Students learn at their own speed. Access all content immediately and complete on their schedule.
</Field.Description>
</Field.Content>
<RadioGroup.Item value="self-paced" id="self-paced-z4k" />
</Field.Field>
</Field.Label>
</RadioGroup.Root>
</Field.Set>
</Field.Group>
</div>
{/snippet}
</Story>
<Story name="Field Group">
{#snippet template()}
<div class="w-lg">
<Field.Group>
<Field.Set>
<Field.Label>Course Updates</Field.Label>
<Field.Description>
Get notified when new lessons, assignments, or announcements are added to your enrolled courses.
</Field.Description>
<Field.Group data-slot="checkbox-group">
<Field.Field orientation="horizontal">
<Checkbox id="course-updates-email" checked disabled />
<Field.Label for="course-updates-email" class="font-normal">Email notifications</Field.Label>
</Field.Field>
</Field.Group>
</Field.Set>
<Field.Separator />
<Field.Set>
<Field.Label>Assignment Reminders</Field.Label>
<Field.Description>
Get notified about upcoming assignment deadlines and when teachers grade your submissions.
<a href="#/">Manage preferences</a>
</Field.Description>
<Field.Group data-slot="checkbox-group">
<Field.Field orientation="horizontal">
<Checkbox id="assignment-reminders-email" />
<Field.Label for="assignment-reminders-email" class="font-normal">Email notifications</Field.Label>
</Field.Field>
<Field.Field orientation="horizontal">
<Checkbox id="assignment-reminders-push" />
<Field.Label for="assignment-reminders-push" class="font-normal">Push notifications</Field.Label>
</Field.Field>
</Field.Group>
</Field.Set>
</Field.Group>
</div>
{/snippet}
</Story>
<Story name="Responsive Layout">
{#snippet template()}
<div class="w-full max-w-4xl">
<form>
<Field.Set>
<Field.Legend>Teacher Profile</Field.Legend>
<Field.Description>Update your teacher profile information</Field.Description>
<Field.Separator />
<Field.Group>
<Field.Field orientation="responsive">
<Field.Content>
<Field.Label for="teacher-name">Name</Field.Label>
<Field.Description>Your name as it will appear to students</Field.Description>
</Field.Content>
<Input id="teacher-name" placeholder="Dr. Jane Smith" required />
</Field.Field>
<Field.Separator />
<Field.Field orientation="responsive">
<Field.Content>
<Field.Label for="teacher-bio">Bio</Field.Label>
<Field.Description>
Write a brief bio about your teaching experience and expertise. This will be visible to students.
</Field.Description>
</Field.Content>
<Textarea
id="teacher-bio"
placeholder="I'm an experienced educator with 10+ years of teaching web development..."
required
class="min-h-[100px] resize-none sm:min-w-[300px]"
/>
</Field.Field>
<Field.Separator />
<Field.Field orientation="responsive">
<Button type="submit">Save Profile</Button>
<Button type="button" variant="outline">Cancel</Button>
</Field.Field>
</Field.Group>
</Field.Set>
</form>
</div>
{/snippet}
</Story>
<Story name="Validation and Errors">
{#snippet template()}
<div class="w-sm">
<Field.Set>
<Field.Group>
<Field.Field data-invalid>
<Field.Label for="student-email-invalid">Student Email</Field.Label>
<Input id="student-email-invalid" type="email" aria-invalid placeholder="invalid-email" />
<Field.Error>Enter a valid email address to enroll the student.</Field.Error>
</Field.Field>
<Field.Field>
<Field.Label for="student-email-valid">Student Email</Field.Label>
<Input id="student-email-valid" type="email" placeholder="student@example.com" />
<Field.Description>This email address is valid and ready for enrollment.</Field.Description>
</Field.Field>
</Field.Group>
</Field.Set>
</div>
{/snippet}
</Story>
@@ -0,0 +1 @@
export const FIELDS = ['class'] as string[];
@@ -0,0 +1,2 @@
export const FIELDS = ['class'] as string[];
@@ -0,0 +1,121 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import CalendarDaysIcon from '@lucide/svelte/icons/calendar-days';
import UsersIcon from '@lucide/svelte/icons/users';
import BookOpenIcon from '@lucide/svelte/icons/book-open';
import * as HoverCard from '@cio/ui/base/hover-card';
import * as Avatar from '@cio/ui/base/avatar';
import { FIELDS } from './fields';
const { Story } = defineMeta({
title: 'Molecules/HoverCard',
component: HoverCard.Root,
parameters: {
layout: 'centered',
controls: {
include: FIELDS
}
},
tags: ['autodocs']
});
</script>
<Story name="Default">
{#snippet template()}
<HoverCard.Root>
<HoverCard.Trigger>Introduction to Web Development</HoverCard.Trigger>
<HoverCard.Content>Learn the fundamentals of modern web development with hands-on projects.</HoverCard.Content>
</HoverCard.Root>
{/snippet}
</Story>
<Story name="Instructor Profile">
{#snippet template()}
<HoverCard.Root>
<HoverCard.Trigger
href="/instructors/sarah-johnson"
class="ui:rounded-sm ui:underline-offset-4 ui:hover:underline ui:focus-visible:outline-2 ui:focus-visible:outline-offset-8 ui:focus-visible:outline-black ui:flex ui:items-center ui:gap-2"
>
<Avatar.Root>
<Avatar.Image src="https://github.com/shadcn.png" />
<Avatar.Fallback>SJ</Avatar.Fallback>
</Avatar.Root> @sarah-johnson
</HoverCard.Trigger>
<HoverCard.Content class="w-80">
<div class="flex justify-between space-x-4">
<Avatar.Root>
<Avatar.Image src="https://github.com/shadcn.png" />
<Avatar.Fallback>SJ</Avatar.Fallback>
</Avatar.Root>
<div class="space-y-1">
<h4 class="text-sm font-semibold">Sarah Johnson</h4>
<p class="text-sm">Senior Full-Stack Developer & Instructor</p>
<div class="flex items-center pt-2">
<CalendarDaysIcon class="me-2 size-4 opacity-70" />
<span class="text-muted-foreground text-xs">Teaching since January 2020</span>
</div>
</div>
</div>
</HoverCard.Content>
</HoverCard.Root>
{/snippet}
</Story>
<Story name="Course Preview">
{#snippet template()}
<HoverCard.Root>
<HoverCard.Trigger href="/courses/advanced-react" class="text-primary underline-offset-4 hover:underline">
Advanced React Patterns
</HoverCard.Trigger>
<HoverCard.Content class="w-64">
<div class="space-y-2">
<h4 class="text-sm font-semibold">Advanced React Patterns</h4>
<p class="text-muted-foreground text-sm">
Master advanced React concepts including hooks, context, performance optimization, and modern patterns used
in production applications.
</p>
</div>
</HoverCard.Content>
</HoverCard.Root>
{/snippet}
</Story>
<Story name="Student Profile">
{#snippet template()}
<HoverCard.Root>
<HoverCard.Trigger class="text-primary ui:flex ui:items-center ui:gap-2 underline-offset-4 hover:underline">
<Avatar.Root>
<Avatar.Image src="https://github.com/rotimi-best.png" />
<Avatar.Fallback>AM</Avatar.Fallback>
</Avatar.Root>
@rotimi-best
</HoverCard.Trigger>
<HoverCard.Content class="w-80">
<div class="space-y-3">
<div class="flex items-center gap-3">
<Avatar.Root>
<Avatar.Image src="https://github.com/rotimi-best.png" />
<Avatar.Fallback>AM</Avatar.Fallback>
</Avatar.Root>
<div>
<h4 class="text-sm font-semibold">Alex Martinez</h4>
<p class="text-muted-foreground text-xs">Student</p>
</div>
</div>
<div class="space-y-2">
<div class="text-muted-foreground flex items-center gap-2 text-xs">
<BookOpenIcon class="size-4" />
<span>5 courses enrolled</span>
</div>
<div class="text-muted-foreground flex items-center gap-2 text-xs">
<UsersIcon class="size-4" />
<span>Active since March 2024</span>
</div>
</div>
</div>
</HoverCard.Content>
</HoverCard.Root>
{/snippet}
</Story>
@@ -0,0 +1,2 @@
export const FIELDS = ['class'] as string[];
@@ -0,0 +1,411 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import SearchIcon from '@lucide/svelte/icons/search';
import MailIcon from '@lucide/svelte/icons/mail';
import LockIcon from '@lucide/svelte/icons/lock';
import CreditCardIcon from '@lucide/svelte/icons/credit-card';
import UserIcon from '@lucide/svelte/icons/user';
import MessageSquareIcon from '@lucide/svelte/icons/message-square';
import GlobeIcon from '@lucide/svelte/icons/globe';
import LoaderCircleIcon from '@lucide/svelte/icons/loader-circle';
import EyeIcon from '@lucide/svelte/icons/eye';
import EyeOffIcon from '@lucide/svelte/icons/eye-off';
import KeyIcon from '@lucide/svelte/icons/key';
import FileTextIcon from '@lucide/svelte/icons/file-text';
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
import PlusIcon from '@lucide/svelte/icons/plus';
import CopyIcon from '@lucide/svelte/icons/copy';
import MoreHorizontalIcon from '@lucide/svelte/icons/more-horizontal';
import StarIcon from '@lucide/svelte/icons/star';
import InfoIcon from '@lucide/svelte/icons/info';
import LoaderIcon from '@lucide/svelte/icons/loader';
import Link2Icon from '@lucide/svelte/icons/link-2';
import { CopyButton } from '@cio/ui/base/copy-button';
import * as InputGroup from '@cio/ui/base/input-group';
import { Input } from '@cio/ui/base/input';
import { Textarea } from '@cio/ui/base/textarea';
import { Label } from '@cio/ui/base/label';
import { Button } from '@cio/ui/base/button';
import { Spinner } from '@cio/ui/base/spinner';
import * as Tooltip from '@cio/ui/base/tooltip';
import * as DropdownMenu from '@cio/ui/base/dropdown-menu';
import * as ButtonGroup from '@cio/ui/base/button-group';
import { Kbd } from '@cio/ui/base/kbd';
import { FIELDS } from './fields';
const { Story } = defineMeta({
title: 'Molecules/InputGroup',
component: InputGroup.Root,
parameters: {
layout: 'centered',
controls: {
include: FIELDS
}
},
tags: ['autodocs']
});
let email = $state('');
let password = $state('');
let showPassword = $state(false);
let apiKey = $state('');
let searchQuery = $state('');
let fileName = $state('');
let username = $state('');
let message = $state('');
let code = $state("console.log('Hello, world!');");
let customInput = $state('Autoresize textarea...');
let domain = $state('example.com');
let amount = $state('0.00');
let cardNumber = $state('');
let fileType = $state('txt');
</script>
<Story name="Icon">
{#snippet template()}
<div class="grid w-full max-w-sm gap-6">
<InputGroup.Root>
<InputGroup.Addon align="inline-start">
<SearchIcon />
</InputGroup.Addon>
<InputGroup.Input placeholder="Search..." />
</InputGroup.Root>
<InputGroup.Root>
<InputGroup.Addon align="inline-start">
<MailIcon />
</InputGroup.Addon>
<InputGroup.Input type="email" placeholder="Enter your email" />
</InputGroup.Root>
<InputGroup.Root>
<InputGroup.Addon align="inline-start">
<CreditCardIcon />
</InputGroup.Addon>
<InputGroup.Input placeholder="Card number" />
</InputGroup.Root>
<InputGroup.Root>
<InputGroup.Input placeholder="Card number" />
<InputGroup.Addon align="inline-end">
<StarIcon />
<InfoIcon />
</InputGroup.Addon>
</InputGroup.Root>
</div>
{/snippet}
</Story>
<Story name="Text">
{#snippet template()}
<div class="ui:flex ui:flex-col ui:gap-4 ui:w-96">
<InputGroup.Root>
<InputGroup.Addon align="inline-start">
<InputGroup.Text>https://</InputGroup.Text>
</InputGroup.Addon>
<InputGroup.Input placeholder="example.com" />
</InputGroup.Root>
<InputGroup.Root>
<InputGroup.Addon align="inline-start">
<InputGroup.Text>$</InputGroup.Text>
</InputGroup.Addon>
<InputGroup.Input type="number" placeholder="0.00" />
</InputGroup.Root>
<InputGroup.Root>
<InputGroup.Addon align="inline-start">
<InputGroup.Text>@</InputGroup.Text>
</InputGroup.Addon>
<InputGroup.Input placeholder="Enter your username" />
</InputGroup.Root>
<InputGroup.Root>
<InputGroup.Addon align="block-start">
<InputGroup.Text>Note</InputGroup.Text>
</InputGroup.Addon>
<InputGroup.Textarea placeholder="Enter your message" />
</InputGroup.Root>
</div>
{/snippet}
</Story>
<Story name="Button">
{#snippet template()}
<div class="ui:flex ui:flex-col ui:gap-4 ui:w-96">
<InputGroup.Root>
<InputGroup.Input placeholder="https://x.com/shadcn" />
<InputGroup.Addon align="inline-end">
<InputGroup.Button>
<CopyIcon />
</InputGroup.Button>
</InputGroup.Addon>
</InputGroup.Root>
<InputGroup.Root>
<InputGroup.Input placeholder="Type to search..." />
<InputGroup.Addon align="inline-end">
<InputGroup.Button>
<SearchIcon />
</InputGroup.Button>
</InputGroup.Addon>
</InputGroup.Root>
</div>
{/snippet}
</Story>
<Story name="Tooltip">
{#snippet template()}
<div class="ui:flex ui:flex-col ui:gap-4 ui:w-96">
<InputGroup.Root>
<InputGroup.Addon align="inline-start">
<LockIcon />
</InputGroup.Addon>
<InputGroup.Input type={showPassword ? 'text' : 'password'} placeholder="Enter password" />
<InputGroup.Addon align="inline-end">
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
{#snippet child({ props })}
<InputGroup.Button
{...props}
onclick={() => (showPassword = !showPassword)}
aria-label={showPassword ? 'Hide password' : 'Show password'}
>
{#if showPassword}
<EyeOffIcon />
{:else}
<EyeIcon />
{/if}
</InputGroup.Button>
{/snippet}
</Tooltip.Trigger>
<Tooltip.Content>{showPassword ? 'Hide password' : 'Show password'}</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
</InputGroup.Addon>
</InputGroup.Root>
<InputGroup.Root>
<InputGroup.Addon align="inline-start">
<MailIcon />
</InputGroup.Addon>
<InputGroup.Input type="email" placeholder="Your email address" />
<InputGroup.Addon align="inline-end">
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
{#snippet child({ props })}
<InputGroup.Button {...props} aria-label="Help">
<MessageSquareIcon />
</InputGroup.Button>
{/snippet}
</Tooltip.Trigger>
<Tooltip.Content>We'll never share your email</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
</InputGroup.Addon>
</InputGroup.Root>
<InputGroup.Root>
<InputGroup.Addon align="inline-start">
<KeyIcon />
</InputGroup.Addon>
<InputGroup.Input type="password" placeholder="Enter API key" />
<InputGroup.Addon align="inline-end">
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
{#snippet child({ props })}
<InputGroup.Button {...props} aria-label="Generate API key">
<KeyIcon />
</InputGroup.Button>
{/snippet}
</Tooltip.Trigger>
<Tooltip.Content>Generate a new API key</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
</InputGroup.Addon>
</InputGroup.Root>
</div>
{/snippet}
</Story>
<Story name="Textarea">
{#snippet template()}
<div class="ui:flex ui:flex-col ui:gap-4 ui:w-96">
<InputGroup.Root>
<InputGroup.Addon align="block-start">
<InputGroup.Text>Code</InputGroup.Text>
</InputGroup.Addon>
<InputGroup.Textarea placeholder="console.log('Hello, world!');" />
</InputGroup.Root>
<InputGroup.Root>
<InputGroup.Addon align="block-end">
<InputGroup.Text>Note</InputGroup.Text>
</InputGroup.Addon>
<InputGroup.Textarea placeholder="Enter your message" />
</InputGroup.Root>
</div>
{/snippet}
</Story>
<Story name="Spinner">
{#snippet template()}
<div class="grid w-full max-w-sm gap-4">
<InputGroup.Root data-disabled>
<InputGroup.Input placeholder="Searching..." disabled />
<InputGroup.Addon align="inline-end">
<Spinner />
</InputGroup.Addon>
</InputGroup.Root>
<InputGroup.Root data-disabled>
<InputGroup.Input placeholder="Processing..." disabled />
<InputGroup.Addon>
<Spinner />
</InputGroup.Addon>
</InputGroup.Root>
<InputGroup.Root data-disabled>
<InputGroup.Input placeholder="Saving changes..." disabled />
<InputGroup.Addon align="inline-end">
<InputGroup.Text>Saving...</InputGroup.Text>
<Spinner />
</InputGroup.Addon>
</InputGroup.Root>
<InputGroup.Root data-disabled>
<InputGroup.Input placeholder="Refreshing data..." disabled />
<InputGroup.Addon>
<LoaderIcon class="animate-spin" />
</InputGroup.Addon>
<InputGroup.Addon align="inline-end">
<InputGroup.Text class="text-muted-foreground">Please wait...</InputGroup.Text>
</InputGroup.Addon>
</InputGroup.Root>
</div>
{/snippet}
</Story>
<Story name="Label">
{#snippet template()}
<div class="grid w-full max-w-sm gap-4">
<InputGroup.Root>
<InputGroup.Input id="email" placeholder="shadcn" />
<InputGroup.Addon>
<Label for="email">@</Label>
</InputGroup.Addon>
</InputGroup.Root>
<InputGroup.Root>
<InputGroup.Input id="email-2" placeholder="shadcn@vercel.com" />
<InputGroup.Addon align="block-start">
<Label for="email-2" class="ui:text-foreground">Email</Label>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
{#snippet child({ props })}
<InputGroup.Button
{...props}
variant="ghost"
aria-label="Help"
class="ms-auto rounded-full"
size="icon-xs"
>
<InfoIcon />
</InputGroup.Button>
{/snippet}
</Tooltip.Trigger>
<Tooltip.Content>
<p>We'll use this to send you notifications</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
</InputGroup.Addon>
</InputGroup.Root>
</div>
{/snippet}
</Story>
<Story name="Dropdown">
{#snippet template()}
<div class="grid w-full max-w-sm gap-4">
<InputGroup.Root>
<InputGroup.Input placeholder="Enter file name" />
<InputGroup.Addon align="inline-end">
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<InputGroup.Button {...props} variant="ghost" aria-label="More" size="icon-xs">
<MoreHorizontalIcon />
</InputGroup.Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Item>Settings</DropdownMenu.Item>
<DropdownMenu.Item>Copy path</DropdownMenu.Item>
<DropdownMenu.Item>Open location</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</InputGroup.Addon>
</InputGroup.Root>
<InputGroup.Root class="[--radius:1rem]">
<InputGroup.Input placeholder="Enter search query" />
<InputGroup.Addon align="inline-end">
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<InputGroup.Button {...props} variant="ghost" class="!pe-1.5 text-xs">
Search In... <ChevronDownIcon class="size-3" />
</InputGroup.Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end" class="[--radius:0.95rem]">
<DropdownMenu.Item>Documentation</DropdownMenu.Item>
<DropdownMenu.Item>Blog Posts</DropdownMenu.Item>
<DropdownMenu.Item>Changelog</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</InputGroup.Addon>
</InputGroup.Root>
</div>
{/snippet}
</Story>
<Story name="Button Group">
{#snippet template()}
<div class="grid w-full max-w-sm gap-6">
<ButtonGroup.Root>
<ButtonGroup.Text>
<Label for="url">https://</Label>
</ButtonGroup.Text>
<InputGroup.Root>
<InputGroup.Input id="url" />
<InputGroup.Addon align="inline-end">
<Link2Icon />
</InputGroup.Addon>
</InputGroup.Root>
<ButtonGroup.Text>.com</ButtonGroup.Text>
</ButtonGroup.Root>
</div>
{/snippet}
</Story>
<Story name="Custom Input">
{#snippet template()}
<div class="w-lg grid gap-6">
<InputGroup.Root>
<textarea
data-slot="input-group-control"
class="field-sizing-content flex min-h-16 w-full resize-none rounded-md bg-transparent px-3 py-2.5 text-base outline-none transition-[color,box-shadow] md:text-sm"
placeholder="Autoresize textarea..."
></textarea>
<InputGroup.Addon align="block-end">
<InputGroup.Button class="ms-auto" size="sm" variant="default">Submit</InputGroup.Button>
</InputGroup.Addon>
</InputGroup.Root>
</div>
{/snippet}
</Story>
@@ -0,0 +1,2 @@
export const FIELDS = ['class'] as string[];
@@ -0,0 +1,387 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import BookIcon from '@lucide/svelte/icons/book';
import GraduationCapIcon from '@lucide/svelte/icons/graduation-cap';
import UsersIcon from '@lucide/svelte/icons/users';
import SearchIcon from '@lucide/svelte/icons/search';
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
import PlusIcon from '@lucide/svelte/icons/plus';
import FilterIcon from '@lucide/svelte/icons/filter';
import DownloadIcon from '@lucide/svelte/icons/download';
import MoreHorizontalIcon from '@lucide/svelte/icons/more-horizontal';
import FolderCodeIcon from '@lucide/svelte/icons/folder-code';
import UserPlusIcon from '@lucide/svelte/icons/user-plus';
import FileTextIcon from '@lucide/svelte/icons/file-text';
import SettingsIcon from '@lucide/svelte/icons/settings';
import TrashIcon from '@lucide/svelte/icons/trash';
import ShareIcon from '@lucide/svelte/icons/share';
import ArchiveIcon from '@lucide/svelte/icons/archive';
import ArrowUpRightIcon from '@lucide/svelte/icons/arrow-up-right';
import * as Page from '@cio/ui/base/page';
import * as ButtonGroup from '@cio/ui/base/button-group';
import * as Empty from '@cio/ui/base/empty';
import * as DropdownMenu from '@cio/ui/base/dropdown-menu';
import * as InputGroup from '@cio/ui/base/input-group';
import { Button } from '@cio/ui/base/button';
import { FIELDS } from './fields';
const { Story } = defineMeta({
title: 'Molecules/Page',
component: Page.Root,
parameters: {
layout: 'centered',
controls: {
include: FIELDS
}
},
tags: ['autodocs']
});
</script>
<Story name="Default">
{#snippet template()}
<Page.Root class="w-4xl h-4xl">
<Page.Header>
<Page.HeaderContent>
<Page.Title>Courses</Page.Title>
<Page.Subtitle>Manage and organize your course offerings.</Page.Subtitle>
</Page.HeaderContent>
<Page.Action>
<Button variant="default">
<PlusIcon />
New Course
</Button>
<Button variant="outline">
Visit Site
<ArrowUpRightIcon />
</Button>
</Page.Action>
</Page.Header>
<Page.Body>
{#snippet child()}
<Empty.Root class="border border-dashed">
<Empty.Header>
<Empty.Media variant="icon">
<FolderCodeIcon />
</Empty.Media>
<Empty.Title>No Courses Yet</Empty.Title>
<Empty.Description>
You haven't created any courses yet. Get started by creating your first course.
</Empty.Description>
</Empty.Header>
<Empty.Content>
<div class="flex gap-2">
<Button>
<PlusIcon />
Create Course
</Button>
<Button variant="outline">Import Course</Button>
</div>
</Empty.Content>
</Empty.Root>
{/snippet}
</Page.Body>
</Page.Root>
{/snippet}
</Story>
<Story name="Courses">
{#snippet template()}
<Page.Root>
<Page.Header>
<Page.HeaderContent>
<Page.Title>Courses</Page.Title>
<Page.Subtitle>Manage and organize your course offerings.</Page.Subtitle>
</Page.HeaderContent>
<Page.Action>
<Button>
<PlusIcon />
New Course
</Button>
</Page.Action>
</Page.Header>
<Page.Body>
<Page.BodyHeader align="right">
<ButtonGroup.Root>
<InputGroup.Root>
<InputGroup.Input placeholder="Search courses..." />
<InputGroup.Addon>
<SearchIcon />
</InputGroup.Addon>
</InputGroup.Root>
<ButtonGroup.Root>
<Button variant="outline">
<FilterIcon />
Filter
</Button>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="outline" class="ps-2!">
<ChevronDownIcon />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end" class="[--radius:1rem]">
<DropdownMenu.Group>
<DropdownMenu.Item>
<DownloadIcon />
Export Courses
</DropdownMenu.Item>
<DropdownMenu.Item>
<ShareIcon />
Share Courses
</DropdownMenu.Item>
<DropdownMenu.Item>
<ArchiveIcon />
Archive Selected
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Group>
<DropdownMenu.Item variant="destructive">
<TrashIcon />
Delete Selected
</DropdownMenu.Item>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
</ButtonGroup.Root>
</ButtonGroup.Root>
</Page.BodyHeader>
{#snippet child()}
<Empty.Root>
<Empty.Header>
<Empty.Media variant="icon">
<FolderCodeIcon />
</Empty.Media>
<Empty.Title>No Courses Yet</Empty.Title>
<Empty.Description>
You haven't created any courses yet. Get started by creating your first course.
</Empty.Description>
</Empty.Header>
<Empty.Content>
<div class="flex gap-2">
<Button>
<PlusIcon />
Create Course
</Button>
<Button variant="outline">Import Course</Button>
</div>
</Empty.Content>
</Empty.Root>
{/snippet}
</Page.Body>
</Page.Root>
{/snippet}
</Story>
<Story name="Classes">
{#snippet template()}
<Page.Root>
<Page.Header>
<Page.HeaderContent>
<Page.Title>Classes</Page.Title>
<Page.Subtitle>View and manage your class schedules and sessions.</Page.Subtitle>
</Page.HeaderContent>
<Page.Action>
<ButtonGroup.Root>
<ButtonGroup.Root>
<Input placeholder="Search classes..." />
<Button variant="outline" size="icon" aria-label="Search">
<SearchIcon />
</Button>
</ButtonGroup.Root>
<ButtonGroup.Root>
<Button variant="outline">
<FilterIcon />
Filter
</Button>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="outline" class="ps-2!">
<ChevronDownIcon />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end" class="[--radius:1rem]">
<DropdownMenu.Group>
<DropdownMenu.Item>
<DownloadIcon />
Export Schedule
</DropdownMenu.Item>
<DropdownMenu.Item>
<SettingsIcon />
Class Settings
</DropdownMenu.Item>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
</ButtonGroup.Root>
</ButtonGroup.Root>
<Button>
<PlusIcon />
New Class
</Button>
</Page.Action>
</Page.Header>
<Page.Body>
{#snippet child()}
<Empty.Root>
<Empty.Header>
<Empty.Media variant="icon">
<GraduationCapIcon />
</Empty.Media>
<Empty.Title>No Classes Scheduled</Empty.Title>
<Empty.Description>
Create your first class session to start teaching. Classes help you organize your teaching schedule.
</Empty.Description>
</Empty.Header>
<Empty.Content>
<Button>
<PlusIcon />
Create Class
</Button>
</Empty.Content>
</Empty.Root>
{/snippet}
</Page.Body>
</Page.Root>
{/snippet}
</Story>
<Story name="Students">
{#snippet template()}
<Page.Root>
<Page.Header>
<Page.HeaderContent>
<Page.Title>Students</Page.Title>
<Page.Subtitle>Manage student enrollments and track progress.</Page.Subtitle>
</Page.HeaderContent>
<Page.Action>
<ButtonGroup.Root>
<ButtonGroup.Root>
<Input placeholder="Search students..." />
<Button variant="outline" size="icon" aria-label="Search">
<SearchIcon />
</Button>
</ButtonGroup.Root>
<ButtonGroup.Root>
<Button variant="outline">
<FilterIcon />
Filter
</Button>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="outline" class="ps-2!">
<ChevronDownIcon />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end" class="[--radius:1rem]">
<DropdownMenu.Group>
<DropdownMenu.Item>
<DownloadIcon />
Export Student List
</DropdownMenu.Item>
<DropdownMenu.Item>
<FileTextIcon />
Generate Report
</DropdownMenu.Item>
<DropdownMenu.Item>
<ShareIcon />
Share Student List
</DropdownMenu.Item>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
</ButtonGroup.Root>
</ButtonGroup.Root>
<Button>
<UserPlusIcon />
Add Student
</Button>
</Page.Action>
</Page.Header>
<Page.Body>
{#snippet child()}
<Empty.Root>
<Empty.Header>
<Empty.Media variant="icon">
<UsersIcon />
</Empty.Media>
<Empty.Title>No Students Enrolled</Empty.Title>
<Empty.Description>
Start by adding students to your courses. You can invite them via email or import from a CSV file.
</Empty.Description>
</Empty.Header>
<Empty.Content>
<div class="flex gap-2">
<Button>
<UserPlusIcon />
Add Student
</Button>
<Button variant="outline">Import CSV</Button>
</div>
</Empty.Content>
</Empty.Root>
{/snippet}
</Page.Body>
</Page.Root>
{/snippet}
</Story>
<Story name="Header Only">
{#snippet template()}
<Page.Root>
<Page.Header>
<Page.HeaderContent>
<Page.Title>Analytics Dashboard</Page.Title>
<Page.Subtitle>View and analyze your learning management data.</Page.Subtitle>
</Page.HeaderContent>
<Page.Action>
<ButtonGroup.Root>
<ButtonGroup.Root>
<Input placeholder="Search..." />
<Button variant="outline" size="icon" aria-label="Search">
<SearchIcon />
</Button>
</ButtonGroup.Root>
<ButtonGroup.Root>
<Button variant="outline">
<DownloadIcon />
Export
</Button>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="outline" class="ps-2!">
<ChevronDownIcon />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end" class="[--radius:1rem]">
<DropdownMenu.Group>
<DropdownMenu.Item>
<ShareIcon />
Share Dashboard
</DropdownMenu.Item>
<DropdownMenu.Item>
<SettingsIcon />
Dashboard Settings
</DropdownMenu.Item>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
</ButtonGroup.Root>
</ButtonGroup.Root>
</Page.Action>
</Page.Header>
</Page.Root>
{/snippet}
</Story>
@@ -0,0 +1,97 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import { Password } from '@cio/ui/custom/password';
import * as Field from '@cio/ui/base/field';
const { Story } = defineMeta({
title: 'Molecules/Password',
component: Password,
parameters: {
layout: 'centered',
controls: {
include: ['placeholder', 'disabled', 'class']
}
},
tags: ['autodocs']
});
let password = $state('');
let passwordWithError = $state('');
let passwordDisabled = $state('password123');
</script>
<Story name="Default">
{#snippet template()}
<div class="ui:flex ui:flex-col ui:gap-4 ui:w-96">
<Password bind:value={password} placeholder="Enter password" />
<p class="ui:text-sm ui:text-muted-foreground">Value: {password || '(empty)'}</p>
</div>
{/snippet}
</Story>
<Story name="With Field">
{#snippet template()}
<div class="ui:flex ui:flex-col ui:gap-4 ui:w-96">
<Field.Field>
<Field.Label for="password">Password</Field.Label>
<Field.Content>
<Password id="password" bind:value={password} placeholder="Enter your password" />
</Field.Content>
<Field.Description>Password must be more than 8 characters</Field.Description>
</Field.Field>
</div>
{/snippet}
</Story>
<Story name="With Error">
{#snippet template()}
<div class="ui:flex ui:flex-col ui:gap-4 ui:w-96">
<Field.Field>
<Field.Label for="password-error">Password</Field.Label>
<Field.Content>
<Password
id="password-error"
bind:value={passwordWithError}
placeholder="Enter your password"
aria-invalid="true"
/>
</Field.Content>
<Field.Error>Password is required</Field.Error>
</Field.Field>
</div>
{/snippet}
</Story>
<Story name="Disabled">
{#snippet template()}
<div class="ui:flex ui:flex-col ui:gap-4 ui:w-96">
<Field.Field>
<Field.Label for="password-disabled">Password</Field.Label>
<Field.Content>
<Password
id="password-disabled"
bind:value={passwordDisabled}
placeholder="Enter your password"
disabled={true}
/>
</Field.Content>
</Field.Field>
</div>
{/snippet}
</Story>
<Story name="Custom Labels">
{#snippet template()}
<div class="ui:flex ui:flex-col ui:gap-4 ui:w-96">
<Password
bind:value={password}
placeholder="Enter your password"
showPasswordTooltip="Click to show password"
hidePasswordTooltip="Click to hide password"
showPasswordAriaLabel="Show password"
hidePasswordAriaLabel="Hide password"
/>
</div>
{/snippet}
</Story>