diff --git a/ATOMIC_LIBRARY_COMPLETION.md b/ATOMIC_LIBRARY_COMPLETION.md new file mode 100644 index 0000000..e4f56a7 --- /dev/null +++ b/ATOMIC_LIBRARY_COMPLETION.md @@ -0,0 +1,92 @@ +# Atomic Component Library - Completion Summary + +## Overview +The atomic component library has been expanded and completed with 50+ production-ready components, all fully integrated into the CodeForge application. + +## New Components Added + +### Core Components (10) +1. **Button** - Full-featured button with icon support, loading states +2. **Badge** - Variant-based badge with icon support +3. **Switch** - Toggle switch with label and description +4. **Separator** - Visual divider for horizontal/vertical layouts +5. **HoverCard** - Popover content on hover +6. **Calendar** - Date selection component +7. **ButtonGroup** - Grouped button layout +8. **CommandPalette** - Command/search palette dialog +9. **ContextMenu** - Right-click context menu +10. **Form** - Form wrapper with validation support + +### Advanced Components (10) +11. **DataTable** - Generic data table with sorting +12. **DatePicker** - Date picker with calendar popup +13. **RangeSlider** - Dual-thumb range selection +14. **InfoPanel** - Variant-based information panels +15. **ResponsiveGrid** - Smart responsive grid layout +16. **Flex** - Flexible layout component +17. **CircularProgress** - Circular progress indicator +18. **AvatarGroup** - Grouped avatar display with overflow + +## Enhanced Components +- **EmptyState** - Now uses Stack and atomic typography +- **StatCard** - Uses Card, Stack, and Text atoms +- **ToolbarButton** - Simplified using IconButton and Tooltip + +## Integration +✅ All components exported from `@/components/atoms` +✅ AtomicLibraryShowcase component created +✅ Added to component registry as `AtomicLibraryShowcase` +✅ Registered in pages.json with route `atomic-library` +✅ Keyboard shortcut: `Ctrl+Shift+A` +✅ Navigation icon: Atom (⚛️) + +## Component Categories + +### Layout (8) +- Container, Section, Stack, Spacer, Divider, Grid, ResponsiveGrid, Flex + +### Typography (5) +- Heading, Text, Link, Code, Kbd + +### Buttons & Actions (5) +- Button, ActionButton, IconButton, ConfirmButton, ButtonGroup + +### Forms (11) +- Input, TextArea, PasswordInput, SearchInput, FilterInput, Select, Checkbox, RadioGroup, Toggle, Switch, Slider, RangeSlider, DatePicker + +### Badges & Indicators (7) +- Badge, StatusBadge, Chip, Dot, CountBadge, DataSourceBadge, BindingIndicator + +### Feedback (6) +- Alert, Spinner, LoadingSpinner, ProgressBar, CircularProgress, Skeleton, Tooltip, InfoPanel + +### Display (10) +- Avatar, AvatarGroup, Card, Image, ColorSwatch, MetricCard, StatCard, HoverCard, DataTable + +### Interactive (8) +- Tabs, Accordion, Menu, Modal, Drawer, Popover, ContextMenu, CommandPalette, Calendar + +### Utility (5) +- Timestamp, CopyButton, FileUpload, BreadcrumbNav, IconText, Rating, Timeline, Stepper + +## Design Principles Followed +1. ✅ **Consistency** - All components use same design tokens +2. ✅ **Accessibility** - ARIA attributes and semantic HTML +3. ✅ **Flexibility** - Comprehensive prop APIs +4. ✅ **Performance** - Lightweight implementations +5. ✅ **Type Safety** - Full TypeScript support + +## Usage +Navigate to "Atomic Components" page via: +- Sidebar navigation +- Keyboard shortcut: `Ctrl+Shift+A` +- URL: `/atomic-library` + +The showcase page demonstrates all components with live examples organized by category. + +## Next Steps +1. Add interactive component playground +2. Create component composition templates +3. Add live code examples with copy functionality +4. Build component search and filter +5. Add prop documentation viewer diff --git a/src/components/AtomicLibraryShowcase.tsx b/src/components/AtomicLibraryShowcase.tsx new file mode 100644 index 0000000..bc66640 --- /dev/null +++ b/src/components/AtomicLibraryShowcase.tsx @@ -0,0 +1,499 @@ +import { useState } from 'react' +import { + Button, + Badge, + Switch, + Separator, + HoverCard, + Calendar, + ButtonGroup, + DatePicker, + RangeSlider, + InfoPanel, + ResponsiveGrid, + Flex, + CircularProgress, + AvatarGroup, + Heading, + Text, + Stack, + Card, + Chip, + Dot, + Tooltip, + Alert, + ProgressBar, + Skeleton, + Code, + Kbd, + Avatar, + Link, + Container, + Section, + Spacer, + Rating, + ColorSwatch, + MetricCard, + CountBadge, + FilterInput, + BasicPageHeader, + IconButton, + ActionButton, + StatusBadge, +} from '@/components/atoms' +import { + Heart, + Star, + Plus, + Trash, + Download, + ShoppingCart, + User, + Bell, + CheckCircle, + Info, + WarningCircle, + XCircle, +} from '@phosphor-icons/react' + +export function AtomicLibraryShowcase() { + const [switchChecked, setSwitchChecked] = useState(false) + const [selectedDate, setSelectedDate] = useState() + const [rangeValue, setRangeValue] = useState<[number, number]>([20, 80]) + const [filterValue, setFilterValue] = useState('') + const [rating, setRating] = useState(3) + + return ( + + + + +
+ Buttons & Actions + + +
+ Button Variants + + + + + + + + + + +
+ +
+ Button Group + + + + + +
+ +
+ Icon Buttons + + } variant="default" /> + } variant="secondary" /> + } variant="outline" /> + } variant="destructive" /> + +
+ +
+ Action Buttons + + } + label="Like" + onClick={() => {}} + tooltip="Like this item" + /> + } + label="Favorite" + onClick={() => {}} + variant="outline" + /> + +
+
+
+ +
+ Badges & Indicators + + +
+ Badges + + Default + Secondary + Destructive + Outline + }>With Icon + Small + Large + +
+ +
+ Status Badges + + + + + + + + +
+ +
+ Chips + + React + TypeScript + Tailwind + {}}>Removable + +
+ +
+ Dots + + + + + + + + +
+ +
+ Count Badge + +
+ Notifications + +
+
+ Messages + +
+
+
+
+
+ +
+ Typography + + +
+ Headings + + Heading 1 + Heading 2 + Heading 3 + Heading 4 + Heading 5 + Heading 6 + +
+ +
+ Text Variants + + Body text - regular content + Caption text - smaller descriptive text + Muted text - less important information + Small text - compact information + +
+ +
+ Inline Elements + + Press Ctrl + K to search + Run npm install to get started + Visit our documentation to learn more + +
+
+
+ +
+ Form Controls + + +
+ Switch + +
+ +
+ Date Picker + +
+ +
+ Filter Input + +
+ +
+ Rating + +
+
+ + + +
+ Range Slider + +
+
+ +
+ Progress & Loading + + +
+ Progress Bar + + + +
+ +
+ Circular Progress + + + + + +
+ +
+ Skeleton Loading + + + + + +
+
+
+ +
+ Feedback + + + + This is an informational alert message. + + + Your changes have been saved successfully. + + + Please review your input before submitting. + + + Something went wrong. Please try again. + + + + + + }> + This is an informational panel with helpful content. + + }> + Operation completed successfully! + + }> + Please proceed with caution. + + }> + An error has occurred. + + + +
+ +
+ Avatars & User Elements + + +
+ Avatar Sizes + + + + + + + +
+ +
+ Avatar Group + +
+
+
+ +
+ Cards & Metrics + + + } + trend={{ value: 12, direction: 'up' }} + /> + } + trend={{ value: 5, direction: 'up' }} + /> + } + /> + +
+ +
+ Interactive Elements + + +
+ Hover Card + Hover over me + } + > + + Additional Information + + This is extra content shown in a hover card. + + + +
+ +
+ Tooltip + + + +
+ +
+ Color Swatches + + + + + + +
+
+
+ +
+ Layout Components + + +
+ Responsive Grid + + {[1, 2, 3, 4, 5, 6, 7, 8].map((i) => ( + + Item {i} + + ))} + +
+ +
+ Flex Layout + + Left Content + Center + + +
+ +
+ Stack Layout + + Stacked Item 1 + Stacked Item 2 + Stacked Item 3 + +
+
+
+ +
+ Summary + + }> + Atomic Component Library Complete! + + The atomic component library includes 50+ production-ready components covering buttons, + badges, typography, forms, progress indicators, feedback, avatars, cards, and layout utilities. + All components are fully typed, accessible, and follow the design system. + + +
+
+
+ ) +} diff --git a/src/components/atoms/AvatarGroup.tsx b/src/components/atoms/AvatarGroup.tsx new file mode 100644 index 0000000..da7b7ed --- /dev/null +++ b/src/components/atoms/AvatarGroup.tsx @@ -0,0 +1,60 @@ +import { cn } from '@/lib/utils' + +interface AvatarGroupProps { + avatars: { + src?: string + alt: string + fallback: string + }[] + max?: number + size?: 'xs' | 'sm' | 'md' | 'lg' + className?: string +} + +const sizeClasses = { + xs: 'h-6 w-6 text-xs', + sm: 'h-8 w-8 text-xs', + md: 'h-10 w-10 text-sm', + lg: 'h-12 w-12 text-base', +} + +export function AvatarGroup({ + avatars, + max = 5, + size = 'md', + className, +}: AvatarGroupProps) { + const displayAvatars = avatars.slice(0, max) + const remainingCount = Math.max(avatars.length - max, 0) + + return ( +
+ {displayAvatars.map((avatar, index) => ( +
+ {avatar.src ? ( + {avatar.alt} + ) : ( + {avatar.fallback} + )} +
+ ))} + {remainingCount > 0 && ( +
+ +{remainingCount} +
+ )} +
+ ) +} diff --git a/src/components/atoms/Badge.tsx b/src/components/atoms/Badge.tsx new file mode 100644 index 0000000..0ac64be --- /dev/null +++ b/src/components/atoms/Badge.tsx @@ -0,0 +1,39 @@ +import { Badge as ShadcnBadge } from '@/components/ui/badge' +import { cn } from '@/lib/utils' +import { ReactNode } from 'react' + +interface BadgeProps { + children: ReactNode + variant?: 'default' | 'secondary' | 'destructive' | 'outline' + size?: 'sm' | 'md' | 'lg' + icon?: ReactNode + className?: string +} + +const sizeClasses = { + sm: 'text-xs px-2 py-0.5', + md: 'text-sm px-2.5 py-0.5', + lg: 'text-base px-3 py-1', +} + +export function Badge({ + children, + variant = 'default', + size = 'md', + icon, + className, +}: BadgeProps) { + return ( + + {icon && {icon}} + {children} + + ) +} diff --git a/src/components/atoms/Button.tsx b/src/components/atoms/Button.tsx new file mode 100644 index 0000000..ed0ce5b --- /dev/null +++ b/src/components/atoms/Button.tsx @@ -0,0 +1,43 @@ +import { Button as ShadcnButton, ButtonProps as ShadcnButtonProps } from '@/components/ui/button' +import { cn } from '@/lib/utils' +import { ReactNode } from 'react' + +export interface ButtonProps extends ShadcnButtonProps { + children: ReactNode + leftIcon?: ReactNode + rightIcon?: ReactNode + loading?: boolean + fullWidth?: boolean +} + +export function Button({ + children, + leftIcon, + rightIcon, + loading, + fullWidth, + disabled, + className, + ...props +}: ButtonProps) { + return ( + + {loading ? ( +
+
+ {children} +
+ ) : ( +
+ {leftIcon && {leftIcon}} + {children} + {rightIcon && {rightIcon}} +
+ )} + + ) +} diff --git a/src/components/atoms/ButtonGroup.tsx b/src/components/atoms/ButtonGroup.tsx new file mode 100644 index 0000000..1141b89 --- /dev/null +++ b/src/components/atoms/ButtonGroup.tsx @@ -0,0 +1,33 @@ +import { cn } from '@/lib/utils' +import { ReactNode } from 'react' + +interface ButtonGroupProps { + children: ReactNode + orientation?: 'horizontal' | 'vertical' + className?: string +} + +export function ButtonGroup({ + children, + orientation = 'horizontal', + className, +}: ButtonGroupProps) { + return ( +
button]:rounded-none', + '[&>button:first-child]:rounded-l-md', + '[&>button:last-child]:rounded-r-md', + orientation === 'vertical' && '[&>button:first-child]:rounded-t-md [&>button:first-child]:rounded-l-none', + orientation === 'vertical' && '[&>button:last-child]:rounded-b-md [&>button:last-child]:rounded-r-none', + '[&>button:not(:last-child)]:border-r-0', + orientation === 'vertical' && '[&>button:not(:last-child)]:border-b-0 [&>button:not(:last-child)]:border-r', + className + )} + > + {children} +
+ ) +} diff --git a/src/components/atoms/Calendar.tsx b/src/components/atoms/Calendar.tsx new file mode 100644 index 0000000..68c6a86 --- /dev/null +++ b/src/components/atoms/Calendar.tsx @@ -0,0 +1,28 @@ +import { Calendar as ShadcnCalendar } from '@/components/ui/calendar' +import { cn } from '@/lib/utils' + +interface CalendarProps { + selected?: Date + onSelect?: (date: Date | undefined) => void + mode?: 'single' | 'multiple' | 'range' + disabled?: Date | ((date: Date) => boolean) + className?: string +} + +export function Calendar({ + selected, + onSelect, + mode = 'single', + disabled, + className, +}: CalendarProps) { + return ( + + ) +} diff --git a/src/components/atoms/CircularProgress.tsx b/src/components/atoms/CircularProgress.tsx new file mode 100644 index 0000000..412f937 --- /dev/null +++ b/src/components/atoms/CircularProgress.tsx @@ -0,0 +1,67 @@ +import { Progress } from '@/components/ui/progress' +import { cn } from '@/lib/utils' + +interface CircularProgressProps { + value: number + max?: number + size?: 'sm' | 'md' | 'lg' | 'xl' + showLabel?: boolean + strokeWidth?: number + className?: string +} + +const sizeClasses = { + sm: { dimension: 48, stroke: 4, fontSize: 'text-xs' }, + md: { dimension: 64, stroke: 5, fontSize: 'text-sm' }, + lg: { dimension: 96, stroke: 6, fontSize: 'text-base' }, + xl: { dimension: 128, stroke: 8, fontSize: 'text-lg' }, +} + +export function CircularProgress({ + value, + max = 100, + size = 'md', + showLabel = true, + strokeWidth, + className, +}: CircularProgressProps) { + const { dimension, stroke, fontSize } = sizeClasses[size] + const actualStroke = strokeWidth || stroke + const percentage = Math.min((value / max) * 100, 100) + const radius = (dimension - actualStroke) / 2 + const circumference = radius * 2 * Math.PI + const offset = circumference - (percentage / 100) * circumference + + return ( +
+ + + + + {showLabel && ( + + {Math.round(percentage)}% + + )} +
+ ) +} diff --git a/src/components/atoms/CommandPalette.tsx b/src/components/atoms/CommandPalette.tsx new file mode 100644 index 0000000..fa68e28 --- /dev/null +++ b/src/components/atoms/CommandPalette.tsx @@ -0,0 +1,62 @@ +import { + Command, + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from '@/components/ui/command' +import { ReactNode } from 'react' + +interface CommandOption { + value: string + label: string + icon?: ReactNode + onSelect?: () => void +} + +interface CommandPaletteProps { + open: boolean + onOpenChange: (open: boolean) => void + placeholder?: string + emptyMessage?: string + groups: { + heading?: string + items: CommandOption[] + }[] +} + +export function CommandPalette({ + open, + onOpenChange, + placeholder = 'Type a command or search...', + emptyMessage = 'No results found.', + groups, +}: CommandPaletteProps) { + return ( + + + + {emptyMessage} + {groups.map((group, groupIndex) => ( + + {group.items.map((item) => ( + { + item.onSelect?.() + onOpenChange(false) + }} + > + {item.icon && {item.icon}} + {item.label} + + ))} + + ))} + + + ) +} diff --git a/src/components/atoms/ContextMenu.tsx b/src/components/atoms/ContextMenu.tsx new file mode 100644 index 0000000..20eacd4 --- /dev/null +++ b/src/components/atoms/ContextMenu.tsx @@ -0,0 +1,73 @@ +import { + ContextMenu as ShadcnContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuTrigger, + ContextMenuSeparator, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, +} from '@/components/ui/context-menu' +import { ReactNode } from 'react' + +export interface ContextMenuItemType { + label: string + icon?: ReactNode + shortcut?: string + onSelect?: () => void + disabled?: boolean + separator?: boolean + submenu?: ContextMenuItemType[] +} + +interface ContextMenuProps { + trigger: ReactNode + items: ContextMenuItemType[] +} + +export function ContextMenu({ trigger, items }: ContextMenuProps) { + const renderItems = (menuItems: ContextMenuItemType[]) => { + return menuItems.map((item, index) => { + if (item.separator) { + return + } + + if (item.submenu && item.submenu.length > 0) { + return ( + + + {item.icon && {item.icon}} + {item.label} + + + {renderItems(item.submenu)} + + + ) + } + + return ( + + {item.icon && {item.icon}} + {item.label} + {item.shortcut && ( + + {item.shortcut} + + )} + + ) + }) + } + + return ( + + {trigger} + {renderItems(items)} + + ) +} diff --git a/src/components/atoms/DataTable.tsx b/src/components/atoms/DataTable.tsx new file mode 100644 index 0000000..b9f170a --- /dev/null +++ b/src/components/atoms/DataTable.tsx @@ -0,0 +1,77 @@ +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table' +import { cn } from '@/lib/utils' +import { ReactNode } from 'react' + +export interface Column { + key: string + header: string | ReactNode + cell?: (item: T) => ReactNode + sortable?: boolean + width?: string +} + +interface DataTableProps { + data: T[] + columns: Column[] + onRowClick?: (item: T) => void + emptyMessage?: string + className?: string +} + +export function DataTable>({ + data, + columns, + onRowClick, + emptyMessage = 'No data available', + className, +}: DataTableProps) { + return ( +
+ + + + {columns.map((column) => ( + + {column.header} + + ))} + + + + {data.length === 0 ? ( + + + {emptyMessage} + + + ) : ( + data.map((item, rowIndex) => ( + onRowClick?.(item)} + className={cn(onRowClick && 'cursor-pointer')} + > + {columns.map((column) => ( + + {column.cell ? column.cell(item) : item[column.key]} + + ))} + + )) + )} + +
+
+ ) +} diff --git a/src/components/atoms/DatePicker.tsx b/src/components/atoms/DatePicker.tsx new file mode 100644 index 0000000..4dc95a0 --- /dev/null +++ b/src/components/atoms/DatePicker.tsx @@ -0,0 +1,48 @@ +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' +import { Button } from '@/components/ui/button' +import { Calendar } from '@/components/ui/calendar' +import { CalendarBlank } from '@phosphor-icons/react' +import { format } from 'date-fns' +import { cn } from '@/lib/utils' + +interface DatePickerProps { + value?: Date + onChange: (date: Date | undefined) => void + placeholder?: string + disabled?: boolean + className?: string +} + +export function DatePicker({ + value, + onChange, + placeholder = 'Pick a date', + disabled, + className, +}: DatePickerProps) { + return ( + + + + + + + + + ) +} diff --git a/src/components/atoms/Flex.tsx b/src/components/atoms/Flex.tsx new file mode 100644 index 0000000..7ac4d32 --- /dev/null +++ b/src/components/atoms/Flex.tsx @@ -0,0 +1,83 @@ +import { cn } from '@/lib/utils' +import { ReactNode } from 'react' + +interface FlexProps { + children: ReactNode + direction?: 'row' | 'col' | 'row-reverse' | 'col-reverse' + align?: 'start' | 'center' | 'end' | 'stretch' | 'baseline' + justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly' + gap?: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' + wrap?: 'wrap' | 'nowrap' | 'wrap-reverse' + grow?: boolean + shrink?: boolean + className?: string +} + +const directionClasses = { + row: 'flex-row', + col: 'flex-col', + 'row-reverse': 'flex-row-reverse', + 'col-reverse': 'flex-col-reverse', +} + +const alignClasses = { + start: 'items-start', + center: 'items-center', + end: 'items-end', + stretch: 'items-stretch', + baseline: 'items-baseline', +} + +const justifyClasses = { + start: 'justify-start', + center: 'justify-center', + end: 'justify-end', + between: 'justify-between', + around: 'justify-around', + evenly: 'justify-evenly', +} + +const gapClasses = { + none: 'gap-0', + xs: 'gap-1', + sm: 'gap-2', + md: 'gap-4', + lg: 'gap-6', + xl: 'gap-8', +} + +const wrapClasses = { + wrap: 'flex-wrap', + nowrap: 'flex-nowrap', + 'wrap-reverse': 'flex-wrap-reverse', +} + +export function Flex({ + children, + direction = 'row', + align = 'stretch', + justify = 'start', + gap = 'md', + wrap = 'nowrap', + grow = false, + shrink = false, + className, +}: FlexProps) { + return ( +
+ {children} +
+ ) +} diff --git a/src/components/atoms/Form.tsx b/src/components/atoms/Form.tsx new file mode 100644 index 0000000..0cf6686 --- /dev/null +++ b/src/components/atoms/Form.tsx @@ -0,0 +1,30 @@ +import { + Form as ShadcnForm, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form' +import { UseFormReturn } from 'react-hook-form' +import { ReactNode } from 'react' + +interface FormProps { + form: UseFormReturn + onSubmit: (values: any) => void | Promise + children: ReactNode + className?: string +} + +export function Form({ form, onSubmit, children, className }: FormProps) { + return ( + +
+ {children} +
+
+ ) +} + +export { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } diff --git a/src/components/atoms/HoverCard.tsx b/src/components/atoms/HoverCard.tsx new file mode 100644 index 0000000..338fbbd --- /dev/null +++ b/src/components/atoms/HoverCard.tsx @@ -0,0 +1,32 @@ +import { + HoverCard as ShadcnHoverCard, + HoverCardContent, + HoverCardTrigger, +} from '@/components/ui/hover-card' +import { ReactNode } from 'react' +import { cn } from '@/lib/utils' + +interface HoverCardProps { + trigger: ReactNode + children: ReactNode + side?: 'top' | 'right' | 'bottom' | 'left' + align?: 'start' | 'center' | 'end' + className?: string +} + +export function HoverCard({ + trigger, + children, + side = 'bottom', + align = 'center', + className, +}: HoverCardProps) { + return ( + + {trigger} + + {children} + + + ) +} diff --git a/src/components/atoms/InfoPanel.tsx b/src/components/atoms/InfoPanel.tsx new file mode 100644 index 0000000..df64750 --- /dev/null +++ b/src/components/atoms/InfoPanel.tsx @@ -0,0 +1,44 @@ +import { cn } from '@/lib/utils' +import { ReactNode } from 'react' + +interface InfoPanelProps { + children: ReactNode + variant?: 'info' | 'warning' | 'success' | 'error' | 'default' + title?: string + icon?: ReactNode + className?: string +} + +const variantClasses = { + default: 'bg-card border-border', + info: 'bg-blue-500/10 border-blue-500/20 text-blue-700 dark:text-blue-300', + warning: 'bg-yellow-500/10 border-yellow-500/20 text-yellow-700 dark:text-yellow-300', + success: 'bg-green-500/10 border-green-500/20 text-green-700 dark:text-green-300', + error: 'bg-red-500/10 border-red-500/20 text-red-700 dark:text-red-300', +} + +export function InfoPanel({ + children, + variant = 'default', + title, + icon, + className, +}: InfoPanelProps) { + return ( +
+ {(title || icon) && ( +
+ {icon &&
{icon}
} + {title &&
{title}
} +
+ )} +
{children}
+
+ ) +} diff --git a/src/components/atoms/PageHeader.tsx b/src/components/atoms/PageHeader.tsx index 3971288..cc53c6f 100644 --- a/src/components/atoms/PageHeader.tsx +++ b/src/components/atoms/PageHeader.tsx @@ -1,13 +1,13 @@ import { cn } from '@/lib/utils' -interface PageHeaderProps { +interface BasicPageHeaderProps { title: string description?: string actions?: React.ReactNode className?: string } -export function PageHeader({ title, description, actions, className }: PageHeaderProps) { +export function BasicPageHeader({ title, description, actions, className }: BasicPageHeaderProps) { return (
diff --git a/src/components/atoms/RangeSlider.tsx b/src/components/atoms/RangeSlider.tsx new file mode 100644 index 0000000..347a5cd --- /dev/null +++ b/src/components/atoms/RangeSlider.tsx @@ -0,0 +1,47 @@ +import { Slider } from '@/components/ui/slider' +import { cn } from '@/lib/utils' + +interface RangeSliderProps { + value: [number, number] + onChange: (value: [number, number]) => void + min?: number + max?: number + step?: number + label?: string + showValue?: boolean + className?: string +} + +export function RangeSlider({ + value, + onChange, + min = 0, + max = 100, + step = 1, + label, + showValue = true, + className, +}: RangeSliderProps) { + return ( +
+ {(label || showValue) && ( +
+ {label && {label}} + {showValue && ( + + {value[0]} - {value[1]} + + )} +
+ )} + +
+ ) +} diff --git a/src/components/atoms/ResponsiveGrid.tsx b/src/components/atoms/ResponsiveGrid.tsx new file mode 100644 index 0000000..e4e4073 --- /dev/null +++ b/src/components/atoms/ResponsiveGrid.tsx @@ -0,0 +1,57 @@ +import { cn } from '@/lib/utils' +import { ReactNode } from 'react' + +interface GridProps { + children: ReactNode + columns?: 1 | 2 | 3 | 4 | 5 | 6 + gap?: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' + responsive?: boolean + className?: string +} + +const columnClasses = { + 1: 'grid-cols-1', + 2: 'grid-cols-2', + 3: 'grid-cols-3', + 4: 'grid-cols-4', + 5: 'grid-cols-5', + 6: 'grid-cols-6', +} + +const gapClasses = { + none: 'gap-0', + xs: 'gap-1', + sm: 'gap-2', + md: 'gap-4', + lg: 'gap-6', + xl: 'gap-8', +} + +const responsiveClasses = { + 2: 'grid-cols-1 sm:grid-cols-2', + 3: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3', + 4: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4', + 5: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5', + 6: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6', +} + +export function ResponsiveGrid({ + children, + columns = 3, + gap = 'md', + responsive = true, + className, +}: GridProps) { + return ( +
1 ? responsiveClasses[columns] : columnClasses[columns], + gapClasses[gap], + className + )} + > + {children} +
+ ) +} diff --git a/src/components/atoms/Separator.tsx b/src/components/atoms/Separator.tsx new file mode 100644 index 0000000..4fe5e2a --- /dev/null +++ b/src/components/atoms/Separator.tsx @@ -0,0 +1,22 @@ +import { Separator as ShadcnSeparator } from '@/components/ui/separator' +import { cn } from '@/lib/utils' + +interface SeparatorProps { + orientation?: 'horizontal' | 'vertical' + decorative?: boolean + className?: string +} + +export function Separator({ + orientation = 'horizontal', + decorative = true, + className, +}: SeparatorProps) { + return ( + + ) +} diff --git a/src/components/atoms/Switch.tsx b/src/components/atoms/Switch.tsx new file mode 100644 index 0000000..9790b4b --- /dev/null +++ b/src/components/atoms/Switch.tsx @@ -0,0 +1,50 @@ +import { Switch as ShadcnSwitch } from '@/components/ui/switch' +import { Label } from '@/components/ui/label' +import { cn } from '@/lib/utils' + +interface SwitchProps { + checked: boolean + onCheckedChange: (checked: boolean) => void + label?: string + description?: string + disabled?: boolean + className?: string +} + +export function Switch({ + checked, + onCheckedChange, + label, + description, + disabled, + className, +}: SwitchProps) { + if (!label) { + return ( + + ) + } + + return ( +
+
+ + {description && ( +

{description}

+ )} +
+ +
+ ) +} diff --git a/src/components/atoms/index.ts b/src/components/atoms/index.ts index 3397e48..4f37d92 100644 --- a/src/components/atoms/index.ts +++ b/src/components/atoms/index.ts @@ -25,6 +25,11 @@ export { EmptyState } from './EmptyState' export { DetailRow } from './DetailRow' export { CompletionCard } from './CompletionCard' export { TipsCard } from './TipsCard' +export { CountBadge } from './CountBadge' +export { ConfirmButton } from './ConfirmButton' +export { FilterInput } from './FilterInput' +export { BasicPageHeader } from './PageHeader' +export { MetricCard } from './MetricCard' export { Link } from './Link' export { Divider } from './Divider' @@ -75,3 +80,24 @@ export { Select } from './Select' export { Modal } from './Modal' export { Drawer } from './Drawer' export { Table } from './Table' + +export { Button } from './Button' +export { Badge } from './Badge' +export { Switch } from './Switch' +export { Separator } from './Separator' +export { HoverCard } from './HoverCard' +export { Calendar } from './Calendar' +export { ButtonGroup } from './ButtonGroup' +export { CommandPalette } from './CommandPalette' +export { ContextMenu } from './ContextMenu' +export type { ContextMenuItemType } from './ContextMenu' +export { DataTable } from './DataTable' +export type { Column } from './DataTable' +export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from './Form' +export { DatePicker } from './DatePicker' +export { RangeSlider } from './RangeSlider' +export { InfoPanel } from './InfoPanel' +export { ResponsiveGrid } from './ResponsiveGrid' +export { Flex } from './Flex' +export { CircularProgress } from './CircularProgress' +export { AvatarGroup } from './AvatarGroup' diff --git a/src/components/molecules/EmptyState.tsx b/src/components/molecules/EmptyState.tsx index 0eb9b72..b4feafa 100644 --- a/src/components/molecules/EmptyState.tsx +++ b/src/components/molecules/EmptyState.tsx @@ -1,4 +1,4 @@ -import { EmptyStateIcon } from '@/components/atoms' +import { EmptyStateIcon, Stack, Heading, Text } from '@/components/atoms' interface EmptyStateProps { icon: React.ReactNode @@ -9,15 +9,21 @@ interface EmptyStateProps { export function EmptyState({ icon, title, description, action }: EmptyStateProps) { return ( -
+ -
-

{title}

+ + {title} {description && ( -

{description}

+ {description} )} -
+
{action &&
{action}
} -
+ ) } diff --git a/src/components/molecules/StatCard.tsx b/src/components/molecules/StatCard.tsx index 7d9bf2c..478746d 100644 --- a/src/components/molecules/StatCard.tsx +++ b/src/components/molecules/StatCard.tsx @@ -1,5 +1,4 @@ -import { Card } from '@/components/ui/card' -import { IconWrapper } from '@/components/atoms' +import { Card, IconWrapper, Stack, Text } from '@/components/atoms' interface StatCardProps { icon: React.ReactNode @@ -17,17 +16,17 @@ export function StatCard({ icon, label, value, variant = 'default' }: StatCardPr return ( -
+ -
-

{label}

-

{value}

-
-
+ + {label} + {value} + +
) } diff --git a/src/components/molecules/ToolbarButton.tsx b/src/components/molecules/ToolbarButton.tsx index b259cd7..3ed3fb3 100644 --- a/src/components/molecules/ToolbarButton.tsx +++ b/src/components/molecules/ToolbarButton.tsx @@ -1,11 +1,11 @@ -import { Button } from '@/components/ui/button' -import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' +import { IconButton, Tooltip } from '@/components/atoms' +import { TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' interface ToolbarButtonProps { icon: React.ReactNode label: string onClick: () => void - variant?: 'default' | 'outline' | 'ghost' | 'destructive' + variant?: 'default' | 'secondary' | 'outline' | 'ghost' | 'destructive' disabled?: boolean className?: string } @@ -19,19 +19,14 @@ export function ToolbarButton({ className = '', }: ToolbarButtonProps) { return ( - - - - - {label} + + ) } diff --git a/src/config/pages.json b/src/config/pages.json index d677474..bc2bd75 100644 --- a/src/config/pages.json +++ b/src/config/pages.json @@ -365,6 +365,16 @@ "toggleKey": "dockerDebugger", "order": 25, "props": {} + }, + { + "id": "atomic-library", + "title": "Atomic Components", + "icon": "Atom", + "component": "AtomicLibraryShowcase", + "enabled": true, + "shortcut": "ctrl+shift+a", + "order": 26, + "props": {} } ] } diff --git a/src/lib/component-registry.ts b/src/lib/component-registry.ts index 0890626..e8b59a7 100644 --- a/src/lib/component-registry.ts +++ b/src/lib/component-registry.ts @@ -156,6 +156,11 @@ export const ComponentRegistry = { () => import('@/components/DockerBuildDebugger').then(m => ({ default: m.DockerBuildDebugger })), 'DockerBuildDebugger' ), + + AtomicLibraryShowcase: lazyWithPreload( + () => import('@/components/AtomicLibraryShowcase').then(m => ({ default: m.AtomicLibraryShowcase })), + 'AtomicLibraryShowcase' + ), } as const export const DialogRegistry = { diff --git a/src/lib/navigation-config.tsx b/src/lib/navigation-config.tsx index cc456a1..4a0dd42 100644 --- a/src/lib/navigation-config.tsx +++ b/src/lib/navigation-config.tsx @@ -17,6 +17,7 @@ import { Faders, Lightbulb, PencilRuler, + Atom, } from '@phosphor-icons/react' import { FeatureToggles } from '@/types/project' @@ -152,6 +153,11 @@ export const tabInfo: Record = { icon: , description: 'JSON-driven UI examples', }, + 'atomic-library': { + title: 'Atomic Components', + icon: , + description: 'Comprehensive atomic component library', + }, } export const navigationGroups: NavigationGroup[] = [