- Mobile-responsive editor with bottom drawer, ribbon nav, HUD overlay - Touch support: single-finger tools, two-finger pinch-zoom - PWA: manifest, service worker, installability - Extract LayerTree from LayersPanel, shared utils (colorToCSS, initials, toolIcons) - Constants moved to src/constants.ts, clipboard state to editor store - reka-ui Popover/DropdownMenu in MobileHud Co-authored-by: Danila Poyarkov <dev@dannote.net>
5.2 KiB
5.2 KiB
1. Store & State
- 1.1 Add mobile state to editor store (
src/stores/editor.ts):activeRibbonTab: 'panels' | 'code' | 'ai' | null(defaultnull),panelMode: 'layers' | 'design'(default'design'),mobileDrawerSnap: 0 | 'half' | 'full' | 'closed'(default0),actionToast: string | null(defaultnull) - 1.2 Fix
showUIinitialization: change frommatchMedia(...)totrue
2. Extract LayerTree Component
- 2.1 Create
src/components/LayerTree.vue— extract tree logic from LayersPanel: reka-ui TreeRoot/TreeItem, drag reorder, expand/collapse, context menu, visibility icon. Import order: reka-ui → vue → @vueuse → icons → @/ → relative - 2.2 Refactor
src/components/LayersPanel.vueto use<LayerTree />
3. MobileRibbon Component
- 3.1 Create
src/components/MobileRibbon.vue— fixed bar at viewport bottom (fixed bottom-0 z-40), outside the drawer. Layers/Design as separate buttons (icon + label when active), Code/AI as icon-only buttons on right. 44px touch targets.env(safe-area-inset-bottom)padding. All tabs haveselect-none outline-none transition-colors. Nohover:states — mobile only uses state-based styles - 3.2 Swipe gestures on ribbon: swipe up opens drawer (half/full), swipe down closes.
SWIPE_THRESHOLD = 30,SWIPE_MAX_DURATION = 500 - 3.3 Tab tap toggles: tap active tab while drawer open → close drawer + reset
activeRibbonTabto null
4. MobileDrawer Component
- 4.1 Create
src/components/MobileDrawer.vue— custom drawer using CSStranslateY+transition(NOT vaul-vue). Snap states: closed, half (3/7 viewport), full (viewport - 44px ribbon - 94px HUD).renderSnapref withnextTick()+requestAnimationFrame()for smooth open animation - 4.2 Drag gestures on entire drawer body (not just handle). 50px threshold. Swipe down: full→half→closed. Swipe up: closed→half→full. Drag handle with
select-none - 4.3 Drawer header: compact PagesPanel (horizontal scrollable) with border-b
- 4.4 Panel content via
v-show: LayerTree, DesignPanel, CodePanel, ChatPanel.@touchstart.stop @touchmove.stopon content area to prevent canvas interaction
5. MobileHud Component
- 5.1 Create
src/components/MobileHud.vue— absolute overlay withpointer-events-noneroot,pointer-events-autoon interactive groups.@touchstart.stopon root - 5.2 Top-left: Undo/redo buttons (
size-8 rounded-full border border-border shadow-md select-none) + active tool indicator (border-accent/30, animated icon change viaTransition mode="out-in") - 5.3 Top-center: Online badge with peers dropdown (simple div toggle, not reka-ui Popover) + action toast (
toast-fadetransition, 800ms duration fromACTION_TOAST_DURATION) - 5.4 Top-right: Share button + burger menu (
onClickOutsidefor dismiss). Menu items: New, Open, Save, Export, Zoom to fit. Each with Lucide icon.border-none bg-transparent outline-none select-none
6. EditorView Responsive Layout
- 6.1 Add
isMobile = useMediaQuery('(max-width: 767px)')in EditorView. Conditional: desktop SplitterGroup vs mobile layout - 6.2 Mobile layout: EditorCanvas + MobileHud (collab props forwarded) + Toolbar + MobileRibbon + MobileDrawer
- 6.3 Wire
@tab-changeon MobileRibbon: auto-open drawer to half if collapsed
7. Keyboard Shortcut: ⌘J on mobile
- 7.1 In
use-keyboard.ts, detectisMobile. On ⌘J: mobile togglesactiveRibbonTabbetween current and'ai'(opens drawer if collapsed); desktop unchanged
8. Toolbar Mobile Adaptation
- 8.1 3 categories: Category 0 (drawing tools), Category 1 (Copy/Paste/Cut/Duplicate/Delete), Category 2 (Front/Back/Group/Ungroup/Lock). Arrow navigation buttons
- 8.2 Animated container width: active category
relative(sets width), othersabsolute left-2 top-1/2 -translate-y-1/2with opacity transition. Width viascrollWidthmeasurement +transition: width 250ms ease - 8.3 Internal clipboard:
mobileCopy(),mobileCut(),mobilePaste()using in-memory buffer - 8.4 Action toast:
onActionTap()setsstore.state.actionToastforACTION_TOAST_DURATION(800ms) - 8.5 All buttons:
select-none border-none,rounded-[8px]container,rounded-[6px]buttons,h-11container withsize-8buttons
9. Touch & Canvas
- 9.1
use-canvas-input.ts:syntheticMouse()helper creates MouseEvent from Touch. Single finger on non-HAND tool → delegate to mouse handlers. Two-finger → pinch-zoom (existing) - 9.2
use-canvas.ts: hide rulers on mobile via!isMobilecheck
10. Code Style Alignment
- 10.1 Import ordering: reka-ui → vue → icons → @vueuse → @/ → relative → type imports
- 10.2 No semicolons (codebase convention)
- 10.3
select-noneon all tappable elements - 10.4
outline-noneon focusable elements (MobileRibbon tabs, menu items) - 10.5 Magic numbers extracted:
ACTION_TOAST_DURATION,SWIPE_MAX_DURATION,SWIPE_THRESHOLD,RIBBON_H,HALF_FRAC,HUD_TOP - 10.6 No
hover:on mobile-only elements — onlyactive:for touch feedback - 10.7
border-none bg-transparenton menu/action buttons
11. Verification
- 11.1 Desktop layout unchanged at ≥768px
- 11.2
bun run checkpasses (0 errors)