From e6746e8acb872f6c514effa2f3fb2f8bbe52f404 Mon Sep 17 00:00:00 2001 From: Remko Date: Sat, 19 Sep 2026 12:41:06 +0200 Subject: [PATCH] feat(playground): draw the Nextcloud 35 header 35 is not a relabelled 34. layout.user.php gained a .header-center column, UnifiedSearchInput replaced its button with a field carrying a Ctrl-K hint and a new placeholder, and AppMenu grouped the waffle and the current app under one .app-menu__trigger with a nested icon. A version switch that answered 35 with the 34 bar would answer the upgrade question wrongly. Renames the is-v34 marker to is-modern: the colour chain and the glyph fade it carries are what 34 introduced and 35 kept, so naming them after either version implies the other does something else. --- css/playground.css | 154 ++++++++++++++++++++++- js/playground.js | 122 ++++++++++++++++-- l10n/be.js | 3 +- l10n/be.json | 3 +- l10n/bg.js | 3 +- l10n/bg.json | 3 +- l10n/bs.js | 3 +- l10n/bs.json | 3 +- l10n/ca.js | 3 +- l10n/ca.json | 3 +- l10n/cs.js | 3 +- l10n/cs.json | 3 +- l10n/da.js | 3 +- l10n/da.json | 3 +- l10n/de.js | 3 +- l10n/de.json | 3 +- l10n/el.js | 3 +- l10n/el.json | 3 +- l10n/en.js | 3 +- l10n/en.json | 3 +- l10n/es.js | 3 +- l10n/es.json | 3 +- l10n/et.js | 3 +- l10n/et.json | 3 +- l10n/fi.js | 3 +- l10n/fi.json | 3 +- l10n/fr.js | 3 +- l10n/fr.json | 3 +- l10n/ga.js | 3 +- l10n/ga.json | 3 +- l10n/hr.js | 3 +- l10n/hr.json | 3 +- l10n/hu.js | 3 +- l10n/hu.json | 3 +- l10n/is.js | 3 +- l10n/is.json | 3 +- l10n/it.js | 3 +- l10n/it.json | 3 +- l10n/lb.js | 3 +- l10n/lb.json | 3 +- l10n/lt.js | 3 +- l10n/lt.json | 3 +- l10n/lv.js | 3 +- l10n/lv.json | 3 +- l10n/mk.js | 3 +- l10n/mk.json | 3 +- l10n/mt.js | 3 +- l10n/mt.json | 3 +- l10n/nb.js | 3 +- l10n/nb.json | 3 +- l10n/nl.js | 3 +- l10n/nl.json | 3 +- l10n/pl.js | 3 +- l10n/pl.json | 3 +- l10n/pt.js | 3 +- l10n/pt.json | 3 +- l10n/rm.js | 3 +- l10n/rm.json | 3 +- l10n/ro.js | 3 +- l10n/ro.json | 3 +- l10n/ru.js | 3 +- l10n/ru.json | 3 +- l10n/sk.js | 3 +- l10n/sk.json | 3 +- l10n/sl.js | 3 +- l10n/sl.json | 3 +- l10n/sq.js | 3 +- l10n/sq.json | 3 +- l10n/sr.js | 3 +- l10n/sr.json | 3 +- l10n/sv.js | 3 +- l10n/sv.json | 3 +- l10n/tr.js | 3 +- l10n/tr.json | 3 +- l10n/uk.js | 3 +- l10n/uk.json | 3 +- tests/vitest/playgroundInventory.spec.js | 85 ++++++++++++- 77 files changed, 486 insertions(+), 97 deletions(-) diff --git a/css/playground.css b/css/playground.css index e0c4ecd78..0b8f8ed3f 100644 --- a/css/playground.css +++ b/css/playground.css @@ -1932,7 +1932,7 @@ content: ' ·'; } -/* --- the Nextcloud 34 app menu --- */ +/* --- the Nextcloud 34 and 35 app menu --- */ #nldesign-preview .nldesign-pg-appmenu { display: inline-flex; @@ -1975,7 +1975,7 @@ .button-vue__icon svg` takes `mask: var(--header-menu-icon-mask)`, which is a top-to-bottom alpha ramp — solid to 25%, 55% transparent by 90%. Without it the specimen's glyphs are flat where the real ones lift off the bar. */ -#nldesign-preview .nldesign-pg-header.is-v34 .nldesign-pg-glyphbox svg { +#nldesign-preview .nldesign-pg-header.is-modern .nldesign-pg-glyphbox svg { mask: var(--header-menu-icon-mask, none); } @@ -2017,12 +2017,15 @@ mask: url('../img/app.svg') center / 20px no-repeat; } -/* --- the 34 header: search in the middle --- */ +/* --- the 34-and-newer header: search in the middle --- */ /* The foreground chain the real bar uses: Nextcloud's own plain-text token, with Thematiq's header token in front of it because that is what - element-overrides.css puts on the live header. */ -#nldesign-preview .nldesign-pg-header.is-v34 { + element-overrides.css puts on the live header. `is-modern` rather than a + per-version class: this chain, and the glyph fade above, are what 34 + introduced and 35 kept, so naming them after either version would say the + other one does something else. */ +#nldesign-preview .nldesign-pg-header.is-modern { position: relative; color: var( --nldesign-color-header-text, @@ -2095,13 +2098,152 @@ background-color: rgb(0 0 0 / 22%); } -#nldesign-preview .nldesign-pg-search .unified-search-input__label { +#nldesign-preview .nldesign-pg-search .unified-search-input__label, +#nldesign-preview .nldesign-pg-searchresting .unified-search-input__label { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } +/* --- the 35 header: three columns, and a real field --- */ + +/* 35 replaced 34's floating search with a genuine `.header-center` between a + `.header-start` and a `.header-end` that both GROW. That is the mechanism + keeping the field dead centre: two equal side columns, not an absolutely + positioned track. Drawing it any other way would put the field in the middle + of the leftover space, which moves with the length of the app name. */ +#nldesign-preview .nldesign-pg-header.is-v35 .nldesign-pg-header-start { + display: flex; + flex: 1 0; + align-items: center; + gap: 2px; + min-width: 0; +} + +/* The auto margin that centred nothing on 34 has to go: with both sides + growing there is no free space left for it to absorb, and leaving it in + would read as the thing doing the work. */ +#nldesign-preview .nldesign-pg-header.is-v35 .nldesign-pg-header-end { + flex: 1 0; + justify-content: flex-end; + margin-left: 0; +} + +/* One shared highlight spanning the waffle AND the current app, where 34 gave + each button its own. Nothing hovers in a specimen, so only the geometry is + drawn — but the element has to exist, because a theme still reaching + `.app-menu__waffle:hover` paints a pill the width of the waffle alone. */ +#nldesign-preview .nldesign-pg-appmenutrigger { + position: relative; + display: inline-flex; + align-items: center; + border-radius: var(--border-radius-element, 8px); +} + +/* The level 35 added around the app icon. Core puts the header's alpha ramp on + this outer element and the icon shape on the inner one, so the fade applies + once to the glyph rather than to a box behind it. */ +#nldesign-preview .nldesign-pg-appfade { + display: inline-flex; + flex: none; + align-items: center; + mask: var(--header-menu-icon-mask, none); +} + +/* Core says `clamp(200px, 35vw, 700px)` — 700, where 34 capped at 600. The + percentage stands in for the vw for the same reason it does on 34: the + header IS the viewport in life, and here it is a ~980px drawing of one. */ +#nldesign-preview .nldesign-pg-search35 { + display: flex; + flex: 0 1 auto; + align-items: center; + width: clamp(140px, 35%, 700px); + min-width: 0; +} + +/* The field itself. Same black wash and inset shadow 34's button carried — 35 + changed what the element IS, not what it looks like at rest — on the full + clickable area rather than 8px inside it. */ +#nldesign-preview .nldesign-pg-searchfield { + position: relative; + display: flex; + align-items: center; + width: 100%; + height: var(--default-clickable-area, 34px); + border-radius: var(--border-radius-element, 8px); + background-color: rgb(0 0 0 / 15%); + box-shadow: inset 0 2px 0 rgb(0 0 0 / 12%); + color: inherit; + font-size: 13px; +} + +/* On dark themes the plain black wash all but disappears into the bar, so 35 + tints it with the primary element colour instead. That swap is a token + decision, which makes it something an admin is here to judge. */ +[data-theme-dark] #nldesign-preview .nldesign-pg-searchfield, +[data-theme-dark-highcontrast] #nldesign-preview .nldesign-pg-searchfield { + background-color: color-mix( + in srgb, + var(--color-primary-element) 16%, + transparent + ); +} + +/* The magnifier and placeholder. Core absolutely positions this group and + slides it from the centre to the leading edge on focus, using container + units to find the centre. A specimen is drawn at rest and never focuses, so + only the resting position is transcribed — as a centred flex row, which + lands in the same place without the machinery that moves it. */ +#nldesign-preview .nldesign-pg-searchresting { + display: flex; + flex: 1; + align-items: center; + justify-content: center; + gap: 8px; + min-width: 0; + padding-inline: 12px; + color: color-mix( + in srgb, + var(--color-background-plain-text) 70%, + var(--color-background-plain) + ); + pointer-events: none; +} + +/* The Ctrl-K hint, pinned to the trailing edge and overlaid on the field. */ +#nldesign-preview .nldesign-pg-searchkbd { + position: absolute; + inset-inline-end: 4px; + top: 50%; + display: flex; + gap: 2px; + transform: translateY(-50%); + pointer-events: none; +} + +#nldesign-preview .nldesign-pg-searchkbd kbd { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 12px; + height: 12px; + padding-inline: 5px; + border: 1px solid + color-mix(in srgb, var(--color-background-plain-text) 20%, transparent); + border-bottom-width: 2px; + border-radius: var(--border-radius-small, 4px); + background: none; + color: color-mix( + in srgb, + var(--color-background-plain-text) 70%, + var(--color-background-plain) + ); + font-family: inherit; + font-size: 13px; + line-height: 1; +} + /* The token editor's own heading, lifted up here with the tab strip. * It governs the specimens AND the rows below them — left in the editor it * sat between the two and read as a heading for the rows alone, with the diff --git a/js/playground.js b/js/playground.js index d9cb96fab..7b1b94419 100644 --- a/js/playground.js +++ b/js/playground.js @@ -113,8 +113,13 @@ * anyway, because "my 33 will look like this" is the question an admin * actually asks, and answering it with a version they did not name leaves * them wondering whether it was understood. + * + * 34 and 35 do NOT draw the same header, which is why 35 is its own entry + * rather than a second label on the 34 one: 35 gave the bar a real centre + * column, turned the search trigger into a field, and grouped the waffle + * and the current app under one highlight. */ - var HEADER_VERSIONS = [32, 33, 34] + var HEADER_VERSIONS = [32, 33, 34, 35] /** * Whether a state is one the admin makes rather than one the stage draws. @@ -1574,9 +1579,11 @@ * * This exists so an admin can answer "what does my theme do to the header * after the upgrade" without upgrading — 34 replaced the app-entry row with - * a waffle and a current-app button, and a theme that reached the old shape - * may reach nothing in the new one. The version they are running is marked, - * so switching away from it is visibly a hypothetical. + * a waffle and a current-app button, and 35 grouped those two under one + * highlight and turned the search trigger into a field in a centre column. + * A theme that reached the old shape may reach nothing in the new one. The + * version they are running is marked, so switching away from it is visibly + * a hypothetical. * * @param {Object} state The instrument state. * @param {Object} component The header component. @@ -2015,20 +2022,35 @@ var STAGES = { 'header-bar': function (state, component, version) { var major = headerMajor(version) + // Everything from 34 on: the waffle app menu, the centred search, + // and the glyph fade. `columns` is the narrower 35-only change. var modern = major >= 34 + // 35 rebuilt the bar as three real columns — `.header-start`, + // `.header-center`, `.header-end` — where 34 centred the search by + // floating it over a two-column bar. The start side has to become a + // wrapper for that, because a centre column only stays centred + // between a start and an end that both grow. + var columns = major >= 35 + + var start = + '' + + (modern ? (columns ? appMenu35() : appMenu34()) : appMenu32()) return ( '
' - + '' - + (modern ? appMenu34() : appMenu32()) + + (columns + ? '' + start + '' + : start) // Version drift, not a style choice, and the second of two in // this one bar: 34 moved the search into the middle, where 32 - // and 33 put a magnifier among the glyphs on the right. It is - // branched the way the app menu above it is, because the shape - // and the position both changed, not just the class name. - + (modern ? headerSearch34() : '') + // and 33 put a magnifier among the glyphs on the right, and 35 + // turned that middle trigger into a field. It is branched the + // way the app menu above it is, because the shape and the + // position both changed, not just the class name. + + (modern ? (columns ? headerSearch35() : headerSearch34()) : '') + '' + (modern ? '' : headerSearch32()) + glyph('bell', 'nldesign-pg-bell', 'bellDot') @@ -2965,6 +2987,42 @@ ) } + /** + * The app menu as Nextcloud 35 draws it: the same two buttons, grouped. + * + * 35 kept 34's waffle and current-app buttons and wrapped them in one + * `.app-menu__trigger`, which carries a single highlight spanning both — + * where 34 gave each button its own. The pair is what an admin sees change, + * so the wrapper is drawn even though nothing hovers here: a theme reaching + * `.app-menu__waffle:hover` for the highlight paints nothing on 35, and + * that is only visible if the element it moved to exists in the specimen. + * + * The current-app icon also gained a level: 34 rendered an ``, 35 nests a `__glyph` span inside + * that class so the outer element can carry the header fade while the inner + * one carries the icon shape. + * + * @return {string} The markup. + */ + function appMenu35() { + return ( + '' + ) + } + /** * The header search, as 32 and 33 draw it: a glyph among the account ones. * @@ -3015,6 +3073,48 @@ ) } + /** + * The header search, as 35 draws it: a real field in a real centre column. + * + * 34's trigger was a `