From 54c577a70035b7a93675f529919b0ec1a3764220 Mon Sep 17 00:00:00 2001 From: buenosairesam Date: Tue, 22 Sep 2026 05:01:51 -0300 Subject: [PATCH] ui refactor phase 2 --- .../shunts/mercadopago/templates/index.html | 13 +++ soleprint/artery/veins/jira/ui/index.html | 13 +++ soleprint/common/theme/parts/example.html | 97 ++++++++++--------- soleprint/common/theme/parts/maximize.css | 91 ++++++++--------- soleprint/common/theme/parts/maximize.js | 54 +++++------ soleprint/common/theme/parts/panel.css | 13 +++ soleprint/common/ui/src/components/Panel.vue | 84 +++++++++++++++- .../common/ui/src/components/VideoPlayer.vue | 26 ++++- 8 files changed, 265 insertions(+), 126 deletions(-) diff --git a/soleprint/artery/shunts/mercadopago/templates/index.html b/soleprint/artery/shunts/mercadopago/templates/index.html index 4506f89..89cdbd6 100755 --- a/soleprint/artery/shunts/mercadopago/templates/index.html +++ b/soleprint/artery/shunts/mercadopago/templates/index.html @@ -159,6 +159,19 @@ display: flex; align-items: center; gap: var(--space-2); + /* The header is a fixed height and the panel clips, so a crowded actions strip + silently loses whatever sits at its end. Labels give way instead: they + truncate, the controls never shrink. (As Panel.vue does.) */ + min-width: 0; + } + .panel-actions > :is(button, input, select, label) { + flex-shrink: 0; + } + .panel-actions > span { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .panel-status { diff --git a/soleprint/artery/veins/jira/ui/index.html b/soleprint/artery/veins/jira/ui/index.html index eb86733..b68b27f 100644 --- a/soleprint/artery/veins/jira/ui/index.html +++ b/soleprint/artery/veins/jira/ui/index.html @@ -165,6 +165,19 @@ textarea { display: flex; align-items: center; gap: var(--space-2); + /* The header is a fixed height and the panel clips, so a crowded actions strip + silently loses whatever sits at its end. Labels give way instead: they + truncate, the controls never shrink. (As Panel.vue does.) */ + min-width: 0; +} +.panel-actions > :is(button, input, select, label) { + flex-shrink: 0; +} +.panel-actions > span { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .panel-status { diff --git a/soleprint/common/theme/parts/example.html b/soleprint/common/theme/parts/example.html index c206e16..8401bcb 100644 --- a/soleprint/common/theme/parts/example.html +++ b/soleprint/common/theme/parts/example.html @@ -14,7 +14,7 @@ WHAT TO LOOK AT · vertical and horizontal splits, nested, all draggable - · ⤢ on any panel header — maximize, Escape or the backdrop to come back + · ⤢ on any panel header — maximize; ⤡ or Escape to come back · sliders that are schema-driven, not hand-written markup · a live feed at a few hundred events a second, with the coalesced and uncoalesced counters side by side. That gap IS the point of onFrame(). @@ -44,7 +44,6 @@ --space-1: 4px; --space-2: 8px; --space-3: 12px; - --space-4: 16px; --status-error: #f06565; --status-escalating: #f5a623; --status-idle: #555568; @@ -131,27 +130,31 @@ textarea { background: transparent; } /* part: maximize — common/theme/parts/maximize.css */ -.panel-maximized { - position: fixed; - inset: var(--space-4); - z-index: 1000; -} - -/* The page behind it keeps its layout — only this panel moves — so a backdrop - * is what stops the rest showing through around the inset. */ -.panel-maximize-backdrop { +/* Covers the window. `fixed` so it escapes the split panes without disturbing + their sizing — restoring puts everything back untouched. */ +[data-maximize].maximized { position: fixed; inset: 0; - z-index: 999; - background: var(--surface-0); - opacity: 0.85; + z-index: 400; + border-radius: 0; + border: none; } -.panel-maximize-btn { - padding: 0 6px; - line-height: 1; +.panel-max { + flex-shrink: 0; + padding: 0 var(--space-1); font-size: 13px; + line-height: 1.4; + color: var(--text-secondary); + background: none; + border: none; + cursor: pointer; + opacity: 0.6; + transition: opacity 0.12s; } +.panel-max:hover, +[data-maximize]:hover .panel-max { opacity: 1; } +.panel-max:hover { color: var(--text-primary); } /* part: panel — common/theme/parts/panel.css */ .panel { position: relative; @@ -188,6 +191,19 @@ textarea { display: flex; align-items: center; gap: var(--space-2); + /* The header is a fixed height and the panel clips, so a crowded actions strip + silently loses whatever sits at its end. Labels give way instead: they + truncate, the controls never shrink. (As Panel.vue does.) */ + min-width: 0; +} +.panel-actions > :is(button, input, select, label) { + flex-shrink: 0; +} +.panel-actions > span { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .panel-status { @@ -467,52 +483,43 @@ input[type="range"]::-moz-range-thumb { 'use strict' var open = null - var backdrop = null + + function label(button, on) { + button.textContent = on ? '⤡' : '⤢' + button.title = on ? 'Restore (Esc)' : 'Maximize' + } function restore() { if (!open) return - open.panel.classList.remove('panel-maximized') - open.button.textContent = '⤢' - open.button.title = 'Maximize' - if (backdrop && backdrop.parentNode) backdrop.parentNode.removeChild(backdrop) + open.panel.classList.remove('maximized') + label(open.button, false) open = null } function maximize(panel, button) { restore() - if (!backdrop) { - backdrop = document.createElement('div') - backdrop.className = 'panel-maximize-backdrop' - backdrop.addEventListener('click', restore) - } - document.body.appendChild(backdrop) - panel.classList.add('panel-maximized') - button.textContent = '⤡' - button.title = 'Restore' + panel.classList.add('maximized') + label(button, true) open = { panel: panel, button: button } } function setup(panel) { - var actions = panel.querySelector(':scope > .panel-header > .panel-actions') - if (!actions) { - // The header has no actions strip, so there is nowhere to put the button. - // Say so rather than failing silently: the fix is one empty . - var header = panel.querySelector(':scope > .panel-header') - if (!header) return - actions = document.createElement('span') - actions.className = 'panel-actions' - header.appendChild(actions) - } + var header = panel.querySelector(':scope > .panel-header') + if (!header) return var button = document.createElement('button') - button.className = 'panel-maximize-btn' + button.className = 'panel-max' button.type = 'button' - button.textContent = '⤢' - button.title = 'Maximize' + label(button, false) button.addEventListener('click', function () { if (open && open.panel === panel) restore() else maximize(panel, button) }) - actions.appendChild(button) + // After the actions strip and before the status dot: the SFC's order. + var actions = header.querySelector(':scope > .panel-actions') + var status = header.querySelector(':scope > .panel-status') + if (actions && actions.nextSibling) header.insertBefore(button, actions.nextSibling) + else if (status) header.insertBefore(button, status) + else header.appendChild(button) } function start() { diff --git a/soleprint/common/theme/parts/maximize.css b/soleprint/common/theme/parts/maximize.css index cd57707..5608b55 100644 --- a/soleprint/common/theme/parts/maximize.css +++ b/soleprint/common/theme/parts/maximize.css @@ -1,4 +1,4 @@ -/* part: maximize — one panel fills the viewport, and comes back. +/* part: maximize — one panel fills the window, and comes back. * * USE WHEN: one panel is the main event and should be able to fill the screen. * NEEDS: panel (it maximizes .panel; there is no other box) @@ -6,55 +6,56 @@ * ADD: paste this into the page, then run `make theme bake`. *
* - * NOT derived from anything. It is the one thing on the "components that work" - * list that the framework does not have: `maximize`, `fullscreen` and `expand` - * appear nowhere in common/ui. mts built a Teleport lightbox for frames and - * doocus built a collapse-to-bar; neither is this, and neither is reusable. - * - * So there is no `Derived from` line and the audit will say so. That is - * correct — this part has no upstream to drift from, and inventing a - * correspondence to make a check pass would be worse than the check printing - * "cannot audit". - * - * TOKENS: --surface-0 --space-4 - * - * Needs maximize.js. Pairs with panel.css — it maximizes `.panel`, and there is - * no second box in this system to maximize. - * - * MARKUP add the attribute; the button is injected into .panel-actions. - *
- *
- * Stream - * - *
- *
…
- *
- * - * `position: fixed` rather than the Fullscreen API on purpose: a fixed element - * still lives in the page, so a feed writing into it keeps working and Escape - * is ours to handle. The Fullscreen API also needs a user gesture and is - * refused outright in some embedded webviews — which is exactly where these - * pages get opened. + * Derived from common/ui/src/components/Panel.vue. The Vue panel learned to + * maximize in mts, the last application that added to the framework, and came + * upstream from there; this part now spells it the same way, so a page and an + * app show the same DOM — `.panel.maximized`, a `.panel-max` button, Escape to + * restore. It had no upstream before, and the audit said "cannot audit"; now it + * has one, and the audit checks it. + * + * One deliberate difference, and it is about the binding, not the look: the Vue + * panel is maximizable unless told otherwise (`maximizable` defaults to true), + * while a plain panel opts in with `data-maximize`. A plain page has no props, + * and a button nobody asked for on every box is noise the markup never chose. + * + * The selector is `[data-maximize].maximized` rather than the SFC's + * `.panel.maximized` for a mechanical reason: bake finds parts by the classes in + * their CSS, and a `.panel` here would summon this part into every page that has + * a panel. The element carries the same classes either way. + * + * TOKENS: --space-1 --text-secondary --text-primary + * + * Needs maximize.js, which injects the button after `.panel-actions` — where + * the Vue panel renders it — so the markup stays a plain panel. + * + * `position: fixed` rather than the Fullscreen API, for the reason the SFC gives: + * a fullscreen element paints over every sibling and needs a user gesture; a + * fixed panel stays in the page, keeps its controls on screen, and a feed + * writing into it keeps working. */ -.panel-maximized { - position: fixed; - inset: var(--space-4); - z-index: 1000; -} - -/* The page behind it keeps its layout — only this panel moves — so a backdrop - * is what stops the rest showing through around the inset. */ -.panel-maximize-backdrop { +/* Covers the window. `fixed` so it escapes the split panes without disturbing + their sizing — restoring puts everything back untouched. */ +[data-maximize].maximized { position: fixed; inset: 0; - z-index: 999; - background: var(--surface-0); - opacity: 0.85; + z-index: 400; + border-radius: 0; + border: none; } -.panel-maximize-btn { - padding: 0 6px; - line-height: 1; +.panel-max { + flex-shrink: 0; + padding: 0 var(--space-1); font-size: 13px; + line-height: 1.4; + color: var(--text-secondary); + background: none; + border: none; + cursor: pointer; + opacity: 0.6; + transition: opacity 0.12s; } +.panel-max:hover, +[data-maximize]:hover .panel-max { opacity: 1; } +.panel-max:hover { color: var(--text-primary); } diff --git a/soleprint/common/theme/parts/maximize.js b/soleprint/common/theme/parts/maximize.js index 2e13c21..d31d54c 100644 --- a/soleprint/common/theme/parts/maximize.js +++ b/soleprint/common/theme/parts/maximize.js @@ -2,11 +2,12 @@ * * DETECT: data-maximize * - * Injects a button into each `[data-maximize] .panel-actions`, so the markup - * stays a plain panel and nothing has to be wired by hand. Escape closes. + * Injects a `.panel-max` button after each `[data-maximize]` panel's + * `.panel-actions` — where Panel.vue renders it — so the markup stays a plain + * panel and nothing has to be wired by hand. Escape restores, as in the SFC. * * Only one panel is maximized at a time: maximizing a second restores the - * first. Two fixed panels at the same inset would sit exactly on top of each + * first. Two panels covering the window at once would sit exactly on top of each * other, which looks like a rendering bug rather than a state. */ @@ -14,52 +15,43 @@ 'use strict' var open = null - var backdrop = null + + function label(button, on) { + button.textContent = on ? '⤡' : '⤢' + button.title = on ? 'Restore (Esc)' : 'Maximize' + } function restore() { if (!open) return - open.panel.classList.remove('panel-maximized') - open.button.textContent = '⤢' - open.button.title = 'Maximize' - if (backdrop && backdrop.parentNode) backdrop.parentNode.removeChild(backdrop) + open.panel.classList.remove('maximized') + label(open.button, false) open = null } function maximize(panel, button) { restore() - if (!backdrop) { - backdrop = document.createElement('div') - backdrop.className = 'panel-maximize-backdrop' - backdrop.addEventListener('click', restore) - } - document.body.appendChild(backdrop) - panel.classList.add('panel-maximized') - button.textContent = '⤡' - button.title = 'Restore' + panel.classList.add('maximized') + label(button, true) open = { panel: panel, button: button } } function setup(panel) { - var actions = panel.querySelector(':scope > .panel-header > .panel-actions') - if (!actions) { - // The header has no actions strip, so there is nowhere to put the button. - // Say so rather than failing silently: the fix is one empty . - var header = panel.querySelector(':scope > .panel-header') - if (!header) return - actions = document.createElement('span') - actions.className = 'panel-actions' - header.appendChild(actions) - } + var header = panel.querySelector(':scope > .panel-header') + if (!header) return var button = document.createElement('button') - button.className = 'panel-maximize-btn' + button.className = 'panel-max' button.type = 'button' - button.textContent = '⤢' - button.title = 'Maximize' + label(button, false) button.addEventListener('click', function () { if (open && open.panel === panel) restore() else maximize(panel, button) }) - actions.appendChild(button) + // After the actions strip and before the status dot: the SFC's order. + var actions = header.querySelector(':scope > .panel-actions') + var status = header.querySelector(':scope > .panel-status') + if (actions && actions.nextSibling) header.insertBefore(button, actions.nextSibling) + else if (status) header.insertBefore(button, status) + else header.appendChild(button) } function start() { diff --git a/soleprint/common/theme/parts/panel.css b/soleprint/common/theme/parts/panel.css index b973d5b..c066760 100644 --- a/soleprint/common/theme/parts/panel.css +++ b/soleprint/common/theme/parts/panel.css @@ -90,6 +90,19 @@ display: flex; align-items: center; gap: var(--space-2); + /* The header is a fixed height and the panel clips, so a crowded actions strip + silently loses whatever sits at its end. Labels give way instead: they + truncate, the controls never shrink. (As Panel.vue does.) */ + min-width: 0; +} +.panel-actions > :is(button, input, select, label) { + flex-shrink: 0; +} +.panel-actions > span { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .panel-status { diff --git a/soleprint/common/ui/src/components/Panel.vue b/soleprint/common/ui/src/components/Panel.vue index 0ed1d95..118bad9 100644 --- a/soleprint/common/ui/src/components/Panel.vue +++ b/soleprint/common/ui/src/components/Panel.vue @@ -1,16 +1,49 @@