feat: move away from carbon components and TinyMCE (#597)
* feat: remove tinymce & replace org sidebar * chore: PR Fixes * chore: move ui font into src dir * remove files in vite config * use google font import for loading font * remove custom font * more fonts update * remove font bold * more removals of font bold * fix import * checkpoint * chore: checkpoint (add course & lms shadcn sidebar) * minor css improvements and try out font sans * chore: replace carbon components with shadcn's * chore: replace carbon components with shadcn _ v3 * chore: ui fixes & storybook update * feat: UI cleanup * fix: quick ui fixes * chore: update pnpm-lock.yaml with new dependencies and versions; fix output.css formatting * chore: add isloading to button * feat: add loading state to button stories * chore: feedback fixes * fix: feedback fixes * chore: remove console * fix: PR comments * fix: PR comments --------- Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
co-authored by
Rotimi Best
parent
2145782385
commit
cbf6ae7e63
@@ -13,6 +13,8 @@
|
||||
"@cio/ui": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tiptap/pm": "^3.7.1",
|
||||
"@tiptap/core": "^3.7.1",
|
||||
"@storybook/addon-essentials": "^8.4.2",
|
||||
"@storybook/addon-interactions": "^8.4.2",
|
||||
"@storybook/addon-links": "^8.4.2",
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import * as Accordion from '@cio/ui/base/accordion';
|
||||
import { FIELDS } from './fields';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Accordion',
|
||||
component: Accordion.Root,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
controls: {
|
||||
include: FIELDS
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<Accordion.Root type="single" class="w-[450px]">
|
||||
<Accordion.Item value="item-1">
|
||||
<Accordion.Trigger>Is it accessible?</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
Yes. It adheres to the WAI-ARIA design pattern and includes proper keyboard navigation support.
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-2">
|
||||
<Accordion.Trigger>Is it styled?</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
Yes. It comes with default styles that matches the other components' aesthetic.
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-3">
|
||||
<Accordion.Trigger>Is it animated?</Accordion.Trigger>
|
||||
<Accordion.Content>Yes. It's animated by default, but you can disable it if you prefer.</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Single Expanded">
|
||||
{#snippet template()}
|
||||
<Accordion.Root type="single" class="w-[450px]">
|
||||
<Accordion.Item value="item-1">
|
||||
<Accordion.Trigger>What is ClassroomIO?</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
ClassroomIO is an open-source learning management system designed to help educators create and manage online
|
||||
courses efficiently.
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-2">
|
||||
<Accordion.Trigger>How do I get started?</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
You can get started by creating an account, setting up your organization, and creating your first course. Our
|
||||
documentation provides step-by-step guides.
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-3">
|
||||
<Accordion.Trigger>Is it free to use?</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
Yes! ClassroomIO is open-source and free to use. We also offer premium features for organizations that need
|
||||
additional capabilities.
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Multiple Expanded">
|
||||
{#snippet template()}
|
||||
<Accordion.Root type="multiple" class="w-[450px]">
|
||||
<Accordion.Item value="features">
|
||||
<Accordion.Trigger>Features</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
<ul class="list-disc space-y-1 pl-4">
|
||||
<li>Course management</li>
|
||||
<li>Student analytics</li>
|
||||
<li>AI-powered grading</li>
|
||||
<li>Custom certificates</li>
|
||||
</ul>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="pricing">
|
||||
<Accordion.Trigger>Pricing</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
We offer flexible pricing plans for individuals, teams, and enterprises. Contact us for more information.
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="support">
|
||||
<Accordion.Trigger>Support</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
We provide email support, documentation, and community forums to help you succeed.
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import { Accordion } from '@cio/ui/base/accordion';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof Accordion>)[] = [
|
||||
'type',
|
||||
'value',
|
||||
'onValueChange',
|
||||
'disabled',
|
||||
'name',
|
||||
'loop',
|
||||
'orientation'
|
||||
];
|
||||
@@ -0,0 +1,44 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { Avatar, AvatarImage, AvatarFallback } from '@cio/ui/base/avatar';
|
||||
import { FIELDS } from './fields';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Avatar',
|
||||
component: Avatar,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
controls: {
|
||||
include: FIELDS
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Fallback">
|
||||
{#snippet template()}
|
||||
<Avatar>
|
||||
<AvatarImage src="https://invalid-url.com/image.png" alt="@user" />
|
||||
<AvatarFallback>AB</AvatarFallback>
|
||||
</Avatar>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Custom Size">
|
||||
{#snippet template()}
|
||||
<Avatar class="h-20 w-20">
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,4 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import { Avatar } from '@cio/ui/base/avatar';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof Avatar>)[] = ['loadingStatus', 'onLoadingStatusChange', 'delayMs'];
|
||||
@@ -0,0 +1,48 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { Badge } from '@cio/ui/base/badge';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Badge',
|
||||
component: Badge,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<Badge>Default Badge</Badge>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Secondary">
|
||||
{#snippet template()}
|
||||
<Badge variant="secondary">Secondary</Badge>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Destructive">
|
||||
{#snippet template()}
|
||||
<Badge variant="destructive">Destructive</Badge>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Outline">
|
||||
{#snippet template()}
|
||||
<Badge variant="outline">Outline</Badge>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Multiple Badges">
|
||||
{#snippet template()}
|
||||
<div class="flex gap-2">
|
||||
<Badge>Default</Badge>
|
||||
<Badge variant="secondary">Secondary</Badge>
|
||||
<Badge variant="destructive">Destructive</Badge>
|
||||
<Badge variant="outline">Outline</Badge>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,65 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbEllipsis,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator
|
||||
} from '@cio/ui/base/breadcrumb';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Breadcrumb',
|
||||
component: Breadcrumb,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/">Home</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/components">Components</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="With Ellipsis">
|
||||
{#snippet template()}
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/">Home</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbEllipsis />
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/components">Components</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -70,6 +70,12 @@
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Loading">
|
||||
{#snippet template(args)}
|
||||
<Button {...args} isLoading>Loading Button</Button>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="As Link">
|
||||
{#snippet template(args)}
|
||||
<Button {...args} href="#">Link Button</Button>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { Checkbox } from '@cio/ui/base/checkbox';
|
||||
import { Label } from '@cio/ui/base/label';
|
||||
import { FIELDS } from './fields';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Checkbox',
|
||||
component: Checkbox,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
controls: {
|
||||
include: FIELDS
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<Checkbox />
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="With Label">
|
||||
{#snippet template()}
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<Label for="terms">Accept terms and conditions</Label>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Checked">
|
||||
{#snippet template()}
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox checked={true} />
|
||||
<Label>Checked by default</Label>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Disabled">
|
||||
{#snippet template()}
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox disabled />
|
||||
<Label>Disabled unchecked</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox disabled checked={true} />
|
||||
<Label>Disabled checked</Label>
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,11 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import { Checkbox } from '@cio/ui/base/checkbox';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof Checkbox>)[] = [
|
||||
'checked',
|
||||
'onCheckedChange',
|
||||
'disabled',
|
||||
'name',
|
||||
'value',
|
||||
'required'
|
||||
];
|
||||
@@ -0,0 +1,52 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@cio/ui/base/collapsible';
|
||||
import { Button } from '@cio/ui';
|
||||
import { FIELDS } from './fields';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Collapsible',
|
||||
component: Collapsible,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
controls: {
|
||||
include: FIELDS
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<Collapsible class="w-[350px] space-y-2">
|
||||
<div class="flex items-center justify-between space-x-4 px-4">
|
||||
<h4 class="text-sm font-semibold">@peduarte starred 3 repositories</h4>
|
||||
<CollapsibleTrigger>
|
||||
<Button.Root variant="ghost" size="sm" class="w-9 p-0">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="h-4 w-4"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
<span class="sr-only">Toggle</span>
|
||||
</Button.Root>
|
||||
</CollapsibleTrigger>
|
||||
</div>
|
||||
<div class="rounded-md border px-4 py-3 font-mono text-sm">@radix-ui/primitives</div>
|
||||
<CollapsibleContent class="space-y-2">
|
||||
<div class="rounded-md border px-4 py-3 font-mono text-sm">@radix-ui/colors</div>
|
||||
<div class="rounded-md border px-4 py-3 font-mono text-sm">@stitches/react</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import { Collapsible } from '@cio/ui/base/collapsible';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof Collapsible>)[] = [
|
||||
'open',
|
||||
'onOpenChange',
|
||||
'onOpenChangeComplete',
|
||||
'disabled'
|
||||
];
|
||||
@@ -0,0 +1,54 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { Command } from '@cio/ui';
|
||||
import { FIELDS } from './fields';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Command',
|
||||
component: Command.Root,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
controls: {
|
||||
include: FIELDS
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<Command.Root class="rounded-lg border shadow-md">
|
||||
<Command.CommandInput placeholder="Type a command or search..." />
|
||||
<Command.CommandList>
|
||||
<Command.CommandEmpty>No results found.</Command.CommandEmpty>
|
||||
<Command.CommandGroup heading="Suggestions">
|
||||
<Command.CommandItem>
|
||||
<span>Calendar</span>
|
||||
</Command.CommandItem>
|
||||
<Command.CommandItem>
|
||||
<span>Search Emoji</span>
|
||||
</Command.CommandItem>
|
||||
<Command.CommandItem>
|
||||
<span>Calculator</span>
|
||||
</Command.CommandItem>
|
||||
</Command.CommandGroup>
|
||||
<Command.CommandSeparator />
|
||||
<Command.CommandGroup heading="Settings">
|
||||
<Command.CommandItem>
|
||||
<span>Profile</span>
|
||||
<Command.CommandShortcut>⌘P</Command.CommandShortcut>
|
||||
</Command.CommandItem>
|
||||
<Command.CommandItem>
|
||||
<span>Billing</span>
|
||||
<Command.CommandShortcut>⌘B</Command.CommandShortcut>
|
||||
</Command.CommandItem>
|
||||
<Command.CommandItem>
|
||||
<span>Settings</span>
|
||||
<Command.CommandShortcut>⌘S</Command.CommandShortcut>
|
||||
</Command.CommandItem>
|
||||
</Command.CommandGroup>
|
||||
</Command.CommandList>
|
||||
</Command.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,11 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import { Command } from '@cio/ui/base/command';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof Command>)[] = [
|
||||
'value',
|
||||
'onValueChange',
|
||||
'label',
|
||||
'filter',
|
||||
'columns',
|
||||
'disablePointerSelection'
|
||||
];
|
||||
@@ -0,0 +1,82 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { Dialog, Button, Input, Label } from '@cio/ui';
|
||||
import { FIELDS } from './fields';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Dialog',
|
||||
component: Dialog.Root,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
controls: {
|
||||
include: FIELDS
|
||||
}
|
||||
},
|
||||
argTypes: {
|
||||
open: {
|
||||
control: 'boolean',
|
||||
description: 'Whether the dialog is open',
|
||||
table: { defaultValue: { summary: 'false' } }
|
||||
},
|
||||
onOpenChange: {
|
||||
control: false,
|
||||
description: 'Callback function called when the open state changes',
|
||||
table: {
|
||||
type: { summary: '(open: boolean) => void' },
|
||||
defaultValue: { summary: 'undefined' }
|
||||
}
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template(args)}
|
||||
<Dialog.Root {...args}>
|
||||
<Dialog.DialogTrigger>
|
||||
<Button.Root>Open Dialog</Button.Root>
|
||||
</Dialog.DialogTrigger>
|
||||
<Dialog.DialogContent>
|
||||
<Dialog.DialogHeader>
|
||||
<Dialog.DialogTitle>Dialog Title</Dialog.DialogTitle>
|
||||
<Dialog.DialogDescription>This is a description of what the dialog is for.</Dialog.DialogDescription>
|
||||
</Dialog.DialogHeader>
|
||||
<div class="py-4">
|
||||
<p>Dialog content goes here.</p>
|
||||
</div>
|
||||
</Dialog.DialogContent>
|
||||
</Dialog.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="With Form">
|
||||
{#snippet template(args)}
|
||||
<Dialog.Root {...args}>
|
||||
<Dialog.DialogTrigger>
|
||||
<Button.Root>Edit Profile</Button.Root>
|
||||
</Dialog.DialogTrigger>
|
||||
<Dialog.DialogContent>
|
||||
<Dialog.DialogHeader>
|
||||
<Dialog.DialogTitle>Edit profile</Dialog.DialogTitle>
|
||||
<Dialog.DialogDescription
|
||||
>Make changes to your profile here. Click save when you're done.</Dialog.DialogDescription
|
||||
>
|
||||
</Dialog.DialogHeader>
|
||||
<div class="grid gap-4 py-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="John Doe" class="col-span-3" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label.Root for="username" class="text-right">Username</Label.Root>
|
||||
<Input.Root id="username" value="@johndoe" class="col-span-3" />
|
||||
</div>
|
||||
</div>
|
||||
<Dialog.DialogFooter>
|
||||
<Button.Root type="submit">Save changes</Button.Root>
|
||||
</Dialog.DialogFooter>
|
||||
</Dialog.DialogContent>
|
||||
</Dialog.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,4 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import * as Dialog from '@cio/ui/base/dialog';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof Dialog.Root>)[] = ['open', 'onOpenChange'];
|
||||
@@ -1,6 +1,7 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { DropdownMenu, Button } from '@cio/ui';
|
||||
import { Button } from '@cio/ui';
|
||||
import * as DropdownMenu from '@cio/ui/base/dropdown-menu';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/DropdownMenu',
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import { Label } from '@cio/ui/base/label';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof Label>)[] = ['children', 'child'];
|
||||
@@ -0,0 +1,45 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { Label } from '@cio/ui/base/label';
|
||||
import { Input } from '@cio/ui/base/input';
|
||||
import { FIELDS } from './fields';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Label',
|
||||
component: Label,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
controls: {
|
||||
include: FIELDS
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<Label>Email</Label>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="With Input">
|
||||
{#snippet template()}
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Label for="email">Email</Label>
|
||||
<Input type="email" id="email" placeholder="Email" />
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Required">
|
||||
{#snippet template()}
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Label for="username">
|
||||
Username
|
||||
<span class="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input id="username" placeholder="Username" required />
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,10 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import { Menubar } from '@cio/ui/base/menubar';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof Menubar>)[] = [
|
||||
'onValueChange',
|
||||
'value',
|
||||
'dir',
|
||||
'onOpenChange',
|
||||
'disabled'
|
||||
];
|
||||
@@ -0,0 +1,90 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import * as Menubar from '@cio/ui/base/menubar';
|
||||
import { FIELDS } from './fields';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Menubar',
|
||||
component: Menubar.Root,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
controls: {
|
||||
include: FIELDS
|
||||
}
|
||||
},
|
||||
argTypes: {
|
||||
value: {
|
||||
control: 'text',
|
||||
description: 'The controlled value of the active menu'
|
||||
},
|
||||
onValueChange: {
|
||||
control: false,
|
||||
description: 'Callback function when the active menu changes'
|
||||
},
|
||||
dir: {
|
||||
control: 'select',
|
||||
options: ['ltr', 'rtl'],
|
||||
description: 'The reading direction of the menubar'
|
||||
},
|
||||
onOpenChange: {
|
||||
control: false,
|
||||
description: 'Callback function when the open state changes'
|
||||
},
|
||||
ref: {
|
||||
control: false,
|
||||
description: 'The underlying DOM element reference'
|
||||
},
|
||||
disabled: {
|
||||
control: 'boolean',
|
||||
description: 'Whether the menubar is disabled'
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<Menubar.Root>
|
||||
<Menubar.Menu>
|
||||
<Menubar.Trigger>File</Menubar.Trigger>
|
||||
<Menubar.Content>
|
||||
<Menubar.Item>
|
||||
New Tab <span class="ml-auto text-xs">⌘T</span>
|
||||
</Menubar.Item>
|
||||
<Menubar.Item>
|
||||
New Window <span class="ml-auto text-xs">⌘N</span>
|
||||
</Menubar.Item>
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item>Share</Menubar.Item>
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item>Print</Menubar.Item>
|
||||
</Menubar.Content>
|
||||
</Menubar.Menu>
|
||||
<Menubar.Menu>
|
||||
<Menubar.Trigger>Edit</Menubar.Trigger>
|
||||
<Menubar.Content>
|
||||
<Menubar.Item>
|
||||
Undo <span class="ml-auto text-xs">⌘Z</span>
|
||||
</Menubar.Item>
|
||||
<Menubar.Item>
|
||||
Redo <span class="ml-auto text-xs">⇧⌘Z</span>
|
||||
</Menubar.Item>
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item>Cut</Menubar.Item>
|
||||
<Menubar.Item>Copy</Menubar.Item>
|
||||
<Menubar.Item>Paste</Menubar.Item>
|
||||
</Menubar.Content>
|
||||
</Menubar.Menu>
|
||||
<Menubar.Menu>
|
||||
<Menubar.Trigger>View</Menubar.Trigger>
|
||||
<Menubar.Content>
|
||||
<Menubar.CheckboxItem checked={true}>Always Show Bookmarks Bar</Menubar.CheckboxItem>
|
||||
<Menubar.CheckboxItem>Always Show Full URLs</Menubar.CheckboxItem>
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item>Reload <span class="ml-auto text-xs">⌘R</span></Menubar.Item>
|
||||
</Menubar.Content>
|
||||
</Menubar.Menu>
|
||||
</Menubar.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { ModeSwitcher } from '@cio/ui/base/dark-mode';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Mode Switcher',
|
||||
component: ModeSwitcher,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<p class="text-muted-foreground text-sm">Click to toggle dark/light mode</p>
|
||||
<ModeSwitcher />
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,10 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import { Pagination } from '@cio/ui/base/pagination';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof Pagination>)[] = [
|
||||
'count',
|
||||
'page',
|
||||
'onPageChange',
|
||||
'perPage',
|
||||
'orientation'
|
||||
];
|
||||
@@ -0,0 +1,73 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import * as Pagination from '@cio/ui/base/pagination';
|
||||
import { FIELDS } from './fields';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Pagination',
|
||||
component: Pagination.Root,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
controls: {
|
||||
include: FIELDS
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
let page = $state(1);
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<Pagination.Root count={100} perPage={10} bind:page>
|
||||
<Pagination.Content>
|
||||
<Pagination.Item>
|
||||
<Pagination.PrevButton />
|
||||
</Pagination.Item>
|
||||
{#each Array.from({ length: 10 }, (_, i) => i + 1) as pageNum}
|
||||
<Pagination.Item>
|
||||
<Pagination.Link page={pageNum} isActive={pageNum === page}>
|
||||
{pageNum}
|
||||
</Pagination.Link>
|
||||
</Pagination.Item>
|
||||
{/each}
|
||||
<Pagination.Item>
|
||||
<Pagination.NextButton />
|
||||
</Pagination.Item>
|
||||
</Pagination.Content>
|
||||
</Pagination.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="With Ellipsis">
|
||||
{#snippet template()}
|
||||
<Pagination.Root count={500} perPage={10} bind:page>
|
||||
<Pagination.Content>
|
||||
<Pagination.Item>
|
||||
<Pagination.PrevButton />
|
||||
</Pagination.Item>
|
||||
<Pagination.Item>
|
||||
<Pagination.Link page={1} isActive={page === 1}>1</Pagination.Link>
|
||||
</Pagination.Item>
|
||||
<Pagination.Item>
|
||||
<Pagination.Ellipsis />
|
||||
</Pagination.Item>
|
||||
<Pagination.Item>
|
||||
<Pagination.Link {page} isActive={true}>{page}</Pagination.Link>
|
||||
</Pagination.Item>
|
||||
<Pagination.Item>
|
||||
<Pagination.Ellipsis />
|
||||
</Pagination.Item>
|
||||
<Pagination.Item>
|
||||
<Pagination.Link page={50}>50</Pagination.Link>
|
||||
</Pagination.Item>
|
||||
<Pagination.Item>
|
||||
<Pagination.NextButton />
|
||||
</Pagination.Item>
|
||||
</Pagination.Content>
|
||||
</Pagination.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,4 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import { Progress } from '@cio/ui/base/progress';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof Progress>)[] = ['max', 'min', 'value'];
|
||||
@@ -0,0 +1,48 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { Progress } from '@cio/ui/base/progress';
|
||||
import { FIELDS } from './fields';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Progress',
|
||||
component: Progress,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
controls: {
|
||||
include: FIELDS
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<div class="w-[400px] space-y-4">
|
||||
<Progress value={33} />
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Different Values">
|
||||
{#snippet template()}
|
||||
<div class="w-[400px] space-y-6">
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm font-medium">25% Complete</p>
|
||||
<Progress value={25} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm font-medium">50% Complete</p>
|
||||
<Progress value={50} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm font-medium">75% Complete</p>
|
||||
<Progress value={75} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm font-medium">100% Complete</p>
|
||||
<Progress value={100} />
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import { RadioGroup } from '@cio/ui/base/radio-group';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof RadioGroup>)[] = [
|
||||
'value',
|
||||
'onValueChange',
|
||||
'disabled',
|
||||
'required',
|
||||
'name',
|
||||
'loop',
|
||||
'orientation'
|
||||
];
|
||||
@@ -0,0 +1,65 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import * as RadioGroup from '@cio/ui/base/radio-group';
|
||||
import { Label } from '@cio/ui/base/label';
|
||||
import { FIELDS } from './fields';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/RadioGroup',
|
||||
component: RadioGroup.Root,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
controls: {
|
||||
include: FIELDS
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<RadioGroup.Root value="option-1">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="option-1" id="option-1" />
|
||||
<Label for="option-1">Option 1</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="option-2" id="option-2" />
|
||||
<Label for="option-2">Option 2</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="option-3" id="option-3" />
|
||||
<Label for="option-3">Option 3</Label>
|
||||
</div>
|
||||
</RadioGroup.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="With Descriptions">
|
||||
{#snippet template()}
|
||||
<RadioGroup.Root value="comfortable" class="space-y-4">
|
||||
<div class="flex items-start space-x-2">
|
||||
<RadioGroup.Item value="default" id="r1" class="mt-1" />
|
||||
<div>
|
||||
<Label for="r1" class="font-medium">Default</Label>
|
||||
<p class="text-muted-foreground text-sm">The standard spacing for your app</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start space-x-2">
|
||||
<RadioGroup.Item value="comfortable" id="r2" class="mt-1" />
|
||||
<div>
|
||||
<Label for="r2" class="font-medium">Comfortable</Label>
|
||||
<p class="text-muted-foreground text-sm">More space for easier interaction</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start space-x-2">
|
||||
<RadioGroup.Item value="compact" id="r3" class="mt-1" />
|
||||
<div>
|
||||
<Label for="r3" class="font-medium">Compact</Label>
|
||||
<p class="text-muted-foreground text-sm">Less space, more content</p>
|
||||
</div>
|
||||
</div>
|
||||
</RadioGroup.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,14 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import * as Select from '@cio/ui/base/select';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof Select.Root>)[] = [
|
||||
'value',
|
||||
'onValueChange',
|
||||
'disabled',
|
||||
'type',
|
||||
'open',
|
||||
'name',
|
||||
'required',
|
||||
'autocomplete',
|
||||
'allowDeselect'
|
||||
];
|
||||
@@ -0,0 +1,88 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import * as Select from '@cio/ui/base/select';
|
||||
import { FIELDS } from './fields';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Select',
|
||||
component: Select.Root,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
controls: {
|
||||
include: FIELDS
|
||||
}
|
||||
},
|
||||
argTypes: {
|
||||
value: {
|
||||
control: 'text',
|
||||
description: 'The controlled value of the select'
|
||||
},
|
||||
onValueChange: {
|
||||
control: false,
|
||||
description: 'Callback function when the value changes'
|
||||
},
|
||||
disabled: {
|
||||
control: 'boolean',
|
||||
description: 'Whether the select is disabled'
|
||||
},
|
||||
type: {
|
||||
control: 'select',
|
||||
options: ['single', 'multiple'],
|
||||
description: 'Whether the select allows single or multiple selection'
|
||||
},
|
||||
open: {
|
||||
control: 'boolean',
|
||||
description: 'The controlled open state of the select'
|
||||
},
|
||||
name: {
|
||||
control: 'text',
|
||||
description: 'The name attribute for the hidden input'
|
||||
},
|
||||
required: {
|
||||
control: 'boolean',
|
||||
description: 'Whether the select is required for form validation'
|
||||
},
|
||||
autocomplete: {
|
||||
control: 'text',
|
||||
description: 'The autocomplete attribute for the hidden input'
|
||||
},
|
||||
allowDeselect: {
|
||||
control: 'boolean',
|
||||
description: 'Whether to allow deselecting the selected value'
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
|
||||
const fruits = [
|
||||
{ value: 'apple', label: 'Apple' },
|
||||
{ value: 'banana', label: 'Banana' },
|
||||
{ value: 'blueberry', label: 'Blueberry' },
|
||||
{ value: 'grapes', label: 'Grapes' },
|
||||
{ value: 'pineapple', label: 'Pineapple' }
|
||||
];
|
||||
|
||||
let value = $state('');
|
||||
|
||||
const triggerContent = $derived(fruits.find((f) => f.value === value)?.label ?? 'Select a fruit');
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<Select.Root type="single" name="favoriteFruit" bind:value>
|
||||
<Select.Trigger class="w-[180px]">
|
||||
{triggerContent}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Group>
|
||||
<Select.Label>Fruits</Select.Label>
|
||||
{#each fruits as fruit (fruit.value)}
|
||||
<Select.Item value={fruit.value} label={fruit.label} disabled={fruit.value === 'grapes'}>
|
||||
{fruit.label}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Group>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,4 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import { Separator } from '@cio/ui/base/separator';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof Separator>)[] = ['orientation', 'decorative'];
|
||||
@@ -0,0 +1,51 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { Separator } from '@cio/ui/base/separator';
|
||||
import { FIELDS } from './fields';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Separator',
|
||||
component: Separator,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
controls: {
|
||||
include: FIELDS
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Horizontal">
|
||||
{#snippet template()}
|
||||
<div class="w-96 space-y-4">
|
||||
<div>
|
||||
<h4 class="text-sm font-medium">Section 1</h4>
|
||||
<p class="text-muted-foreground text-sm">Content for the first section</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<div>
|
||||
<h4 class="text-sm font-medium">Section 2</h4>
|
||||
<p class="text-muted-foreground text-sm">Content for the second section</p>
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Vertical">
|
||||
{#snippet template()}
|
||||
<div class="flex h-20 items-center space-x-4">
|
||||
<div class="space-y-1">
|
||||
<h4 class="text-sm font-medium">Item 1</h4>
|
||||
</div>
|
||||
<Separator orientation="vertical" />
|
||||
<div class="space-y-1">
|
||||
<h4 class="text-sm font-medium">Item 2</h4>
|
||||
</div>
|
||||
<Separator orientation="vertical" />
|
||||
<div class="space-y-1">
|
||||
<h4 class="text-sm font-medium">Item 3</h4>
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,4 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import { Sheet } from '@cio/ui/base/sheet';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof Sheet>)[] = ['open', 'onOpenChange'];
|
||||
@@ -0,0 +1,108 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { Sheet, Button, Input, Label } from '@cio/ui';
|
||||
import { FIELDS } from './fields';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Sheet',
|
||||
component: Sheet.Root,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
controls: {
|
||||
include: FIELDS
|
||||
}
|
||||
},
|
||||
argTypes: {
|
||||
open: {
|
||||
control: 'boolean',
|
||||
description: 'The controlled open state of the sheet'
|
||||
},
|
||||
onOpenChange: {
|
||||
control: false,
|
||||
description: 'Callback function when the open state changes'
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Right">
|
||||
{#snippet template()}
|
||||
<Sheet.Root>
|
||||
<Sheet.SheetTrigger>
|
||||
<Button.Root variant="outline">Open Sheet</Button.Root>
|
||||
</Sheet.SheetTrigger>
|
||||
<Sheet.SheetContent>
|
||||
<Sheet.SheetHeader>
|
||||
<Sheet.SheetTitle>Edit profile</Sheet.SheetTitle>
|
||||
<Sheet.SheetDescription
|
||||
>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 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" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label.Root for="username" class="text-right">Username</Label.Root>
|
||||
<Input.Root id="username" value="@peduarte" class="col-span-3" />
|
||||
</div>
|
||||
</div>
|
||||
<Sheet.SheetFooter>
|
||||
<Sheet.SheetClose>
|
||||
<Button.Root type="submit">Save changes</Button.Root>
|
||||
</Sheet.SheetClose>
|
||||
</Sheet.SheetFooter>
|
||||
</Sheet.SheetContent>
|
||||
</Sheet.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Left">
|
||||
{#snippet template()}
|
||||
<Sheet.Root>
|
||||
<Sheet.SheetTrigger>
|
||||
<Button.Root>Open Left Sheet</Button.Root>
|
||||
</Sheet.SheetTrigger>
|
||||
<Sheet.SheetContent side="left">
|
||||
<Sheet.SheetHeader>
|
||||
<Sheet.SheetTitle>Left Sheet</Sheet.SheetTitle>
|
||||
<Sheet.SheetDescription>This sheet opens from the left side.</Sheet.SheetDescription>
|
||||
</Sheet.SheetHeader>
|
||||
</Sheet.SheetContent>
|
||||
</Sheet.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Top">
|
||||
{#snippet template()}
|
||||
<Sheet.Root>
|
||||
<Sheet.SheetTrigger>
|
||||
<Button.Root>Open Top Sheet</Button.Root>
|
||||
</Sheet.SheetTrigger>
|
||||
<Sheet.SheetContent side="top">
|
||||
<Sheet.SheetHeader>
|
||||
<Sheet.SheetTitle>Top Sheet</Sheet.SheetTitle>
|
||||
<Sheet.SheetDescription>This sheet opens from the top.</Sheet.SheetDescription>
|
||||
</Sheet.SheetHeader>
|
||||
</Sheet.SheetContent>
|
||||
</Sheet.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Bottom">
|
||||
{#snippet template()}
|
||||
<Sheet.Root>
|
||||
<Sheet.SheetTrigger>
|
||||
<Button.Root>Open Bottom Sheet</Button.Root>
|
||||
</Sheet.SheetTrigger>
|
||||
<Sheet.SheetContent side="bottom">
|
||||
<Sheet.SheetHeader>
|
||||
<Sheet.SheetTitle>Bottom Sheet</Sheet.SheetTitle>
|
||||
<Sheet.SheetDescription>This sheet opens from the bottom.</Sheet.SheetDescription>
|
||||
</Sheet.SheetHeader>
|
||||
</Sheet.SheetContent>
|
||||
</Sheet.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,4 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import { Toaster as Sonner } from '@cio/ui/base/sonner';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof Sonner>)[] = ['types', 'position', 'expand'];
|
||||
@@ -0,0 +1,67 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { Toaster, toast } from '@cio/ui/base/sonner';
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
import { FIELDS } from './fields';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Sonner (Toast)',
|
||||
component: Toaster,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
controls: {
|
||||
include: FIELDS
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<div class="flex flex-col gap-4">
|
||||
<Toaster />
|
||||
<Button onclick={() => toast('This is a default toast message')}>Show Toast</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Success">
|
||||
{#snippet template()}
|
||||
<div class="flex flex-col gap-4">
|
||||
<Toaster />
|
||||
<Button variant="outline" onclick={() => toast.success('Course created successfully!')}>
|
||||
Show Success Toast
|
||||
</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Error">
|
||||
{#snippet template()}
|
||||
<div class="flex flex-col gap-4">
|
||||
<Toaster />
|
||||
<Button variant="destructive" onclick={() => toast.error('Failed to save changes. Please try again.')}>
|
||||
Show Error Toast
|
||||
</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Warning">
|
||||
{#snippet template()}
|
||||
<div class="flex flex-col gap-4">
|
||||
<Toaster />
|
||||
<Button variant="outline" onclick={() => toast.warning('You have unsaved changes!')}>Show Warning Toast</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Info">
|
||||
{#snippet template()}
|
||||
<div class="flex flex-col gap-4">
|
||||
<Toaster />
|
||||
<Button variant="secondary" onclick={() => toast.info('New updates are available')}>Show Info Toast</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,51 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { Spinner } from '@cio/ui/base/spinner';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Spinner',
|
||||
component: Spinner,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<Spinner />
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Small">
|
||||
{#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>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="With Text">
|
||||
{#snippet template()}
|
||||
<div class="flex items-center gap-2">
|
||||
<Spinner />
|
||||
<span>Loading...</span>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,11 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import { Switch } from '@cio/ui/base/switch';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof Switch>)[] = [
|
||||
'checked',
|
||||
'onCheckedChange',
|
||||
'disabled',
|
||||
'name',
|
||||
'required',
|
||||
'value'
|
||||
];
|
||||
@@ -0,0 +1,29 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { Switch } from '@cio/ui/base/switch';
|
||||
import { Label } from '@cio/ui/base/label';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Switch',
|
||||
component: Switch,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<Switch id="airplane-mode" />
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="With Label">
|
||||
{#snippet template()}
|
||||
<div class="flex items-center space-x-2">
|
||||
<Switch id="airplane-mode" />
|
||||
<Label for="airplane-mode">Airplane Mode</Label>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,92 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import * as Table from '@cio/ui/base/table';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Table',
|
||||
component: Table.Root,
|
||||
parameters: {
|
||||
layout: 'padded'
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<Table.Root>
|
||||
<Table.Caption>A list of your recent invoices.</Table.Caption>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.Head class="w-[100px]">Invoice</Table.Head>
|
||||
<Table.Head>Status</Table.Head>
|
||||
<Table.Head>Method</Table.Head>
|
||||
<Table.Head class="text-right">Amount</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell class="font-medium">INV001</Table.Cell>
|
||||
<Table.Cell>Paid</Table.Cell>
|
||||
<Table.Cell>Credit Card</Table.Cell>
|
||||
<Table.Cell class="text-right">$250.00</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell class="font-medium">INV002</Table.Cell>
|
||||
<Table.Cell>Pending</Table.Cell>
|
||||
<Table.Cell>PayPal</Table.Cell>
|
||||
<Table.Cell class="text-right">$150.00</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell class="font-medium">INV003</Table.Cell>
|
||||
<Table.Cell>Unpaid</Table.Cell>
|
||||
<Table.Cell>Bank Transfer</Table.Cell>
|
||||
<Table.Cell class="text-right">$350.00</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell class="font-medium">INV004</Table.Cell>
|
||||
<Table.Cell>Paid</Table.Cell>
|
||||
<Table.Cell>Credit Card</Table.Cell>
|
||||
<Table.Cell class="text-right">$450.00</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
<Table.Footer>
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={3}>Total</Table.Cell>
|
||||
<Table.Cell class="text-right">$1,200.00</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Footer>
|
||||
</Table.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Simple Table">
|
||||
{#snippet template()}
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.Head>Name</Table.Head>
|
||||
<Table.Head>Email</Table.Head>
|
||||
<Table.Head>Role</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>John Doe</Table.Cell>
|
||||
<Table.Cell>[email protected]</Table.Cell>
|
||||
<Table.Cell>Admin</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>Jane Smith</Table.Cell>
|
||||
<Table.Cell>[email protected]</Table.Cell>
|
||||
<Table.Cell>User</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>Bob Johnson</Table.Cell>
|
||||
<Table.Cell>[email protected]</Table.Cell>
|
||||
<Table.Cell>Editor</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,10 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import { Tabs } from '@cio/ui/base/tabs';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof Tabs>)[] = [
|
||||
'value',
|
||||
'onValueChange',
|
||||
'disabled',
|
||||
'activationMode',
|
||||
'orientation'
|
||||
];
|
||||
@@ -0,0 +1,44 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import * as Tabs from '@cio/ui/base/tabs';
|
||||
import { FIELDS } from './fields';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Tabs',
|
||||
component: Tabs.Root,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
controls: {
|
||||
include: FIELDS
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<Tabs.Root value="overview" class="w-[500px]">
|
||||
<Tabs.List class="grid w-full grid-cols-3">
|
||||
<Tabs.Trigger value="overview">Overview</Tabs.Trigger>
|
||||
<Tabs.Trigger value="analytics">Analytics</Tabs.Trigger>
|
||||
<Tabs.Trigger value="reports">Reports</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
<Tabs.Content value="overview">
|
||||
<div class="space-y-4 pt-4">
|
||||
<p class="text-muted-foreground text-sm">View your overview dashboard with key metrics and insights.</p>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="analytics">
|
||||
<div class="space-y-4 pt-4">
|
||||
<p class="text-muted-foreground text-sm">Deep dive into your analytics and user behavior patterns.</p>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="reports">
|
||||
<div class="space-y-4 pt-4">
|
||||
<p class="text-muted-foreground text-sm">Generate and download detailed reports for your records.</p>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type ComponentProps } from 'svelte';
|
||||
import { Tooltip } from '@cio/ui/base/tooltip';
|
||||
|
||||
export const FIELDS: (keyof ComponentProps<typeof Tooltip>)[] = [
|
||||
'disableHoverableContent',
|
||||
'delayDuration',
|
||||
'ignoreNonKeyboardFocus',
|
||||
'disabled'
|
||||
];
|
||||
@@ -2,13 +2,34 @@
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { Tooltip, Provider, Trigger, Content } from '@cio/ui/base/tooltip';
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
// import { Arro}
|
||||
import { FIELDS } from './fields';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Atom/Tooltip',
|
||||
component: Provider,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
layout: 'centered',
|
||||
controls: {
|
||||
include: FIELDS
|
||||
}
|
||||
},
|
||||
argTypes: {
|
||||
disableHoverableContent: {
|
||||
control: 'boolean',
|
||||
description: 'Prevents the content from being hoverable'
|
||||
},
|
||||
delayDuration: {
|
||||
control: 'number',
|
||||
description: 'The delay in milliseconds before the tooltip appears'
|
||||
},
|
||||
ignoreNonKeyboardFocus: {
|
||||
control: 'boolean',
|
||||
description: 'Whether to ignore non-keyboard focus events'
|
||||
},
|
||||
disabled: {
|
||||
control: 'boolean',
|
||||
description: 'Whether the tooltip is disabled'
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarGroupLabel,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarProvider,
|
||||
SidebarTrigger
|
||||
} from '@cio/ui/base/sidebar';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Molecules/Sidebar',
|
||||
component: Sidebar,
|
||||
parameters: {
|
||||
layout: 'fullscreen'
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<SidebarProvider>
|
||||
<Sidebar>
|
||||
<SidebarHeader>
|
||||
<h2 class="px-4 text-lg font-semibold">My App</h2>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Navigation</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton>
|
||||
<span>Dashboard</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton>
|
||||
<span>Projects</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton>
|
||||
<span>Settings</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<p class="text-muted-foreground px-4 text-sm">© 2024 My App</p>
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
<main class="flex-1 p-4">
|
||||
<SidebarTrigger />
|
||||
<div class="mt-4">
|
||||
<h1 class="text-2xl font-bold">Main Content</h1>
|
||||
<p class="mt-2">This is the main content area.</p>
|
||||
</div>
|
||||
</main>
|
||||
</SidebarProvider>
|
||||
{/snippet}
|
||||
</Story>
|
||||
@@ -16,7 +16,10 @@
|
||||
"@tailwindcss/typography": "^0.5.18",
|
||||
"bits-ui": "^2.11.7",
|
||||
"clsx": "^2.1.1",
|
||||
"layerchart": "2.0.0-next.27",
|
||||
"mode-watcher": "^1.1.0",
|
||||
"svelte": "^5.39.11",
|
||||
"svelte-sonner": "^1.0.5",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwind-variants": "^3.1.1",
|
||||
"tailwindcss": "^4.1.14",
|
||||
@@ -42,9 +45,10 @@
|
||||
"@tiptap/pm": "^3.7.1",
|
||||
"@tiptap/starter-kit": "^3.7.1",
|
||||
"@tiptap/suggestion": "^3.7.1",
|
||||
"d3-array": "^3.2.4",
|
||||
"d3-scale": "^4.0.2",
|
||||
"katex": "^0.16.22",
|
||||
"lowlight": "^3.3.0",
|
||||
"mode-watcher": "^0.5.1",
|
||||
"svelte-tiptap": "^3.0.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"tiptap-extension-auto-joiner": "^0.1.3",
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { Accordion as AccordionPrimitive } from "bits-ui";
|
||||
import { cn, type WithoutChild } from "../../tools";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithoutChild<AccordionPrimitive.ContentProps> = $props();
|
||||
</script>
|
||||
|
||||
<AccordionPrimitive.Content
|
||||
bind:ref
|
||||
data-slot="accordion-content"
|
||||
class="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
||||
{...restProps}
|
||||
>
|
||||
<div class={cn("pb-4 pt-0", className)}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</AccordionPrimitive.Content>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Accordion as AccordionPrimitive } from "bits-ui";
|
||||
import { cn } from "../../tools";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AccordionPrimitive.ItemProps = $props();
|
||||
</script>
|
||||
|
||||
<AccordionPrimitive.Item
|
||||
bind:ref
|
||||
data-slot="accordion-item"
|
||||
class={cn("border-b last:border-b-0", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { Accordion as AccordionPrimitive } from "bits-ui";
|
||||
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
|
||||
import { cn, type WithoutChild } from "../../tools";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
level = 3,
|
||||
children,
|
||||
...restProps
|
||||
}: WithoutChild<AccordionPrimitive.TriggerProps> & {
|
||||
level?: AccordionPrimitive.HeaderProps["level"];
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<AccordionPrimitive.Header {level} class="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
data-slot="accordion-trigger"
|
||||
bind:ref
|
||||
class={cn(
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium outline-none transition-all hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
<ChevronDownIcon
|
||||
class="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200"
|
||||
/>
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { Accordion as AccordionPrimitive } from "bits-ui";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
...restProps
|
||||
}: AccordionPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<AccordionPrimitive.Root
|
||||
bind:ref
|
||||
bind:value={value as never}
|
||||
data-slot="accordion"
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,16 @@
|
||||
import Root from "./accordion.svelte";
|
||||
import Content from "./accordion-content.svelte";
|
||||
import Item from "./accordion-item.svelte";
|
||||
import Trigger from "./accordion-trigger.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Item,
|
||||
Trigger,
|
||||
//
|
||||
Root as Accordion,
|
||||
Content as AccordionContent,
|
||||
Item as AccordionItem,
|
||||
Trigger as AccordionTrigger,
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from 'bits-ui';
|
||||
import { cn } from '../../tools';
|
||||
|
||||
let { ref = $bindable(null), class: className, ...restProps }: AvatarPrimitive.FallbackProps = $props();
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Fallback
|
||||
bind:ref
|
||||
data-slot="avatar-fallback"
|
||||
class={cn('bg-muted flex size-full items-center justify-center rounded-full', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from 'bits-ui';
|
||||
import { cn } from '../../tools';
|
||||
|
||||
let { ref = $bindable(null), class: className, ...restProps }: AvatarPrimitive.ImageProps = $props();
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Image
|
||||
bind:ref
|
||||
data-slot="avatar-image"
|
||||
class={cn('aspect-square size-full', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from 'bits-ui';
|
||||
import { cn } from '../../tools';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
loadingStatus = $bindable('loading'),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AvatarPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Root
|
||||
bind:ref
|
||||
bind:loadingStatus
|
||||
data-slot="avatar"
|
||||
class={cn('relative flex size-8 shrink-0 overflow-hidden rounded-full', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,13 @@
|
||||
import Root from "./avatar.svelte";
|
||||
import Image from "./avatar-image.svelte";
|
||||
import Fallback from "./avatar-fallback.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Image,
|
||||
Fallback,
|
||||
//
|
||||
Root as Avatar,
|
||||
Image as AvatarImage,
|
||||
Fallback as AvatarFallback,
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
<script lang="ts" module>
|
||||
import { type VariantProps, tv } from 'tailwind-variants';
|
||||
|
||||
export const badgeVariants = tv({
|
||||
base: 'focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-full border px-2 py-0.5 text-xs font-medium transition-[color,box-shadow] focus-visible:ring-[3px] [&>svg]:pointer-events-none [&>svg]:size-3',
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent',
|
||||
secondary: 'bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent',
|
||||
destructive:
|
||||
'bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70 border-transparent text-white',
|
||||
outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default'
|
||||
}
|
||||
});
|
||||
|
||||
export type BadgeVariant = VariantProps<typeof badgeVariants>['variant'];
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { HTMLAnchorAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef } from '../../tools';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
href,
|
||||
class: className,
|
||||
variant = 'default',
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAnchorAttributes> & {
|
||||
variant?: BadgeVariant;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<svelte:element
|
||||
this={href ? 'a' : 'span'}
|
||||
bind:this={ref}
|
||||
data-slot="badge"
|
||||
{href}
|
||||
class={cn(badgeVariants({ variant }), className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</svelte:element>
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as Badge } from "./badge.svelte";
|
||||
export { badgeVariants, type BadgeVariant } from "./badge.svelte";
|
||||
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from '@lucide/svelte/icons/ellipsis';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef, type WithoutChildren } from '../../tools';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLSpanElement>>> = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
bind:this={ref}
|
||||
data-slot="breadcrumb-ellipsis"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
class={cn('flex size-9 items-center justify-center', className)}
|
||||
{...restProps}
|
||||
>
|
||||
<EllipsisIcon class="size-4" />
|
||||
<span class="sr-only">More</span>
|
||||
</span>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLLiAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from '../../tools';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLLiAttributes> = $props();
|
||||
</script>
|
||||
|
||||
<li
|
||||
bind:this={ref}
|
||||
data-slot="breadcrumb-item"
|
||||
class={cn("inline-flex items-center gap-1.5", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</li>
|
||||
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAnchorAttributes } from 'svelte/elements';
|
||||
import type { Snippet } from 'svelte';
|
||||
import { cn, type WithElementRef } from '../../tools';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
href = undefined,
|
||||
child,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAnchorAttributes> & {
|
||||
child?: Snippet<[{ props: HTMLAnchorAttributes }]>;
|
||||
} = $props();
|
||||
|
||||
const attrs = $derived({
|
||||
'data-slot': 'breadcrumb-link',
|
||||
class: cn('hover:text-foreground transition-colors', className),
|
||||
href,
|
||||
...restProps
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if child}
|
||||
{@render child({ props: attrs })}
|
||||
{:else}
|
||||
<a bind:this={ref} {...attrs}>
|
||||
{@render children?.()}
|
||||
</a>
|
||||
{/if}
|
||||
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLOlAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef } from '../../tools';
|
||||
|
||||
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLOlAttributes> = $props();
|
||||
</script>
|
||||
|
||||
<ol
|
||||
bind:this={ref}
|
||||
data-slot="breadcrumb-list"
|
||||
class={cn('text-muted-foreground flex flex-wrap items-center gap-1.5 break-words text-sm sm:gap-2.5', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</ol>
|
||||
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef } from '../../tools';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
bind:this={ref}
|
||||
data-slot="breadcrumb-page"
|
||||
role="link"
|
||||
aria-disabled="true"
|
||||
aria-current="page"
|
||||
class={cn('text-foreground font-normal', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</span>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
|
||||
import { cn, type WithElementRef } from '../../tools';
|
||||
import type { HTMLLiAttributes } from 'svelte/elements';
|
||||
|
||||
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLLiAttributes> = $props();
|
||||
</script>
|
||||
|
||||
<li
|
||||
bind:this={ref}
|
||||
data-slot="breadcrumb-separator"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
class={cn('[&>svg]:size-3.5', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{#if children}
|
||||
{@render children?.()}
|
||||
{:else}
|
||||
<ChevronRightIcon />
|
||||
{/if}
|
||||
</li>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import type { WithElementRef } from '../../tools';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
|
||||
</script>
|
||||
|
||||
<nav bind:this={ref} data-slot="breadcrumb" class={className} aria-label="breadcrumb" {...restProps}>
|
||||
{@render children?.()}
|
||||
</nav>
|
||||
@@ -0,0 +1,25 @@
|
||||
import Root from "./breadcrumb.svelte";
|
||||
import Ellipsis from "./breadcrumb-ellipsis.svelte";
|
||||
import Item from "./breadcrumb-item.svelte";
|
||||
import Separator from "./breadcrumb-separator.svelte";
|
||||
import Link from "./breadcrumb-link.svelte";
|
||||
import List from "./breadcrumb-list.svelte";
|
||||
import Page from "./breadcrumb-page.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Ellipsis,
|
||||
Item,
|
||||
Separator,
|
||||
Link,
|
||||
List,
|
||||
Page,
|
||||
//
|
||||
Root as Breadcrumb,
|
||||
Ellipsis as BreadcrumbEllipsis,
|
||||
Item as BreadcrumbItem,
|
||||
Separator as BreadcrumbSeparator,
|
||||
Link as BreadcrumbLink,
|
||||
List as BreadcrumbList,
|
||||
Page as BreadcrumbPage,
|
||||
};
|
||||
@@ -2,6 +2,7 @@
|
||||
import { cn, type WithElementRef } from '../../tools';
|
||||
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
||||
import { type VariantProps, tv } from 'tailwind-variants';
|
||||
import { Spinner } from '../spinner';
|
||||
|
||||
export const buttonVariants = tv({
|
||||
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
@@ -38,6 +39,7 @@
|
||||
WithElementRef<HTMLAnchorAttributes> & {
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
isLoading?: boolean;
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -48,11 +50,13 @@
|
||||
size = 'default',
|
||||
ref = $bindable(null),
|
||||
href = undefined,
|
||||
isLoading = false,
|
||||
type = 'button',
|
||||
disabled,
|
||||
children,
|
||||
...restProps
|
||||
}: ButtonProps = $props();
|
||||
|
||||
let disabled = $derived(restProps.disabled || isLoading);
|
||||
</script>
|
||||
|
||||
{#if href}
|
||||
@@ -66,6 +70,9 @@
|
||||
tabindex={disabled ? -1 : undefined}
|
||||
{...restProps}
|
||||
>
|
||||
{#if isLoading}
|
||||
<Spinner />
|
||||
{/if}
|
||||
{@render children?.()}
|
||||
</a>
|
||||
{:else}
|
||||
@@ -77,6 +84,9 @@
|
||||
{disabled}
|
||||
{...restProps}
|
||||
>
|
||||
{#if isLoading}
|
||||
<Spinner />
|
||||
{/if}
|
||||
{@render children?.()}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from '../../tools';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import ChartStyle from './chart-style.svelte';
|
||||
import { setChartContext, type ChartConfig } from './chart-utils.js';
|
||||
|
||||
const uid = $props.id();
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
id = uid,
|
||||
class: className,
|
||||
children,
|
||||
config,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLElement>> & {
|
||||
config: ChartConfig;
|
||||
} = $props();
|
||||
|
||||
const chartId = `chart-${id || uid.replace(/:/g, '')}`;
|
||||
|
||||
setChartContext({
|
||||
get config() {
|
||||
return config;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-chart={chartId}
|
||||
data-slot="chart"
|
||||
class={cn(
|
||||
'flex aspect-video justify-center overflow-visible text-xs',
|
||||
// Overrides
|
||||
//
|
||||
// Stroke around dots/marks when hovering
|
||||
'[&_.stroke-white]:stroke-transparent',
|
||||
// override the default stroke color of lines
|
||||
'[&_.lc-line]:stroke-border/50',
|
||||
|
||||
// by default, layerchart shows a line intersecting the point when hovering, this hides that
|
||||
'[&_.lc-highlight-line]:stroke-0',
|
||||
|
||||
// by default, when you hover a point on a stacked series chart, it will drop the opacity
|
||||
// of the other series, this overrides that
|
||||
'[&_.lc-area-path]:opacity-100 [&_.lc-highlight-line]:opacity-100 [&_.lc-highlight-point]:opacity-100 [&_.lc-spline-path]:opacity-100 [&_.lc-text-svg]:overflow-visible [&_.lc-text]:text-xs',
|
||||
|
||||
// We don't want the little tick lines between the axis labels and the chart, so we remove
|
||||
// the stroke. The alternative is to manually disable `tickMarks` on the x/y axis of every
|
||||
// chart.
|
||||
'[&_.lc-axis-tick]:stroke-0',
|
||||
|
||||
// We don't want to display the rule on the x/y axis, as there is already going to be
|
||||
// a grid line there and rule ends up overlapping the marks because it is rendered after
|
||||
// the marks
|
||||
'[&_.lc-rule-x-line:not(.lc-grid-x-rule)]:stroke-0 [&_.lc-rule-y-line:not(.lc-grid-y-rule)]:stroke-0',
|
||||
'[&_.lc-grid-x-radial-line]:stroke-border [&_.lc-grid-x-radial-circle]:stroke-border',
|
||||
'[&_.lc-grid-y-radial-line]:stroke-border [&_.lc-grid-y-radial-circle]:stroke-border',
|
||||
|
||||
// Legend adjustments
|
||||
'[&_.lc-legend-swatch-button]:items-center [&_.lc-legend-swatch-button]:gap-1.5',
|
||||
'[&_.lc-legend-swatch-group]:items-center [&_.lc-legend-swatch-group]:gap-4',
|
||||
'[&_.lc-legend-swatch]:size-2.5 [&_.lc-legend-swatch]:rounded-[2px]',
|
||||
|
||||
// Labels
|
||||
'[&_.lc-labels-text:not([fill])]:fill-foreground [&_text]:stroke-transparent',
|
||||
|
||||
// Tick labels on th x/y axes
|
||||
'[&_.lc-axis-tick-label]:fill-muted-foreground [&_.lc-axis-tick-label]:font-normal',
|
||||
'[&_.lc-tooltip-rects-g]:fill-transparent',
|
||||
'[&_.lc-layout-svg-g]:fill-transparent',
|
||||
'[&_.lc-root-container]:w-full',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
<ChartStyle id={chartId} {config} />
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import { THEMES, type ChartConfig } from "./chart-utils.js";
|
||||
|
||||
let { id, config }: { id: string; config: ChartConfig } = $props();
|
||||
|
||||
const colorConfig = $derived(
|
||||
config ? Object.entries(config).filter(([, config]) => config.theme || config.color) : null
|
||||
);
|
||||
|
||||
const themeContents = $derived.by(() => {
|
||||
if (!colorConfig || !colorConfig.length) return;
|
||||
|
||||
const themeContents = [];
|
||||
for (let [_theme, prefix] of Object.entries(THEMES)) {
|
||||
let content = `${prefix} [data-chart=${id}] {\n`;
|
||||
const color = colorConfig.map(([key, itemConfig]) => {
|
||||
const theme = _theme as keyof typeof itemConfig.theme;
|
||||
const color = itemConfig.theme?.[theme] || itemConfig.color;
|
||||
return color ? `\t--color-${key}: ${color};` : null;
|
||||
});
|
||||
|
||||
content += color.join("\n") + "\n}";
|
||||
|
||||
themeContents.push(content);
|
||||
}
|
||||
|
||||
return themeContents.join("\n");
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if themeContents}
|
||||
{#key id}
|
||||
<svelte:element this={"style"}>
|
||||
{themeContents}
|
||||
</svelte:element>
|
||||
{/key}
|
||||
{/if}
|
||||
@@ -0,0 +1,152 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef, type WithoutChildren } from '../../tools';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { getPayloadConfigFromPayload, useChart, type TooltipPayload } from './chart-utils.js';
|
||||
import { getTooltipContext, Tooltip as TooltipPrimitive } from 'layerchart';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function defaultFormatter(value: any, _payload: TooltipPayload[]) {
|
||||
return `${value}`;
|
||||
}
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
hideLabel = false,
|
||||
indicator = 'dot',
|
||||
hideIndicator = false,
|
||||
labelKey,
|
||||
label,
|
||||
labelFormatter = defaultFormatter,
|
||||
labelClassName,
|
||||
formatter,
|
||||
nameKey,
|
||||
color,
|
||||
...restProps
|
||||
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> & {
|
||||
hideLabel?: boolean;
|
||||
label?: string;
|
||||
indicator?: 'line' | 'dot' | 'dashed';
|
||||
nameKey?: string;
|
||||
labelKey?: string;
|
||||
hideIndicator?: boolean;
|
||||
labelClassName?: string;
|
||||
labelFormatter?: // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
((value: any, payload: TooltipPayload[]) => string | number | Snippet) | null;
|
||||
formatter?: Snippet<
|
||||
[
|
||||
{
|
||||
value: unknown;
|
||||
name: string;
|
||||
item: TooltipPayload;
|
||||
index: number;
|
||||
payload: TooltipPayload[];
|
||||
}
|
||||
]
|
||||
>;
|
||||
} = $props();
|
||||
|
||||
const chart = useChart();
|
||||
const tooltipCtx = getTooltipContext();
|
||||
|
||||
const formattedLabel = $derived.by(() => {
|
||||
if (hideLabel || !tooltipCtx.payload?.length) return null;
|
||||
|
||||
const [item] = tooltipCtx.payload;
|
||||
const key = labelKey ?? item?.label ?? item?.name ?? 'value';
|
||||
|
||||
const itemConfig = getPayloadConfigFromPayload(chart.config, item, key);
|
||||
|
||||
const value =
|
||||
!labelKey && typeof label === 'string'
|
||||
? (chart.config[label as keyof typeof chart.config]?.label ?? label)
|
||||
: (itemConfig?.label ?? item.label);
|
||||
|
||||
if (value === undefined) return null;
|
||||
if (!labelFormatter) return value;
|
||||
return labelFormatter(value, tooltipCtx.payload);
|
||||
});
|
||||
|
||||
const nestLabel = $derived(tooltipCtx.payload.length === 1 && indicator !== 'dot');
|
||||
</script>
|
||||
|
||||
{#snippet TooltipLabel()}
|
||||
{#if formattedLabel}
|
||||
<div class={cn('font-medium', labelClassName)}>
|
||||
{#if typeof formattedLabel === 'function'}
|
||||
{@render formattedLabel()}
|
||||
{:else}
|
||||
{formattedLabel}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<TooltipPrimitive.Root variant="none">
|
||||
<div
|
||||
class={cn(
|
||||
'border-border/50 bg-background grid min-w-[9rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#if !nestLabel}
|
||||
{@render TooltipLabel()}
|
||||
{/if}
|
||||
<div class="grid gap-1.5">
|
||||
{#each tooltipCtx.payload as item, i (item.key + i)}
|
||||
{@const key = `${nameKey || item.key || item.name || 'value'}`}
|
||||
{@const itemConfig = getPayloadConfigFromPayload(chart.config, item, key)}
|
||||
{@const indicatorColor = color || item.payload?.color || item.color}
|
||||
<div
|
||||
class={cn(
|
||||
'[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:size-2.5',
|
||||
indicator === 'dot' && 'items-center'
|
||||
)}
|
||||
>
|
||||
{#if formatter && item.value !== undefined && item.name}
|
||||
{@render formatter({
|
||||
value: item.value,
|
||||
name: item.name,
|
||||
item,
|
||||
index: i,
|
||||
payload: tooltipCtx.payload
|
||||
})}
|
||||
{:else}
|
||||
{#if itemConfig?.icon}
|
||||
<itemConfig.icon />
|
||||
{:else if !hideIndicator}
|
||||
<div
|
||||
style="--color-bg: {indicatorColor}; --color-border: {indicatorColor};"
|
||||
class={cn('border-(--color-border) bg-(--color-bg) shrink-0 rounded-[2px]', {
|
||||
'size-2.5': indicator === 'dot',
|
||||
'h-full w-1': indicator === 'line',
|
||||
'w-0 border-[1.5px] border-dashed bg-transparent': indicator === 'dashed',
|
||||
'my-0.5': nestLabel && indicator === 'dashed'
|
||||
})}
|
||||
></div>
|
||||
{/if}
|
||||
<div
|
||||
class={cn('flex flex-1 shrink-0 justify-between leading-none', nestLabel ? 'items-end' : 'items-center')}
|
||||
>
|
||||
<div class="grid gap-1.5">
|
||||
{#if nestLabel}
|
||||
{@render TooltipLabel()}
|
||||
{/if}
|
||||
<span class="text-muted-foreground">
|
||||
{itemConfig?.label || item.name}
|
||||
</span>
|
||||
</div>
|
||||
{#if item.value !== undefined}
|
||||
<span class="text-foreground font-mono font-medium tabular-nums">
|
||||
{item.value.toLocaleString()}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</TooltipPrimitive.Root>
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { Tooltip } from "layerchart";
|
||||
import { getContext, setContext, type Component, type ComponentProps, type Snippet } from "svelte";
|
||||
|
||||
export const THEMES = { light: "", dark: ".dark" } as const;
|
||||
|
||||
export type ChartConfig = {
|
||||
[k in string]: {
|
||||
label?: string;
|
||||
icon?: Component;
|
||||
} & (
|
||||
| { color?: string; theme?: never }
|
||||
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
||||
);
|
||||
};
|
||||
|
||||
export type ExtractSnippetParams<T> = T extends Snippet<[infer P]> ? P : never;
|
||||
|
||||
export type TooltipPayload = ExtractSnippetParams<
|
||||
ComponentProps<typeof Tooltip.Root>["children"]
|
||||
>["payload"][number];
|
||||
|
||||
// Helper to extract item config from a payload.
|
||||
export function getPayloadConfigFromPayload(
|
||||
config: ChartConfig,
|
||||
payload: TooltipPayload,
|
||||
key: string
|
||||
) {
|
||||
if (typeof payload !== "object" || payload === null) return undefined;
|
||||
|
||||
const payloadPayload =
|
||||
"payload" in payload && typeof payload.payload === "object" && payload.payload !== null
|
||||
? payload.payload
|
||||
: undefined;
|
||||
|
||||
let configLabelKey: string = key;
|
||||
|
||||
if (payload.key === key) {
|
||||
configLabelKey = payload.key;
|
||||
} else if (payload.name === key) {
|
||||
configLabelKey = payload.name;
|
||||
} else if (key in payload && typeof payload[key as keyof typeof payload] === "string") {
|
||||
configLabelKey = payload[key as keyof typeof payload] as string;
|
||||
} else if (
|
||||
payloadPayload !== undefined &&
|
||||
key in payloadPayload &&
|
||||
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
||||
) {
|
||||
configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;
|
||||
}
|
||||
|
||||
return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config];
|
||||
}
|
||||
|
||||
type ChartContextValue = {
|
||||
config: ChartConfig;
|
||||
};
|
||||
|
||||
const chartContextKey = Symbol("chart-context");
|
||||
|
||||
export function setChartContext(value: ChartContextValue) {
|
||||
return setContext(chartContextKey, value);
|
||||
}
|
||||
|
||||
export function useChart() {
|
||||
return getContext<ChartContextValue>(chartContextKey);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import ChartContainer from './chart-container.svelte';
|
||||
import ChartTooltip from './chart-tooltip.svelte';
|
||||
|
||||
export { getPayloadConfigFromPayload, type ChartConfig } from './chart-utils.js';
|
||||
|
||||
export { ChartContainer, ChartTooltip, ChartContainer as Container, ChartTooltip as Tooltip };
|
||||
|
||||
export { BarChart, LineChart, AreaChart, PieChart } from 'layerchart';
|
||||
export { scaleBand } from 'd3-scale';
|
||||
@@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { Checkbox as CheckboxPrimitive } from 'bits-ui';
|
||||
import CheckIcon from '@lucide/svelte/icons/check';
|
||||
import MinusIcon from '@lucide/svelte/icons/minus';
|
||||
import { cn, type WithoutChildrenOrChild } from '../../tools';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
checked = $bindable(false),
|
||||
indeterminate = $bindable(false),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> = $props();
|
||||
</script>
|
||||
|
||||
<CheckboxPrimitive.Root
|
||||
bind:ref
|
||||
data-slot="checkbox"
|
||||
class={cn(
|
||||
'border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive shadow-xs peer flex size-4 shrink-0 items-center justify-center rounded-[4px] border outline-none transition-shadow focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
bind:checked
|
||||
bind:indeterminate
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ checked, indeterminate })}
|
||||
<div data-slot="checkbox-indicator" class="text-current transition-none">
|
||||
{#if checked}
|
||||
<CheckIcon class="size-3.5" />
|
||||
{:else if indeterminate}
|
||||
<MinusIcon class="size-3.5" />
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
</CheckboxPrimitive.Root>
|
||||
@@ -0,0 +1,6 @@
|
||||
import Root from "./checkbox.svelte";
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Checkbox,
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Collapsible as CollapsiblePrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: CollapsiblePrimitive.ContentProps = $props();
|
||||
</script>
|
||||
|
||||
<CollapsiblePrimitive.Content bind:ref data-slot="collapsible-content" {...restProps} />
|
||||
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Collapsible as CollapsiblePrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: CollapsiblePrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<CollapsiblePrimitive.Trigger bind:ref data-slot="collapsible-trigger" {...restProps} />
|
||||
@@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { Collapsible as CollapsiblePrimitive } from "bits-ui";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
open = $bindable(false),
|
||||
...restProps
|
||||
}: CollapsiblePrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<CollapsiblePrimitive.Root bind:ref bind:open data-slot="collapsible" {...restProps} />
|
||||
@@ -0,0 +1,13 @@
|
||||
import Root from "./collapsible.svelte";
|
||||
import Trigger from "./collapsible-trigger.svelte";
|
||||
import Content from "./collapsible-content.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Trigger,
|
||||
//
|
||||
Root as Collapsible,
|
||||
Content as CollapsibleContent,
|
||||
Trigger as CollapsibleTrigger,
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import type { Command as CommandPrimitive, Dialog as DialogPrimitive } from 'bits-ui';
|
||||
import type { Snippet } from 'svelte';
|
||||
import Command from './command.svelte';
|
||||
import * as Dialog from '../dialog';
|
||||
import type { WithoutChildrenOrChild } from '../../tools';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
ref = $bindable(null),
|
||||
value = $bindable(''),
|
||||
title = 'Command Palette',
|
||||
description = 'Search for a command to run',
|
||||
portalProps,
|
||||
children,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<DialogPrimitive.RootProps> &
|
||||
WithoutChildrenOrChild<CommandPrimitive.RootProps> & {
|
||||
portalProps?: DialogPrimitive.PortalProps;
|
||||
children: Snippet;
|
||||
title?: string;
|
||||
description?: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open {...restProps}>
|
||||
<Dialog.Header class="sr-only">
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
<Dialog.Description>{description}</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<Dialog.Content class="overflow-hidden p-0" {portalProps}>
|
||||
<Command
|
||||
class="**:data-[slot=command-input-wrapper]:h-12 [&_[data-command-group]:not([hidden])_~[data-command-group]]:pt-0 [&_[data-command-group]]:px-2 [&_[data-command-input-wrapper]_svg]:h-5 [&_[data-command-input-wrapper]_svg]:w-5 [&_[data-command-input]]:h-12 [&_[data-command-item]]:px-2 [&_[data-command-item]]:py-3 [&_[data-command-item]_svg]:h-5 [&_[data-command-item]_svg]:w-5"
|
||||
{...restProps}
|
||||
bind:value
|
||||
bind:ref
|
||||
{children}
|
||||
/>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from 'bits-ui';
|
||||
import { cn } from '../../tools';
|
||||
|
||||
let { ref = $bindable(null), class: className, ...restProps }: CommandPrimitive.EmptyProps = $props();
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.Empty
|
||||
bind:ref
|
||||
data-slot="command-empty"
|
||||
class={cn('py-6 text-center text-sm', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive, useId } from 'bits-ui';
|
||||
import { cn } from '../../tools';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
heading,
|
||||
value,
|
||||
...restProps
|
||||
}: CommandPrimitive.GroupProps & {
|
||||
heading?: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.Group
|
||||
bind:ref
|
||||
data-slot="command-group"
|
||||
class={cn('text-foreground overflow-hidden p-1', className)}
|
||||
value={value ?? heading ?? `----${useId()}`}
|
||||
{...restProps}
|
||||
>
|
||||
{#if heading}
|
||||
<CommandPrimitive.GroupHeading class="text-muted-foreground px-2 py-1.5 text-xs font-medium">
|
||||
{heading}
|
||||
</CommandPrimitive.GroupHeading>
|
||||
{/if}
|
||||
<CommandPrimitive.GroupItems {children} />
|
||||
</CommandPrimitive.Group>
|
||||
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from 'bits-ui';
|
||||
import SearchIcon from '@lucide/svelte/icons/search';
|
||||
import { cn } from '../../tools';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
value = $bindable(''),
|
||||
...restProps
|
||||
}: CommandPrimitive.InputProps = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex h-9 items-center gap-2 border-b pl-3 pr-8" data-slot="command-input-wrapper">
|
||||
<SearchIcon class="size-4 shrink-0 opacity-50" />
|
||||
<CommandPrimitive.Input
|
||||
data-slot="command-input"
|
||||
class={cn(
|
||||
'placeholder:text-muted-foreground outline-hidden flex h-10 w-full rounded-md bg-transparent py-3 text-sm disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
bind:ref
|
||||
{...restProps}
|
||||
bind:value
|
||||
/>
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from 'bits-ui';
|
||||
import { cn } from '../../tools';
|
||||
|
||||
let { ref = $bindable(null), class: className, ...restProps }: CommandPrimitive.ItemProps = $props();
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.Item
|
||||
bind:ref
|
||||
data-slot="command-item"
|
||||
class={cn(
|
||||
"aria-selected:bg-accent aria-selected:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from 'bits-ui';
|
||||
import { cn } from '../../tools';
|
||||
|
||||
let { ref = $bindable(null), class: className, ...restProps }: CommandPrimitive.LinkItemProps = $props();
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.LinkItem
|
||||
bind:ref
|
||||
data-slot="command-item"
|
||||
class={cn(
|
||||
"aria-selected:bg-accent aria-selected:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from 'bits-ui';
|
||||
import { cn } from '../../tools';
|
||||
|
||||
let { ref = $bindable(null), class: className, ...restProps }: CommandPrimitive.ListProps = $props();
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.List
|
||||
bind:ref
|
||||
data-slot="command-list"
|
||||
class={cn('max-h-[300px] scroll-py-1 overflow-y-auto overflow-x-hidden', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from 'bits-ui';
|
||||
import { cn } from '../../tools';
|
||||
|
||||
let { ref = $bindable(null), class: className, ...restProps }: CommandPrimitive.SeparatorProps = $props();
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.Separator
|
||||
bind:ref
|
||||
data-slot="command-separator"
|
||||
class={cn('bg-border -mx-1 h-px', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from '../../tools';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
bind:this={ref}
|
||||
data-slot="command-shortcut"
|
||||
class={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</span>
|
||||
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '../../tools';
|
||||
import { Command as CommandPrimitive } from 'bits-ui';
|
||||
|
||||
export type CommandRootApi = CommandPrimitive.Root;
|
||||
|
||||
let {
|
||||
api = $bindable(null),
|
||||
ref = $bindable(null),
|
||||
value = $bindable(''),
|
||||
class: className,
|
||||
...restProps
|
||||
}: CommandPrimitive.RootProps & {
|
||||
api?: CommandRootApi | null;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.Root
|
||||
bind:this={api}
|
||||
bind:value
|
||||
bind:ref
|
||||
data-slot="command"
|
||||
class={cn('bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Command as CommandPrimitive } from "bits-ui";
|
||||
|
||||
import Root from "./command.svelte";
|
||||
import Dialog from "./command-dialog.svelte";
|
||||
import Empty from "./command-empty.svelte";
|
||||
import Group from "./command-group.svelte";
|
||||
import Item from "./command-item.svelte";
|
||||
import Input from "./command-input.svelte";
|
||||
import List from "./command-list.svelte";
|
||||
import Separator from "./command-separator.svelte";
|
||||
import Shortcut from "./command-shortcut.svelte";
|
||||
import LinkItem from "./command-link-item.svelte";
|
||||
|
||||
const Loading = CommandPrimitive.Loading;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Dialog,
|
||||
Empty,
|
||||
Group,
|
||||
Item,
|
||||
LinkItem,
|
||||
Input,
|
||||
List,
|
||||
Separator,
|
||||
Shortcut,
|
||||
Loading,
|
||||
//
|
||||
Root as Command,
|
||||
Dialog as CommandDialog,
|
||||
Empty as CommandEmpty,
|
||||
Group as CommandGroup,
|
||||
Item as CommandItem,
|
||||
LinkItem as CommandLinkItem,
|
||||
Input as CommandInput,
|
||||
List as CommandList,
|
||||
Separator as CommandSeparator,
|
||||
Shortcut as CommandShortcut,
|
||||
Loading as CommandLoading,
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: DialogPrimitive.CloseProps = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} />
|
||||
@@ -0,0 +1,43 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
import XIcon from '@lucide/svelte/icons/x';
|
||||
import type { Snippet } from 'svelte';
|
||||
import * as Dialog from './index.js';
|
||||
import { cn, type WithoutChildrenOrChild } from '../../tools';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
portalProps,
|
||||
children,
|
||||
showCloseButton = true,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<DialogPrimitive.ContentProps> & {
|
||||
portalProps?: DialogPrimitive.PortalProps;
|
||||
children: Snippet;
|
||||
showCloseButton?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<Dialog.Portal {...portalProps}>
|
||||
<Dialog.Overlay />
|
||||
<DialogPrimitive.Content
|
||||
bind:ref
|
||||
data-slot="dialog-content"
|
||||
class={cn(
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed left-[50%] top-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{#if showCloseButton}
|
||||
<DialogPrimitive.Close
|
||||
class="ring-offset-background focus:ring-ring rounded-xs focus:outline-hidden absolute end-4 top-4 opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"
|
||||
>
|
||||
<XIcon />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
{/if}
|
||||
</DialogPrimitive.Content>
|
||||
</Dialog.Portal>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
import { cn } from '../../tools';
|
||||
|
||||
let { ref = $bindable(null), class: className, ...restProps }: DialogPrimitive.DescriptionProps = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Description
|
||||
bind:ref
|
||||
data-slot="dialog-description"
|
||||
class={cn('text-muted-foreground text-sm', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from '../../tools';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="dialog-footer"
|
||||
class={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef } from '../../tools';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="dialog-header"
|
||||
class={cn('flex flex-col gap-2 text-center sm:text-left', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
import { cn } from '../../tools';
|
||||
|
||||
let { ref = $bindable(null), class: className, ...restProps }: DialogPrimitive.OverlayProps = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Overlay
|
||||
bind:ref
|
||||
data-slot="dialog-overlay"
|
||||
class={cn(
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
import { cn } from '../../tools';
|
||||
|
||||
let { ref = $bindable(null), class: className, ...restProps }: DialogPrimitive.TitleProps = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Title
|
||||
bind:ref
|
||||
data-slot="dialog-title"
|
||||
class={cn('text-lg font-semibold leading-none', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: DialogPrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {...restProps} />
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
|
||||
import Title from "./dialog-title.svelte";
|
||||
import Footer from "./dialog-footer.svelte";
|
||||
import Header from "./dialog-header.svelte";
|
||||
import Overlay from "./dialog-overlay.svelte";
|
||||
import Content from "./dialog-content.svelte";
|
||||
import Description from "./dialog-description.svelte";
|
||||
import Trigger from "./dialog-trigger.svelte";
|
||||
import Close from "./dialog-close.svelte";
|
||||
|
||||
const Root = DialogPrimitive.Root;
|
||||
const Portal = DialogPrimitive.Portal;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Title,
|
||||
Portal,
|
||||
Footer,
|
||||
Header,
|
||||
Trigger,
|
||||
Overlay,
|
||||
Content,
|
||||
Description,
|
||||
Close,
|
||||
//
|
||||
Root as Dialog,
|
||||
Title as DialogTitle,
|
||||
Portal as DialogPortal,
|
||||
Footer as DialogFooter,
|
||||
Header as DialogHeader,
|
||||
Trigger as DialogTrigger,
|
||||
Overlay as DialogOverlay,
|
||||
Content as DialogContent,
|
||||
Description as DialogDescription,
|
||||
Close as DialogClose,
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Label as LabelPrimitive } from 'bits-ui';
|
||||
import { cn } from '$src/tools/index.js';
|
||||
import { cn } from '../../tools';
|
||||
|
||||
let { ref = $bindable(null), class: className, ...restProps }: LabelPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import Root from "./menubar.svelte";
|
||||
import CheckboxItem from "./menubar-checkbox-item.svelte";
|
||||
import Content from "./menubar-content.svelte";
|
||||
import Item from "./menubar-item.svelte";
|
||||
import Group from "./menubar-group.svelte";
|
||||
import RadioItem from "./menubar-radio-item.svelte";
|
||||
import Separator from "./menubar-separator.svelte";
|
||||
import Shortcut from "./menubar-shortcut.svelte";
|
||||
import SubContent from "./menubar-sub-content.svelte";
|
||||
import SubTrigger from "./menubar-sub-trigger.svelte";
|
||||
import Trigger from "./menubar-trigger.svelte";
|
||||
import Label from "./menubar-label.svelte";
|
||||
import GroupHeading from "./menubar-group-heading.svelte";
|
||||
|
||||
const Menu = MenubarPrimitive.Menu;
|
||||
const Sub = MenubarPrimitive.Sub;
|
||||
const RadioGroup = MenubarPrimitive.RadioGroup;
|
||||
|
||||
export {
|
||||
Root,
|
||||
CheckboxItem,
|
||||
Content,
|
||||
Item,
|
||||
RadioItem,
|
||||
Separator,
|
||||
Shortcut,
|
||||
SubContent,
|
||||
SubTrigger,
|
||||
Trigger,
|
||||
Menu,
|
||||
Group,
|
||||
Sub,
|
||||
RadioGroup,
|
||||
Label,
|
||||
GroupHeading,
|
||||
//
|
||||
Root as Menubar,
|
||||
CheckboxItem as MenubarCheckboxItem,
|
||||
Content as MenubarContent,
|
||||
Item as MenubarItem,
|
||||
RadioItem as MenubarRadioItem,
|
||||
Separator as MenubarSeparator,
|
||||
Shortcut as MenubarShortcut,
|
||||
SubContent as MenubarSubContent,
|
||||
SubTrigger as MenubarSubTrigger,
|
||||
Trigger as MenubarTrigger,
|
||||
Menu as MenubarMenu,
|
||||
Group as MenubarGroup,
|
||||
Sub as MenubarSub,
|
||||
RadioGroup as MenubarRadioGroup,
|
||||
Label as MenubarLabel,
|
||||
GroupHeading as MenubarGroupHeading,
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from 'bits-ui';
|
||||
import CheckIcon from '@lucide/svelte/icons/check';
|
||||
import MinusIcon from '@lucide/svelte/icons/minus';
|
||||
import { cn, type WithoutChildrenOrChild } from '../../tools';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
checked = $bindable(false),
|
||||
indeterminate = $bindable(false),
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<MenubarPrimitive.CheckboxItemProps> & {
|
||||
children?: Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.CheckboxItem
|
||||
bind:ref
|
||||
bind:checked
|
||||
bind:indeterminate
|
||||
data-slot="menubar-checkbox-item"
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground rounded-xs outline-hidden relative flex cursor-default select-none items-center gap-2 py-1.5 pl-8 pr-2 text-sm data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ checked, indeterminate })}
|
||||
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{#if indeterminate}
|
||||
<MinusIcon class="size-4" />
|
||||
{:else}
|
||||
<CheckIcon class={cn('size-4', !checked && 'text-transparent')} />
|
||||
{/if}
|
||||
</span>
|
||||
{@render childrenProp?.()}
|
||||
{/snippet}
|
||||
</MenubarPrimitive.CheckboxItem>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user