diff --git a/src/components/DocumentationView.tsx b/src/components/DocumentationView.tsx
index 58d29dc..7fbdcc9 100644
--- a/src/components/DocumentationView.tsx
+++ b/src/components/DocumentationView.tsx
@@ -7,25 +7,20 @@ import {
FileCode,
GitBranch,
MagnifyingGlass,
- MapPin,
- PaintBrush,
- Rocket
+ GitBranch,
+ FileCode,
+ Sparkle,
+ CheckCircle
} from '@phosphor-icons/react'
-import { ReadmeTab } from './DocumentationView/ReadmeTab'
-import { RoadmapTab } from './DocumentationView/RoadmapTab'
-import { AgentsTab } from './DocumentationView/AgentsTab'
-import { PwaTab } from './DocumentationView/PwaTab'
-import { SassTab } from './DocumentationView/SassTab'
-import { CicdTab } from './DocumentationView/CicdTab'
-
-const tabs = [
- { value: 'readme', label: 'README', icon: },
- { value: 'roadmap', label: 'Roadmap', icon: },
- { value: 'agents', label: 'Agents Files', icon: },
- { value: 'pwa', label: 'PWA Guide', icon: },
- { value: 'sass', label: 'Sass Styles Guide', icon: },
- { value: 'cicd', label: 'CI/CD Guide', icon: }
-]
+import { AIFeatureCard } from './DocumentationView/AIFeatureCard'
+import { AgentFileItem } from './DocumentationView/AgentFileItem'
+import { AnimationItem } from './DocumentationView/AnimationItem'
+import { CICDPlatformItem } from './DocumentationView/CICDPlatformItem'
+import { FeatureItem } from './DocumentationView/FeatureItem'
+import { IntegrationPoint } from './DocumentationView/IntegrationPoint'
+import { PipelineStageCard } from './DocumentationView/PipelineStageCard'
+import { RoadmapItem } from './DocumentationView/RoadmapItem'
+import { SassComponentItem } from './DocumentationView/SassComponentItem'
export function DocumentationView() {
const [activeTab, setActiveTab] = useState('readme')
diff --git a/src/components/DocumentationView/AIFeatureCard.tsx b/src/components/DocumentationView/AIFeatureCard.tsx
new file mode 100644
index 0000000..3cfc368
--- /dev/null
+++ b/src/components/DocumentationView/AIFeatureCard.tsx
@@ -0,0 +1,18 @@
+import { Card, CardContent } from '@/components/ui/card'
+import { Sparkle } from '@phosphor-icons/react'
+
+export function AIFeatureCard({ title, description }: { title: string; description: string }) {
+ return (
+
+
+
+
+
+
{title}
+
{description}
+
+
+
+
+ )
+}
diff --git a/src/components/DocumentationView/AgentFileItem.tsx b/src/components/DocumentationView/AgentFileItem.tsx
new file mode 100644
index 0000000..66d98da
--- /dev/null
+++ b/src/components/DocumentationView/AgentFileItem.tsx
@@ -0,0 +1,32 @@
+import { FileCode, CheckCircle } from '@phosphor-icons/react'
+
+export function AgentFileItem({ filename, path, description, features }: {
+ filename: string
+ path: string
+ description: string
+ features: string[]
+}) {
+ return (
+
+
+
+
+ {filename}
+
+
{path}
+
{description}
+
+
+
Key Features:
+
+ {features.map((feature, idx) => (
+ -
+
+ {feature}
+
+ ))}
+
+
+
+ )
+}
diff --git a/src/components/DocumentationView/AnimationItem.tsx b/src/components/DocumentationView/AnimationItem.tsx
new file mode 100644
index 0000000..b19077d
--- /dev/null
+++ b/src/components/DocumentationView/AnimationItem.tsx
@@ -0,0 +1,8 @@
+export function AnimationItem({ name, description }: { name: string; description: string }) {
+ return (
+
+
{name}
+
{description}
+
+ )
+}
diff --git a/src/components/DocumentationView/CICDPlatformItem.tsx b/src/components/DocumentationView/CICDPlatformItem.tsx
new file mode 100644
index 0000000..b19839a
--- /dev/null
+++ b/src/components/DocumentationView/CICDPlatformItem.tsx
@@ -0,0 +1,32 @@
+import { CheckCircle, GitBranch } from '@phosphor-icons/react'
+
+export function CICDPlatformItem({ name, file, description, features }: {
+ name: string
+ file: string
+ description: string
+ features: string[]
+}) {
+ return (
+
+
+
+
+
{name}
+
+
{file}
+
{description}
+
+
+
Key Features:
+
+ {features.map((feature, idx) => (
+ -
+
+ {feature}
+
+ ))}
+
+
+
+ )
+}
diff --git a/src/components/DocumentationView/FeatureItem.tsx b/src/components/DocumentationView/FeatureItem.tsx
new file mode 100644
index 0000000..74368fa
--- /dev/null
+++ b/src/components/DocumentationView/FeatureItem.tsx
@@ -0,0 +1,13 @@
+import type { ReactNode } from 'react'
+
+export function FeatureItem({ icon, title, description }: { icon: ReactNode; title: string; description: string }) {
+ return (
+
+
{icon}
+
+
{title}
+
{description}
+
+
+ )
+}
diff --git a/src/components/DocumentationView/IntegrationPoint.tsx b/src/components/DocumentationView/IntegrationPoint.tsx
new file mode 100644
index 0000000..490822c
--- /dev/null
+++ b/src/components/DocumentationView/IntegrationPoint.tsx
@@ -0,0 +1,20 @@
+import { Sparkle } from '@phosphor-icons/react'
+
+export function IntegrationPoint({ component, capabilities }: { component: string; capabilities: string[] }) {
+ return (
+
+
+
+ {component}
+
+
+ {capabilities.map((capability, idx) => (
+ -
+ •
+ {capability}
+
+ ))}
+
+
+ )
+}
diff --git a/src/components/DocumentationView/PipelineStageCard.tsx b/src/components/DocumentationView/PipelineStageCard.tsx
new file mode 100644
index 0000000..434b630
--- /dev/null
+++ b/src/components/DocumentationView/PipelineStageCard.tsx
@@ -0,0 +1,24 @@
+import { Card, CardContent } from '@/components/ui/card'
+import { Badge } from '@/components/ui/badge'
+
+export function PipelineStageCard({ stage, description, duration }: {
+ stage: string
+ description: string
+ duration: string
+}) {
+ return (
+
+
+
+
+
{stage}
+
{description}
+
+
+ {duration}
+
+
+
+
+ )
+}
diff --git a/src/components/DocumentationView/ReadmeTab.tsx b/src/components/DocumentationView/ReadmeTab.tsx
index b1c1efc..7d8ce82 100644
--- a/src/components/DocumentationView/ReadmeTab.tsx
+++ b/src/components/DocumentationView/ReadmeTab.tsx
@@ -1,7 +1,8 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Separator } from '@/components/ui/separator'
import { Code, Database, Tree, PaintBrush, Flask, Play, Cube, Wrench, Gear, Rocket, Lightbulb, CheckCircle } from '@phosphor-icons/react'
-import { FeatureItem, AIFeatureCard } from './FeatureItems'
+import { AIFeatureCard } from './AIFeatureCard'
+import { FeatureItem } from './FeatureItem'
import readmeData from '@/data/documentation/readme-data.json'
const Sparkle = ({ size }: { size: number }) => ✨
diff --git a/src/components/DocumentationView/SassComponentItem.tsx b/src/components/DocumentationView/SassComponentItem.tsx
new file mode 100644
index 0000000..a34905b
--- /dev/null
+++ b/src/components/DocumentationView/SassComponentItem.tsx
@@ -0,0 +1,13 @@
+export function SassComponentItem({ name, classes, description }: { name: string; classes: string[]; description: string }) {
+ return (
+
+
{name}
+
{description}
+
+ {classes.map((cls, idx) => (
+ {cls}
+ ))}
+
+
+ )
+}
diff --git a/src/components/GlobalSearch.tsx b/src/components/GlobalSearch.tsx
index e32a19c..f5e2f90 100644
--- a/src/components/GlobalSearch.tsx
+++ b/src/components/GlobalSearch.tsx
@@ -1,60 +1,19 @@
-import { useState, useMemo, useEffect } from 'react'
-import { useKV } from '@/hooks/use-kv'
+import { CommandDialog, CommandInput, CommandList } from '@/components/ui/command'
+import { EmptyState } from '@/components/global-search/EmptyState'
+import { RecentSearches } from '@/components/global-search/RecentSearches'
+import { SearchResults } from '@/components/global-search/SearchResults'
+import { useGlobalSearchData } from '@/components/global-search/useGlobalSearchData'
import {
- CommandDialog,
- CommandEmpty,
- CommandGroup,
- CommandInput,
- CommandItem,
- CommandList,
- CommandSeparator,
-} from '@/components/ui/command'
-import {
- Code,
- Database,
- Tree,
- PaintBrush,
- Flask,
- Play,
- BookOpen,
- Cube,
- FlowArrow,
- Wrench,
- FileText,
- Gear,
- DeviceMobile,
- Faders,
- ChartBar,
- Image,
- File,
- Folder,
- MagnifyingGlass,
- ClockCounterClockwise,
- X,
- Lightbulb,
-} from '@phosphor-icons/react'
-import { ProjectFile, PrismaModel, ComponentNode, ComponentTree, Workflow, Lambda, PlaywrightTest, StorybookStory, UnitTest } from '@/types/project'
-import { Badge } from '@/components/ui/badge'
-import { Button } from '@/components/ui/button'
-
-interface SearchResult {
- id: string
- title: string
- subtitle?: string
- category: string
- icon: React.ReactNode
- action: () => void
- tags?: string[]
-}
-
-interface SearchHistoryItem {
- id: string
- query: string
- timestamp: number
- resultId?: string
- resultTitle?: string
- resultCategory?: string
-}
+ ComponentNode,
+ ComponentTree,
+ Lambda,
+ PlaywrightTest,
+ PrismaModel,
+ ProjectFile,
+ StorybookStory,
+ UnitTest,
+ Workflow,
+} from '@/types/project'
interface GlobalSearchProps {
open: boolean
@@ -87,361 +46,18 @@ export function GlobalSearch({
onNavigate,
onFileSelect,
}: GlobalSearchProps) {
- const [searchQuery, setSearchQuery] = useState('')
- const [searchHistory, setSearchHistory] = useKV('search-history', [])
-
- useEffect(() => {
- if (!open) {
- setSearchQuery('')
- }
- }, [open])
-
- const addToHistory = (query: string, result?: SearchResult) => {
- if (!query.trim()) return
-
- const historyItem: SearchHistoryItem = {
- id: `history-${Date.now()}`,
- query: query.trim(),
- timestamp: Date.now(),
- resultId: result?.id,
- resultTitle: result?.title,
- resultCategory: result?.category,
- }
-
- setSearchHistory((currentHistory) => {
- const filtered = (currentHistory || []).filter(
- (item) => item.query.toLowerCase() !== query.toLowerCase()
- )
- return [historyItem, ...filtered].slice(0, 20)
- })
- }
-
- const clearHistory = () => {
- setSearchHistory([])
- }
-
- const removeHistoryItem = (id: string) => {
- setSearchHistory((currentHistory) =>
- (currentHistory || []).filter((item) => item.id !== id)
- )
- }
-
- const allResults = useMemo(() => {
- const results: SearchResult[] = []
-
- results.push({
- id: 'nav-dashboard',
- title: 'Dashboard',
- subtitle: 'View project overview and statistics',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('dashboard'),
- tags: ['home', 'overview', 'stats', 'metrics'],
- })
-
- results.push({
- id: 'nav-code',
- title: 'Code Editor',
- subtitle: 'Edit project files with Monaco',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('code'),
- tags: ['editor', 'monaco', 'typescript', 'javascript'],
- })
-
- results.push({
- id: 'nav-models',
- title: 'Models',
- subtitle: 'Design Prisma database models',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('models'),
- tags: ['prisma', 'database', 'schema', 'orm'],
- })
-
- results.push({
- id: 'nav-components',
- title: 'Components',
- subtitle: 'Build React components',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('components'),
- tags: ['react', 'mui', 'ui', 'design'],
- })
-
- results.push({
- id: 'nav-component-trees',
- title: 'Component Trees',
- subtitle: 'Manage component hierarchies',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('component-trees'),
- tags: ['hierarchy', 'structure', 'layout'],
- })
-
- results.push({
- id: 'nav-workflows',
- title: 'Workflows',
- subtitle: 'Design n8n-style workflows',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('workflows'),
- tags: ['automation', 'n8n', 'flow', 'pipeline'],
- })
-
- results.push({
- id: 'nav-lambdas',
- title: 'Lambdas',
- subtitle: 'Create serverless functions',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('lambdas'),
- tags: ['serverless', 'functions', 'api'],
- })
-
- results.push({
- id: 'nav-styling',
- title: 'Styling',
- subtitle: 'Design themes and colors',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('styling'),
- tags: ['theme', 'colors', 'css', 'design'],
- })
-
- results.push({
- id: 'nav-flask',
- title: 'Flask API',
- subtitle: 'Configure Flask backend',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('flask'),
- tags: ['python', 'backend', 'api', 'rest'],
- })
-
- results.push({
- id: 'nav-playwright',
- title: 'Playwright Tests',
- subtitle: 'E2E testing configuration',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('playwright'),
- tags: ['testing', 'e2e', 'automation'],
- })
-
- results.push({
- id: 'nav-storybook',
- title: 'Storybook',
- subtitle: 'Component documentation',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('storybook'),
- tags: ['documentation', 'components', 'stories'],
- })
-
- results.push({
- id: 'nav-unit-tests',
- title: 'Unit Tests',
- subtitle: 'Configure unit testing',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('unit-tests'),
- tags: ['testing', 'jest', 'vitest'],
- })
-
- results.push({
- id: 'nav-errors',
- title: 'Error Repair',
- subtitle: 'Auto-detect and fix errors',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('errors'),
- tags: ['debugging', 'errors', 'fixes'],
- })
-
- results.push({
- id: 'nav-docs',
- title: 'Documentation',
- subtitle: 'View project documentation',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('docs'),
- tags: ['readme', 'guide', 'help'],
- })
-
- results.push({
- id: 'nav-sass',
- title: 'Sass Styles',
- subtitle: 'Custom Sass styling',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('sass'),
- tags: ['sass', 'scss', 'styles', 'css'],
- })
-
- results.push({
- id: 'nav-favicon',
- title: 'Favicon Designer',
- subtitle: 'Design app icons',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('favicon'),
- tags: ['icon', 'logo', 'design'],
- })
-
- results.push({
- id: 'nav-settings',
- title: 'Settings',
- subtitle: 'Configure Next.js and npm',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('settings'),
- tags: ['config', 'nextjs', 'npm', 'packages'],
- })
-
- results.push({
- id: 'nav-pwa',
- title: 'PWA Settings',
- subtitle: 'Progressive Web App config',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('pwa'),
- tags: ['mobile', 'install', 'offline'],
- })
-
- results.push({
- id: 'nav-features',
- title: 'Feature Toggles',
- subtitle: 'Enable or disable features',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('features'),
- tags: ['settings', 'toggles', 'enable'],
- })
-
- results.push({
- id: 'nav-ideas',
- title: 'Feature Ideas',
- subtitle: 'Brainstorm and organize ideas',
- category: 'Navigation',
- icon: ,
- action: () => onNavigate('ideas'),
- tags: ['brainstorm', 'ideas', 'planning'],
- })
-
- files.forEach((file) => {
- results.push({
- id: `file-${file.id}`,
- title: file.name,
- subtitle: file.path,
- category: 'Files',
- icon: ,
- action: () => {
- onNavigate('code')
- onFileSelect(file.id)
- },
- tags: [file.language, file.path, 'code', 'file'],
- })
- })
-
- models.forEach((model) => {
- results.push({
- id: `model-${model.id}`,
- title: model.name,
- subtitle: `${model.fields.length} fields`,
- category: 'Models',
- icon: ,
- action: () => onNavigate('models', model.id),
- tags: ['prisma', 'database', 'schema', model.name.toLowerCase()],
- })
- })
-
- components.forEach((component) => {
- results.push({
- id: `component-${component.id}`,
- title: component.name,
- subtitle: component.type,
- category: 'Components',
- icon: ,
- action: () => onNavigate('components', component.id),
- tags: ['react', 'component', component.type.toLowerCase(), component.name.toLowerCase()],
- })
- })
-
- componentTrees.forEach((tree) => {
- results.push({
- id: `tree-${tree.id}`,
- title: tree.name,
- subtitle: tree.description || `${tree.rootNodes.length} root nodes`,
- category: 'Component Trees',
- icon: ,
- action: () => onNavigate('component-trees', tree.id),
- tags: ['hierarchy', 'structure', tree.name.toLowerCase()],
- })
- })
-
- workflows.forEach((workflow) => {
- results.push({
- id: `workflow-${workflow.id}`,
- title: workflow.name,
- subtitle: workflow.description || `${workflow.nodes.length} nodes`,
- category: 'Workflows',
- icon: ,
- action: () => onNavigate('workflows', workflow.id),
- tags: ['automation', 'flow', workflow.name.toLowerCase()],
- })
- })
-
- lambdas.forEach((lambda) => {
- results.push({
- id: `lambda-${lambda.id}`,
- title: lambda.name,
- subtitle: lambda.description || lambda.runtime,
- category: 'Lambdas',
- icon: ,
- action: () => onNavigate('lambdas', lambda.id),
- tags: ['serverless', 'function', lambda.runtime, lambda.name.toLowerCase()],
- })
- })
-
- playwrightTests.forEach((test) => {
- results.push({
- id: `playwright-${test.id}`,
- title: test.name,
- subtitle: test.description,
- category: 'Playwright Tests',
- icon: ,
- action: () => onNavigate('playwright', test.id),
- tags: ['testing', 'e2e', test.name.toLowerCase()],
- })
- })
-
- storybookStories.forEach((story) => {
- results.push({
- id: `storybook-${story.id}`,
- title: story.storyName,
- subtitle: story.componentName,
- category: 'Storybook Stories',
- icon: ,
- action: () => onNavigate('storybook', story.id),
- tags: ['documentation', 'story', story.componentName.toLowerCase()],
- })
- })
-
- unitTests.forEach((test) => {
- results.push({
- id: `unit-test-${test.id}`,
- title: test.name,
- subtitle: test.description,
- category: 'Unit Tests',
- icon: ,
- action: () => onNavigate('unit-tests', test.id),
- tags: ['testing', 'unit', test.name.toLowerCase()],
- })
- })
-
- return results
- }, [
+ const {
+ searchQuery,
+ setSearchQuery,
+ recentSearches,
+ groupedResults,
+ clearHistory,
+ removeHistoryItem,
+ handleSelect,
+ handleHistorySelect,
+ } = useGlobalSearchData({
+ open,
+ onOpenChange,
files,
models,
components,
@@ -453,201 +69,26 @@ export function GlobalSearch({
unitTests,
onNavigate,
onFileSelect,
- ])
-
- const filteredResults = useMemo(() => {
- if (!searchQuery.trim()) {
- return []
- }
-
- const query = searchQuery.toLowerCase().trim()
- const queryWords = query.split(/\s+/)
-
- return allResults
- .map((result) => {
- let score = 0
- const titleLower = result.title.toLowerCase()
- const subtitleLower = result.subtitle?.toLowerCase() || ''
- const categoryLower = result.category.toLowerCase()
- const tagsLower = result.tags?.map(t => t.toLowerCase()) || []
-
- if (titleLower === query) score += 100
- else if (titleLower.startsWith(query)) score += 50
- else if (titleLower.includes(query)) score += 30
-
- if (subtitleLower.includes(query)) score += 20
- if (categoryLower.includes(query)) score += 15
-
- tagsLower.forEach(tag => {
- if (tag === query) score += 40
- else if (tag.includes(query)) score += 10
- })
-
- queryWords.forEach(word => {
- if (titleLower.includes(word)) score += 5
- if (subtitleLower.includes(word)) score += 3
- if (tagsLower.some(tag => tag.includes(word))) score += 2
- })
-
- return { result, score }
- })
- .filter(({ score }) => score > 0)
- .sort((a, b) => b.score - a.score)
- .slice(0, 50)
- .map(({ result }) => result)
- }, [allResults, searchQuery])
-
- const recentSearches = useMemo(() => {
- const safeHistory = searchHistory || []
- return safeHistory
- .slice(0, 10)
- .map((item) => {
- const result = allResults.find((r) => r.id === item.resultId)
- return {
- historyItem: item,
- result,
- }
- })
- }, [searchHistory, allResults])
-
- const groupedResults = useMemo(() => {
- const groups: Record = {}
- filteredResults.forEach((result) => {
- if (!groups[result.category]) {
- groups[result.category] = []
- }
- groups[result.category].push(result)
- })
- return groups
- }, [filteredResults])
-
- const handleSelect = (result: SearchResult) => {
- addToHistory(searchQuery, result)
- result.action()
- onOpenChange(false)
- }
-
- const handleHistorySelect = (historyItem: SearchHistoryItem, result?: SearchResult) => {
- if (result) {
- result.action()
- onOpenChange(false)
- } else {
- setSearchQuery(historyItem.query)
- }
- }
+ })
return (
-
-
-
-
-
- {!searchQuery.trim() && recentSearches.length > 0 && (
- <>
-
- Recent Searches
-
-
- }
- >
- {recentSearches.map(({ historyItem, result }) => (
- handleHistorySelect(historyItem, result)}
- className="flex items-center gap-3 px-4 py-3 cursor-pointer group"
- >
-
-
-
-
- {result ? (
- <>
-
{result.title}
-
- Searched: {historyItem.query}
-
- >
- ) : (
- <>
-
{historyItem.query}
-
- Search again
-
- >
- )}
-
- {result && (
-
- {result.category}
-
- )}
-
-
- ))}
-
-
- >
+
+ {!searchQuery.trim() && (
+
)}
-
- {Object.entries(groupedResults).map(([category, results], index) => (
-
- {index > 0 &&
}
-
- {results.map((result) => (
- handleSelect(result)}
- className="flex items-center gap-3 px-4 py-3 cursor-pointer"
- >
-
- {result.icon}
-
-
-
{result.title}
- {result.subtitle && (
-
- {result.subtitle}
-
- )}
-
-
- {category}
-
-
- ))}
-
-
- ))}
+
)
diff --git a/src/components/global-search/EmptyState.tsx b/src/components/global-search/EmptyState.tsx
new file mode 100644
index 0000000..6973576
--- /dev/null
+++ b/src/components/global-search/EmptyState.tsx
@@ -0,0 +1,13 @@
+import { MagnifyingGlass } from '@phosphor-icons/react'
+import { CommandEmpty } from '@/components/ui/command'
+
+export function EmptyState() {
+ return (
+
+
+
+ )
+}
diff --git a/src/components/global-search/RecentSearches.tsx b/src/components/global-search/RecentSearches.tsx
new file mode 100644
index 0000000..99b2728
--- /dev/null
+++ b/src/components/global-search/RecentSearches.tsx
@@ -0,0 +1,106 @@
+import { ClockCounterClockwise, X } from '@phosphor-icons/react'
+import { Badge } from '@/components/ui/badge'
+import { Button } from '@/components/ui/button'
+import { CommandGroup, CommandItem, CommandSeparator } from '@/components/ui/command'
+
+interface SearchHistoryItem {
+ id: string
+ query: string
+ timestamp: number
+ resultId?: string
+ resultTitle?: string
+ resultCategory?: string
+}
+
+interface SearchResult {
+ id: string
+ title: string
+ subtitle?: string
+ category: string
+ icon: React.ReactNode
+ action: () => void
+ tags?: string[]
+}
+
+interface RecentSearchesProps {
+ recentSearches: Array<{ historyItem: SearchHistoryItem; result?: SearchResult }>
+ onClear: () => void
+ onSelect: (historyItem: SearchHistoryItem, result?: SearchResult) => void
+ onRemove: (id: string) => void
+}
+
+export function RecentSearches({
+ recentSearches,
+ onClear,
+ onSelect,
+ onRemove,
+}: RecentSearchesProps) {
+ if (recentSearches.length === 0) {
+ return null
+ }
+
+ return (
+ <>
+
+ Recent Searches
+
+
+ }
+ >
+ {recentSearches.map(({ historyItem, result }) => (
+ onSelect(historyItem, result)}
+ className="flex items-center gap-3 px-4 py-3 cursor-pointer group"
+ >
+
+
+
+
+ {result ? (
+ <>
+
{result.title}
+
+ Searched: {historyItem.query}
+
+ >
+ ) : (
+ <>
+
{historyItem.query}
+
Search again
+ >
+ )}
+
+ {result && (
+
+ {result.category}
+
+ )}
+
+
+ ))}
+
+
+ >
+ )
+}
diff --git a/src/components/global-search/SearchResults.tsx b/src/components/global-search/SearchResults.tsx
new file mode 100644
index 0000000..87a8dc1
--- /dev/null
+++ b/src/components/global-search/SearchResults.tsx
@@ -0,0 +1,54 @@
+import { Badge } from '@/components/ui/badge'
+import { CommandGroup, CommandItem, CommandSeparator } from '@/components/ui/command'
+
+interface SearchResult {
+ id: string
+ title: string
+ subtitle?: string
+ category: string
+ icon: React.ReactNode
+ action: () => void
+ tags?: string[]
+}
+
+interface SearchResultsProps {
+ groupedResults: Record
+ onSelect: (result: SearchResult) => void
+}
+
+export function SearchResults({ groupedResults, onSelect }: SearchResultsProps) {
+ return (
+ <>
+ {Object.entries(groupedResults).map(([category, results], index) => (
+
+ {index > 0 &&
}
+
+ {results.map((result) => (
+ onSelect(result)}
+ className="flex items-center gap-3 px-4 py-3 cursor-pointer"
+ >
+
+ {result.icon}
+
+
+
{result.title}
+ {result.subtitle && (
+
+ {result.subtitle}
+
+ )}
+
+
+ {category}
+
+
+ ))}
+
+
+ ))}
+ >
+ )
+}
diff --git a/src/components/global-search/useGlobalSearchData.tsx b/src/components/global-search/useGlobalSearchData.tsx
new file mode 100644
index 0000000..cb217b3
--- /dev/null
+++ b/src/components/global-search/useGlobalSearchData.tsx
@@ -0,0 +1,391 @@
+import { useEffect, useMemo, useState } from 'react'
+import { useKV } from '@/hooks/use-kv'
+import {
+ BookOpen,
+ ChartBar,
+ Code,
+ Cube,
+ Database,
+ DeviceMobile,
+ Faders,
+ File,
+ FileText,
+ Flask,
+ FlowArrow,
+ Folder,
+ Gear,
+ Image,
+ Lightbulb,
+ PaintBrush,
+ Play,
+ Tree,
+ Wrench,
+} from '@phosphor-icons/react'
+import {
+ ComponentNode,
+ ComponentTree,
+ Lambda,
+ PlaywrightTest,
+ PrismaModel,
+ ProjectFile,
+ StorybookStory,
+ UnitTest,
+ Workflow,
+} from '@/types/project'
+import navigationData from '@/data/global-search.json'
+
+export interface SearchResult {
+ id: string
+ title: string
+ subtitle?: string
+ category: string
+ icon: React.ReactNode
+ action: () => void
+ tags?: string[]
+}
+
+export interface SearchHistoryItem {
+ id: string
+ query: string
+ timestamp: number
+ resultId?: string
+ resultTitle?: string
+ resultCategory?: string
+}
+
+const navigationIconMap = {
+ BookOpen,
+ ChartBar,
+ Code,
+ Cube,
+ Database,
+ DeviceMobile,
+ Faders,
+ FileText,
+ Flask,
+ FlowArrow,
+ Gear,
+ Image,
+ Lightbulb,
+ PaintBrush,
+ Play,
+ Tree,
+ Wrench,
+}
+
+type NavigationIconName = keyof typeof navigationIconMap
+
+interface NavigationMeta {
+ id: string
+ title: string
+ subtitle: string
+ category: string
+ icon: NavigationIconName
+ tab: string
+ tags: string[]
+}
+
+interface UseGlobalSearchDataProps {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ files: ProjectFile[]
+ models: PrismaModel[]
+ components: ComponentNode[]
+ componentTrees: ComponentTree[]
+ workflows: Workflow[]
+ lambdas: Lambda[]
+ playwrightTests: PlaywrightTest[]
+ storybookStories: StorybookStory[]
+ unitTests: UnitTest[]
+ onNavigate: (tab: string, itemId?: string) => void
+ onFileSelect: (fileId: string) => void
+}
+
+export function useGlobalSearchData({
+ open,
+ onOpenChange,
+ files,
+ models,
+ components,
+ componentTrees,
+ workflows,
+ lambdas,
+ playwrightTests,
+ storybookStories,
+ unitTests,
+ onNavigate,
+ onFileSelect,
+}: UseGlobalSearchDataProps) {
+ const [searchQuery, setSearchQuery] = useState('')
+ const [searchHistory, setSearchHistory] = useKV('search-history', [])
+
+ useEffect(() => {
+ if (!open) {
+ setSearchQuery('')
+ }
+ }, [open])
+
+ const addToHistory = (query: string, result?: SearchResult) => {
+ if (!query.trim()) return
+
+ const historyItem: SearchHistoryItem = {
+ id: `history-${Date.now()}`,
+ query: query.trim(),
+ timestamp: Date.now(),
+ resultId: result?.id,
+ resultTitle: result?.title,
+ resultCategory: result?.category,
+ }
+
+ setSearchHistory((currentHistory) => {
+ const filtered = (currentHistory || []).filter(
+ (item) => item.query.toLowerCase() !== query.toLowerCase()
+ )
+ return [historyItem, ...filtered].slice(0, 20)
+ })
+ }
+
+ const clearHistory = () => {
+ setSearchHistory([])
+ }
+
+ const removeHistoryItem = (id: string) => {
+ setSearchHistory((currentHistory) =>
+ (currentHistory || []).filter((item) => item.id !== id)
+ )
+ }
+
+ const allResults = useMemo(() => {
+ const results: SearchResult[] = []
+ const navigationResults = (navigationData as NavigationMeta[]).map((item) => {
+ const Icon = navigationIconMap[item.icon]
+
+ return {
+ id: item.id,
+ title: item.title,
+ subtitle: item.subtitle,
+ category: item.category,
+ icon: ,
+ action: () => onNavigate(item.tab),
+ tags: item.tags,
+ }
+ })
+
+ results.push(...navigationResults)
+
+ files.forEach((file) => {
+ results.push({
+ id: `file-${file.id}`,
+ title: file.name,
+ subtitle: file.path,
+ category: 'Files',
+ icon: ,
+ action: () => {
+ onNavigate('code')
+ onFileSelect(file.id)
+ },
+ tags: [file.language, file.path, 'code', 'file'],
+ })
+ })
+
+ models.forEach((model) => {
+ results.push({
+ id: `model-${model.id}`,
+ title: model.name,
+ subtitle: `${model.fields.length} fields`,
+ category: 'Models',
+ icon: ,
+ action: () => onNavigate('models', model.id),
+ tags: ['prisma', 'database', 'schema', model.name.toLowerCase()],
+ })
+ })
+
+ components.forEach((component) => {
+ results.push({
+ id: `component-${component.id}`,
+ title: component.name,
+ subtitle: component.type,
+ category: 'Components',
+ icon: ,
+ action: () => onNavigate('components', component.id),
+ tags: ['react', 'component', component.type.toLowerCase(), component.name.toLowerCase()],
+ })
+ })
+
+ componentTrees.forEach((tree) => {
+ results.push({
+ id: `tree-${tree.id}`,
+ title: tree.name,
+ subtitle: tree.description || `${tree.rootNodes.length} root nodes`,
+ category: 'Component Trees',
+ icon: ,
+ action: () => onNavigate('component-trees', tree.id),
+ tags: ['hierarchy', 'structure', tree.name.toLowerCase()],
+ })
+ })
+
+ workflows.forEach((workflow) => {
+ results.push({
+ id: `workflow-${workflow.id}`,
+ title: workflow.name,
+ subtitle: workflow.description || `${workflow.nodes.length} nodes`,
+ category: 'Workflows',
+ icon: ,
+ action: () => onNavigate('workflows', workflow.id),
+ tags: ['automation', 'flow', workflow.name.toLowerCase()],
+ })
+ })
+
+ lambdas.forEach((lambda) => {
+ results.push({
+ id: `lambda-${lambda.id}`,
+ title: lambda.name,
+ subtitle: lambda.description || lambda.runtime,
+ category: 'Lambdas',
+ icon: ,
+ action: () => onNavigate('lambdas', lambda.id),
+ tags: ['serverless', 'function', lambda.runtime, lambda.name.toLowerCase()],
+ })
+ })
+
+ playwrightTests.forEach((test) => {
+ results.push({
+ id: `playwright-${test.id}`,
+ title: test.name,
+ subtitle: test.description,
+ category: 'Playwright Tests',
+ icon: ,
+ action: () => onNavigate('playwright', test.id),
+ tags: ['testing', 'e2e', test.name.toLowerCase()],
+ })
+ })
+
+ storybookStories.forEach((story) => {
+ results.push({
+ id: `storybook-${story.id}`,
+ title: story.storyName,
+ subtitle: story.componentName,
+ category: 'Storybook Stories',
+ icon: ,
+ action: () => onNavigate('storybook', story.id),
+ tags: ['documentation', 'story', story.componentName.toLowerCase()],
+ })
+ })
+
+ unitTests.forEach((test) => {
+ results.push({
+ id: `unit-test-${test.id}`,
+ title: test.name,
+ subtitle: test.description,
+ category: 'Unit Tests',
+ icon: ,
+ action: () => onNavigate('unit-tests', test.id),
+ tags: ['testing', 'unit', test.name.toLowerCase()],
+ })
+ })
+
+ return results
+ }, [
+ files,
+ models,
+ components,
+ componentTrees,
+ workflows,
+ lambdas,
+ playwrightTests,
+ storybookStories,
+ unitTests,
+ onNavigate,
+ onFileSelect,
+ ])
+
+ const filteredResults = useMemo(() => {
+ if (!searchQuery.trim()) {
+ return []
+ }
+
+ const query = searchQuery.toLowerCase().trim()
+ const queryWords = query.split(/\s+/)
+
+ return allResults
+ .map((result) => {
+ let score = 0
+ const titleLower = result.title.toLowerCase()
+ const subtitleLower = result.subtitle?.toLowerCase() || ''
+ const categoryLower = result.category.toLowerCase()
+ const tagsLower = result.tags?.map((tag) => tag.toLowerCase()) || []
+
+ if (titleLower === query) score += 100
+ else if (titleLower.startsWith(query)) score += 50
+ else if (titleLower.includes(query)) score += 30
+
+ if (subtitleLower.includes(query)) score += 20
+ if (categoryLower.includes(query)) score += 15
+
+ tagsLower.forEach((tag) => {
+ if (tag === query) score += 40
+ else if (tag.includes(query)) score += 10
+ })
+
+ queryWords.forEach((word) => {
+ if (titleLower.includes(word)) score += 5
+ if (subtitleLower.includes(word)) score += 3
+ if (tagsLower.some((tag) => tag.includes(word))) score += 2
+ })
+
+ return { result, score }
+ })
+ .filter(({ score }) => score > 0)
+ .sort((a, b) => b.score - a.score)
+ .slice(0, 50)
+ .map(({ result }) => result)
+ }, [allResults, searchQuery])
+
+ const recentSearches = useMemo(() => {
+ const safeHistory = searchHistory || []
+ return safeHistory.slice(0, 10).map((item) => {
+ const result = allResults.find((searchResult) => searchResult.id === item.resultId)
+ return {
+ historyItem: item,
+ result,
+ }
+ })
+ }, [searchHistory, allResults])
+
+ const groupedResults = useMemo(() => {
+ const groups: Record = {}
+ filteredResults.forEach((result) => {
+ if (!groups[result.category]) {
+ groups[result.category] = []
+ }
+ groups[result.category].push(result)
+ })
+ return groups
+ }, [filteredResults])
+
+ const handleSelect = (result: SearchResult) => {
+ addToHistory(searchQuery, result)
+ result.action()
+ onOpenChange(false)
+ }
+
+ const handleHistorySelect = (historyItem: SearchHistoryItem, result?: SearchResult) => {
+ if (result) {
+ result.action()
+ onOpenChange(false)
+ } else {
+ setSearchQuery(historyItem.query)
+ }
+ }
+
+ return {
+ searchQuery,
+ setSearchQuery,
+ recentSearches,
+ groupedResults,
+ clearHistory,
+ removeHistoryItem,
+ handleSelect,
+ handleHistorySelect,
+ }
+}
diff --git a/src/data/global-search.json b/src/data/global-search.json
new file mode 100644
index 0000000..1b34f78
--- /dev/null
+++ b/src/data/global-search.json
@@ -0,0 +1,182 @@
+[
+ {
+ "id": "nav-dashboard",
+ "title": "Dashboard",
+ "subtitle": "View project overview and statistics",
+ "category": "Navigation",
+ "icon": "ChartBar",
+ "tab": "dashboard",
+ "tags": ["home", "overview", "stats", "metrics"]
+ },
+ {
+ "id": "nav-code",
+ "title": "Code Editor",
+ "subtitle": "Edit project files with Monaco",
+ "category": "Navigation",
+ "icon": "Code",
+ "tab": "code",
+ "tags": ["editor", "monaco", "typescript", "javascript"]
+ },
+ {
+ "id": "nav-models",
+ "title": "Models",
+ "subtitle": "Design Prisma database models",
+ "category": "Navigation",
+ "icon": "Database",
+ "tab": "models",
+ "tags": ["prisma", "database", "schema", "orm"]
+ },
+ {
+ "id": "nav-components",
+ "title": "Components",
+ "subtitle": "Build React components",
+ "category": "Navigation",
+ "icon": "Tree",
+ "tab": "components",
+ "tags": ["react", "mui", "ui", "design"]
+ },
+ {
+ "id": "nav-component-trees",
+ "title": "Component Trees",
+ "subtitle": "Manage component hierarchies",
+ "category": "Navigation",
+ "icon": "Tree",
+ "tab": "component-trees",
+ "tags": ["hierarchy", "structure", "layout"]
+ },
+ {
+ "id": "nav-workflows",
+ "title": "Workflows",
+ "subtitle": "Design n8n-style workflows",
+ "category": "Navigation",
+ "icon": "FlowArrow",
+ "tab": "workflows",
+ "tags": ["automation", "n8n", "flow", "pipeline"]
+ },
+ {
+ "id": "nav-lambdas",
+ "title": "Lambdas",
+ "subtitle": "Create serverless functions",
+ "category": "Navigation",
+ "icon": "Code",
+ "tab": "lambdas",
+ "tags": ["serverless", "functions", "api"]
+ },
+ {
+ "id": "nav-styling",
+ "title": "Styling",
+ "subtitle": "Design themes and colors",
+ "category": "Navigation",
+ "icon": "PaintBrush",
+ "tab": "styling",
+ "tags": ["theme", "colors", "css", "design"]
+ },
+ {
+ "id": "nav-flask",
+ "title": "Flask API",
+ "subtitle": "Configure Flask backend",
+ "category": "Navigation",
+ "icon": "Flask",
+ "tab": "flask",
+ "tags": ["python", "backend", "api", "rest"]
+ },
+ {
+ "id": "nav-playwright",
+ "title": "Playwright Tests",
+ "subtitle": "E2E testing configuration",
+ "category": "Navigation",
+ "icon": "Play",
+ "tab": "playwright",
+ "tags": ["testing", "e2e", "automation"]
+ },
+ {
+ "id": "nav-storybook",
+ "title": "Storybook",
+ "subtitle": "Component documentation",
+ "category": "Navigation",
+ "icon": "BookOpen",
+ "tab": "storybook",
+ "tags": ["documentation", "components", "stories"]
+ },
+ {
+ "id": "nav-unit-tests",
+ "title": "Unit Tests",
+ "subtitle": "Configure unit testing",
+ "category": "Navigation",
+ "icon": "Cube",
+ "tab": "unit-tests",
+ "tags": ["testing", "jest", "vitest"]
+ },
+ {
+ "id": "nav-errors",
+ "title": "Error Repair",
+ "subtitle": "Auto-detect and fix errors",
+ "category": "Navigation",
+ "icon": "Wrench",
+ "tab": "errors",
+ "tags": ["debugging", "errors", "fixes"]
+ },
+ {
+ "id": "nav-docs",
+ "title": "Documentation",
+ "subtitle": "View project documentation",
+ "category": "Navigation",
+ "icon": "FileText",
+ "tab": "docs",
+ "tags": ["readme", "guide", "help"]
+ },
+ {
+ "id": "nav-sass",
+ "title": "Sass Styles",
+ "subtitle": "Custom Sass styling",
+ "category": "Navigation",
+ "icon": "PaintBrush",
+ "tab": "sass",
+ "tags": ["sass", "scss", "styles", "css"]
+ },
+ {
+ "id": "nav-favicon",
+ "title": "Favicon Designer",
+ "subtitle": "Design app icons",
+ "category": "Navigation",
+ "icon": "Image",
+ "tab": "favicon",
+ "tags": ["icon", "logo", "design"]
+ },
+ {
+ "id": "nav-settings",
+ "title": "Settings",
+ "subtitle": "Configure Next.js and npm",
+ "category": "Navigation",
+ "icon": "Gear",
+ "tab": "settings",
+ "tags": ["config", "nextjs", "npm", "packages"]
+ },
+ {
+ "id": "nav-pwa",
+ "title": "PWA Settings",
+ "subtitle": "Progressive Web App config",
+ "category": "Navigation",
+ "icon": "DeviceMobile",
+ "tab": "pwa",
+ "tags": ["mobile", "install", "offline"]
+ },
+ {
+ "id": "nav-features",
+ "title": "Feature Toggles",
+ "subtitle": "Enable or disable features",
+ "category": "Navigation",
+ "icon": "Faders",
+ "tab": "features",
+ "tags": ["settings", "toggles", "enable"]
+ },
+ {
+ "id": "nav-ideas",
+ "title": "Feature Ideas",
+ "subtitle": "Brainstorm and organize ideas",
+ "category": "Navigation",
+ "icon": "Lightbulb",
+ "tab": "ideas",
+ "tags": ["brainstorm", "ideas", "planning"]
+ }
+]