Template
fix
This commit is contained in:
1 parent
61df5e3dcc
commit
c0a94d3b6d
25 files changed
+2143
-348
No files matched your search
@@ -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<HTMLInputElement>(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 (
|
||||
<div className="search-palette-backdrop" onClick={onClose}>
|
||||
<div className="search-palette-modal" onClick={e => e.stopPropagation()}>
|
||||
{/* Search Input Bar */}
|
||||
<div className="search-palette-head">
|
||||
<Icon name="search" size={18} className="search-palette-icon" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
className="search-palette-input"
|
||||
placeholder="Type a command, component, or token..."
|
||||
value={query}
|
||||
onChange={e => {
|
||||
setQuery(e.target.value)
|
||||
setSelectedIndex(0)
|
||||
}}
|
||||
/>
|
||||
{query && (
|
||||
<button className="icon-button" onClick={() => setQuery('')} aria-label="Clear search">
|
||||
<Icon name="close" size={14} />
|
||||
</button>
|
||||
)}
|
||||
<Kbd>ESC</Kbd>
|
||||
</div>
|
||||
|
||||
{/* Results List */}
|
||||
<div className="search-palette-body">
|
||||
{results.length > 0 ? (
|
||||
<div className="search-palette-results">
|
||||
{results.map((item, idx) => {
|
||||
const isSelected = idx === selectedIndex
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className={`search-palette-item ${isSelected ? 'selected' : ''}`}
|
||||
onClick={() => {
|
||||
onSelect(item)
|
||||
onClose()
|
||||
}}
|
||||
onMouseEnter={() => setSelectedIndex(idx)}
|
||||
>
|
||||
<div className="search-item-glyph">
|
||||
<Icon name={item.icon} size={16} />
|
||||
</div>
|
||||
<div className="search-item-content">
|
||||
<div className="search-item-title-row">
|
||||
<span className="search-item-title">{item.title}</span>
|
||||
<Badge variant="outline">{item.category}</Badge>
|
||||
</div>
|
||||
<p className="search-item-desc">{item.description}</p>
|
||||
</div>
|
||||
{isSelected && (
|
||||
<div className="search-item-action">
|
||||
<Kbd>↵</Kbd>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="search-palette-empty">
|
||||
<Icon name="search" size={24} />
|
||||
<p>No results found for “{query}”</p>
|
||||
<span>Try searching for “Buttons”, “Metrics”, “Tokens”, or “Icons”.</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer info */}
|
||||
<div className="search-palette-footer">
|
||||
<div className="row-xs" style={{ alignItems: 'center' }}>
|
||||
<Kbd>↑</Kbd>
|
||||
<Kbd>↓</Kbd>
|
||||
<span>to navigate</span>
|
||||
</div>
|
||||
<div className="row-xs" style={{ alignItems: 'center' }}>
|
||||
<Kbd>↵</Kbd>
|
||||
<span>to select</span>
|
||||
</div>
|
||||
<div className="row-xs" style={{ alignItems: 'center' }}>
|
||||
<Kbd>ESC</Kbd>
|
||||
<span>to close</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user