mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 02:44:02 +00:00
feat(ui): keep one tab presentation and document the right rail tabs
Drop the four alternative strip presentations (cards-up, spine, icons, minimal) and the icon glyphs they needed; the strip marks the rail with data-rail-tabs so a rail without it keeps its legacy geometry. Describe the tab strip and the desktop one-owner rule in docs/CURRENT-STATE.md. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
1 parent
71bd645d59
commit
3fb2105fb1
5 files changed
+54
-232
No files matched your search
+8
-30
@@ -12,25 +12,12 @@ import { msg, t } from '../i18n/index.js';
|
||||
|
||||
export const RIGHT_RAIL_TABS_ID = 'right-rail-tabs';
|
||||
|
||||
/** Presentation variants, selected through `data-rail-tabs` on the rail. */
|
||||
export const RIGHT_RAIL_TAB_VARIANTS = Object.freeze([
|
||||
'cards-down',
|
||||
'cards-up',
|
||||
'spine',
|
||||
'icons',
|
||||
'minimal',
|
||||
]);
|
||||
export const DEFAULT_RIGHT_RAIL_TAB_VARIANT = 'cards-down';
|
||||
|
||||
/** Glyphs for the icon variant; every name is in index.html's icon_names. */
|
||||
const PANEL_ICONS = Object.freeze({
|
||||
'pp-toggles': 'tune',
|
||||
'cctv-panel': 'videocam',
|
||||
'street-level-panel': 'streetview',
|
||||
'global-context-panel': 'radar',
|
||||
'weather-panel': 'cloud',
|
||||
'recent-imagery-panel': 'satellite_alt',
|
||||
});
|
||||
/**
|
||||
* Marks the rail while its strip is mounted; styles/right-rail-tabs.css keys
|
||||
* the rail's tab geometry on it so a rail without a strip keeps its legacy
|
||||
* layout.
|
||||
*/
|
||||
export const RIGHT_RAIL_TABS_ATTRIBUTE = 'data-rail-tabs';
|
||||
|
||||
/**
|
||||
* The tab keyboard navigation moves to in a vertical tablist: Down and Up
|
||||
@@ -80,14 +67,12 @@ function panelTitle(panel) {
|
||||
* @param {(panelId: string, collapsed: boolean, options: object) => void} input.setPanelCollapsed
|
||||
* Owner callback; the strip never touches panel classes itself.
|
||||
* @param {Document} [input.documentRef]
|
||||
* @param {string} [input.variant] Initial presentation variant.
|
||||
* @returns {{ element: HTMLElement, sync: () => void, destroy: () => void }}
|
||||
*/
|
||||
export function createRightRailTabs({
|
||||
rail,
|
||||
setPanelCollapsed,
|
||||
documentRef = rail?.ownerDocument,
|
||||
variant = DEFAULT_RIGHT_RAIL_TAB_VARIANT,
|
||||
}) {
|
||||
if (!rail || !documentRef || typeof setPanelCollapsed !== 'function') {
|
||||
throw new TypeError(
|
||||
@@ -100,7 +85,7 @@ export function createRightRailTabs({
|
||||
strip.setAttribute('role', 'tablist');
|
||||
strip.setAttribute('aria-orientation', 'vertical');
|
||||
strip.setAttribute('aria-label', t('Panels'));
|
||||
if (!rail.dataset.railTabs) rail.dataset.railTabs = variant;
|
||||
rail.setAttribute(RIGHT_RAIL_TABS_ATTRIBUTE, 'vertical');
|
||||
|
||||
/** @type {Map<string, {tab: HTMLElement, label: HTMLElement}>} */
|
||||
const entries = new Map();
|
||||
@@ -116,14 +101,6 @@ export function createRightRailTabs({
|
||||
tab.dataset.railTab = panel.id;
|
||||
tab.setAttribute('role', 'tab');
|
||||
tab.setAttribute('aria-controls', panel.id);
|
||||
const icon = PANEL_ICONS[panel.id];
|
||||
if (icon) {
|
||||
const glyph = documentRef.createElement('span');
|
||||
glyph.className = 'material-symbols-outlined right-rail-tab-icon';
|
||||
glyph.setAttribute('aria-hidden', 'true');
|
||||
glyph.textContent = icon;
|
||||
tab.append(glyph);
|
||||
}
|
||||
const label = documentRef.createElement('span');
|
||||
label.className = 'right-rail-tab-label';
|
||||
tab.append(label);
|
||||
@@ -216,6 +193,7 @@ export function createRightRailTabs({
|
||||
strip.removeEventListener('click', onClick);
|
||||
strip.removeEventListener('keydown', onKeydown);
|
||||
strip.remove();
|
||||
rail.removeAttribute(RIGHT_RAIL_TABS_ATTRIBUTE);
|
||||
entries.clear();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
DEFAULT_RIGHT_RAIL_TAB_VARIANT,
|
||||
RIGHT_RAIL_TABS_ATTRIBUTE,
|
||||
RIGHT_RAIL_TABS_ID,
|
||||
createRightRailTabs,
|
||||
nextRailTabIndex,
|
||||
@@ -196,7 +196,7 @@ test('builds one tab per visible rail panel, in rail order, labelled from the pa
|
||||
assert.equal(f.strip.id, RIGHT_RAIL_TABS_ID);
|
||||
assert.equal(f.strip.getAttribute('role'), 'tablist');
|
||||
assert.equal(f.strip.getAttribute('aria-orientation'), 'vertical');
|
||||
assert.equal(f.rail.dataset.railTabs, DEFAULT_RIGHT_RAIL_TAB_VARIANT);
|
||||
assert.equal(f.rail.getAttribute(RIGHT_RAIL_TABS_ATTRIBUTE), 'vertical');
|
||||
assert.deepEqual(
|
||||
f.strip.children.map((tab) => [
|
||||
tab.dataset.railTab,
|
||||
@@ -328,6 +328,7 @@ test('destroy removes the strip and stops listening', () => {
|
||||
f.tabs.destroy();
|
||||
assert.equal(f.strip.parentElement, null);
|
||||
assert.equal(f.rail.children.includes(f.strip), false);
|
||||
assert.equal(f.rail.getAttribute(RIGHT_RAIL_TABS_ATTRIBUTE), null);
|
||||
fire(tab, 'click');
|
||||
assert.deepEqual(f.calls, []);
|
||||
f.tabs.destroy();
|
||||
|
||||
@@ -4,16 +4,10 @@
|
||||
exclusive layout (rightPanelRail.js) hides the collapsed ones, so the strip
|
||||
is the only launcher. Phones keep the sheet shell (mobile-shell.css).
|
||||
|
||||
`data-rail-tabs` on the rail picks a presentation:
|
||||
cards-down one glass card per tab, labels read top to bottom (default)
|
||||
cards-up the same cards, labels read bottom to top
|
||||
spine one continuous bar with an accent edge on the open tab
|
||||
icons upright icon above a sideways label
|
||||
minimal bare labels with an accent bar, like the HUD edge text
|
||||
|
||||
The attribute arrives with the strip (rightRailTabs.js), so a rail without
|
||||
it keeps its legacy geometry; the selectors carry it so they outrank the
|
||||
Cyber rail rules that follow this sheet. */
|
||||
One glass card per tab, labels read top to bottom like the HUD edge text.
|
||||
`data-rail-tabs` arrives on the rail with the strip (rightRailTabs.js), so
|
||||
a rail without it keeps its legacy geometry; the selectors carry it so they
|
||||
outrank the Cyber rail rules that follow this sheet. */
|
||||
|
||||
#right-context-rail[data-rail-tabs] {
|
||||
--right-rail-panel-width: 330px;
|
||||
@@ -87,11 +81,6 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* The icon font's own display rule must not reveal the glyph elsewhere. */
|
||||
.right-rail-tab > .right-rail-tab-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.right-rail-tab:hover {
|
||||
border-color: var(--glass-border-hover);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
@@ -105,135 +94,6 @@
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* ── cards-up: labels read bottom to top ───────────────────────────────── */
|
||||
#right-context-rail[data-rail-tabs='cards-up'] .right-rail-tab-label {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
@supports (writing-mode: sideways-lr) {
|
||||
#right-context-rail[data-rail-tabs='cards-up'] .right-rail-tab-label {
|
||||
writing-mode: sideways-lr;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── spine: one bar, accent edge toward the open panel ─────────────────── */
|
||||
#right-context-rail[data-rail-tabs='spine'] {
|
||||
--right-rail-tabs-gap: 8px;
|
||||
}
|
||||
|
||||
#right-context-rail[data-rail-tabs='spine'] #right-rail-tabs {
|
||||
gap: 0;
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 10px;
|
||||
background: var(--glass-bg);
|
||||
box-shadow:
|
||||
0 8px 32px rgba(0, 0, 0, 0.42),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
backdrop-filter: blur(24px) saturate(1.4);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(1.4);
|
||||
}
|
||||
|
||||
#right-context-rail[data-rail-tabs='spine'] .right-rail-tab {
|
||||
min-height: 64px;
|
||||
padding: 16px 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
|
||||
#right-context-rail[data-rail-tabs='spine'] .right-rail-tab + .right-rail-tab {
|
||||
border-top: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
#right-context-rail[data-rail-tabs='spine'] .right-rail-tab:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
#right-context-rail[data-rail-tabs='spine']
|
||||
.right-rail-tab[aria-selected='true'] {
|
||||
background: var(--accent-dim);
|
||||
box-shadow: inset 2px 0 0 var(--accent);
|
||||
}
|
||||
|
||||
/* ── icons: upright glyph over the sideways label ──────────────────────── */
|
||||
#right-context-rail[data-rail-tabs='icons'] {
|
||||
--right-rail-tabs-width: 40px;
|
||||
}
|
||||
|
||||
#right-context-rail[data-rail-tabs='icons'] .right-rail-tab {
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-height: 64px;
|
||||
padding: 10px 0 12px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
#right-context-rail[data-rail-tabs='icons']
|
||||
.right-rail-tab
|
||||
> .right-rail-tab-icon {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* ── minimal: bare labels with an accent bar, like the HUD edge text ───── */
|
||||
#right-context-rail[data-rail-tabs='minimal'] {
|
||||
--right-rail-tabs-width: 26px;
|
||||
--right-rail-tabs-gap: 14px;
|
||||
}
|
||||
|
||||
#right-context-rail[data-rail-tabs='minimal'] #right-rail-tabs {
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
#right-context-rail[data-rail-tabs='minimal'] .right-rail-tab {
|
||||
min-height: 0;
|
||||
padding: 4px 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
color: var(--text-secondary);
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
|
||||
#right-context-rail[data-rail-tabs='minimal'] .right-rail-tab::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
bottom: 2px;
|
||||
left: 2px;
|
||||
width: 2px;
|
||||
border-radius: 1px;
|
||||
background: transparent;
|
||||
transition: background-color var(--transition-fast);
|
||||
}
|
||||
|
||||
#right-context-rail[data-rail-tabs='minimal'] .right-rail-tab:hover {
|
||||
background: transparent;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
#right-context-rail[data-rail-tabs='minimal']
|
||||
.right-rail-tab[aria-selected='true'] {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
color: var(--accent);
|
||||
text-shadow: 0 0 8px var(--accent-glow);
|
||||
}
|
||||
|
||||
#right-context-rail[data-rail-tabs='minimal']
|
||||
.right-rail-tab[aria-selected='true']::before {
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 8px var(--accent-glow);
|
||||
}
|
||||
|
||||
/* ── Cyber skin ────────────────────────────────────────────────────────── */
|
||||
:root[data-ui-theme='cyber'] .right-rail-tab {
|
||||
border-color: var(--glass-border);
|
||||
@@ -256,48 +116,6 @@
|
||||
color: var(--cyber-red-bright);
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber']
|
||||
#right-context-rail[data-rail-tabs='spine']
|
||||
#right-rail-tabs {
|
||||
border-radius: var(--btn-radius);
|
||||
background-color: var(--cyber-panel-surface);
|
||||
background-image: var(--cyber-panel-surface-detail);
|
||||
box-shadow: var(--cyber-panel-shadow);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber']
|
||||
#right-context-rail[data-rail-tabs='spine']
|
||||
.right-rail-tab,
|
||||
:root[data-ui-theme='cyber']
|
||||
#right-context-rail[data-rail-tabs='minimal']
|
||||
.right-rail-tab {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber']
|
||||
#right-context-rail[data-rail-tabs='spine']
|
||||
.right-rail-tab[aria-selected='true'] {
|
||||
background: var(--accent-dim);
|
||||
box-shadow: inset 3px 0 0 var(--cyber-red);
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber']
|
||||
#right-context-rail[data-rail-tabs='minimal']
|
||||
.right-rail-tab[aria-selected='true'] {
|
||||
color: var(--cyber-red-bright);
|
||||
text-shadow: 0 0 8px var(--accent-glow);
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber']
|
||||
#right-context-rail[data-rail-tabs='minimal']
|
||||
.right-rail-tab[aria-selected='true']::before {
|
||||
background: var(--cyber-red);
|
||||
box-shadow: 0 0 8px var(--accent-glow);
|
||||
}
|
||||
|
||||
/* ── Phones keep the sheet shell ───────────────────────────────────────── */
|
||||
body.mobile-shell #right-context-rail {
|
||||
padding-right: 0;
|
||||
|
||||
Reference in new issue
Block a user