diff --git a/frontend/src/components/CanvasMenubar.tsx b/frontend/src/app/canvas/CanvasMenubar.tsx similarity index 100% rename from frontend/src/components/CanvasMenubar.tsx rename to frontend/src/app/canvas/CanvasMenubar.tsx diff --git a/frontend/src/app/canvas/CanvasPage.tsx b/frontend/src/app/canvas/CanvasPage.tsx index 3937c56..acb230f 100644 --- a/frontend/src/app/canvas/CanvasPage.tsx +++ b/frontend/src/app/canvas/CanvasPage.tsx @@ -37,8 +37,8 @@ import { ContextMenuTrigger, ContextMenuGroup, } from '@/components/ui/context-menu' -import { CanvasMenubar } from '@/components/CanvasMenubar' -import { FlowKeyboardShortcuts } from '@/components/FlowKeyboardShortcuts' +import { CanvasMenubar } from '@/app/canvas/CanvasMenubar' +import { FlowKeyboardShortcuts } from '@/components/base/FlowKeyboardShortcuts' import { Empty, EmptyContent, @@ -485,11 +485,10 @@ export function CanvasPage({ projectId: _projectId }: CanvasPageProps) { {canvasMessage && (
{canvasMessage.text}
diff --git a/frontend/src/app/platform/AppSidebar.tsx b/frontend/src/app/platform/AppSidebar.tsx index 665406b..60c0b8e 100644 --- a/frontend/src/app/platform/AppSidebar.tsx +++ b/frontend/src/app/platform/AppSidebar.tsx @@ -53,10 +53,10 @@ export function AppSidebar({ - + Zo - Zui + Zui diff --git a/frontend/src/app/platform/NewProjectDialog.tsx b/frontend/src/app/platform/NewProjectDialog.tsx index 00a06bb..4d5587b 100644 --- a/frontend/src/app/platform/NewProjectDialog.tsx +++ b/frontend/src/app/platform/NewProjectDialog.tsx @@ -27,7 +27,7 @@ type NewProjectDialogProps = { export function NewProjectDialog({ onCreate, trigger }: NewProjectDialogProps) { const [open, setOpen] = useState(false) const [name, setName] = useState('') - const [iconId, setIconId] = useState('layout') + const [iconId, setIconId] = useState('cat') const handleSubmit = (e: React.FormEvent) => { e.preventDefault() @@ -41,7 +41,7 @@ export function NewProjectDialog({ onCreate, trigger }: NewProjectDialogProps) { } onCreate(project) setName('') - setIconId('layout') + setIconId('cat') setOpen(false) } diff --git a/frontend/src/app/platform/icon-map.tsx b/frontend/src/app/platform/icon-map.tsx index 0348042..3f89596 100644 --- a/frontend/src/app/platform/icon-map.tsx +++ b/frontend/src/app/platform/icon-map.tsx @@ -1,27 +1,40 @@ /** * Map project icon ids to Lucide icons for the sidebar. + * Only Lucide "Animals" category icons are allowed for projects. */ import { - LayoutDashboard, - Folder, - FileStack, - Sparkles, - Box, - Layers, + Bird, + Bug, + Cat, + Dog, + Egg, + Fish, + Rabbit, + Rat, + Shrimp, + Snail, + Squirrel, + Turtle, type LucideIcon, } from 'lucide-react' import type { ProjectIconId } from './types' export const PROJECT_ICON_MAP: Record = { - layout: LayoutDashboard, - folder: Folder, - 'file-stack': FileStack, - sparkles: Sparkles, - box: Box, - layers: Layers, + bird: Bird, + bug: Bug, + cat: Cat, + dog: Dog, + fish: Fish, + rat: Rat, + rabbit: Rabbit, + squirrel: Squirrel, + turtle: Turtle, + snail: Snail, + shrimp: Shrimp, + egg: Egg } export function getProjectIcon(iconId: string): LucideIcon { - return PROJECT_ICON_MAP[iconId as ProjectIconId] ?? LayoutDashboard + return PROJECT_ICON_MAP[iconId as ProjectIconId] ?? Cat } diff --git a/frontend/src/app/platform/types.ts b/frontend/src/app/platform/types.ts index 255eb85..3c4808f 100644 --- a/frontend/src/app/platform/types.ts +++ b/frontend/src/app/platform/types.ts @@ -12,13 +12,20 @@ export type Project = { createdAt: number } +/** Project icon ids: Lucide "Animals" category only */ export const PROJECT_ICON_IDS = [ - 'layout', - 'folder', - 'file-stack', - 'sparkles', - 'box', - 'layers', + 'bird', + 'bug', + 'cat', + 'dog', + 'fish', + 'rat', + 'rabbit', + 'squirrel', + 'turtle', + 'snail', + 'shrimp', + 'egg' ] as const export type ProjectIconId = (typeof PROJECT_ICON_IDS)[number] diff --git a/frontend/src/components/AppMenubar.tsx b/frontend/src/components/AppMenubar.tsx deleted file mode 100644 index 741b443..0000000 --- a/frontend/src/components/AppMenubar.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import React, { useEffect } from 'react' -import { - Menubar, - MenubarContent, - MenubarItem, - MenubarMenu, - MenubarSeparator, - MenubarTrigger, - MenubarSub, - MenubarSubTrigger, - MenubarSubContent, - MenubarCheckboxItem, -} from '@/components/ui/menubar' -import { Kbd, KbdGroup } from '@/components/ui/kbd' -import { useTheme } from '@/lib/themeContext' -import { Download, FolderOpen, Moon, Redo2, Sun, Undo2 } from 'lucide-react' - -type AppMenubarProps = { - onImport: () => void - onExport: () => void - undo: () => void - redo: () => void - canUndo: boolean - canRedo: boolean - onFitView?: () => void -} - -const UNDO_KEYS = { key: 'z', shiftKey: false } -const REDO_KEYS = { key: 'z', shiftKey: true } - -function matchKey(ev: KeyboardEvent, want: { key: string; shiftKey: boolean }) { - const mod = ev.ctrlKey || ev.metaKey - return ( - ev.key.toLowerCase() === want.key && - !!mod && - !!ev.shiftKey === want.shiftKey - ) -} - -export function AppMenubar({ onImport, onExport, undo, redo, canUndo, canRedo, onFitView }: AppMenubarProps) { - const { theme, setTheme } = useTheme() - - useEffect(() => { - const onKeyDown = (ev: KeyboardEvent) => { - if (matchKey(ev, UNDO_KEYS)) { - if (canUndo) { - ev.preventDefault() - ev.stopPropagation() - undo() - } - return - } - if (matchKey(ev, REDO_KEYS)) { - if (canRedo) { - ev.preventDefault() - ev.stopPropagation() - redo() - } - } - } - // Capture phase so we run before CodeMirror/inputs; then graph undo applies even when focus is in an editor - window.addEventListener('keydown', onKeyDown, true) - return () => window.removeEventListener('keydown', onKeyDown, true) - }, [undo, redo, canUndo, canRedo]) - - return ( - - - Project - - - - Import… - - - - Export… - - - - - Edit - - - - Undo - - - ⌘ + Z - - - - - - Redo - - - ⌘ + ⇧ + Z - - - - - - - View - - {onFitView && ( - - Fit View - - - ⌘0 - - - - )} - {onFitView && } - - Theme - - setTheme('light')} - className="gap-2" - > - - Light - - setTheme('dark')} - className="gap-2" - > - - Dark - - - - - - - ) -} diff --git a/frontend/src/components/FlowKeyboardShortcuts.tsx b/frontend/src/components/base/FlowKeyboardShortcuts.tsx similarity index 100% rename from frontend/src/components/FlowKeyboardShortcuts.tsx rename to frontend/src/components/base/FlowKeyboardShortcuts.tsx diff --git a/frontend/src/components/ui/sidebar.tsx b/frontend/src/components/ui/sidebar.tsx index 2c3df7c..c7ca99c 100644 --- a/frontend/src/components/ui/sidebar.tsx +++ b/frontend/src/components/ui/sidebar.tsx @@ -145,7 +145,7 @@ const SidebarProvider = React.forwardRef< } as React.CSSProperties } className={cn( - "group/sidebar-wrapper flex h-svh w-full has-[[data-variant=inset]]:bg-sidebar overflow-hidden", + "group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar", className )} ref={ref} @@ -308,7 +308,7 @@ const SidebarRail = React.forwardRef< onClick={toggleSidebar} title="Toggle Sidebar" className={cn( - "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] after:bg-transparent hover:after:bg-sidebar-border/50 group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex", + "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex", "[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize", "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize", "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar", diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 7c53242..652e10b 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -164,6 +164,12 @@ pre { scrollbar-color: hsl(var(--muted-foreground) / 0.35) transparent; } +*, +::before, +::after { + border-color: unset; +} + .dark * { scrollbar-color: hsl(var(--muted-foreground) / 0.4) transparent; } diff --git a/frontend/tailwind.config.cjs b/frontend/tailwind.config.cjs index 635ebf7..fb0f5cd 100644 --- a/frontend/tailwind.config.cjs +++ b/frontend/tailwind.config.cjs @@ -3,65 +3,65 @@ module.exports = { darkMode: ['class'], content: ['./index.html', './src/**/*.{ts,tsx,js,jsx}'], theme: { - extend: { - borderRadius: { - lg: 'var(--radius)', - md: 'calc(var(--radius) - 2px)', - sm: 'calc(var(--radius) - 4px)' - }, - colors: { - background: 'hsl(var(--background))', - foreground: 'hsl(var(--foreground))', - card: { - DEFAULT: 'hsl(var(--card))', - foreground: 'hsl(var(--card-foreground))' - }, - popover: { - DEFAULT: 'hsl(var(--popover))', - foreground: 'hsl(var(--popover-foreground))' - }, - primary: { - DEFAULT: 'hsl(var(--primary))', - foreground: 'hsl(var(--primary-foreground))' - }, - secondary: { - DEFAULT: 'hsl(var(--secondary))', - foreground: 'hsl(var(--secondary-foreground))' - }, - muted: { - DEFAULT: 'hsl(var(--muted))', - foreground: 'hsl(var(--muted-foreground))' - }, - accent: { - DEFAULT: 'hsl(var(--accent))', - foreground: 'hsl(var(--accent-foreground))' - }, - destructive: { - DEFAULT: 'hsl(var(--destructive))', - foreground: 'hsl(var(--destructive-foreground))' - }, - border: 'hsl(var(--border))', - input: 'hsl(var(--input))', - ring: 'hsl(var(--ring))', - chart: { - '1': 'hsl(var(--chart-1))', - '2': 'hsl(var(--chart-2))', - '3': 'hsl(var(--chart-3))', - '4': 'hsl(var(--chart-4))', - '5': 'hsl(var(--chart-5))' - }, - sidebar: { - DEFAULT: 'hsl(var(--sidebar-background))', - foreground: 'hsl(var(--sidebar-foreground))', - primary: 'hsl(var(--sidebar-primary))', - 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', - accent: 'hsl(var(--sidebar-accent))', - 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', - border: 'hsl(var(--sidebar-border))', - ring: 'hsl(var(--sidebar-ring))' - } - } - } - }, + extend: { + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + }, + colors: { + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + chart: { + '1': 'hsl(var(--chart-1))', + '2': 'hsl(var(--chart-2))', + '3': 'hsl(var(--chart-3))', + '4': 'hsl(var(--chart-4))', + '5': 'hsl(var(--chart-5))' + }, + sidebar: { + DEFAULT: 'hsl(var(--sidebar-background))', + foreground: 'hsl(var(--sidebar-foreground))', + primary: 'hsl(var(--sidebar-primary))', + 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', + accent: 'hsl(var(--sidebar-accent))', + 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', + border: 'hsl(var(--sidebar-border))', + ring: 'hsl(var(--sidebar-ring))' + } + } + } + }, plugins: [require('tailwindcss-animate')], }