-
-
- {AI_QUICK_RECIPES.map((recipe, idx) => (
-
- ))}
+ {/* Render Active Real Example Component */}
+
+ {activePattern === 'metrics' &&
}
+
+ {activePattern === 'settings' &&
}
+
+ {activePattern === 'table' &&
}
+
+ {activePattern === 'deploy' && (
+
+
+
+ Canary Deployment Orchestrator
+
+ A complete interactive modal flow demonstrating Dialog, Alert, Switch, and Progress components.
+
+
+
+
+
+
+
+
-
-
-
- {AI_QUICK_RECIPES[activeRecipe].code}
-
+ )}
)
diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx
index cd5ba7a..760d2e2 100644
--- a/src/components/Icon.tsx
+++ b/src/components/Icon.tsx
@@ -1,33 +1,138 @@
-type IconProps = { name: string; size?: number }
+import type { ComponentType, SVGProps } from 'react'
+import {
+ Search,
+ Sun,
+ Moon,
+ Copy,
+ Check,
+ Eye,
+ ArrowRight,
+ ArrowLeft,
+ Menu,
+ Layers,
+ Sparkles,
+ Terminal,
+ AlertTriangle,
+ AlertCircle,
+ Info,
+ CheckCircle2,
+ X,
+ Plus,
+ Minus,
+ Trash2,
+ ExternalLink,
+ ChevronDown,
+ ChevronRight,
+ ChevronLeft,
+ ChevronUp,
+ Filter,
+ SlidersHorizontal,
+ Code2,
+ Cpu,
+ Database,
+ User,
+ Shield,
+ Folder,
+ FileText,
+ Calendar,
+ Clock,
+ RefreshCw,
+ Download,
+ Upload,
+ Settings,
+ HelpCircle,
+ type LucideIcon,
+ type LucideProps,
+} from 'lucide-react'
-const paths: Record
= {
- search: 'M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm6-2 4 4',
- sun: 'M12 3v2m0 14v2M4 12h2m12 0h2M12 8a4 4 0 1 1 0 8 4 4 0 0 1 0-8Z',
- moon: 'M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z',
- copy: 'M8 8h10v12H8zM6 16H4V4h12v2',
- check: 'm5 12 4 4L19 6',
- eye: 'M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12Zm10 3a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z',
- arrow: 'M5 12h14m-6-6 6 6-6 6',
- menu: 'M4 6h16M4 12h16M4 18h16',
- layers: 'm12 3 9 5-9 5-9-5 9-5Zm-9 9 9 5 9-5M3 16l9 5 9-5',
- sparkle: 'm12 3 2.5 6.5L21 12l-6.5 2.5L12 21l-2.5-6.5L3 12l6.5-2.5L12 3Z',
- terminal: 'm4 17 6-6-6-6m8 14h8',
+export type { LucideIcon, LucideProps }
+
+const iconMap: Record = {
+ // Northstar legacy shorthands
+ search: Search,
+ sun: Sun,
+ moon: Moon,
+ copy: Copy,
+ check: Check,
+ eye: Eye,
+ arrow: ArrowRight,
+ 'arrow-right': ArrowRight,
+ 'arrow-left': ArrowLeft,
+ menu: Menu,
+ layers: Layers,
+ sparkle: Sparkles,
+ sparkles: Sparkles,
+ terminal: Terminal,
+
+ // Common UI primitives
+ close: X,
+ x: X,
+ plus: Plus,
+ minus: Minus,
+ trash: Trash2,
+ 'trash-2': Trash2,
+ settings: Settings,
+ filter: Filter,
+ sliders: SlidersHorizontal,
+ refresh: RefreshCw,
+ download: Download,
+ upload: Upload,
+ external: ExternalLink,
+ 'external-link': ExternalLink,
+ info: Info,
+ warning: AlertTriangle,
+ 'alert-triangle': AlertTriangle,
+ alert: AlertCircle,
+ 'alert-circle': AlertCircle,
+ 'check-circle': CheckCircle2,
+ help: HelpCircle,
+ 'chevron-down': ChevronDown,
+ 'chevron-right': ChevronRight,
+ 'chevron-left': ChevronLeft,
+ 'chevron-up': ChevronUp,
+ code: Code2,
+ cpu: Cpu,
+ database: Database,
+ user: User,
+ shield: Shield,
+ folder: Folder,
+ file: FileText,
+ calendar: Calendar,
+ clock: Clock,
}
-export function Icon({ name, size = 18 }: IconProps) {
+export type IconName = keyof typeof iconMap | string
+
+export type IconProps = Omit, 'ref'> & {
+ name?: IconName
+ icon?: LucideIcon | ComponentType<{ size?: number | string; strokeWidth?: number; className?: string }>
+ size?: number | string
+ strokeWidth?: number
+ className?: string
+}
+
+/**
+ * Universal Icon component backed by lucide-react.
+ * Accepts either a shorthand name (e.g. name="sparkle", name="check")
+ * or a direct Lucide icon component (e.g. icon={Sparkles}).
+ */
+export function Icon({
+ name,
+ icon: IconComponent,
+ size = 18,
+ strokeWidth = 1.8,
+ className,
+ ...props
+}: IconProps) {
+ const Component = IconComponent || (name ? iconMap[name.toLowerCase()] : undefined) || Layers
+
return (
-
+ {...props}
+ />
)
}
diff --git a/src/components/IconMatrix.tsx b/src/components/IconMatrix.tsx
new file mode 100644
index 0000000..c27daa0
--- /dev/null
+++ b/src/components/IconMatrix.tsx
@@ -0,0 +1,233 @@
+import { useState, useMemo } from 'react'
+import {
+ Search,
+ Sun,
+ Moon,
+ Copy,
+ Check,
+ Eye,
+ ArrowRight,
+ ArrowLeft,
+ Menu,
+ Layers,
+ Sparkles,
+ Terminal,
+ AlertTriangle,
+ AlertCircle,
+ Info,
+ CheckCircle2,
+ X,
+ Plus,
+ Minus,
+ Trash2,
+ ExternalLink,
+ ChevronDown,
+ ChevronRight,
+ ChevronLeft,
+ ChevronUp,
+ Filter,
+ SlidersHorizontal,
+ Code2,
+ Cpu,
+ Database,
+ User,
+ Shield,
+ Folder,
+ FileText,
+ Calendar,
+ Clock,
+ RefreshCw,
+ Download,
+ Upload,
+ Settings,
+ HelpCircle,
+ type LucideIcon,
+} from 'lucide-react'
+import { Icon } from './Icon'
+import { Kbd } from './ui/Kbd'
+import { Badge } from './ui/Badge'
+
+type IconItem = {
+ name: string
+ pascalName: string
+ category: 'Navigation' | 'Interface' | 'System' | 'Status'
+ component: LucideIcon
+}
+
+const ICONS: IconItem[] = [
+ // Navigation
+ { name: 'arrow-right', pascalName: 'ArrowRight', category: 'Navigation', component: ArrowRight },
+ { name: 'arrow-left', pascalName: 'ArrowLeft', category: 'Navigation', component: ArrowLeft },
+ { name: 'chevron-right', pascalName: 'ChevronRight', category: 'Navigation', component: ChevronRight },
+ { name: 'chevron-left', pascalName: 'ChevronLeft', category: 'Navigation', component: ChevronLeft },
+ { name: 'chevron-down', pascalName: 'ChevronDown', category: 'Navigation', component: ChevronDown },
+ { name: 'chevron-up', pascalName: 'ChevronUp', category: 'Navigation', component: ChevronUp },
+ { name: 'external-link', pascalName: 'ExternalLink', category: 'Navigation', component: ExternalLink },
+ { name: 'menu', pascalName: 'Menu', category: 'Navigation', component: Menu },
+
+ // System & AI
+ { name: 'sparkles', pascalName: 'Sparkles', category: 'System', component: Sparkles },
+ { name: 'terminal', pascalName: 'Terminal', category: 'System', component: Terminal },
+ { name: 'code', pascalName: 'Code2', category: 'System', component: Code2 },
+ { name: 'cpu', pascalName: 'Cpu', category: 'System', component: Cpu },
+ { name: 'layers', pascalName: 'Layers', category: 'System', component: Layers },
+ { name: 'sun', pascalName: 'Sun', category: 'System', component: Sun },
+ { name: 'moon', pascalName: 'Moon', category: 'System', component: Moon },
+ { name: 'database', pascalName: 'Database', category: 'System', component: Database },
+
+ // Interface & Actions
+ { name: 'search', pascalName: 'Search', category: 'Interface', component: Search },
+ { name: 'copy', pascalName: 'Copy', category: 'Interface', component: Copy },
+ { name: 'settings', pascalName: 'Settings', category: 'Interface', component: Settings },
+ { name: 'sliders', pascalName: 'SlidersHorizontal', category: 'Interface', component: SlidersHorizontal },
+ { name: 'filter', pascalName: 'Filter', category: 'Interface', component: Filter },
+ { name: 'plus', pascalName: 'Plus', category: 'Interface', component: Plus },
+ { name: 'minus', pascalName: 'Minus', category: 'Interface', component: Minus },
+ { name: 'trash', pascalName: 'Trash2', category: 'Interface', component: Trash2 },
+ { name: 'refresh', pascalName: 'RefreshCw', category: 'Interface', component: RefreshCw },
+ { name: 'download', pascalName: 'Download', category: 'Interface', component: Download },
+ { name: 'upload', pascalName: 'Upload', category: 'Interface', component: Upload },
+ { name: 'folder', pascalName: 'Folder', category: 'Interface', component: Folder },
+ { name: 'file', pascalName: 'FileText', category: 'Interface', component: FileText },
+ { name: 'user', pascalName: 'User', category: 'Interface', component: User },
+ { name: 'calendar', pascalName: 'Calendar', category: 'Interface', component: Calendar },
+ { name: 'clock', pascalName: 'Clock', category: 'Interface', component: Clock },
+
+ // Status & Feedback
+ { name: 'check', pascalName: 'Check', category: 'Status', component: Check },
+ { name: 'check-circle', pascalName: 'CheckCircle2', category: 'Status', component: CheckCircle2 },
+ { name: 'warning', pascalName: 'AlertTriangle', category: 'Status', component: AlertTriangle },
+ { name: 'alert', pascalName: 'AlertCircle', category: 'Status', component: AlertCircle },
+ { name: 'info', pascalName: 'Info', category: 'Status', component: Info },
+ { name: 'close', pascalName: 'X', category: 'Status', component: X },
+ { name: 'shield', pascalName: 'Shield', category: 'Status', component: Shield },
+ { name: 'help', pascalName: 'HelpCircle', category: 'Status', component: HelpCircle },
+ { name: 'eye', pascalName: 'Eye', category: 'Status', component: Eye },
+]
+
+const CATEGORIES = ['All', 'Navigation', 'Interface', 'System', 'Status'] as const
+type Category = (typeof CATEGORIES)[number]
+
+export function IconMatrix() {
+ const [activeCategory, setActiveCategory] = useState('All')
+ const [search, setSearch] = useState('')
+ const [size, setSize] = useState(20)
+ const [copiedName, setCopiedName] = useState(null)
+
+ const filteredIcons = useMemo(() => {
+ return ICONS.filter(icon => {
+ const matchesCategory = activeCategory === 'All' || icon.category === activeCategory
+ const matchesSearch =
+ icon.name.toLowerCase().includes(search.toLowerCase()) ||
+ icon.pascalName.toLowerCase().includes(search.toLowerCase())
+ return matchesCategory && matchesSearch
+ })
+ }, [activeCategory, search])
+
+ const copyUsage = (icon: IconItem) => {
+ const code = `<${icon.pascalName} size={${size}} />`
+ navigator.clipboard?.writeText(code)
+ setCopiedName(icon.name)
+ setTimeout(() => setCopiedName(null), 2000)
+ }
+
+ return (
+
+ {/* Header controls */}
+
+
+
+ setSearch(e.target.value)}
+ className="icon-search-input"
+ />
+ {search && (
+
+ )}
+
+
+
+ Size:
+ {[16, 20, 24].map(s => (
+
+ ))}
+
+
+
+ {/* Category tabs */}
+
+ {CATEGORIES.map(cat => (
+
+ ))}
+
+
+ {/* Grid of icons */}
+
+ {filteredIcons.map(icon => {
+ const isCopied = copiedName === icon.name
+ return (
+
+ )
+ })}
+
+
+ {filteredIcons.length === 0 && (
+
+
+
+
+
No icons found
+
+ No icon matches "{search}". You can use any of Lucide's 1,400+ icons directly via{' '}
+ lucide-react.
+
+
+ )}
+
+ {/* Footer usage tip */}
+
+
+ lucide-react
+ Over 1,400+ SVG icons available. Fully tree-shaken and styled via currentColor.
+
+
+
+ )
+}
diff --git a/src/components/SearchPalette.tsx b/src/components/SearchPalette.tsx
new file mode 100644
index 0000000..b90ea3c
--- /dev/null
+++ b/src/components/SearchPalette.tsx
@@ -0,0 +1,271 @@
+import { useState, useEffect, useMemo, useRef } from 'react'
+import { Icon } from './Icon'
+import { Badge } from './ui/Badge'
+import { Kbd } from './ui/Kbd'
+import type { PatternId } from './AIWorkflowSection'
+
+export type SearchItem = {
+ id: string
+ title: string
+ description: string
+ category: 'Patterns' | 'Components' | 'Foundations' | 'Documentation'
+ icon: string
+ targetId: string
+ patternId?: PatternId
+}
+
+const SEARCH_INDEX: SearchItem[] = [
+ // Patterns
+ {
+ id: 'pat-metrics',
+ title: 'Metrics Dashboard',
+ description: 'Telemetry dashboard with StatCards, time range picker, and progress gauges',
+ category: 'Patterns',
+ icon: 'sparkles',
+ targetId: 'application-patterns',
+ patternId: 'metrics',
+ },
+ {
+ id: 'pat-settings',
+ title: 'Settings & Safeguards',
+ description: 'Preferences panel with API keys, toggle switches, and alert banners',
+ category: 'Patterns',
+ icon: 'settings',
+ targetId: 'application-patterns',
+ patternId: 'settings',
+ },
+ {
+ id: 'pat-table',
+ title: 'Service Monitor Table',
+ description: 'Real-time service health table with status filtering and search',
+ category: 'Patterns',
+ icon: 'layers',
+ targetId: 'application-patterns',
+ patternId: 'table',
+ },
+ {
+ id: 'pat-deploy',
+ title: 'Canary Deployment Flow',
+ description: 'Interactive rollout modal with live progress simulation',
+ category: 'Patterns',
+ icon: 'terminal',
+ targetId: 'application-patterns',
+ patternId: 'deploy',
+ },
+
+ // Components
+ {
+ id: 'comp-buttons',
+ title: 'Buttons & Action States',
+ description: 'Primary, secondary, destructive, ghost variants with resting, hover, active and focus states',
+ category: 'Components',
+ icon: 'arrow',
+ targetId: 'buttons',
+ },
+ {
+ id: 'comp-icons',
+ title: 'Lucide Icons Explorer',
+ description: 'Over 1,400+ tree-shaken SVG icons with search, category filtering and 1-click copy',
+ category: 'Components',
+ icon: 'sparkles',
+ targetId: 'icons',
+ },
+ {
+ id: 'comp-forms',
+ title: 'Form Controls & Inputs',
+ description: 'Accessible text inputs, secret key revealers, and toggle switches',
+ category: 'Components',
+ icon: 'eye',
+ targetId: 'form-controls',
+ },
+ {
+ id: 'comp-code',
+ title: 'Composable UI Primitives',
+ description: 'Zero-drift Dialog, Sheet, Tabs, Table, Alert, Progress, Switch, and StatCard',
+ category: 'Components',
+ icon: 'code',
+ targetId: 'composable-by-default',
+ },
+
+ // Foundations
+ {
+ id: 'found-colors',
+ title: 'Color Tokens (CSS Color 4 lab())',
+ description: 'Pitch black dark theme and pure white daylight light theme tokens',
+ category: 'Foundations',
+ icon: 'sun',
+ targetId: 'color-tokens',
+ },
+ {
+ id: 'found-type',
+ title: 'Typography & Scale',
+ description: 'Modular rem-based 9px to 70px scale with Manrope and DM Mono families',
+ category: 'Foundations',
+ icon: 'file',
+ targetId: 'typography',
+ },
+
+ // Documentation
+ {
+ id: 'doc-md',
+ title: 'Markdown Editorial Layer',
+ description: 'Changelogs, release notes, and documentation typography',
+ category: 'Documentation',
+ icon: 'file',
+ targetId: 'markdown-rendering',
+ },
+ {
+ id: 'doc-cm',
+ title: 'CodeMirror Editor Theme',
+ description: 'Syntax-highlighted code editor preview with line numbers and token palette',
+ category: 'Documentation',
+ icon: 'terminal',
+ targetId: 'codemirror-theme',
+ },
+]
+
+type SearchPaletteProps = {
+ isOpen: boolean
+ onClose: () => void
+ onSelect: (item: SearchItem) => void
+ initialQuery?: string
+}
+
+export function SearchPalette({ isOpen, onClose, onSelect, initialQuery = '' }: SearchPaletteProps) {
+ const [query, setQuery] = useState(initialQuery)
+ const [selectedIndex, setSelectedIndex] = useState(0)
+ const inputRef = useRef(null)
+
+ useEffect(() => {
+ if (isOpen) {
+ inputRef.current?.focus()
+ }
+ }, [isOpen, initialQuery])
+
+ const results = useMemo(() => {
+ if (!query.trim()) return SEARCH_INDEX
+ const q = query.toLowerCase()
+ return SEARCH_INDEX.filter(
+ item =>
+ item.title.toLowerCase().includes(q) ||
+ item.description.toLowerCase().includes(q) ||
+ item.category.toLowerCase().includes(q),
+ )
+ }, [query])
+
+ // Keyboard navigation
+ useEffect(() => {
+ if (!isOpen) return
+
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ e.preventDefault()
+ onClose()
+ } else if (e.key === 'ArrowDown') {
+ e.preventDefault()
+ setSelectedIndex(prev => (prev + 1) % (results.length || 1))
+ } else if (e.key === 'ArrowUp') {
+ e.preventDefault()
+ setSelectedIndex(prev => (prev - 1 + results.length) % (results.length || 1))
+ } else if (e.key === 'Enter' && results[selectedIndex]) {
+ e.preventDefault()
+ onSelect(results[selectedIndex])
+ onClose()
+ }
+ }
+
+ window.addEventListener('keydown', handleKeyDown)
+ return () => window.removeEventListener('keydown', handleKeyDown)
+ }, [isOpen, results, selectedIndex, onClose, onSelect])
+
+ if (!isOpen) return null
+
+ return (
+
+
e.stopPropagation()}>
+ {/* Search Input Bar */}
+
+
+ {
+ setQuery(e.target.value)
+ setSelectedIndex(0)
+ }}
+ />
+ {query && (
+
+ )}
+ ESC
+
+
+ {/* Results List */}
+
+ {results.length > 0 ? (
+
+ {results.map((item, idx) => {
+ const isSelected = idx === selectedIndex
+ return (
+
{
+ onSelect(item)
+ onClose()
+ }}
+ onMouseEnter={() => setSelectedIndex(idx)}
+ >
+
+
+
+
+
+ {item.title}
+ {item.category}
+
+
{item.description}
+
+ {isSelected && (
+
+ ↵
+
+ )}
+
+ )
+ })}
+
+ ) : (
+
+
+
No results found for “{query}”
+
Try searching for “Buttons”, “Metrics”, “Tokens”, or “Icons”.
+
+ )}
+
+
+ {/* Footer info */}
+
+
+ ↑
+ ↓
+ to navigate
+
+
+ ↵
+ to select
+
+
+ ESC
+ to close
+
+
+
+
+ )
+}
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index 5ec38e0..1869776 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -1,12 +1,12 @@
import { Icon } from './Icon'
-export type NavigationItem = { label: string; count?: number }
+export type NavigationItem = { label: string; count?: number; id?: string }
export type NavigationGroup = { label: string; items: NavigationItem[] }
type SidebarProps = {
active: string
groups: NavigationGroup[]
- onSelect: (item: string) => void
+ onSelect: (item: NavigationItem) => void
}
export function Sidebar({ active, groups, onSelect }: SidebarProps) {
@@ -34,7 +34,7 @@ export function Sidebar({ active, groups, onSelect }: SidebarProps) {