mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 13:44:54 +00:00
Generated by Spark: Probably better off with grouped burger menu
This commit is contained in:
8
PRD.md
8
PRD.md
@@ -242,8 +242,12 @@ Animations should feel responsive and purposeful - quick panel transitions (200m
|
||||
- Section margins: mb-6 (24px) between major sections
|
||||
|
||||
- **Mobile**:
|
||||
- Burger menu navigation with grouped feature categories for cleaner mobile experience
|
||||
- Page header displays current section with icon and description
|
||||
- Responsive toolbar with icon-only buttons on mobile, full labels on desktop
|
||||
- Stack panels vertically instead of resizable horizontal splits
|
||||
- Convert main tabs to a drawer menu on mobile
|
||||
- Full-screen editor mode as default on small screens
|
||||
- Collapse file tree into slide-out sheet
|
||||
- Touch-friendly hit areas (min 44px) for all tree items and buttons
|
||||
- Touch-friendly hit areas (min 44px) for all interactive elements
|
||||
- Error badge overlays on navigation icon when errors present
|
||||
- Search accessible via icon-only button on mobile
|
||||
|
||||
208
src/App.tsx
208
src/App.tsx
@@ -34,6 +34,8 @@ import { PWAStatusBar } from '@/components/PWAStatusBar'
|
||||
import { PWASettings } from '@/components/PWASettings'
|
||||
import { FaviconDesigner } from '@/components/FaviconDesigner'
|
||||
import { GlobalSearch } from '@/components/GlobalSearch'
|
||||
import { NavigationMenu } from '@/components/NavigationMenu'
|
||||
import { PageHeader } from '@/components/PageHeader'
|
||||
import { useKeyboardShortcuts } from '@/hooks/use-keyboard-shortcuts'
|
||||
import { generateNextJSProject, generatePrismaSchema, generateMUITheme, generatePlaywrightTests, generateStorybookStories, generateUnitTests, generateFlaskApp } from '@/lib/generators'
|
||||
import { AIService } from '@/lib/ai-service'
|
||||
@@ -526,30 +528,34 @@ Navigate to the backend directory and follow the setup instructions.
|
||||
<PWAStatusBar />
|
||||
<PWAUpdatePrompt />
|
||||
|
||||
<header className="border-b border-border bg-card px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-primary to-accent flex items-center justify-center">
|
||||
<Code size={24} weight="duotone" className="text-white" />
|
||||
<header className="border-b border-border bg-card px-4 sm:px-6 py-3 sm:py-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2 sm:gap-3 min-w-0">
|
||||
<NavigationMenu
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
featureToggles={safeFeatureToggles}
|
||||
errorCount={autoDetectedErrors.length}
|
||||
/>
|
||||
<div className="w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-gradient-to-br from-primary to-accent flex items-center justify-center shrink-0">
|
||||
<Code size={20} weight="duotone" className="text-white sm:w-6 sm:h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-xl font-bold">CodeForge</h1>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<div className="min-w-0">
|
||||
<h1 className="text-base sm:text-xl font-bold truncate">CodeForge</h1>
|
||||
<p className="text-xs text-muted-foreground hidden sm:block">
|
||||
Low-Code Next.js App Builder
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-1 sm:gap-2 shrink-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => setSearchDialogOpen(true)}
|
||||
className="gap-2"
|
||||
className="shrink-0"
|
||||
title="Search (Ctrl+K)"
|
||||
>
|
||||
<MagnifyingGlass size={16} />
|
||||
Search
|
||||
<kbd className="pointer-events-none hidden h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 sm:flex">
|
||||
<span className="text-xs">⌘</span>K
|
||||
</kbd>
|
||||
<MagnifyingGlass size={18} />
|
||||
</Button>
|
||||
<ProjectManager
|
||||
currentProject={getCurrentProject()}
|
||||
@@ -558,158 +564,68 @@ Navigate to the backend directory and follow the setup instructions.
|
||||
{safeFeatureToggles.errorRepair && autoDetectedErrors.length > 0 && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => setActiveTab('errors')}
|
||||
className="border-destructive text-destructive hover:bg-destructive hover:text-destructive-foreground"
|
||||
className="border-destructive text-destructive hover:bg-destructive hover:text-destructive-foreground shrink-0 relative"
|
||||
title={`${autoDetectedErrors.length} ${autoDetectedErrors.length === 1 ? 'Error' : 'Errors'}`}
|
||||
>
|
||||
<Wrench size={16} className="mr-2" />
|
||||
{autoDetectedErrors.length} {autoDetectedErrors.length === 1 ? 'Error' : 'Errors'}
|
||||
<Wrench size={18} />
|
||||
<Badge
|
||||
variant="destructive"
|
||||
className="absolute -top-1 -right-1 h-5 w-5 p-0 flex items-center justify-center text-[10px]"
|
||||
>
|
||||
{autoDetectedErrors.length}
|
||||
</Badge>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setActiveTab('features')}
|
||||
title="Toggle Features"
|
||||
>
|
||||
<Faders size={20} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setShortcutsDialogOpen(true)}
|
||||
title="Keyboard Shortcuts (Ctrl+/)"
|
||||
className="hidden sm:flex shrink-0"
|
||||
>
|
||||
<Keyboard size={20} />
|
||||
<Keyboard size={18} />
|
||||
</Button>
|
||||
<Button variant="outline" onClick={handleGenerateWithAI}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleGenerateWithAI}
|
||||
className="hidden lg:flex shrink-0"
|
||||
>
|
||||
<Sparkle size={16} className="mr-2" weight="duotone" />
|
||||
AI Generate
|
||||
</Button>
|
||||
<Button onClick={handleExportProject}>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handleGenerateWithAI}
|
||||
className="lg:hidden shrink-0"
|
||||
title="AI Generate"
|
||||
>
|
||||
<Sparkle size={18} weight="duotone" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleExportProject}
|
||||
className="hidden md:flex shrink-0"
|
||||
>
|
||||
<Download size={16} className="mr-2" />
|
||||
Export Project
|
||||
Export
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
onClick={handleExportProject}
|
||||
className="md:hidden shrink-0"
|
||||
title="Export Project"
|
||||
>
|
||||
<Download size={18} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col">
|
||||
<div className="border-b border-border bg-card px-3 sm:px-6">
|
||||
<TabsList className="h-auto bg-transparent flex-wrap py-2 gap-1 justify-start">
|
||||
<TabsTrigger value="dashboard" className="gap-2">
|
||||
<ChartBar size={18} />
|
||||
Dashboard
|
||||
</TabsTrigger>
|
||||
{safeFeatureToggles.codeEditor && (
|
||||
<TabsTrigger value="code" className="gap-2">
|
||||
<Code size={18} />
|
||||
Code Editor
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{safeFeatureToggles.models && (
|
||||
<TabsTrigger value="models" className="gap-2">
|
||||
<Database size={18} />
|
||||
Models
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{safeFeatureToggles.components && (
|
||||
<TabsTrigger value="components" className="gap-2">
|
||||
<Tree size={18} />
|
||||
Components
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{safeFeatureToggles.componentTrees && (
|
||||
<TabsTrigger value="component-trees" className="gap-2">
|
||||
<Tree size={18} />
|
||||
Component Trees
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{safeFeatureToggles.workflows && (
|
||||
<TabsTrigger value="workflows" className="gap-2">
|
||||
<FlowArrow size={18} />
|
||||
Workflows
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{safeFeatureToggles.lambdas && (
|
||||
<TabsTrigger value="lambdas" className="gap-2">
|
||||
<Code size={18} />
|
||||
Lambdas
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{safeFeatureToggles.styling && (
|
||||
<TabsTrigger value="styling" className="gap-2">
|
||||
<PaintBrush size={18} />
|
||||
Styling
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{safeFeatureToggles.flaskApi && (
|
||||
<TabsTrigger value="flask" className="gap-2">
|
||||
<Flask size={18} />
|
||||
Flask API
|
||||
</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="settings" className="gap-2">
|
||||
<Gear size={18} />
|
||||
Settings
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="pwa" className="gap-2">
|
||||
<DeviceMobile size={18} />
|
||||
PWA
|
||||
</TabsTrigger>
|
||||
{safeFeatureToggles.faviconDesigner && (
|
||||
<TabsTrigger value="favicon" className="gap-2">
|
||||
<Image size={18} />
|
||||
Favicon
|
||||
</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="features" className="gap-2">
|
||||
<Faders size={18} />
|
||||
Features
|
||||
</TabsTrigger>
|
||||
{safeFeatureToggles.playwright && (
|
||||
<TabsTrigger value="playwright" className="gap-2">
|
||||
<Play size={18} />
|
||||
Playwright
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{safeFeatureToggles.storybook && (
|
||||
<TabsTrigger value="storybook" className="gap-2">
|
||||
<BookOpen size={18} />
|
||||
Storybook
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{safeFeatureToggles.unitTests && (
|
||||
<TabsTrigger value="unit-tests" className="gap-2">
|
||||
<Cube size={18} />
|
||||
Unit Tests
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{safeFeatureToggles.errorRepair && (
|
||||
<TabsTrigger value="errors" className="gap-2">
|
||||
<Wrench size={18} />
|
||||
Error Repair
|
||||
{autoDetectedErrors.length > 0 && (
|
||||
<Badge variant="destructive" className="ml-1">
|
||||
{autoDetectedErrors.length}
|
||||
</Badge>
|
||||
)}
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{safeFeatureToggles.documentation && (
|
||||
<TabsTrigger value="docs" className="gap-2">
|
||||
<FileText size={18} />
|
||||
Documentation
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{safeFeatureToggles.sassStyles && (
|
||||
<TabsTrigger value="sass" className="gap-2">
|
||||
<PaintBrush size={18} />
|
||||
Sass Styles
|
||||
</TabsTrigger>
|
||||
)}
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
<PageHeader activeTab={activeTab} />
|
||||
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<TabsContent value="dashboard" className="h-full m-0">
|
||||
<ProjectDashboard
|
||||
|
||||
@@ -53,8 +53,24 @@ export function KeyboardShortcutsDialog({ open, onOpenChange }: KeyboardShortcut
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, '5']}
|
||||
description="Go to Component Trees"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, '6']}
|
||||
description="Go to Workflows"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, '7']}
|
||||
description="Go to Lambdas"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, '8']}
|
||||
description="Go to Styling"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, '9']}
|
||||
description="Go to Favicon Designer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -63,18 +79,18 @@ export function KeyboardShortcutsDialog({ open, onOpenChange }: KeyboardShortcut
|
||||
<div>
|
||||
<h3 className="font-semibold mb-3">Actions</h3>
|
||||
<div className="space-y-2">
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, 'K']}
|
||||
description="Search Everything"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, 'E']}
|
||||
description="Export Project"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, 'K']}
|
||||
keys={[ctrlKey, 'Shift', 'G']}
|
||||
description="AI Generate"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, 'S']}
|
||||
description="Save (Auto-save enabled)"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, '/']}
|
||||
description="Show Keyboard Shortcuts"
|
||||
|
||||
310
src/components/NavigationMenu.tsx
Normal file
310
src/components/NavigationMenu.tsx
Normal file
@@ -0,0 +1,310 @@
|
||||
import { useState } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import {
|
||||
List,
|
||||
ChartBar,
|
||||
Code,
|
||||
Database,
|
||||
Tree,
|
||||
FlowArrow,
|
||||
PaintBrush,
|
||||
Flask,
|
||||
Play,
|
||||
BookOpen,
|
||||
Cube,
|
||||
Wrench,
|
||||
FileText,
|
||||
Gear,
|
||||
DeviceMobile,
|
||||
Image,
|
||||
Faders,
|
||||
} from '@phosphor-icons/react'
|
||||
import { FeatureToggles } from '@/types/project'
|
||||
|
||||
interface NavigationGroup {
|
||||
id: string
|
||||
label: string
|
||||
items: NavigationItem[]
|
||||
}
|
||||
|
||||
interface NavigationItem {
|
||||
id: string
|
||||
label: string
|
||||
icon: React.ReactNode
|
||||
value: string
|
||||
badge?: number
|
||||
featureKey?: keyof FeatureToggles
|
||||
}
|
||||
|
||||
interface NavigationMenuProps {
|
||||
activeTab: string
|
||||
onTabChange: (tab: string) => void
|
||||
featureToggles: FeatureToggles
|
||||
errorCount?: number
|
||||
}
|
||||
|
||||
export function NavigationMenu({
|
||||
activeTab,
|
||||
onTabChange,
|
||||
featureToggles,
|
||||
errorCount = 0,
|
||||
}: NavigationMenuProps) {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const navigationGroups: NavigationGroup[] = [
|
||||
{
|
||||
id: 'overview',
|
||||
label: 'Overview',
|
||||
items: [
|
||||
{
|
||||
id: 'dashboard',
|
||||
label: 'Dashboard',
|
||||
icon: <ChartBar size={18} />,
|
||||
value: 'dashboard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'development',
|
||||
label: 'Development',
|
||||
items: [
|
||||
{
|
||||
id: 'code',
|
||||
label: 'Code Editor',
|
||||
icon: <Code size={18} />,
|
||||
value: 'code',
|
||||
featureKey: 'codeEditor',
|
||||
},
|
||||
{
|
||||
id: 'models',
|
||||
label: 'Models',
|
||||
icon: <Database size={18} />,
|
||||
value: 'models',
|
||||
featureKey: 'models',
|
||||
},
|
||||
{
|
||||
id: 'components',
|
||||
label: 'Components',
|
||||
icon: <Tree size={18} />,
|
||||
value: 'components',
|
||||
featureKey: 'components',
|
||||
},
|
||||
{
|
||||
id: 'component-trees',
|
||||
label: 'Component Trees',
|
||||
icon: <Tree size={18} />,
|
||||
value: 'component-trees',
|
||||
featureKey: 'componentTrees',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'automation',
|
||||
label: 'Automation',
|
||||
items: [
|
||||
{
|
||||
id: 'workflows',
|
||||
label: 'Workflows',
|
||||
icon: <FlowArrow size={18} />,
|
||||
value: 'workflows',
|
||||
featureKey: 'workflows',
|
||||
},
|
||||
{
|
||||
id: 'lambdas',
|
||||
label: 'Lambdas',
|
||||
icon: <Code size={18} />,
|
||||
value: 'lambdas',
|
||||
featureKey: 'lambdas',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'design',
|
||||
label: 'Design & Styling',
|
||||
items: [
|
||||
{
|
||||
id: 'styling',
|
||||
label: 'Styling',
|
||||
icon: <PaintBrush size={18} />,
|
||||
value: 'styling',
|
||||
featureKey: 'styling',
|
||||
},
|
||||
{
|
||||
id: 'sass',
|
||||
label: 'Sass Styles',
|
||||
icon: <PaintBrush size={18} />,
|
||||
value: 'sass',
|
||||
featureKey: 'sassStyles',
|
||||
},
|
||||
{
|
||||
id: 'favicon',
|
||||
label: 'Favicon Designer',
|
||||
icon: <Image size={18} />,
|
||||
value: 'favicon',
|
||||
featureKey: 'faviconDesigner',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'backend',
|
||||
label: 'Backend',
|
||||
items: [
|
||||
{
|
||||
id: 'flask',
|
||||
label: 'Flask API',
|
||||
icon: <Flask size={18} />,
|
||||
value: 'flask',
|
||||
featureKey: 'flaskApi',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'testing',
|
||||
label: 'Testing',
|
||||
items: [
|
||||
{
|
||||
id: 'playwright',
|
||||
label: 'Playwright',
|
||||
icon: <Play size={18} />,
|
||||
value: 'playwright',
|
||||
featureKey: 'playwright',
|
||||
},
|
||||
{
|
||||
id: 'storybook',
|
||||
label: 'Storybook',
|
||||
icon: <BookOpen size={18} />,
|
||||
value: 'storybook',
|
||||
featureKey: 'storybook',
|
||||
},
|
||||
{
|
||||
id: 'unit-tests',
|
||||
label: 'Unit Tests',
|
||||
icon: <Cube size={18} />,
|
||||
value: 'unit-tests',
|
||||
featureKey: 'unitTests',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'tools',
|
||||
label: 'Tools & Configuration',
|
||||
items: [
|
||||
{
|
||||
id: 'errors',
|
||||
label: 'Error Repair',
|
||||
icon: <Wrench size={18} />,
|
||||
value: 'errors',
|
||||
badge: errorCount,
|
||||
featureKey: 'errorRepair',
|
||||
},
|
||||
{
|
||||
id: 'docs',
|
||||
label: 'Documentation',
|
||||
icon: <FileText size={18} />,
|
||||
value: 'docs',
|
||||
featureKey: 'documentation',
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
label: 'Settings',
|
||||
icon: <Gear size={18} />,
|
||||
value: 'settings',
|
||||
},
|
||||
{
|
||||
id: 'pwa',
|
||||
label: 'PWA',
|
||||
icon: <DeviceMobile size={18} />,
|
||||
value: 'pwa',
|
||||
},
|
||||
{
|
||||
id: 'features',
|
||||
label: 'Features',
|
||||
icon: <Faders size={18} />,
|
||||
value: 'features',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const handleItemClick = (value: string) => {
|
||||
onTabChange(value)
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const isItemVisible = (item: NavigationItem) => {
|
||||
if (!item.featureKey) return true
|
||||
return featureToggles[item.featureKey]
|
||||
}
|
||||
|
||||
const getVisibleItemsCount = (group: NavigationGroup) => {
|
||||
return group.items.filter(isItemVisible).length
|
||||
}
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="outline" size="icon" className="shrink-0">
|
||||
<List size={20} weight="bold" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="w-80">
|
||||
<SheetHeader>
|
||||
<SheetTitle>Navigation</SheetTitle>
|
||||
</SheetHeader>
|
||||
<ScrollArea className="h-[calc(100vh-8rem)] mt-6">
|
||||
<div className="space-y-6">
|
||||
{navigationGroups.map((group) => {
|
||||
const visibleItemsCount = getVisibleItemsCount(group)
|
||||
if (visibleItemsCount === 0) return null
|
||||
|
||||
return (
|
||||
<div key={group.id}>
|
||||
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2 px-2">
|
||||
{group.label}
|
||||
</h3>
|
||||
<div className="space-y-1">
|
||||
{group.items.map((item) => {
|
||||
if (!isItemVisible(item)) return null
|
||||
|
||||
const isActive = activeTab === item.value
|
||||
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
onClick={() => handleItemClick(item.value)}
|
||||
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${
|
||||
isActive
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'hover:bg-muted text-foreground'
|
||||
}`}
|
||||
>
|
||||
<span className={isActive ? 'text-primary-foreground' : 'text-muted-foreground'}>
|
||||
{item.icon}
|
||||
</span>
|
||||
<span className="flex-1 text-left text-sm font-medium">
|
||||
{item.label}
|
||||
</span>
|
||||
{item.badge !== undefined && item.badge > 0 && (
|
||||
<Badge
|
||||
variant={isActive ? 'secondary' : 'destructive'}
|
||||
className="ml-auto"
|
||||
>
|
||||
{item.badge}
|
||||
</Badge>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)
|
||||
}
|
||||
145
src/components/PageHeader.tsx
Normal file
145
src/components/PageHeader.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import {
|
||||
ChartBar,
|
||||
Code,
|
||||
Database,
|
||||
Tree,
|
||||
FlowArrow,
|
||||
PaintBrush,
|
||||
Flask,
|
||||
Play,
|
||||
BookOpen,
|
||||
Cube,
|
||||
Wrench,
|
||||
FileText,
|
||||
Gear,
|
||||
DeviceMobile,
|
||||
Image,
|
||||
Faders,
|
||||
} from '@phosphor-icons/react'
|
||||
|
||||
interface PageHeaderProps {
|
||||
activeTab: string
|
||||
}
|
||||
|
||||
const tabInfo: Record<string, { title: string; icon: React.ReactNode; description?: string }> = {
|
||||
dashboard: {
|
||||
title: 'Dashboard',
|
||||
icon: <ChartBar size={24} weight="duotone" />,
|
||||
description: 'Project overview and statistics',
|
||||
},
|
||||
code: {
|
||||
title: 'Code Editor',
|
||||
icon: <Code size={24} weight="duotone" />,
|
||||
description: 'Edit project files',
|
||||
},
|
||||
models: {
|
||||
title: 'Models',
|
||||
icon: <Database size={24} weight="duotone" />,
|
||||
description: 'Define Prisma data models',
|
||||
},
|
||||
components: {
|
||||
title: 'Components',
|
||||
icon: <Tree size={24} weight="duotone" />,
|
||||
description: 'Create React components',
|
||||
},
|
||||
'component-trees': {
|
||||
title: 'Component Trees',
|
||||
icon: <Tree size={24} weight="duotone" />,
|
||||
description: 'Manage component hierarchies',
|
||||
},
|
||||
workflows: {
|
||||
title: 'Workflows',
|
||||
icon: <FlowArrow size={24} weight="duotone" />,
|
||||
description: 'Design automation workflows',
|
||||
},
|
||||
lambdas: {
|
||||
title: 'Lambdas',
|
||||
icon: <Code size={24} weight="duotone" />,
|
||||
description: 'Serverless functions',
|
||||
},
|
||||
styling: {
|
||||
title: 'Styling',
|
||||
icon: <PaintBrush size={24} weight="duotone" />,
|
||||
description: 'Theme and design tokens',
|
||||
},
|
||||
sass: {
|
||||
title: 'Sass Styles',
|
||||
icon: <PaintBrush size={24} weight="duotone" />,
|
||||
description: 'Custom Sass stylesheets',
|
||||
},
|
||||
favicon: {
|
||||
title: 'Favicon Designer',
|
||||
icon: <Image size={24} weight="duotone" />,
|
||||
description: 'Design app icons',
|
||||
},
|
||||
flask: {
|
||||
title: 'Flask API',
|
||||
icon: <Flask size={24} weight="duotone" />,
|
||||
description: 'Backend API configuration',
|
||||
},
|
||||
playwright: {
|
||||
title: 'Playwright',
|
||||
icon: <Play size={24} weight="duotone" />,
|
||||
description: 'E2E test scenarios',
|
||||
},
|
||||
storybook: {
|
||||
title: 'Storybook',
|
||||
icon: <BookOpen size={24} weight="duotone" />,
|
||||
description: 'Component documentation',
|
||||
},
|
||||
'unit-tests': {
|
||||
title: 'Unit Tests',
|
||||
icon: <Cube size={24} weight="duotone" />,
|
||||
description: 'Unit test suites',
|
||||
},
|
||||
errors: {
|
||||
title: 'Error Repair',
|
||||
icon: <Wrench size={24} weight="duotone" />,
|
||||
description: 'Automated error detection and fixing',
|
||||
},
|
||||
docs: {
|
||||
title: 'Documentation',
|
||||
icon: <FileText size={24} weight="duotone" />,
|
||||
description: 'Project guides and references',
|
||||
},
|
||||
settings: {
|
||||
title: 'Settings',
|
||||
icon: <Gear size={24} weight="duotone" />,
|
||||
description: 'Project configuration',
|
||||
},
|
||||
pwa: {
|
||||
title: 'PWA',
|
||||
icon: <DeviceMobile size={24} weight="duotone" />,
|
||||
description: 'Progressive Web App settings',
|
||||
},
|
||||
features: {
|
||||
title: 'Features',
|
||||
icon: <Faders size={24} weight="duotone" />,
|
||||
description: 'Toggle feature modules',
|
||||
},
|
||||
}
|
||||
|
||||
export function PageHeader({ activeTab }: PageHeaderProps) {
|
||||
const info = tabInfo[activeTab] || {
|
||||
title: 'Unknown',
|
||||
icon: <Code size={24} weight="duotone" />,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="border-b border-border bg-card px-4 sm:px-6 py-3 sm:py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-primary/20 to-accent/20 flex items-center justify-center text-primary shrink-0">
|
||||
{info.icon}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<h2 className="text-lg sm:text-xl font-bold truncate">{info.title}</h2>
|
||||
{info.description && (
|
||||
<p className="text-xs sm:text-sm text-muted-foreground hidden sm:block">
|
||||
{info.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user