feat: collapse the ribbon to its tab strip
A chevron in the tab strip drops the band, leaving the strip; persisted, so it survives a reload. Clicking a tab expands again, since a collapsed strip whose tabs do nothing is a trap. Word puts that chevron in the band's corner. It rides the strip so the band keeps its full width: a flex row can only reserve a column, never a corner. Skipped: Word floats the band over the document until the next click lands in the text.
This commit is contained in:
1 parent
33784b239f
commit
c334b5368e
6 files changed
+48
-2
No files matched your search
@@ -53,6 +53,15 @@ The band's height never changes: it scrolls horizontally with a hidden scrollbar
|
||||
collapsing groups into popovers the way Word does when the window narrows. That is the single
|
||||
biggest simplification here, and the reason two things below are necessary.
|
||||
|
||||
A chevron in the tab strip drops the band entirely, leaving the strip (`odf-editor-ribbon-collapsed`).
|
||||
Clicking any tab brings it back — Word instead floats it over the document until the next click
|
||||
lands in the text, which is the part not built.
|
||||
|
||||
Word puts that chevron in the band's bottom-right corner, and this one did too. Both ways of
|
||||
reserving that corner failed: as a layer it covered whichever group's dialog launcher reached the
|
||||
edge, and since the band scrolls, which one that is varies; in the flow it cost the band a column
|
||||
at every height. A flex row cannot reserve width in part of its height, so it moved to the strip.
|
||||
|
||||
- **A menu must leave the flow.** `.ribbon-body` clips its own overflow, so the `anchored` action
|
||||
(`ribbon/menu.svelte.ts`) switches a panel to `position: fixed` and pins it under its wrapper,
|
||||
clamped inside the window. Panels drop below the **whole band**, not below their own button —
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import { t } from '../../i18n/i18n.svelte';
|
||||
import { withShortcut } from '../../i18n/shortcut';
|
||||
import { shortcutHint } from '../../editor/shortcuts';
|
||||
import type { ChromeMode, ThemeMode } from '../../storage/theme';
|
||||
import { loadRibbonCollapsed, saveRibbonCollapsed, type ChromeMode, type ThemeMode } from '../../storage/theme';
|
||||
import type { StyleFamily } from '../../styles/styleSheet';
|
||||
import { DEFAULT_MARGINS, type PageMargins } from '../../storage/pageMargins';
|
||||
import type { Orientation } from '../../storage/pageOrientation';
|
||||
@@ -130,6 +130,9 @@
|
||||
let paragraphDialogOpen = $state(false);
|
||||
let tabsDialogOpen = $state(false);
|
||||
|
||||
let collapsed = $state(loadRibbonCollapsed());
|
||||
$effect(() => saveRibbonCollapsed(collapsed));
|
||||
|
||||
function run(fn?: () => void) {
|
||||
closeMenu();
|
||||
fn?.();
|
||||
@@ -202,7 +205,7 @@
|
||||
class="ribbon-tab"
|
||||
class:active={tab === id}
|
||||
class:contextual={!(TABS as readonly string[]).includes(id)}
|
||||
onclick={() => (tab = id)}
|
||||
onclick={() => { tab = id; collapsed = false; }}
|
||||
>
|
||||
{t().ribbon.tabs[id]}
|
||||
</button>
|
||||
@@ -225,6 +228,20 @@
|
||||
<span class="doc-name-ext">.odt</span>
|
||||
</div>
|
||||
|
||||
<!-- Word puts this chevron in the band's corner. Here it rides the strip: in
|
||||
the corner it covered whichever group's dialog launcher reached the edge,
|
||||
and reserving that corner costs the band a column at every height. -->
|
||||
<button
|
||||
class="qa-btn rb-collapse"
|
||||
class:rb-collapse-up={!collapsed}
|
||||
onclick={() => (collapsed = !collapsed)}
|
||||
title={collapsed ? t().ribbon.expand : t().ribbon.collapse}
|
||||
aria-label={collapsed ? t().ribbon.expand : t().ribbon.collapse}
|
||||
aria-expanded={!collapsed}
|
||||
>
|
||||
<Icon name="chevronDown" size={16} />
|
||||
</button>
|
||||
|
||||
<div class="appearance-wrap" use:clickOutside={'appearance'}>
|
||||
<button
|
||||
class="qa-btn"
|
||||
@@ -259,6 +276,7 @@
|
||||
<UiLanguagePicker />
|
||||
</div>
|
||||
|
||||
{#if !collapsed}
|
||||
<div class="ribbon-body" use:pinPanels>
|
||||
{#if tab === 'home'}
|
||||
<HomeTab {editor} {tick} bind:showFormattingMarks {onManageStyles} {onFind} onParagraphDialog={() => (paragraphDialogOpen = true)} />
|
||||
@@ -287,6 +305,7 @@
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<ParagraphDialog bind:open={paragraphDialogOpen} {editor} {tick} onTabs={() => (tabsDialogOpen = true)} />
|
||||
<TabsDialog bind:open={tabsDialogOpen} {editor} {tick} bind:tabIntervalCm />
|
||||
@@ -319,6 +338,10 @@
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Points down to bring the band back, up to send it away. */
|
||||
.rb-collapse :global(svg) { transition: transform 0.12s ease; }
|
||||
.rb-collapse-up :global(svg) { transform: rotate(180deg); }
|
||||
|
||||
/* Those pickers frame their trigger for the classic toolbar's tinted island. A
|
||||
ribbon row is borderless icon buttons, so the frame comes off and the hover
|
||||
tint takes over — same 30px box as .rb-icon, so a row reads as one strip. */
|
||||
|
||||
@@ -474,6 +474,8 @@ const de: Messages = {
|
||||
shapeFormat: 'Formformat',
|
||||
},
|
||||
saveAs: 'Speichern unter…',
|
||||
collapse: 'Menüband minimieren',
|
||||
expand: 'Menüband erweitern',
|
||||
export: 'Exportieren',
|
||||
find: 'Suchen',
|
||||
replace: 'Ersetzen',
|
||||
|
||||
@@ -478,6 +478,8 @@ const en = {
|
||||
shapeFormat: 'Shape Format',
|
||||
},
|
||||
saveAs: 'Save as…',
|
||||
collapse: 'Collapse the ribbon',
|
||||
expand: 'Expand the ribbon',
|
||||
export: 'Export',
|
||||
find: 'Find',
|
||||
replace: 'Replace',
|
||||
|
||||
@@ -20,6 +20,7 @@ Both margins and orientation are passed into the ODT export so the exported docu
|
||||
- **Theme:** `odf-editor-theme` — `'light' | 'dark' | 'allBlack' | 'auto'`.
|
||||
- **Toolbar expanded:** `odf-editor-toolbar-expanded` — boolean string.
|
||||
- **Chrome:** `odf-editor-chrome` — `'classic' | 'ribbon'`; absent = classic.
|
||||
- **Ribbon collapsed:** `odf-editor-ribbon-collapsed` — boolean string; absent = expanded.
|
||||
- **Formatting marks:** `odf-editor-formatting-marks` — boolean string.
|
||||
- **Ruler:** `odf-editor-ruler` — boolean string; absent = on.
|
||||
- **Zoom:** `odf-editor-zoom` — integer percent.
|
||||
|
||||
@@ -7,6 +7,7 @@ export type ChromeMode = 'classic' | 'ribbon';
|
||||
const THEME_KEY = 'odf-editor-theme';
|
||||
const TOOLBAR_KEY = 'odf-editor-toolbar-expanded';
|
||||
const CHROME_KEY = 'odf-editor-chrome';
|
||||
const RIBBON_COLLAPSED_KEY = 'odf-editor-ribbon-collapsed';
|
||||
const FORMATTING_MARKS_KEY = 'odf-editor-formatting-marks';
|
||||
const RULER_KEY = 'odf-editor-ruler';
|
||||
|
||||
@@ -36,6 +37,14 @@ export function saveChromeMode(mode: ChromeMode): void {
|
||||
localStorage.setItem(CHROME_KEY, mode);
|
||||
}
|
||||
|
||||
export function loadRibbonCollapsed(): boolean {
|
||||
return localStorage.getItem(RIBBON_COLLAPSED_KEY) === 'true';
|
||||
}
|
||||
|
||||
export function saveRibbonCollapsed(collapsed: boolean): void {
|
||||
localStorage.setItem(RIBBON_COLLAPSED_KEY, String(collapsed));
|
||||
}
|
||||
|
||||
export function loadFormattingMarks(): boolean {
|
||||
return localStorage.getItem(FORMATTING_MARKS_KEY) === 'true';
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user