feat: refactor names
This commit is contained in:
210
frontend/src/app/kosmos/KosmosSidebar.tsx
Normal file
210
frontend/src/app/kosmos/KosmosSidebar.tsx
Normal file
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* Platform sidebar: All projects, Recently used, New project.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { Link, useNavigate, useParams, useLocation } from 'react-router-dom'
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarRail,
|
||||
SidebarTrigger,
|
||||
} from '@/components/ui/sidebar'
|
||||
import { Plus, Settings, Triangle } from 'lucide-react'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useSidebar } from '@/components/ui/sidebar'
|
||||
import { usePlatform } from './KosmosContext'
|
||||
import { getProjectIcon } from '../../lib/iconMap'
|
||||
import { NewProjectDialog } from './NewProjectDialog'
|
||||
import { SettingsDialog } from './SettingsDialog'
|
||||
import type { Project } from './types'
|
||||
|
||||
export function KosmosSidebar() {
|
||||
const { state, setOpen } = useSidebar()
|
||||
const isCollapsed = state === 'collapsed'
|
||||
const { orderedProjects, createProject, recentProjectIds } = usePlatform()
|
||||
const recentProjects = useMemo(() => {
|
||||
const byId = new Map(orderedProjects.map((p) => [p.id, p]))
|
||||
return recentProjectIds.map((id) => byId.get(id)).filter((p): p is Project => p != null)
|
||||
}, [orderedProjects, recentProjectIds])
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const { projectId: selectedProjectId } = useParams<{ projectId: string }>()
|
||||
const isKeroma = location.pathname === '/keroma'
|
||||
|
||||
const handleSelectProject = useCallback((id: string) => navigate(`/projects/${id}`), [navigate])
|
||||
|
||||
const handleCreateProject = useCallback(
|
||||
(project: Parameters<typeof createProject>[0]) => {
|
||||
createProject(project)
|
||||
navigate(`/projects/${project.id}`)
|
||||
},
|
||||
[createProject, navigate]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<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>
|
||||
{isCollapsed ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="font-semibold font-serif"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setOpen(true)
|
||||
}}
|
||||
>
|
||||
<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
|
||||
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>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip="All projects" isActive={location.pathname === '/projects' && !selectedProjectId}>
|
||||
<Link to="/projects">
|
||||
<Triangle className="size-4" />
|
||||
<span>Pleroma</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip="Keroma" isActive={isKeroma}>
|
||||
<Link to="/keroma">
|
||||
<Triangle className="size-4 rotate-180" />
|
||||
<span>Keroma</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
{recentProjects.length > 0 && (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel className="group-data-[collapsible=icon]:hidden">Recently used</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{recentProjects.map((project) => {
|
||||
const Icon = getProjectIcon(project.iconId)
|
||||
const isActive = selectedProjectId === project.id
|
||||
return (
|
||||
<SidebarMenuItem key={project.id}>
|
||||
<SidebarMenuButton
|
||||
tooltip={project.name}
|
||||
isActive={isActive}
|
||||
onClick={() => handleSelectProject(project.id)}
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
<span>{project.name}</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
)
|
||||
})}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
)}
|
||||
<SidebarGroup>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<NewProjectDialog
|
||||
onCreate={handleCreateProject}
|
||||
existingNames={orderedProjects.map((p) => p.name)}
|
||||
trigger={
|
||||
<SidebarMenuButton className="text-sidebar-foreground/70 w-full cursor-pointer">
|
||||
<Plus className="size-4" />
|
||||
<span>{orderedProjects.length === 0 ? 'Create your first project' : 'New project'}</span>
|
||||
</SidebarMenuButton>
|
||||
}
|
||||
/>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
<SidebarFooter className="relative z-10">
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SettingsDialog
|
||||
trigger={
|
||||
<SidebarMenuButton tooltip="Settings" className="w-full">
|
||||
<Settings className="size-4" />
|
||||
<span className="group-data-[collapsible=icon]:hidden">Settings</span>
|
||||
</SidebarMenuButton>
|
||||
}
|
||||
/>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarFooter>
|
||||
<SidebarRail className="relative z-10" />
|
||||
</Sidebar>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user