diff --git a/docs/components/basic/button-group.mdx b/docs/components/basic/button-group.mdx new file mode 100644 index 000000000..04223d416 --- /dev/null +++ b/docs/components/basic/button-group.mdx @@ -0,0 +1,128 @@ +--- +title: "Button Group" +description: "Group multiple buttons together with shared styling" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Button Group component groups multiple buttons together with consistent styling. + +## Basic Usage + + + +## Variants + + + + + + +## Selection Mode + + + + + + +## Schema + +```typescript +interface ButtonGroupButton { + label?: string; + value: string; + icon?: string; + disabled?: boolean; +} + +interface ButtonGroupSchema { + type: 'button-group'; + buttons: ButtonGroupButton[]; // Button definitions + value?: string | string[]; // Selected value(s) + selectionMode?: 'single' | 'multiple' | 'none'; + variant?: 'default' | 'outline' | 'ghost'; + size?: 'sm' | 'default' | 'lg'; + + // Events + onValueChange?: string | ActionConfig; + + // States + disabled?: boolean; + + // Styling + className?: string; +} +``` + +## Examples + +### Toolbar Actions + + diff --git a/docs/components/basic/meta.json b/docs/components/basic/meta.json index 0a7b5fb78..a0a95cec5 100644 --- a/docs/components/basic/meta.json +++ b/docs/components/basic/meta.json @@ -5,6 +5,10 @@ "icon", "image", "separator", - "html" + "html", + "button-group", + "pagination", + "navigation-menu", + "sidebar" ] } diff --git a/docs/components/basic/navigation-menu.mdx b/docs/components/basic/navigation-menu.mdx new file mode 100644 index 000000000..c3fdb4e6d --- /dev/null +++ b/docs/components/basic/navigation-menu.mdx @@ -0,0 +1,88 @@ +--- +title: "Navigation Menu" +description: "Accessible navigation menu with dropdown support" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Navigation Menu component provides an accessible menu for site navigation. + +## Basic Usage + + + +## With Descriptions + + + +## Schema + +```typescript +interface NavigationMenuItem { + label: string; + href?: string; + description?: string; + icon?: string; + items?: NavigationMenuItem[]; // Submenu items +} + +interface NavigationMenuSchema { + type: 'navigation-menu'; + items: NavigationMenuItem[]; // Menu items + className?: string; +} +``` diff --git a/docs/components/basic/pagination.mdx b/docs/components/basic/pagination.mdx new file mode 100644 index 000000000..04a957e61 --- /dev/null +++ b/docs/components/basic/pagination.mdx @@ -0,0 +1,50 @@ +--- +title: "Pagination" +description: "Navigate through pages of content" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Pagination component allows users to navigate through pages of data. + +## Basic Usage + + + +## With Page Size + + + +## Schema + +```typescript +interface PaginationSchema { + type: 'pagination'; + currentPage: number; // Current page (1-based) + totalPages: number; // Total number of pages + pageSize?: number; // Items per page + totalItems?: number; // Total number of items + + // Events + onPageChange?: string | ActionConfig; + + // Styling + className?: string; +} +``` diff --git a/docs/components/basic/sidebar.mdx b/docs/components/basic/sidebar.mdx new file mode 100644 index 000000000..7f0ad83b0 --- /dev/null +++ b/docs/components/basic/sidebar.mdx @@ -0,0 +1,109 @@ +--- +title: "Sidebar" +description: "Collapsible navigation sidebar" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Sidebar component provides a collapsible navigation sidebar for applications. + +## Basic Usage + + + +## With Groups + + + +## Collapsible + + + +## Schema + +```typescript +interface SidebarItem { + label: string; + icon?: string; + href?: string; + active?: boolean; + badge?: string | number; +} + +interface SidebarGroup { + title?: string; + items: SidebarItem[]; +} + +interface SidebarSchema { + type: 'sidebar'; + items?: SidebarItem[]; // Flat list of items + groups?: SidebarGroup[]; // Grouped items + collapsible?: boolean; // Allow collapse + defaultCollapsed?: boolean; // Initial state + + // Styling + className?: string; +} +``` + +## Examples + +### With Badges + + diff --git a/docs/components/data-display/breadcrumb.mdx b/docs/components/data-display/breadcrumb.mdx new file mode 100644 index 000000000..0290dcbfb --- /dev/null +++ b/docs/components/data-display/breadcrumb.mdx @@ -0,0 +1,54 @@ +--- +title: "Breadcrumb" +description: "Display the current location within a navigational hierarchy" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Breadcrumb component shows the current page's location within the site hierarchy. + +## Basic Usage + + + +## With Icons + + + +## Schema + +```typescript +interface BreadcrumbItem { + label: string; + href?: string; + icon?: string; +} + +interface BreadcrumbSchema { + type: 'breadcrumb'; + items: BreadcrumbItem[]; // Breadcrumb items + separator?: string; // Custom separator + className?: string; +} +``` diff --git a/docs/components/data-display/calendar.mdx b/docs/components/data-display/calendar.mdx new file mode 100644 index 000000000..bd8963913 --- /dev/null +++ b/docs/components/data-display/calendar.mdx @@ -0,0 +1,70 @@ +--- +title: "Calendar" +description: "Display and select dates in a calendar view" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Calendar component displays a calendar for date selection. + +## Basic Usage + + + +## Multiple Selection + + + +## Date Range + + + +## Schema + +```typescript +interface CalendarSchema { + type: 'calendar'; + mode?: 'single' | 'multiple' | 'range'; + selected?: Date | Date[] | { from: Date; to: Date }; + + // Events + onSelect?: string | ActionConfig; + + // States + disabled?: boolean | Date[]; // Disabled dates + + // Styling + className?: string; +} +``` + +## Examples + +### With Disabled Dates + + diff --git a/docs/components/data-display/carousel.mdx b/docs/components/data-display/carousel.mdx new file mode 100644 index 000000000..8b347b6e7 --- /dev/null +++ b/docs/components/data-display/carousel.mdx @@ -0,0 +1,96 @@ +--- +title: "Carousel" +description: "Slideshow component for cycling through content" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Carousel component displays content in a slideshow format with navigation controls. + +## Basic Usage + + + +## Auto-play + + + +## Schema + +```typescript +interface CarouselSchema { + type: 'carousel'; + items: ComponentSchema[]; // Carousel slides + autoPlay?: boolean; // Auto-advance slides + interval?: number; // Auto-play interval (ms) + loop?: boolean; // Loop back to start + showControls?: boolean; // Show prev/next buttons + showIndicators?: boolean; // Show dot indicators + className?: string; +} +``` + +## Examples + +### Image Gallery + + ({ + type: 'aspect-ratio', + ratio: 16/9, + content: { + type: 'image', + src: `https://picsum.photos/800/450?random=${i}`, + alt: `Image ${i + 1}` + } + })) + }} + title="Image Gallery" +/> diff --git a/docs/components/data-display/kbd.mdx b/docs/components/data-display/kbd.mdx new file mode 100644 index 000000000..cec48bda9 --- /dev/null +++ b/docs/components/data-display/kbd.mdx @@ -0,0 +1,72 @@ +--- +title: "Kbd" +description: "Display keyboard shortcuts and hotkeys" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Kbd component displays keyboard shortcuts in a styled format. + +## Basic Usage + + + +## Multiple Key Combinations + + + + + + + +## Schema + +```typescript +interface KbdSchema { + type: 'kbd'; + keys: string[]; // Keyboard keys + className?: string; +} +``` + +## Examples + +### In Documentation + + diff --git a/docs/components/data-display/meta.json b/docs/components/data-display/meta.json index 84cd041cc..40459a02e 100644 --- a/docs/components/data-display/meta.json +++ b/docs/components/data-display/meta.json @@ -4,6 +4,11 @@ "badge", "avatar", "alert", - "list" + "list", + "breadcrumb", + "calendar", + "carousel", + "kbd", + "timeline" ] } diff --git a/docs/components/data-display/timeline.mdx b/docs/components/data-display/timeline.mdx new file mode 100644 index 000000000..166268a4f --- /dev/null +++ b/docs/components/data-display/timeline.mdx @@ -0,0 +1,80 @@ +--- +title: "Timeline" +description: "Display events in chronological order" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Timeline component displays events in chronological order with a vertical line. + +## Basic Usage + + + +## With Icons + + + +## Schema + +```typescript +interface TimelineItem { + title: string; + description?: string; + date?: string; + icon?: string; +} + +interface TimelineSchema { + type: 'timeline'; + items: TimelineItem[]; // Timeline events + className?: string; +} +``` diff --git a/docs/components/disclosure/meta.json b/docs/components/disclosure/meta.json index 019b84645..060414ee8 100644 --- a/docs/components/disclosure/meta.json +++ b/docs/components/disclosure/meta.json @@ -2,6 +2,8 @@ "title": "Disclosure", "pages": [ "accordion", - "collapsible" + "collapsible", + "toggle", + "toggle-group" ] } diff --git a/docs/components/disclosure/toggle-group.mdx b/docs/components/disclosure/toggle-group.mdx new file mode 100644 index 000000000..4425dcc01 --- /dev/null +++ b/docs/components/disclosure/toggle-group.mdx @@ -0,0 +1,81 @@ +--- +title: "Toggle Group" +description: "A group of toggle buttons where one or more can be selected" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Toggle Group component allows users to toggle between multiple options. + +## Basic Usage + + + +## Multiple Selection + + + +## With Labels + + + +## Schema + +```typescript +interface ToggleGroupItem { + value: string; + icon?: string; + label?: string; +} + +interface ToggleGroupSchema { + type: 'toggle-group'; + type: 'single' | 'multiple'; // Selection mode + items: ToggleGroupItem[]; // Toggle items + value?: string | string[]; // Selected value(s) + variant?: 'default' | 'outline'; + size?: 'sm' | 'default' | 'lg'; + + // Events + onValueChange?: string | ActionConfig; + + // States + disabled?: boolean; + + // Styling + className?: string; +} +``` diff --git a/docs/components/disclosure/toggle.mdx b/docs/components/disclosure/toggle.mdx new file mode 100644 index 000000000..3963b7f7a --- /dev/null +++ b/docs/components/disclosure/toggle.mdx @@ -0,0 +1,111 @@ +--- +title: "Toggle" +description: "A two-state button that can be on or off" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Toggle component is a two-state button that can be toggled on or off. + +## Basic Usage + + + +## Variants + + + + + + +## Sizes + + + + + + + +## Schema + +```typescript +interface ToggleSchema { + type: 'toggle'; + label?: string; // Toggle label + icon?: string; // Lucide icon name + variant?: 'default' | 'outline'; + size?: 'sm' | 'default' | 'lg'; + pressed?: boolean; // Toggle state + + // Events + onPressedChange?: string | ActionConfig; + + // States + disabled?: boolean; + + // Styling + className?: string; +} +``` + +## Examples + +### Text Formatting Toolbar + + diff --git a/docs/components/feedback/empty.mdx b/docs/components/feedback/empty.mdx new file mode 100644 index 000000000..2ea7e6b53 --- /dev/null +++ b/docs/components/feedback/empty.mdx @@ -0,0 +1,98 @@ +--- +title: "Empty" +description: "Empty state placeholder with optional action" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Empty component displays a placeholder when there's no content to show. + +## Basic Usage + + + +## With Icon + + + +## With Action + + + +## Schema + +```typescript +interface EmptySchema { + type: 'empty'; + icon?: string; // Lucide icon name + title?: string; // Main message + description?: string; // Supporting text + action?: ComponentSchema; // Call to action button + className?: string; +} +``` + +## Examples + +### Search Results + + + +### Empty List + + diff --git a/docs/components/feedback/meta.json b/docs/components/feedback/meta.json index 9353c60c4..c0f19aea7 100644 --- a/docs/components/feedback/meta.json +++ b/docs/components/feedback/meta.json @@ -3,6 +3,10 @@ "pages": [ "loading", "progress", - "skeleton" + "skeleton", + "spinner", + "empty", + "toast", + "sonner" ] } diff --git a/docs/components/feedback/sonner.mdx b/docs/components/feedback/sonner.mdx new file mode 100644 index 000000000..c800f2db8 --- /dev/null +++ b/docs/components/feedback/sonner.mdx @@ -0,0 +1,132 @@ +--- +title: "Sonner" +description: "Toast notifications using Sonner library" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Sonner component provides beautiful toast notifications with a rich API. + +## Basic Usage + + + +## Toast Types + + + + + + + + +## With Action + + + +## Schema + +```typescript +interface SonnerSchema { + action: 'sonner'; + message: string; // Toast message + description?: string; // Additional description + type?: 'default' | 'success' | 'error' | 'warning' | 'info'; + duration?: number; // Auto-close duration (ms) + + // Action + action?: { + label: string; + onClick: string | ActionConfig; + }; +} +``` + +## Examples + +### Promise Toast + + diff --git a/docs/components/feedback/spinner.mdx b/docs/components/feedback/spinner.mdx new file mode 100644 index 000000000..4a8446e90 --- /dev/null +++ b/docs/components/feedback/spinner.mdx @@ -0,0 +1,85 @@ +--- +title: "Spinner" +description: "Loading spinner indicator" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Spinner component displays a loading indicator. + +## Basic Usage + + + +## Sizes + + + + + + + +## Schema + +```typescript +interface SpinnerSchema { + type: 'spinner'; + size?: 'sm' | 'md' | 'lg'; + className?: string; +} +``` + +## Examples + +### In Button + + + +### Centered + + diff --git a/docs/components/feedback/toast.mdx b/docs/components/feedback/toast.mdx new file mode 100644 index 000000000..787ffff0c --- /dev/null +++ b/docs/components/feedback/toast.mdx @@ -0,0 +1,139 @@ +--- +title: "Toast" +description: "Brief notification messages that appear temporarily" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Toast component displays brief notifications to users that appear temporarily and then disappear. + +## Basic Usage + + + +## Variants + + + + + + +## With Action + + + +## Schema + +```typescript +interface ToastSchema { + type: 'toast'; + title?: string; // Toast title + description?: string; // Toast message + variant?: 'default' | 'destructive'; + + // Action + actionLabel?: string; // Action button label + onAction?: string | ActionConfig; + + // Timing + duration?: number; // Auto-dismiss time in ms +} +``` + +## Examples + +### Success Message + + + +### Error Message + + + +### With Undo Action + + diff --git a/docs/components/form/combobox.mdx b/docs/components/form/combobox.mdx new file mode 100644 index 000000000..65ac946c3 --- /dev/null +++ b/docs/components/form/combobox.mdx @@ -0,0 +1,123 @@ +--- +title: "Combobox" +description: "Searchable dropdown for selecting from a list of options" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Combobox component combines a text input with a dropdown list, allowing users to search and select from options. + +## Basic Usage + + + +## With Search + + + +## States + + + + + + +## Schema + +```typescript +interface ComboboxOption { + value: string; + label: string; +} + +interface ComboboxSchema { + type: 'combobox'; + options: ComboboxOption[]; // Available options + value?: string; // Selected value + + // Text + placeholder?: string; // Button placeholder + searchPlaceholder?: string; // Search input placeholder + emptyText?: string; // Text when no results + + // Events + onValueChange?: string | ActionConfig; + + // States + disabled?: boolean; + + // Styling + className?: string; +} +``` + +## Examples + +### Dynamic Options + + diff --git a/docs/components/form/command.mdx b/docs/components/form/command.mdx new file mode 100644 index 000000000..11b996ed5 --- /dev/null +++ b/docs/components/form/command.mdx @@ -0,0 +1,87 @@ +--- +title: "Command" +description: "Fast command menu with search and keyboard navigation" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Command component provides a fast, searchable command menu with keyboard navigation. + +## Basic Usage + + + +## Schema + +```typescript +interface CommandItem { + value: string; + label: string; + icon?: string; + shortcut?: string[]; +} + +interface CommandGroup { + heading?: string; + items: CommandItem[]; +} + +interface CommandSchema { + type: 'command'; + placeholder?: string; // Search placeholder + groups: CommandGroup[]; // Command groups + emptyText?: string; // Text when no results + + // Events + onSelect?: string | ActionConfig; + + // Styling + className?: string; +} +``` + +## Examples + +### With Shortcuts + + diff --git a/docs/components/form/date-picker.mdx b/docs/components/form/date-picker.mdx new file mode 100644 index 000000000..bcc5f745c --- /dev/null +++ b/docs/components/form/date-picker.mdx @@ -0,0 +1,119 @@ +--- +title: "Date Picker" +description: "Input for selecting dates with a calendar popup" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Date Picker component allows users to select a date from a calendar interface. + +## Basic Usage + + + +## With Default Date + + + +## States + + + + + + +## Schema + +```typescript +interface DatePickerSchema { + type: 'date-picker'; + value?: string | Date; // Selected date (ISO string or Date) + placeholder?: string; // Placeholder text + + // Events + onDateChange?: string | ActionConfig; + + // States + disabled?: boolean; + + // Styling + className?: string; +} +``` + +## Examples + +### In a Form + + + +### Date Range + + diff --git a/docs/components/form/form.mdx b/docs/components/form/form.mdx new file mode 100644 index 000000000..58b2365a3 --- /dev/null +++ b/docs/components/form/form.mdx @@ -0,0 +1,143 @@ +--- +title: "Form" +description: "Form container with validation and submission handling" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Form component provides a complete form solution with validation and submission handling. + +## Basic Usage + + + +## With Validation + + + +## Schema + +```typescript +interface FormField { + name: string; + label?: string; + type: 'input' | 'textarea' | 'select' | 'checkbox' | 'radio-group' | 'date-picker'; + placeholder?: string; + required?: boolean; + disabled?: boolean; + + // Validation + minLength?: number; + maxLength?: number; + min?: number; + max?: number; + pattern?: string; + + // Options (for select, radio-group) + options?: Array<{ value: string; label: string }>; + + // Default value + defaultValue?: any; +} + +interface FormSchema { + type: 'form'; + fields: FormField[]; // Form fields + submitButton?: { + label: string; + variant?: string; + }; + + // Events + onSubmit?: string | ActionConfig; + + // Layout + columns?: number; // Grid layout + + // Styling + className?: string; +} +``` + +## Examples + +### Multi-column Form + + diff --git a/docs/components/form/input-otp.mdx b/docs/components/form/input-otp.mdx new file mode 100644 index 000000000..82d028dd0 --- /dev/null +++ b/docs/components/form/input-otp.mdx @@ -0,0 +1,98 @@ +--- +title: "Input OTP" +description: "One-time password input with auto-focus" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Input OTP component provides a secure way to enter one-time passwords or verification codes. + +## Basic Usage + + + +## With Separator + + + +## Different Lengths + + + + + + +## Schema + +```typescript +interface InputOTPSchema { + type: 'input-otp'; + length: number; // Number of digits + value?: string; // Current value + separator?: boolean; // Show visual separator + + // Events + onChange?: string | ActionConfig; + onComplete?: string | ActionConfig; + + // States + disabled?: boolean; + + // Styling + className?: string; +} +``` + +## Examples + +### In a Form + + diff --git a/docs/components/form/label.mdx b/docs/components/form/label.mdx new file mode 100644 index 000000000..910ca9a75 --- /dev/null +++ b/docs/components/form/label.mdx @@ -0,0 +1,67 @@ +--- +title: "Label" +description: "Text label for form elements" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Label component renders accessible labels for form fields. + +## Basic Usage + + + +## Required Field + + + +## Schema + +```typescript +interface LabelSchema { + type: 'label'; + text: string; // Label text + htmlFor?: string; // Associated input ID + required?: boolean; // Show required indicator + className?: string; +} +``` diff --git a/docs/components/form/meta.json b/docs/components/form/meta.json index 449b89971..e83fe7126 100644 --- a/docs/components/form/meta.json +++ b/docs/components/form/meta.json @@ -3,10 +3,17 @@ "pages": [ "button", "input", + "textarea", "select", + "combobox", "checkbox", + "radio-group", "switch", - "textarea", - "slider" + "slider", + "label", + "date-picker", + "input-otp", + "command", + "form" ] } diff --git a/docs/components/form/radio-group.mdx b/docs/components/form/radio-group.mdx new file mode 100644 index 000000000..6d6752643 --- /dev/null +++ b/docs/components/form/radio-group.mdx @@ -0,0 +1,164 @@ +--- +title: "Radio Group" +description: "A set of checkable buttons where only one can be selected at a time" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Radio Group component allows users to select one option from a set of mutually exclusive options. + +## Basic Usage + + + +## With Default Value + + + +## Layout Options + + + + + + +## States + + + + + + +## Schema + +```typescript +interface RadioOption { + value: string; + label: string; + disabled?: boolean; +} + +interface RadioGroupSchema { + type: 'radio-group'; + options: RadioOption[]; // Available options + value?: string; // Selected value + + // Layout + direction?: 'vertical' | 'horizontal'; + + // Events + onValueChange?: string | ActionConfig; + + // States + disabled?: boolean; + + // Styling + className?: string; +} +``` + +## Examples + +### In a Form + + + +### With Descriptions + + diff --git a/docs/components/layout/aspect-ratio.mdx b/docs/components/layout/aspect-ratio.mdx new file mode 100644 index 000000000..c0e21eb84 --- /dev/null +++ b/docs/components/layout/aspect-ratio.mdx @@ -0,0 +1,89 @@ +--- +title: "Aspect Ratio" +description: "Maintain a consistent width to height ratio" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Aspect Ratio component maintains a consistent width to height ratio for content. + +## Basic Usage + + + +## Different Ratios + + + + + + + +## Schema + +```typescript +interface AspectRatioSchema { + type: 'aspect-ratio'; + ratio: number; // Width / Height ratio + content: ComponentSchema; // Content to display + className?: string; +} +``` + +## Examples + +### Video Container + + diff --git a/docs/components/layout/meta.json b/docs/components/layout/meta.json index 52c471e6a..8cb6a1da0 100644 --- a/docs/components/layout/meta.json +++ b/docs/components/layout/meta.json @@ -6,6 +6,9 @@ "grid", "flex", "stack", - "tabs" + "tabs", + "aspect-ratio", + "scroll-area", + "resizable" ] } diff --git a/docs/components/layout/resizable.mdx b/docs/components/layout/resizable.mdx new file mode 100644 index 000000000..f2e205554 --- /dev/null +++ b/docs/components/layout/resizable.mdx @@ -0,0 +1,78 @@ +--- +title: "Resizable" +description: "Resizable panel groups with draggable handles" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Resizable component allows users to resize panels by dragging handles between them. + +## Basic Usage + + + +## Vertical Layout + + + +## Schema + +```typescript +interface ResizablePanel { + defaultSize?: number; // Default size (percentage) + minSize?: number; // Minimum size + maxSize?: number; // Maximum size + content: ComponentSchema; // Panel content +} + +interface ResizableSchema { + type: 'resizable'; + direction: 'horizontal' | 'vertical'; + panels: ResizablePanel[]; // Panels to display + className?: string; +} +``` diff --git a/docs/components/layout/scroll-area.mdx b/docs/components/layout/scroll-area.mdx new file mode 100644 index 000000000..03e527d13 --- /dev/null +++ b/docs/components/layout/scroll-area.mdx @@ -0,0 +1,59 @@ +--- +title: "Scroll Area" +description: "Styled scrollable container with custom scrollbars" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Scroll Area component provides a scrollable container with customized scrollbars. + +## Basic Usage + + ({ + type: 'text', + value: `Item ${i + 1}` + })) + } + }} + title="Vertical Scroll" +/> + +## Horizontal Scroll + + ({ + type: 'badge', + text: `Tag ${i + 1}` + })) + } + }} + title="Horizontal Scroll" +/> + +## Schema + +```typescript +interface ScrollAreaSchema { + type: 'scroll-area'; + content: ComponentSchema; // Scrollable content + height?: string; // Container height + width?: string; // Container width + orientation?: 'vertical' | 'horizontal'; + className?: string; +} +``` diff --git a/docs/components/meta.json b/docs/components/meta.json index 077b80b3e..58510a9bc 100644 --- a/docs/components/meta.json +++ b/docs/components/meta.json @@ -2,45 +2,13 @@ "title": "Components", "pages": [ "index", - "---Basic", - "basic/text", - "basic/icon", - "basic/image", - "basic/separator", - "basic/html", - "---Form", - "form/button", - "form/input", - "form/select", - "form/checkbox", - "form/switch", - "form/textarea", - "form/slider", - "---Layout", - "layout/container", - "layout/card", - "layout/grid", - "layout/flex", - "layout/stack", - "layout/tabs", - "---Data Display", - "data-display/badge", - "data-display/avatar", - "data-display/alert", - "data-display/list", - "---Feedback", - "feedback/loading", - "feedback/progress", - "feedback/skeleton", - "---Overlay", - "overlay/dialog", - "overlay/drawer", - "overlay/tooltip", - "overlay/popover", - "---Disclosure", - "disclosure/accordion", - "disclosure/collapsible", - "---Complex", - "complex/table" + "basic", + "form", + "layout", + "data-display", + "feedback", + "overlay", + "disclosure", + "complex" ] } diff --git a/docs/components/overlay/alert-dialog.mdx b/docs/components/overlay/alert-dialog.mdx new file mode 100644 index 000000000..2eea3b3d2 --- /dev/null +++ b/docs/components/overlay/alert-dialog.mdx @@ -0,0 +1,123 @@ +--- +title: "Alert Dialog" +description: "A modal dialog that interrupts the user with important content and expects a response" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Alert Dialog component is used to interrupt the user with important content and require a confirmation before proceeding. + +## Basic Usage + + + +## Variants + + + + + + +## Schema + +```typescript +interface AlertDialogSchema { + type: 'alert-dialog'; + title: string; // Dialog title + description: string; // Dialog description + + // Trigger + trigger: ComponentSchema; // Component that triggers the dialog + + // Actions + actions?: ComponentSchema[]; // Action buttons + + // Styling + className?: string; +} +``` + +## Examples + +### With Custom Actions + + diff --git a/docs/components/overlay/context-menu.mdx b/docs/components/overlay/context-menu.mdx new file mode 100644 index 000000000..72717dbe9 --- /dev/null +++ b/docs/components/overlay/context-menu.mdx @@ -0,0 +1,49 @@ +--- +title: "Context Menu" +description: "Display a menu on right-click or long-press" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Context Menu component displays a menu when right-clicking on an element. + +## Basic Usage + + + +## Schema + +```typescript +interface ContextMenuItem { + label?: string; + value?: string; + icon?: string; + type?: 'separator'; + disabled?: boolean; +} + +interface ContextMenuSchema { + type: 'context-menu'; + trigger: ComponentSchema; // Trigger element + items: ContextMenuItem[]; // Menu items + onSelect?: string | ActionConfig; + className?: string; +} +``` diff --git a/docs/components/overlay/dropdown-menu.mdx b/docs/components/overlay/dropdown-menu.mdx new file mode 100644 index 000000000..33b7eb8e0 --- /dev/null +++ b/docs/components/overlay/dropdown-menu.mdx @@ -0,0 +1,71 @@ +--- +title: "Dropdown Menu" +description: "Display a menu of actions or options triggered by a button" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Dropdown Menu component displays a list of actions or options when triggered. + +## Basic Usage + + + +## With Icons + + + +## Schema + +```typescript +interface DropdownMenuItem { + label?: string; + value?: string; + icon?: string; + variant?: 'default' | 'destructive'; + type?: 'separator'; + disabled?: boolean; +} + +interface DropdownMenuSchema { + type: 'dropdown-menu'; + trigger: ComponentSchema; // Trigger component + items: DropdownMenuItem[]; // Menu items + + // Events + onSelect?: string | ActionConfig; + + // Styling + className?: string; +} +``` diff --git a/docs/components/overlay/hover-card.mdx b/docs/components/overlay/hover-card.mdx new file mode 100644 index 000000000..6961957a8 --- /dev/null +++ b/docs/components/overlay/hover-card.mdx @@ -0,0 +1,64 @@ +--- +title: "Hover Card" +description: "Display rich content in a popup when hovering over a trigger" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Hover Card component displays rich content when hovering over an element. + +## Basic Usage + + + +## With Rich Content + + + +## Schema + +```typescript +interface HoverCardSchema { + type: 'hover-card'; + trigger: ComponentSchema; // Trigger element + content: ComponentSchema; // Card content + side?: 'top' | 'right' | 'bottom' | 'left'; + align?: 'start' | 'center' | 'end'; + openDelay?: number; // Delay before opening (ms) + closeDelay?: number; // Delay before closing (ms) + className?: string; +} +``` diff --git a/docs/components/overlay/menubar.mdx b/docs/components/overlay/menubar.mdx new file mode 100644 index 000000000..983ed1c35 --- /dev/null +++ b/docs/components/overlay/menubar.mdx @@ -0,0 +1,76 @@ +--- +title: "Menubar" +description: "Horizontal menu bar with dropdown menus" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Menubar component provides a horizontal menu bar similar to desktop applications. + +## Basic Usage + + + +## Schema + +```typescript +interface MenubarItem { + label?: string; + value?: string; + icon?: string; + shortcut?: string[]; + type?: 'separator'; + disabled?: boolean; +} + +interface MenubarMenu { + label: string; + items: MenubarItem[]; +} + +interface MenubarSchema { + type: 'menubar'; + menus: MenubarMenu[]; // Menu definitions + + // Events + onSelect?: string | ActionConfig; + + // Styling + className?: string; +} +``` diff --git a/docs/components/overlay/meta.json b/docs/components/overlay/meta.json index 706a4540d..54855a1bf 100644 --- a/docs/components/overlay/meta.json +++ b/docs/components/overlay/meta.json @@ -4,6 +4,12 @@ "dialog", "drawer", "tooltip", - "popover" + "popover", + "alert-dialog", + "sheet", + "hover-card", + "context-menu", + "dropdown-menu", + "menubar" ] } diff --git a/docs/components/overlay/sheet.mdx b/docs/components/overlay/sheet.mdx new file mode 100644 index 000000000..f1da44b2d --- /dev/null +++ b/docs/components/overlay/sheet.mdx @@ -0,0 +1,66 @@ +--- +title: "Sheet" +description: "Slide-in panel from the edge of the screen" +--- + +import { ComponentDemo, DemoGrid } from '@/app/components/ComponentDemo'; + +The Sheet component displays content in a panel that slides in from the edge of the screen. + +## Basic Usage + + + +## Sides + + + + + + +## Schema + +```typescript +interface SheetSchema { + type: 'sheet'; + trigger: ComponentSchema; // Trigger component + title?: string; // Sheet title + description?: string; // Sheet description + content: ComponentSchema; // Sheet content + side?: 'left' | 'right' | 'top' | 'bottom'; + className?: string; +} +``` diff --git a/packages/components/src/hooks/use-toast.ts b/packages/components/src/hooks/use-toast.ts new file mode 100644 index 000000000..5c0b364ee --- /dev/null +++ b/packages/components/src/hooks/use-toast.ts @@ -0,0 +1,197 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import * as React from "react" + +import type { + ToastActionElement, + ToastProps, +} from "../ui/toast" + +const TOAST_LIMIT = 1 +const TOAST_REMOVE_DELAY = 5000 + +type ToasterToast = ToastProps & { + id: string + title?: React.ReactNode + description?: React.ReactNode + action?: ToastActionElement +} + +const actionTypes = { + ADD_TOAST: "ADD_TOAST", + UPDATE_TOAST: "UPDATE_TOAST", + DISMISS_TOAST: "DISMISS_TOAST", + REMOVE_TOAST: "REMOVE_TOAST", +} as const + +let count = 0 + +function genId() { + count = (count + 1) % Number.MAX_SAFE_INTEGER + return count.toString() +} + +type ActionType = typeof actionTypes + +type Action = + | { + type: ActionType["ADD_TOAST"] + toast: ToasterToast + } + | { + type: ActionType["UPDATE_TOAST"] + toast: Partial + } + | { + type: ActionType["DISMISS_TOAST"] + toastId?: ToasterToast["id"] + } + | { + type: ActionType["REMOVE_TOAST"] + toastId?: ToasterToast["id"] + } + +interface State { + toasts: ToasterToast[] +} + +const toastTimeouts = new Map>() + +const addToRemoveQueue = (toastId: string) => { + if (toastTimeouts.has(toastId)) { + return + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId) + dispatch({ + type: "REMOVE_TOAST", + toastId: toastId, + }) + }, TOAST_REMOVE_DELAY) + + toastTimeouts.set(toastId, timeout) +} + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case "ADD_TOAST": + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + } + + case "UPDATE_TOAST": + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id ? { ...t, ...action.toast } : t + ), + } + + case "DISMISS_TOAST": { + const { toastId } = action + + if (toastId) { + addToRemoveQueue(toastId) + } else { + state.toasts.forEach((toast) => { + addToRemoveQueue(toast.id) + }) + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t + ), + } + } + case "REMOVE_TOAST": + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + } + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + } + } +} + +const listeners: Array<(state: State) => void> = [] + +let memoryState: State = { toasts: [] } + +function dispatch(action: Action) { + memoryState = reducer(memoryState, action) + listeners.forEach((listener) => { + listener(memoryState) + }) +} + +type Toast = Omit + +function toast({ ...props }: Toast) { + const id = genId() + + const update = (props: ToasterToast) => + dispatch({ + type: "UPDATE_TOAST", + toast: { ...props, id }, + }) + const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) + + dispatch({ + type: "ADD_TOAST", + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss() + }, + }, + }) + + return { + id: id, + dismiss, + update, + } +} + +function useToast() { + const [state, setState] = React.useState(memoryState) + + React.useEffect(() => { + listeners.push(setState) + return () => { + const index = listeners.indexOf(setState) + if (index > -1) { + listeners.splice(index, 1) + } + } + }, [state]) + + return { + ...state, + toast, + dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), + } +} + +export { useToast, toast } diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index d6e396e2b..512dbdba8 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -14,6 +14,9 @@ import './renderers'; // Export utils export * from './lib/utils'; +// Export hooks +export * from './hooks/use-toast'; + // Export raw Shadcn UI components export * from './ui'; diff --git a/packages/components/src/renderers/basic/button-group.tsx b/packages/components/src/renderers/basic/button-group.tsx new file mode 100644 index 000000000..d49602b38 --- /dev/null +++ b/packages/components/src/renderers/basic/button-group.tsx @@ -0,0 +1,78 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { ComponentRegistry } from '@object-ui/core'; +import type { ButtonGroupSchema } from '@object-ui/types'; +import { Button } from '../../ui'; +import { cn } from '../../lib/utils'; + +ComponentRegistry.register('button-group', + ({ schema, ...props }: { schema: ButtonGroupSchema; [key: string]: any }) => { + const { + 'data-obj-id': dataObjId, + 'data-obj-type': dataObjType, + style, + ...buttonGroupProps + } = props; + + return ( +
+ {schema.buttons?.map((button, idx) => ( + + ))} +
+ ); + }, + { + label: 'Button Group', + inputs: [ + { + name: 'variant', + type: 'enum', + enum: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'], + defaultValue: 'default', + label: 'Variant' + }, + { + name: 'size', + type: 'enum', + enum: ['default', 'sm', 'lg', 'icon'], + defaultValue: 'default', + label: 'Size' + }, + { name: 'className', type: 'string', label: 'CSS Class' } + ], + defaultProps: { + variant: 'default', + size: 'default', + buttons: [ + { label: 'Left' }, + { label: 'Middle' }, + { label: 'Right' } + ] + } + } +); diff --git a/packages/components/src/renderers/basic/index.ts b/packages/components/src/renderers/basic/index.ts index f24577171..62b8b3582 100644 --- a/packages/components/src/renderers/basic/index.ts +++ b/packages/components/src/renderers/basic/index.ts @@ -13,3 +13,6 @@ import './separator'; import './image'; import './icon'; import './html'; +import './button-group'; +import './pagination'; +import './navigation-menu'; diff --git a/packages/components/src/renderers/basic/navigation-menu.tsx b/packages/components/src/renderers/basic/navigation-menu.tsx new file mode 100644 index 000000000..eb76a97ed --- /dev/null +++ b/packages/components/src/renderers/basic/navigation-menu.tsx @@ -0,0 +1,80 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { ComponentRegistry } from '@object-ui/core'; +import type { NavigationMenuSchema } from '@object-ui/types'; +import { NavigationMenu, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink } from '../../ui/navigation-menu'; +import { cn } from '../../lib/utils'; + +ComponentRegistry.register('navigation-menu', + ({ schema, ...props }: { schema: NavigationMenuSchema; [key: string]: any }) => { + const { + 'data-obj-id': dataObjId, + 'data-obj-type': dataObjType, + style, + ...navigationMenuProps + } = props; + + return ( + + + {schema.items?.map((item, idx) => ( + + {item.children ? ( + <> + {item.label} + + + + + ) : ( + {item.label} + )} + + ))} + + + ); + }, + { + label: 'Navigation Menu', + inputs: [ + { name: 'className', type: 'string', label: 'CSS Class' } + ], + defaultProps: { + items: [ + { label: 'Home', href: '/' }, + { label: 'About', href: '/about' } + ] + } + } +); diff --git a/packages/components/src/renderers/basic/pagination.tsx b/packages/components/src/renderers/basic/pagination.tsx new file mode 100644 index 000000000..511055472 --- /dev/null +++ b/packages/components/src/renderers/basic/pagination.tsx @@ -0,0 +1,82 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { ComponentRegistry } from '@object-ui/core'; +import type { PaginationSchema } from '@object-ui/types'; +import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from '../../ui/pagination'; + +ComponentRegistry.register('pagination', + ({ schema, ...props }: { schema: PaginationSchema; [key: string]: any }) => { + const { + 'data-obj-id': dataObjId, + 'data-obj-type': dataObjType, + style, + ...paginationProps + } = props; + + const currentPage = schema.currentPage || schema.page || 1; + const totalPages = schema.totalPages || 1; + const showEllipsis = totalPages > 7; + + const getPageNumbers = () => { + if (totalPages <= 7) { + return Array.from({ length: totalPages }, (_, i) => i + 1); + } + + if (currentPage <= 3) { + return [1, 2, 3, 4, 5, -1, totalPages]; + } + + if (currentPage >= totalPages - 2) { + return [1, -1, totalPages - 4, totalPages - 3, totalPages - 2, totalPages - 1, totalPages]; + } + + return [1, -1, currentPage - 1, currentPage, currentPage + 1, -1, totalPages]; + }; + + return ( + + + + + + {getPageNumbers().map((page, idx) => ( + + {page === -1 ? ( + + ) : ( + + {page} + + )} + + ))} + + + + + + ); + }, + { + label: 'Pagination', + inputs: [ + { name: 'currentPage', type: 'number', label: 'Current Page', defaultValue: 1 }, + { name: 'totalPages', type: 'number', label: 'Total Pages', defaultValue: 10 }, + { name: 'className', type: 'string', label: 'CSS Class' } + ], + defaultProps: { + currentPage: 1, + totalPages: 10 + } + } +); diff --git a/packages/components/src/renderers/data-display/breadcrumb.tsx b/packages/components/src/renderers/data-display/breadcrumb.tsx new file mode 100644 index 000000000..111545717 --- /dev/null +++ b/packages/components/src/renderers/data-display/breadcrumb.tsx @@ -0,0 +1,59 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { ComponentRegistry } from '@object-ui/core'; +import type { BreadcrumbSchema } from '@object-ui/types'; +import { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator } from '../../ui/breadcrumb'; +import { renderChildren } from '../../lib/utils'; + +ComponentRegistry.register('breadcrumb', + ({ schema, ...props }: { schema: BreadcrumbSchema; [key: string]: any }) => { + const { + 'data-obj-id': dataObjId, + 'data-obj-type': dataObjType, + style, + ...breadcrumbProps + } = props; + + return ( + + + {schema.items?.map((item, idx) => ( +
+ + {idx === (schema.items?.length || 0) - 1 ? ( + {item.label} + ) : ( + {item.label} + )} + + {idx < (schema.items?.length || 0) - 1 && } +
+ ))} +
+
+ ); + }, + { + label: 'Breadcrumb', + inputs: [ + { name: 'className', type: 'string', label: 'CSS Class' } + ], + defaultProps: { + items: [ + { label: 'Home', href: '/' }, + { label: 'Products', href: '/products' }, + { label: 'Product' } + ] + } + } +); diff --git a/packages/components/src/renderers/data-display/index.ts b/packages/components/src/renderers/data-display/index.ts index e8516f6f8..1bb019675 100644 --- a/packages/components/src/renderers/data-display/index.ts +++ b/packages/components/src/renderers/data-display/index.ts @@ -12,3 +12,5 @@ import './alert'; import './list'; import './tree-view'; import './statistic'; +import './breadcrumb'; +import './kbd'; diff --git a/packages/components/src/renderers/data-display/kbd.tsx b/packages/components/src/renderers/data-display/kbd.tsx new file mode 100644 index 000000000..81c6d19ad --- /dev/null +++ b/packages/components/src/renderers/data-display/kbd.tsx @@ -0,0 +1,49 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { ComponentRegistry } from '@object-ui/core'; +import type { KbdSchema } from '@object-ui/types'; +import { cn } from '../../lib/utils'; + +ComponentRegistry.register('kbd', + ({ schema, ...props }: { schema: KbdSchema; [key: string]: any }) => { + const { + 'data-obj-id': dataObjId, + 'data-obj-type': dataObjType, + style, + ...kbdProps + } = props; + + const keys = Array.isArray(schema.keys) ? schema.keys : [schema.keys || schema.label || 'K']; + + return ( +
+ {keys.map((key, idx) => ( + + {key} + + ))} +
+ ); + }, + { + label: 'Keyboard Key', + inputs: [ + { name: 'label', type: 'string', label: 'Label' }, + { name: 'className', type: 'string', label: 'CSS Class' } + ], + defaultProps: { + label: 'K' + } + } +); diff --git a/packages/components/src/renderers/disclosure/index.ts b/packages/components/src/renderers/disclosure/index.ts index 3e7cad4fe..b8e55ae64 100644 --- a/packages/components/src/renderers/disclosure/index.ts +++ b/packages/components/src/renderers/disclosure/index.ts @@ -8,3 +8,4 @@ import './accordion'; import './collapsible'; +import './toggle-group'; diff --git a/packages/components/src/renderers/disclosure/toggle-group.tsx b/packages/components/src/renderers/disclosure/toggle-group.tsx new file mode 100644 index 000000000..4e846db86 --- /dev/null +++ b/packages/components/src/renderers/disclosure/toggle-group.tsx @@ -0,0 +1,77 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { ComponentRegistry } from '@object-ui/core'; +import type { ToggleGroupSchema } from '@object-ui/types'; +import { ToggleGroup, ToggleGroupItem } from '../../ui/toggle-group'; + +ComponentRegistry.register('toggle-group', + ({ schema, ...props }: { schema: ToggleGroupSchema; [key: string]: any }) => { + const { + 'data-obj-id': dataObjId, + 'data-obj-type': dataObjType, + style, + ...toggleGroupProps + } = props; + + return ( + + {schema.items?.map((item, idx) => ( + + {item.label} + + ))} + + ); + }, + { + label: 'Toggle Group', + inputs: [ + { + name: 'selectionType', + type: 'enum', + enum: ['single', 'multiple'], + defaultValue: 'single', + label: 'Selection Type' + }, + { + name: 'variant', + type: 'enum', + enum: ['default', 'outline'], + defaultValue: 'default', + label: 'Variant' + }, + { + name: 'size', + type: 'enum', + enum: ['default', 'sm', 'lg'], + defaultValue: 'default', + label: 'Size' + }, + { name: 'className', type: 'string', label: 'CSS Class' } + ], + defaultProps: { + selectionType: 'single', + variant: 'default', + size: 'default', + items: [ + { value: 'bold', label: 'Bold' }, + { value: 'italic', label: 'Italic' }, + { value: 'underline', label: 'Underline' } + ] + } + } +); diff --git a/packages/components/src/renderers/feedback/empty.tsx b/packages/components/src/renderers/feedback/empty.tsx new file mode 100644 index 000000000..f59bb6b84 --- /dev/null +++ b/packages/components/src/renderers/feedback/empty.tsx @@ -0,0 +1,48 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { ComponentRegistry } from '@object-ui/core'; +import type { EmptySchema } from '@object-ui/types'; +import { InboxIcon } from 'lucide-react'; +import { cn } from '../../lib/utils'; + +ComponentRegistry.register('empty', + ({ schema, ...props }: { schema: EmptySchema; [key: string]: any }) => { + const { + 'data-obj-id': dataObjId, + 'data-obj-type': dataObjType, + style, + ...emptyProps + } = props; + + return ( +
+ +

{schema.title || 'No data'}

+ {schema.description && ( +

{schema.description}

+ )} +
+ ); + }, + { + label: 'Empty', + inputs: [ + { name: 'title', type: 'string', label: 'Title', defaultValue: 'No data' }, + { name: 'description', type: 'string', label: 'Description' }, + { name: 'className', type: 'string', label: 'CSS Class' } + ], + defaultProps: { + title: 'No data' + } + } +); diff --git a/packages/components/src/renderers/feedback/index.ts b/packages/components/src/renderers/feedback/index.ts index 90239104b..791b9f479 100644 --- a/packages/components/src/renderers/feedback/index.ts +++ b/packages/components/src/renderers/feedback/index.ts @@ -10,3 +10,7 @@ import './progress'; import './skeleton'; import './toaster'; import './loading'; +import './toast'; +import './spinner'; +import './empty'; +import './sonner'; diff --git a/packages/components/src/renderers/feedback/sonner.tsx b/packages/components/src/renderers/feedback/sonner.tsx new file mode 100644 index 000000000..c5af9fbf1 --- /dev/null +++ b/packages/components/src/renderers/feedback/sonner.tsx @@ -0,0 +1,55 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { ComponentRegistry } from '@object-ui/core'; +import type { SonnerSchema } from '@object-ui/types'; +import { toast } from 'sonner'; +import { Button } from '../../ui'; + +ComponentRegistry.register('sonner', + ({ schema, ...props }: { schema: SonnerSchema; [key: string]: any }) => { + const showToast = () => { + const toastFn = schema.variant === 'success' ? toast.success : + schema.variant === 'error' ? toast.error : + schema.variant === 'warning' ? toast.warning : + schema.variant === 'info' ? toast.info : + toast; + + toastFn(schema.message || schema.title || 'Notification', { + description: schema.description, + }); + }; + + return ( + + ); + }, + { + label: 'Sonner Toast', + inputs: [ + { name: 'message', type: 'string', label: 'Message' }, + { name: 'description', type: 'string', label: 'Description' }, + { + name: 'variant', + type: 'enum', + enum: ['default', 'success', 'error', 'warning', 'info'], + defaultValue: 'default', + label: 'Variant' + }, + { name: 'buttonLabel', type: 'string', label: 'Button Label' }, + { name: 'className', type: 'string', label: 'CSS Class' } + ], + defaultProps: { + message: 'Notification', + buttonLabel: 'Show Toast', + variant: 'default' + } + } +); diff --git a/packages/components/src/renderers/feedback/spinner.tsx b/packages/components/src/renderers/feedback/spinner.tsx new file mode 100644 index 000000000..c2e6ac84a --- /dev/null +++ b/packages/components/src/renderers/feedback/spinner.tsx @@ -0,0 +1,54 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { ComponentRegistry } from '@object-ui/core'; +import type { SpinnerSchema } from '@object-ui/types'; +import { Loader2 } from 'lucide-react'; +import { cn } from '../../lib/utils'; + +ComponentRegistry.register('spinner', + ({ schema, ...props }: { schema: SpinnerSchema; [key: string]: any }) => { + const { + 'data-obj-id': dataObjId, + 'data-obj-type': dataObjType, + style, + ...spinnerProps + } = props; + + const sizeClasses = { + sm: 'h-4 w-4', + md: 'h-6 w-6', + lg: 'h-8 w-8', + xl: 'h-12 w-12' + }; + + return ( + + ); + }, + { + label: 'Spinner', + inputs: [ + { + name: 'size', + type: 'enum', + enum: ['sm', 'md', 'lg', 'xl'], + defaultValue: 'md', + label: 'Size' + }, + { name: 'className', type: 'string', label: 'CSS Class' } + ], + defaultProps: { + size: 'md' + } + } +); diff --git a/packages/components/src/renderers/feedback/toast.tsx b/packages/components/src/renderers/feedback/toast.tsx new file mode 100644 index 000000000..58f45cc8f --- /dev/null +++ b/packages/components/src/renderers/feedback/toast.tsx @@ -0,0 +1,53 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { ComponentRegistry } from '@object-ui/core'; +import type { ToastSchema } from '@object-ui/types'; +import { useToast } from '../../hooks/use-toast'; +import { Button } from '../../ui'; + +ComponentRegistry.register('toast', + ({ schema, ...props }: { schema: ToastSchema; [key: string]: any }) => { + const { toast } = useToast(); + + const showToast = () => { + toast({ + title: schema.title, + description: schema.description, + variant: schema.variant as any, + }); + }; + + return ( + + ); + }, + { + label: 'Toast', + inputs: [ + { name: 'title', type: 'string', label: 'Title' }, + { name: 'description', type: 'string', label: 'Description' }, + { + name: 'variant', + type: 'enum', + enum: ['default', 'destructive'], + defaultValue: 'default', + label: 'Variant' + }, + { name: 'buttonLabel', type: 'string', label: 'Button Label' }, + { name: 'className', type: 'string', label: 'CSS Class' } + ], + defaultProps: { + title: 'Notification', + buttonLabel: 'Show Toast', + variant: 'default' + } + } +); diff --git a/packages/components/src/renderers/feedback/toaster.tsx b/packages/components/src/renderers/feedback/toaster.tsx index 711630a9b..28fb9cfb0 100644 --- a/packages/components/src/renderers/feedback/toaster.tsx +++ b/packages/components/src/renderers/feedback/toaster.tsx @@ -8,8 +8,8 @@ import { ComponentRegistry } from '@object-ui/core'; import type { ToasterSchema } from '@object-ui/types'; -import { Toaster as SonnerToaster } from '../../ui'; -import { Toaster as DefaultToaster } from '../../ui'; +import { Toaster as SonnerToaster } from '../../ui/sonner'; +import { ToastNotifier as DefaultToaster } from '../../ui'; // Note: In shadcn/ui typical setup, Toaster is exported from 'components/ui/toaster' and 'components/ui/sonner'. // But in @object-ui/ui index.tsx, we need to check if they are exported. // Assuming they are exported as Toaster and Sonner (or similar). diff --git a/packages/components/src/renderers/form/combobox.tsx b/packages/components/src/renderers/form/combobox.tsx new file mode 100644 index 000000000..aabd4ca47 --- /dev/null +++ b/packages/components/src/renderers/form/combobox.tsx @@ -0,0 +1,47 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { ComponentRegistry } from '@object-ui/core'; +import type { ComboboxSchema } from '@object-ui/types'; +import { Combobox } from '../../ui'; + +ComponentRegistry.register('combobox', + ({ schema, ...props }: { schema: ComboboxSchema; [key: string]: any }) => { + const { + 'data-obj-id': dataObjId, + 'data-obj-type': dataObjType, + style, + ...comboboxProps + } = props; + + return ( + + ); + }, + { + label: 'Combobox', + inputs: [ + { name: 'placeholder', type: 'string', label: 'Placeholder' }, + { name: 'value', type: 'string', label: 'Value' }, + { name: 'disabled', type: 'boolean', label: 'Disabled', defaultValue: false }, + { name: 'className', type: 'string', label: 'CSS Class' } + ], + defaultProps: { + placeholder: 'Select option...', + options: [] + } + } +); diff --git a/packages/components/src/renderers/form/command.tsx b/packages/components/src/renderers/form/command.tsx new file mode 100644 index 000000000..18baf02c1 --- /dev/null +++ b/packages/components/src/renderers/form/command.tsx @@ -0,0 +1,57 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { ComponentRegistry } from '@object-ui/core'; +import type { CommandSchema } from '@object-ui/types'; +import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from '../../ui/command'; + +ComponentRegistry.register('command', + ({ schema, ...props }: { schema: CommandSchema; [key: string]: any }) => { + const { + 'data-obj-id': dataObjId, + 'data-obj-type': dataObjType, + style, + ...commandProps + } = props; + + return ( + + + + {schema.emptyText || 'No results found.'} + {schema.groups?.map((group, idx) => ( + + {group.items?.map((item, itemIdx) => ( + + {item.label} + + ))} + + ))} + + + ); + }, + { + label: 'Command', + inputs: [ + { name: 'placeholder', type: 'string', label: 'Placeholder' }, + { name: 'emptyText', type: 'string', label: 'Empty Text' }, + { name: 'className', type: 'string', label: 'CSS Class' } + ], + defaultProps: { + placeholder: 'Type a command or search...', + emptyText: 'No results found.', + groups: [] + } + } +); diff --git a/packages/components/src/renderers/form/index.ts b/packages/components/src/renderers/form/index.ts index 54cb00a70..1230512d1 100644 --- a/packages/components/src/renderers/form/index.ts +++ b/packages/components/src/renderers/form/index.ts @@ -22,3 +22,5 @@ import './input-otp'; import './calendar'; import './date-picker'; import './file-upload'; +import './combobox'; +import './command'; diff --git a/packages/components/src/renderers/layout/aspect-ratio.tsx b/packages/components/src/renderers/layout/aspect-ratio.tsx new file mode 100644 index 000000000..d0b73953c --- /dev/null +++ b/packages/components/src/renderers/layout/aspect-ratio.tsx @@ -0,0 +1,50 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { ComponentRegistry } from '@object-ui/core'; +import type { AspectRatioSchema } from '@object-ui/types'; +import { AspectRatio } from '../../ui/aspect-ratio'; +import { renderChildren } from '../../lib/utils'; + +ComponentRegistry.register('aspect-ratio', + ({ schema, ...props }: { schema: AspectRatioSchema; [key: string]: any }) => { + const { + 'data-obj-id': dataObjId, + 'data-obj-type': dataObjType, + style, + ...aspectRatioProps + } = props; + + return ( + + {schema.image ? ( + {schema.alt + ) : ( + renderChildren(schema.body) + )} + + ); + }, + { + label: 'Aspect Ratio', + inputs: [ + { name: 'ratio', type: 'number', label: 'Ratio', defaultValue: 16/9 }, + { name: 'image', type: 'string', label: 'Image URL' }, + { name: 'alt', type: 'string', label: 'Alt Text' }, + { name: 'className', type: 'string', label: 'CSS Class' } + ], + defaultProps: { + ratio: 16 / 9 + } + } +); diff --git a/packages/components/src/renderers/layout/index.ts b/packages/components/src/renderers/layout/index.ts index 1f36ba837..f778c720e 100644 --- a/packages/components/src/renderers/layout/index.ts +++ b/packages/components/src/renderers/layout/index.ts @@ -14,5 +14,6 @@ import './stack'; import './container'; import './page'; import './semantic'; +import './aspect-ratio'; diff --git a/packages/components/src/renderers/overlay/index.ts b/packages/components/src/renderers/overlay/index.ts index a5f328dab..f4cdc4396 100644 --- a/packages/components/src/renderers/overlay/index.ts +++ b/packages/components/src/renderers/overlay/index.ts @@ -15,3 +15,4 @@ import './drawer'; import './hover-card'; import './dropdown-menu'; import './context-menu'; +import './menubar'; diff --git a/packages/components/src/renderers/overlay/menubar.tsx b/packages/components/src/renderers/overlay/menubar.tsx new file mode 100644 index 000000000..db6f211d0 --- /dev/null +++ b/packages/components/src/renderers/overlay/menubar.tsx @@ -0,0 +1,75 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { ComponentRegistry } from '@object-ui/core'; +import type { MenubarSchema } from '@object-ui/types'; +import { Menubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem, MenubarSeparator, MenubarSub, MenubarSubTrigger, MenubarSubContent } from '../../ui/menubar'; + +ComponentRegistry.register('menubar', + ({ schema, ...props }: { schema: MenubarSchema; [key: string]: any }) => { + const { + 'data-obj-id': dataObjId, + 'data-obj-type': dataObjType, + style, + ...menubarProps + } = props; + + return ( + + {schema.menus?.map((menu, idx) => ( + + {menu.label} + + {menu.items?.map((item, itemIdx) => ( + item.separator ? ( + + ) : item.children ? ( + + {item.label} + + {item.children.map((child, childIdx) => ( + {child.label} + ))} + + + ) : ( + + {item.label} + + ) + ))} + + + ))} + + ); + }, + { + label: 'Menubar', + inputs: [ + { name: 'className', type: 'string', label: 'CSS Class' } + ], + defaultProps: { + menus: [ + { + label: 'File', + items: [ + { label: 'New' }, + { label: 'Open' }, + { separator: true }, + { label: 'Exit' } + ] + } + ] + } + } +); diff --git a/packages/components/src/ui/combobox.tsx b/packages/components/src/ui/combobox.tsx new file mode 100644 index 000000000..35ec3e2b4 --- /dev/null +++ b/packages/components/src/ui/combobox.tsx @@ -0,0 +1,104 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use client" + +import * as React from "react" +import { Check, ChevronsUpDown } from "lucide-react" + +import { cn } from "../lib/utils" +import { Button } from "./button" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "./command" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "./popover" + +export interface ComboboxOption { + value: string + label: string +} + +export interface ComboboxProps { + options: ComboboxOption[] + value?: string + onValueChange?: (value: string) => void + placeholder?: string + searchPlaceholder?: string + emptyText?: string + className?: string + disabled?: boolean +} + +export function Combobox({ + options, + value, + onValueChange, + placeholder = "Select option...", + searchPlaceholder = "Search...", + emptyText = "No option found.", + className, + disabled, +}: ComboboxProps) { + const [open, setOpen] = React.useState(false) + + return ( + + + + + + + + + {emptyText} + + {options.map((option) => ( + { + onValueChange?.(currentValue === value ? "" : currentValue) + setOpen(false) + }} + > + + {option.label} + + ))} + + + + + + ) +} diff --git a/packages/components/src/ui/date-picker.tsx b/packages/components/src/ui/date-picker.tsx new file mode 100644 index 000000000..ac47175ea --- /dev/null +++ b/packages/components/src/ui/date-picker.tsx @@ -0,0 +1,61 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use client" + +import * as React from "react" +import { CalendarIcon } from "lucide-react" +import { format } from "date-fns" + +import { cn } from "../lib/utils" +import { Button } from "./button" +import { Calendar } from "./calendar" +import { Popover, PopoverContent, PopoverTrigger } from "./popover" + +export interface DatePickerProps { + date?: Date + onDateChange?: (date: Date | undefined) => void + placeholder?: string + className?: string + disabled?: boolean +} + +export function DatePicker({ + date, + onDateChange, + placeholder = "Pick a date", + className, + disabled, +}: DatePickerProps) { + return ( + + + + + + + + + ) +} diff --git a/packages/components/src/ui/index.ts b/packages/components/src/ui/index.ts index 66e3d7b60..7ae8593ae 100644 --- a/packages/components/src/ui/index.ts +++ b/packages/components/src/ui/index.ts @@ -22,8 +22,10 @@ export * from './carousel'; export * from './chatbot'; export * from './checkbox'; export * from './collapsible'; +export * from './combobox'; export * from './command'; export * from './context-menu'; +export * from './date-picker'; export * from './dialog'; export * from './drawer'; export * from './dropdown-menu'; @@ -59,6 +61,8 @@ export * from './table'; export * from './tabs'; export * from './textarea'; export * from './timeline'; +export * from './toast'; +export { Toaster as ToastNotifier } from './toaster'; export * from './toggle-group'; export * from './toggle'; export * from './tooltip'; diff --git a/packages/components/src/ui/toast.tsx b/packages/components/src/ui/toast.tsx new file mode 100644 index 000000000..5e01d452e --- /dev/null +++ b/packages/components/src/ui/toast.tsx @@ -0,0 +1,135 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import * as React from "react" +import * as ToastPrimitives from "@radix-ui/react-toast" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from "lucide-react" + +import { cn } from "../lib/utils" + +const ToastProvider = ToastPrimitives.Provider + +const ToastViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastViewport.displayName = ToastPrimitives.Viewport.displayName + +const toastVariants = cva( + "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", + { + variants: { + variant: { + default: "border bg-background text-foreground", + destructive: + "destructive group border-destructive bg-destructive text-destructive-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Toast = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, variant, ...props }, ref) => { + return ( + + ) +}) +Toast.displayName = ToastPrimitives.Root.displayName + +const ToastAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastAction.displayName = ToastPrimitives.Action.displayName + +const ToastClose = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +ToastClose.displayName = ToastPrimitives.Close.displayName + +const ToastTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastTitle.displayName = ToastPrimitives.Title.displayName + +const ToastDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastDescription.displayName = ToastPrimitives.Description.displayName + +type ToastProps = React.ComponentPropsWithoutRef + +type ToastActionElement = React.ReactElement + +export { + type ToastProps, + type ToastActionElement, + ToastProvider, + ToastViewport, + Toast, + ToastTitle, + ToastDescription, + ToastClose, + ToastAction, +} diff --git a/packages/components/src/ui/toaster.tsx b/packages/components/src/ui/toaster.tsx new file mode 100644 index 000000000..af8a212ed --- /dev/null +++ b/packages/components/src/ui/toaster.tsx @@ -0,0 +1,43 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use client" + +import { + Toast, + ToastClose, + ToastDescription, + ToastProvider, + ToastTitle, + ToastViewport, +} from "./toast" +import { useToast } from "../hooks/use-toast" + +export function Toaster() { + const { toasts } = useToast() + + return ( + + {toasts.map(function ({ id, title, description, action, ...props }) { + return ( + +
+ {title && {title}} + {description && ( + {description} + )} +
+ {action} + +
+ ) + })} + +
+ ) +} diff --git a/packages/types/src/data-display.ts b/packages/types/src/data-display.ts index 61a0010a9..83474da52 100644 --- a/packages/types/src/data-display.ts +++ b/packages/types/src/data-display.ts @@ -609,6 +609,51 @@ export interface TimelineSchema extends BaseSchema { position?: 'left' | 'right' | 'alternate'; } +/** + * Breadcrumb item + */ +export interface BreadcrumbItem { + /** + * Item label + */ + label: string; + /** + * Item href/link + */ + href?: string; +} + +/** + * Breadcrumb component + */ +export interface BreadcrumbSchema extends BaseSchema { + type: 'breadcrumb'; + /** + * Breadcrumb items + */ + items: BreadcrumbItem[]; + /** + * Separator character + * @default '/' + */ + separator?: string; +} + +/** + * Keyboard key component + */ +export interface KbdSchema extends BaseSchema { + type: 'kbd'; + /** + * Key label (single key) + */ + label?: string; + /** + * Key labels (multiple keys) + */ + keys?: string | string[]; +} + /** * Union type of all data display schemas */ @@ -624,7 +669,9 @@ export type DataDisplaySchema = | ChartSchema | TimelineSchema | HtmlSchema - | StatisticSchema; + | StatisticSchema + | BreadcrumbSchema + | KbdSchema; /** * Raw HTML component diff --git a/packages/types/src/disclosure.ts b/packages/types/src/disclosure.ts index 1f0d9855b..7915bd9bf 100644 --- a/packages/types/src/disclosure.ts +++ b/packages/types/src/disclosure.ts @@ -113,9 +113,74 @@ export interface CollapsibleSchema extends BaseSchema { onOpenChange?: (open: boolean) => void; } +/** + * Toggle group item + */ +export interface ToggleGroupItem { + /** + * Item value + */ + value: string; + /** + * Item label + */ + label: string; + /** + * Item icon + */ + icon?: string; + /** + * Whether item is disabled + */ + disabled?: boolean; +} + +/** + * Toggle group component + */ +export interface ToggleGroupSchema extends BaseSchema { + type: 'toggle-group'; + /** + * Toggle group selection mode + * @default 'single' + */ + selectionType?: 'single' | 'multiple'; + /** + * Toggle group variant + * @default 'default' + */ + variant?: 'default' | 'outline'; + /** + * Toggle group size + * @default 'default' + */ + size?: 'default' | 'sm' | 'lg'; + /** + * Toggle group items + */ + items?: ToggleGroupItem[]; + /** + * Default selected value(s) + */ + defaultValue?: string | string[]; + /** + * Controlled selected value(s) + */ + value?: string | string[]; + /** + * Whether toggle group is disabled + */ + disabled?: boolean; + /** + * Change handler + */ + onValueChange?: (value: string | string[]) => void; +} + /** * Union type of all disclosure schemas */ export type DisclosureSchema = | AccordionSchema - | CollapsibleSchema; + | CollapsibleSchema + | ToggleGroupSchema; diff --git a/packages/types/src/feedback.ts b/packages/types/src/feedback.ts index 098a8bf72..720c53ae8 100644 --- a/packages/types/src/feedback.ts +++ b/packages/types/src/feedback.ts @@ -167,6 +167,69 @@ export interface ToasterSchema extends BaseSchema { limit?: number; } +/** + * Spinner component + */ +export interface SpinnerSchema extends BaseSchema { + type: 'spinner'; + /** + * Spinner size + * @default 'md' + */ + size?: 'sm' | 'md' | 'lg' | 'xl'; +} + +/** + * Empty state component + */ +export interface EmptySchema extends BaseSchema { + type: 'empty'; + /** + * Empty state title + */ + title?: string; + /** + * Empty state description + */ + description?: string; + /** + * Icon to display + */ + icon?: string; +} + +/** + * Sonner toast component (using sonner library) + */ +export interface SonnerSchema extends BaseSchema { + type: 'sonner'; + /** + * Toast message/title + */ + message?: string; + /** + * Toast title (alias for message) + */ + title?: string; + /** + * Toast description + */ + description?: string; + /** + * Toast variant + * @default 'default' + */ + variant?: 'default' | 'success' | 'error' | 'warning' | 'info'; + /** + * Button label to trigger toast + */ + buttonLabel?: string; + /** + * Button variant + */ + buttonVariant?: 'default' | 'secondary' | 'destructive' | 'outline' | 'ghost' | 'link'; +} + /** * Union type of all feedback schemas */ @@ -175,4 +238,7 @@ export type FeedbackSchema = | ProgressSchema | SkeletonSchema | ToastSchema - | ToasterSchema; + | ToasterSchema + | SpinnerSchema + | EmptySchema + | SonnerSchema; diff --git a/packages/types/src/form.ts b/packages/types/src/form.ts index 43eba573f..0883aff05 100644 --- a/packages/types/src/form.ts +++ b/packages/types/src/form.ts @@ -940,6 +940,126 @@ export interface LabelSchema extends BaseSchema { htmlFor?: string; } +/** + * Combobox option + */ +export interface ComboboxOption { + /** + * Option value + */ + value: string; + /** + * Option label (displayed to user) + */ + label: string; + /** + * Whether option is disabled + */ + disabled?: boolean; +} + +/** + * Combobox component (searchable select) + */ +export interface ComboboxSchema extends BaseSchema { + type: 'combobox'; + /** + * Field name for form submission + */ + name?: string; + /** + * Combobox label + */ + label?: string; + /** + * Placeholder text + */ + placeholder?: string; + /** + * Combobox options + */ + options?: ComboboxOption[]; + /** + * Default selected value + */ + defaultValue?: string; + /** + * Controlled value + */ + value?: string; + /** + * Whether field is disabled + */ + disabled?: boolean; + /** + * Help text or description + */ + description?: string; + /** + * Error message + */ + error?: string; + /** + * Change handler + */ + onChange?: (value: string) => void; +} + +/** + * Command menu item + */ +export interface CommandItem { + /** + * Item value + */ + value: string; + /** + * Item label (displayed to user) + */ + label: string; + /** + * Item icon + */ + icon?: string; +} + +/** + * Command menu group + */ +export interface CommandGroup { + /** + * Group heading + */ + heading?: string; + /** + * Group items + */ + items: CommandItem[]; +} + +/** + * Command component (command palette) + */ +export interface CommandSchema extends BaseSchema { + type: 'command'; + /** + * Placeholder text + */ + placeholder?: string; + /** + * Empty state text + */ + emptyText?: string; + /** + * Command groups + */ + groups?: CommandGroup[]; + /** + * Change handler + */ + onChange?: (value: string) => void; +} + /** * Union type of all form schemas */ @@ -958,5 +1078,7 @@ export type FormComponentSchema = | CalendarSchema | InputOTPSchema | FormSchema - | LabelSchema; + | LabelSchema + | ComboboxSchema + | CommandSchema; diff --git a/packages/types/src/layout.ts b/packages/types/src/layout.ts index 5ac68395a..b89656555 100644 --- a/packages/types/src/layout.ts +++ b/packages/types/src/layout.ts @@ -396,6 +396,34 @@ export interface ResizablePanel { content: SchemaNode | SchemaNode[]; } +/** + * Aspect ratio component + */ +export interface AspectRatioSchema extends BaseSchema { + type: 'aspect-ratio'; + /** + * Aspect ratio (width / height) + * @default 16/9 + */ + ratio?: number; + /** + * Image URL to display + */ + image?: string; + /** + * Image alt text + */ + alt?: string; + /** + * Child components (alternative to image) + */ + body?: SchemaNode | SchemaNode[]; + /** + * Child components (alternative syntax) + */ + children?: SchemaNode | SchemaNode[]; +} + /** * Page layout component * Top-level container for a page route @@ -442,19 +470,6 @@ export type LayoutSchema = | TabsSchema | ScrollAreaSchema | ResizableSchema + | AspectRatioSchema | PageSchema; -/** - * Page container component - */ -export interface PageSchema extends BaseSchema { - type: 'page'; - /** - * Page title - */ - title?: string; - /** - * Child components - */ - children?: SchemaNode | SchemaNode[]; -} diff --git a/packages/types/src/navigation.ts b/packages/types/src/navigation.ts index 6bbb3e496..5afe3db2a 100644 --- a/packages/types/src/navigation.ts +++ b/packages/types/src/navigation.ts @@ -207,7 +207,11 @@ export interface PaginationSchema extends BaseSchema { /** * Current page (1-indexed) */ - page: number; + currentPage?: number; + /** + * Legacy page property + */ + page?: number; /** * Total number of pages */ @@ -233,6 +237,99 @@ export interface PaginationSchema extends BaseSchema { onPageChange?: (page: number) => void; } +/** + * Navigation menu item + */ +export interface NavigationMenuItem { + /** + * Item label + */ + label: string; + /** + * Item href/link + */ + href?: string; + /** + * Item description + */ + description?: string; + /** + * Item icon + */ + icon?: string; + /** + * Child items + */ + children?: NavigationMenuItem[]; +} + +/** + * Navigation menu component + */ +export interface NavigationMenuSchema extends BaseSchema { + type: 'navigation-menu'; + /** + * Navigation menu items + */ + items?: NavigationMenuItem[]; + /** + * Navigation menu orientation + * @default 'horizontal' + */ + orientation?: 'horizontal' | 'vertical'; +} + +/** + * Button group button + */ +export interface ButtonGroupButton { + /** + * Button label + */ + label: string; + /** + * Button variant + */ + variant?: 'default' | 'secondary' | 'destructive' | 'outline' | 'ghost' | 'link'; + /** + * Button size + */ + size?: 'default' | 'sm' | 'lg' | 'icon'; + /** + * Whether button is disabled + */ + disabled?: boolean; + /** + * Click handler + */ + onClick?: () => void; + /** + * Button CSS class + */ + className?: string; +} + +/** + * Button group component + */ +export interface ButtonGroupSchema extends BaseSchema { + type: 'button-group'; + /** + * Button group buttons + */ + buttons?: ButtonGroupButton[]; + /** + * Default button variant + * @default 'default' + */ + variant?: 'default' | 'secondary' | 'destructive' | 'outline' | 'ghost' | 'link'; + /** + * Default button size + * @default 'default' + */ + size?: 'default' | 'sm' | 'lg' | 'icon'; +} + /** * Union type of all navigation schemas */ @@ -240,4 +337,6 @@ export type NavigationSchema = | HeaderBarSchema | SidebarSchema | BreadcrumbSchema - | PaginationSchema; + | PaginationSchema + | NavigationMenuSchema + | ButtonGroupSchema; diff --git a/packages/types/src/overlay.ts b/packages/types/src/overlay.ts index 411c0f296..76167428b 100644 --- a/packages/types/src/overlay.ts +++ b/packages/types/src/overlay.ts @@ -411,6 +411,31 @@ export interface ContextMenuSchema extends BaseSchema { children: SchemaNode | SchemaNode[]; } +/** + * Menubar menu + */ +export interface MenubarMenu { + /** + * Menu label + */ + label: string; + /** + * Menu items + */ + items: MenuItem[]; +} + +/** + * Menubar component + */ +export interface MenubarSchema extends BaseSchema { + type: 'menubar'; + /** + * Menubar menus + */ + menus?: MenubarMenu[]; +} + /** * Union type of all overlay schemas */ @@ -423,4 +448,5 @@ export type OverlaySchema = | TooltipSchema | HoverCardSchema | DropdownMenuSchema - | ContextMenuSchema; + | ContextMenuSchema + | MenubarSchema;