diff --git a/packages/core/src/TabList/Tab.tsx b/packages/core/src/TabList/Tab.tsx index eeccdfe69e3..dfa5fc593b0 100644 --- a/packages/core/src/TabList/Tab.tsx +++ b/packages/core/src/TabList/Tab.tsx @@ -105,18 +105,7 @@ const styles = stylex.create({ alignItems: 'center', justifyContent: 'center', gap: spacingVars['--spacing-1'], - // The edge stop of a full-bleed strip pads back in by the container's - // padding, so its label still lands on the container's content inset. - '--_tab-inset-start': { - default: spacingVars['--spacing-3'], - ':first-child': `var(--_tab-edge-inset-start, ${spacingVars['--spacing-3']})`, - }, - '--_tab-inset-end': { - default: spacingVars['--spacing-3'], - ':last-child': `var(--_tab-edge-inset-end, ${spacingVars['--spacing-3']})`, - }, - paddingInlineStart: 'var(--_tab-inset-start)', - paddingInlineEnd: 'var(--_tab-inset-end)', + paddingInline: spacingVars['--spacing-3'], backgroundColor: 'transparent', borderWidth: 0, borderStyle: 'none', @@ -164,8 +153,8 @@ const styles = stylex.create({ // with a bottom divider — that ancestor sets `--_tab-indicator-bottom` // to drop the indicator onto the rail beneath the reserved gap. bottom: 'var(--_tab-indicator-bottom, -1px)', - insetInlineStart: `var(--_tab-inset-start, ${spacingVars['--spacing-3']})`, - insetInlineEnd: `var(--_tab-inset-end, ${spacingVars['--spacing-3']})`, + insetInlineStart: spacingVars['--spacing-3'], + insetInlineEnd: spacingVars['--spacing-3'], height: '2px', borderRadius: radiusVars['--radius-full'], pointerEvents: 'none', diff --git a/packages/core/src/TabList/TabList.tsx b/packages/core/src/TabList/TabList.tsx index 3cf93bb2b64..5a29293d8dd 100644 --- a/packages/core/src/TabList/TabList.tsx +++ b/packages/core/src/TabList/TabList.tsx @@ -115,16 +115,16 @@ export interface TabListProps extends Omit, 'onChange'> { /** * Bleeds the strip out to its container's content edges. * - * Two things move, and both are computed from the `--container-padding-*` - * custom properties a padded Layout container publishes. The strip's box is - * pulled out through the container's inline padding, so a `hasDivider` - * underline — or the header's own divider, when the strip is docked on it — - * spans the full content width. The first and last stop then pad that same - * amount back in, so their labels still land on the container's content - * inset rather than 4px short of it. + * Both halves are computed from the `--container-padding-*` custom + * properties a padded Layout container publishes. The strip's box is pulled + * out through the container's inline padding, so a `hasDivider` underline — + * or the header's own divider, when the strip is docked on it — spans the + * full content width. The strip then pads back in by whatever that bleed + * exceeds a stop's own inline padding, so the first and last label still + * land on the container's content inset rather than 4px short of it. * - * Outside a padded container the properties are unset and the fallbacks - * leave the strip exactly as it was. + * Outside a padded container the properties are unset, the pad-back clamps + * to zero, and the strip is left exactly as it was. * @default false */ isFullBleed?: boolean; @@ -182,6 +182,17 @@ export interface TabListProps extends Omit, 'onChange'> { const RING_BLEED = `calc(${focusVars['--focus-outline-width']} + ${focusVars['--focus-outline-offset']})`; const BLEED_VAR = '--_tab-strip-bleed'; const BLEED = `var(${BLEED_VAR})`; + +/** + * A stop's own inline padding, restated here because a full-bleed strip has to + * subtract it: the strip pads back in only the part of the container's padding + * the stop does not already supply, so the edge label lands on the content + * inset. Kept in step with `Tab`/`TabMenu`'s `paddingInline` by hand — the + * import would be circular. + */ +const STOP_INLINE_PADDING = spacingVars['--spacing-3']; +const EDGE_PAD_START = `max(var(--container-padding-inline-start, 0px) - ${STOP_INLINE_PADDING}, 0px)`; +const EDGE_PAD_END = `max(var(--container-padding-inline-end, 0px) - ${STOP_INLINE_PADDING}, 0px)`; const INDICATOR_BLEED = 'calc(-1 * var(--_tab-indicator-bottom, -1px))'; const BLOCK_END_BLEED = `max(${BLEED}, ${INDICATOR_BLEED})`; @@ -233,20 +244,23 @@ const styles = stylex.create({ '--_tab-indicator-bottom': `calc(-1 * (${spacingVars['--spacing-1']} + ${borderVars['--border-width']}))`, }, fullBleed: { - // Half one: the box. Cancel the container's inline padding so the strip - // reaches its content edges. Same spelling as Divider's `isFullBleed`. + // Cancel the container's inline padding so the strip's box reaches its + // content edges. Same spelling as Divider's `isFullBleed`. marginInlineStart: 'calc(-1 * var(--container-padding-inline-start, 0px))', marginInlineEnd: 'calc(-1 * var(--container-padding-inline-end, 0px))', // The `nav` base clamps to 100%, which would pull the widened box back. maxWidth: 'none', width: 'calc(100% + var(--container-padding-inline-start, 0px) + var(--container-padding-inline-end, 0px))', - // Half two: the content. Publish the amount the edge stops pad back in. - // `max(…, --spacing-2)` is Table's `containerEdgeStyles` clamp: a stop - // never insets less than 8px, and an unpadded container (the properties - // are unset) falls through to the tab's own inset, changing nothing. - '--_tab-edge-inset-start': `max(var(--container-padding-inline-start, ${spacingVars['--spacing-3']}), ${spacingVars['--spacing-2']})`, - '--_tab-edge-inset-end': `max(var(--container-padding-inline-end, ${spacingVars['--spacing-3']}), ${spacingVars['--spacing-2']})`, + }, + // Put back the part of the bleed a stop's own inline padding does not + // already supply, so the first and last label land on the container's + // content inset while their boxes still hang out over it. Sits after + // `stripScroll` because both write the strip's inline padding; the ring + // bleed is carried through so the negative margin there still cancels it. + fullBleedStrip: { + paddingInlineStart: `calc(var(${BLEED_VAR}, 0px) + ${EDGE_PAD_START})`, + paddingInlineEnd: `calc(var(${BLEED_VAR}, 0px) + ${EDGE_PAD_END})`, }, strip: { display: 'flex', @@ -675,6 +689,7 @@ export function TabList({ stylex.props( styles.strip, hasScroll && styles.stripScroll, + isFullBleed && styles.fullBleedStrip, fadeStyle, ), )}> diff --git a/packages/core/src/TabList/TabMenu.tsx b/packages/core/src/TabList/TabMenu.tsx index 6e2dd4851ef..e2cb390a855 100644 --- a/packages/core/src/TabList/TabMenu.tsx +++ b/packages/core/src/TabList/TabMenu.tsx @@ -78,18 +78,7 @@ const styles = stylex.create({ alignItems: 'center', justifyContent: 'center', gap: spacingVars['--spacing-1'], - // Mirrors Tab: the edge stop of a full-bleed strip pads back in by the - // container's padding. - '--_tab-inset-start': { - default: spacingVars['--spacing-3'], - ':first-child': `var(--_tab-edge-inset-start, ${spacingVars['--spacing-3']})`, - }, - '--_tab-inset-end': { - default: spacingVars['--spacing-3'], - ':last-child': `var(--_tab-edge-inset-end, ${spacingVars['--spacing-3']})`, - }, - paddingInlineStart: 'var(--_tab-inset-start)', - paddingInlineEnd: 'var(--_tab-inset-end)', + paddingInline: spacingVars['--spacing-3'], backgroundColor: 'transparent', borderWidth: 0, borderStyle: 'none', @@ -135,8 +124,8 @@ const styles = stylex.create({ // drops onto the divider rail when an ancestor (TabList `hasDivider` or a // Toolbar with a bottom divider) sets `--_tab-indicator-bottom`. bottom: 'var(--_tab-indicator-bottom, -1px)', - insetInlineStart: `var(--_tab-inset-start, ${spacingVars['--spacing-3']})`, - insetInlineEnd: `var(--_tab-inset-end, ${spacingVars['--spacing-3']})`, + insetInlineStart: spacingVars['--spacing-3'], + insetInlineEnd: spacingVars['--spacing-3'], height: '2px', borderRadius: radiusVars['--radius-full'], pointerEvents: 'none',