279 lines
6.7 KiB
CSS
279 lines
6.7 KiB
CSS
/* Base tabs component */
|
|
.tabs {
|
|
@apply flex gap-2;
|
|
|
|
/* Orientation styles */
|
|
&[data-orientation="horizontal"] {
|
|
@apply flex-col;
|
|
}
|
|
|
|
&[data-orientation="vertical"] {
|
|
@apply flex-row;
|
|
}
|
|
}
|
|
|
|
/* Tab list container */
|
|
.tabs__list-container {
|
|
@apply relative bg-default;
|
|
|
|
border-radius: calc(var(--radius) * 2.5);
|
|
|
|
/* Vertical scroller fills the container height so overflow detection
|
|
engages when the wrapper height is constrained (e.g. h-[240px]) */
|
|
> .tabs__list-container__scroller[data-orientation="vertical"] {
|
|
@apply h-full;
|
|
}
|
|
|
|
/* Overflow chevron buttons */
|
|
> .tabs__list-container__scroll-prev,
|
|
> .tabs__list-container__scroll-next {
|
|
@apply absolute z-2 hidden size-4 cursor-(--cursor-interactive) items-center justify-center rounded-full border-0 bg-transparent p-0 text-foreground outline-none;
|
|
|
|
/**
|
|
* Transitions
|
|
* CRITICAL: motion-reduce must be AFTER transition for correct override specificity
|
|
*/
|
|
transition: opacity 150ms var(--ease-smooth);
|
|
@apply motion-reduce:transition-none;
|
|
|
|
@media (hover: hover) {
|
|
&:hover {
|
|
@apply opacity-70;
|
|
}
|
|
}
|
|
|
|
&:focus-visible {
|
|
@apply status-focused;
|
|
}
|
|
}
|
|
|
|
/* Chevron position - horizontal orientation */
|
|
&:has([data-orientation="horizontal"]) {
|
|
> .tabs__list-container__scroll-prev {
|
|
@apply top-1/2 left-1 -translate-y-1/2;
|
|
}
|
|
|
|
> .tabs__list-container__scroll-next {
|
|
@apply top-1/2 right-1 -translate-y-1/2;
|
|
}
|
|
}
|
|
|
|
/* Chevron position - vertical orientation */
|
|
&:has([data-orientation="vertical"]) {
|
|
> .tabs__list-container__scroll-prev {
|
|
@apply top-1 left-1/2 -translate-x-1/2;
|
|
}
|
|
|
|
> .tabs__list-container__scroll-next {
|
|
@apply bottom-1 left-1/2 -translate-x-1/2;
|
|
}
|
|
}
|
|
|
|
/* Show chevrons only when ScrollShadow reports scrolling is possible */
|
|
&:has(
|
|
> :is(
|
|
[data-left-scroll="true"],
|
|
[data-left-right-scroll="true"],
|
|
[data-top-scroll="true"],
|
|
[data-top-bottom-scroll="true"]
|
|
)
|
|
)
|
|
> .tabs__list-container__scroll-prev {
|
|
@apply inline-flex;
|
|
}
|
|
|
|
&:has(
|
|
> :is(
|
|
[data-right-scroll="true"],
|
|
[data-left-right-scroll="true"],
|
|
[data-bottom-scroll="true"],
|
|
[data-top-bottom-scroll="true"]
|
|
)
|
|
)
|
|
> .tabs__list-container__scroll-next {
|
|
@apply inline-flex;
|
|
}
|
|
}
|
|
|
|
/* Tab list */
|
|
.tabs__list {
|
|
@apply inline-flex p-1;
|
|
|
|
/* Horizontal orientation - grow with content so ScrollShadow can detect overflow */
|
|
&[data-orientation="horizontal"] {
|
|
@apply w-max min-w-full flex-row;
|
|
}
|
|
|
|
/* Vertical orientation */
|
|
&[data-orientation="vertical"] {
|
|
@apply flex-col gap-1;
|
|
|
|
.tabs__tab {
|
|
@apply min-w-20;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Individual tab */
|
|
.tabs__tab {
|
|
@apply relative flex h-8 w-full items-center justify-center rounded-3xl px-4 text-center text-sm font-medium text-muted outline-none no-highlight;
|
|
|
|
z-index: 1;
|
|
cursor: var(--cursor-interactive);
|
|
|
|
/**
|
|
* Transitions
|
|
* CRITICAL: motion-reduce must be AFTER transition for correct override specificity
|
|
*/
|
|
transition:
|
|
color 150ms var(--ease-smooth),
|
|
background-color 150ms var(--ease-smooth),
|
|
box-shadow 150ms var(--ease-out),
|
|
opacity 150ms var(--ease-smooth);
|
|
@apply motion-reduce:transition-none;
|
|
|
|
/* Selected state */
|
|
&[data-selected="true"] {
|
|
@apply text-segment-foreground;
|
|
|
|
/* Hide separator on the selected tab and the one following it */
|
|
.tabs__separator,
|
|
& + .tabs__tab .tabs__separator {
|
|
@apply opacity-0;
|
|
}
|
|
}
|
|
|
|
/* Disabled state */
|
|
&:disabled,
|
|
&[data-disabled="true"],
|
|
&[aria-disabled="true"] {
|
|
@apply status-disabled;
|
|
}
|
|
|
|
/* Hover states - both approaches for compatibility */
|
|
@media (hover: hover) {
|
|
&:not([data-selected="true"]):not([data-disabled="true"]):hover,
|
|
&[data-hovered="true"]:not([data-selected="true"]):not([data-disabled="true"]) {
|
|
@apply opacity-70;
|
|
}
|
|
}
|
|
|
|
/* Focus states - comprehensive fallback */
|
|
&:focus-visible:not(:focus),
|
|
&[data-focus-visible="true"] {
|
|
@apply status-focused;
|
|
}
|
|
}
|
|
|
|
/* Tab separator */
|
|
.tabs__separator {
|
|
@apply pointer-events-none absolute rounded-sm bg-muted/25;
|
|
|
|
/**
|
|
* Transitions
|
|
* CRITICAL: motion-reduce must be AFTER transition for correct override specificity
|
|
*/
|
|
transition: opacity 150ms var(--ease-smooth);
|
|
@apply motion-reduce:transition-none;
|
|
|
|
/* Horizontal tabs - vertical separator */
|
|
.tabs__list[data-orientation="horizontal"] & {
|
|
@apply top-1/4 left-0 h-1/2 w-px;
|
|
}
|
|
|
|
/* Vertical tabs - horizontal separator */
|
|
.tabs__list[data-orientation="vertical"] & {
|
|
@apply top-0 left-[5%] h-px w-[90%];
|
|
}
|
|
}
|
|
|
|
/* Tab panel */
|
|
.tabs__panel {
|
|
@apply w-full p-2 outline-none;
|
|
|
|
/* Exiting animations */
|
|
&[data-exiting="true"] {
|
|
@apply absolute top-0 left-0 w-full;
|
|
}
|
|
|
|
/* Orientation spacing */
|
|
&[data-orientation="horizontal"] {
|
|
@apply mt-4;
|
|
}
|
|
|
|
&[data-orientation="vertical"] {
|
|
@apply ml-4;
|
|
}
|
|
}
|
|
|
|
/* Tab indicator - React Aria's SelectionIndicator */
|
|
.tabs__indicator {
|
|
@apply absolute top-0 left-0 size-full rounded-3xl bg-segment shadow-surface;
|
|
|
|
z-index: -1;
|
|
|
|
/**
|
|
* Transitions
|
|
* CRITICAL: motion-reduce must be AFTER transition for correct override specificity
|
|
*/
|
|
transition-duration: 250ms;
|
|
transition-property: translate, width, height;
|
|
transition-timing-function: var(--ease-out-fluid);
|
|
@apply motion-reduce:transition-none;
|
|
}
|
|
|
|
/* ==========================================================================
|
|
Secondary Variant
|
|
========================================================================== */
|
|
|
|
.tabs--secondary {
|
|
/* Container - flat, no pill. Underline on container (not list) so it stays
|
|
stable when the inner list scrolls horizontally inside the ScrollShadow. */
|
|
> .tabs__list-container {
|
|
@apply rounded-none bg-transparent;
|
|
|
|
/* Tab list - remove padding */
|
|
.tabs__list {
|
|
@apply p-0;
|
|
}
|
|
|
|
/* Hide separators in secondary variant */
|
|
.tabs__separator {
|
|
@apply hidden;
|
|
}
|
|
|
|
/* Tab - adjust for secondary variant */
|
|
.tabs__tab {
|
|
@apply rounded-none;
|
|
|
|
/* Selected state - use text-foreground */
|
|
&[data-selected="true"] {
|
|
@apply text-foreground;
|
|
}
|
|
}
|
|
|
|
/* Tab indicator - line style */
|
|
.tabs__indicator {
|
|
@apply rounded-none bg-accent shadow-none;
|
|
}
|
|
}
|
|
|
|
/* Horizontal orientation - bottom border + bottom line indicator */
|
|
&[data-orientation="horizontal"] > .tabs__list-container {
|
|
@apply border-b border-border;
|
|
|
|
.tabs__indicator {
|
|
@apply top-auto bottom-0 h-0.5;
|
|
}
|
|
}
|
|
|
|
/* Vertical orientation - left border + left line indicator */
|
|
&[data-orientation="vertical"] > .tabs__list-container {
|
|
@apply border-l border-border;
|
|
|
|
.tabs__indicator {
|
|
@apply top-0 left-0 h-full w-0.5;
|
|
}
|
|
}
|
|
}
|