feat: add navigation link to projects in CanvasMenubar and enhance AppSidebar with tooltip for expand button
This commit is contained in:
@@ -36,6 +36,7 @@ import {
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { MoreHorizontal, Plus, Trash2, Pencil, FolderOpen, ListTodo } from 'lucide-react'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useSidebar } from '@/components/ui/sidebar'
|
||||
import { usePlatform } from './platformContext'
|
||||
import { getProjectIcon } from './icon-map'
|
||||
@@ -43,7 +44,8 @@ import { NewProjectDialog } from './NewProjectDialog'
|
||||
import type { Project } from './types'
|
||||
|
||||
export function AppSidebar() {
|
||||
const { isMobile } = useSidebar()
|
||||
const { isMobile, state, setOpen } = useSidebar()
|
||||
const isCollapsed = state === 'collapsed'
|
||||
const { orderedProjects, createProject, deleteProject, renameProject, reorderProjects, projectOrder } = usePlatform()
|
||||
const navigate = useNavigate()
|
||||
const { projectId: selectedProjectId } = useParams<{ projectId: string }>()
|
||||
@@ -121,36 +123,84 @@ export function AppSidebar() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Sidebar collapsible="icon">
|
||||
<SidebarHeader>
|
||||
<SidebarMenu>
|
||||
<Sidebar collapsible="icon" className="relative">
|
||||
{isCollapsed && (
|
||||
<button
|
||||
type="button"
|
||||
className="absolute inset-0 z-0 cursor-pointer border-0 bg-transparent p-0 outline-none"
|
||||
onClick={() => setOpen(true)}
|
||||
aria-label="Expand sidebar"
|
||||
/>
|
||||
)}
|
||||
<SidebarHeader className="relative z-10 flex flex-row items-center gap-2">
|
||||
<SidebarMenu className="flex-1 min-w-0">
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild size="lg" tooltip="Zoë" className="font-semibold font-serif">
|
||||
<Link to="/projects">
|
||||
<span className="flex size-8 min-w-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground dark:bg-white dark:text-black">
|
||||
<span
|
||||
className="size-6 shrink-0 rounded-[2px] opacity-90"
|
||||
style={{
|
||||
maskImage: 'url(/app-icon.svg)',
|
||||
maskRepeat: 'no-repeat',
|
||||
maskPosition: 'center',
|
||||
maskSize: 'contain',
|
||||
WebkitMaskImage: 'url(/app-icon.svg)',
|
||||
WebkitMaskRepeat: 'no-repeat',
|
||||
WebkitMaskPosition: 'center',
|
||||
WebkitMaskSize: 'contain',
|
||||
backgroundColor: 'currentColor',
|
||||
{isCollapsed ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="font-semibold font-serif"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setOpen(true)
|
||||
}}
|
||||
aria-hidden
|
||||
/>
|
||||
</span>
|
||||
<span className="font-serif group-data-[collapsible=icon]:hidden uppercase tracking-wide">ZOË</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
>
|
||||
<span className="flex size-8 min-w-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground dark:bg-white dark:text-black">
|
||||
<span
|
||||
className="size-6 shrink-0 rounded-[2px] opacity-90"
|
||||
style={{
|
||||
maskImage: 'url(/app-icon.svg)',
|
||||
maskRepeat: 'no-repeat',
|
||||
maskPosition: 'center',
|
||||
maskSize: 'contain',
|
||||
WebkitMaskImage: 'url(/app-icon.svg)',
|
||||
WebkitMaskRepeat: 'no-repeat',
|
||||
WebkitMaskPosition: 'center',
|
||||
WebkitMaskSize: 'contain',
|
||||
backgroundColor: 'currentColor',
|
||||
}}
|
||||
aria-hidden
|
||||
/>
|
||||
</span>
|
||||
<span className="font-serif group-data-[collapsible=icon]:hidden uppercase tracking-wide">ZOË</span>
|
||||
</SidebarMenuButton>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Click to expand sidebar</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<SidebarMenuButton asChild size="lg" tooltip="Zoë" className="font-semibold font-serif">
|
||||
<Link to="/projects">
|
||||
<span className="flex size-8 min-w-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground dark:bg-white dark:text-black">
|
||||
<span
|
||||
className="size-6 shrink-0 rounded-[2px] opacity-90"
|
||||
style={{
|
||||
maskImage: 'url(/app-icon.svg)',
|
||||
maskRepeat: 'no-repeat',
|
||||
maskPosition: 'center',
|
||||
maskSize: 'contain',
|
||||
WebkitMaskImage: 'url(/app-icon.svg)',
|
||||
WebkitMaskRepeat: 'no-repeat',
|
||||
WebkitMaskPosition: 'center',
|
||||
WebkitMaskSize: 'contain',
|
||||
backgroundColor: 'currentColor',
|
||||
}}
|
||||
aria-hidden
|
||||
/>
|
||||
</span>
|
||||
<span className="font-serif group-data-[collapsible=icon]:hidden uppercase tracking-wide">ZOË</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
)}
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
{!isCollapsed && <SidebarTrigger className="ml-auto shrink-0" />}
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<SidebarContent
|
||||
className="relative z-10"
|
||||
onClick={isCollapsed ? (e) => { if ((e.target as HTMLElement).closest('button') || (e.target as HTMLElement).closest('a')) return; setOpen(true) } : undefined}
|
||||
>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel className="group-data-[collapsible=icon]:hidden">Projects</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
@@ -250,8 +300,8 @@ export function AppSidebar() {
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
<SidebarFooter />
|
||||
<SidebarRail />
|
||||
<SidebarFooter className="relative z-10" />
|
||||
<SidebarRail className="relative z-10" />
|
||||
</Sidebar>
|
||||
|
||||
<Dialog open={!!renameTarget} onOpenChange={(open) => !open && setRenameTarget(null)}>
|
||||
|
||||
@@ -2,65 +2,30 @@
|
||||
* Platform: main layout with sidebar and header. Renders child routes (projects list or canvas) via Outlet.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { Link, Outlet, useParams, useLocation } from 'react-router-dom'
|
||||
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 React, { useEffect, useState } from 'react'
|
||||
import { Outlet, useParams } from 'react-router-dom'
|
||||
import { SidebarProvider } from '@/components/ui/sidebar'
|
||||
import { PlatformProvider, usePlatform } from './platformContext'
|
||||
import { AppSidebar } from './AppSidebar'
|
||||
|
||||
function PlatformLayoutInner() {
|
||||
const { projectId } = useParams<{ projectId: string }>()
|
||||
const location = useLocation()
|
||||
const { projects } = usePlatform()
|
||||
const project = projectId ? projects.find((p) => p.id === projectId) : null
|
||||
const isProjectsList = location.pathname === '/projects'
|
||||
const [sidebarOpen, setSidebarOpen] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
if (projectId) setSidebarOpen(false)
|
||||
else setSidebarOpen(true)
|
||||
}, [projectId])
|
||||
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<SidebarProvider open={sidebarOpen} onOpenChange={setSidebarOpen}>
|
||||
<AppSidebar />
|
||||
<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-1 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>
|
||||
{isProjectsList ? (
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage className="line-clamp-1">Projects</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
) : (
|
||||
<>
|
||||
<BreadcrumbItem className="hidden md:block">
|
||||
<BreadcrumbLink asChild>
|
||||
<Link to="/projects">Projects</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage className="line-clamp-1">
|
||||
{project ? project.name : 'Project'}
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</>
|
||||
)}
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
</header>
|
||||
<div className="relative flex min-h-0 w-full flex-1 flex-col bg-background">
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<Outlet />
|
||||
</div>
|
||||
</SidebarInset>
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user