feat: the table's four row/column inserts are big buttons
Rows & Columns follows Word: four big buttons carrying a 28px glyph and a two-line label, with the three deletions folded into one Entfernen dropdown beside them so the group keeps its width. Two lines cost the band 12px (--w-ribbon-h 96 -> 108). The labels size to min-content, not max-width, which a wrapped box ignores; and the inserts drop the reserved caret slot, without which the group's foot label sits 8px below every other group's.
This commit is contained in:
1 parent
2f3e48ecf9
commit
efd4e9c8a7
5 files changed
+65
-19
No files matched your search
@@ -46,7 +46,7 @@ so the split is invisible there.
|
||||
Ribbon.svelte
|
||||
├─ .ribbon-tabs File pill · quick access (save, undo, redo) · tabs · contextual tabs
|
||||
│ · spacer · document name · appearance · UI language
|
||||
└─ .ribbon-body the active tab's groups, a fixed --w-ribbon-h (96px) band
|
||||
└─ .ribbon-body the active tab's groups, a fixed --w-ribbon-h (108px) band
|
||||
```
|
||||
|
||||
The band's height never changes: it scrolls horizontally with a hidden scrollbar instead of
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import { CellSelection } from '@tiptap/pm/tables';
|
||||
import RibbonGroup from '../RibbonGroup.svelte';
|
||||
import RibbonButton from '../RibbonButton.svelte';
|
||||
import Icon from '../Icon.svelte';
|
||||
import type { IconName } from '../icons';
|
||||
import ColorPicker from '../../ColorPicker.svelte';
|
||||
import TableBorderPicker from '../../TableBorderPicker.svelte';
|
||||
import TableStylePicker from '../../TableStylePicker.svelte';
|
||||
@@ -76,6 +78,14 @@
|
||||
editor.chain().focus().setCellAttribute('cellPadding', next).run();
|
||||
}
|
||||
|
||||
// Word folds the three deletions into one big button's menu, so the four inserts
|
||||
// beside it are the group's whole width.
|
||||
const DELETES: { icon: IconName; label: () => string; cmd: (c: ChainedCommands) => ChainedCommands }[] = [
|
||||
{ icon: 'deleteRow', label: () => t().table.deleteRow, cmd: (c) => c.deleteRow() },
|
||||
{ icon: 'deleteCol', label: () => t().table.deleteColumn, cmd: (c) => c.deleteColumn() },
|
||||
{ icon: 'deleteTable', label: () => t().table.deleteTable, cmd: (c) => c.deleteTable() },
|
||||
];
|
||||
|
||||
const VALIGNS: { key: CellVerticalAlign | null; icon: 'alignTop' | 'alignMiddle' | 'alignBottom'; label: () => string }[] = [
|
||||
{ key: null, icon: 'alignTop', label: () => t().table.cellAlignTop },
|
||||
{ key: 'middle', icon: 'alignMiddle', label: () => t().table.cellAlignMiddle },
|
||||
@@ -124,23 +134,32 @@
|
||||
</RibbonGroup>
|
||||
{:else}
|
||||
<RibbonGroup label={t().ribbon.groups.rowsColumns}>
|
||||
<div class="rb-col">
|
||||
<RibbonButton variant="small" icon="rowAbove" label={t().table.insertRowAbove} onclick={() => run((c) => c.addRowBefore())} />
|
||||
<RibbonButton variant="small" icon="rowBelow" label={t().table.insertRowBelow} onclick={() => run((c) => c.addRowAfter())} />
|
||||
<div class="rb-menu-wrap" use:clickOutside={'tableDelete'}>
|
||||
<RibbonButton
|
||||
variant="big"
|
||||
icon="deleteRow"
|
||||
label={t().common.remove}
|
||||
title={t().common.remove}
|
||||
caret
|
||||
active={isMenuOpen('tableDelete')}
|
||||
onclick={() => toggleMenu('tableDelete')}
|
||||
/>
|
||||
{#if isMenuOpen('tableDelete')}
|
||||
<div class="ribbon-menu" use:anchored role="menu">
|
||||
{#each DELETES as d}
|
||||
<button role="menuitem" onclick={() => { closeMenu(); run(d.cmd); }}>
|
||||
<Icon name={d.icon} size={16} />{d.label()}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="rb-col">
|
||||
<RibbonButton variant="small" icon="colLeft" label={t().table.insertColumnLeft} onclick={() => run((c) => c.addColumnBefore())} />
|
||||
<RibbonButton variant="small" icon="colRight" label={t().table.insertColumnRight} onclick={() => run((c) => c.addColumnAfter())} />
|
||||
</div>
|
||||
</RibbonGroup>
|
||||
|
||||
<div class="ribbon-sep"></div>
|
||||
|
||||
<RibbonGroup label={t().common.remove}>
|
||||
<div class="rb-col">
|
||||
<RibbonButton variant="small" icon="deleteRow" label={t().table.deleteRow} onclick={() => run((c) => c.deleteRow())} />
|
||||
<RibbonButton variant="small" icon="deleteCol" label={t().table.deleteColumn} onclick={() => run((c) => c.deleteColumn())} />
|
||||
<RibbonButton variant="small" icon="deleteTable" label={t().table.deleteTable} onclick={() => run((c) => c.deleteTable())} />
|
||||
<div class="rb-mini-sep"></div>
|
||||
<div class="inserts">
|
||||
<RibbonButton variant="big" icon="rowAbove" label={t().ribbon.insertAbove} onclick={() => run((c) => c.addRowBefore())} />
|
||||
<RibbonButton variant="big" icon="rowBelow" label={t().ribbon.insertBelow} onclick={() => run((c) => c.addRowAfter())} />
|
||||
<RibbonButton variant="big" icon="colLeft" label={t().ribbon.insertLeft} onclick={() => run((c) => c.addColumnBefore())} />
|
||||
<RibbonButton variant="big" icon="colRight" label={t().ribbon.insertRight} onclick={() => run((c) => c.addColumnAfter())} />
|
||||
</div>
|
||||
</RibbonGroup>
|
||||
|
||||
@@ -227,6 +246,24 @@
|
||||
|
||||
.rb-menu-wrap { position: relative; }
|
||||
|
||||
/* Wrapped onto two lines, as in Word — on one they are half the group's width.
|
||||
`min-content` is what keeps the button off the label's unwrapped width, and
|
||||
the caret slot goes because the two lines already reach the group's foot. */
|
||||
.inserts {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.inserts :global(.rb-label) {
|
||||
width: min-content;
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.inserts :global(.rb-stack-caret) { display: none; }
|
||||
|
||||
/* The dialog positions itself in viewport coordinates, so the trigger only has
|
||||
to hand it the button's rect. */
|
||||
.split-trigger {
|
||||
|
||||
@@ -537,6 +537,10 @@ const de: Messages = {
|
||||
margins: 'Seitenränder',
|
||||
size: 'Größe',
|
||||
breaks: 'Umbrüche',
|
||||
insertAbove: 'Darüber einfügen',
|
||||
insertBelow: 'Darunter einfügen',
|
||||
insertLeft: 'Links einfügen',
|
||||
insertRight: 'Rechts einfügen',
|
||||
sectionBreak: 'Abschnittsumbruch',
|
||||
sectionBreakHint: 'Beginnt eine eigene Kopf- und Fußzeile',
|
||||
indentLeft: 'Einzug links',
|
||||
|
||||
@@ -541,6 +541,10 @@ const en = {
|
||||
margins: 'Margins',
|
||||
size: 'Size',
|
||||
breaks: 'Breaks',
|
||||
insertAbove: 'Insert above',
|
||||
insertBelow: 'Insert below',
|
||||
insertLeft: 'Insert left',
|
||||
insertRight: 'Insert right',
|
||||
sectionBreak: 'Section break',
|
||||
sectionBreakHint: 'Starts its own header and footer',
|
||||
indentLeft: 'Indent left',
|
||||
|
||||
@@ -280,8 +280,9 @@
|
||||
--w-accent-soft: #e8f1fb;
|
||||
--w-menu-shadow: 0 8px 24px rgb(0 0 0 / 18%);
|
||||
--w-font: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
|
||||
/* Body height: three control rows plus the group-label row. A tuning knob. */
|
||||
--w-ribbon-h: 96px;
|
||||
/* Body height: three control rows plus the group-label row, and a big button's
|
||||
label may take two of them. A tuning knob. */
|
||||
--w-ribbon-h: 108px;
|
||||
}
|
||||
|
||||
/* Word's dark mode: the Fluent dark neutrals, and the accent lightened because
|
||||
|
||||
Reference in new issue
Block a user