fix: minor styles and ux
This commit is contained in:
@@ -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>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user