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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user