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..53f4c04347 --- /dev/null +++ b/apps/web/client/src/stories/Alert.stories.tsx @@ -0,0 +1,113 @@ +import type { Meta, StoryObj } from '@storybook/nextjs-vite'; +import { Alert, AlertDescription, AlertTitle } from '@onlook/ui/alert'; +import { AlertCircle, CheckCircle2, Info, AlertTriangle } from 'lucide-react'; + +const meta = { + component: Alert, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + decorators: [ + (Story) => ( +
+ +
+ ), + ], + argTypes: { + variant: { + description: 'Visual style variant of the alert', + control: { type: 'select' }, + options: ['default', 'destructive'], + }, + }, +} 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: () => ( + + + Alert with title only + + ), +}; + +export const DescriptionOnly: Story = { + render: () => ( + + + + Alert with description only, no title. + + + ), +}; + +export const AllVariants: 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..adff485840 --- /dev/null +++ b/apps/web/client/src/stories/Avatar.stories.tsx @@ -0,0 +1,93 @@ +import type { Meta, StoryObj } from '@storybook/nextjs-vite'; +import { Avatar, AvatarFallback, AvatarImage } 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: () => ( + + + JD + + ), +}; + +export const WithFallback: Story = { + render: () => ( + + + AB + + ), +}; + +export const FallbackOnly: Story = { + render: () => ( + + CD + + ), +}; + +export const Sizes: Story = { + render: () => ( +
+ + + SM + + + + DF + + + + LG + + + + XL + +
+ ), +}; + +export const Group: Story = { + render: () => ( +
+ + + U1 + + + + U2 + + + + U3 + + + +5 + +
+ ), +}; + +export const WithRealImage: Story = { + render: () => ( + + + JD + + ), +}; 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..0b51229d20 --- /dev/null +++ b/apps/web/client/src/stories/Badge.stories.tsx @@ -0,0 +1,78 @@ +import type { Meta, StoryObj } from '@storybook/nextjs-vite'; +import { Badge } from '@onlook/ui/badge'; + +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 AllVariants: Story = { + render: () => ( +
+ Default + Secondary + Destructive + Outline +
+ ), +}; + +export const WithIcon: Story = { + render: () => ( +
+ + * + New + + + ! + Warning + +
+ ), +}; 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..5c7b478a1b --- /dev/null +++ b/apps/web/client/src/stories/Card.stories.tsx @@ -0,0 +1,104 @@ +import type { Meta, StoryObj } from '@storybook/nextjs-vite'; +import { Button } from '@onlook/ui/button'; +import { + Card, + CardAction, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@onlook/ui/card'; + +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 with some text explaining the purpose of this card.

+
+ + + +
+ ), +}; + +export const WithAction: Story = { + render: () => ( + + + Card with Action + This card has an action button in the header + + + + + +

The action button appears in the top right corner of the header.

+
+
+ ), +}; + +export const Simple: Story = { + render: () => ( + + + Simple Card + + +

A simple card with just a title and content.

+
+
+ ), +}; + +export const WithFooterActions: Story = { + render: () => ( + + + Confirm Action + Are you sure you want to proceed? + + +

This action cannot be undone. Please review before confirming.

+
+ + + + +
+ ), +}; + +export const ContentOnly: Story = { + render: () => ( + + +

A card with only content, no header or footer.

+
+
+ ), +}; 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..d8cc39cea2 --- /dev/null +++ b/apps/web/client/src/stories/Checkbox.stories.tsx @@ -0,0 +1,95 @@ +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, + }, +}; + +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 WithDescription: Story = { + render: () => ( +
+ +
+ +

+ Get notified about new features and updates. +

+
+
+ ), +}; + +export const CheckboxGroup: Story = { + render: () => ( +
+
+ + +
+
+ + +
+
+ + +
+
+ ), +}; diff --git a/apps/web/client/src/stories/HighlightText.stories.tsx b/apps/web/client/src/stories/HighlightText.stories.tsx new file mode 100644 index 0000000000..956d4b7a25 --- /dev/null +++ b/apps/web/client/src/stories/HighlightText.stories.tsx @@ -0,0 +1,86 @@ +import type { Meta, StoryObj } from '@storybook/nextjs-vite'; +import { HighlightText } from '@/app/projects/_components/select/highlight-text'; + +const meta = { + component: HighlightText, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + text: { + description: 'The text to display', + control: 'text', + }, + searchQuery: { + description: 'The search query to highlight within the text', + control: 'text', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + text: 'E-commerce Dashboard', + searchQuery: 'dash', + }, +}; + +export const NoMatch: Story = { + args: { + text: 'E-commerce Dashboard', + searchQuery: 'xyz', + }, +}; + +export const EmptyQuery: Story = { + args: { + text: 'E-commerce Dashboard', + searchQuery: '', + }, +}; + +export const FullMatch: Story = { + args: { + text: 'Dashboard', + searchQuery: 'Dashboard', + }, +}; + +export const CaseInsensitive: Story = { + args: { + text: 'E-commerce Dashboard', + searchQuery: 'DASH', + }, +}; + +export const MultipleMatches: Story = { + args: { + text: 'Dashboard for dashboard analytics', + searchQuery: 'dashboard', + }, +}; + +export const PartialWord: Story = { + args: { + text: 'Portfolio Website Project', + searchQuery: 'port', + }, +}; + +export const SpecialCharacters: Story = { + args: { + text: 'Project (v2.0) - Beta', + searchQuery: '(v2', + }, +}; + +export const LongText: Story = { + args: { + text: 'This is a very long project name that contains the word dashboard somewhere in the middle of the text', + searchQuery: 'dashboard', + }, +}; 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..b85fa3c6d8 --- /dev/null +++ b/apps/web/client/src/stories/Input.stories.tsx @@ -0,0 +1,109 @@ +import type { Meta, StoryObj } from '@storybook/nextjs-vite'; +import { Input } from '@onlook/ui/input'; +import { Label } from '@onlook/ui/label'; + +const meta = { + component: Input, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + decorators: [ + (Story) => ( +
+ +
+ ), + ], + argTypes: { + type: { + description: 'The type of input', + control: { type: 'select' }, + options: ['text', 'email', 'password', 'number', 'search', 'tel', 'url'], + }, + placeholder: { + description: 'Placeholder text', + control: 'text', + }, + disabled: { + description: 'Whether the input is disabled', + control: 'boolean', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + type: 'text', + placeholder: 'Enter text...', + }, +}; + +export const WithLabel: Story = { + render: () => ( +
+ + +
+ ), +}; + +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: 'Pre-filled value', + }, +}; + +export const Search: Story = { + args: { + type: 'search', + placeholder: 'Search...', + }, +}; + +export const Number: Story = { + args: { + type: 'number', + placeholder: '0', + }, +}; + +export const File: Story = { + args: { + type: 'file', + }, +}; + +export const Invalid: 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..6deed46b69 --- /dev/null +++ b/apps/web/client/src/stories/Progress.stories.tsx @@ -0,0 +1,89 @@ +import type { Meta, StoryObj } from '@storybook/nextjs-vite'; +import { Progress } from '@onlook/ui/progress'; + +const meta = { + component: Progress, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + decorators: [ + (Story) => ( +
+ +
+ ), + ], + argTypes: { + value: { + description: 'Progress value (0-100)', + control: { type: 'range', min: 0, max: 100, step: 1 }, + }, + }, +} 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 AllStates: Story = { + render: () => ( +
+
+

0%

+ +
+
+

25%

+ +
+
+

50%

+ +
+
+

75%

+ +
+
+

100%

+ +
+
+ ), +}; 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..f80a3526e4 --- /dev/null +++ b/apps/web/client/src/stories/Skeleton.stories.tsx @@ -0,0 +1,102 @@ +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 = { + render: () => , +}; + +export const Circle: Story = { + render: () => , +}; + +export const Card: Story = { + render: () => ( +
+ +
+ + +
+
+ ), +}; + +export const Avatar: Story = { + render: () => ( +
+ +
+ + +
+
+ ), +}; + +export const List: Story = { + render: () => ( +
+ {[1, 2, 3].map((i) => ( +
+ +
+ + +
+
+ ))} +
+ ), +}; + +export const Form: Story = { + render: () => ( +
+
+ + +
+
+ + +
+
+ + +
+ +
+ ), +}; + +export const Table: Story = { + render: () => ( +
+
+ + + + +
+ {[1, 2, 3, 4].map((i) => ( +
+ + + + +
+ ))} +
+ ), +}; 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..b5930a8bf7 --- /dev/null +++ b/apps/web/client/src/stories/Switch.stories.tsx @@ -0,0 +1,95 @@ +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, + }, +}; + +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 WithDescription: Story = { + render: () => ( +
+ +
+ +

+ Receive push notifications for important updates. +

+
+
+ ), +}; + +export const SwitchGroup: Story = { + render: () => ( +
+
+ + +
+
+ + +
+
+ + +
+
+ ), +}; diff --git a/apps/web/client/src/stories/TemplateCard.stories.tsx b/apps/web/client/src/stories/TemplateCard.stories.tsx new file mode 100644 index 0000000000..5a12a10692 --- /dev/null +++ b/apps/web/client/src/stories/TemplateCard.stories.tsx @@ -0,0 +1,160 @@ +import type { Meta, StoryObj } from '@storybook/nextjs-vite'; +import { fn } from '@storybook/test'; +import { TemplateCard } from '@/app/projects/_components/templates/template-card'; + +const meta = { + component: TemplateCard, + parameters: { + layout: 'centered', + backgrounds: { + default: 'dark', + }, + }, + tags: ['autodocs'], + argTypes: { + title: { + description: 'Title of the template', + control: 'text', + }, + description: { + description: 'Description of the template', + control: 'text', + }, + image: { + description: 'Preview image URL', + control: 'text', + }, + isNew: { + description: 'Whether to show the "New" badge', + control: 'boolean', + }, + isStarred: { + description: 'Whether the template is starred/favorited', + control: 'boolean', + }, + }, + args: { + onClick: fn(), + onToggleStar: fn(), + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + title: 'Next.js Starter', + description: 'A full-featured Next.js template with authentication and database integration.', + image: 'https://images.unsplash.com/photo-1633356122544-f134324a6cee?w=800&q=80', + isNew: false, + isStarred: false, + }, +}; + +export const WithNewBadge: Story = { + args: { + title: 'React Dashboard', + description: 'Modern dashboard template with charts, tables, and analytics components.', + image: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=800&q=80', + isNew: true, + isStarred: false, + }, +}; + +export const Starred: Story = { + args: { + title: 'Portfolio Template', + description: 'Clean and minimal portfolio template for showcasing your work.', + image: 'https://images.unsplash.com/photo-1507238691740-187a5b1d37b8?w=800&q=80', + isNew: false, + isStarred: true, + }, +}; + +export const NewAndStarred: Story = { + args: { + title: 'E-commerce Starter', + description: 'Complete e-commerce solution with cart, checkout, and payment integration.', + image: 'https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=800&q=80', + isNew: true, + isStarred: true, + }, +}; + +export const NoImage: Story = { + args: { + title: 'Blank Template', + description: 'Start from scratch with a minimal setup.', + image: undefined, + isNew: false, + isStarred: false, + }, +}; + +export const LongTitle: Story = { + args: { + title: 'Super Long Template Name That Should Truncate Properly', + description: 'A template with a very long name to test truncation behavior.', + image: 'https://images.unsplash.com/photo-1547658719-da2b51169166?w=800&q=80', + isNew: false, + isStarred: false, + }, +}; + +export const LongDescription: Story = { + args: { + title: 'Documentation Site', + description: 'A comprehensive documentation template with search functionality, versioning support, multiple language support, and beautiful syntax highlighting for code blocks.', + image: 'https://images.unsplash.com/photo-1504868584819-f8e8b4b6d7e3?w=800&q=80', + isNew: false, + isStarred: false, + }, +}; + +export const WithoutStarButton: Story = { + args: { + title: 'Simple Template', + description: 'A template without the star/favorite functionality.', + image: 'https://images.unsplash.com/photo-1512941937669-90a1b58e7e9c?w=800&q=80', + isNew: false, + isStarred: false, + onToggleStar: undefined, + }, +}; + +export const AllVariants: Story = { + render: () => ( +
+ + + + +
+ ), +}; 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..0c696afc1a --- /dev/null +++ b/apps/web/client/src/stories/Textarea.stories.tsx @@ -0,0 +1,84 @@ +import type { Meta, StoryObj } from '@storybook/nextjs-vite'; +import { Textarea } from '@onlook/ui/textarea'; +import { Label } from '@onlook/ui/label'; + +const meta = { + component: Textarea, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + decorators: [ + (Story) => ( +
+ +
+ ), + ], + 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', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + placeholder: 'Enter your message...', + }, +}; + +export const WithLabel: Story = { + render: () => ( +
+ +