You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/eidos/components/tabs/tabs.css

499 lines
20 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/* ── Provider — variant-shared base ─────────────────────────────────────── */
[data-tabs] {
--_tabs-trigger-height: var(--tabs-trigger-height-md);
--_tabs-trigger-padding-inline: var(--tabs-trigger-padding-inline-md);
--_tabs-trigger-gap: var(--tabs-trigger-gap-md);
--_tabs-trigger-font-size: var(--tabs-trigger-font-size-md);
--_tabs-content-padding: var(--tabs-content-padding-md);
--_tabs-stack-gap: var(--tabs-stack-gap);
display: flex;
flex-direction: column;
gap: var(--_tabs-stack-gap);
min-width: 0;
}
[data-tabs][data-orientation='vertical'] {
flex-direction: row;
align-items: stretch;
}
/* ── Sizes ──────────────────────────────────────────────────────────────── */
[data-tabs][data-size='xs'] {
--_tabs-trigger-height: var(--tabs-trigger-height-xs);
--_tabs-trigger-padding-inline: var(--tabs-trigger-padding-inline-xs);
--_tabs-trigger-gap: var(--tabs-trigger-gap-xs);
--_tabs-trigger-font-size: var(--tabs-trigger-font-size-xs);
--_tabs-content-padding: var(--tabs-content-padding-xs);
}
[data-tabs][data-size='sm'] {
--_tabs-trigger-height: var(--tabs-trigger-height-sm);
--_tabs-trigger-padding-inline: var(--tabs-trigger-padding-inline-sm);
--_tabs-trigger-gap: var(--tabs-trigger-gap-sm);
--_tabs-trigger-font-size: var(--tabs-trigger-font-size-sm);
--_tabs-content-padding: var(--tabs-content-padding-sm);
}
[data-tabs][data-size='lg'] {
--_tabs-trigger-height: var(--tabs-trigger-height-lg);
--_tabs-trigger-padding-inline: var(--tabs-trigger-padding-inline-lg);
--_tabs-trigger-gap: var(--tabs-trigger-gap-lg);
--_tabs-trigger-font-size: var(--tabs-trigger-font-size-lg);
--_tabs-content-padding: var(--tabs-content-padding-lg);
}
/* ── List ───────────────────────────────────────────────────────────────── */
[data-tabs-list] {
position: relative;
display: flex;
flex-wrap: wrap;
gap: var(--tabs-list-gap);
min-width: 0;
/* border-box so a `fitted` list's intrinsic padding (pills / segmented track)
is absorbed INSIDE width:100% — without it the band overflows its column by
the padding (the right-edge asymmetry inside a popover). */
box-sizing: border-box;
background: var(--tabs-list-bg);
}
[data-tabs-list][data-orientation='vertical'] {
flex-direction: column;
flex: 0 0 var(--tabs-list-width);
align-items: stretch;
}
/* ── Trigger ────────────────────────────────────────────────────────────── */
[data-tabs-trigger] {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--_tabs-trigger-gap);
min-height: var(--_tabs-trigger-height);
padding-inline: var(--_tabs-trigger-padding-inline);
border: var(--tabs-trigger-border-width) solid var(--tabs-trigger-border);
border-radius: var(--tabs-trigger-radius);
background: var(--tabs-trigger-bg);
color: var(--tabs-trigger-fg);
font-family: var(--tabs-trigger-font-family);
font-size: var(--_tabs-trigger-font-size);
font-weight: var(--tabs-trigger-font-weight);
line-height: var(--tabs-trigger-line-height);
letter-spacing: var(--tabs-trigger-letter-spacing);
text-decoration: none;
cursor: pointer;
transition:
background var(--tabs-trigger-transition-duration) var(--tabs-trigger-transition-ease),
border-color var(--tabs-trigger-transition-duration) var(--tabs-trigger-transition-ease),
color var(--tabs-trigger-transition-duration) var(--tabs-trigger-transition-ease),
box-shadow var(--tabs-trigger-transition-duration) var(--tabs-trigger-transition-ease);
}
[data-tabs-trigger]:hover {
background-image: linear-gradient(var(--state-hover), var(--state-hover));
color: var(--tabs-hover-trigger-fg);
}
[data-tabs-trigger]:focus-visible {
/* Canonical focus ring — `outline` (HCM-safe, survives forced-colors, unlike the
2-layer box-shadow this replaced) with the shared focus tokens. Matches the
framework-wide focus sweep; on filled variants (pills / segmented) it hugs the
pill radius cleanly instead of reading as a heavy boxed input. */
outline: var(--tabs-trigger-focus-ring-width) solid var(--tabs-trigger-focus-ring-color);
outline-offset: var(--tabs-trigger-focus-ring-offset);
}
[data-tabs-trigger][data-disabled],
[data-tabs-trigger]:disabled {
cursor: default;
opacity: var(--tabs-disabled-opacity);
}
/* ── Content ────────────────────────────────────────────────────────────── */
[data-tabs-content] {
min-width: 0;
flex: 1 1 auto;
padding: var(--_tabs-content-padding);
border: var(--tabs-content-border-width) solid var(--tabs-content-border);
border-radius: var(--tabs-content-radius);
background: var(--tabs-content-bg);
color: var(--tabs-content-fg);
}
/* ── Variant: line ──────────────────────────────────────────────────────── */
[data-tabs][data-variant='line'] {
--_tabs-stack-gap: var(--tabs-line-stack-gap);
}
[data-tabs][data-variant='line'] [data-tabs-list] {
padding: 0;
border-bottom: var(--tabs-list-border-width) solid var(--tabs-list-border);
}
[data-tabs][data-variant='line'][data-orientation='vertical'] [data-tabs-list] {
border-bottom: none;
border-inline-end: var(--tabs-list-border-width) solid var(--tabs-list-border);
padding-inline-end: var(--tabs-line-list-padding-inline-end);
}
[data-tabs][data-variant='line'] [data-tabs-trigger] {
border-radius: 0;
border-color: transparent;
}
[data-tabs][data-variant='line'] [data-tabs-trigger][data-state='active'] {
color: var(--tabs-active-trigger-fg);
}
/* In `line` variant the underline is the indicator's job (when present)
* — but if no indicator is rendered, fall back to a static border so the
* active state remains visible. The indicator's styles (below) override
* this when the consumer renders it. */
[data-tabs][data-variant='line']:not(:has([data-tabs-indicator]))
[data-tabs-trigger][data-state='active'] {
border-bottom-color: var(--tabs-active-trigger-border);
}
[data-tabs][data-variant='line'][data-orientation='vertical']:not(:has([data-tabs-indicator]))
[data-tabs-trigger][data-state='active'] {
border-bottom-color: transparent;
border-inline-end-color: var(--tabs-active-trigger-border);
}
/* ── Variant: surface ───────────────────────────────────────────────────── */
[data-tabs][data-variant='surface'] {
padding: var(--tabs-surface-provider-padding);
border: var(--tabs-provider-border-width) solid var(--tabs-provider-border);
border-radius: var(--tabs-provider-radius);
background: var(--tabs-provider-bg);
box-shadow: var(--tabs-surface-provider-shadow);
}
[data-tabs][data-variant='surface'] [data-tabs-list] {
padding: var(--tabs-list-padding);
border: var(--tabs-list-border-width) solid var(--tabs-list-border);
border-radius: var(--tabs-list-radius);
background: var(--tabs-surface-list-bg);
}
[data-tabs][data-variant='surface'] [data-tabs-trigger][data-state='active'] {
background: var(--tabs-active-trigger-bg);
color: var(--tabs-active-trigger-fg);
border-color: var(--tabs-active-surface-trigger-border);
}
[data-tabs][data-variant='surface'] [data-tabs-content] {
border-color: var(--tabs-surface-content-border);
background: var(--tabs-surface-content-bg);
}
/* ── Variant: pills ─────────────────────────────────────────────────────── */
[data-tabs][data-variant='pills'] [data-tabs-list] {
padding: var(--tabs-list-padding);
border-radius: var(--tabs-list-radius);
background: var(--tabs-pills-list-bg);
}
[data-tabs][data-variant='pills'] [data-tabs-trigger] {
border-radius: var(--tabs-pills-trigger-radius);
}
/* In pills, the indicator (when present) IS the active background.
* Without an indicator, fall back to a static active background. */
[data-tabs][data-variant='pills']:not(:has([data-tabs-indicator]))
[data-tabs-trigger][data-state='active'] {
background: var(--tabs-active-trigger-bg);
color: var(--tabs-active-trigger-fg);
border-color: var(--tabs-active-pills-trigger-border);
box-shadow: var(--tabs-active-pills-trigger-shadow);
}
/* When the indicator IS present in pills, the active trigger keeps a
* neutral bg but raises its color above the indicator's tint. */
[data-tabs][data-variant='pills']:has([data-tabs-indicator])
[data-tabs-trigger][data-state='active'] {
color: var(--tabs-active-trigger-fg);
}
[data-tabs][data-variant='pills'] [data-tabs-content] {
padding-inline: 0;
padding-block: var(--tabs-pills-content-padding-block-start) 0;
border: none;
border-radius: 0;
background: transparent;
}
/* ── Variant: segmented ──────────────────────────────────────────────────────
* iOS / Untitled-UI segmented control. A filled, recessed track holds equal
* segments; the active segment is a PRIMARY accent pill (`segmented-trigger-
* bg-active` = primary-solid + on-solid text + subtle shadow), NOT an accent
* border nor a near-invisible neutral pill — so it reads clearly in light AND
* dark, not as a focus-ringed box. Both the active bg and the on-solid text
* are themeable tokens (override `--tabs-active-segmented-trigger-bg` /
* `-color-active` to retint or restore a neutral pill). The trigger radius is
* CONCENTRIC with the track (`segmented-trigger-radius` = track radius − list
* padding). Pair with `fitted` for equal-width segments. */
[data-tabs][data-variant='segmented'] [data-tabs-list] {
gap: 0;
padding: var(--tabs-list-padding);
border-radius: var(--tabs-segmented-list-radius);
background: var(--tabs-segmented-list-bg);
}
[data-tabs][data-variant='segmented'] [data-tabs-trigger] {
border-radius: var(--tabs-segmented-trigger-radius);
border-color: transparent;
}
/* Without an indicator, the active segment paints its own accent pill. */
[data-tabs][data-variant='segmented']:not(:has([data-tabs-indicator]))
[data-tabs-trigger][data-state='active'] {
background: var(--tabs-active-segmented-trigger-bg);
color: var(--tabs-active-segmented-trigger-fg);
box-shadow: var(--tabs-active-segmented-trigger-shadow);
}
/* With the indicator present, it IS the pill; the active trigger only raises
* its label to the on-solid colour above the accent indicator. */
[data-tabs][data-variant='segmented']:has([data-tabs-indicator])
[data-tabs-trigger][data-state='active'] {
color: var(--tabs-active-segmented-trigger-fg);
}
[data-tabs][data-variant='segmented'] [data-tabs-content] {
padding-inline: 0;
padding-block: var(--tabs-pills-content-padding-block-start) 0;
border: none;
border-radius: 0;
background: transparent;
}
/* ── Indicator ──────────────────────────────────────────────────────────────
* The wrapper writes inline `transform`, `width`, `height` based on the
* active trigger's offset rect. The recipe handles the slide animation,
* z-index, and per-variant appearance. */
/* Position + size come from soma's `--indicator-*` (the "soma positions, eidos
shapes" contract); the variants below own the look and may pin a cross-axis
dimension on top. Uses the independent `translate` property, not `transform`,
so a per-variant `transform` never wipes the slide.
Those four vars are soma's VALUE CHANNEL, not a theming surface: the
`MeasuredIndicator` layer writes the active trigger's offset rect onto this
element on every move, so a public `--tabs-indicator-width` on top would be a
knob a theme could pin and thereby break the slide — the adjudication
`navigation-menu` already carries for its own `indicator-w` (and the reason
its `-x` was retired dead). What belongs to a theme is the indicator's FORM —
thickness, fill, radius, shadow, the slide's duration and ease — and those
are the keys below.
`left` is PHYSICAL on purpose: soma measures `triggerRect.left - listRect.left`,
an offset that grows rightwards whatever the direction. A logical
`inset-inline-start` anchored the indicator to the RIGHT edge in RTL while the
offset kept pointing right, landing it on the wrong tab (measured: 400px off,
and RTL-1 could not see it because the offset used to be an INLINE transform
written by JS, not a CSS declaration). In LTR the two are identical. */
[data-tabs-indicator] {
position: absolute;
inset-block-start: 0;
left: 0;
pointer-events: none;
z-index: 0;
translate: var(--indicator-x, 0px) var(--indicator-y, 0px);
inline-size: var(--indicator-w, 0px);
block-size: var(--indicator-h, 0px);
transition:
translate var(--tabs-indicator-transition-duration) var(--tabs-indicator-transition-ease),
inline-size var(--tabs-indicator-transition-duration) var(--tabs-indicator-transition-ease),
block-size var(--tabs-indicator-transition-duration) var(--tabs-indicator-transition-ease),
opacity var(--duration-fast) var(--ease-out);
will-change: translate, inline-size, block-size;
}
[data-tabs-indicator]:not([data-ready]) {
opacity: 0;
}
/* `line` variant: indicator is a thin underline along the leading edge */
[data-tabs][data-variant='line'] [data-tabs-indicator] {
background: var(--tabs-active-trigger-border);
height: var(--tabs-indicator-line-thickness, 2px);
inset-block-start: auto;
inset-block-end: calc(var(--tabs-list-border-width) * -1);
}
[data-tabs][data-variant='line'][data-orientation='vertical'] [data-tabs-indicator] {
width: var(--tabs-indicator-line-thickness, 2px);
inset-inline-start: auto;
inset-inline-end: calc(var(--tabs-list-border-width) * -1);
height: auto;
}
/* When the recipe takes over indicator dimensions (line variant uses a
* fixed thickness), the wrapper's inline width/height for the cross axis
* are overridden by the rules above; the inline axis stays driven by JS. */
/* `pills` variant: indicator is the rounded active background */
[data-tabs][data-variant='pills'] [data-tabs-indicator] {
background: var(--tabs-active-trigger-bg);
border: var(--tabs-trigger-border-width) solid var(--tabs-active-pills-trigger-border);
border-radius: var(--tabs-pills-trigger-radius);
box-shadow: var(--tabs-active-pills-trigger-shadow);
z-index: 0;
}
/* `segmented` variant: indicator is the PRIMARY accent pill (no accent
* border), concentric with the track. */
[data-tabs][data-variant='segmented'] [data-tabs-indicator] {
background: var(--tabs-active-segmented-trigger-bg);
border-radius: var(--tabs-segmented-trigger-radius);
box-shadow: var(--tabs-active-segmented-trigger-shadow);
z-index: 0;
}
/* Triggers inside a list with an indicator need to layer above it so the
* label is readable. */
[data-tabs-list]:has([data-tabs-indicator]) [data-tabs-trigger] {
position: relative;
z-index: 1;
background: transparent;
}
[data-tabs-list]:has([data-tabs-indicator])
[data-tabs-trigger][data-state='active'] {
background: transparent;
}
/* Surface variant doesn't use the indicator — hide if accidentally rendered. */
[data-tabs][data-variant='surface'] [data-tabs-indicator] {
display: none;
}
/* ── Fitted ─────────────────────────────────────────────────────────────────
* List fills inline width and triggers split it equally (flex: 1 1 0).
* Wraps still work because flex-wrap is on the list — when wrapping
* happens, each row's triggers re-distribute. */
[data-tabs][data-fitted] [data-tabs-list] {
width: 100%; /* literal: identity — `fitted` IS "the list fills the inline axis"; a token here would be a knob that can only hold one value without unsaying the prop */
flex-wrap: nowrap;
}
[data-tabs][data-fitted] [data-tabs-trigger] {
flex: 1 1 0;
min-width: 0;
}
/* ── Scrollable list ───────────────────────────────────────────────────────
* Horizontal-only — vertical scroll is handled by the natural overflow
* of the column. Edge fade signals scrollability. */
[data-tabs][data-scrollable]:not([data-orientation='vertical']) [data-tabs-list] {
flex-wrap: nowrap;
overflow-x: auto;
scroll-snap-type: x proximity;
scrollbar-width: none;
mask-image: linear-gradient(
to right,
transparent,
black var(--tabs-scrollable-fade, 16px),
black calc(100% - var(--tabs-scrollable-fade, 16px)),
transparent
);
}
[data-tabs][data-scrollable] [data-tabs-list]::-webkit-scrollbar {
display: none;
}
[data-tabs][data-scrollable] [data-tabs-trigger] {
flex: 0 0 auto;
scroll-snap-align: start;
}
/* ── Collapse vertical → horizontal at narrow widths ────────────────────────
* `data-collapsed` is written by the wrapper via ResizeObserver when the
* provider is narrower than the `collapseAt` threshold. Selectors below
* mirror the horizontal-orientation rules so a vertical tablist gracefully
* adopts the horizontal layout on narrow widths. */
[data-tabs][data-collapsed][data-orientation='vertical'] {
flex-direction: column;
}
[data-tabs][data-collapsed][data-orientation='vertical'] [data-tabs-list] {
flex: 0 1 auto;
flex-direction: row;
align-items: center;
}
[data-tabs][data-collapsed][data-variant='line'][data-orientation='vertical']
[data-tabs-list] {
border-inline-end: none;
border-bottom: var(--tabs-list-border-width) solid var(--tabs-list-border);
padding-inline-end: 0;
}
[data-tabs][data-collapsed][data-variant='line'][data-orientation='vertical']
[data-tabs-indicator] {
width: auto;
height: var(--tabs-indicator-line-thickness, 2px);
inset-block-start: auto;
inset-block-end: calc(var(--tabs-list-border-width) * -1);
inset-inline-end: auto;
inset-inline-start: 0;
}
/* ── Content motion presets ────────────────────────────────────────────────
* The active panel animates in; inactive panels are unmounted (default
* lazy) or hidden (`keepMounted`). All presets respect prefers-reduced-
* motion below.
*
* MATERIALS from the motion channel (registered, themeable keyframes) — the
* trigger stays these recipe rules because tabs' own `data-motion` prop
* ('fade'/'slide') × `data-orientation` selects the flavour, a per-component
* axis the generic preset trigger doesn't model. The subtle 4px nudge feeds
* the distance hook from the canonical scale. */
[data-tabs-content][data-motion='fade'][data-state='active'] {
animation: fade-in var(--duration-fast) var(--ease-out) both;
}
[data-tabs-content][data-motion='slide'][data-state='active'][data-orientation='horizontal'] {
--motion-distance-md: var(--motion-distance-sm);
animation:
slide-from-bottom var(--duration-normal) var(--ease-out) both,
fade-in var(--duration-normal) var(--ease-out) both;
}
[data-tabs-content][data-motion='slide'][data-state='active'][data-orientation='vertical'] {
--motion-distance-md: var(--motion-distance-sm);
animation:
slide-from-right var(--duration-normal) var(--ease-out) both,
fade-in var(--duration-normal) var(--ease-out) both;
}
/* ── Reduced motion ─────────────────────────────────────────────────────────
* Honour the user's OS preference: indicator snaps without slide,
* content panels appear instantly. */
@media (prefers-reduced-motion: reduce) {
[data-tabs-indicator] {
transition: opacity var(--duration-fast) var(--ease-out);
}
[data-tabs-content][data-motion='fade'],
[data-tabs-content][data-motion='slide'] {
animation: none;
}
}

Powered by TurnKey Linux.