mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
Improve Live UI state inspector
AI-assisted: implemented and validated with Codex.
This commit is contained in:
@@ -66,22 +66,22 @@ const statesJson = JSON.stringify(statePayload).replace(/</g, '\\u003c');
|
||||
and dark host pages (barPaletteForTheme, live-browser.js). The two columns
|
||||
below therefore vary the host canvas, not the chrome palette.
|
||||
-->
|
||||
<section class="live-ui-gallery" data-live-ui-gallery data-dev-only="true" aria-labelledby="live-ui-gallery-title">
|
||||
<header class="live-ui-gallery__header">
|
||||
<div class="live-ui-gallery__intro">
|
||||
<p class="live-ui-gallery__eyebrow">Dev-only · Live chrome</p>
|
||||
<h2 id="live-ui-gallery-title">Live UI state gallery</h2>
|
||||
<p>
|
||||
Inspect the real picker, progressive review, copy-apply, and recovery states without starting Live.
|
||||
Every state renders against both host themes.
|
||||
</p>
|
||||
<section class="live-ui-gallery" data-live-ui-gallery data-dev-only="true" aria-label="Live UI state inspector">
|
||||
<header class="live-ui-gallery__header" data-live-ui-gallery-controls>
|
||||
<div class="live-ui-gallery__state-readout" aria-live="polite" aria-atomic="true">
|
||||
<span data-gallery-state-group>Global chrome</span>
|
||||
<strong data-gallery-state-label>Ready</strong>
|
||||
</div>
|
||||
|
||||
<div class="live-ui-gallery__control-panel" aria-label="Gallery controls">
|
||||
<label for="live-ui-gallery-state">State</label>
|
||||
<div
|
||||
class="live-ui-gallery__control-panel"
|
||||
aria-label="Gallery controls"
|
||||
aria-keyshortcuts="ArrowLeft ArrowRight Home End"
|
||||
>
|
||||
<label class="live-ui-gallery__sr-only" for="live-ui-gallery-state">State</label>
|
||||
<div class="live-ui-gallery__select-row">
|
||||
<button type="button" class="live-ui-gallery__step" data-gallery-step="-1" aria-label="Previous state">←</button>
|
||||
<select id="live-ui-gallery-state" data-gallery-state>
|
||||
<select id="live-ui-gallery-state" data-gallery-state aria-controls="live-ui-gallery-light-stage live-ui-gallery-dark-stage">
|
||||
{STATE_GROUPS.map((group) => (
|
||||
<optgroup label={group.label}>
|
||||
{group.states.map(([key, label]) => <option value={key}>{label}</option>)}
|
||||
@@ -90,36 +90,30 @@ const statesJson = JSON.stringify(statePayload).replace(/</g, '\\u003c');
|
||||
</select>
|
||||
<button type="button" class="live-ui-gallery__step" data-gallery-step="1" aria-label="Next state">→</button>
|
||||
</div>
|
||||
<p class="live-ui-gallery__shortcut">Use ←/→ while this panel is focused to step through states.</p>
|
||||
</div>
|
||||
|
||||
<nav class="live-ui-gallery__quick-nav" aria-label="Quick state selection">
|
||||
{STATE_GROUPS.map((group) => (
|
||||
<div class="live-ui-gallery__quick-group">
|
||||
<span>{group.label}</span>
|
||||
<div>
|
||||
{group.states.map(([key, label]) => (
|
||||
<button type="button" data-gallery-state-button={key}>{label}</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<nav class="live-ui-gallery__quick-nav" aria-label="Quick state selection">
|
||||
{STATE_GROUPS.map((group) => (
|
||||
<div class="live-ui-gallery__quick-group">
|
||||
<span>{group.label}</span>
|
||||
<div>
|
||||
{group.states.map(([key, label]) => (
|
||||
<button type="button" data-gallery-state-button={key}>{label}</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div class="live-ui-gallery__state-readout" aria-live="polite" aria-atomic="true">
|
||||
<span data-gallery-state-group>Global chrome</span>
|
||||
<strong data-gallery-state-label>Ready</strong>
|
||||
</div>
|
||||
|
||||
<div class="live-ui-gallery__previews">
|
||||
<div class="live-ui-gallery__previews" data-live-ui-gallery-workspace>
|
||||
<section class="live-ui-gallery__preview" aria-labelledby="live-ui-gallery-light-title">
|
||||
<header>
|
||||
<span class="live-ui-gallery__theme-dot" aria-hidden="true"></span>
|
||||
<h3 id="live-ui-gallery-light-title">Light host</h3>
|
||||
<code>host: light</code>
|
||||
</header>
|
||||
<div class="live-ui-stage is-light" data-live-gallery-preview="light"></div>
|
||||
<div id="live-ui-gallery-light-stage" class="live-ui-stage is-light" data-live-gallery-preview="light"></div>
|
||||
</section>
|
||||
|
||||
<section class="live-ui-gallery__preview" aria-labelledby="live-ui-gallery-dark-title">
|
||||
@@ -128,16 +122,10 @@ const statesJson = JSON.stringify(statePayload).replace(/</g, '\\u003c');
|
||||
<h3 id="live-ui-gallery-dark-title">Dark host</h3>
|
||||
<code>host: dark</code>
|
||||
</header>
|
||||
<div class="live-ui-stage is-dark" data-live-gallery-preview="dark"></div>
|
||||
<div id="live-ui-gallery-dark-stage" class="live-ui-stage is-dark" data-live-gallery-preview="dark"></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<p class="live-ui-gallery__fidelity-note">
|
||||
Fidelity anchors: <code>live-browser.js</code> configure row, generation row, cycling row, Tune panel,
|
||||
pending-copy dock, global bar, annotation overlay, and DESIGN.md panel. Command icons and order are imported
|
||||
from <code>live/vocabulary.mjs</code>.
|
||||
</p>
|
||||
|
||||
<script is:inline type="application/json" data-live-gallery-states set:html={statesJson}></script>
|
||||
<script is:inline type="application/json" data-live-gallery-vocabulary set:html={commandPayload}></script>
|
||||
</section>
|
||||
|
||||
@@ -352,6 +352,7 @@ function initLiveUiGallery(root) {
|
||||
const select = root.querySelector('[data-gallery-state]');
|
||||
const groupReadout = root.querySelector('[data-gallery-state-group]');
|
||||
const labelReadout = root.querySelector('[data-gallery-state-label]');
|
||||
const quickNav = root.querySelector('.live-ui-gallery__quick-nav');
|
||||
const previews = [...root.querySelectorAll('[data-live-gallery-preview]')];
|
||||
let selectedAction = 'impeccable';
|
||||
let count = 3;
|
||||
@@ -366,9 +367,11 @@ function initLiveUiGallery(root) {
|
||||
if (groupReadout) groupReadout.textContent = state.group;
|
||||
if (labelReadout) labelReadout.textContent = state.label;
|
||||
|
||||
let activeStateButton = null;
|
||||
root.querySelectorAll('[data-gallery-state-button]').forEach((button) => {
|
||||
const active = button.dataset.galleryStateButton === state.key;
|
||||
button.setAttribute('aria-current', active ? 'true' : 'false');
|
||||
if (active) activeStateButton = button;
|
||||
});
|
||||
|
||||
for (const preview of previews) {
|
||||
@@ -376,6 +379,15 @@ function initLiveUiGallery(root) {
|
||||
preview.setAttribute('aria-label', `${preview.dataset.liveGalleryPreview} host — ${state.label}`);
|
||||
preview.innerHTML = sceneFor(state.key, { commands, selectedAction, count, designTab });
|
||||
}
|
||||
|
||||
if (quickNav && activeStateButton) {
|
||||
requestAnimationFrame(() => {
|
||||
const navRect = quickNav.getBoundingClientRect();
|
||||
const buttonRect = activeStateButton.getBoundingClientRect();
|
||||
if (buttonRect.left < navRect.left) quickNav.scrollLeft -= navRect.left - buttonRect.left + 8;
|
||||
if (buttonRect.right > navRect.right) quickNav.scrollLeft += buttonRect.right - navRect.right + 8;
|
||||
});
|
||||
}
|
||||
if (focusSelect) select?.focus();
|
||||
}
|
||||
|
||||
@@ -456,6 +468,21 @@ function initLiveUiGallery(root) {
|
||||
render('steer-processing');
|
||||
return;
|
||||
}
|
||||
|
||||
const stateButton = event.target.closest('[data-gallery-state-button]');
|
||||
if (stateButton && ['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) {
|
||||
event.preventDefault();
|
||||
const current = stateIndex(stateButton.dataset.galleryStateButton);
|
||||
const next = event.key === 'Home'
|
||||
? 0
|
||||
: event.key === 'End'
|
||||
? states.length - 1
|
||||
: (current + (event.key === 'ArrowLeft' ? -1 : 1) + states.length) % states.length;
|
||||
render(states[next].key);
|
||||
root.querySelector(`[data-gallery-state-button="${states[next].key}"]`)?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const panel = event.target.closest('.live-ui-gallery__control-panel');
|
||||
if (!panel || event.target.matches('input')) return;
|
||||
if (event.key === 'ArrowLeft') {
|
||||
@@ -464,6 +491,12 @@ function initLiveUiGallery(root) {
|
||||
} else if (event.key === 'ArrowRight') {
|
||||
event.preventDefault();
|
||||
step(1);
|
||||
} else if (event.key === 'Home') {
|
||||
event.preventDefault();
|
||||
render(states[0].key, { focusSelect: true });
|
||||
} else if (event.key === 'End') {
|
||||
event.preventDefault();
|
||||
render(states[states.length - 1].key, { focusSelect: true });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -481,4 +514,3 @@ if (document.readyState === 'loading') {
|
||||
}
|
||||
|
||||
document.addEventListener('astro:page-load', initLiveUiGalleries);
|
||||
|
||||
|
||||
+124
-127
@@ -45,10 +45,33 @@
|
||||
--lvg-fs-14: 0.875rem;
|
||||
--lvg-fs-15: 0.9375rem;
|
||||
|
||||
container: live-ui-gallery / inline-size;
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
height: clamp(580px, calc(100dvh - 112px), 820px);
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
color: var(--ks-text);
|
||||
background: var(--ks-lacquer-raised);
|
||||
font-family: var(--ks-font);
|
||||
}
|
||||
|
||||
/* Stable contract with the dev-only Live Lab shell. The outer toolbar owns
|
||||
* the view title; this component owns every pixel below it when UI is active. */
|
||||
.live-lab-ui-panel:has(> .live-ui-gallery) {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.live-lab-workbench[data-active-view='ui'] .live-lab-ui-panel:has(> .live-ui-gallery) {
|
||||
height: calc(100dvh - 112px);
|
||||
min-height: 560px;
|
||||
}
|
||||
|
||||
.live-lab-workbench[data-active-view='ui'] .live-ui-gallery {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.live-ui-gallery *,
|
||||
.live-ui-gallery *::before,
|
||||
.live-ui-gallery *::after {
|
||||
@@ -62,82 +85,58 @@
|
||||
}
|
||||
|
||||
.live-ui-gallery__header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 50;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(17rem, 25rem);
|
||||
gap: var(--ks-space-xl, 48px);
|
||||
align-items: end;
|
||||
padding-bottom: var(--ks-space-lg, 32px);
|
||||
grid-template-areas:
|
||||
'readout controls'
|
||||
'states states';
|
||||
grid-template-rows: 36px 40px;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(250px, 390px);
|
||||
column-gap: 16px;
|
||||
min-height: 77px;
|
||||
padding: 0 12px;
|
||||
background: var(--ks-lacquer-deep);
|
||||
border-bottom: 1px solid var(--ks-rule);
|
||||
}
|
||||
|
||||
.live-ui-gallery__intro {
|
||||
max-width: 44rem;
|
||||
}
|
||||
|
||||
.live-ui-gallery__eyebrow {
|
||||
margin: 0 0 10px;
|
||||
color: var(--ks-patina);
|
||||
font-family: var(--ks-mono);
|
||||
font-size: var(--ks-type-eyebrow-size);
|
||||
font-weight: 600;
|
||||
letter-spacing: var(--ks-type-eyebrow-track);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.live-ui-gallery__intro h2 {
|
||||
margin: 0;
|
||||
color: var(--ks-champagne);
|
||||
font-family: var(--ks-font-display);
|
||||
font-size: var(--ks-type-headline-size);
|
||||
font-weight: var(--ks-type-headline-weight);
|
||||
line-height: var(--ks-type-headline-line);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.live-ui-gallery__intro > p:last-child {
|
||||
max-width: 68ch;
|
||||
margin: 16px 0 0;
|
||||
color: var(--ks-text-muted);
|
||||
font-size: var(--ks-type-body-size);
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.live-ui-gallery__control-panel {
|
||||
padding: 18px;
|
||||
background: var(--ks-lacquer-raised);
|
||||
border: 1px solid var(--ks-rule);
|
||||
border-radius: 4px;
|
||||
grid-area: controls;
|
||||
min-width: 0;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.live-ui-gallery__control-panel > label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: var(--ks-text-muted);
|
||||
font-family: var(--ks-mono);
|
||||
font-size: var(--ks-type-eyebrow-size);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
.live-ui-gallery__sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.live-ui-gallery__select-row {
|
||||
display: grid;
|
||||
grid-template-columns: 38px minmax(0, 1fr) 38px;
|
||||
gap: 6px;
|
||||
grid-template-columns: 32px minmax(0, 1fr) 32px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.live-ui-gallery__select-row select,
|
||||
.live-ui-gallery__step {
|
||||
min-height: 38px;
|
||||
min-height: 32px;
|
||||
color: var(--ks-champagne);
|
||||
background: var(--ks-lacquer-deep);
|
||||
background: var(--ks-graphite);
|
||||
border: 1px solid var(--ks-rule);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.live-ui-gallery__select-row select {
|
||||
width: 100%;
|
||||
padding: 0 34px 0 12px;
|
||||
padding: 0 30px 0 10px;
|
||||
font-size: var(--lvg-fs-12);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -156,29 +155,36 @@
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.live-ui-gallery__shortcut {
|
||||
margin: 8px 0 0;
|
||||
color: var(--ks-text-faint);
|
||||
font-size: var(--lvg-fs-12);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.live-ui-gallery__quick-nav {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
padding: 20px 0;
|
||||
border-bottom: 1px solid var(--ks-rule);
|
||||
grid-area: states;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
padding: 7px 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
overscroll-behavior-inline: contain;
|
||||
scrollbar-color: var(--ks-graphite-2) transparent;
|
||||
scrollbar-width: thin;
|
||||
border-top: 1px solid var(--ks-rule);
|
||||
}
|
||||
|
||||
.live-ui-gallery__quick-group {
|
||||
display: grid;
|
||||
grid-template-columns: 8rem minmax(0, 1fr);
|
||||
gap: 14px;
|
||||
align-items: start;
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
padding-right: 12px;
|
||||
border-right: 1px solid var(--ks-rule);
|
||||
}
|
||||
|
||||
.live-ui-gallery__quick-group:last-child {
|
||||
padding-right: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.live-ui-gallery__quick-group > span {
|
||||
padding-top: 6px;
|
||||
white-space: nowrap;
|
||||
color: var(--ks-text-faint);
|
||||
font-family: var(--ks-mono);
|
||||
font-size: var(--ks-type-eyebrow-size);
|
||||
@@ -188,13 +194,13 @@
|
||||
|
||||
.live-ui-gallery__quick-group > div {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
flex-wrap: nowrap;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.live-ui-gallery__quick-nav button {
|
||||
min-height: 28px;
|
||||
padding: 5px 9px;
|
||||
min-height: 24px;
|
||||
padding: 3px 7px;
|
||||
color: var(--ks-text-muted);
|
||||
background: transparent;
|
||||
border: 1px solid var(--ks-rule);
|
||||
@@ -216,10 +222,11 @@
|
||||
}
|
||||
|
||||
.live-ui-gallery__state-readout {
|
||||
grid-area: readout;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
gap: 8px;
|
||||
align-items: baseline;
|
||||
padding: 24px 0 14px;
|
||||
}
|
||||
|
||||
.live-ui-gallery__state-readout span {
|
||||
@@ -231,19 +238,29 @@
|
||||
}
|
||||
|
||||
.live-ui-gallery__state-readout strong {
|
||||
overflow: hidden;
|
||||
color: var(--ks-champagne);
|
||||
font-size: var(--ks-type-title-size);
|
||||
font-weight: var(--ks-type-title-weight);
|
||||
font-size: var(--lvg-fs-14);
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.live-ui-gallery__previews {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
gap: 8px;
|
||||
min-height: 0;
|
||||
padding: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.live-ui-gallery__preview {
|
||||
container: live-ui-preview / inline-size;
|
||||
display: grid;
|
||||
grid-template-rows: 34px minmax(0, 1fr);
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background: var(--ks-lacquer-deep);
|
||||
border: 1px solid var(--ks-rule);
|
||||
@@ -254,8 +271,8 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 42px;
|
||||
padding: 0 14px;
|
||||
min-height: 34px;
|
||||
padding: 0 10px;
|
||||
color: var(--ks-text-muted);
|
||||
border-bottom: 1px solid var(--ks-rule);
|
||||
}
|
||||
@@ -290,8 +307,11 @@
|
||||
|
||||
.live-ui-stage {
|
||||
position: relative;
|
||||
min-height: 530px;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
isolation: isolate;
|
||||
font-family: var(--lvg-live-font);
|
||||
}
|
||||
@@ -1563,23 +1583,6 @@ button.lvg-dot {
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.live-ui-gallery__fidelity-note {
|
||||
max-width: 80ch;
|
||||
margin: 18px 0 0;
|
||||
color: var(--ks-text-faint);
|
||||
font-size: var(--lvg-fs-12);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.live-ui-gallery__fidelity-note code {
|
||||
padding: var(--ks-code-pad);
|
||||
color: var(--ks-code-fg);
|
||||
background: var(--ks-code-bg);
|
||||
border-radius: var(--ks-code-radius);
|
||||
font-family: var(--ks-mono);
|
||||
font-size: var(--ks-type-mono-size);
|
||||
}
|
||||
|
||||
@keyframes lvg-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
@@ -1604,31 +1607,7 @@ button.lvg-dot {
|
||||
35% { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
@media (max-width: 1120px) {
|
||||
.live-ui-gallery__previews {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.live-ui-stage {
|
||||
min-height: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.live-ui-gallery__header {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.live-ui-gallery__quick-group {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.live-ui-gallery__quick-group > span {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
@container live-ui-preview (max-width: 540px) {
|
||||
.lvg-global-mode span:not(.lvg-design-mark):not(.lvg-detect-badge) {
|
||||
display: none;
|
||||
}
|
||||
@@ -1643,7 +1622,7 @@ button.lvg-dot {
|
||||
}
|
||||
|
||||
.lvg-steer[data-expanded="true"] {
|
||||
width: min(230px, 42vw);
|
||||
width: min(230px, 42cqi);
|
||||
}
|
||||
|
||||
.lvg-tune-grid {
|
||||
@@ -1655,7 +1634,25 @@ button.lvg-dot {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
@container live-ui-gallery (max-width: 680px) {
|
||||
.live-ui-gallery__header {
|
||||
grid-template-areas:
|
||||
'readout'
|
||||
'controls'
|
||||
'states';
|
||||
grid-template-rows: 30px 36px 40px;
|
||||
grid-template-columns: 1fr;
|
||||
min-height: 107px;
|
||||
}
|
||||
|
||||
.live-ui-gallery__previews {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: repeat(2, minmax(340px, 1fr));
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@container live-ui-preview (max-width: 390px) {
|
||||
.lvg-host-page {
|
||||
padding-inline: 18px;
|
||||
background-position: 18px 0;
|
||||
|
||||
Reference in New Issue
Block a user