Add mobile overflow menu to Docs formatting toolbar#132
Conversation
Apply the same mobile-responsive pattern used in Sheets: on viewports narrower than 768px, show only core formatting tools (undo/redo, bold, italic, underline, text/highlight color) inline and collapse styles, insert, alignment, list, and export actions into a ⋮ overflow menu. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
📝 WalkthroughWalkthroughAdds mobile-responsive behavior to the docs formatting toolbar using a Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Verification: verify:selfResult: ✅ PASS in 111.8s
Verification: verify:integrationResult: ✅ PASS |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/tasks/active/20260415-docs-mobile-toolbar-todo.md`:
- Around line 1-253: Create a paired lessons file for the existing todo by
adding a new markdown file named 20260415-docs-mobile-toolbar-lessons.md
alongside the todo; in that file include a short summary of what was implemented
(mobile overflow menu for DocsFormattingToolbar), key implementation decisions
(useIsMobile hook, DropdownMenu sections, kept core tools inline), any gotchas
or edge-cases encountered (event handlers, file input for image upload, editor
focus calls), verification steps you ran (lint, verify:fast, manual
mobile/desktop checks) and a short checklist of follow-ups or known issues for
future work so reviewers have context for the change.
In `@packages/frontend/src/app/docs/docs-formatting-toolbar.tsx`:
- Around line 821-851: The mobile Insert dropdown removed the existing
image-by-URL and variable-size table flows and hard-coded disk upload + a 3×3
table; restore parity by replacing the inline image/file handler and fixed table
action with the same components/handlers used on desktop—use InsertImageDropdown
(or the same URL dialog flow used there) alongside the existing
insertImageFromFile logic (e.g., keep the file input branch) and replace the
fixed editor?.insertTable(3,3) call with the TableDropdown component or the same
table-size picker logic so users can choose table dimensions; ensure to call
editor?.focus() after insertion as before.
- Around line 888-892: Add a labeled "Export" section header before the export
menu item so the toolbar matches other labeled groups; insert a
DropdownMenuLabel (or the existing label component used for
"Styles"/"Insert"/"Align"/"List") immediately after the DropdownMenuSeparator
and above the DropdownMenuItem that calls handleExportDocx, keeping the disabled
and onClick props as-is so the export entry remains functional and visually
grouped.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: efa16e96-4213-4734-9e24-cc4a2cde22a3
📒 Files selected for processing (2)
docs/tasks/active/20260415-docs-mobile-toolbar-todo.mdpackages/frontend/src/app/docs/docs-formatting-toolbar.tsx
| # Docs Mobile Toolbar Implementation Plan | ||
|
|
||
| > **For agentic workers:** Use superpowers:subagent-driven-development or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. | ||
|
|
||
| **Goal:** Add mobile-responsive overflow menu to the Docs formatting toolbar, matching the Sheets toolbar pattern. | ||
|
|
||
| **Architecture:** Add `useIsMobile()` hook to `DocsFormattingToolbar`. On mobile (<768px), show only core tools (Undo, Redo, Bold, Italic, Underline, Text Color, Highlight Color) inline, and collapse everything else (Styles dropdown, Link, Image, Table, Alignment, Lists, Indent, Export) into a single `⋮` overflow `DropdownMenu` with labeled sections and separators. | ||
|
|
||
| **Tech Stack:** React, Radix DropdownMenu, Tabler Icons, `useIsMobile()` hook | ||
|
|
||
| --- | ||
|
|
||
| ## File Map | ||
|
|
||
| - **Modify:** `packages/frontend/src/app/docs/docs-formatting-toolbar.tsx` | ||
| - Add `useIsMobile` import | ||
| - Wrap desktop-only items in `{!isMobile && (...)}` | ||
| - Add mobile overflow menu in `{isMobile && (...)}` | ||
|
|
||
| No new files needed. Single-file change following the established Sheets pattern. | ||
|
|
||
| --- | ||
|
|
||
| ### Task 1: Add mobile hook and hide desktop-only items | ||
|
|
||
| **Files:** | ||
| - Modify: `packages/frontend/src/app/docs/docs-formatting-toolbar.tsx` | ||
|
|
||
| - [ ] **Step 1: Add imports** | ||
|
|
||
| Add `useIsMobile` hook and new icons needed for the overflow menu: | ||
|
|
||
| ```tsx | ||
| import { useIsMobile } from "@/hooks/use-mobile"; | ||
| import { | ||
| IconDotsVertical, | ||
| } from "@tabler/icons-react"; | ||
| ``` | ||
|
|
||
| Also add these imports from the dropdown-menu (already partially imported): | ||
|
|
||
| ```tsx | ||
| import { | ||
| DropdownMenu, | ||
| DropdownMenuContent, | ||
| DropdownMenuTrigger, | ||
| DropdownMenuItem, | ||
| DropdownMenuLabel, // NEW | ||
| DropdownMenuSeparator, // NEW | ||
| } from "@/components/ui/dropdown-menu"; | ||
| ``` | ||
|
|
||
| - [ ] **Step 2: Call the hook** | ||
|
|
||
| Inside `DocsFormattingToolbar`, right after the existing state declarations: | ||
|
|
||
| ```tsx | ||
| const isMobile = useIsMobile(); | ||
| ``` | ||
|
|
||
| - [ ] **Step 3: Hide desktop-only items on mobile** | ||
|
|
||
| Wrap the following sections with `{!isMobile && (...)}`: | ||
|
|
||
| 1. **Styles dropdown** (lines 501-535) — the `min-w-[110px]` block type dropdown | ||
| 2. **The separator after Styles** (line 537) | ||
| 3. **Insert group** — Link button, InsertImageDropdown, TableDropdown (lines 654-669) | ||
| 4. **The separator after Insert** (line 671) | ||
| 5. **Block styles group** — Alignment dropdown, Numbered list, Bulleted list, Indent decrease, Indent increase (lines 674-759) | ||
| 6. **The separator before Export** (line 761) | ||
| 7. **Export DOCX button** (lines 764-776) | ||
|
|
||
| Keep always visible: Undo, Redo, separator, Bold, Italic, Underline, Text Color, Highlight Color. | ||
|
|
||
| - [ ] **Step 4: Run lint to verify no syntax errors** | ||
|
|
||
| Run: `cd /Users/hackerwins/Development/wafflebase/wafflebase && pnpm lint` | ||
| Expected: PASS (no errors in docs-formatting-toolbar.tsx) | ||
|
|
||
| --- | ||
|
|
||
| ### Task 2: Add the mobile overflow menu | ||
|
|
||
| **Files:** | ||
| - Modify: `packages/frontend/src/app/docs/docs-formatting-toolbar.tsx` | ||
|
|
||
| - [ ] **Step 1: Add the overflow menu after the Highlight Color dropdown** | ||
|
|
||
| Right after the closing `</DropdownMenu>` for Highlight Color and before the `{!isMobile && (` block, add: | ||
|
|
||
| ```tsx | ||
| {isMobile && ( | ||
| <> | ||
| <ToolbarSeparator /> | ||
| <DropdownMenu> | ||
| <DropdownMenuTrigger asChild> | ||
| <button | ||
| className="inline-flex h-7 w-7 cursor-pointer items-center justify-center rounded-md text-sm hover:bg-muted" | ||
| aria-label="More formatting options" | ||
| > | ||
| <IconDotsVertical size={16} /> | ||
| </button> | ||
| </DropdownMenuTrigger> | ||
| <DropdownMenuContent align="end"> | ||
| {/* ── Styles ── */} | ||
| <DropdownMenuLabel>Styles</DropdownMenuLabel> | ||
| {STYLE_OPTIONS.map((opt) => ( | ||
| <DropdownMenuItem | ||
| key={opt.label} | ||
| onClick={() => | ||
| handleBlockType( | ||
| opt.type, | ||
| opt.headingLevel | ||
| ? { headingLevel: opt.headingLevel } | ||
| : undefined, | ||
| ) | ||
| } | ||
| > | ||
| <span className={opt.className}>{opt.label}</span> | ||
| </DropdownMenuItem> | ||
| ))} | ||
| <DropdownMenuSeparator /> | ||
|
|
||
| {/* ── Insert ── */} | ||
| <DropdownMenuLabel>Insert</DropdownMenuLabel> | ||
| <DropdownMenuItem onClick={handleInsertLink}> | ||
| <IconLink size={16} className="mr-2" /> | ||
| Link | ||
| </DropdownMenuItem> | ||
| <DropdownMenuItem | ||
| onClick={() => { | ||
| /* Trigger file input for image upload */ | ||
| const input = document.createElement("input"); | ||
| input.type = "file"; | ||
| input.accept = "image/*"; | ||
| input.onchange = async (e) => { | ||
| const file = (e.target as HTMLInputElement).files?.[0]; | ||
| if (file && editor) { | ||
| await insertImageFromFile(editor, file); | ||
| } | ||
| }; | ||
| input.click(); | ||
| }} | ||
| > | ||
| <IconPhoto size={16} className="mr-2" /> | ||
| Image | ||
| </DropdownMenuItem> | ||
| <DropdownMenuItem | ||
| onClick={() => { | ||
| editor?.insertTable(3, 3); | ||
| editor?.focus(); | ||
| }} | ||
| > | ||
| <IconTable size={16} className="mr-2" /> | ||
| Table (3×3) | ||
| </DropdownMenuItem> | ||
| <DropdownMenuSeparator /> | ||
|
|
||
| {/* ── Align ── */} | ||
| <DropdownMenuLabel>Align</DropdownMenuLabel> | ||
| <DropdownMenuItem onClick={() => handleAlign("left")}> | ||
| <IconAlignLeft size={16} className="mr-2" /> | ||
| Left | ||
| </DropdownMenuItem> | ||
| <DropdownMenuItem onClick={() => handleAlign("center")}> | ||
| <IconAlignCenter size={16} className="mr-2" /> | ||
| Center | ||
| </DropdownMenuItem> | ||
| <DropdownMenuItem onClick={() => handleAlign("right")}> | ||
| <IconAlignRight size={16} className="mr-2" /> | ||
| Right | ||
| </DropdownMenuItem> | ||
| <DropdownMenuItem onClick={() => handleAlign("justify")}> | ||
| <IconAlignJustified size={16} className="mr-2" /> | ||
| Justify | ||
| </DropdownMenuItem> | ||
| <DropdownMenuSeparator /> | ||
|
|
||
| {/* ── List ── */} | ||
| <DropdownMenuLabel>List</DropdownMenuLabel> | ||
| <DropdownMenuItem | ||
| onClick={() => { | ||
| editor?.toggleList("ordered"); | ||
| editor?.focus(); | ||
| }} | ||
| > | ||
| <IconListNumbers size={16} className="mr-2" /> | ||
| Numbered list | ||
| </DropdownMenuItem> | ||
| <DropdownMenuItem | ||
| onClick={() => { | ||
| editor?.toggleList("unordered"); | ||
| editor?.focus(); | ||
| }} | ||
| > | ||
| <IconList size={16} className="mr-2" /> | ||
| Bulleted list | ||
| </DropdownMenuItem> | ||
| <DropdownMenuItem | ||
| onClick={() => { | ||
| editor?.outdent(); | ||
| editor?.focus(); | ||
| }} | ||
| > | ||
| <IconIndentDecrease size={16} className="mr-2" /> | ||
| Decrease indent | ||
| </DropdownMenuItem> | ||
| <DropdownMenuItem | ||
| onClick={() => { | ||
| editor?.indent(); | ||
| editor?.focus(); | ||
| }} | ||
| > | ||
| <IconIndentIncrease size={16} className="mr-2" /> | ||
| Increase indent | ||
| </DropdownMenuItem> | ||
| <DropdownMenuSeparator /> | ||
|
|
||
| {/* ── Export ── */} | ||
| <DropdownMenuItem onClick={handleExportDocx} disabled={!editor || exporting}> | ||
| <IconFileDownload size={16} className="mr-2" /> | ||
| Export as DOCX | ||
| </DropdownMenuItem> | ||
| </DropdownMenuContent> | ||
| </DropdownMenu> | ||
| </> | ||
| )} | ||
| ``` | ||
|
|
||
| - [ ] **Step 2: Run verify:fast** | ||
|
|
||
| Run: `cd /Users/hackerwins/Development/wafflebase/wafflebase && pnpm verify:fast` | ||
| Expected: PASS | ||
|
|
||
| - [ ] **Step 3: Manual test — resize browser to < 768px** | ||
|
|
||
| Open the Docs editor, resize browser width below 768px: | ||
| - Verify: Only Undo, Redo, Bold, Italic, Underline, Text Color, Highlight Color, and ⋮ button visible | ||
| - Verify: ⋮ menu opens and shows Styles, Insert, Align, List, Export sections | ||
| - Verify: Each menu item triggers the correct action | ||
| - Verify: Desktop (>768px) shows all items as before | ||
|
|
||
| - [ ] **Step 4: Commit** | ||
|
|
||
| ```bash | ||
| git add packages/frontend/src/app/docs/docs-formatting-toolbar.tsx | ||
| git commit -m "Add mobile overflow menu to Docs formatting toolbar | ||
|
|
||
| Apply the same mobile-responsive pattern used in Sheets: on viewports | ||
| narrower than 768px, show only core formatting tools (undo/redo, bold, | ||
| italic, underline, text/highlight color) inline and collapse styles, | ||
| insert, alignment, list, and export actions into a ⋮ overflow menu." | ||
| ``` |
There was a problem hiding this comment.
Add the paired lessons file for this task.
This is a non-trivial task, but only the -todo.md file was added. Please add docs/tasks/active/20260415-docs-mobile-toolbar-lessons.md alongside it.
As per coding guidelines, "For non-trivial tasks, use paired files in docs/tasks/active/: YYYYMMDD-<slug>-todo.md and YYYYMMDD-<slug>-lessons.md."
🧰 Tools
🪛 LanguageTool
[style] ~72-~72: The adverb ‘always’ is usually put before the verb ‘Keep’.
Context: ...Export DOCX button (lines 764-776) Keep always visible: Undo, Redo, separator, Bold, I...
(ADVERB_WORD_ORDER)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/tasks/active/20260415-docs-mobile-toolbar-todo.md` around lines 1 - 253,
Create a paired lessons file for the existing todo by adding a new markdown file
named 20260415-docs-mobile-toolbar-lessons.md alongside the todo; in that file
include a short summary of what was implemented (mobile overflow menu for
DocsFormattingToolbar), key implementation decisions (useIsMobile hook,
DropdownMenu sections, kept core tools inline), any gotchas or edge-cases
encountered (event handlers, file input for image upload, editor focus calls),
verification steps you ran (lint, verify:fast, manual mobile/desktop checks) and
a short checklist of follow-ups or known issues for future work so reviewers
have context for the change.
| <DropdownMenuLabel>Insert</DropdownMenuLabel> | ||
| <DropdownMenuItem onClick={handleInsertLink}> | ||
| <IconLink size={16} className="mr-2" /> | ||
| Link | ||
| </DropdownMenuItem> | ||
| <DropdownMenuItem | ||
| onClick={() => { | ||
| const input = document.createElement("input"); | ||
| input.type = "file"; | ||
| input.accept = "image/*"; | ||
| input.onchange = async (e) => { | ||
| const file = (e.target as HTMLInputElement).files?.[0]; | ||
| if (file && editor) { | ||
| await insertImageFromFile(editor, file); | ||
| } | ||
| }; | ||
| input.click(); | ||
| }} | ||
| > | ||
| <IconPhoto size={16} className="mr-2" /> | ||
| Image | ||
| </DropdownMenuItem> | ||
| <DropdownMenuItem | ||
| onClick={() => { | ||
| editor?.insertTable(3, 3); | ||
| editor?.focus(); | ||
| }} | ||
| > | ||
| <IconTable size={16} className="mr-2" /> | ||
| Table (3×3) | ||
| </DropdownMenuItem> |
There was a problem hiding this comment.
Keep mobile Insert actions at feature parity with desktop.
Below 768px this stops exposing two existing flows: image-by-URL and variable table sizing. The desktop toolbar still supports both via InsertImageDropdown and TableDropdown, but the mobile menu now only uploads from disk and inserts a fixed 3×3 table. That is a behavior regression, not just a responsive rearrangement.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/frontend/src/app/docs/docs-formatting-toolbar.tsx` around lines 821
- 851, The mobile Insert dropdown removed the existing image-by-URL and
variable-size table flows and hard-coded disk upload + a 3×3 table; restore
parity by replacing the inline image/file handler and fixed table action with
the same components/handlers used on desktop—use InsertImageDropdown (or the
same URL dialog flow used there) alongside the existing insertImageFromFile
logic (e.g., keep the file input branch) and replace the fixed
editor?.insertTable(3,3) call with the TableDropdown component or the same
table-size picker logic so users can choose table dimensions; ensure to call
editor?.focus() after insertion as before.
| <DropdownMenuSeparator /> | ||
| <DropdownMenuItem onClick={handleExportDocx} disabled={!editor || exporting}> | ||
| <IconFileDownload size={16} className="mr-2" /> | ||
| Export as DOCX | ||
| </DropdownMenuItem> |
There was a problem hiding this comment.
Add the missing Export section header.
The overflow has labeled Styles / Insert / Align / List sections, but Export is just a trailing item after a separator. The PR objective/test plan calls for an Export section as well.
✏️ Proposed fix
<DropdownMenuSeparator />
+ <DropdownMenuLabel>Export</DropdownMenuLabel>
<DropdownMenuItem onClick={handleExportDocx} disabled={!editor || exporting}>
<IconFileDownload size={16} className="mr-2" />
Export as DOCX📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <DropdownMenuSeparator /> | |
| <DropdownMenuItem onClick={handleExportDocx} disabled={!editor || exporting}> | |
| <IconFileDownload size={16} className="mr-2" /> | |
| Export as DOCX | |
| </DropdownMenuItem> | |
| <DropdownMenuSeparator /> | |
| <DropdownMenuLabel>Export</DropdownMenuLabel> | |
| <DropdownMenuItem onClick={handleExportDocx} disabled={!editor || exporting}> | |
| <IconFileDownload size={16} className="mr-2" /> | |
| Export as DOCX | |
| </DropdownMenuItem> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/frontend/src/app/docs/docs-formatting-toolbar.tsx` around lines 888
- 892, Add a labeled "Export" section header before the export menu item so the
toolbar matches other labeled groups; insert a DropdownMenuLabel (or the
existing label component used for "Styles"/"Insert"/"Align"/"List") immediately
after the DropdownMenuSeparator and above the DropdownMenuItem that calls
handleExportDocx, keeping the disabled and onClick props as-is so the export
entry remains functional and visually grouped.
|
Regarding the review comments: Lessons file — Skipping for this small change. Mobile Insert parity (image-by-URL, variable table sizing) — Intentionally simplified for mobile in this PR. The grid picker is hover-based (not touch-friendly) and image-by-URL is a rare mobile use case. Will address in a follow-up if needed. Export section label — Export is a single item, so the label adds visual noise. Will reconsider if more export options are added. |
Summary
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit