fix: minor styles and ux

This commit is contained in:
2026-03-11 00:21:37 +01:00
parent 08d66b59c2
commit 7c1039f4c6
3 changed files with 185 additions and 78 deletions

View File

@@ -1,8 +1,8 @@
/**
* Menubar for the canvas page: Project (Import/Export), Edit (Undo/Redo), View (Fit View, Theme).
* Menubar for the canvas page: Project (Import/Export), Edit (Undo/Redo, Duplicate/Copy/Paste, Rename), View (Fit View, Theme).
*/
import React, { useEffect, useMemo } from 'react'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Link, useParams } from 'react-router-dom'
import {
Menubar,
@@ -19,7 +19,8 @@ import {
import { Kbd, KbdGroup } from '@/components/ui/kbd'
import { useTheme } from '@/lib/themeContext'
import { usePlatform } from '@/app/platform/platformContext'
import { ArrowLeft, Download, FolderOpen, Moon, Redo2, Sun, Undo2 } from 'lucide-react'
import { ArrowLeft, ClipboardPaste, Copy, CopyPlus, Download, FolderOpen, Moon, Pencil, Redo2, Sun, Undo2 } from 'lucide-react'
import { Input } from '@/components/ui/input'
export type CanvasMenubarProps = {
onImport: () => void
@@ -28,6 +29,11 @@ export type CanvasMenubarProps = {
redo: () => void
canUndo: boolean
canRedo: boolean
onDuplicate?: () => void
onCopy?: () => void
onPaste?: () => void
canDuplicate?: boolean
canCopy?: boolean
onFitView?: () => void
}
@@ -46,16 +52,58 @@ export function CanvasMenubar({
redo,
canUndo,
canRedo,
onDuplicate,
onCopy,
onPaste,
canDuplicate = false,
canCopy = false,
onFitView,
}: CanvasMenubarProps) {
const { theme, setTheme } = useTheme()
const { projectId } = useParams<{ projectId: string }>()
const { projects } = usePlatform()
const { projects, renameProject } = usePlatform()
const projectName = useMemo(
() => (projectId ? projects.find((p) => p.id === projectId)?.name ?? null : null),
[projectId, projects]
)
const [isRenamingProject, setIsRenamingProject] = useState(false)
const [renameValue, setRenameValue] = useState('')
const renameInputRef = useRef<HTMLInputElement>(null)
const ignoreNextBlurRef = useRef(false)
useEffect(() => {
if (isRenamingProject) {
setRenameValue(projectName ?? '')
ignoreNextBlurRef.current = true
// Delay focus so the Project dropdown can close first and not steal focus back (which would trigger blur)
const t = setTimeout(() => {
renameInputRef.current?.focus()
renameInputRef.current?.select()
}, 100)
return () => clearTimeout(t)
}
}, [isRenamingProject, projectName])
const applyRename = useCallback(() => {
if (!projectId || !renameProject) return
const trimmed = renameValue.trim()
if (trimmed) renameProject(projectId, trimmed)
setIsRenamingProject(false)
}, [projectId, renameProject, renameValue])
const cancelRename = useCallback(() => {
setIsRenamingProject(false)
}, [])
const handleRenameBlur = useCallback(() => {
if (ignoreNextBlurRef.current) {
ignoreNextBlurRef.current = false
return
}
applyRename()
}, [applyRename])
useEffect(() => {
const onKeyDown = (ev: KeyboardEvent) => {
if (matchKey(ev, UNDO_KEYS)) {
@@ -99,6 +147,18 @@ export function CanvasMenubar({
<Download className="h-4 w-4" />
Export
</MenubarItem>
{projectId && (
<>
<MenubarSeparator />
<MenubarItem
onClick={() => setIsRenamingProject(true)}
className="gap-2"
>
<Pencil className="h-4 w-4" />
Rename
</MenubarItem>
</>
)}
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
@@ -122,6 +182,40 @@ export function CanvasMenubar({
</KbdGroup>
</span>
</MenubarItem>
{(onDuplicate != null || onCopy != null || onPaste != null) && <MenubarSeparator />}
{onDuplicate != null && (
<MenubarItem onClick={onDuplicate} disabled={!canDuplicate} className="gap-2">
<CopyPlus className="h-4 w-4" />
Duplicate
<span className="ml-auto pl-4">
<KbdGroup>
<Kbd>D</Kbd>
</KbdGroup>
</span>
</MenubarItem>
)}
{onCopy != null && (
<MenubarItem onClick={onCopy} disabled={!canCopy} className="gap-2">
<Copy className="h-4 w-4" />
Copy
<span className="ml-auto pl-4">
<KbdGroup>
<Kbd>C</Kbd>
</KbdGroup>
</span>
</MenubarItem>
)}
{onPaste != null && (
<MenubarItem onClick={onPaste} className="gap-2">
<ClipboardPaste className="h-4 w-4" />
Paste
<span className="ml-auto pl-4">
<KbdGroup>
<Kbd>V</Kbd>
</KbdGroup>
</span>
</MenubarItem>
)}
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
@@ -162,10 +256,33 @@ export function CanvasMenubar({
</MenubarContent>
</MenubarMenu>
</Menubar>
{projectName && (
<span className="pointer-events-none absolute left-1/2 -translate-x-1/2 truncate max-w-[40%] text-sm font-medium text-foreground">
{projectName}
</span>
{projectId && (
<div className="absolute left-1/2 -translate-x-1/2 flex justify-center max-w-[40%] min-w-[120px]">
{isRenamingProject ? (
<Input
ref={renameInputRef}
type="text"
value={renameValue}
onChange={(e) => setRenameValue(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault()
applyRename()
} else if (e.key === 'Escape') {
e.preventDefault()
cancelRename()
}
}}
onBlur={handleRenameBlur}
className="h-7 text-sm font-medium text-center"
aria-label="Project name"
/>
) : (
<span className="pointer-events-none truncate text-sm font-medium text-foreground">
{projectName ?? 'Untitled'}
</span>
)}
</div>
)}
</div>
)