From 232e6fe6dcf95cc3d980e2b048ae427e7591a28b Mon Sep 17 00:00:00 2001 From: Umut Kantarci Date: Tue, 15 Sep 2026 14:49:37 +0200 Subject: [PATCH] Let a single column sit in a section and give buttons an alignment A column may now live directly in a section, not only in a row - the way to centre or narrow a button or a short text without a one-column row. The align design token applies to buttons; because a button is inline-flex, auto margins never centred it, so site.css turns an aligned button into a fit-content block per device. The Drews seed uses the token for its centred buttons. Co-Authored-By: Claude Fable 5.1 --- docs/STATUS.md | 10 ++++++++++ public/assets/design-tokens.json | 3 ++- public/assets/editor-model.js | 8 ++++++-- public/assets/site.css | 7 +++++++ scripts/seed-maler-drews.php | 12 +++++++++++- tests/editor-model.test.mjs | 8 +++++--- 6 files changed, 41 insertions(+), 7 deletions(-) diff --git a/docs/STATUS.md b/docs/STATUS.md index 049b1cf..6cdc40d 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1,5 +1,15 @@ # Projektstatus +## Einzelne Spalte in einer Sektion, Buttons ausrichten (15.09.2026) + +**Anlass:** Umut konnte im Editor keine Spalte direkt in eine Sektion legen (ausgegraut) – so zentriert er einen Button –, und der Button „Alle Mitteilungen“ auf der Drews-Startseite stand links, ohne eine Option dafür. + +**Editor (`editor-model.js`):** `REQUIRE.column` ist jetzt `['row', 'section']`. Eine einzelne Spalte darf direkt in einer Sektion stehen (schmaler Inhalt, zentrierte Elemente), in einer Spalte oder lose im Dokument weiter nicht. Test angepasst. + +**Buttons (`design-tokens.json`, `site.css`, `design.css` neu gebaut):** Das Design-Token „Ausrichtung“ gilt jetzt auch für Buttons. Weil ein Button `inline-flex` ist, zentriert ihn `margin:auto` nicht; `site.css` macht ihn bei `align-center`/`-right` zum Block mit eigener Breite (`display:flex;width:fit-content;margin-inline:auto`, dazu `justify-self` für Raster), je Gerät an den Breakpoints von `design.css` (1024/650 px). Das Drews-Aufbauskript nutzt das Token für alle mittigen Buttons statt der wirkungslosen auto-Ränder. + +**Geprüft:** `npm test` 33 grün (darunter der geänderte Einfügetest), PHP-Tests zu Design-Tokens 11 grün; Drews-Startseite nach Neuaufbau: „Alle Mitteilungen“ und „Alle Projekte ansehen“ mittig, Aufruf-Buttons mittig, auf 1440 und 390 px. Unveröffentlicht, gehört mit den beiden Korrekturen darunter in 1.16.1. + ## Ihr Malerservice Drews nachgebaut, 15.09.2026 **Anlass:** Umut wollte maler-drews.com (Flatsome-Auftritt, Malermeister Axel Drews, Gernsbach) nach dem bekannten Prinzip in eigener Installation nachbauen – exakt im Viewer, mit Lighthouse, axe und maschinenlesbaren Ausgaben auf 100. diff --git a/public/assets/design-tokens.json b/public/assets/design-tokens.json index ed9db88..e825098 100644 --- a/public/assets/design-tokens.json +++ b/public/assets/design-tokens.json @@ -10,7 +10,8 @@ "iconcard", "stat", "quote", - "cta" + "cta", + "button" ], "values": { "left": [ diff --git a/public/assets/editor-model.js b/public/assets/editor-model.js index d312c4f..715e642 100644 --- a/public/assets/editor-model.js +++ b/public/assets/editor-model.js @@ -55,8 +55,12 @@ export const isContainer = block => ['section', 'row', 'column', 'gallery', 'log /** Containers that take one kind of child and nothing else. */ export const ALLOW = { row: ['column'], gallery: ['image'], logos: ['image'], accordion: ['accordionitem'], tabs: ['tabitem'] }; -/** Blocks that exist only inside one of these parents. */ -export const REQUIRE = { column: ['row'], accordionitem: ['accordion'], tabitem: ['tabs'] }; +/** + * Blocks that exist only inside one of these parents. A column belongs to a row - and, as a + * single one, directly to a section: that is how a button or a short text gets centred and + * narrowed without a one-column row around it. Umut asked for exactly that. + */ +export const REQUIRE = { column: ['row', 'section'], accordionitem: ['accordion'], tabitem: ['tabs'] }; /** * A row is not refused by any particular parent - a tab panel or an accordion entry may well * hold one - but it is not a thing a document starts with. At the top level a page begins with diff --git a/public/assets/site.css b/public/assets/site.css index 4000b45..d32076c 100644 --- a/public/assets/site.css +++ b/public/assets/site.css @@ -96,6 +96,13 @@ p{hyphens:auto} /* Forms. Server rendered, no JavaScript; every message sits at its field. */ .block-form{display:grid;gap:20px;max-width:640px} .block-form>.cms-button{justify-self:start} +/* Ausrichtung eines Buttons (Design-Token „align"): ein inline-flex-Element zentriert sich nicht + über margin:auto, darum hier als Block mit eigener Breite - in Spalten, Zeilen und Rastern. */ +.kant .cms-button.d-desktop-align-left{justify-self:start;margin-inline:0} +.kant .cms-button.d-desktop-align-center{display:flex;width:fit-content;margin-inline:auto;justify-self:center} +.kant .cms-button.d-desktop-align-right{display:flex;width:fit-content;margin-inline:auto 0;justify-self:end} +@media(max-width:1024px){.kant .cms-button.d-tablet-align-left{display:inline-flex;width:auto;justify-self:start;margin-inline:0}.kant .cms-button.d-tablet-align-center{display:flex;width:fit-content;margin-inline:auto;justify-self:center}.kant .cms-button.d-tablet-align-right{display:flex;width:fit-content;margin-inline:auto 0;justify-self:end}} +@media(max-width:650px){.kant .cms-button.d-mobile-align-left{display:inline-flex;width:auto;justify-self:start;margin-inline:0}.kant .cms-button.d-mobile-align-center{display:flex;width:fit-content;margin-inline:auto;justify-self:center}.kant .cms-button.d-mobile-align-right{display:flex;width:fit-content;margin-inline:auto 0;justify-self:end}} .form-field{display:grid;gap:7px} .form-field label{font-weight:600;font-size:14px} .form-field input,.form-field textarea,.form-field select{width:100%;padding:12px 13px;border:1px solid var(--form-border,currentColor);border-radius:8px;background:var(--form-bg,#fff);color:inherit;font:inherit;min-height:46px} diff --git a/scripts/seed-maler-drews.php b/scripts/seed-maler-drews.php index 9bdc1f6..ef791de 100644 --- a/scripts/seed-maler-drews.php +++ b/scripts/seed-maler-drews.php @@ -182,11 +182,21 @@ $button = static function (string $id, string $text, string $href, string $fassu $css .= "\n& span{margin-left:6px}"; } $block = ['id' => $id, 'type' => 'button', 'props' => ['text' => $text, 'href' => $href], 'css' => $css]; + // Ein Button zentriert sich nicht über auto-Ränder (er ist inline-flex); dafür gibt es die + // Ausrichtung als Design-Token, dieselbe, die der Editor unter „Ausrichtung“ anbietet. + $rand = $style['desktop']['margin'] ?? []; + if ('auto' === ($rand['left'] ?? '') && 'auto' === ($rand['right'] ?? '')) { + unset($style['desktop']['margin']['left'], $style['desktop']['margin']['right']); + if ([] === $style['desktop']['margin']) { + unset($style['desktop']['margin']); + } + $block['layout'] = ['design' => ['desktop' => ['align' => 'center']]]; + } if ([] !== $style) { $block['style'] = $style; } if ('large' !== $groesse) { - $block['layout'] = ['button' => ['desktop' => ['size' => $groesse]]]; + $block['layout'] = [...($block['layout'] ?? []), 'button' => ['desktop' => ['size' => $groesse]]]; } return $block; diff --git a/tests/editor-model.test.mjs b/tests/editor-model.test.mjs index 0759132..a03acb2 100644 --- a/tests/editor-model.test.mjs +++ b/tests/editor-model.test.mjs @@ -508,7 +508,7 @@ test('the top level takes everything the validator takes there, and nothing else // Die Struktur, die der Editor in seinem eigenen Hinweistext beschreibt, gilt jetzt auch fuer // die Schaltflaechen: eine Zeile nahm bisher jedes Kind, eine Spalte stand ueberall. -test('a row takes columns and nothing else, a column only lives in a row', () => { +test('a row takes columns and nothing else, a column lives in a row or alone in a section', () => { const document = fixture(); const rowId = insertRow(document, 3, 's2'); const row = locate(document.blocks, rowId).block; @@ -518,8 +518,10 @@ test('a row takes columns and nothing else, a column only lives in a row', () => assert.equal(insertBlockAt(document, 'heading', rowId, 0), null); assert.equal(row.children.length, 3); assert.ok(insertBlockAt(document, 'heading', row.children[0].id, 0)); - // Eine Spalte gehoert nicht in eine Sektion. - assert.equal(insertBlockAt(document, 'column', 's2', 0), null); + // Eine einzelne Spalte darf direkt in einer Sektion stehen - so wird ein Button zentriert -, + // aber nicht in einer Spalte oder lose im Dokument. + assert.ok(insertBlockAt(document, 'column', 's2', 0)); + assert.equal(insertBlockAt(document, 'column', row.children[0].id, 0), null); assert.ok(insertBlockAt(document, 'column', rowId, 3)); });