Files
HMDesign/packages/ui/src/theme/tabs.css
T

201 lines
9.8 KiB
CSS

/* Tabs mapping: default Primary Fill (content), Secondary Fill (surface), Legacy non-fill and overflow move buttons (D-090). Horizontal, text-only, no size/variant.
States use Radix attributes: [data-state="active"|"inactive"], :hover, :focus-visible. */
:root, [data-hm-theme] {
/* ui_viz: docs/components/tabs.md §8, §16 — SOURCE_ADOPTED / COMPONENT_SCOPED (owner 2026-09-18). Not aliased to Button or radius/border tokens. */
--hm-tab-height: 34px;
--hm-tab-padding-x: 16px;
--hm-tab-radius-top: 4px;
--hm-tab-border-width: 1px;
/* ui_viz: docs/components/tabs.md §9 — Navigation Label 16/24/700, same weight for Selected */
--hm-tab-font-size: 16px;
--hm-tab-line-height: 24px;
--hm-tab-font-weight: 700;
/* ui_viz: docs/components/tabs.md §10 — SOURCE_ADOPTED. Default face and border are also marked TEMPORARY there (alpha composites of
Green tint / Green Light 2). Selected border is #DEDBD2 (color.stroke.tab.selected); no Semantic exists in HM, so the Primitive is used here. */
--hm-tab-background: color-mix(in srgb, var(--hm-sem-palette-green-tint) 45%, transparent);
--hm-tab-border-color: color-mix(in srgb, var(--hm-sem-palette-green-light-2) 35%, transparent);
--hm-tab-text: var(--hm-sem-palette-green-light-2);
--hm-tab-selected-background: var(--hm-sem-bg-surface);
--hm-tab-selected-border-color: var(--hm-sem-stroke-input-default);
--hm-tab-selected-text: var(--hm-sem-action-primary);
/* Hover (inactive only): Black 20% overlay — value Confirmed (tab.md 6, 8); foreground SOURCE_ADOPTED */
--hm-tab-hover-overlay: color-mix(in srgb, var(--hm-sem-palette-black) 20%, transparent);
--hm-tab-hover-text: var(--hm-sem-action-primary);
--hm-tab-transition-duration: 0.15s;
}
/* Frame holds the Bar (and the move buttons when the tabs overflow). The whole Bar overlaps the content boundary by 1px; Selected paints its bottom stroke in the surface color. */
[data-slot="tabs-frame"] {
display: flex;
align-items: flex-end;
width: 100%;
margin-bottom: -1px;
}
/* Bar: no surface of its own, gap 0, scrolls horizontally with the native scrollbar hidden. */
[data-slot="tabs-list"] {
display: flex;
align-items: flex-end;
flex: 0 1 auto;
gap: 0;
min-width: 0;
max-width: 100%;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
}
[data-slot="tabs-trigger"] {
box-sizing: border-box;
height: var(--hm-tab-height);
min-height: var(--hm-tab-height);
padding: 0 var(--hm-tab-padding-x);
border: var(--hm-tab-border-width) solid var(--hm-tab-border-color);
border-bottom-color: var(--hm-tab-selected-border-color);
border-radius: var(--hm-tab-radius-top) var(--hm-tab-radius-top) 0 0;
background-color: var(--hm-tab-background);
background-image: none;
color: var(--hm-tab-text);
font-size: var(--hm-tab-font-size);
line-height: var(--hm-tab-line-height);
font-weight: var(--hm-tab-font-weight);
box-shadow: none;
transition: color var(--hm-tab-transition-duration) ease, background-color var(--hm-tab-transition-duration) ease;
}
/* One shared border between neighbours. Must come before the Selected rule. */
[data-slot="tabs-trigger"] + [data-slot="tabs-trigger"] {
border-left-width: 0;
}
[data-slot="tabs-trigger"][data-state="active"] {
z-index: 2;
border-color: var(--hm-tab-selected-border-color);
border-bottom-color: var(--hm-tab-selected-background);
background-color: var(--hm-tab-selected-background);
color: var(--hm-tab-selected-text);
}
[data-slot="tabs-trigger"][data-state="inactive"]:hover:not(:disabled) {
background-image: linear-gradient(var(--hm-tab-hover-overlay), var(--hm-tab-hover-overlay));
color: var(--hm-tab-hover-text);
}
/* Focus is a global TBD in ui_viz: the shadcn focus ring from ui/tabs.tsx stays. The theme box-shadow is none, so keep the ring variables. */
[data-slot="tabs-trigger"]:focus-visible {
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000);
}
/* ---- Overflow move buttons (ui_viz tabs.md §24 D-090, CURRENT 2026-10-01). Outside the tablist, shown only when the tabs overflow. ---- */
:root, [data-hm-theme] {
--hm-tab-arrow-size: 34px;
--hm-tab-arrow-icon-size: 18px;
--hm-tab-arrow-background: var(--hm-sem-palette-green-light-1);
--hm-tab-arrow-border-color: var(--hm-sem-palette-green-dark-1);
--hm-tab-arrow-icon-color: var(--hm-sem-on-action-primary);
--hm-tab-arrow-disabled-opacity: 0.6;
}
[data-slot="tabs-frame"][data-overflow="true"] > [data-slot="tabs-list"] {
flex: 1 1 0;
}
[data-slot="tabs-arrow"] {
display: inline-flex;
flex: none;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: var(--hm-tab-arrow-size);
height: var(--hm-tab-arrow-size);
padding: 0;
border: var(--hm-tab-border-width) solid var(--hm-tab-arrow-border-color);
background-color: var(--hm-tab-arrow-background);
background-image: none;
color: var(--hm-tab-arrow-icon-color);
cursor: pointer;
outline: none;
}
[data-slot="tabs-arrow"][data-direction="previous"] { border-radius: var(--hm-tab-radius-top) 0 0 0; }
[data-slot="tabs-arrow"][data-direction="next"] { border-radius: 0 var(--hm-tab-radius-top) 0 0; }
[data-slot="tabs-arrow"] svg { width: var(--hm-tab-arrow-icon-size); height: var(--hm-tab-arrow-icon-size); }
[data-slot="tabs-arrow"]:hover:not(:disabled) {
background-image: linear-gradient(var(--hm-tab-hover-overlay), var(--hm-tab-hover-overlay));
}
[data-slot="tabs-arrow"]:disabled {
opacity: var(--hm-tab-arrow-disabled-opacity);
cursor: default;
pointer-events: none;
}
/* Focus value is global TBD: shadcn ring level kept (ring variables come from the tailwind focus-visible utilities). */
[data-slot="tabs-arrow"]:focus-visible {
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent);
}
/* In the overflow state every item keeps its own label width (no equal split). */
[data-slot="tabs-frame"][data-overflow="true"] [data-slot="tabs-trigger"] {
flex: 0 0 auto;
}
/* ---- Fill profile (ui_viz tabs.md §24 D-071~075): default Primary content; explicit surface is Secondary. ---- */
:root, [data-hm-theme] {
--hm-tab-fill-line-color: var(--hm-sem-palette-green-light-3);
/* REVIEW VALUE (ui_viz tabs.md §24: curve radius 8px, BORDER FOLLOW-UP). The curve itself is Confirmed, the radius is not. */
--hm-tab-fill-curve-radius: 8px;
}
[data-slot="tabs-frame"][data-fill="true"] {
--hm-tab-fill-panel: var(--hm-sem-bg-surface);
--hm-tab-fill-face: linear-gradient(var(--hm-sem-bg-surface), var(--hm-sem-bg-surface));
}
[data-slot="tabs-frame"][data-fill="true"][data-panel-surface="content"] {
--hm-tab-fill-panel: var(--hm-sem-bg-content);
/* D-02 (2026-10-09): only Primary's selected face starts at White alpha .8; the content stop, direction and geometry stay unchanged. */
--hm-tab-fill-face: linear-gradient(to bottom, color-mix(in srgb, var(--hm-sem-palette-white) 80%, transparent) 0%, var(--hm-sem-bg-content) 100%);
}
[data-slot="tabs-frame"][data-fill="true"] > [data-slot="tabs-list"] {
flex: 1 1 auto;
width: 100%;
}
[data-slot="tabs-frame"][data-fill="true"][data-overflow="false"] [data-slot="tabs-trigger"] {
flex: 1 1 0;
min-width: auto;
}
/* Inactive: only the bottom line is Green Light 3; face and top / vertical borders keep the basic profile values. */
[data-slot="tabs-frame"][data-fill="true"] [data-slot="tabs-trigger"] {
border-bottom-color: var(--hm-tab-fill-line-color);
}
/* Every fill neighbour restores its left border and overlaps it by 1px, matching the tested ui-viz geometry.
The active tab's z-index keeps its Green edge above the inactive border without producing a double-width line. */
[data-slot="tabs-frame"][data-fill="true"] [data-slot="tabs-trigger"] + [data-slot="tabs-trigger"] {
margin-left: calc(-1 * var(--hm-tab-border-width));
border-left-width: var(--hm-tab-border-width);
}
/* Selected: Green Light 3 top / left / right, bottom open in the panel color. */
[data-slot="tabs-frame"][data-fill="true"] [data-slot="tabs-trigger"][data-state="active"] {
border-color: var(--hm-tab-fill-line-color);
border-bottom-color: var(--hm-tab-fill-panel);
background-color: transparent;
background-image: var(--hm-tab-fill-face);
}
/* Curves: a quarter arc of the vertical Green line toward each neighbour, 1px Green. Inside the arc is transparent (the neighbour shows),
outside is the panel color. Decoration only: no pointer input. */
[data-slot="tabs-frame"][data-fill="true"] [data-slot="tabs-trigger"][data-state="active"]:not(:last-child)::after,
[data-slot="tabs-frame"][data-fill="true"] [data-slot="tabs-trigger"][data-state="active"]:not(:first-child)::before {
content: "";
position: absolute;
bottom: calc(-1 * var(--hm-tab-border-width));
width: var(--hm-tab-fill-curve-radius);
height: var(--hm-tab-fill-curve-radius);
pointer-events: none;
}
[data-slot="tabs-frame"][data-fill="true"] [data-slot="tabs-trigger"][data-state="active"]:not(:last-child)::after {
right: calc(-1 * var(--hm-tab-fill-curve-radius));
background: radial-gradient(circle at 100% 0, transparent calc(var(--hm-tab-fill-curve-radius) - var(--hm-tab-border-width)),
var(--hm-tab-fill-line-color) calc(var(--hm-tab-fill-curve-radius) - var(--hm-tab-border-width)),
var(--hm-tab-fill-line-color) var(--hm-tab-fill-curve-radius), var(--hm-tab-fill-panel) var(--hm-tab-fill-curve-radius));
}
[data-slot="tabs-frame"][data-fill="true"] [data-slot="tabs-trigger"][data-state="active"]:not(:first-child)::before {
left: calc(-1 * var(--hm-tab-fill-curve-radius));
background: radial-gradient(circle at 0 0, transparent calc(var(--hm-tab-fill-curve-radius) - var(--hm-tab-border-width)),
var(--hm-tab-fill-line-color) calc(var(--hm-tab-fill-curve-radius) - var(--hm-tab-border-width)),
var(--hm-tab-fill-line-color) var(--hm-tab-fill-curve-radius), var(--hm-tab-fill-panel) var(--hm-tab-fill-curve-radius));
}