+
-
+ >
+ ),
+};
+
+// ============================================================================
+// Promise Toast
+// ============================================================================
+
+export const PromiseToast = {
+ name: 'Promise Toast',
+ render: () => {
+ function simulateSuccess() {
+ return new Promise((resolve) => setTimeout(resolve, 2000));
+ }
+
+ function simulateFailure() {
+ return new Promise((_, reject) => setTimeout(() => reject(new Error('Network error')), 2000));
+ }
+
+ return (
+ <>
+
+
+
+ toast.promise(simulateSuccess(), {
+ loading: 'Saving changes…',
+ success: 'Changes saved successfully!',
+ error: 'Failed to save changes.',
+ })
+ }
+ >
+ Promise (Success)
+
+
+ toast.promise(simulateFailure(), {
+ loading: 'Uploading file…',
+ success: 'File uploaded!',
+ error: 'Upload failed. Please try again.',
+ })
+ }
+ >
+ Promise (Failure)
+
+
+ >
+ );
+ },
+};
+
+// ============================================================================
+// Rich Content Toast
+// ============================================================================
+
+export const RichContentToast = {
+ name: 'Rich Content Toast',
+ render: () => (
+ <>
+
+
+
+ toast('Deployment Complete', {
+ description: (
+
+
Environment: Production
+
Version: v2.4.0
+
Duration: 1m 23s
+
+ ),
+ })
+ }
+ >
+ Rich Description
+
+
+ toast.success('Team member added', {
+ description: 'jane.doe@company.com has been invited to the project.',
+ })
+ }
+ >
+ With Long Description
+
+
+ >
+ ),
+};
+
+// ============================================================================
+// Multiple Toasts
+// ============================================================================
+
+export const MultipleToasts = {
+ name: 'Multiple Toasts',
+ render: () => {
+ function fireAll() {
+ toast.success('File uploaded');
+ setTimeout(() => toast.info('Processing started'), 300);
+ setTimeout(() => toast.warning('Large file detected'), 600);
+ setTimeout(() => toast('Estimated time: 2 minutes'), 900);
+ }
+
+ return (
+ <>
+
+
+
+ Fire Multiple Toasts
+
+ toast.dismiss()}>
+ Dismiss All
+
+
+ >
+ );
+ },
+};
+
+// ============================================================================
+// Toast Positioning
+// ============================================================================
+
+export const ToastPositioning = {
+ name: 'Toast Positioning',
+ render: () => (
+
+
+
Bottom Right
+
+ Used for background notifications, system events, and non-critical feedback.
+
+
+
toast('Background sync complete', { position: 'bottom-right' })}
+ >
+ Bottom Right Toast
+
+
+
+
+
Top Center
+
+ Used for important confirmations, form submissions, and user-initiated actions that need prominent visibility.
+
+
toast.success('Profile updated', { position: 'top-center' })}
+ >
+ Top Center Toast
+
+
+
+ ),
+};
+
+// ============================================================================
+// Toast Duration
+// ============================================================================
+
+export const ToastDuration = {
+ name: 'Toast Duration',
+ render: () => (
+ <>
+
+
+ toast('Quick notification', { duration: 2000 })}
+ >
+ Short (2s)
+
+ toast('Standard notification', { duration: 4000 })}
+ >
+ Default (4s)
+
+
+ toast('This toast stays longer for important messages', { duration: 8000 })
+ }
+ >
+ Long (8s)
+
+
+ toast('This toast will not auto-dismiss', {
+ duration: Number.POSITIVE_INFINITY,
+ action: { label: 'Dismiss', onClick: () => {} },
+ })
+ }
+ >
+ Persistent
+
+
+ >
+ ),
+};
+
+// ============================================================================
+// Usage Examples — Form Submissions
+// ============================================================================
+
+export const FormSubmissions = {
+ name: 'Form Submissions',
+ render: () => {
+ function simulateSave() {
+ return new Promise((resolve) => setTimeout(resolve, 1500));
+ }
+
+ return (
+ <>
+
+
+
+ toast.promise(simulateSave(), {
+ loading: 'Saving profile…',
+ success: 'Profile updated successfully!',
+ error: 'Failed to update profile.',
+ })
+ }
+ >
+ Save Profile
+
+
+ toast.success('Settings saved', {
+ description: 'Your notification preferences have been updated.',
+ })
+ }
+ >
+ Save Settings
+
+
+ toast.error('Validation failed', {
+ description: 'Please fix the errors in the form before submitting.',
+ })
+ }
+ >
+ Validation Error
+
+
+ >
+ );
+ },
+};
+
+// ============================================================================
+// Usage Examples — File Operations
+// ============================================================================
+
+export const FileOperations = {
+ name: 'File Operations',
+ render: () => {
+ function simulateUpload() {
+ return new Promise((resolve) => setTimeout(resolve, 2000));
+ }
+
+ return (
+ <>
+
+
+
+ toast.promise(simulateUpload(), {
+ loading: 'Uploading report.pdf…',
+ success: 'File uploaded successfully!',
+ error: 'Upload failed. Check your connection.',
+ })
+ }
+ >
+ Upload File
+
+
+ toast('File moved to trash', {
+ description: 'quarterly-report.pdf',
+ action: {
+ label: 'Undo',
+ onClick: () => toast.success('File restored'),
+ },
+ })
+ }
+ >
+ Delete File
+
+
+ toast.success('Export complete', {
+ description: 'data-export.csv has been downloaded.',
+ })
+ }
+ >
+ Export Data
+
+
+ >
+ );
+ },
+};
+
+// ============================================================================
+// Usage Examples — User Actions
+// ============================================================================
+
+export const UserActions = {
+ name: 'User Actions',
+ render: () => (
+ <>
+
+
+
+ toast.success('Invitation sent', {
+ description: 'An invite has been sent to jane@company.com.',
+ })
+ }
+ >
+ Invite Member
+
+
+ toast('Link copied to clipboard', { duration: 2000 })
+ }
+ >
+ Copy Link
+
+
+ toast.warning('Are you sure?', {
+ description: 'This action cannot be undone.',
+ action: {
+ label: 'Confirm',
+ onClick: () => toast.success('Item permanently deleted'),
+ },
+ cancel: {
+ label: 'Cancel',
+ onClick: () => {},
+ },
+ duration: Number.POSITIVE_INFINITY,
+ })
+ }
+ >
+ Destructive Action
>
diff --git a/packages/apollo-wind/src/components/ui/spinner.stories.tsx b/packages/apollo-wind/src/components/ui/spinner.stories.tsx
index d6c6f2ce7..f5e8b11ef 100644
--- a/packages/apollo-wind/src/components/ui/spinner.stories.tsx
+++ b/packages/apollo-wind/src/components/ui/spinner.stories.tsx
@@ -4,7 +4,7 @@ import { Spinner } from './spinner';
import { Row, Column } from './layout';
const meta: Meta
= {
- title: 'Design System/Feedback/Spinner',
+ title: 'Components/Feedback/Spinner',
component: Spinner,
tags: ['autodocs'],
argTypes: {
diff --git a/packages/apollo-wind/src/components/ui/stats-card.stories.tsx b/packages/apollo-wind/src/components/ui/stats-card.stories.tsx
index 8a7245796..92264c19b 100644
--- a/packages/apollo-wind/src/components/ui/stats-card.stories.tsx
+++ b/packages/apollo-wind/src/components/ui/stats-card.stories.tsx
@@ -3,7 +3,7 @@ import { Activity, CreditCard, DollarSign, ShoppingCart, TrendingUp, Users } fro
import { StatsCard } from './stats-card';
const meta = {
- title: 'Design System/Data Display/Stats Card',
+ title: 'Components/Data Display/Stats Card',
component: StatsCard,
parameters: {
layout: 'centered',
diff --git a/packages/apollo-wind/src/components/ui/stepper.stories.tsx b/packages/apollo-wind/src/components/ui/stepper.stories.tsx
index 43fc16aa9..2639933e8 100644
--- a/packages/apollo-wind/src/components/ui/stepper.stories.tsx
+++ b/packages/apollo-wind/src/components/ui/stepper.stories.tsx
@@ -5,7 +5,7 @@ import { Card, CardContent } from './card';
import { Step, Stepper } from './stepper';
const meta = {
- title: 'Design System/Navigation/Stepper',
+ title: 'Components/Navigation/Stepper',
component: Stepper,
parameters: {
layout: 'centered',
diff --git a/packages/apollo-wind/src/components/ui/switch.stories.tsx b/packages/apollo-wind/src/components/ui/switch.stories.tsx
index 18111bac0..66165ac18 100644
--- a/packages/apollo-wind/src/components/ui/switch.stories.tsx
+++ b/packages/apollo-wind/src/components/ui/switch.stories.tsx
@@ -4,7 +4,7 @@ import { Switch } from './switch';
import { Row } from './layout';
const meta: Meta = {
- title: 'Design System/Core/Switch',
+ title: 'Components/Core/Switch',
component: Switch,
tags: ['autodocs'],
};
diff --git a/packages/apollo-wind/src/components/ui/table.stories.tsx b/packages/apollo-wind/src/components/ui/table.stories.tsx
deleted file mode 100644
index 00ae77a35..000000000
--- a/packages/apollo-wind/src/components/ui/table.stories.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import type { Meta } from '@storybook/react-vite';
-import {
- Table,
- TableBody,
- TableCaption,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from './table';
-
-const meta: Meta = {
- title: 'Design System/Data Display/Table',
- component: Table,
- tags: ['autodocs'],
-};
-
-export default meta;
-
-const invoices = [
- {
- invoice: 'INV001',
- paymentStatus: 'Paid',
- totalAmount: '$250.00',
- paymentMethod: 'Credit Card',
- },
- {
- invoice: 'INV002',
- paymentStatus: 'Pending',
- totalAmount: '$150.00',
- paymentMethod: 'PayPal',
- },
- {
- invoice: 'INV003',
- paymentStatus: 'Unpaid',
- totalAmount: '$350.00',
- paymentMethod: 'Bank Transfer',
- },
- {
- invoice: 'INV004',
- paymentStatus: 'Paid',
- totalAmount: '$450.00',
- paymentMethod: 'Credit Card',
- },
-];
-
-export const Default = {
- args: {},
- render: () => (
-
- A list of your recent invoices.
-
-
- Invoice
- Status
- Method
- Amount
-
-
-
- {invoices.map((invoice) => (
-
- {invoice.invoice}
- {invoice.paymentStatus}
- {invoice.paymentMethod}
- {invoice.totalAmount}
-
- ))}
-
-
- ),
-};
diff --git a/packages/apollo-wind/src/components/ui/tabs.stories.tsx b/packages/apollo-wind/src/components/ui/tabs.stories.tsx
index 8251564f3..48b0a0dab 100644
--- a/packages/apollo-wind/src/components/ui/tabs.stories.tsx
+++ b/packages/apollo-wind/src/components/ui/tabs.stories.tsx
@@ -1,20 +1,26 @@
import type { Meta } from '@storybook/react-vite';
+import { Bell, Shield, User } from 'lucide-react';
import { Button } from './button';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './card';
import { Input } from './input';
import { Label } from './label';
+import { Switch } from './switch';
import { Tabs, TabsContent, TabsList, TabsTrigger } from './tabs';
const meta: Meta = {
- title: 'Design System/Navigation/Tabs',
+ title: 'Components/Navigation/Tabs',
component: Tabs,
tags: ['autodocs'],
};
export default meta;
-export const Default = {
- args: {},
+// ============================================================================
+// Basic
+// ============================================================================
+
+export const Basic = {
+ name: 'Basic',
render: () => (
@@ -22,51 +28,303 @@ export const Default = {
Password
-
-
- Account
-
- Make changes to your account here. Click save when you're done.
-
-
-
-
-
-
-
-
-
-
-
-
-
- Save changes
-
-
+
+
Manage your account settings and preferences.
+
-
-
- Password
-
- Change your password here. After saving, you'll be logged out.
-
-
-
-
-
-
-
-
-
-
-
-
-
- Save password
-
-
+
+
Change your password and security settings.
+
+
+
+ ),
+};
+
+// ============================================================================
+// Line
+// ============================================================================
+
+export const Line = {
+ name: 'Line',
+ render: () => (
+
+
+
+ Overview
+
+
+ Analytics
+
+
+ Reports
+
+
+
+
+
Overview of your project performance and metrics.
+
+
+
+
+
Detailed analytics and usage data.
+
+
+
+
+
Generated reports and exports.
+
+
+
+ ),
+};
+
+// ============================================================================
+// Disabled
+// ============================================================================
+
+export const Disabled = {
+ name: 'Disabled',
+ render: () => (
+
+
+ General
+ Security
+
+ Billing
+
+
+ Advanced
+
+
+
+
+
General settings for your workspace.
+
+
+
+
+
Security and access control settings.
+
),
};
+
+// ============================================================================
+// Examples
+// ============================================================================
+
+export const Examples = {
+ name: 'Examples',
+ render: () => (
+
+ {/* Settings page */}
+
+
Settings Page
+
+
+
+
+ Profile
+
+
+
+ Notifications
+
+
+
+ Security
+
+
+
+
+
+ Profile
+
+ Update your personal information.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Save changes
+
+
+
+
+
+
+ Notifications
+
+ Configure how you receive notifications.
+
+
+
+
+
+
Email notifications
+
Receive updates via email
+
+
+
+
+
+
Push notifications
+
Receive push alerts
+
+
+
+
+
+
Weekly digest
+
Get a summary every week
+
+
+
+
+
+
+
+
+
+ Security
+
+ Manage your password and two-factor authentication.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Update password
+
+
+
+
+
+
+ {/* Pricing tabs */}
+
+
Pricing Plans
+
+
+ Monthly
+ Annual
+
+
+
+ {[
+ { name: 'Starter', price: '$9', desc: 'For individuals' },
+ { name: 'Pro', price: '$29', desc: 'For small teams' },
+ { name: 'Enterprise', price: '$99', desc: 'For organizations' },
+ ].map((plan) => (
+
+
+
{plan.name}
+
{plan.desc}
+
+
+
{plan.price}
+
/month
+
+
+ ))}
+
+
+
+
+ {[
+ { name: 'Starter', price: '$7', desc: 'For individuals' },
+ { name: 'Pro', price: '$23', desc: 'For small teams' },
+ { name: 'Enterprise', price: '$79', desc: 'For organizations' },
+ ].map((plan) => (
+
+
+
{plan.name}
+
{plan.desc}
+
+
+
{plan.price}
+
/month (billed annually)
+
+
+ ))}
+
+
+
+
+
+ {/* Dashboard tabs (line style) */}
+
+
Dashboard
+
+
+ {['Overview', 'Automations', 'Logs', 'Settings'].map((tab) => (
+
+ {tab}
+
+ ))}
+
+
+
+ {[
+ { label: 'Total Runs', value: '2,450' },
+ { label: 'Success Rate', value: '98.2%' },
+ { label: 'Avg Duration', value: '1.3s' },
+ ].map((stat) => (
+
+
{stat.label}
+
{stat.value}
+
+ ))}
+
+
+
+
+
Your active automations and workflows.
+
+
+
+
+
Recent execution logs and audit trail.
+
+
+
+
+
Workspace configuration and preferences.
+
+
+
+
+
+ ),
+};
diff --git a/packages/apollo-wind/src/components/ui/textarea.stories.tsx b/packages/apollo-wind/src/components/ui/textarea.stories.tsx
index 6ba2ed174..f21c1b913 100644
--- a/packages/apollo-wind/src/components/ui/textarea.stories.tsx
+++ b/packages/apollo-wind/src/components/ui/textarea.stories.tsx
@@ -3,7 +3,7 @@ import { Label } from './label';
import { Textarea } from './textarea';
const meta = {
- title: 'Design System/Core/Textarea',
+ title: 'Components/Core/Textarea',
component: Textarea,
parameters: {
layout: 'centered',
diff --git a/packages/apollo-wind/src/components/ui/toggle-group.stories.tsx b/packages/apollo-wind/src/components/ui/toggle-group.stories.tsx
index a32bfe7fb..6e90cec7f 100644
--- a/packages/apollo-wind/src/components/ui/toggle-group.stories.tsx
+++ b/packages/apollo-wind/src/components/ui/toggle-group.stories.tsx
@@ -2,7 +2,7 @@ import type { Meta } from '@storybook/react-vite';
import { ToggleGroup, ToggleGroupItem } from './toggle-group';
const meta = {
- title: 'Design System/Core/Toggle Group',
+ title: 'Components/Core/Toggle Group',
component: ToggleGroup,
parameters: {
layout: 'centered',
diff --git a/packages/apollo-wind/src/components/ui/toggle.stories.tsx b/packages/apollo-wind/src/components/ui/toggle.stories.tsx
index 26fac4fdb..e631adb29 100644
--- a/packages/apollo-wind/src/components/ui/toggle.stories.tsx
+++ b/packages/apollo-wind/src/components/ui/toggle.stories.tsx
@@ -2,7 +2,7 @@ import type { Meta } from '@storybook/react-vite';
import { Toggle } from './toggle';
const meta: Meta = {
- title: 'Design System/Core/Toggle',
+ title: 'Components/Core/Toggle',
component: Toggle,
tags: ['autodocs'],
};
diff --git a/packages/apollo-wind/src/components/ui/tooltip.stories.tsx b/packages/apollo-wind/src/components/ui/tooltip.stories.tsx
index 6977fb645..aeb651344 100644
--- a/packages/apollo-wind/src/components/ui/tooltip.stories.tsx
+++ b/packages/apollo-wind/src/components/ui/tooltip.stories.tsx
@@ -1,17 +1,43 @@
import type { Meta } from '@storybook/react-vite';
+import {
+ Bold,
+ Italic,
+ Underline,
+ AlignLeft,
+ AlignCenter,
+ AlignRight,
+ Copy,
+ Download,
+ Heart,
+ Info,
+ Pencil,
+ Plus,
+ Settings,
+ Share2,
+ Trash2,
+ Undo,
+ Redo,
+ Keyboard,
+} from 'lucide-react';
import { Button } from './button';
+import { Input } from './input';
+import { Separator } from './separator';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip';
const meta: Meta = {
- title: 'Design System/Overlays/Tooltip',
+ title: 'Components/Overlays/Tooltip',
component: Tooltip,
tags: ['autodocs'],
};
export default meta;
-export const Default = {
- args: {},
+// ============================================================================
+// Basic
+// ============================================================================
+
+export const Basic = {
+ name: 'Basic',
render: () => (
@@ -19,9 +45,378 @@ export const Default = {
Hover me
- Add to library
+ This is a tooltip
),
};
+
+// ============================================================================
+// Position
+// ============================================================================
+
+export const Position = {
+ name: 'Position',
+ render: () => (
+
+
+
+
+ Top
+
+
+ Tooltip on top
+
+
+
+
+
+ Bottom
+
+
+ Tooltip on bottom
+
+
+
+
+
+ Left
+
+
+ Tooltip on left
+
+
+
+
+
+ Right
+
+
+ Tooltip on right
+
+
+
+
+ ),
+};
+
+// ============================================================================
+// Tooltip with Icons
+// ============================================================================
+
+export const TooltipWithIcons = {
+ name: 'Tooltip with Icons',
+ render: () => (
+
+
+ {[
+ { icon:
, label: 'Bold' },
+ { icon:
, label: 'Italic' },
+ { icon:
, label: 'Underline' },
+ { divider: true },
+ { icon:
, label: 'Align Left' },
+ { icon:
, label: 'Align Center' },
+ { icon:
, label: 'Align Right' },
+ ].map((item, i) =>
+ 'divider' in item ? (
+
+ ) : (
+
+
+
+ {item.icon}
+
+
+
+ {item.label}
+
+
+ )
+ )}
+
+
+ ),
+};
+
+// ============================================================================
+// Tooltip with Rich Content
+// ============================================================================
+
+export const TooltipWithRichContent = {
+ name: 'Tooltip with Rich Content',
+ render: () => (
+
+
+
+
+
+
+ API Limits
+
+
+
+
+
Rate Limiting
+
+ Your current plan allows 1,000 requests per minute. Exceeding this limit will result in 429 errors.
+
+
+
+ Press ⌘K to view full docs
+
+
+
+
+
+
+
+
+
+ Storage
+
+
+
+
+
Storage Usage
+
+
+ 7.5 GB of 10 GB used
+
+
+
+
+
+
+ ),
+};
+
+// ============================================================================
+// Tooltip with Different Content
+// ============================================================================
+
+export const TooltipWithDifferentContent = {
+ name: 'Tooltip with Different Content',
+ render: () => (
+
+
+ {/* Short text */}
+
+
+ Short
+
+
+ Save
+
+
+
+ {/* With shortcut */}
+
+
+ With Shortcut
+
+
+
+ Save
+ ⌘S
+
+
+
+
+ {/* Multi-line */}
+
+
+ Multi-line
+
+
+ This tooltip has a longer description that wraps across multiple lines.
+
+
+
+ {/* Disabled state info */}
+
+
+
+ Disabled
+
+
+
+ You need admin access to perform this action.
+
+
+
+
+ ),
+};
+
+// ============================================================================
+// Tooltip with Form Elements
+// ============================================================================
+
+export const TooltipWithFormElements = {
+ name: 'Tooltip with Form Elements',
+ render: () => (
+
+
+
+
+
+
+
+
+
+
+ Your API key is used to authenticate requests. Keep it secret.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ We'll send POST requests to this URL when events occur.
+
+
+
+
+
+
+
+ ),
+};
+
+// ============================================================================
+// Tooltip with Delay (Instant)
+// ============================================================================
+
+export const TooltipWithDelay = {
+ name: 'Tooltip with Delay',
+ render: () => (
+
+
+
+
+ Instant Tooltip
+
+
+ This tooltip appears immediately with zero delay.
+
+
+
+
+ ),
+};
+
+// ============================================================================
+// Examples
+// ============================================================================
+
+export const Examples = {
+ name: 'Examples',
+ render: () => (
+
+
+ {/* Toolbar */}
+
+
Toolbar
+
+ {[
+ { icon:
, label: 'Undo', shortcut: '⌘Z' },
+ { icon:
, label: 'Redo', shortcut: '⇧⌘Z' },
+ { divider: true },
+ { icon:
, label: 'Add item', shortcut: '⌘N' },
+ { icon:
, label: 'Edit', shortcut: '⌘E' },
+ { icon:
, label: 'Duplicate', shortcut: '⌘D' },
+ { divider: true },
+ { icon:
, label: 'Share' },
+ { icon:
, label: 'Download' },
+ { divider: true },
+ { icon:
, label: 'Delete', shortcut: '⌫' },
+ ].map((item, i) =>
+ 'divider' in item ? (
+
+ ) : (
+
+
+
+ {item.icon}
+
+
+
+
+ {item.label}
+ {item.shortcut && (
+ {item.shortcut}
+ )}
+
+
+
+ )
+ )}
+
+
+
+ {/* Action buttons */}
+
+
Action Buttons
+
+
+
+
+
+
+
+
+ Add to favorites
+
+
+
+
+
+
+
+
+
+
+ Share project
+
+
+
+
+
+
+
+
+
+
+ Settings
+
+
+
+
+
+
+
+
+
+
+
+
+ Select items to delete
+
+
+
+
+
+
+ ),
+};
diff --git a/packages/apollo-wind/src/components/ui/tree-view.stories.tsx b/packages/apollo-wind/src/components/ui/tree-view.stories.tsx
new file mode 100644
index 000000000..708e4dc28
--- /dev/null
+++ b/packages/apollo-wind/src/components/ui/tree-view.stories.tsx
@@ -0,0 +1,271 @@
+import type { Meta } from '@storybook/react-vite';
+import { Folder, File, Globe, Pencil } from 'lucide-react';
+import TreeView, {
+ type TreeViewItem,
+ type TreeViewIconMap,
+ type TreeViewMenuItem,
+} from './tree-view';
+
+const meta: Meta = {
+ title: 'Components/Data Display/Tree View',
+ component: TreeView,
+ tags: ['autodocs'],
+ parameters: {
+ layout: 'centered',
+ docs: {
+ description: {
+ component: `
+A hierarchical tree component for displaying and selecting items like file explorers, navigation, or admin sidebars.
+
+## Features
+
+- **Search** – Filters by name and auto-expands matching branches
+- **Selection modes** – \`multiple\` (default), \`single\`, or \`none\`
+- **Selection checkboxes** – Shown only on leaf items (folders do not get checkboxes)
+- **Disabled items** – Set \`disabled: true\` on items to grey them out and prevent selection
+- **Badges & metadata** – Add \`badge\` and \`meta\` to items for status, version, or counts
+- **Item actions** – Add \`actions\` for Edit/Delete; rendered in a compact dropdown (single "more" icon) to prevent horizontal scroll
+- **Context menu** – Right-click with \`menuItems\` for custom actions
+- **Access checkboxes** – \`showCheckboxes\` for check/uncheck with \`onCheckChange\`
+
+## Layout
+
+The tree uses a fixed vertical layout: **Title** → **Search bar** (full width) → **Expand / Collapse / Selected count / Clear** → **Tree content**. Overflow is handled so the tree never scrolls horizontally.
+ `,
+ },
+ },
+ },
+ argTypes: {
+ selectionMode: {
+ control: 'select',
+ options: ['single', 'multiple', 'none'],
+ description: 'Controls selection behavior. "multiple" allows shift/ctrl-click and drag-select.',
+ },
+ showSelectionCheckboxes: {
+ description: 'Shows checkboxes on leaf items only. Folders do not get checkboxes.',
+ },
+ showCheckboxes: {
+ description: 'Shows access-rights checkboxes with Check/Uncheck actions in the toolbar.',
+ },
+ },
+};
+
+export default meta;
+
+const sampleData: TreeViewItem[] = [
+ {
+ id: '1',
+ name: 'Root',
+ type: 'region',
+ children: [
+ {
+ id: '1.1',
+ name: 'Folder 1',
+ type: 'store',
+ children: [
+ {
+ id: '1.1.1',
+ name: 'Subfolder',
+ type: 'department',
+ children: [
+ { id: '1.1.1.1', name: 'File 1', type: 'item' },
+ { id: '1.1.1.2', name: 'File 2', type: 'item' },
+ ],
+ },
+ ],
+ },
+ {
+ id: '1.2',
+ name: 'Folder 2',
+ type: 'store',
+ children: [
+ { id: '1.2.1', name: 'Document.pdf', type: 'item' },
+ { id: '1.2.2', name: 'Spreadsheet.xlsx', type: 'item' },
+ ],
+ },
+ ],
+ },
+];
+
+const iconMap: TreeViewIconMap = {
+ region: ,
+ store: ,
+ department: ,
+ item: ,
+};
+
+// ============================================================================
+// Basic
+// ============================================================================
+
+export const Basic = {
+ name: 'Basic',
+ parameters: {
+ docs: { description: { story: 'Default tree with expand/collapse and search.' } },
+ },
+ render: () => (
+
+ ),
+};
+
+// ============================================================================
+// With Checkboxes
+// ============================================================================
+
+export const WithCheckboxes = {
+ name: 'With Checkboxes',
+ parameters: {
+ docs: { description: { story: 'Access-rights checkboxes with Check/Uncheck toolbar actions.' } },
+ },
+ render: () => (
+ {
+ console.log(`${item.name} ${checked ? 'checked' : 'unchecked'}`);
+ }}
+ />
+ ),
+};
+
+// ============================================================================
+// With Disabled Items, Badges, Meta, and Actions
+// ============================================================================
+
+const sampleDataWithFeatures: TreeViewItem[] = [
+ {
+ id: '1',
+ name: 'Root',
+ type: 'region',
+ children: [
+ {
+ id: '1.1',
+ name: 'Folder 1',
+ type: 'store',
+ children: [
+ {
+ id: '1.1.1',
+ name: 'Active Item',
+ type: 'item',
+ badge: Live,
+ meta: '2.4.1',
+ actions: [
+ {
+ id: 'edit',
+ icon: ,
+ label: 'Edit',
+ onClick: (item) => console.log('Edit', item.name),
+ },
+ ],
+ },
+ {
+ id: '1.1.2',
+ name: 'Disabled Item',
+ type: 'item',
+ disabled: true,
+ meta: 'Unavailable',
+ },
+ ],
+ },
+ ],
+ },
+];
+
+export const WithDisabledBadgesMetaAndActions = {
+ name: 'With Disabled, Badges, Meta & Actions',
+ parameters: {
+ docs: {
+ description: {
+ story:
+ 'Demonstrates disabled items (greyed out), badges, meta text, and item actions in a dropdown. Actions use a single "more" icon to prevent horizontal scroll.',
+ },
+ },
+ },
+ render: () => (
+
+ ),
+};
+
+// ============================================================================
+// Single Selection Mode
+// ============================================================================
+
+export const SingleSelectionMode = {
+ name: 'Single Selection',
+ parameters: {
+ docs: { description: { story: 'Only one item can be selected at a time.' } },
+ },
+ render: () => (
+
+ ),
+};
+
+// ============================================================================
+// No Selection (Expand only)
+// ============================================================================
+
+export const NoSelectionMode = {
+ name: 'No Selection',
+ parameters: {
+ docs: { description: { story: 'Expand/collapse only; no selection or selection UI.' } },
+ },
+ render: () => (
+
+ ),
+};
+
+// ============================================================================
+// With Context Menu
+// ============================================================================
+
+export const WithContextMenu = {
+ name: 'With Context Menu',
+ parameters: {
+ docs: { description: { story: 'Right-click for custom context menu actions.' } },
+ },
+ render: () => {
+ const menuItems: TreeViewMenuItem[] = [
+ {
+ id: 'open',
+ label: 'Open',
+ action: (items) => console.log('Open:', items.map((i) => i.name)),
+ },
+ {
+ id: 'download',
+ label: 'Download',
+ action: (items) => console.log('Download:', items.map((i) => i.name)),
+ },
+ ];
+
+ return (
+
+ );
+ },
+};
diff --git a/packages/apollo-wind/src/components/ui/tree-view.tsx b/packages/apollo-wind/src/components/ui/tree-view.tsx
new file mode 100644
index 000000000..a537c551c
--- /dev/null
+++ b/packages/apollo-wind/src/components/ui/tree-view.tsx
@@ -0,0 +1,1267 @@
+"use client";
+
+import * as React from "react";
+import { useState, useRef, useEffect, useCallback, useMemo } from "react";
+import { Button } from "@/components/ui/button";
+import {
+ Collapsible,
+ CollapsibleTrigger,
+ CollapsibleContent,
+} from "@/components/ui/collapsible";
+import {
+ Folder,
+ ChevronRight,
+ Box,
+ Search,
+ MoreHorizontal,
+ Info,
+} from "lucide-react";
+import { motion, AnimatePresence } from "framer-motion";
+import {
+ ContextMenu,
+ ContextMenuContent,
+ ContextMenuItem,
+ ContextMenuTrigger,
+} from "@/components/ui/context-menu";
+import { X } from "lucide-react";
+import {
+ HoverCard,
+ HoverCardContent,
+ HoverCardTrigger,
+} from "@/components/ui/hover-card";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+} from "@/components/ui/dropdown-menu";
+import { Badge } from "@/components/ui/badge";
+import { cn } from "@/lib";
+import { Input } from "@/components/ui/input";
+import { Checkbox } from "@/components/ui/checkbox";
+
+/**
+ * Action shown in the item's dropdown menu (Edit, Delete, etc.).
+ * Actions appear as a single "more" icon that opens a dropdown to avoid horizontal overflow.
+ */
+export interface TreeViewItemAction {
+ /** Unique action identifier */
+ id: string;
+ /** Icon to display in the dropdown menu item */
+ icon: React.ReactNode;
+ /** Accessible label for the action */
+ label?: string;
+ /** Called when the action is clicked */
+ onClick: (item: TreeViewItem) => void;
+}
+
+/**
+ * A single item in the tree. Supports hierarchical structure, selection, and rich metadata.
+ */
+export interface TreeViewItem {
+ /** Unique identifier (must be unique across the entire tree) */
+ id: string;
+ /** Display name */
+ name: string;
+ /** Type used for icon lookup in iconMap */
+ type: string;
+ /** Child items for expandable folders */
+ children?: TreeViewItem[];
+ /** Checked state when using access-rights checkboxes (showCheckboxes) */
+ checked?: boolean;
+ /** When true, item is greyed out and cannot be selected */
+ disabled?: boolean;
+ /** Badge or tag shown after the name (e.g. status, version) */
+ badge?: React.ReactNode;
+ /** Secondary metadata shown after the badge (e.g. "Production", "2 workflows") */
+ meta?: string | React.ReactNode;
+ /** Actions shown in a dropdown menu (Edit, Delete, etc.). Uses a single "more" icon to prevent horizontal scroll. */
+ actions?: TreeViewItemAction[];
+}
+
+export interface TreeViewIconMap {
+ [key: string]: React.ReactNode | undefined;
+}
+
+export interface TreeViewMenuItem {
+ id: string;
+ label: string;
+ icon?: React.ReactNode;
+ action: (items: TreeViewItem[]) => void;
+}
+
+/**
+ * Controls how items can be selected.
+ * - `"multiple"`: Shift/ctrl-click for multi-select, drag to select range (default)
+ * - `"single"`: Only one item can be selected at a time
+ * - `"none"`: No selection; expand/collapse only
+ */
+export type TreeViewSelectionMode = "single" | "multiple" | "none";
+
+export interface TreeViewProps {
+ className?: string;
+ containerClassName?: string;
+ data: TreeViewItem[];
+ title?: string;
+ showExpandAll?: boolean;
+ showCheckboxes?: boolean;
+ /** Show selection checkboxes on leaf items only (folders do not get checkboxes) */
+ showSelectionCheckboxes?: boolean;
+ /** Selection behavior: "multiple" (default), "single", or "none" */
+ selectionMode?: TreeViewSelectionMode;
+ checkboxPosition?: "left" | "right";
+ searchPlaceholder?: string;
+ selectionText?: string;
+ checkboxLabels?: {
+ check: string;
+ uncheck: string;
+ };
+ getIcon?: (item: TreeViewItem, depth: number) => React.ReactNode;
+ onSelectionChange?: (selectedItems: TreeViewItem[]) => void;
+ onAction?: (action: string, items: TreeViewItem[]) => void;
+ onCheckChange?: (item: TreeViewItem, checked: boolean) => void;
+ iconMap?: TreeViewIconMap;
+ menuItems?: TreeViewMenuItem[];
+}
+
+interface TreeItemProps {
+ item: TreeViewItem;
+ depth?: number;
+ selectedIds: Set;
+ lastSelectedId: React.MutableRefObject;
+ onSelect: (ids: Set) => void;
+ expandedIds: Set;
+ onToggleExpand: (id: string, isOpen: boolean) => void;
+ selectionMode: TreeViewSelectionMode;
+ getIcon?: (item: TreeViewItem, depth: number) => React.ReactNode;
+ onAction?: (action: string, items: TreeViewItem[]) => void;
+ onAccessChange?: (item: TreeViewItem, hasAccess: boolean) => void;
+ allItems: TreeViewItem[];
+ showAccessRights?: boolean;
+ showSelectionCheckboxes?: boolean;
+ itemMap: Map;
+ iconMap?: TreeViewIconMap;
+ menuItems?: TreeViewMenuItem[];
+ getSelectedItems: () => TreeViewItem[];
+}
+
+// Helper function to build a map of all items by ID
+const buildItemMap = (items: TreeViewItem[]): Map => {
+ const map = new Map();
+ const processItem = (item: TreeViewItem) => {
+ map.set(item.id, item);
+ item.children?.forEach(processItem);
+ };
+ items.forEach(processItem);
+ return map;
+};
+
+// Update the getCheckState function to work bottom-up
+const getCheckState = (
+ item: TreeViewItem,
+ itemMap: Map
+): "checked" | "unchecked" | "indeterminate" => {
+ // Get the original item from the map
+ const originalItem = itemMap.get(item.id);
+ if (!originalItem) return "unchecked";
+
+ // If it's a leaf node (no children), return its check state
+ if (!originalItem.children || originalItem.children.length === 0) {
+ return originalItem.checked ? "checked" : "unchecked";
+ }
+
+ // Count the check states of immediate children
+ let checkedCount = 0;
+ let indeterminateCount = 0;
+
+ originalItem.children.forEach(child => {
+ const childState = getCheckState(child, itemMap);
+ if (childState === "checked") checkedCount++;
+ if (childState === "indeterminate") indeterminateCount++;
+ });
+
+ // Calculate parent state based on children states
+ const totalChildren = originalItem.children.length;
+
+ // If all children are checked
+ if (checkedCount === totalChildren) {
+ return "checked";
+ }
+ // If any child is checked or indeterminate
+ if (checkedCount > 0 || indeterminateCount > 0) {
+ return "indeterminate";
+ }
+ // If no children are checked or indeterminate
+ return "unchecked";
+};
+
+// Add this default icon map
+const defaultIconMap: TreeViewIconMap = {
+ file: ,
+ folder: ,
+};
+
+function TreeItem({
+ item,
+ depth = 0,
+ selectedIds,
+ lastSelectedId,
+ onSelect,
+ expandedIds,
+ onToggleExpand,
+ selectionMode,
+ getIcon,
+ onAction,
+ onAccessChange,
+ allItems,
+ showAccessRights,
+ showSelectionCheckboxes,
+ itemMap,
+ iconMap = defaultIconMap,
+ menuItems,
+ getSelectedItems,
+}: TreeItemProps): React.ReactElement {
+ const isOpen = expandedIds.has(item.id);
+ const isSelected = selectedIds.has(item.id);
+ const isDisabled = item.disabled === true;
+ const itemRef = useRef(null);
+ const [selectionStyle, setSelectionStyle] = useState("");
+
+ // Get all visible items in order
+ const getVisibleItems = useCallback(
+ (items: TreeViewItem[]): TreeViewItem[] => {
+ let visibleItems: TreeViewItem[] = [];
+
+ items.forEach((item) => {
+ visibleItems.push(item);
+ if (item.children && expandedIds.has(item.id)) {
+ visibleItems = [...visibleItems, ...getVisibleItems(item.children)];
+ }
+ });
+
+ return visibleItems;
+ },
+ [expandedIds]
+ );
+
+ useEffect(() => {
+ if (!isSelected) {
+ setSelectionStyle("");
+ return;
+ }
+
+ // Get all visible items from the entire tree
+ const visibleItems = getVisibleItems(allItems);
+ const currentIndex = visibleItems.findIndex((i) => i.id === item.id);
+
+ const prevItem = visibleItems[currentIndex - 1];
+ const nextItem = visibleItems[currentIndex + 1];
+
+ const isPrevSelected = prevItem && selectedIds.has(prevItem.id);
+ const isNextSelected = nextItem && selectedIds.has(nextItem.id);
+
+ const roundTop = !isPrevSelected;
+ const roundBottom = !isNextSelected;
+
+ setSelectionStyle(
+ `${roundTop ? "rounded-t-md" : ""} ${roundBottom ? "rounded-b-md" : ""}`
+ );
+ }, [
+ isSelected,
+ selectedIds,
+ expandedIds,
+ item.id,
+ getVisibleItems,
+ allItems,
+ ]);
+
+ const handleClick = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+
+ if (isDisabled || selectionMode === "none") return;
+
+ let newSelection = new Set(selectedIds);
+
+ if (!itemRef.current) return;
+
+ if (selectionMode === "single") {
+ newSelection = new Set([item.id]);
+ lastSelectedId.current = item.id;
+ onSelect(newSelection);
+ if (item.children && isSelected) {
+ onToggleExpand(item.id, !isOpen);
+ }
+ return;
+ }
+
+ if (e.shiftKey && lastSelectedId.current !== null) {
+ const items = Array.from(
+ document.querySelectorAll("[data-tree-item]")
+ ) as HTMLElement[];
+ const lastIndex = items.findIndex(
+ (el) => el.getAttribute("data-id") === lastSelectedId.current
+ );
+ const currentIndex = items.findIndex((el) => el === itemRef.current);
+ const [start, end] = [
+ Math.min(lastIndex, currentIndex),
+ Math.max(lastIndex, currentIndex),
+ ];
+
+ items.slice(start, end + 1).forEach((el) => {
+ const id = el.getAttribute("data-id");
+ const isDisabledEl = el.getAttribute("data-disabled") === "true";
+ const parentFolderClosed = el.closest('[data-folder-closed="true"]');
+ const isClosedFolder = el.getAttribute("data-folder-closed") === "true";
+
+ if (id && !isDisabledEl && (isClosedFolder || !parentFolderClosed)) {
+ newSelection.add(id);
+ }
+ });
+ } else if (e.ctrlKey || e.metaKey) {
+ if (newSelection.has(item.id)) {
+ newSelection.delete(item.id);
+ } else {
+ newSelection.add(item.id);
+ }
+ } else {
+ newSelection = new Set([item.id]);
+ // Open folder on single click if it's a folder
+ if (item.children && isSelected) {
+ onToggleExpand(item.id, !isOpen);
+ }
+ }
+
+ lastSelectedId.current = item.id;
+ onSelect(newSelection);
+ };
+
+ // Helper function to get all descendants of an item (including the item itself)
+ const getAllDescendants = (item: TreeViewItem): TreeViewItem[] => {
+ const descendants = [item];
+ if (item.children) {
+ item.children.forEach((child) => {
+ descendants.push(...getAllDescendants(child));
+ });
+ }
+ return descendants;
+ };
+
+ const handleAccessClick = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ if (onAccessChange) {
+ const currentState = getCheckState(item, itemMap);
+ // Toggle between checked and unchecked, treating indeterminate as unchecked
+ const newChecked = currentState === "checked" ? false : true;
+ onAccessChange(item, newChecked);
+ }
+ };
+
+ const renderIcon = () => {
+ if (getIcon) {
+ return getIcon(item, depth);
+ }
+
+ // Use the provided iconMap or fall back to default
+ return iconMap[item.type] || iconMap.folder || defaultIconMap.folder;
+ };
+
+ const getItemPath = (item: TreeViewItem, items: TreeViewItem[]): string => {
+ const path: string[] = [item.name];
+
+ const findParent = (
+ currentItem: TreeViewItem,
+ allItems: TreeViewItem[]
+ ) => {
+ for (const potentialParent of allItems) {
+ if (
+ potentialParent.children?.some((child) => child.id === currentItem.id)
+ ) {
+ path.unshift(potentialParent.name);
+ findParent(potentialParent, allItems);
+ break;
+ }
+ if (potentialParent.children) {
+ findParent(currentItem, potentialParent.children);
+ }
+ }
+ };
+
+ findParent(item, items);
+ return path.join(" → ");
+ };
+
+ // Add function to count selected items in a folder
+ const getSelectedChildrenCount = (item: TreeViewItem): number => {
+ let count = 0;
+
+ if (!item.children) return 0;
+
+ item.children.forEach((child) => {
+ if (selectedIds.has(child.id)) {
+ count++;
+ }
+ if (child.children) {
+ count += getSelectedChildrenCount(child);
+ }
+ });
+
+ return count;
+ };
+
+ // Get selected count only if item has children and is collapsed
+ const selectedCount =
+ (item.children && !isOpen && getSelectedChildrenCount(item)) || null;
+
+ return (
+
+
+
+
+
+ {item.children ? (
+
+
onToggleExpand(item.id, open)}
+ >
+ e.stopPropagation()}
+ >
+
+
+
+
+
+
+
+ {showAccessRights && (
+
+ {getCheckState(item, itemMap) === "checked" && (
+
+ )}
+ {getCheckState(item, itemMap) === "unchecked" && (
+
+ )}
+ {getCheckState(item, itemMap) === "indeterminate" && (
+
+ )}
+
+ )}
+ {renderIcon()}
+
{item.name}
+ {item.badge && (
+
{item.badge}
+ )}
+ {item.meta && (
+
+ {item.meta}
+
+ )}
+ {selectedCount !== null && selectedCount > 0 && (
+
+ {selectedCount} selected
+
+ )}
+ {(item.actions?.length ?? 0) > 0 ? (
+
+
+ e.stopPropagation()}
+ >
+
+
+
+
+ {item.actions?.map((action) => (
+ {
+ e.stopPropagation();
+ action.onClick(item);
+ }}
+ >
+ {action.icon}
+ {action.label ?? action.id}
+
+ ))}
+
+ View details
+
+
+
{item.name}
+
+
+ Type:{" "}
+ {item.type.charAt(0).toUpperCase() +
+ item.type.slice(1).replace("_", " ")}
+
+
+ ID: {item.id}
+
+
+ Location:{" "}
+ {getItemPath(item, allItems)}
+
+
+ Items:{" "}
+ {item.children?.length ?? 0} direct items
+
+
+
+
+
+
+
+ ) : (
+
+
+ e.stopPropagation()}
+ >
+
+
+
+
+
+
{item.name}
+
+
+ Type:{" "}
+ {item.type.charAt(0).toUpperCase() +
+ item.type.slice(1).replace("_", " ")}
+
+
+ ID: {item.id}
+
+
+ Location:{" "}
+ {getItemPath(item, allItems)}
+
+
+ Items:{" "}
+ {item.children?.length ?? 0} direct items
+
+
+
+
+
+ )}
+
+ ) : (
+
+ {showSelectionCheckboxes && !item.children && !isDisabled && (
+
{
+ if (selectionMode === "none") return;
+ const newSelection =
+ selectionMode === "single"
+ ? new Set(checked ? [item.id] : [])
+ : new Set(selectedIds);
+ if (selectionMode === "multiple") {
+ if (checked) newSelection.add(item.id);
+ else newSelection.delete(item.id);
+ }
+ lastSelectedId.current = item.id;
+ onSelect(newSelection);
+ }}
+ onClick={(e) => e.stopPropagation()}
+ aria-label={`Select ${item.name}`}
+ />
+ )}
+ {showAccessRights && (
+
+ {item.checked ? (
+
+ ) : (
+
+ )}
+
+ )}
+ {renderIcon()}
+ {item.name}
+ {item.badge && (
+ {item.badge}
+ )}
+ {item.meta && (
+
+ {item.meta}
+
+ )}
+ {(item.actions?.length ?? 0) > 0 ? (
+
+
+ e.stopPropagation()}
+ >
+
+
+
+
+ {item.actions?.map((action) => (
+ {
+ e.stopPropagation();
+ action.onClick(item);
+ }}
+ >
+ {action.icon}
+ {action.label ?? action.id}
+
+ ))}
+
+ View details
+
+
+
{item.name}
+
+
+ Type:{" "}
+ {item.type.charAt(0).toUpperCase() +
+ item.type.slice(1).replace("_", " ")}
+
+
+ ID: {item.id}
+
+
+ Location:{" "}
+ {getItemPath(item, allItems)}
+
+
+
+
+
+
+
+ ) : (
+
+
+ e.stopPropagation()}
+ >
+
+
+
+
+
+
{item.name}
+
+
+ Type:{" "}
+ {item.type.charAt(0).toUpperCase() +
+ item.type.slice(1).replace("_", " ")}
+
+
+ ID: {item.id}
+
+
+ Location:{" "}
+ {getItemPath(item, allItems)}
+
+
+
+
+
+ )}
+
+ )}
+
+
+
+ {item.children && (
+
onToggleExpand(item.id, open)}
+ >
+
+ {isOpen && (
+
+
+ {item.children?.map((child) => (
+
+ ))}
+
+
+ )}
+
+
+ )}
+
+
+
+ {menuItems?.map((menuItem) => (
+ {
+ const items = selectedIds.has(item.id)
+ ? getSelectedItems()
+ : [item];
+ menuItem.action(items);
+ }}
+ >
+ {menuItem.icon && (
+ {menuItem.icon}
+ )}
+ {menuItem.label}
+
+ ))}
+
+
+ );
+}
+
+/**
+ * TreeView displays hierarchical data with expand/collapse, search, selection, and optional actions.
+ *
+ * **Features:**
+ * - **Search**: Filters items by name and auto-expands matching branches
+ * - **Selection**: Single, multiple, or none via `selectionMode`
+ * - **Selection checkboxes**: Shown only on leaf items (not on folders) when `showSelectionCheckboxes` is true
+ * - **Disabled items**: Use `disabled: true` on items to grey them out and prevent selection
+ * - **Badges & metadata**: Add `badge` and `meta` to items for status, version, or counts
+ * - **Item actions**: Add `actions` for Edit/Delete; shown in a compact dropdown to prevent horizontal scroll
+ * - **Right-click context menu**: Use `menuItems` for custom context menu actions
+ * - **Access-rights checkboxes**: Use `showCheckboxes` for check/uncheck with `onCheckChange`
+ *
+ * **Layout**: Title → Search bar → Expand/Collapse + Selected count + Clear → Tree. No horizontal scroll.
+ */
+export default function TreeView({
+ className,
+ containerClassName,
+ checkboxLabels = {
+ check: "Check",
+ uncheck: "Uncheck",
+ },
+ data,
+ iconMap,
+ searchPlaceholder = "Search...",
+ selectionText = "selected",
+ showExpandAll = true,
+ showCheckboxes = false,
+ showSelectionCheckboxes = false,
+ selectionMode = "multiple",
+ title,
+ getIcon,
+ onSelectionChange,
+ onAction,
+ onCheckChange,
+ menuItems,
+}: TreeViewProps) {
+ const [currentMousePos, setCurrentMousePos] = useState(0);
+ const [dragStart, setDragStart] = useState(null);
+ const [dragStartPosition, setDragStartPosition] = useState<{
+ x: number;
+ y: number;
+ } | null>(null);
+ const [expandedIds, setExpandedIds] = useState>(new Set());
+ const [isDragging, setIsDragging] = useState(false);
+ const [selectedIds, setSelectedIds] = useState>(new Set());
+ const [searchQuery, setSearchQuery] = useState("");
+
+ const dragRef = useRef(null);
+ const lastSelectedId = useRef(null);
+ const treeRef = useRef(null);
+
+ const DRAG_THRESHOLD = 10; // pixels
+
+ // Create a map of all items by ID
+ const itemMap = useMemo(() => buildItemMap(data), [data]);
+
+ // Memoize the search results and expanded IDs
+ const { filteredData, searchExpandedIds } = useMemo(() => {
+ if (!searchQuery.trim()) {
+ return { filteredData: data, searchExpandedIds: new Set() };
+ }
+
+ const searchLower = searchQuery.toLowerCase();
+ const newExpandedIds = new Set();
+
+ // Helper function to check if an item or its descendants match the search
+ const itemMatches = (item: TreeViewItem): boolean => {
+ const nameMatches = item.name.toLowerCase().includes(searchLower);
+ if (nameMatches) return true;
+
+ if (item.children) {
+ return item.children.some((child) => itemMatches(child));
+ }
+
+ return false;
+ };
+
+ // Helper function to filter tree while keeping parent structure
+ const filterTree = (items: TreeViewItem[]): TreeViewItem[] => {
+ return items
+ .map((item) => {
+ if (!item.children) {
+ return itemMatches(item) ? item : null;
+ }
+
+ const filteredChildren = filterTree(item.children);
+ if (filteredChildren.length > 0 || itemMatches(item)) {
+ if (item.children) {
+ newExpandedIds.add(item.id);
+ }
+ return {
+ ...item,
+ children: filteredChildren,
+ };
+ }
+ return null;
+ })
+ .filter((item): item is TreeViewItem => item !== null);
+ };
+
+ return {
+ filteredData: filterTree(data),
+ searchExpandedIds: newExpandedIds,
+ };
+ }, [data, searchQuery]);
+
+ // Update expanded IDs when search changes
+ useEffect(() => {
+ if (searchQuery.trim()) {
+ setExpandedIds(prev => new Set([...prev, ...searchExpandedIds]));
+ }
+ }, [searchExpandedIds, searchQuery]);
+
+ useEffect(() => {
+ const handleClickAway = (e: MouseEvent) => {
+ const target = e.target as Element;
+
+ const clickedInside =
+ (treeRef.current && treeRef.current.contains(target)) ||
+ (dragRef.current && dragRef.current.contains(target)) ||
+ // Ignore clicks on context menus
+ target.closest('[role="menu"]') ||
+ target.closest("[data-radix-popper-content-wrapper]");
+
+ if (!clickedInside) {
+ setSelectedIds(new Set());
+ lastSelectedId.current = null;
+ }
+ };
+
+ document.addEventListener("mousedown", handleClickAway);
+ return () => document.removeEventListener("mousedown", handleClickAway);
+ }, []);
+
+ // Function to collect all folder IDs
+ const getAllFolderIds = (items: TreeViewItem[]): string[] => {
+ let ids: string[] = [];
+ items.forEach((item) => {
+ if (item.children) {
+ ids.push(item.id);
+ ids = [...ids, ...getAllFolderIds(item.children)];
+ }
+ });
+ return ids;
+ };
+
+ const handleExpandAll = () => {
+ setExpandedIds(new Set(getAllFolderIds(data)));
+ };
+
+ const handleCollapseAll = () => {
+ setExpandedIds(new Set());
+ };
+
+ const handleToggleExpand = (id: string, isOpen: boolean) => {
+ const newExpandedIds = new Set(expandedIds);
+ if (isOpen) {
+ newExpandedIds.add(id);
+ } else {
+ newExpandedIds.delete(id);
+ }
+ setExpandedIds(newExpandedIds);
+ };
+
+ // Get selected items
+ const getSelectedItems = useCallback((): TreeViewItem[] => {
+ const items: TreeViewItem[] = [];
+ const processItem = (item: TreeViewItem) => {
+ if (selectedIds.has(item.id)) {
+ items.push(item);
+ }
+ item.children?.forEach(processItem);
+ };
+ data.forEach(processItem);
+ return items;
+ }, [selectedIds, data]);
+
+ // Get selected items, filtering out parents if their children are selected
+ const getEffectiveSelectedItems = useCallback((): TreeViewItem[] => {
+ const selectedItems = getSelectedItems();
+
+ // Build a set of all selected IDs for quick lookup
+ const selectedIdsSet = new Set(selectedItems.map((item) => item.id));
+
+ // Filter out parents whose children are also selected
+ return selectedItems.filter((item) => {
+ // If this item has no children, always include it
+ if (!item.children) return true;
+
+ // Check if any children of this item are selected
+ const hasSelectedChildren = item.children.some((child) =>
+ selectedIdsSet.has(child.id)
+ );
+
+ // Only include this item if none of its children are selected
+ return !hasSelectedChildren;
+ });
+ }, [getSelectedItems]);
+
+ const handleMouseDown = useCallback((e: React.MouseEvent) => {
+ // Only track on left click and not on buttons
+ if (e.button !== 0 || (e.target as HTMLElement).closest("button")) return;
+
+ setDragStartPosition({ x: e.clientX, y: e.clientY });
+ }, []);
+
+ const handleMouseMove = useCallback(
+ (e: React.MouseEvent) => {
+ if (selectionMode === "none") return;
+
+ // Check if primary button is still held down
+ if (!(e.buttons & 1)) {
+ setIsDragging(false);
+ setDragStart(null);
+ setDragStartPosition(null);
+ return;
+ }
+
+ // If we haven't registered a potential drag start position, ignore
+ if (!dragStartPosition) return;
+
+ // Calculate distance moved
+ const deltaX = e.clientX - dragStartPosition.x;
+ const deltaY = e.clientY - dragStartPosition.y;
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+
+ // If we haven't started dragging yet, check if we should start
+ if (!isDragging) {
+ if (distance > DRAG_THRESHOLD) {
+ setIsDragging(true);
+ setDragStart(dragStartPosition.y);
+
+ // Clear selection if not holding shift/ctrl
+ if (!e.shiftKey && !e.ctrlKey) {
+ setSelectedIds(new Set());
+ lastSelectedId.current = null;
+ }
+ }
+ return;
+ }
+
+ // Rest of the existing drag logic
+ if (!dragRef.current) return;
+
+ const items = Array.from(
+ dragRef.current.querySelectorAll("[data-tree-item]")
+ ) as HTMLElement[];
+
+ const startY = dragStart;
+ const currentY = e.clientY;
+ const [selectionStart, selectionEnd] = [
+ Math.min(startY || 0, currentY),
+ Math.max(startY || 0, currentY),
+ ];
+
+ const newSelection = new Set(
+ e.shiftKey || e.ctrlKey ? Array.from(selectedIds) : []
+ );
+
+ items.forEach((item) => {
+ const rect = item.getBoundingClientRect();
+ const itemTop = rect.top;
+ const itemBottom = rect.top + rect.height;
+
+ if (itemBottom >= selectionStart && itemTop <= selectionEnd) {
+ const id = item.getAttribute("data-id");
+ const isDisabledEl = item.getAttribute("data-disabled") === "true";
+ const isClosedFolder =
+ item.getAttribute("data-folder-closed") === "true";
+ const parentFolderClosed = item.closest(
+ '[data-folder-closed="true"]'
+ );
+
+ if (id && !isDisabledEl && (isClosedFolder || !parentFolderClosed)) {
+ newSelection.add(id);
+ }
+ }
+ });
+
+ setSelectedIds(newSelection);
+ setCurrentMousePos(e.clientY);
+ },
+ [isDragging, dragStart, selectedIds, dragStartPosition, selectionMode]
+ );
+
+ const handleMouseUp = useCallback(() => {
+ setIsDragging(false);
+ setDragStart(null);
+ setDragStartPosition(null);
+ }, []);
+
+ // Add cleanup for mouse events
+ useEffect(() => {
+ if (isDragging) {
+ document.addEventListener("mouseup", handleMouseUp);
+ document.addEventListener("mouseleave", handleMouseUp);
+ }
+ return () => {
+ document.removeEventListener("mouseup", handleMouseUp);
+ document.removeEventListener("mouseleave", handleMouseUp);
+ };
+ }, [isDragging, handleMouseUp]);
+
+ // Call onSelectionChange when selection changes
+ useEffect(() => {
+ if (onSelectionChange) {
+ onSelectionChange(getSelectedItems());
+ }
+ }, [selectedIds, onSelectionChange, getSelectedItems]);
+
+ return (
+
+
+ {title && (
+
{title}
+ )}
+
+
+ setSearchQuery(e.target.value)}
+ className="h-10 w-full pl-9"
+ />
+
+
+
+ {showExpandAll && (
+ <>
+
+ Expand
+
+
+ Collapse
+
+ >
+ )}
+
+ {selectionMode !== "none" && selectedIds.size > 0 ? (
+
+
+ {selectedIds.size} {selectionText}
+
+
{
+ setSelectedIds(new Set());
+ lastSelectedId.current = null;
+ }}
+ >
+
+ Clear
+
+ {showCheckboxes && (
+
+ {
+ const effectiveItems = getEffectiveSelectedItems();
+ const processItem = (item: TreeViewItem) => {
+ onCheckChange?.(item, true);
+ item.children?.forEach(processItem);
+ };
+ effectiveItems.forEach(processItem);
+ }}
+ >
+ {checkboxLabels.check}
+
+ {
+ const effectiveItems = getEffectiveSelectedItems();
+ const processItem = (item: TreeViewItem) => {
+ onCheckChange?.(item, false);
+ item.children?.forEach(processItem);
+ };
+ effectiveItems.forEach(processItem);
+ }}
+ >
+ {checkboxLabels.uncheck}
+
+
+ )}
+
+ ) : null}
+
+
+ {isDragging && (
+
+ )}
+ {filteredData.map((item) => (
+
+ ))}
+
+
+
+ );
+}
diff --git a/packages/apollo-wind/src/examples/admin-layout-example.stories.tsx b/packages/apollo-wind/src/examples/admin-layout-example.stories.tsx
deleted file mode 100644
index 038ce4057..000000000
--- a/packages/apollo-wind/src/examples/admin-layout-example.stories.tsx
+++ /dev/null
@@ -1,305 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react-vite';
-import * as React from 'react';
-import { Globe, Code, Users, RefreshCw, HelpCircle, MoreHorizontal } from 'lucide-react';
-import { ColumnDef } from '@tanstack/react-table';
-import { Button } from '@/components/ui/button';
-import { Badge } from '@/components/ui/badge';
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from '@/components/ui/dropdown-menu';
-import {
- AdminLayout,
- AdminHeader,
- AdminHeaderActions,
- AdminContent,
- AdminSidebar,
- AdminSidebarHeader,
- AdminSidebarNav,
- AdminMain,
- AdminPageHeader,
- AdminToolbar,
- AdminFilter,
- AdminTable,
- AdminPagination,
- DataTableColumnHeader,
- DataTableSelectColumn,
-} from './admin-layout-example';
-import { Row, Column } from '@/components/ui/layout';
-
-const meta = {
- title: 'Examples/Admin Layout',
- component: AdminLayout,
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-// Sample data
-const tenants = [
- { id: '1', name: 'Maestro', type: 'tenant' },
- { id: '2', name: 'Staging', type: 'tenant' },
- { id: '3', name: 'ao', type: 'tenant' },
- {
- id: '4',
- name: 'Development',
- type: 'service',
- badge: 'Canary Environment',
- },
- { id: '5', name: 'DefaultTenant', type: 'tenant' },
- { id: '6', name: 'optimize', type: 'service' },
-];
-
-type User = {
- id: string;
- name: string;
- email: string;
- role: string;
- type: 'user' | 'group';
-};
-
-const users: User[] = [
- {
- id: '1',
- name: 'Finance-test',
- email: '',
- role: 'Test-role-viewer',
- type: 'group',
- },
- {
- id: '2',
- name: 'John Doe',
- email: 'john.doe@example.com',
- role: 'Tenant Administrator',
- type: 'user',
- },
-];
-
-function AdminPageDemo() {
- const [selectedTenant, setSelectedTenant] = React.useState('1');
- const [activeTab, setActiveTab] = React.useState('assignments');
- const [nameFilter, setNameFilter] = React.useState('all');
- const [roleFilter, setRoleFilter] = React.useState('all');
- const [page, setPage] = React.useState(1);
- const [pageSize, setPageSize] = React.useState(10);
-
- const navItems = tenants.map((t) => ({
- id: t.id,
- label: t.name,
- icon: t.type === 'tenant' ? : ,
- badge: t.badge ? (
-
- {t.badge}
-
- ) : undefined,
- }));
-
- const columns: ColumnDef[] = [
- DataTableSelectColumn(),
- {
- accessorKey: 'name',
- header: ({ column }) => ,
- cell: ({ row }) => (
-
-
- {row.original.name}
-
- ),
- },
- {
- accessorKey: 'email',
- header: ({ column }) => ,
- cell: ({ row }) => row.original.email || '—',
- },
- {
- accessorKey: 'role',
- header: ({ column }) => (
-
-
-
- ),
- cell: ({ row }) => row.original.role,
- },
- {
- id: 'actions',
- cell: ({ row }) => (
-
-
-
-
-
-
-
- console.log('edit', row.original)}>
- Edit
-
- console.log('remove', row.original)}>
- Remove
-
-
-
- ),
- },
- ];
-
- return (
-
-
-
-
-
-
- }
- title="Administration"
- >
-
-
-
-
-
-
- undefined} onAdd={() => undefined} />
-
-
-
-
-
- Check access
- Assign role
- >
- }
- />
-
-
-
-
- }
- >
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-export const Default = {
- // @ts-expect-error - args not needed when using render, but Storybook requires it
- args: {},
- render: () => ,
-} satisfies Story;
-
-// Simple settings page
-function SettingsPageDemo() {
- const [selectedSection, setSelectedSection] = React.useState('general');
-
- const sections = [
- { id: 'general', label: 'General', icon: },
- { id: 'users', label: 'Users', icon: },
- { id: 'security', label: 'Security', icon: },
- ];
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
Settings content goes here.
-
-
-
-
- );
-}
-
-export const SettingsPage = {
- // @ts-expect-error - args not needed when using render, but Storybook requires it
- args: {},
- render: () => ,
-} satisfies Story;
diff --git a/packages/apollo-wind/src/examples/admin-layout-example.tsx b/packages/apollo-wind/src/examples/admin-layout-example.tsx
deleted file mode 100644
index a1570b9f4..000000000
--- a/packages/apollo-wind/src/examples/admin-layout-example.tsx
+++ /dev/null
@@ -1,483 +0,0 @@
-import * as React from 'react';
-import { cn } from '@/lib';
-import { Search, Plus, ChevronsLeft, ChevronsRight, HelpCircle, Bell } from 'lucide-react';
-import { Button } from '@/components/ui/button';
-import { ScrollArea } from '@/components/ui/scroll-area';
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from '@/components/ui/select';
-import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
-import {
- Breadcrumb,
- BreadcrumbList,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbPage,
- BreadcrumbSeparator,
-} from '@/components/ui/breadcrumb';
-import {
- DataTable,
- DataTableColumnHeader,
- DataTableSelectColumn,
-} from '@/components/ui/data-table';
-import {
- Pagination,
- PaginationContent,
- PaginationItem,
- PaginationLink,
- PaginationPrevious,
- PaginationNext,
- PaginationEllipsis,
-} from '@/components/ui/pagination';
-import { Row, Column } from '@/components/ui/layout';
-
-// Admin Layout Container
-interface AdminLayoutProps extends React.HTMLAttributes {
- children: React.ReactNode;
-}
-
-export function AdminLayout({ className, children, ...props }: AdminLayoutProps) {
- return (
-
- {children}
-
- );
-}
-
-// Top Header Bar
-interface AdminHeaderProps extends React.HTMLAttributes {
- logo?: React.ReactNode;
- title?: string;
- children?: React.ReactNode;
-}
-
-export function AdminHeader({ className, logo, title, children, ...props }: AdminHeaderProps) {
- return (
-
-
- {logo}
- {title && {title}}
-
-
- {children}
-
-
- );
-}
-
-// Header Actions (notifications, help, profile)
-interface AdminHeaderActionsProps {
- notifications?: number;
- avatar?: React.ReactNode;
-}
-
-export function AdminHeaderActions({ notifications, avatar }: AdminHeaderActionsProps) {
- return (
-
-
-
- {notifications && notifications > 0 && (
-
- {notifications}
-
- )}
-
-
-
-
- {avatar || (
-
- SN
-
- )}
-
- );
-}
-
-// Main Content Area with Sidebar
-interface AdminContentProps extends React.HTMLAttributes {
- children: React.ReactNode;
-}
-
-export function AdminContent({ className, children, ...props }: AdminContentProps) {
- return (
-
- {children}
-
- );
-}
-
-// Sidebar
-interface AdminSidebarProps extends React.HTMLAttributes {
- children: React.ReactNode;
- width?: number;
-}
-
-export function AdminSidebar({ className, children, width = 280, ...props }: AdminSidebarProps) {
- return (
-
- {children}
-
- );
-}
-
-// Sidebar Header with title and actions
-interface AdminSidebarHeaderProps {
- title: string;
- onSearch?: () => void;
- onAdd?: () => void;
-}
-
-export function AdminSidebarHeader({ title, onSearch, onAdd }: AdminSidebarHeaderProps) {
- return (
-
- {title}
-
- {onSearch && (
-
-
-
- )}
- {onAdd && (
-
-
-
- )}
-
-
- );
-}
-
-// Sidebar Navigation Item
-interface AdminNavItem {
- id: string;
- label: string;
- icon?: React.ReactNode;
- badge?: React.ReactNode;
-}
-
-interface AdminSidebarNavProps {
- items: AdminNavItem[];
- selectedId?: string;
- onSelect?: (id: string) => void;
-}
-
-export function AdminSidebarNav({ items, selectedId, onSelect }: AdminSidebarNavProps) {
- return (
-
-
-
- );
-}
-
-// Main Panel
-interface AdminMainProps extends React.HTMLAttributes {
- children: React.ReactNode;
-}
-
-export function AdminMain({ className, children, ...props }: AdminMainProps) {
- return (
-
- {children}
-
- );
-}
-
-// Breadcrumb
-interface BreadcrumbItemType {
- label: string;
- href?: string;
-}
-
-interface AdminBreadcrumbProps {
- items: BreadcrumbItemType[];
-}
-
-export function AdminBreadcrumb({ items }: AdminBreadcrumbProps) {
- return (
-
-
- {items.map((item, index) => (
-
- {index > 0 && }
-
- {index === items.length - 1 ? (
- {item.label}
- ) : (
- {item.label}
- )}
-
-
- ))}
-
-
- );
-}
-
-// Page Header
-interface AdminPageHeaderProps {
- title: string;
- breadcrumb?: BreadcrumbItemType[];
- actions?: React.ReactNode;
- tabs?: { value: string; label: string }[];
- activeTab?: string;
- onTabChange?: (value: string) => void;
-}
-
-export function AdminPageHeader({
- title,
- breadcrumb,
- actions,
- tabs,
- activeTab,
- onTabChange,
-}: AdminPageHeaderProps) {
- return (
-
- {breadcrumb && (
-
- )}
-
- {title}
- {actions && (
-
- {actions}
-
- )}
-
- {tabs && (
-
-
- {tabs.map((tab) => (
-
- {tab.label}
-
- ))}
-
-
- )}
-
- );
-}
-
-// Toolbar with filters
-interface AdminToolbarProps {
- children?: React.ReactNode;
- actions?: React.ReactNode;
-}
-
-export function AdminToolbar({ children, actions }: AdminToolbarProps) {
- return (
-
-
- {children}
-
-
- {actions}
-
-
- );
-}
-
-// Filter Dropdown
-interface AdminFilterProps {
- label: string;
- value: string;
- options: { value: string; label: string }[];
- onValueChange?: (value: string) => void;
-}
-
-export function AdminFilter({ label, value, options, onValueChange }: AdminFilterProps) {
- return (
-
- {label}:
-
-
- );
-}
-
-// Data Table for Admin
-// Re-export DataTable components for use with AdminLayout
-export { DataTable as AdminTable, DataTableColumnHeader, DataTableSelectColumn };
-
-// Pagination
-interface AdminPaginationProps {
- total: number;
- page: number;
- pageSize: number;
- onPageChange?: (page: number) => void;
- onPageSizeChange?: (size: number) => void;
-}
-
-export function AdminPagination({
- total,
- page,
- pageSize,
- onPageChange,
- onPageSizeChange,
-}: AdminPaginationProps) {
- const totalPages = Math.ceil(total / pageSize);
- const start = (page - 1) * pageSize + 1;
- const end = Math.min(page * pageSize, total);
-
- // Generate page numbers to display
- const getPageNumbers = () => {
- const pages: number[] = [];
- const delta = 1; // Number of pages to show on each side of current page
-
- for (let i = 1; i <= totalPages; i++) {
- if (
- i === 1 || // First page
- i === totalPages || // Last page
- (i >= page - delta && i <= page + delta) // Pages around current
- ) {
- pages.push(i);
- }
- }
-
- return pages;
- };
-
- const pageNumbers = getPageNumbers();
-
- return (
-
-
- {start} - {end} / {total}
-
-
-
-
-
- onPageChange?.(1)}
- >
-
-
-
-
- page > 1 && onPageChange?.(page - 1)}
- className={cn(page === 1 && 'pointer-events-none opacity-50')}
- />
-
-
- {pageNumbers.map((pageNum, index) => {
- const prevPage = pageNumbers[index - 1];
- const showEllipsis = prevPage && pageNum - prevPage > 1;
-
- return (
-
- {showEllipsis && (
-
-
-
- )}
-
- onPageChange?.(pageNum)}
- isActive={page === pageNum}
- >
- {pageNum}
-
-
-
- );
- })}
-
-
- page < totalPages && onPageChange?.(page + 1)}
- className={cn(page === totalPages && 'pointer-events-none opacity-50')}
- />
-
-
- onPageChange?.(totalPages)}
- >
-
-
-
-
-
-
- {onPageSizeChange && (
-
- Items
-
-
- )}
-
-
- );
-}
diff --git a/packages/apollo-wind/src/examples/app-shell-example.stories.tsx b/packages/apollo-wind/src/examples/app-shell-example.stories.tsx
deleted file mode 100644
index cb4bd5764..000000000
--- a/packages/apollo-wind/src/examples/app-shell-example.stories.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react-vite';
-import { AppShellExample } from './app-shell-example';
-
-const meta = {
- title: 'Examples/App Shell',
- component: AppShellExample,
- parameters: {
- layout: 'fullscreen',
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {};
diff --git a/packages/apollo-wind/src/examples/app-shell-example.tsx b/packages/apollo-wind/src/examples/app-shell-example.tsx
deleted file mode 100644
index da934d310..000000000
--- a/packages/apollo-wind/src/examples/app-shell-example.tsx
+++ /dev/null
@@ -1,362 +0,0 @@
-import { Bell, ChevronLeft, ChevronRight, HelpCircle, Menu } from 'lucide-react';
-import * as React from 'react';
-import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
-import { Badge } from '@/components/ui/badge';
-import { Button } from '@/components/ui/button';
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from '@/components/ui/card';
-import { Separator } from '@/components/ui/separator';
-import { cn } from '@/lib';
-import { Row, Column, Grid } from '@/components/ui/layout';
-
-export interface AppShellExampleProps {
- className?: string;
- logo?: React.ReactNode;
- tenant?: string;
- user?: {
- name: string;
- email: string;
- avatar?: string;
- };
- navigation?: Array<{
- label: string;
- href?: string;
- active?: boolean;
- badge?: string;
- }>;
- heroSection?: {
- title: React.ReactNode;
- description?: string;
- cta?: {
- label: string;
- onClick?: () => void;
- };
- illustration?: React.ReactNode;
- };
- sections?: Array<{
- title: string;
- viewAllLink?: {
- label: string;
- onClick?: () => void;
- };
- items: Array<{
- id: string;
- title: string;
- description?: string;
- badge?: {
- label: string;
- variant?: 'default' | 'secondary' | 'destructive' | 'outline';
- };
- footer?: string;
- action?: {
- label: string;
- onClick?: () => void;
- };
- }>;
- }>;
- sidebar?: {
- title: string;
- content: React.ReactNode;
- };
-}
-
-export function AppShellExample({
- className,
- logo = Logo
,
- tenant = 'DefaultTenant',
- user = {
- name: 'John Doe',
- email: 'john.doe@example.com',
- },
- navigation = [
- { label: 'Home', active: true },
- { label: 'Process instances' },
- { label: 'Process incidents' },
- { label: 'Case app', badge: 'Preview' },
- { label: 'Case instances', badge: 'Preview' },
- ],
- heroSection = {
- title: (
- <>
- Orchestrate AI agents, robots, and people with{' '}
- Platform to exceed business outcomes
- >
- ),
- cta: {
- label: 'Get Started',
- },
- },
- sections = [
- {
- title: 'Recent projects',
- viewAllLink: { label: 'View all projects' },
- items: [
- {
- id: '1',
- title: 'Solution 161',
- badge: { label: 'DRAFT', variant: 'outline' },
- footer: 'Last modified 13 minutes ago',
- },
- {
- id: '2',
- title: 'Property insurance claims',
- badge: { label: 'DRAFT', variant: 'outline' },
- footer: 'Last modified 7 days ago',
- },
- {
- id: '3',
- title: 'Property insurance claims',
- badge: { label: 'DEPLOYED', variant: 'default' },
- footer: 'Last modified 7 hours ago',
- },
- ],
- },
- {
- title: 'Examples',
- items: [
- {
- id: '1',
- title: 'Supplier Onboarding',
- description:
- 'An agentic process built with Platform (Agentic Orchestration), modeled using...',
- action: { label: 'View' },
- },
- {
- id: '2',
- title: 'Invoice Processing',
- description:
- 'An agentic process built with Platform (Agentic Orchestration), modeled using...',
- action: { label: 'View' },
- },
- {
- id: '3',
- title: 'MyPoNewTeMplate',
- description: 'aav',
- action: { label: 'View' },
- },
- ],
- },
- ],
- sidebar = {
- title: 'How it works',
- content: (
-
-
-
Learn more about how to use this platform effectively.
-
-
- ),
- },
-}: AppShellExampleProps) {
- const [leftSidebarCollapsed, setLeftSidebarCollapsed] = React.useState(false);
- const [rightSidebarCollapsed, setRightSidebarCollapsed] = React.useState(false);
-
- return (
-
- {/* Header */}
-
-
- setLeftSidebarCollapsed(!leftSidebarCollapsed)}
- >
-
-
- {logo}
-
-
-
-
-
-
-
-
-
-
-
- Tenant:
-
- {tenant}
-
-
-
-
-
-
- {user.name
- .split(' ')
- .map((n) => n[0])
- .join('')
- .toUpperCase()}
-
-
-
-
-
-
- {/* Left Sidebar */}
-
-
- {/* Main Content */}
-
-
- {/* Hero Section */}
- {heroSection && (
-
-
-
-
-
- {heroSection.title}
-
- {heroSection.description && (
- {heroSection.description}
- )}
- {heroSection.cta && (
-
- {heroSection.cta.label}
-
- )}
-
- {heroSection.illustration && (
- {heroSection.illustration}
- )}
-
-
-
- )}
-
- {/* Content Sections */}
- {sections.map((section, sectionIndex) => (
-
-
- {section.title}
- {section.viewAllLink && (
-
- {section.viewAllLink.label}
-
- )}
-
-
- {section.items.map((item) => (
-
-
- {item.badge && (
-
- {item.badge.label}
-
- )}
- {item.title}
- {item.description && {item.description}}
-
- {(item.footer || item.action) && (
-
-
- {item.footer && (
- {item.footer}
- )}
- {item.action && (
-
- {item.action.label}
-
- )}
-
-
- )}
-
- ))}
-
-
- ))}
-
-
-
- {/* Right Sidebar */}
- {sidebar && (
-
- )}
-
-
- );
-}
diff --git a/packages/apollo-wind/src/examples/dashboard-example.stories.tsx b/packages/apollo-wind/src/examples/dashboard-example.stories.tsx
deleted file mode 100644
index 9be0482fd..000000000
--- a/packages/apollo-wind/src/examples/dashboard-example.stories.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react-vite';
-import { DashboardExample } from './dashboard-example';
-
-const meta = {
- title: 'Examples/Dashboard',
- component: DashboardExample,
- parameters: {
- layout: 'fullscreen',
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {};
diff --git a/packages/apollo-wind/src/examples/dashboard-example.tsx b/packages/apollo-wind/src/examples/dashboard-example.tsx
deleted file mode 100644
index 31f3279f3..000000000
--- a/packages/apollo-wind/src/examples/dashboard-example.tsx
+++ /dev/null
@@ -1,388 +0,0 @@
-import * as React from 'react';
-import { ColumnDef } from '@tanstack/react-table';
-import { Activity, CreditCard, DollarSign, Users, MoreHorizontal } from 'lucide-react';
-import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
-import { Badge } from '@/components/ui/badge';
-import { Button } from '@/components/ui/button';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
-import {
- DataTable,
- DataTableColumnHeader,
- DataTableSelectColumn,
-} from '@/components/ui/data-table';
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from '@/components/ui/dropdown-menu';
-import { Row, Column, Grid } from '@/components/ui/layout';
-import { Progress } from '@/components/ui/progress';
-import { Separator } from '@/components/ui/separator';
-import { StatsCard } from '@/components/ui/stats-card';
-import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
-import { cn } from '@/lib';
-
-// Types
-interface Transaction {
- id: string;
- customer: string;
- email: string;
- amount: number;
- status: 'completed' | 'pending' | 'failed';
- date: string;
-}
-
-interface ActivityItem {
- id: string;
- user: string;
- action: string;
- time: string;
- avatar?: string;
-}
-
-export interface DashboardExampleProps {
- className?: string;
- title?: string;
- description?: string;
- user?: {
- name: string;
- email: string;
- avatar?: string;
- };
-}
-
-// Sample data
-const transactions: Transaction[] = [
- {
- id: '1',
- customer: 'Olivia Martin',
- email: 'olivia@example.com',
- amount: 1999.0,
- status: 'completed',
- date: '2024-01-15',
- },
- {
- id: '2',
- customer: 'Jackson Lee',
- email: 'jackson@example.com',
- amount: 39.0,
- status: 'completed',
- date: '2024-01-14',
- },
- {
- id: '3',
- customer: 'Isabella Nguyen',
- email: 'isabella@example.com',
- amount: 299.0,
- status: 'pending',
- date: '2024-01-14',
- },
- {
- id: '4',
- customer: 'William Kim',
- email: 'will@example.com',
- amount: 99.0,
- status: 'completed',
- date: '2024-01-13',
- },
- {
- id: '5',
- customer: 'Sofia Davis',
- email: 'sofia@example.com',
- amount: 450.0,
- status: 'failed',
- date: '2024-01-12',
- },
-];
-
-const recentActivity: ActivityItem[] = [
- {
- id: '1',
- user: 'Olivia Martin',
- action: 'Completed purchase of $1,999.00',
- time: '2 min ago',
- },
- {
- id: '2',
- user: 'Jackson Lee',
- action: 'Created new account',
- time: '1 hour ago',
- },
- {
- id: '3',
- user: 'Isabella Nguyen',
- action: 'Submitted support ticket #1234',
- time: '3 hours ago',
- },
- {
- id: '4',
- user: 'William Kim',
- action: 'Updated billing information',
- time: '5 hours ago',
- },
-];
-
-const columns: ColumnDef[] = [
- DataTableSelectColumn(),
- {
- accessorKey: 'customer',
- header: ({ column }) => ,
- cell: ({ row }) => (
-
-
-
- {row.original.customer
- .split(' ')
- .map((n) => n[0])
- .join('')}
-
-
-
- {row.original.customer}
- {row.original.email}
-
-
- ),
- },
- {
- accessorKey: 'amount',
- header: ({ column }) => ,
- cell: ({ row }) => {
- const amount = parseFloat(row.getValue('amount'));
- const formatted = new Intl.NumberFormat('en-US', {
- style: 'currency',
- currency: 'USD',
- }).format(amount);
- return {formatted}
;
- },
- },
- {
- accessorKey: 'status',
- header: ({ column }) => ,
- cell: ({ row }) => {
- const status = row.getValue('status') as string;
- return (
-
- {status}
-
- );
- },
- },
- {
- accessorKey: 'date',
- header: ({ column }) => ,
- },
- {
- id: 'actions',
- cell: () => (
-
-
-
-
-
-
-
- View details
- Download receipt
-
-
- ),
- },
-];
-
-export function DashboardExample({
- className,
- title = 'Dashboard',
- description = "Welcome back! Here's an overview of your business.",
- user = { name: 'John Doe', email: 'john@example.com' },
-}: DashboardExampleProps) {
- const [activeTab, setActiveTab] = React.useState('overview');
-
- return (
-
- {/* Header */}
-
-
-
-
{title}
-
{description}
-
-
-
- Download Report
-
-
-
-
-
-
- {user.name
- .split(' ')
- .map((n) => n[0])
- .join('')
- .toUpperCase()}
-
-
-
- {user.name}
- {user.email}
-
-
-
-
-
-
- {/* Main Content */}
-
-
-
- Overview
- Analytics
- Reports
-
-
-
- {/* Stats Cards */}
-
- }
- />
- }
- />
- }
- />
- }
- />
-
-
- {/* Content Grid */}
-
- {/* Transactions Table */}
-
-
- Recent Transactions
- Latest customer transactions
-
-
-
-
-
-
- {/* Recent Activity */}
-
-
- Recent Activity
- Latest updates from your team
-
-
- {recentActivity.map((activity) => (
-
-
-
-
- {activity.user
- .split(' ')
- .map((n) => n[0])
- .join('')}
-
-
-
-
- {activity.user}{' '}
- {activity.action}
-
- {activity.time}
-
-
- ))}
-
-
-
-
- {/* Goals Progress */}
-
-
- Monthly Goals
- Track your progress towards monthly targets
-
-
-
-
- Revenue Target
- $45,231 / $50,000
-
-
-
-
-
- New Customers
- 2,350 / 3,000
-
-
-
-
-
- Support Tickets Resolved
- 145 / 150
-
-
-
-
-
-
-
-
-
-
- Analytics
- Detailed analytics coming soon
-
-
- Analytics charts and graphs would go here
-
-
-
-
-
-
-
- Reports
- Generate and download reports
-
-
- Report generation interface would go here
-
-
-
-
-
-
- );
-}
diff --git a/packages/apollo-wind/src/examples/data-management-example.stories.tsx b/packages/apollo-wind/src/examples/data-management-example.stories.tsx
deleted file mode 100644
index 9b54ca913..000000000
--- a/packages/apollo-wind/src/examples/data-management-example.stories.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react-vite';
-import { DataManagementExample } from './data-management-example';
-
-const meta = {
- title: 'Examples/Data Management',
- component: DataManagementExample,
- parameters: {
- layout: 'fullscreen',
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {};
diff --git a/packages/apollo-wind/src/examples/data-management-example.tsx b/packages/apollo-wind/src/examples/data-management-example.tsx
deleted file mode 100644
index c27d7a6a9..000000000
--- a/packages/apollo-wind/src/examples/data-management-example.tsx
+++ /dev/null
@@ -1,399 +0,0 @@
-import * as React from 'react';
-import { ColumnDef } from '@tanstack/react-table';
-import { Download, Filter, MoreHorizontal, Plus, RefreshCw, Upload } from 'lucide-react';
-import { Badge } from '@/components/ui/badge';
-import { Button } from '@/components/ui/button';
-import {
- DataTable,
- DataTableColumnHeader,
- DataTableSelectColumn,
-} from '@/components/ui/data-table';
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from '@/components/ui/dialog';
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from '@/components/ui/dropdown-menu';
-import { Input } from '@/components/ui/input';
-import { Label } from '@/components/ui/label';
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from '@/components/ui/select';
-import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
-import { Row, Column, Grid } from '@/components/ui/layout';
-
-// Types
-interface Product {
- id: string;
- name: string;
- sku: string;
- category: string;
- price: number;
- stock: number;
- status: 'active' | 'draft' | 'archived';
- createdAt: string;
-}
-
-// Sample data
-const products: Product[] = [
- {
- id: '1',
- name: 'Wireless Headphones',
- sku: 'WH-001',
- category: 'Electronics',
- price: 99.99,
- stock: 150,
- status: 'active',
- createdAt: '2024-01-10',
- },
- {
- id: '2',
- name: 'USB-C Cable',
- sku: 'UC-002',
- category: 'Accessories',
- price: 12.99,
- stock: 500,
- status: 'active',
- createdAt: '2024-01-09',
- },
- {
- id: '3',
- name: 'Laptop Stand',
- sku: 'LS-003',
- category: 'Accessories',
- price: 49.99,
- stock: 75,
- status: 'active',
- createdAt: '2024-01-08',
- },
- {
- id: '4',
- name: 'Mechanical Keyboard',
- sku: 'MK-004',
- category: 'Electronics',
- price: 149.99,
- stock: 0,
- status: 'draft',
- createdAt: '2024-01-07',
- },
- {
- id: '5',
- name: 'Monitor Light Bar',
- sku: 'ML-005',
- category: 'Electronics',
- price: 59.99,
- stock: 200,
- status: 'active',
- createdAt: '2024-01-06',
- },
- {
- id: '6',
- name: 'Webcam HD',
- sku: 'WC-006',
- category: 'Electronics',
- price: 79.99,
- stock: 45,
- status: 'active',
- createdAt: '2024-01-05',
- },
- {
- id: '7',
- name: 'Mouse Pad XL',
- sku: 'MP-007',
- category: 'Accessories',
- price: 24.99,
- stock: 300,
- status: 'active',
- createdAt: '2024-01-04',
- },
- {
- id: '8',
- name: 'Phone Holder',
- sku: 'PH-008',
- category: 'Accessories',
- price: 19.99,
- stock: 0,
- status: 'archived',
- createdAt: '2024-01-03',
- },
- {
- id: '9',
- name: 'Bluetooth Speaker',
- sku: 'BS-009',
- category: 'Electronics',
- price: 39.99,
- stock: 120,
- status: 'active',
- createdAt: '2024-01-02',
- },
- {
- id: '10',
- name: 'Screen Protector',
- sku: 'SP-010',
- category: 'Accessories',
- price: 9.99,
- stock: 1000,
- status: 'active',
- createdAt: '2024-01-01',
- },
-];
-
-const columns: ColumnDef[] = [
- DataTableSelectColumn(),
- {
- accessorKey: 'name',
- header: ({ column }) => ,
- cell: ({ row }) => (
-
-
{row.original.name}
-
{row.original.sku}
-
- ),
- },
- {
- accessorKey: 'category',
- header: ({ column }) => ,
- },
- {
- accessorKey: 'price',
- header: ({ column }) => ,
- cell: ({ row }) => {
- const price = parseFloat(row.getValue('price'));
- return new Intl.NumberFormat('en-US', {
- style: 'currency',
- currency: 'USD',
- }).format(price);
- },
- },
- {
- accessorKey: 'stock',
- header: ({ column }) => ,
- cell: ({ row }) => {
- const stock = row.getValue('stock') as number;
- return (
-
- {stock === 0 ? 'Out of stock' : stock}
-
- );
- },
- },
- {
- accessorKey: 'status',
- header: ({ column }) => ,
- cell: ({ row }) => {
- const status = row.getValue('status') as string;
- return (
-
- {status}
-
- );
- },
- },
- {
- accessorKey: 'createdAt',
- header: ({ column }) => ,
- },
- {
- id: 'actions',
- cell: () => (
-
-
-
-
-
-
-
- Edit
- Duplicate
-
- Delete
-
-
- ),
- },
-];
-
-export function DataManagementExample() {
- const [activeTab, setActiveTab] = React.useState('all');
- const [categoryFilter, setCategoryFilter] = React.useState('all');
- const [isCreateDialogOpen, setIsCreateDialogOpen] = React.useState(false);
-
- const filteredProducts = React.useMemo(() => {
- let filtered = products;
-
- if (activeTab !== 'all') {
- filtered = filtered.filter((p) => p.status === activeTab);
- }
-
- if (categoryFilter !== 'all') {
- filtered = filtered.filter((p) => p.category === categoryFilter);
- }
-
- return filtered;
- }, [activeTab, categoryFilter]);
-
- const categories = [...new Set(products.map((p) => p.category))];
-
- return (
-
- {/* Header */}
-
-
-
- {/* Tabs and Filters */}
-
-
-
-
- All{' '}
-
- {products.length}
-
-
-
- Active{' '}
-
- {products.filter((p) => p.status === 'active').length}
-
-
-
- Draft{' '}
-
- {products.filter((p) => p.status === 'draft').length}
-
-
-
- Archived{' '}
-
- {products.filter((p) => p.status === 'archived').length}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* Data Table */}
-
-
-
- );
-}
diff --git a/packages/apollo-wind/src/examples/flow-editor-layout-example.stories.tsx b/packages/apollo-wind/src/examples/flow-editor-layout-example.stories.tsx
deleted file mode 100644
index 70e42eec0..000000000
--- a/packages/apollo-wind/src/examples/flow-editor-layout-example.stories.tsx
+++ /dev/null
@@ -1,255 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react-vite';
-import {
- FlowEditorLayout,
- SidebarPanelConfig,
- CanvasToolbar,
- PublishToolbar,
-} from './flow-editor-layout-example';
-import { Folders, Package, Sparkles } from 'lucide-react';
-import * as React from 'react';
-import { Column, Row } from '@/components/ui/layout';
-import { Button } from '@/components/ui/button';
-
-const defaultSidebarOptions: SidebarPanelConfig[] = [
- { id: 'folders', icon: , label: 'Folders' },
- {
- id: 'resources',
- icon: ,
- label: 'Resources',
- },
- {
- id: 'autopilot',
- icon: ,
- label: 'Autopilot',
- },
-];
-
-const meta = {
- title: 'Examples/Flow/Editor Layout',
- component: FlowEditorLayout,
- parameters: {
- layout: 'fullscreen',
- },
- argTypes: {
- activeSidebarId: {
- control: 'radio',
- options: ['folders', 'resources', 'autopilot'],
- },
- onSidebarChange: { action: 'sidebar changed' },
- onSidebarOpenChange: { action: 'sidebar open changed' },
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Default = {
- render: () => {
- const [activeSidebarId, setActiveSidebarId] = React.useState('autopilot');
- const [sidebarOpen, setSidebarOpen] = React.useState(true);
-
- return (
-
- {activeSidebarId} content
-
- }
- mainContent={
-
- Main content
-
- }
- />
- );
- },
-} satisfies Story;
-
-export const WithToolbars = {
- render: () => {
- const [activeSidebarId, setActiveSidebarId] = React.useState('autopilot');
- const [sidebarOpen, setSidebarOpen] = React.useState(true);
-
- return (
-
- {activeSidebarId}
-
- }
- mainContent={
-
-
- Build mode
- Drag and drop nodes to build your flow
-
-
-
-
-
-
- }
- />
- );
- },
-} satisfies Story;
-
-export const WithBottomPanel = {
- render: () => {
- const [activeSidebarId, setActiveSidebarId] = React.useState('folders');
- const [sidebarOpen, setSidebarOpen] = React.useState(true);
- return (
-
- {activeSidebarId}
-
- }
- mainContent={
-
-
- Flow Canvas
-
-
-
-
-
- }
- bottomOpen={true}
- bottomContent={
-
-
- [00:00:01]
- Starting execution...
-
-
- [00:00:02]
- Node 1 completed
-
-
- }
- />
- );
- },
-} satisfies Story;
-
-export const WithCustomSidebar = {
- render: () => {
- const [activeSidebarId, setActiveSidebarId] = React.useState('autopilot');
- const [sidebarOpen, setSidebarOpen] = React.useState(true);
-
- return (
-
- Sidebar
- Custom sidebar content
-
-
- Item 1
-
-
- Item 2
-
-
- Item 3
-
-
-
- }
- mainContent={
-
-
- Canvas
-
-
-
-
- }
- />
- );
- },
-} satisfies Story;
-
-const customSidebarOptions: SidebarPanelConfig[] = [
- { id: 'files', icon: , label: 'Files' },
- { id: 'packages', icon: , label: 'Packages' },
- { id: 'ai', icon: , label: 'AI Assistant' },
-];
-
-export const CustomPanels = {
- render: () => {
- const [activeSidebarId, setActiveSidebarId] = React.useState('ai');
- const [sidebarOpen, setSidebarOpen] = React.useState(true);
-
- return (
-
-
- {customSidebarOptions.find((p) => p.id === activeSidebarId)?.label}
-
- Panel content for {activeSidebarId}
-
- }
- mainContent={
-
- Main content area
-
- }
- />
- );
- },
-} satisfies Story;
-
-export const PanelClosed = {
- render: () => {
- const [activeSidebarId, setActiveSidebarId] = React.useState('autopilot');
- const [sidebarOpen, setSidebarOpen] = React.useState(false);
-
- return (
-
- {activeSidebarId}
-
- }
- mainContent={
-
- Main content
-
- }
- />
- );
- },
-} satisfies Story;
diff --git a/packages/apollo-wind/src/examples/flow-editor-layout-example.tsx b/packages/apollo-wind/src/examples/flow-editor-layout-example.tsx
deleted file mode 100644
index a9a0843c5..000000000
--- a/packages/apollo-wind/src/examples/flow-editor-layout-example.tsx
+++ /dev/null
@@ -1,285 +0,0 @@
-import { Copy, MoreVertical, Play, Plus } from 'lucide-react';
-import * as React from 'react';
-import { Button } from '@/components/ui/button';
-import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable';
-import { Separator } from '@/components/ui/separator';
-import { Switch } from '@/components/ui/switch';
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
-import { Row, Column } from '@/components/ui/layout';
-import { cn } from '@/lib';
-import { useCallback, useRef, useEffect } from 'react';
-import { ImperativePanelHandle } from 'react-resizable-panels';
-import { Label } from '@radix-ui/react-label';
-import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from '@/components/ui/dropdown-menu';
-
-interface LatchedButtonProps {
- isActive: boolean;
- icon: React.ReactNode;
- label: string;
- onClick: () => void;
-}
-
-function LatchedButton({ isActive, icon, label, onClick }: LatchedButtonProps) {
- return (
-
-
-
-
- {icon}
-
-
-
- {label}
-
- );
-}
-
-export interface SidebarPanelConfig {
- id: string;
- icon: React.ReactNode;
- label: string;
-}
-
-export interface FlowEditorLayoutProps {
- className?: string;
-
- // Sidebar
- sidebarOptions?: SidebarPanelConfig[];
- sidebarContent?: React.ReactNode;
- activeSidebarId?: string;
- sidebarOpen?: boolean;
- onSidebarChange?: (panelId: string) => void;
- onSidebarOpenChange?: (open: boolean) => void;
-
- // Main content
- mainContent?: React.ReactNode;
-
- // Bottom panel
- bottomContent?: React.ReactNode;
- bottomOpen?: boolean;
-}
-
-export function FlowEditorLayout({
- className,
- sidebarOptions,
- sidebarContent,
- activeSidebarId,
- sidebarOpen = false,
- onSidebarChange,
- onSidebarOpenChange,
- mainContent,
- bottomContent,
- bottomOpen = false,
-}: FlowEditorLayoutProps) {
- const containerRef = useRef(null);
- const bottomPanelRef = useRef(null);
- const switcherRef = useRef(null);
- const rafRef = useRef(0);
-
- const updateSwitcherPosition = useCallback(() => {
- if (!containerRef.current || !switcherRef.current) return;
-
- if (!bottomOpen) {
- switcherRef.current.style.bottom = '24px';
- return;
- }
-
- const size = bottomPanelRef.current?.getSize();
- if (size !== undefined) {
- const containerHeight = containerRef.current.offsetHeight;
- const bottomHeight = (size / 100) * containerHeight;
- switcherRef.current.style.bottom = `${bottomHeight + 24}px`;
- }
- }, [bottomOpen]);
-
- useEffect(() => {
- updateSwitcherPosition();
-
- const handleResize = () => {
- cancelAnimationFrame(rafRef.current);
- rafRef.current = requestAnimationFrame(updateSwitcherPosition);
- };
-
- window.addEventListener('resize', handleResize);
- return () => {
- window.removeEventListener('resize', handleResize);
- cancelAnimationFrame(rafRef.current);
- };
- }, [updateSwitcherPosition]);
-
- const handlePanelResize = useCallback(() => {
- cancelAnimationFrame(rafRef.current);
- rafRef.current = requestAnimationFrame(updateSwitcherPosition);
- }, [updateSwitcherPosition]);
-
- const handlePanelSelect = useCallback(
- (panelId: string) => {
- if (activeSidebarId === panelId) {
- onSidebarOpenChange?.(!sidebarOpen);
- } else {
- onSidebarChange?.(panelId);
- if (!sidebarOpen) {
- onSidebarOpenChange?.(true);
- }
- }
- },
- [activeSidebarId, sidebarOpen, onSidebarChange, onSidebarOpenChange]
- );
-
- return (
-
-
- {/* Left Sidebar */}
-
- {sidebarOpen && {sidebarContent}}
-
-
- {/* Main Content */}
-
- {/* Panel Switcher - latched to bottom left edge */}
- {sidebarOptions && sidebarOptions.length > 0 && (
-
- {sidebarOptions.map((panel) => (
- handlePanelSelect(panel.id)}
- />
- ))}
-
- )}
-
-
-
- {/* Center */}
-
- {mainContent}
-
-
- {/* Bottom Panel */}
- {bottomOpen && bottomContent && (
- <>
-
-
- {bottomContent}
-
- >
- )}
-
-
-
-
-
- );
-}
-
-// ============================================================================
-// Exports -- TEMPORARY
-// ============================================================================
-
-function CanvasToolbar() {
- return (
-
-
-
-
- Build
-
-
- Evaluate
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-function PublishToolbar() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Edit
- Duplicate
- Delete
-
-
-
-
- );
-}
-
-export { CanvasToolbar, PublishToolbar };
diff --git a/packages/apollo-wind/src/examples/flow-start-example.stories.tsx b/packages/apollo-wind/src/examples/flow-start-example.stories.tsx
deleted file mode 100644
index 91070574d..000000000
--- a/packages/apollo-wind/src/examples/flow-start-example.stories.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react-vite';
-import { Blocks, FolderKanban, GitBranch, Sparkles } from 'lucide-react';
-import type { ProcessOption, RecentProject } from './flow-start-example';
-import { FlowStartExample } from './flow-start-example';
-
-const meta = {
- title: 'Examples/Flow/Start',
- component: FlowStartExample,
- parameters: {
- layout: 'fullscreen',
- },
- argTypes: {
- onOptionSelect: { action: 'option selected' },
- onProjectSelect: { action: 'project selected' },
- defaultViewMode: {
- control: 'radio',
- options: ['cards', 'table'],
- },
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-const processOptions: ProcessOption[] = [
- {
- id: 'flow',
- title: 'Flow',
- description: 'Effortlessly build and deploy powerful automations—no code required',
- icon: ,
- badge: 'NEW',
- badgeVariant: 'default',
- },
- {
- id: 'bpmn',
- title: 'BPMN',
- description: 'Design and implement business processes with the structure of BPMN 2.0',
- icon: ,
- },
- {
- id: 'case',
- title: 'Case management',
- description: 'Creating and deploy end to end case management workflows',
- icon: ,
- },
- {
- id: 'autopilot',
- title: 'Generate with Autopilot',
- description: 'Describe what you need, and Autopilot will build it',
- icon: ,
- },
-];
-
-const recentProjects: RecentProject[] = [
- {
- id: '1',
- name: 'Invoice Processing Automation',
- type: 'flow',
- lastModified: '2 hours ago',
- status: 'published',
- },
- {
- id: '2',
- name: 'Customer Onboarding BPMN',
- type: 'bpmn',
- lastModified: 'Yesterday',
- status: 'draft',
- },
- {
- id: '3',
- name: 'Support Ticket Case Flow',
- type: 'case',
- lastModified: '3 days ago',
- status: 'published',
- },
- {
- id: '4',
- name: 'HR Request Handler',
- type: 'autopilot',
- lastModified: '1 week ago',
- status: 'archived',
- },
- {
- id: '5',
- name: 'Expense Report Approval',
- type: 'bpmn',
- lastModified: '2 weeks ago',
- status: 'published',
- },
- {
- id: '6',
- name: 'New Employee Onboarding',
- type: 'case',
- lastModified: '3 weeks ago',
- status: 'draft',
- },
- {
- id: '7',
- name: 'Contract Review Process',
- type: 'flow',
- lastModified: '1 month ago',
- status: 'published',
- },
- {
- id: '8',
- name: 'Customer Feedback Analysis',
- type: 'autopilot',
- lastModified: '1 month ago',
- status: 'archived',
- },
-];
-
-export const Default: Story = {
- args: {
- processOptions,
- recentProjects,
- showSkeleton: false,
- defaultViewMode: 'cards',
- },
-};
diff --git a/packages/apollo-wind/src/examples/flow-start-example.tsx b/packages/apollo-wind/src/examples/flow-start-example.tsx
deleted file mode 100644
index c4634d23d..000000000
--- a/packages/apollo-wind/src/examples/flow-start-example.tsx
+++ /dev/null
@@ -1,390 +0,0 @@
-import { ColumnDef } from '@tanstack/react-table';
-import {
- Blocks,
- Bot,
- FolderKanban,
- GitBranch,
- LayoutGrid,
- List,
- MoreHorizontal,
-} from 'lucide-react';
-import * as React from 'react';
-import { Badge } from '@/components/ui/badge';
-import { Button } from '@/components/ui/button';
-import { ButtonGroup } from '@/components/ui/button-group';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
-import { DataTable, DataTableColumnHeader } from '@/components/ui/data-table';
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from '@/components/ui/dropdown-menu';
-import { Skeleton } from '@/components/ui/skeleton';
-import { Row, Column, Grid } from '@/components/ui/layout';
-import { cn } from '@/lib';
-
-export interface ProcessOption {
- id: string;
- title: string;
- description: string;
- icon: React.ReactNode;
- badge?: string;
- badgeVariant?: 'default' | 'secondary' | 'destructive' | 'outline';
- onClick?: () => void;
-}
-
-export interface RecentProject {
- id: string;
- name: string;
- type: 'flow' | 'bpmn' | 'case' | 'autopilot';
- lastModified: string;
- status?: 'draft' | 'published' | 'archived';
-}
-
-export type ViewMode = 'cards' | 'table';
-
-export interface FlowStartExampleProps {
- className?: string;
- title?: string;
- subtitle?: string;
- processOptions?: ProcessOption[];
- recentProjects?: RecentProject[];
- showSkeleton?: boolean;
- defaultViewMode?: ViewMode;
- onOptionSelect?: (optionId: string) => void;
- onProjectSelect?: (projectId: string) => void;
-}
-
-function ProcessOptionCard({
- option,
- isSelected,
- onSelect,
-}: {
- option: ProcessOption;
- isSelected?: boolean;
- onSelect?: () => void;
-}) {
- return (
-
- {option.badge && (
-
- {option.badge}
-
- )}
-
-
-
- {option.icon}
-
-
-
-
- {option.title}
- {option.description}
-
-
- );
-}
-
-function getTypeIcon(type: RecentProject['type']) {
- switch (type) {
- case 'flow':
- return ;
- case 'bpmn':
- return ;
- case 'case':
- return ;
- case 'autopilot':
- return ;
- }
-}
-
-function getTypeLabel(type: RecentProject['type']) {
- switch (type) {
- case 'flow':
- return 'Flow';
- case 'bpmn':
- return 'BPMN';
- case 'case':
- return 'Case Management';
- case 'autopilot':
- return 'Autopilot';
- }
-}
-
-function getStatusVariant(status: RecentProject['status']) {
- switch (status) {
- case 'published':
- return 'default';
- case 'draft':
- return 'secondary';
- case 'archived':
- return 'outline';
- default:
- return 'secondary';
- }
-}
-
-function RecentProjectCard({
- project,
- onSelect,
-}: {
- project: RecentProject;
- onSelect?: () => void;
-}) {
- return (
-
-
-
-
- {getTypeIcon(project.type)}
-
-
-
- {project.name}
- {project.status && (
-
- {project.status}
-
- )}
-
- {project.lastModified}
-
-
-
-
- );
-}
-
-function SkeletonSection() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-function createProjectColumns(
- onProjectSelect?: (projectId: string) => void
-): ColumnDef[] {
- return [
- {
- accessorKey: 'name',
- header: ({ column }) => ,
- cell: ({ row }) => (
-
-
- {getTypeIcon(row.original.type)}
-
- {row.original.name}
-
- ),
- },
- {
- accessorKey: 'type',
- header: ({ column }) => ,
- cell: ({ row }) => (
- {getTypeLabel(row.original.type)}
- ),
- },
- {
- accessorKey: 'status',
- header: ({ column }) => ,
- cell: ({ row }) => {
- const status = row.original.status;
- return status ? (
-
- {status}
-
- ) : null;
- },
- },
- {
- accessorKey: 'lastModified',
- header: ({ column }) => ,
- cell: ({ row }) => {row.original.lastModified},
- },
- {
- id: 'actions',
- cell: ({ row }) => (
-
-
-
-
-
-
-
- onProjectSelect?.(row.original.id)}>
- Open
-
- Duplicate
- Archive
-
-
- ),
- },
- ];
-}
-
-export function FlowStartExample({
- className,
- title = 'BUILD YOUR FIRST AGENTIC PROCESS',
- subtitle = 'What would you like to start with?',
- processOptions = [],
- recentProjects = [],
- showSkeleton = false,
- defaultViewMode = 'cards',
- onOptionSelect,
- onProjectSelect,
-}: FlowStartExampleProps) {
- const [selectedOption, setSelectedOption] = React.useState(null);
- const [viewMode, setViewMode] = React.useState(defaultViewMode);
-
- const handleOptionSelect = (optionId: string) => {
- setSelectedOption(optionId);
- onOptionSelect?.(optionId);
- };
-
- const columns = React.useMemo(() => createProjectColumns(onProjectSelect), [onProjectSelect]);
-
- return (
-
- {/* Hero Section with background */}
-
- {/* Header */}
-
-
- {/* Hero Content */}
-
-
- {title}
- {subtitle}
-
-
- {/* Process Options Grid */}
-
- {processOptions.map((option) => (
- handleOptionSelect(option.id)}
- />
- ))}
-
-
- {/* Skeleton Loading Sections */}
- {showSkeleton && (
-
-
-
-
-
-
-
- )}
-
-
-
- {/* Recent Projects Section - White background */}
- {recentProjects.length > 0 && !showSkeleton && (
-
-
-
- Recent Projects
- {/* View Mode Toggle */}
-
- setViewMode('cards')}
- aria-label="Card view"
- >
-