From c8a41aba4187fade39023e63f606e559ab3c7ae1 Mon Sep 17 00:00:00 2001
From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Date: Fri, 12 Dec 2025 10:32:14 +0000
Subject: [PATCH] feat: add Storybook stories for UI components and update
existing stories
- Add 16 new story files for UI components:
- Alert, Avatar, Badge, Card, Checkbox, Input, Label, Progress
- Separator, Skeleton, Slider, Switch, Tabs, Textarea, Toggle, Tooltip
- Update existing stories to use @storybook/nextjs-vite instead of @storybook/react
- Remove title property from meta objects per playbook guidelines
- All stories follow the playbook pattern with Default story first
- Include argTypes with descriptions for all controllable props
- Add showcase stories for variants, sizes, and states
Co-Authored-By: unknown <>
---
apps/web/client/src/stories/Alert.stories.tsx | 106 +++++++++++++
.../web/client/src/stories/Avatar.stories.tsx | 82 +++++++++++
apps/web/client/src/stories/Badge.stories.tsx | 95 ++++++++++++
.../web/client/src/stories/Button.stories.tsx | 3 +-
apps/web/client/src/stories/Card.stories.tsx | 104 +++++++++++++
.../client/src/stories/Checkbox.stories.tsx | 86 +++++++++++
apps/web/client/src/stories/Input.stories.tsx | 97 ++++++++++++
apps/web/client/src/stories/Label.stories.tsx | 87 +++++++++++
.../client/src/stories/Progress.stories.tsx | 99 +++++++++++++
.../src/stories/ProjectCard.stories.tsx | 7 +-
.../src/stories/ProjectsPage.stories.tsx | 11 +-
.../src/stories/SelectProject.stories.tsx | 7 +-
.../client/src/stories/Separator.stories.tsx | 84 +++++++++++
.../client/src/stories/Skeleton.stories.tsx | 95 ++++++++++++
.../web/client/src/stories/Slider.stories.tsx | 106 +++++++++++++
.../web/client/src/stories/Switch.stories.tsx | 86 +++++++++++
apps/web/client/src/stories/Tabs.stories.tsx | 119 +++++++++++++++
.../client/src/stories/Textarea.stories.tsx | 77 ++++++++++
.../web/client/src/stories/Toggle.stories.tsx | 110 ++++++++++++++
.../client/src/stories/Tooltip.stories.tsx | 139 ++++++++++++++++++
.../web/client/src/stories/TopBar.stories.tsx | 6 +-
21 files changed, 1577 insertions(+), 29 deletions(-)
create mode 100644 apps/web/client/src/stories/Alert.stories.tsx
create mode 100644 apps/web/client/src/stories/Avatar.stories.tsx
create mode 100644 apps/web/client/src/stories/Badge.stories.tsx
create mode 100644 apps/web/client/src/stories/Card.stories.tsx
create mode 100644 apps/web/client/src/stories/Checkbox.stories.tsx
create mode 100644 apps/web/client/src/stories/Input.stories.tsx
create mode 100644 apps/web/client/src/stories/Label.stories.tsx
create mode 100644 apps/web/client/src/stories/Progress.stories.tsx
create mode 100644 apps/web/client/src/stories/Separator.stories.tsx
create mode 100644 apps/web/client/src/stories/Skeleton.stories.tsx
create mode 100644 apps/web/client/src/stories/Slider.stories.tsx
create mode 100644 apps/web/client/src/stories/Switch.stories.tsx
create mode 100644 apps/web/client/src/stories/Tabs.stories.tsx
create mode 100644 apps/web/client/src/stories/Textarea.stories.tsx
create mode 100644 apps/web/client/src/stories/Toggle.stories.tsx
create mode 100644 apps/web/client/src/stories/Tooltip.stories.tsx
diff --git a/apps/web/client/src/stories/Alert.stories.tsx b/apps/web/client/src/stories/Alert.stories.tsx
new file mode 100644
index 0000000000..d636e27626
--- /dev/null
+++ b/apps/web/client/src/stories/Alert.stories.tsx
@@ -0,0 +1,106 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import { Alert, AlertTitle, AlertDescription } from '@onlook/ui/alert';
+import { AlertCircle, Info, CheckCircle2, AlertTriangle } from 'lucide-react';
+
+const meta = {
+ component: Alert,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ variant: {
+ description: 'Visual style variant of the alert',
+ control: { type: 'select' },
+ options: ['default', 'destructive'],
+ },
+ },
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => (
+
+
+ Information
+
+ This is an informational alert message.
+
+
+ ),
+};
+
+export const Destructive: Story = {
+ render: () => (
+
+
+ Error
+
+ Something went wrong. Please try again later.
+
+
+ ),
+};
+
+export const Success: Story = {
+ render: () => (
+
+
+ Success
+
+ Your changes have been saved successfully.
+
+
+ ),
+};
+
+export const Warning: Story = {
+ render: () => (
+
+
+ Warning
+
+ This action may have unintended consequences.
+
+
+ ),
+};
+
+export const TitleOnly: Story = {
+ render: () => (
+
+
+ Quick notification
+
+ ),
+};
+
+export const Variants: Story = {
+ render: () => (
+
+
+
+ Default Alert
+
+ This is the default alert style.
+
+
+
+
+ Destructive Alert
+
+ This is the destructive alert style.
+
+
+
+ ),
+};
diff --git a/apps/web/client/src/stories/Avatar.stories.tsx b/apps/web/client/src/stories/Avatar.stories.tsx
new file mode 100644
index 0000000000..dd8dec6bb2
--- /dev/null
+++ b/apps/web/client/src/stories/Avatar.stories.tsx
@@ -0,0 +1,82 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import { Avatar, AvatarImage, AvatarFallback } from '@onlook/ui/avatar';
+
+const meta = {
+ component: Avatar,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => (
+
+
+ CN
+
+ ),
+};
+
+export const WithFallback: Story = {
+ render: () => (
+
+
+ JD
+
+ ),
+};
+
+export const FallbackOnly: Story = {
+ render: () => (
+
+ AB
+
+ ),
+};
+
+export const Sizes: Story = {
+ render: () => (
+
+
+
+ SM
+
+
+
+ MD
+
+
+
+ LG
+
+
+
+ XL
+
+
+ ),
+};
+
+export const Group: Story = {
+ render: () => (
+
+
+
+ U1
+
+
+ U2
+
+
+ U3
+
+
+ +5
+
+
+ ),
+};
diff --git a/apps/web/client/src/stories/Badge.stories.tsx b/apps/web/client/src/stories/Badge.stories.tsx
new file mode 100644
index 0000000000..daa15c8960
--- /dev/null
+++ b/apps/web/client/src/stories/Badge.stories.tsx
@@ -0,0 +1,95 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import { Badge } from '@onlook/ui/badge';
+import { Check, AlertCircle, Clock } from 'lucide-react';
+
+const meta = {
+ component: Badge,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ variant: {
+ description: 'Visual style variant of the badge',
+ control: { type: 'select' },
+ options: ['default', 'secondary', 'destructive', 'outline'],
+ },
+ asChild: {
+ description: 'Whether to render as a child component',
+ control: 'boolean',
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ children: 'Badge',
+ variant: 'default',
+ },
+};
+
+export const Secondary: Story = {
+ args: {
+ children: 'Secondary',
+ variant: 'secondary',
+ },
+};
+
+export const Destructive: Story = {
+ args: {
+ children: 'Destructive',
+ variant: 'destructive',
+ },
+};
+
+export const Outline: Story = {
+ args: {
+ children: 'Outline',
+ variant: 'outline',
+ },
+};
+
+export const WithIcon: Story = {
+ args: {
+ children: (
+ <>
+
+ Success
+ >
+ ),
+ variant: 'default',
+ },
+};
+
+export const Variants: Story = {
+ render: () => (
+
+ Default
+ Secondary
+ Destructive
+ Outline
+
+ ),
+};
+
+export const WithIcons: Story = {
+ render: () => (
+
+
+
+ Success
+
+
+
+ Error
+
+
+
+ Pending
+
+
+ ),
+};
diff --git a/apps/web/client/src/stories/Button.stories.tsx b/apps/web/client/src/stories/Button.stories.tsx
index a8e4cd4e91..a65a2ae9cb 100644
--- a/apps/web/client/src/stories/Button.stories.tsx
+++ b/apps/web/client/src/stories/Button.stories.tsx
@@ -1,9 +1,8 @@
-import type { Meta, StoryObj } from '@storybook/react';
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
import { Button } from '@onlook/ui/button';
import { Heart, Plus, Trash2 } from 'lucide-react';
const meta = {
- title: 'UI/Button',
component: Button,
parameters: {
layout: 'centered',
diff --git a/apps/web/client/src/stories/Card.stories.tsx b/apps/web/client/src/stories/Card.stories.tsx
new file mode 100644
index 0000000000..61e371e053
--- /dev/null
+++ b/apps/web/client/src/stories/Card.stories.tsx
@@ -0,0 +1,104 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import {
+ Card,
+ CardHeader,
+ CardTitle,
+ CardDescription,
+ CardContent,
+ CardFooter,
+ CardAction,
+} from '@onlook/ui/card';
+import { Button } from '@onlook/ui/button';
+
+const meta = {
+ component: Card,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => (
+
+
+ Card Title
+ Card description goes here
+
+
+ Card content goes here. This is where you put the main content of the card.
+
+
+
+
+
+ ),
+};
+
+export const Simple: Story = {
+ render: () => (
+
+
+ A simple card with just content.
+
+
+ ),
+};
+
+export const WithAction: Story = {
+ render: () => (
+
+
+ Project Settings
+ Manage your project configuration
+
+
+
+
+
+ Configure your project settings here.
+
+
+ ),
+};
+
+export const WithFooterActions: Story = {
+ render: () => (
+
+
+ Delete Project
+ This action cannot be undone
+
+
+ Are you sure you want to delete this project? All data will be permanently removed.
+
+
+
+
+
+
+ ),
+};
+
+export const HeaderOnly: Story = {
+ render: () => (
+
+
+ Notifications
+ You have 3 unread messages
+
+
+ ),
+};
diff --git a/apps/web/client/src/stories/Checkbox.stories.tsx b/apps/web/client/src/stories/Checkbox.stories.tsx
new file mode 100644
index 0000000000..c430e68513
--- /dev/null
+++ b/apps/web/client/src/stories/Checkbox.stories.tsx
@@ -0,0 +1,86 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import { fn } from '@storybook/test';
+import { Checkbox } from '@onlook/ui/checkbox';
+import { Label } from '@onlook/ui/label';
+
+const meta = {
+ component: Checkbox,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ checked: {
+ description: 'Whether the checkbox is checked',
+ control: 'boolean',
+ },
+ disabled: {
+ description: 'Whether the checkbox is disabled',
+ control: 'boolean',
+ },
+ },
+ args: {
+ onCheckedChange: fn(),
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ checked: false,
+ disabled: false,
+ },
+};
+
+export const Checked: Story = {
+ args: {
+ checked: true,
+ },
+};
+
+export const Disabled: Story = {
+ args: {
+ disabled: true,
+ },
+};
+
+export const DisabledChecked: Story = {
+ args: {
+ checked: true,
+ disabled: true,
+ },
+};
+
+export const WithLabel: Story = {
+ render: () => (
+
+
+
+
+ ),
+};
+
+export const States: Story = {
+ render: () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+};
diff --git a/apps/web/client/src/stories/Input.stories.tsx b/apps/web/client/src/stories/Input.stories.tsx
new file mode 100644
index 0000000000..eb1e57a2e2
--- /dev/null
+++ b/apps/web/client/src/stories/Input.stories.tsx
@@ -0,0 +1,97 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import { Input } from '@onlook/ui/input';
+
+const meta = {
+ component: Input,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ type: {
+ description: 'The type of input',
+ control: { type: 'select' },
+ options: ['text', 'password', 'email', 'number', 'search', 'tel', 'url'],
+ },
+ placeholder: {
+ description: 'Placeholder text',
+ control: 'text',
+ },
+ disabled: {
+ description: 'Whether the input is disabled',
+ control: 'boolean',
+ },
+ },
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ type: 'text',
+ placeholder: 'Enter text...',
+ },
+};
+
+export const Email: Story = {
+ args: {
+ type: 'email',
+ placeholder: 'Enter email...',
+ },
+};
+
+export const Password: Story = {
+ args: {
+ type: 'password',
+ placeholder: 'Enter password...',
+ },
+};
+
+export const Disabled: Story = {
+ args: {
+ type: 'text',
+ placeholder: 'Disabled input',
+ disabled: true,
+ },
+};
+
+export const WithValue: Story = {
+ args: {
+ type: 'text',
+ defaultValue: 'Hello World',
+ },
+};
+
+export const Search: Story = {
+ args: {
+ type: 'search',
+ placeholder: 'Search...',
+ },
+};
+
+export const Number: Story = {
+ args: {
+ type: 'number',
+ placeholder: 'Enter number...',
+ },
+};
+
+export const Types: Story = {
+ render: () => (
+
+
+
+
+
+
+
+ ),
+};
diff --git a/apps/web/client/src/stories/Label.stories.tsx b/apps/web/client/src/stories/Label.stories.tsx
new file mode 100644
index 0000000000..64e23ac7dd
--- /dev/null
+++ b/apps/web/client/src/stories/Label.stories.tsx
@@ -0,0 +1,87 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import { Label } from '@onlook/ui/label';
+import { Input } from '@onlook/ui/input';
+import { Checkbox } from '@onlook/ui/checkbox';
+
+const meta = {
+ component: Label,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ htmlFor: {
+ description: 'ID of the form element this label is for',
+ control: 'text',
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ children: 'Label',
+ },
+};
+
+export const WithInput: Story = {
+ render: () => (
+
+
+
+
+ ),
+};
+
+export const WithCheckbox: Story = {
+ render: () => (
+
+
+
+
+ ),
+};
+
+export const Required: Story = {
+ render: () => (
+
+
+
+
+ ),
+};
+
+export const WithDescription: Story = {
+ render: () => (
+
+
+
+
+ Must be at least 8 characters long.
+
+
+ ),
+};
+
+export const FormFields: Story = {
+ render: () => (
+
+ ),
+};
diff --git a/apps/web/client/src/stories/Progress.stories.tsx b/apps/web/client/src/stories/Progress.stories.tsx
new file mode 100644
index 0000000000..891b89a534
--- /dev/null
+++ b/apps/web/client/src/stories/Progress.stories.tsx
@@ -0,0 +1,99 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import { Progress } from '@onlook/ui/progress';
+
+const meta = {
+ component: Progress,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ value: {
+ description: 'Progress value (0-100)',
+ control: { type: 'range', min: 0, max: 100, step: 1 },
+ },
+ },
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ value: 50,
+ },
+};
+
+export const Empty: Story = {
+ args: {
+ value: 0,
+ },
+};
+
+export const Quarter: Story = {
+ args: {
+ value: 25,
+ },
+};
+
+export const Half: Story = {
+ args: {
+ value: 50,
+ },
+};
+
+export const ThreeQuarters: Story = {
+ args: {
+ value: 75,
+ },
+};
+
+export const Complete: Story = {
+ args: {
+ value: 100,
+ },
+};
+
+export const ProgressSteps: Story = {
+ render: () => (
+
+ ),
+};
diff --git a/apps/web/client/src/stories/ProjectCard.stories.tsx b/apps/web/client/src/stories/ProjectCard.stories.tsx
index 4fff925d67..3c592cece0 100644
--- a/apps/web/client/src/stories/ProjectCard.stories.tsx
+++ b/apps/web/client/src/stories/ProjectCard.stories.tsx
@@ -1,14 +1,9 @@
-import type { Meta, StoryObj } from '@storybook/react';
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
import { ProjectCard } from '@/app/projects/_components/select/project-card';
import { HighlightText } from '@/app/projects/_components/select/highlight-text';
import type { Project } from '@onlook/models';
-/**
- * ProjectCard displays individual project information with hover effects,
- * preview images, and interactive elements like edit and settings buttons.
- */
const meta = {
- title: 'Projects/ProjectCard',
component: ProjectCard,
parameters: {
layout: 'padded',
diff --git a/apps/web/client/src/stories/ProjectsPage.stories.tsx b/apps/web/client/src/stories/ProjectsPage.stories.tsx
index 016d4f95f1..8cb9304e6c 100644
--- a/apps/web/client/src/stories/ProjectsPage.stories.tsx
+++ b/apps/web/client/src/stories/ProjectsPage.stories.tsx
@@ -1,14 +1,9 @@
-import type { Meta, StoryObj } from '@storybook/react';
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
import { TopBarPresentation } from '@/app/projects/_components/top-bar-presentation';
import { SelectProjectPresentation } from '@/app/projects/_components/select-presentation';
import type { Project, User } from '@onlook/models';
import { fn } from '@storybook/test';
import { useState } from 'react';
-
-/**
- * ProjectsPageComposed - Full projects page combining TopBar and SelectProject.
- * This demonstrates the complete page layout and interactions.
- */
const ProjectsPageComposed = ({
user,
projects,
@@ -60,11 +55,7 @@ const ProjectsPageComposed = ({
);
};
-/**
- * ProjectsPage - Full page view demonstrating the complete projects interface.
- */
const meta = {
- title: 'Pages/ProjectsPage',
component: ProjectsPageComposed,
parameters: {
layout: 'fullscreen',
diff --git a/apps/web/client/src/stories/SelectProject.stories.tsx b/apps/web/client/src/stories/SelectProject.stories.tsx
index 8423972c9f..daca379246 100644
--- a/apps/web/client/src/stories/SelectProject.stories.tsx
+++ b/apps/web/client/src/stories/SelectProject.stories.tsx
@@ -1,14 +1,9 @@
-import type { Meta, StoryObj } from '@storybook/react';
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
import { SelectProjectPresentation } from '@/app/projects/_components/select-presentation';
import type { Project } from '@onlook/models';
import { fn } from '@storybook/test';
-/**
- * SelectProject displays the main project selection interface with recent projects carousel,
- * templates section, and a full projects grid/masonry layout.
- */
const meta = {
- title: 'Projects/SelectProject',
component: SelectProjectPresentation,
parameters: {
layout: 'fullscreen',
diff --git a/apps/web/client/src/stories/Separator.stories.tsx b/apps/web/client/src/stories/Separator.stories.tsx
new file mode 100644
index 0000000000..530dd08518
--- /dev/null
+++ b/apps/web/client/src/stories/Separator.stories.tsx
@@ -0,0 +1,84 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import { Separator } from '@onlook/ui/separator';
+
+const meta = {
+ component: Separator,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ orientation: {
+ description: 'Orientation of the separator',
+ control: { type: 'select' },
+ options: ['horizontal', 'vertical'],
+ },
+ decorative: {
+ description: 'Whether the separator is decorative',
+ control: 'boolean',
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ orientation: 'horizontal',
+ },
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+};
+
+export const Horizontal: Story = {
+ render: () => (
+
+
+
Radix Primitives
+
+ An open-source UI component library.
+
+
+
+
+
Blog
+
+
Docs
+
+
Source
+
+
+ ),
+};
+
+export const Vertical: Story = {
+ render: () => (
+
+
Blog
+
+
Docs
+
+
Source
+
+ ),
+};
+
+export const InList: Story = {
+ render: () => (
+
+
Item 1
+
+
Item 2
+
+
Item 3
+
+
Item 4
+
+ ),
+};
diff --git a/apps/web/client/src/stories/Skeleton.stories.tsx b/apps/web/client/src/stories/Skeleton.stories.tsx
new file mode 100644
index 0000000000..dadab7f42d
--- /dev/null
+++ b/apps/web/client/src/stories/Skeleton.stories.tsx
@@ -0,0 +1,95 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import { Skeleton } from '@onlook/ui/skeleton';
+
+const meta = {
+ component: Skeleton,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ className: 'h-4 w-[250px]',
+ },
+};
+
+export const Circle: Story = {
+ args: {
+ className: 'h-12 w-12 rounded-full',
+ },
+};
+
+export const Card: Story = {
+ render: () => (
+
+ ),
+};
+
+export const TextLines: Story = {
+ render: () => (
+
+
+
+
+
+ ),
+};
+
+export const ProfileCard: Story = {
+ render: () => (
+
+ ),
+};
+
+export const ListItems: Story = {
+ render: () => (
+
+ {[1, 2, 3].map((i) => (
+
+ ))}
+
+ ),
+};
+
+export const FormSkeleton: Story = {
+ render: () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+};
diff --git a/apps/web/client/src/stories/Slider.stories.tsx b/apps/web/client/src/stories/Slider.stories.tsx
new file mode 100644
index 0000000000..f84e1c3394
--- /dev/null
+++ b/apps/web/client/src/stories/Slider.stories.tsx
@@ -0,0 +1,106 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import { fn } from '@storybook/test';
+import { Slider } from '@onlook/ui/slider';
+
+const meta = {
+ component: Slider,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ min: {
+ description: 'Minimum value',
+ control: 'number',
+ },
+ max: {
+ description: 'Maximum value',
+ control: 'number',
+ },
+ step: {
+ description: 'Step increment',
+ control: 'number',
+ },
+ disabled: {
+ description: 'Whether the slider is disabled',
+ control: 'boolean',
+ },
+ },
+ args: {
+ onValueChange: fn(),
+ },
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ defaultValue: [50],
+ min: 0,
+ max: 100,
+ },
+};
+
+export const WithStep: Story = {
+ args: {
+ defaultValue: [25],
+ min: 0,
+ max: 100,
+ step: 25,
+ },
+};
+
+export const Range: Story = {
+ args: {
+ defaultValue: [25, 75],
+ min: 0,
+ max: 100,
+ },
+};
+
+export const Disabled: Story = {
+ args: {
+ defaultValue: [50],
+ disabled: true,
+ },
+};
+
+export const CustomRange: Story = {
+ args: {
+ defaultValue: [5],
+ min: 0,
+ max: 10,
+ step: 1,
+ },
+};
+
+export const Values: Story = {
+ render: () => (
+
+ ),
+};
diff --git a/apps/web/client/src/stories/Switch.stories.tsx b/apps/web/client/src/stories/Switch.stories.tsx
new file mode 100644
index 0000000000..57bc4e093a
--- /dev/null
+++ b/apps/web/client/src/stories/Switch.stories.tsx
@@ -0,0 +1,86 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import { fn } from '@storybook/test';
+import { Switch } from '@onlook/ui/switch';
+import { Label } from '@onlook/ui/label';
+
+const meta = {
+ component: Switch,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ checked: {
+ description: 'Whether the switch is checked',
+ control: 'boolean',
+ },
+ disabled: {
+ description: 'Whether the switch is disabled',
+ control: 'boolean',
+ },
+ },
+ args: {
+ onCheckedChange: fn(),
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ checked: false,
+ disabled: false,
+ },
+};
+
+export const Checked: Story = {
+ args: {
+ checked: true,
+ },
+};
+
+export const Disabled: Story = {
+ args: {
+ disabled: true,
+ },
+};
+
+export const DisabledChecked: Story = {
+ args: {
+ checked: true,
+ disabled: true,
+ },
+};
+
+export const WithLabel: Story = {
+ render: () => (
+
+
+
+
+ ),
+};
+
+export const States: Story = {
+ render: () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+};
diff --git a/apps/web/client/src/stories/Tabs.stories.tsx b/apps/web/client/src/stories/Tabs.stories.tsx
new file mode 100644
index 0000000000..55bb4b72e9
--- /dev/null
+++ b/apps/web/client/src/stories/Tabs.stories.tsx
@@ -0,0 +1,119 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import { Tabs, TabsList, TabsTrigger, TabsContent } from '@onlook/ui/tabs';
+import { Settings, User, CreditCard } from 'lucide-react';
+
+const meta = {
+ component: Tabs,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => (
+
+
+ Account
+ Password
+
+
+
+ Make changes to your account here.
+
+
+
+
+ Change your password here.
+
+
+
+ ),
+};
+
+export const WithIcons: Story = {
+ render: () => (
+
+
+
+
+ Profile
+
+
+
+ Settings
+
+
+
+ Billing
+
+
+
+
+ Manage your profile information.
+
+
+
+
+ Configure your application settings.
+
+
+
+
+ Manage your billing and subscription.
+
+
+
+ ),
+};
+
+export const ThreeTabs: Story = {
+ render: () => (
+
+
+ Tab 1
+ Tab 2
+ Tab 3
+
+
+ Content for Tab 1
+
+
+ Content for Tab 2
+
+
+ Content for Tab 3
+
+
+ ),
+};
+
+export const DisabledTab: Story = {
+ render: () => (
+
+
+ Active
+
+ Disabled
+
+ Another
+
+
+ This tab is active.
+
+
+ Another tab content.
+
+
+ ),
+};
diff --git a/apps/web/client/src/stories/Textarea.stories.tsx b/apps/web/client/src/stories/Textarea.stories.tsx
new file mode 100644
index 0000000000..169e78bf67
--- /dev/null
+++ b/apps/web/client/src/stories/Textarea.stories.tsx
@@ -0,0 +1,77 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import { Textarea } from '@onlook/ui/textarea';
+
+const meta = {
+ component: Textarea,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ placeholder: {
+ description: 'Placeholder text',
+ control: 'text',
+ },
+ disabled: {
+ description: 'Whether the textarea is disabled',
+ control: 'boolean',
+ },
+ rows: {
+ description: 'Number of visible text lines',
+ control: 'number',
+ },
+ },
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ placeholder: 'Type your message here...',
+ },
+};
+
+export const WithValue: Story = {
+ args: {
+ defaultValue: 'This is some pre-filled content in the textarea.',
+ },
+};
+
+export const Disabled: Story = {
+ args: {
+ placeholder: 'Disabled textarea',
+ disabled: true,
+ },
+};
+
+export const WithRows: Story = {
+ args: {
+ placeholder: 'Textarea with 5 rows',
+ rows: 5,
+ },
+};
+
+export const LongContent: Story = {
+ args: {
+ defaultValue:
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
+ },
+};
+
+export const States: Story = {
+ render: () => (
+
+
+
+
+
+ ),
+};
diff --git a/apps/web/client/src/stories/Toggle.stories.tsx b/apps/web/client/src/stories/Toggle.stories.tsx
new file mode 100644
index 0000000000..a97ea94f6c
--- /dev/null
+++ b/apps/web/client/src/stories/Toggle.stories.tsx
@@ -0,0 +1,110 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import { fn } from '@storybook/test';
+import { Toggle } from '@onlook/ui/toggle';
+import { Bold, Italic, Underline } from 'lucide-react';
+
+const meta = {
+ component: Toggle,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ variant: {
+ description: 'Visual style variant',
+ control: { type: 'select' },
+ options: ['default', 'outline'],
+ },
+ size: {
+ description: 'Size of the toggle',
+ control: { type: 'select' },
+ options: ['default', 'sm', 'lg'],
+ },
+ pressed: {
+ description: 'Whether the toggle is pressed',
+ control: 'boolean',
+ },
+ disabled: {
+ description: 'Whether the toggle is disabled',
+ control: 'boolean',
+ },
+ },
+ args: {
+ onPressedChange: fn(),
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ children: ,
+ variant: 'default',
+ size: 'default',
+ },
+};
+
+export const Outline: Story = {
+ args: {
+ children: ,
+ variant: 'outline',
+ },
+};
+
+export const Pressed: Story = {
+ args: {
+ children: ,
+ pressed: true,
+ },
+};
+
+export const Disabled: Story = {
+ args: {
+ children: ,
+ disabled: true,
+ },
+};
+
+export const WithText: Story = {
+ args: {
+ children: (
+ <>
+
+ Bold
+ >
+ ),
+ },
+};
+
+export const Sizes: Story = {
+ render: () => (
+
+
+
+
+
+
+
+
+
+
+
+ ),
+};
+
+export const TextFormatting: Story = {
+ render: () => (
+
+
+
+
+
+
+
+
+
+
+
+ ),
+};
diff --git a/apps/web/client/src/stories/Tooltip.stories.tsx b/apps/web/client/src/stories/Tooltip.stories.tsx
new file mode 100644
index 0000000000..0cd623c30e
--- /dev/null
+++ b/apps/web/client/src/stories/Tooltip.stories.tsx
@@ -0,0 +1,139 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import { Tooltip, TooltipTrigger, TooltipContent } from '@onlook/ui/tooltip';
+import { Button } from '@onlook/ui/button';
+import { Plus, Settings, Trash2, Info } from 'lucide-react';
+
+const meta = {
+ component: Tooltip,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ delayDuration: {
+ description: 'Delay before tooltip appears (ms)',
+ control: 'number',
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => (
+
+
+
+
+
+ This is a tooltip
+
+
+ ),
+};
+
+export const WithDelay: Story = {
+ render: () => (
+
+
+
+
+
+ Tooltip with delay
+
+
+ ),
+};
+
+export const Positions: Story = {
+ render: () => (
+
+
+
+
+
+
+ Top tooltip
+
+
+
+
+
+
+
+ Bottom tooltip
+
+
+
+
+
+
+
+ Left tooltip
+
+
+
+
+
+
+
+ Right tooltip
+
+
+
+ ),
+};
+
+export const IconButtons: Story = {
+ render: () => (
+
+
+
+
+
+
+ Add new item
+
+
+
+
+
+
+
+ Settings
+
+
+
+
+
+
+
+ Delete
+
+
+
+ ),
+};
+
+export const WithoutArrow: Story = {
+ render: () => (
+
+
+
+
+
+ Tooltip without arrow
+
+
+ ),
+};
diff --git a/apps/web/client/src/stories/TopBar.stories.tsx b/apps/web/client/src/stories/TopBar.stories.tsx
index b5cf798285..03f833991b 100644
--- a/apps/web/client/src/stories/TopBar.stories.tsx
+++ b/apps/web/client/src/stories/TopBar.stories.tsx
@@ -1,13 +1,9 @@
-import type { Meta, StoryObj } from '@storybook/react';
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
import { TopBarPresentation } from '@/app/projects/_components/top-bar-presentation';
import type { User } from '@onlook/models';
import { fn } from '@storybook/test';
-/**
- * TopBar displays the main navigation bar with logo, search, create dropdown, and user avatar.
- */
const meta = {
- title: 'Projects/TopBar',
component: TopBarPresentation,
parameters: {
layout: 'fullscreen',