ZDDC/tables/css/table.css
ZDDC 1721b4b1db feat(tables): explicit Save button + clearer dirty-row marker
Three triggers for flushing pending edits:
  - Save button in the toolbar — shown only when ≥1 row is dirty,
    label reads "Save (N unsaved)". Disappears after a clean settle.
  - Ctrl+S (Cmd+S) anywhere on the page, capturing-phase so it beats
    the browser's "Save Page As" default.
  - focusout of #table-root with a relatedTarget outside the grid —
    catches "edit cell, click a header link, expect it to save".

The row-blur trigger stays — moving between rows still flushes. The
new triggers fill the gap when the user edits one row and then leaves
the grid entirely without first navigating to another row.

Dirty marker gets a 4px (was 3px) left swatch AND a faint blue
background tint on the row, so "unsaved" reads as a row state rather
than a small marker on the edge.

editor.setDraft / clearDraftField notify save.onDraftsChanged,
which refreshes the Save button + reapplies the dirty class.
saveRow on 200/201/202 also refreshes the button so it disappears
the moment its row settles.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 08:38:35 -05:00

209 lines
6.3 KiB
CSS

/* tables/ — directory-of-YAML table view. Reuses tokens from shared/base.css. */
.table-main {
padding: var(--spacing-md);
max-width: 100%;
}
.table-description {
margin: 0 0 var(--spacing-md);
color: var(--color-text-muted);
font-size: 0.95rem;
}
.table-status {
margin: 0 0 var(--spacing-md);
padding: var(--spacing-sm) var(--spacing-md);
background: var(--color-bg-warning, #fff8e6);
border: 1px solid var(--color-border, #d6cfa3);
border-radius: var(--radius-sm, 4px);
}
.table-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--spacing-md);
margin: 0 0 var(--spacing-sm);
}
.table-toolbar__left,
.table-toolbar__right {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
#table-add-row {
text-decoration: none;
}
.table-rowcount {
color: var(--color-text-muted);
font-size: 0.9rem;
}
.table-scroll {
overflow: auto;
max-height: calc(100vh - 200px);
border: 1px solid var(--color-border, #d8d8d8);
border-radius: var(--radius-sm, 4px);
}
.zddc-table {
border-collapse: collapse;
width: 100%;
font-size: 0.95rem;
}
.zddc-table thead {
position: sticky;
top: 0;
z-index: 2;
background: var(--color-bg-elevated, #f5f5f5);
}
.zddc-table__title-row .zddc-table__th {
padding: var(--spacing-sm) var(--spacing-md);
text-align: left;
font-weight: 600;
border-bottom: 1px solid var(--color-border, #d8d8d8);
cursor: pointer;
user-select: none;
white-space: nowrap;
}
.zddc-table__title-row .zddc-table__th:hover {
background: var(--color-bg-hover, rgba(0, 0, 0, 0.04));
}
.zddc-table__filter-row .zddc-table__filter-cell {
padding: 4px var(--spacing-sm);
border-bottom: 1px solid var(--color-border, #d8d8d8);
background: var(--color-bg-elevated, #f5f5f5);
}
.zddc-table__filter-text,
.zddc-table__filter-enum {
width: 100%;
box-sizing: border-box;
padding: 2px 4px;
font-size: 0.85rem;
border: 1px solid var(--color-border, #d0d0d0);
border-radius: 3px;
background: var(--color-bg, #fff);
color: var(--color-text, #111);
}
.zddc-table__filter-enum {
min-height: 1.8em;
}
.zddc-table__row:nth-child(even) {
background: var(--color-bg-zebra, rgba(0, 0, 0, 0.02));
}
/* Minimum row height so a freshly-added row (every cell empty) stays
visible — without this the row collapses to just cell padding and
looks like a thin divider line. Acts as a floor; rows with content
grow naturally to fit the text. */
.zddc-table__row {
height: 2.4em;
}
.zddc-table__row--readonly {
color: var(--color-text-muted);
}
.zddc-table__cell {
padding: var(--spacing-sm) var(--spacing-md);
border-bottom: 1px solid var(--color-border-soft, rgba(0, 0, 0, 0.06));
vertical-align: top;
cursor: cell;
/* Hide the browser's default outline; the grid pattern renders
its own selection chrome via the --selected class. */
outline: none;
}
/* Currently-selected cell — Excel-style focus ring. The 2px outset
border doesn't push surrounding cells around because outline is
used instead of border. */
.zddc-table__cell--selected {
outline: 2px solid var(--color-accent, #2868c8);
outline-offset: -2px;
background: var(--color-bg-selected, rgba(40, 104, 200, 0.08));
}
/* Cells in the multi-cell range get a fainter highlight; the focus
cell (the one with --selected) stays brighter so the anchor /
focus distinction is visible. */
.zddc-table__cell--in-range:not(.zddc-table__cell--selected) {
background: var(--color-bg-range, rgba(40, 104, 200, 0.05));
}
/* Inline cell-editor input: occupies the cell verbatim, no border so
it visually replaces the cell text. The selected outline on the
surrounding td still shows. */
.zddc-table__cell-input {
width: 100%;
box-sizing: border-box;
padding: 0;
margin: 0;
border: none;
background: var(--color-bg, #fff);
color: var(--color-text, #111);
font: inherit;
outline: none;
}
/* Row-save state markers (Phase 3). The first cell of the row gets a
left-border swatch; the row tooltip on hover surfaces the state.
Colors track the state's urgency: dirty (subtle), saving (info),
queued (warm), invalid/stale (warning), errored (alert). */
/* Dirty row gets a wider swatch (4px → easier to see at a glance) AND
a faint blue background so the unsaved state reads as "row is in a
different state" not "small marker on the edge". */
.zddc-table__row--dirty td:first-child { box-shadow: inset 4px 0 0 var(--color-info, #4a90e2); }
.zddc-table__row--dirty { background: var(--color-bg-info, rgba(74, 144, 226, 0.08)); }
.zddc-table__row--saving td:first-child { box-shadow: inset 3px 0 0 var(--color-muted, #888); }
.zddc-table__row--queued td:first-child { box-shadow: inset 3px 0 0 var(--color-warm, #d4a017); }
.zddc-table__row--stale td:first-child { box-shadow: inset 3px 0 0 var(--color-warning, #e8a33d); background: var(--color-bg-warning, rgba(232, 163, 61, 0.06)); }
.zddc-table__row--invalid td:first-child { box-shadow: inset 3px 0 0 var(--color-warning, #e8a33d); }
.zddc-table__row--errored td:first-child { box-shadow: inset 3px 0 0 var(--color-error, #c14242); background: var(--color-bg-error, rgba(193, 66, 66, 0.06)); }
/* Per-cell invalid marker — small red corner triangle, Excel-style.
The hover tooltip carries the validation message via title attr. */
.zddc-table__cell--invalid {
position: relative;
}
.zddc-table__cell--invalid::after {
content: '';
position: absolute;
top: 0;
right: 0;
width: 0;
height: 0;
border-style: solid;
border-width: 0 6px 6px 0;
border-color: transparent var(--color-error, #c14242) transparent transparent;
}
/* Status bar (table-status) when used as the stale-row prompt host. */
.table-status.table-status--prompt {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
background: var(--color-bg-warning, rgba(232, 163, 61, 0.08));
border: 1px solid var(--color-warning, #e8a33d);
border-radius: var(--radius-sm, 4px);
margin-bottom: var(--spacing-sm);
color: var(--color-text, #111);
}
.table-empty {
padding: var(--spacing-lg) var(--spacing-md);
text-align: center;
color: var(--color-text-muted);
font-style: italic;
}