22 * Minimal embedded toolbar for Phoenix live preview.
33 * Read mode: "Edit" button
44 * Edit mode: Format row + "Done" button
5- * Responsive: collapses into dropdown groups when narrow.
5+ * Responsive: progressively collapses groups into dropdowns as width shrinks.
6+ * Level 0: all expanded
7+ * Level 1: block elements collapse
8+ * Level 2: block elements + lists collapse
9+ * Level 3: all groups collapse
610 */
711import {
812 createIcons ,
@@ -30,10 +34,12 @@ import { t, tp } from "../core/i18n.js";
3034
3135let toolbar = null ;
3236let resizeObserver = null ;
33- let isCollapsed = false ;
37+ let collapseLevel = 0 ; // 0=expanded, 1=blocks, 2=blocks+lists, 3=all
3438
35- // Minimum width needed for the expanded toolbar (block-type-select ~90 + 15 buttons*24 + dividers + done ~50)
36- const COLLAPSE_WIDTH = 520 ;
39+ // Width thresholds for progressive collapse
40+ const THRESHOLD_BLOCKS = 480 ; // collapse block elements first
41+ const THRESHOLD_LISTS = 390 ; // then lists
42+ const THRESHOLD_TEXT = 300 ; // finally text formatting
3743
3844const allIcons = { Bold, Italic, Strikethrough, Underline, Code, Link, List, ListOrdered,
3945 ListChecks, Quote, Minus, Table, FileCode, ChevronDown, Type, MoreHorizontal, Pencil } ;
@@ -59,7 +65,7 @@ function render() {
5965 }
6066
6167 if ( state . editMode ) {
62- renderEditMode ( isCollapsed ) ;
68+ renderEditMode ( collapseLevel ) ;
6369 setupResponsiveToggle ( ) ;
6470 } else {
6571 renderReadMode ( ) ;
@@ -85,7 +91,18 @@ function renderReadMode() {
8591 }
8692}
8793
88- function renderEditMode ( collapsed ) {
94+ function btn ( id , icon , tooltip ) {
95+ return `<button class="toolbar-btn format-btn" id="${ id } " data-tooltip="${ tooltip } " aria-pressed="false"><i data-lucide="${ icon } "></i></button>` ;
96+ }
97+
98+ function dropdown ( group , triggerIcon , tooltip , content ) {
99+ return `<div class="toolbar-dropdown" data-group="${ group } ">
100+ <button class="toolbar-btn toolbar-dropdown-trigger" data-tooltip="${ tooltip } "><i data-lucide="${ triggerIcon } "></i><i data-lucide="chevron-down" class="dropdown-chevron"></i></button>
101+ <div class="toolbar-dropdown-panel">${ content } </div>
102+ </div>` ;
103+ }
104+
105+ function renderEditMode ( level ) {
89106 const isMac = / M a c | i P h o n e | i P a d / . test ( navigator . platform ) ;
90107 const mod = isMac ? "\u2318" : "Ctrl" ;
91108
@@ -97,9 +114,6 @@ function renderEditMode(collapsed) {
97114 <option value="<h3>">${ t ( "slash.heading3" ) || "Heading 3" } </option>
98115 </select>` ;
99116
100- const btn = ( id , icon , tooltip ) =>
101- `<button class="toolbar-btn format-btn" id="${ id } " data-tooltip="${ tooltip } " aria-pressed="false"><i data-lucide="${ icon } "></i></button>` ;
102-
103117 const textBtns = [
104118 btn ( "emb-bold" , "bold" , tp ( "format.bold" , { mod } ) || "Bold" ) ,
105119 btn ( "emb-italic" , "italic" , tp ( "format.italic" , { mod } ) || "Italic" ) ,
@@ -122,37 +136,31 @@ function renderEditMode(collapsed) {
122136 btn ( "emb-codeblock" , "file-code" , t ( "format.code_block" ) || "Code block" )
123137 ] . join ( "" ) ;
124138
125- let formatRow ;
126- if ( collapsed ) {
127- formatRow = `
128- <div class="format-row">
129- ${ blockTypeSelect }
130- <div class="toolbar-divider"></div>
131- <div class="toolbar-dropdown" data-group="text">
132- <button class="toolbar-btn toolbar-dropdown-trigger" data-tooltip="${ t ( "format.text_formatting" ) || "Text formatting" } "><i data-lucide="type"></i><i data-lucide="chevron-down" class="dropdown-chevron"></i></button>
133- <div class="toolbar-dropdown-panel">${ textBtns } </div>
134- </div>
135- <div class="toolbar-dropdown" data-group="lists">
136- <button class="toolbar-btn toolbar-dropdown-trigger" data-tooltip="${ t ( "format.lists" ) || "Lists" } "><i data-lucide="list"></i><i data-lucide="chevron-down" class="dropdown-chevron"></i></button>
137- <div class="toolbar-dropdown-panel">${ listBtns } </div>
138- </div>
139- <div class="toolbar-dropdown" data-group="blocks">
140- <button class="toolbar-btn toolbar-dropdown-trigger" data-tooltip="${ t ( "format.more_elements" ) || "More" } "><i data-lucide="more-horizontal"></i><i data-lucide="chevron-down" class="dropdown-chevron"></i></button>
141- <div class="toolbar-dropdown-panel">${ blockBtns } </div>
142- </div>
143- </div>` ;
144- } else {
145- formatRow = `
139+ // Build the text section (inline or dropdown)
140+ const textSection = level >= 3
141+ ? dropdown ( "text" , "type" , t ( "format.text_formatting" ) || "Text formatting" , textBtns )
142+ : textBtns ;
143+
144+ // Build the list section (inline or dropdown)
145+ const listSection = level >= 2
146+ ? dropdown ( "lists" , "list" , t ( "format.lists" ) || "Lists" , listBtns )
147+ : listBtns ;
148+
149+ // Build the block section (inline or dropdown)
150+ const blockSection = level >= 1
151+ ? dropdown ( "blocks" , "more-horizontal" , t ( "format.more_elements" ) || "More" , blockBtns )
152+ : blockBtns ;
153+
154+ const formatRow = `
146155 <div class="format-row">
147156 ${ blockTypeSelect }
148157 <div class="toolbar-divider"></div>
149- ${ textBtns }
158+ ${ textSection }
150159 <div class="toolbar-divider"></div>
151- ${ listBtns }
160+ ${ listSection }
152161 <div class="toolbar-divider"></div>
153- ${ blockBtns }
162+ ${ blockSection }
154163 </div>` ;
155- }
156164
157165 toolbar . innerHTML = `<div class="embedded-toolbar">
158166 ${ formatRow }
@@ -166,7 +174,7 @@ function renderEditMode(collapsed) {
166174
167175 wireFormatButtons ( ) ;
168176 wireBlockTypeSelect ( ) ;
169- if ( collapsed ) {
177+ if ( level > 0 ) {
170178 wireDropdowns ( ) ;
171179 }
172180 wireDoneButton ( ) ;
@@ -250,15 +258,20 @@ function wireDoneButton() {
250258 }
251259}
252260
261+ function widthToCollapseLevel ( width ) {
262+ if ( width < THRESHOLD_TEXT ) return 3 ;
263+ if ( width < THRESHOLD_LISTS ) return 2 ;
264+ if ( width < THRESHOLD_BLOCKS ) return 1 ;
265+ return 0 ;
266+ }
267+
253268function setupResponsiveToggle ( ) {
254- // Observe the #toolbar element (grid-constrained) not .embedded-toolbar (can overflow)
255269 function checkWidth ( ) {
256270 const width = toolbar . offsetWidth ;
257- const shouldCollapse = width < COLLAPSE_WIDTH ;
258- if ( shouldCollapse !== isCollapsed ) {
259- isCollapsed = shouldCollapse ;
260- renderEditMode ( isCollapsed ) ;
261- // Re-attach observer after re-render
271+ const newLevel = widthToCollapseLevel ( width ) ;
272+ if ( newLevel !== collapseLevel ) {
273+ collapseLevel = newLevel ;
274+ renderEditMode ( collapseLevel ) ;
262275 resizeObserver . observe ( toolbar ) ;
263276 }
264277 }
0 commit comments