Files
zui/frontend/src/app/platform/PlatformPage.tsx
dtoro 4e51b6866f feat: add Sheet and Sidebar components for improved UI layout
- Introduced a new Sheet component for modal-like functionality with customizable content and animations.
- Implemented a Sidebar component with mobile responsiveness, state management, and keyboard shortcuts for toggling.
- Added Skeleton component for loading states and Tooltip component for enhanced user guidance.
- Created a useIsMobile hook to manage mobile view detection.
- Updated main entry point to render the new PlatformPage component.
- Enhanced styles for sidebar and scrollbar customization in CSS.
- Extended Tailwind configuration to include sidebar color variables for better theming.
2026-03-09 20:51:29 +01:00

134 lines
4.5 KiB
TypeScript

/**
* Platform: main entry layout with sidebar (sidebar-07). Projects in sidebar;
* when a project is selected, the canvas is shown in the main area.
*/
import React, { useCallback, useState } from 'react'
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/components/ui/breadcrumb'
import { Separator } from '@/components/ui/separator'
import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'
import { Button } from '@/components/ui/button'
import { Plus } from 'lucide-react'
import { AppSidebar } from './AppSidebar'
import { CanvasPage } from '../canvas/CanvasPage'
import type { Project } from './types'
import { NewProjectDialog } from './NewProjectDialog'
const STORAGE_KEY = 'zui_platform_projects'
function loadProjects(): Project[] {
try {
const raw = localStorage.getItem(STORAGE_KEY)
if (!raw) return []
const parsed = JSON.parse(raw) as unknown
if (!Array.isArray(parsed)) return []
return parsed.filter(
(p): p is Project =>
p &&
typeof p === 'object' &&
typeof (p as Project).id === 'string' &&
typeof (p as Project).name === 'string' &&
typeof (p as Project).iconId === 'string' &&
typeof (p as Project).createdAt === 'number'
)
} catch {
return []
}
}
function saveProjects(projects: Project[]) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(projects))
}
export function PlatformPage() {
const [projects, setProjects] = useState<Project[]>(loadProjects)
const [selectedProjectId, setSelectedProjectId] = useState<string | null>(() => {
const list = loadProjects()
return list.length > 0 ? list[0].id : null
})
const persist = useCallback((next: Project[]) => {
setProjects(next)
saveProjects(next)
}, [])
const handleCreateProject = useCallback(
(project: Project) => {
persist([...projects, project])
setSelectedProjectId(project.id)
},
[projects, persist]
)
const handleDeleteProject = useCallback(
(id: string) => {
const next = projects.filter((p) => p.id !== id)
persist(next)
if (selectedProjectId === id) setSelectedProjectId(next[0]?.id ?? null)
},
[projects, persist, selectedProjectId]
)
const selectedProject = projects.find((p) => p.id === selectedProjectId)
return (
<SidebarProvider>
<AppSidebar
projects={projects}
selectedProjectId={selectedProjectId}
onSelectProject={setSelectedProjectId}
onDeleteProject={handleDeleteProject}
onCreateProject={handleCreateProject}
/>
<SidebarInset className="flex min-h-0 flex-1 flex-col">
<header className="flex h-12 shrink-0 items-center gap-2 border-b border-border/40 px-4 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
<div className="flex items-center gap-2 px-2">
<SidebarTrigger className="-ml-1" />
<Separator orientation="vertical" className="mr-2 h-4" />
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem className="hidden md:block">
<BreadcrumbLink href="#">Zui</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="hidden md:block" />
<BreadcrumbItem>
<BreadcrumbPage className="line-clamp-1">
{selectedProject ? selectedProject.name : 'No project'}
</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div>
</header>
<div className="flex min-h-0 flex-1 flex-col">
{selectedProjectId && selectedProject ? (
<div className="flex-1 min-h-0 flex flex-col">
<CanvasPage projectId={selectedProjectId} />
</div>
) : (
<div className="flex flex-1 flex-col items-center justify-center gap-4 rounded-xl border border-dashed bg-muted/30 p-8">
<p className="text-sm text-muted-foreground">No project selected. Create one to open the canvas.</p>
<NewProjectDialog
onCreate={handleCreateProject}
trigger={
<Button>
<Plus className="size-4" />
New project
</Button>
}
/>
</div>
)}
</div>
</SidebarInset>
</SidebarProvider>
)
}