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 <best@classroomio.com>
This commit is contained in:
Adeyemi Adetunji
2025-11-21 12:18:04 +01:00
committed by GitHub
co-authored by Rotimi Best
parent 2145782385
commit cbf6ae7e63
499 changed files with 19708 additions and 15603 deletions
@@ -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>john@example.com</Table.Cell>
<Table.Cell>Admin</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>Jane Smith</Table.Cell>
<Table.Cell>jane@example.com</Table.Cell>
<Table.Cell>User</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>Bob Johnson</Table.Cell>
<Table.Cell>bob@example.com</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>