diff --git a/apps/template-viewer/public/template-assets b/apps/template-viewer/public/template-assets new file mode 120000 index 00000000000..3a89aaf1f30 --- /dev/null +++ b/apps/template-viewer/public/template-assets @@ -0,0 +1 @@ +../../docsite/public/template-assets \ No newline at end of file diff --git a/packages/cli/assets/templates/pages/detail-page/page.tsx b/packages/cli/assets/templates/pages/detail-page/page.tsx index 40314412d0c..e94d4531f73 100644 --- a/packages/cli/assets/templates/pages/detail-page/page.tsx +++ b/packages/cli/assets/templates/pages/detail-page/page.tsx @@ -44,14 +44,16 @@ import { import type {CSSProperties} from 'react'; // The only custom CSS in this template is small optical-alignment negative -// margins: LayoutHeader/TabList have no edge-dock prop (#2622) and List -// has no "bleed to container edge" prop (#2626). Everything else uses props. -// Plain inline styles — no StyleX compiler required. +// margins: the tab row's block dock has no prop (see tabsRow) and List has no +// "bleed to container edge" prop (#2626). Everything else uses props. Plain +// inline styles — no StyleX compiler required. -// Bleed the tab bar to the header's content edges so the active-tab underline -// meets the header divider. No edge-dock prop on TabList (#2622). +// Dock the tab bar on the header's bottom edge so the active-tab underline +// meets the header divider. TabList's `isFullBleed` handles the inline bleed; +// the block-end dock is still hand-tuned because the tab row sits inside +// nested flex wrappers (StackItem > HStack), where no component prop can +// reach the header's padding edge (a LayoutHeader docking slot would). const tabsRow: CSSProperties = { - marginInline: -12, marginBottom: -16, marginTop: 12, }; @@ -260,7 +262,11 @@ function PageHeader({ - + diff --git a/packages/core/src/TabList/TabList.tsx b/packages/core/src/TabList/TabList.tsx index a40fef5fed6..ab2d927803c 100644 --- a/packages/core/src/TabList/TabList.tsx +++ b/packages/core/src/TabList/TabList.tsx @@ -112,6 +112,22 @@ export interface TabListProps extends Omit, 'onChange'> { * @default false */ hasDivider?: boolean; + /** + * Makes the tab strip escape its parent's container padding, extending it + * out to the container's content edges. Reads the `--container-padding-*` + * custom properties that padded Layout containers (LayoutHeader, Card, + * Section, LayoutContent, ...) set and cancels them with negative margins. + * Use it to stretch a tab bar to a header's content edges so a + * `hasDivider` underline spans the full content width, replacing the + * negative-margin CSS that case otherwise requires. + * + * Matches Divider's `isFullBleed`: only the inline (start/end) edges + * bleed. Block-edge docking stays with the surrounding layout — the + * padded container's padding can't be reliably detected from here, so + * cancelling it block-wise would pull the strip into its siblings. + * @default false + */ + isFullBleed?: boolean; /** * ARIA role for the strip. * @@ -216,6 +232,22 @@ const styles = stylex.create({ paddingBlockEnd: spacingVars['--spacing-1'], '--_tab-indicator-bottom': `calc(-1 * (${spacingVars['--spacing-1']} + ${borderVars['--border-width']}))`, }, + // Cancel the nearest padded Layout container's inline padding so the strip + // reaches its content edges. Same `--container-padding-*` mechanism as + // Divider's horizontal `isFullBleed`, and like it inline-only: the + // `--container-padding-block-*` vars inherit through any wrapper, but + // first/last-child checks only see the strip's own parent, so a block-wise + // cancel would fire whenever the strip is alone in a wrapper and pull it + // into its siblings. The one addition over Divider is `maxWidth: 'none'`, + // needed because the `nav` base sets `maxWidth: '100%'`, which would + // otherwise clamp the widened box back inside the container's padding. + fullBleed: { + marginInlineStart: 'calc(-1 * var(--container-padding-inline-start, 0px))', + marginInlineEnd: 'calc(-1 * var(--container-padding-inline-end, 0px))', + maxWidth: 'none', + width: + 'calc(100% + var(--container-padding-inline-start, 0px) + var(--container-padding-inline-end, 0px))', + }, strip: { display: 'flex', alignItems: 'stretch', @@ -349,6 +381,7 @@ export function TabList({ size: sizeProp, layout = 'hug', hasDivider = false, + isFullBleed = false, role, overflow = 'auto', xstyle, @@ -625,6 +658,7 @@ export function TabList({ styles.nav, layout === 'fill' && styles.fill, hasDivider && styles.divider, + isFullBleed && styles.fullBleed, xstyle, ), className,