frontend: move shared/tabs.js+css into one-dir tabs/
Pure structural move, no API or behaviour change: tabs.js/tabs.css move into shared/src/tabs/, matching the one-dir-per-component layout the other shared components already use (hive-btn, hive-dialog, hive-toast, hive-menu, side-panel). Both files are consumed exclusively through @hive/shared's package.json exports map (./tabs.js, ./tabs.css), never by a raw relative path, so updating the two export targets is the only change needed -- none of the 9 call sites (dashboard tabbar, logs, core, builds, credentials, stats x2, agent stats) touch anything. Verified the built dashboard/agent bundles still resolve both files correctly.
This commit is contained in:
parent
f3569160d3
commit
fbc09f1b3d
3 changed files with 2 additions and 2 deletions
|
|
@ -8,8 +8,8 @@
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.js",
|
".": "./src/index.js",
|
||||||
"./terminal.js": "./src/terminal.js",
|
"./terminal.js": "./src/terminal.js",
|
||||||
"./tabs.js": "./src/tabs.js",
|
"./tabs.js": "./src/tabs/tabs.js",
|
||||||
"./tabs.css": "./src/tabs.css",
|
"./tabs.css": "./src/tabs/tabs.css",
|
||||||
"./colors.css": "./src/colors.css",
|
"./colors.css": "./src/colors.css",
|
||||||
"./theme.css": "./src/theme.css",
|
"./theme.css": "./src/theme.css",
|
||||||
"./base.css": "./src/base.css",
|
"./base.css": "./src/base.css",
|
||||||
|
|
|
||||||
37
frontend/packages/shared/src/tabs/tabs.css
Normal file
37
frontend/packages/shared/src/tabs/tabs.css
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
/* Shared tab-strip chrome — the generic tab/sub-tab styling used across
|
||||||
|
the dashboard tabbar, the logs sub-tabs, and the agent stat-window
|
||||||
|
selector. Behaviour lives in `@hive/shared/tabs.js` (createTabStrip).
|
||||||
|
|
||||||
|
Colours come from the semantic theme layer (theme.css → colors.css),
|
||||||
|
so this inherits theme-swap safety for free. Call sites keep a thin
|
||||||
|
variant class for size/spacing deltas; richer affordances (count
|
||||||
|
pills, the dashboard's responsive overflow ⋮ menu) layer on top in the
|
||||||
|
per-page CSS. See docs/web-ui/css-vars.md. */
|
||||||
|
|
||||||
|
.hive-tabbar {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hive-tab {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.35em 0.9em;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--subtext0);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.85em;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 100ms, color 100ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hive-tab:hover {
|
||||||
|
background: var(--border);
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hive-tab--active {
|
||||||
|
background: var(--border);
|
||||||
|
color: var(--purple);
|
||||||
|
}
|
||||||
71
frontend/packages/shared/src/tabs/tabs.js
Normal file
71
frontend/packages/shared/src/tabs/tabs.js
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
// Shared hash-routed tab strip — the generic behaviour behind the
|
||||||
|
// dashboard tabbar, the logs sub-tabs, and the agent stat-window
|
||||||
|
// selector. Pairs with `@hive/shared/tabs.css` (the .hive-tab* styles).
|
||||||
|
//
|
||||||
|
// Convention over configuration (no element-resolver callbacks):
|
||||||
|
// - Tab controls are the `[data-tab="<id>"]` elements inside the
|
||||||
|
// `tabbar` you pass in (any element — `<a>` or `<button>`; give
|
||||||
|
// them class `hive-tab`).
|
||||||
|
// - Panels are matched by `[data-tab-pane="<id>"]` anywhere in the
|
||||||
|
// document.
|
||||||
|
//
|
||||||
|
// Routing is always via `location.hash` so tabs are deep-linkable and
|
||||||
|
// honour back/forward. A page may only host one strip (one hash); if a
|
||||||
|
// second independent strip is ever needed we add a key prefix then.
|
||||||
|
//
|
||||||
|
// On show: toggles `.hive-tab--active` + `aria-selected` on each control
|
||||||
|
// and `hidden` on each panel, then fires `onShow(id)` for per-tab
|
||||||
|
// side-effects (lazy loads, etc.). See docs/web-ui/css-vars.md.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Element|null} tabbar container holding the `[data-tab]` controls
|
||||||
|
* @param {{ defaultId?: string, onShow?: (id: string) => void }} [opts]
|
||||||
|
* @returns {{ show: (id: string) => void, active: () => string|null }}
|
||||||
|
*/
|
||||||
|
export function createTabStrip(tabbar, opts = {}) {
|
||||||
|
const { defaultId, onShow } = opts;
|
||||||
|
if (!tabbar) {
|
||||||
|
return { show() {}, active: () => null };
|
||||||
|
}
|
||||||
|
const tabs = Array.from(tabbar.querySelectorAll('[data-tab]'));
|
||||||
|
const ids = tabs.map((t) => t.getAttribute('data-tab'));
|
||||||
|
const fallback = defaultId && ids.includes(defaultId) ? defaultId : ids[0];
|
||||||
|
|
||||||
|
const paneFor = (id) => document.querySelector(`[data-tab-pane="${id}"]`);
|
||||||
|
|
||||||
|
const active = () => {
|
||||||
|
const hash = location.hash.replace(/^#/, '');
|
||||||
|
return ids.includes(hash) ? hash : fallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
const show = (id) => {
|
||||||
|
const target = ids.includes(id) ? id : fallback;
|
||||||
|
for (const tab of tabs) {
|
||||||
|
const tid = tab.getAttribute('data-tab');
|
||||||
|
const on = tid === target;
|
||||||
|
tab.classList.toggle('hive-tab--active', on);
|
||||||
|
tab.setAttribute('aria-selected', String(on));
|
||||||
|
const pane = paneFor(tid);
|
||||||
|
if (pane) pane.hidden = !on;
|
||||||
|
}
|
||||||
|
if (typeof onShow === 'function') onShow(target);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Drive every activation through the hash so deep-links + back/forward
|
||||||
|
// stay authoritative; the hashchange listener does the actual showing.
|
||||||
|
for (const tab of tabs) {
|
||||||
|
tab.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const id = tab.getAttribute('data-tab');
|
||||||
|
if (location.hash.replace(/^#/, '') === id) {
|
||||||
|
show(id); // same hash → no hashchange, re-show directly
|
||||||
|
} else {
|
||||||
|
location.hash = id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
window.addEventListener('hashchange', () => show(active()));
|
||||||
|
|
||||||
|
show(active()); // initial render from the current hash
|
||||||
|
return { show, active };
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue