@trackunit/react-components 2.10.3 → 2.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.cjs.js CHANGED
@@ -11178,6 +11178,12 @@ const useOverflowItems = ({ threshold = 1, childUniqueIdentifierAttribute = "id"
11178
11178
  * ### When to use
11179
11179
  * Use Sidebar for secondary in-page navigation (e.g., switching between views within a page). Works well with `Tabs` for page-level navigation.
11180
11180
  *
11181
+ * If you need a persistent nav column beside a content region (a page-level sidebar+content
11182
+ * layout, e.g. Site Home, Administration, Asset Home), use `SidebarContentLayout` instead - it
11183
+ * composes `Sidebar` internally and keeps its stacking breakpoint in sync with the content grid.
11184
+ * Use `Sidebar` directly only for a standalone collapsing nav list with no content pane (e.g.
11185
+ * inside a `PageHeader`'s `tabsList`, see `Tabs.stories.tsx`'s `ResponsivenessTwo`/`Three`).
11186
+ *
11181
11187
  * ### When not to use
11182
11188
  * Do not use Sidebar for primary app navigation (the main menu). For top-level navigation, use the app shell navigation.
11183
11189
  *
@@ -11237,6 +11243,205 @@ const Sidebar = ({ childContainerClassName, children, breakpoint = "lg", classNa
11237
11243
  }) })) })) : null] }));
11238
11244
  };
11239
11245
 
11246
+ // Responsive-switch mechanism decision (GLU-1576 spike)
11247
+ // ------------------------------------------------------
11248
+ // Three mechanisms were considered for the stack<->side-by-side switch:
11249
+ //
11250
+ // 1. Native CSS `@container` (the `layout-dual-column-sections` precedent in
11251
+ // `libs/css/core/src/generators/generate-css-theme.ts`) - responds to this component's own
11252
+ // available space rather than the viewport, which is what the parent spec asks for as a
11253
+ // best-effort goal. Rejected for two reasons:
11254
+ // a. It cannot be meaningfully unit-tested in this repo's test environment. Vitest here runs
11255
+ // against jsdom, which has no layout engine and does not evaluate `@container` conditions -
11256
+ // the class list would be identical regardless of the (fake) container width, so a test
11257
+ // asserting "stacks below the breakpoint" vs. "side-by-side above it" would pass or fail for
11258
+ // reasons unrelated to the actual behavior it's meant to guard.
11259
+ // b. More fundamentally, `Sidebar`'s own internal tab-bar<->vertical-list switch
11260
+ // (`cvaSidebarChildContainer` in `../Sidebar/Sidebar.variants`) is hard-wired to viewport-based
11261
+ // Tailwind breakpoint classes (`lg:grid-cols-1` etc.), not container queries. Changing only
11262
+ // this component to a container query would let the two switches disagree whenever this
11263
+ // component's own width diverges from the viewport's - precisely the scenario a future side
11264
+ // panel would create by narrowing this component without narrowing the viewport. Migrating
11265
+ // `Sidebar` itself to `@container` is out of scope for this phase (blast radius across every
11266
+ // existing `Sidebar` consumer, not just this one).
11267
+ // 2. `useContainerBreakpoints` (`../../hooks/useContainerBreakpoints`) - real container-width-based
11268
+ // and unit-testable (it's backed by a mockable `ResizeObserver`, see its own spec file), but
11269
+ // suffers from the exact same mechanism-mismatch problem as (1): `Sidebar`'s own switch still
11270
+ // only reacts to viewport width, so "passing a consistent breakpoint value through" would only
11271
+ // guarantee a shared prop *label*, not a shared trigger *condition*.
11272
+ // 3. Viewport-based Tailwind breakpoint classes (chosen) - matches `Sidebar`'s own existing
11273
+ // mechanism byte-for-byte, so both switches are driven by the identical `@media (min-width: ...)`
11274
+ // condition for the identical breakpoint token and can never disagree, by construction. This is
11275
+ // the specification's explicitly-sanctioned final fallback ("non-blocking... acceptable if both
11276
+ // above prove impractical"). It also keeps this component a genuinely pure-presentation one - no
11277
+ // refs, no ResizeObserver, no effects - which matches `HostPage`'s own hook-free design.
11278
+ //
11279
+ // Trade-off: this does not satisfy the parent spec's best-effort "respond to own available space"
11280
+ // user story. That gap is accepted deliberately (the spec tracks it as "a known gap rather than a
11281
+ // blocker") rather than solved by a mechanism that would make the two breakpoint switches capable
11282
+ // of disagreeing with each other.
11283
+ /**
11284
+ * The root grid. `min-h-0 flex-1` opts this into an ancestor `HostPage`'s height-cascading flex-col
11285
+ * fill contract (see `HostPage.tsx`), so it gets a real, bounded height instead of an auto-height
11286
+ * block. No `gap-*` utility is applied here, ever: the single visible gap between sidebar and
11287
+ * content at the side-by-side breakpoint is contributed entirely by the content cell's own
11288
+ * `p-content-space` left padding (see `cvaSidebarContentLayoutContent`) - a grid gap on top of that
11289
+ * would silently double it.
11290
+ *
11291
+ * Below the breakpoint (stacked, single column), `grid-rows-[min-content_minmax(0px,1fr)]` pins the
11292
+ * sidebar's row to its own content height and gives the content row every pixel of leftover space.
11293
+ * Without an explicit row template, both rows default to `auto` sizing, and this grid's own bounded
11294
+ * height (from the `flex-1` fill contract above) is almost always taller than the two rows' combined
11295
+ * natural content height - `align-content`'s default `normal` computes to `stretch` for a grid
11296
+ * container, so that leftover space gets distributed *proportionally across every `auto` row*, not
11297
+ * just content's. That silently inflates the sidebar's row (and thus its box) well past its own
11298
+ * content's height, leaving a blank gap below the nav items before content starts - a stacked-only
11299
+ * sibling of the `minmax(min-content,1fr)` grid-blowout class of bug already fixed once for Asset
11300
+ * Home's own internal grid (GLU-1562/Phase 17). `{breakpoint}:grid-rows-none` resets this back to a
11301
+ * single implicit `auto` row at/above the breakpoint, where sidebar and content share one grid row
11302
+ * (as two columns) and must both keep stretching to the full available height per `{bp}:h-full`.
11303
+ *
11304
+ * Each breakpoint's arbitrary-value class is spelled out in full (rather than built via string
11305
+ * interpolation) because Tailwind's build-time scanner extracts candidate class names via a static
11306
+ * regex over each source file's raw text, not by evaluating JavaScript - an interpolated class name
11307
+ * would never appear as a complete literal string anywhere in this file and so would silently fail
11308
+ * to generate any CSS at all.
11309
+ */
11310
+ const cvaSidebarContentLayout = cssClassVarianceUtilities.cvaMerge(["grid", "min-h-0", "flex-1", "grid-rows-[min-content_minmax(0px,1fr)]"], {
11311
+ variants: {
11312
+ breakpoint: {
11313
+ xs: ["grid-cols-1", "xs:grid-cols-[200px_minmax(0px,1fr)]", "xs:grid-rows-none"],
11314
+ sm: ["grid-cols-1", "sm:grid-cols-[200px_minmax(0px,1fr)]", "sm:grid-rows-none"],
11315
+ md: ["grid-cols-1", "md:grid-cols-[200px_minmax(0px,1fr)]", "md:grid-rows-none"],
11316
+ lg: ["grid-cols-1", "lg:grid-cols-[200px_minmax(0px,1fr)]", "lg:grid-rows-none"],
11317
+ xl: ["grid-cols-1", "xl:grid-cols-[200px_minmax(0px,1fr)]", "xl:grid-rows-none"],
11318
+ "2xl": ["grid-cols-1", "2xl:grid-cols-[200px_minmax(0px,1fr)]", "2xl:grid-rows-none"],
11319
+ "3xl": ["grid-cols-1", "3xl:grid-cols-[200px_minmax(0px,1fr)]", "3xl:grid-rows-none"],
11320
+ },
11321
+ },
11322
+ defaultVariants: {
11323
+ breakpoint: "lg",
11324
+ },
11325
+ });
11326
+ /**
11327
+ * The sidebar's own grid cell. `pt-4 pl-4` give it a fixed top/left inset (matching
11328
+ * `p-content-space`'s own `spacing-4`) now that there's no ambient ancestor padding left to inherit
11329
+ * one from.
11330
+ *
11331
+ * The right edge is a genuinely different edge depending on the breakpoint, so it's handled
11332
+ * accordingly rather than uniformly:
11333
+ * - Below the breakpoint, the grid is a single column (see `cvaSidebarContentLayout`), so the
11334
+ * sidebar spans the grid's full width and this edge is a true page edge, exactly like the left
11335
+ * edge - it gets the same `pr-4` inset.
11336
+ * - At/above the breakpoint, this edge instead borders the content column, and *most* of it is
11337
+ * owned entirely by content's own `p-content-space` left padding (see module doc comment above and
11338
+ * `cvaSidebarContentLayoutContent`) - stacking this cell's own `pr-4` on top of that would silently
11339
+ * double the gap. `{breakpoint}:pr-2` cancels *most*, but not all, of it there - see below for the
11340
+ * deliberate remainder.
11341
+ *
11342
+ * `h-full min-h-0 overflow-y-auto` (applied only from the breakpoint upward, matching `Sidebar`'s
11343
+ * own vertical-list mode) give the sidebar its own bounded height and local scroll: `Sidebar`'s
11344
+ * vertical nav list is unclipped by default, so a long list could otherwise be taller than the
11345
+ * grid row's available height, silently growing the row (the classic CSS grid "blowout") instead of
11346
+ * scrolling locally.
11347
+ *
11348
+ * `[scrollbar-gutter:stable]` rides along with that same `overflow-y-auto`, reserving genuine,
11349
+ * platform-correct scrollbar space (0 on overlay-scrollbar platforms like macOS, the native width
11350
+ * elsewhere) on the same edge, so a long enough nav list's scrollbar never overlaps the items - and
11351
+ * so the sidebar's rendered width stays consistent whether or not a *particular* page's item list
11352
+ * happens to be long enough to overflow. Supported by every evergreen browser (Chrome/Edge 94+,
11353
+ * Firefox 97+, Safari 18.2+); a no-op elsewhere, i.e. today's pre-fix behavior, so no regression risk.
11354
+ *
11355
+ * That reservation alone butts the items directly against the scrollbar track with zero breathing
11356
+ * room, though - `scrollbar-gutter` only guarantees no *overlap*, it isn't a visual gap. `{bp}:pr-2`
11357
+ * is the deliberate small remainder of the otherwise-cancelled `pr-4` that supplies that breathing
11358
+ * room, sized independently of both the scrollbar's own width (which the browser already reserves via
11359
+ * `scrollbar-gutter`) and of the sidebar/content gap above (which is `p-content-space`'s job, not
11360
+ * this padding's) - it exists purely so the sidebar's own content doesn't look glued to the sidebar's
11361
+ * own scrollbar. Keeping it a small fraction of the full `pr-4` (rather than restoring all of it, or
11362
+ * clawing space back from the reserved gutter) avoids meaningfully inflating the sidebar/content gap
11363
+ * `p-content-space` already owns - it is deliberately *not* zero, but not enough to look like a second
11364
+ * gap either.
11365
+ */
11366
+ const cvaSidebarContentLayoutSidebar = cssClassVarianceUtilities.cvaMerge(["pt-4", "pl-4", "pr-4"], {
11367
+ variants: {
11368
+ breakpoint: {
11369
+ xs: ["xs:pr-2", "xs:h-full", "xs:min-h-0", "xs:overflow-y-auto", "xs:[scrollbar-gutter:stable]"],
11370
+ sm: ["sm:pr-2", "sm:h-full", "sm:min-h-0", "sm:overflow-y-auto", "sm:[scrollbar-gutter:stable]"],
11371
+ md: ["md:pr-2", "md:h-full", "md:min-h-0", "md:overflow-y-auto", "md:[scrollbar-gutter:stable]"],
11372
+ lg: ["lg:pr-2", "lg:h-full", "lg:min-h-0", "lg:overflow-y-auto", "lg:[scrollbar-gutter:stable]"],
11373
+ xl: ["xl:pr-2", "xl:h-full", "xl:min-h-0", "xl:overflow-y-auto", "xl:[scrollbar-gutter:stable]"],
11374
+ "2xl": ["2xl:pr-2", "2xl:h-full", "2xl:min-h-0", "2xl:overflow-y-auto", "2xl:[scrollbar-gutter:stable]"],
11375
+ "3xl": ["3xl:pr-2", "3xl:h-full", "3xl:min-h-0", "3xl:overflow-y-auto", "3xl:[scrollbar-gutter:stable]"],
11376
+ },
11377
+ },
11378
+ defaultVariants: {
11379
+ breakpoint: "lg",
11380
+ },
11381
+ });
11382
+ /**
11383
+ * The content's own grid cell: `relative` (a positioning context for a future side panel/drawer to
11384
+ * anchor into), a definite height (`h-full`, chained from the root grid's own fill contract) with
11385
+ * its own local `overflow-y-auto` (this cell's content may be a third-party iframe, which must
11386
+ * contain its own overflow rather than bubbling it up).
11387
+ *
11388
+ * `p-content-space` is applied here by default (`contentSpace: true`): the padding decision is
11389
+ * exposed on the component that owns the content's box, not silently forced by some invisible
11390
+ * ancestor. Callers rendering a route that must sit flush (e.g. a full-page extension iframe) opt
11391
+ * out with `contentSpace={false}`.
11392
+ */
11393
+ const cvaSidebarContentLayoutContent = cssClassVarianceUtilities.cvaMerge(["relative", "h-full", "min-h-0", "overflow-y-auto"], {
11394
+ variants: {
11395
+ contentSpace: {
11396
+ true: ["p-content-space"],
11397
+ false: [],
11398
+ },
11399
+ },
11400
+ defaultVariants: {
11401
+ contentSpace: true,
11402
+ },
11403
+ });
11404
+
11405
+ /**
11406
+ * SidebarContentLayout is the shared page shell for any screen built from a persistent navigation
11407
+ * sidebar next to a main content area - the pattern behind Site Home, Administration, and Asset
11408
+ * Home. It keeps that pairing consistent everywhere it's used: the same breakpoint decides both
11409
+ * when the sidebar collapses to a tab bar and when content drops below it, so the two can never
11410
+ * disagree, and every page gets identical spacing without reimplementing it by hand.
11411
+ *
11412
+ * The content slot renders whatever it's given untouched - a routed `Outlet`, a form, a table - and
11413
+ * never imposes padding on it from the outside (see `contentSpace` to opt out of this component's
11414
+ * own default inset, e.g. for a full-page extension iframe that needs to sit flush to the edges).
11415
+ *
11416
+ * ### When to use
11417
+ * Use as the layout directly inside a `HostPage` for any page that needs a persistent navigation
11418
+ * sidebar alongside routed/tabbed content (e.g. Site Home, Administration, Asset Home).
11419
+ *
11420
+ * ### When not to use
11421
+ * Do not use for pages without a persistent sidebar - use `HostPage` directly instead.
11422
+ *
11423
+ * @example SidebarContentLayout with navigation items and routed content
11424
+ * ```tsx
11425
+ * import { SidebarContentLayout, SidebarItemProps } from "@trackunit/react-components";
11426
+ *
11427
+ * const SitePage = () => (
11428
+ * <SidebarContentLayout
11429
+ * content={<Outlet />}
11430
+ * sidebar={
11431
+ * <Button id="overview" variant="ghost-neutral">
11432
+ * Overview
11433
+ * </Button>
11434
+ * }
11435
+ * />
11436
+ * );
11437
+ * ```
11438
+ * @param {SidebarContentLayoutProps} props - The props for the SidebarContentLayout component
11439
+ * @returns {ReactElement} SidebarContentLayout component
11440
+ */
11441
+ const SidebarContentLayout = ({ breakpoint = "lg", className, content, contentSpace = true, "data-testid": dataTestId = "sidebar-content-layout", sidebar, style, ref, }) => {
11442
+ return (jsxRuntime.jsxs("div", { className: cvaSidebarContentLayout({ breakpoint, className }), "data-testid": dataTestId, ref: ref, style: style, children: [jsxRuntime.jsx("div", { className: cvaSidebarContentLayoutSidebar({ breakpoint }), "data-testid": `${dataTestId}-sidebar`, children: jsxRuntime.jsx(Sidebar, { breakpoint: breakpoint, children: sidebar }) }), jsxRuntime.jsx("div", { className: cvaSidebarContentLayoutContent({ contentSpace }), "data-testid": `${dataTestId}-content`, children: content })] }));
11443
+ };
11444
+
11240
11445
  const cvaTabsRoot = cssClassVarianceUtilities.cvaMerge([]);
11241
11446
  const cvaTabList = cssClassVarianceUtilities.cvaMerge([
11242
11447
  "flex",
@@ -14395,6 +14600,7 @@ exports.SectionHeader = SectionHeader;
14395
14600
  exports.SegmentedValueBar = SegmentedValueBar;
14396
14601
  exports.Sheet = Sheet;
14397
14602
  exports.Sidebar = Sidebar;
14603
+ exports.SidebarContentLayout = SidebarContentLayout;
14398
14604
  exports.SkeletonBlock = SkeletonBlock;
14399
14605
  exports.SkeletonLabel = SkeletonLabel;
14400
14606
  exports.SkeletonLines = SkeletonLines;
package/index.esm.js CHANGED
@@ -11177,6 +11177,12 @@ const useOverflowItems = ({ threshold = 1, childUniqueIdentifierAttribute = "id"
11177
11177
  * ### When to use
11178
11178
  * Use Sidebar for secondary in-page navigation (e.g., switching between views within a page). Works well with `Tabs` for page-level navigation.
11179
11179
  *
11180
+ * If you need a persistent nav column beside a content region (a page-level sidebar+content
11181
+ * layout, e.g. Site Home, Administration, Asset Home), use `SidebarContentLayout` instead - it
11182
+ * composes `Sidebar` internally and keeps its stacking breakpoint in sync with the content grid.
11183
+ * Use `Sidebar` directly only for a standalone collapsing nav list with no content pane (e.g.
11184
+ * inside a `PageHeader`'s `tabsList`, see `Tabs.stories.tsx`'s `ResponsivenessTwo`/`Three`).
11185
+ *
11180
11186
  * ### When not to use
11181
11187
  * Do not use Sidebar for primary app navigation (the main menu). For top-level navigation, use the app shell navigation.
11182
11188
  *
@@ -11236,6 +11242,205 @@ const Sidebar = ({ childContainerClassName, children, breakpoint = "lg", classNa
11236
11242
  }) })) })) : null] }));
11237
11243
  };
11238
11244
 
11245
+ // Responsive-switch mechanism decision (GLU-1576 spike)
11246
+ // ------------------------------------------------------
11247
+ // Three mechanisms were considered for the stack<->side-by-side switch:
11248
+ //
11249
+ // 1. Native CSS `@container` (the `layout-dual-column-sections` precedent in
11250
+ // `libs/css/core/src/generators/generate-css-theme.ts`) - responds to this component's own
11251
+ // available space rather than the viewport, which is what the parent spec asks for as a
11252
+ // best-effort goal. Rejected for two reasons:
11253
+ // a. It cannot be meaningfully unit-tested in this repo's test environment. Vitest here runs
11254
+ // against jsdom, which has no layout engine and does not evaluate `@container` conditions -
11255
+ // the class list would be identical regardless of the (fake) container width, so a test
11256
+ // asserting "stacks below the breakpoint" vs. "side-by-side above it" would pass or fail for
11257
+ // reasons unrelated to the actual behavior it's meant to guard.
11258
+ // b. More fundamentally, `Sidebar`'s own internal tab-bar<->vertical-list switch
11259
+ // (`cvaSidebarChildContainer` in `../Sidebar/Sidebar.variants`) is hard-wired to viewport-based
11260
+ // Tailwind breakpoint classes (`lg:grid-cols-1` etc.), not container queries. Changing only
11261
+ // this component to a container query would let the two switches disagree whenever this
11262
+ // component's own width diverges from the viewport's - precisely the scenario a future side
11263
+ // panel would create by narrowing this component without narrowing the viewport. Migrating
11264
+ // `Sidebar` itself to `@container` is out of scope for this phase (blast radius across every
11265
+ // existing `Sidebar` consumer, not just this one).
11266
+ // 2. `useContainerBreakpoints` (`../../hooks/useContainerBreakpoints`) - real container-width-based
11267
+ // and unit-testable (it's backed by a mockable `ResizeObserver`, see its own spec file), but
11268
+ // suffers from the exact same mechanism-mismatch problem as (1): `Sidebar`'s own switch still
11269
+ // only reacts to viewport width, so "passing a consistent breakpoint value through" would only
11270
+ // guarantee a shared prop *label*, not a shared trigger *condition*.
11271
+ // 3. Viewport-based Tailwind breakpoint classes (chosen) - matches `Sidebar`'s own existing
11272
+ // mechanism byte-for-byte, so both switches are driven by the identical `@media (min-width: ...)`
11273
+ // condition for the identical breakpoint token and can never disagree, by construction. This is
11274
+ // the specification's explicitly-sanctioned final fallback ("non-blocking... acceptable if both
11275
+ // above prove impractical"). It also keeps this component a genuinely pure-presentation one - no
11276
+ // refs, no ResizeObserver, no effects - which matches `HostPage`'s own hook-free design.
11277
+ //
11278
+ // Trade-off: this does not satisfy the parent spec's best-effort "respond to own available space"
11279
+ // user story. That gap is accepted deliberately (the spec tracks it as "a known gap rather than a
11280
+ // blocker") rather than solved by a mechanism that would make the two breakpoint switches capable
11281
+ // of disagreeing with each other.
11282
+ /**
11283
+ * The root grid. `min-h-0 flex-1` opts this into an ancestor `HostPage`'s height-cascading flex-col
11284
+ * fill contract (see `HostPage.tsx`), so it gets a real, bounded height instead of an auto-height
11285
+ * block. No `gap-*` utility is applied here, ever: the single visible gap between sidebar and
11286
+ * content at the side-by-side breakpoint is contributed entirely by the content cell's own
11287
+ * `p-content-space` left padding (see `cvaSidebarContentLayoutContent`) - a grid gap on top of that
11288
+ * would silently double it.
11289
+ *
11290
+ * Below the breakpoint (stacked, single column), `grid-rows-[min-content_minmax(0px,1fr)]` pins the
11291
+ * sidebar's row to its own content height and gives the content row every pixel of leftover space.
11292
+ * Without an explicit row template, both rows default to `auto` sizing, and this grid's own bounded
11293
+ * height (from the `flex-1` fill contract above) is almost always taller than the two rows' combined
11294
+ * natural content height - `align-content`'s default `normal` computes to `stretch` for a grid
11295
+ * container, so that leftover space gets distributed *proportionally across every `auto` row*, not
11296
+ * just content's. That silently inflates the sidebar's row (and thus its box) well past its own
11297
+ * content's height, leaving a blank gap below the nav items before content starts - a stacked-only
11298
+ * sibling of the `minmax(min-content,1fr)` grid-blowout class of bug already fixed once for Asset
11299
+ * Home's own internal grid (GLU-1562/Phase 17). `{breakpoint}:grid-rows-none` resets this back to a
11300
+ * single implicit `auto` row at/above the breakpoint, where sidebar and content share one grid row
11301
+ * (as two columns) and must both keep stretching to the full available height per `{bp}:h-full`.
11302
+ *
11303
+ * Each breakpoint's arbitrary-value class is spelled out in full (rather than built via string
11304
+ * interpolation) because Tailwind's build-time scanner extracts candidate class names via a static
11305
+ * regex over each source file's raw text, not by evaluating JavaScript - an interpolated class name
11306
+ * would never appear as a complete literal string anywhere in this file and so would silently fail
11307
+ * to generate any CSS at all.
11308
+ */
11309
+ const cvaSidebarContentLayout = cvaMerge(["grid", "min-h-0", "flex-1", "grid-rows-[min-content_minmax(0px,1fr)]"], {
11310
+ variants: {
11311
+ breakpoint: {
11312
+ xs: ["grid-cols-1", "xs:grid-cols-[200px_minmax(0px,1fr)]", "xs:grid-rows-none"],
11313
+ sm: ["grid-cols-1", "sm:grid-cols-[200px_minmax(0px,1fr)]", "sm:grid-rows-none"],
11314
+ md: ["grid-cols-1", "md:grid-cols-[200px_minmax(0px,1fr)]", "md:grid-rows-none"],
11315
+ lg: ["grid-cols-1", "lg:grid-cols-[200px_minmax(0px,1fr)]", "lg:grid-rows-none"],
11316
+ xl: ["grid-cols-1", "xl:grid-cols-[200px_minmax(0px,1fr)]", "xl:grid-rows-none"],
11317
+ "2xl": ["grid-cols-1", "2xl:grid-cols-[200px_minmax(0px,1fr)]", "2xl:grid-rows-none"],
11318
+ "3xl": ["grid-cols-1", "3xl:grid-cols-[200px_minmax(0px,1fr)]", "3xl:grid-rows-none"],
11319
+ },
11320
+ },
11321
+ defaultVariants: {
11322
+ breakpoint: "lg",
11323
+ },
11324
+ });
11325
+ /**
11326
+ * The sidebar's own grid cell. `pt-4 pl-4` give it a fixed top/left inset (matching
11327
+ * `p-content-space`'s own `spacing-4`) now that there's no ambient ancestor padding left to inherit
11328
+ * one from.
11329
+ *
11330
+ * The right edge is a genuinely different edge depending on the breakpoint, so it's handled
11331
+ * accordingly rather than uniformly:
11332
+ * - Below the breakpoint, the grid is a single column (see `cvaSidebarContentLayout`), so the
11333
+ * sidebar spans the grid's full width and this edge is a true page edge, exactly like the left
11334
+ * edge - it gets the same `pr-4` inset.
11335
+ * - At/above the breakpoint, this edge instead borders the content column, and *most* of it is
11336
+ * owned entirely by content's own `p-content-space` left padding (see module doc comment above and
11337
+ * `cvaSidebarContentLayoutContent`) - stacking this cell's own `pr-4` on top of that would silently
11338
+ * double the gap. `{breakpoint}:pr-2` cancels *most*, but not all, of it there - see below for the
11339
+ * deliberate remainder.
11340
+ *
11341
+ * `h-full min-h-0 overflow-y-auto` (applied only from the breakpoint upward, matching `Sidebar`'s
11342
+ * own vertical-list mode) give the sidebar its own bounded height and local scroll: `Sidebar`'s
11343
+ * vertical nav list is unclipped by default, so a long list could otherwise be taller than the
11344
+ * grid row's available height, silently growing the row (the classic CSS grid "blowout") instead of
11345
+ * scrolling locally.
11346
+ *
11347
+ * `[scrollbar-gutter:stable]` rides along with that same `overflow-y-auto`, reserving genuine,
11348
+ * platform-correct scrollbar space (0 on overlay-scrollbar platforms like macOS, the native width
11349
+ * elsewhere) on the same edge, so a long enough nav list's scrollbar never overlaps the items - and
11350
+ * so the sidebar's rendered width stays consistent whether or not a *particular* page's item list
11351
+ * happens to be long enough to overflow. Supported by every evergreen browser (Chrome/Edge 94+,
11352
+ * Firefox 97+, Safari 18.2+); a no-op elsewhere, i.e. today's pre-fix behavior, so no regression risk.
11353
+ *
11354
+ * That reservation alone butts the items directly against the scrollbar track with zero breathing
11355
+ * room, though - `scrollbar-gutter` only guarantees no *overlap*, it isn't a visual gap. `{bp}:pr-2`
11356
+ * is the deliberate small remainder of the otherwise-cancelled `pr-4` that supplies that breathing
11357
+ * room, sized independently of both the scrollbar's own width (which the browser already reserves via
11358
+ * `scrollbar-gutter`) and of the sidebar/content gap above (which is `p-content-space`'s job, not
11359
+ * this padding's) - it exists purely so the sidebar's own content doesn't look glued to the sidebar's
11360
+ * own scrollbar. Keeping it a small fraction of the full `pr-4` (rather than restoring all of it, or
11361
+ * clawing space back from the reserved gutter) avoids meaningfully inflating the sidebar/content gap
11362
+ * `p-content-space` already owns - it is deliberately *not* zero, but not enough to look like a second
11363
+ * gap either.
11364
+ */
11365
+ const cvaSidebarContentLayoutSidebar = cvaMerge(["pt-4", "pl-4", "pr-4"], {
11366
+ variants: {
11367
+ breakpoint: {
11368
+ xs: ["xs:pr-2", "xs:h-full", "xs:min-h-0", "xs:overflow-y-auto", "xs:[scrollbar-gutter:stable]"],
11369
+ sm: ["sm:pr-2", "sm:h-full", "sm:min-h-0", "sm:overflow-y-auto", "sm:[scrollbar-gutter:stable]"],
11370
+ md: ["md:pr-2", "md:h-full", "md:min-h-0", "md:overflow-y-auto", "md:[scrollbar-gutter:stable]"],
11371
+ lg: ["lg:pr-2", "lg:h-full", "lg:min-h-0", "lg:overflow-y-auto", "lg:[scrollbar-gutter:stable]"],
11372
+ xl: ["xl:pr-2", "xl:h-full", "xl:min-h-0", "xl:overflow-y-auto", "xl:[scrollbar-gutter:stable]"],
11373
+ "2xl": ["2xl:pr-2", "2xl:h-full", "2xl:min-h-0", "2xl:overflow-y-auto", "2xl:[scrollbar-gutter:stable]"],
11374
+ "3xl": ["3xl:pr-2", "3xl:h-full", "3xl:min-h-0", "3xl:overflow-y-auto", "3xl:[scrollbar-gutter:stable]"],
11375
+ },
11376
+ },
11377
+ defaultVariants: {
11378
+ breakpoint: "lg",
11379
+ },
11380
+ });
11381
+ /**
11382
+ * The content's own grid cell: `relative` (a positioning context for a future side panel/drawer to
11383
+ * anchor into), a definite height (`h-full`, chained from the root grid's own fill contract) with
11384
+ * its own local `overflow-y-auto` (this cell's content may be a third-party iframe, which must
11385
+ * contain its own overflow rather than bubbling it up).
11386
+ *
11387
+ * `p-content-space` is applied here by default (`contentSpace: true`): the padding decision is
11388
+ * exposed on the component that owns the content's box, not silently forced by some invisible
11389
+ * ancestor. Callers rendering a route that must sit flush (e.g. a full-page extension iframe) opt
11390
+ * out with `contentSpace={false}`.
11391
+ */
11392
+ const cvaSidebarContentLayoutContent = cvaMerge(["relative", "h-full", "min-h-0", "overflow-y-auto"], {
11393
+ variants: {
11394
+ contentSpace: {
11395
+ true: ["p-content-space"],
11396
+ false: [],
11397
+ },
11398
+ },
11399
+ defaultVariants: {
11400
+ contentSpace: true,
11401
+ },
11402
+ });
11403
+
11404
+ /**
11405
+ * SidebarContentLayout is the shared page shell for any screen built from a persistent navigation
11406
+ * sidebar next to a main content area - the pattern behind Site Home, Administration, and Asset
11407
+ * Home. It keeps that pairing consistent everywhere it's used: the same breakpoint decides both
11408
+ * when the sidebar collapses to a tab bar and when content drops below it, so the two can never
11409
+ * disagree, and every page gets identical spacing without reimplementing it by hand.
11410
+ *
11411
+ * The content slot renders whatever it's given untouched - a routed `Outlet`, a form, a table - and
11412
+ * never imposes padding on it from the outside (see `contentSpace` to opt out of this component's
11413
+ * own default inset, e.g. for a full-page extension iframe that needs to sit flush to the edges).
11414
+ *
11415
+ * ### When to use
11416
+ * Use as the layout directly inside a `HostPage` for any page that needs a persistent navigation
11417
+ * sidebar alongside routed/tabbed content (e.g. Site Home, Administration, Asset Home).
11418
+ *
11419
+ * ### When not to use
11420
+ * Do not use for pages without a persistent sidebar - use `HostPage` directly instead.
11421
+ *
11422
+ * @example SidebarContentLayout with navigation items and routed content
11423
+ * ```tsx
11424
+ * import { SidebarContentLayout, SidebarItemProps } from "@trackunit/react-components";
11425
+ *
11426
+ * const SitePage = () => (
11427
+ * <SidebarContentLayout
11428
+ * content={<Outlet />}
11429
+ * sidebar={
11430
+ * <Button id="overview" variant="ghost-neutral">
11431
+ * Overview
11432
+ * </Button>
11433
+ * }
11434
+ * />
11435
+ * );
11436
+ * ```
11437
+ * @param {SidebarContentLayoutProps} props - The props for the SidebarContentLayout component
11438
+ * @returns {ReactElement} SidebarContentLayout component
11439
+ */
11440
+ const SidebarContentLayout = ({ breakpoint = "lg", className, content, contentSpace = true, "data-testid": dataTestId = "sidebar-content-layout", sidebar, style, ref, }) => {
11441
+ return (jsxs("div", { className: cvaSidebarContentLayout({ breakpoint, className }), "data-testid": dataTestId, ref: ref, style: style, children: [jsx("div", { className: cvaSidebarContentLayoutSidebar({ breakpoint }), "data-testid": `${dataTestId}-sidebar`, children: jsx(Sidebar, { breakpoint: breakpoint, children: sidebar }) }), jsx("div", { className: cvaSidebarContentLayoutContent({ contentSpace }), "data-testid": `${dataTestId}-content`, children: content })] }));
11442
+ };
11443
+
11239
11444
  const cvaTabsRoot = cvaMerge([]);
11240
11445
  const cvaTabList = cvaMerge([
11241
11446
  "flex",
@@ -14325,4 +14530,4 @@ const useWindowActivity = ({ onFocus, onBlur, skip = false } = { onBlur: undefin
14325
14530
  */
14326
14531
  setupLibraryTranslations();
14327
14532
 
14328
- export { Alert, Badge, Breadcrumb, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyButton, CopyableText, DEFAULT_SKELETON_PREFERENCE_CARD_PROPS, DetailsList, EmptyState, EmptyValue, ExternalLink, GridAreas, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, KPICardSkeleton, KPISkeleton, LabeledValue, LabeledValueList, List, ListItem, MAX_HASH_LENGTH, MAX_URL_LENGTH, MenuContent, MenuDivider, MenuItem, MenuTree, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, PreferenceCard, PreferenceCardSkeleton, Prompt, ROLE_CARD, SHEET_TRANSITION_DURATION, SHEET_TRANSITION_DURATION_MS, SHEET_TRANSITION_EASING, SectionHeader, SegmentedValueBar, Sheet, Sidebar, SkeletonBlock, SkeletonLabel, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, TrendIndicator, TrendIndicators, ValueBar, ZStack, createGrid, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaContentContainer, cvaContentWrapper, cvaDescriptionCard, cvaIconBackground, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInputContainer, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaPreferenceCard, cvaTitleCard, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, preferenceCardGrid, useBidirectionalScroll, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCursorUrlSync, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHashParamSync, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMenuTree, useMergeRefs, useModifierKey, useOptionalPopoverContext, useOverflowBorder, useOverflowItems, usePersistedState, usePopoverContext, usePrevious, usePrompt, useRandomCSSLengths, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSearchParamSync, useSelfUpdatingRef, useSessionStorage, useSessionStorageReducer, useSheet, useSheetSnap, useStorageKey, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
14533
+ export { Alert, Badge, Breadcrumb, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyButton, CopyableText, DEFAULT_SKELETON_PREFERENCE_CARD_PROPS, DetailsList, EmptyState, EmptyValue, ExternalLink, GridAreas, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, KPICardSkeleton, KPISkeleton, LabeledValue, LabeledValueList, List, ListItem, MAX_HASH_LENGTH, MAX_URL_LENGTH, MenuContent, MenuDivider, MenuItem, MenuTree, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, PreferenceCard, PreferenceCardSkeleton, Prompt, ROLE_CARD, SHEET_TRANSITION_DURATION, SHEET_TRANSITION_DURATION_MS, SHEET_TRANSITION_EASING, SectionHeader, SegmentedValueBar, Sheet, Sidebar, SidebarContentLayout, SkeletonBlock, SkeletonLabel, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, TrendIndicator, TrendIndicators, ValueBar, ZStack, createGrid, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaContentContainer, cvaContentWrapper, cvaDescriptionCard, cvaIconBackground, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInputContainer, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaPreferenceCard, cvaTitleCard, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, preferenceCardGrid, useBidirectionalScroll, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCursorUrlSync, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHashParamSync, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMenuTree, useMergeRefs, useModifierKey, useOptionalPopoverContext, useOverflowBorder, useOverflowItems, usePersistedState, usePopoverContext, usePrevious, usePrompt, useRandomCSSLengths, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSearchParamSync, useSelfUpdatingRef, useSessionStorage, useSessionStorageReducer, useSheet, useSheetSnap, useStorageKey, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-components",
3
- "version": "2.10.3",
3
+ "version": "2.10.4",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "migrations": "./migrations.json",
@@ -14,16 +14,16 @@
14
14
  "@floating-ui/react": "^0.26.25",
15
15
  "string-ts": "^2.0.0",
16
16
  "tailwind-merge": "^2.0.0",
17
- "@trackunit/ui-design-tokens": "1.15.5",
18
- "@trackunit/css-class-variance-utilities": "1.14.16",
19
- "@trackunit/shared-utils": "1.16.19",
20
- "@trackunit/ui-icons": "1.14.15",
17
+ "@trackunit/ui-design-tokens": "1.15.6",
18
+ "@trackunit/css-class-variance-utilities": "1.14.17",
19
+ "@trackunit/shared-utils": "1.16.20",
20
+ "@trackunit/ui-icons": "1.14.16",
21
21
  "es-toolkit": "^1.39.10",
22
22
  "@tanstack/react-virtual": "^3.14.3",
23
23
  "dequal": "^2.0.3",
24
24
  "fflate": "^0.8.2",
25
25
  "zod": "^3.25.76",
26
- "@trackunit/i18n-library-translation": "2.4.12"
26
+ "@trackunit/i18n-library-translation": "2.4.13"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "react": "^19.0.0",
@@ -58,6 +58,12 @@ export interface SidebarProps extends CommonProps, Styleable, Refable<HTMLDivEle
58
58
  * ### When to use
59
59
  * Use Sidebar for secondary in-page navigation (e.g., switching between views within a page). Works well with `Tabs` for page-level navigation.
60
60
  *
61
+ * If you need a persistent nav column beside a content region (a page-level sidebar+content
62
+ * layout, e.g. Site Home, Administration, Asset Home), use `SidebarContentLayout` instead - it
63
+ * composes `Sidebar` internally and keeps its stacking breakpoint in sync with the content grid.
64
+ * Use `Sidebar` directly only for a standalone collapsing nav list with no content pane (e.g.
65
+ * inside a `PageHeader`'s `tabsList`, see `Tabs.stories.tsx`'s `ResponsivenessTwo`/`Three`).
66
+ *
61
67
  * ### When not to use
62
68
  * Do not use Sidebar for primary app navigation (the main menu). For top-level navigation, use the app shell navigation.
63
69
  *
@@ -0,0 +1,70 @@
1
+ import { ReactElement, ReactNode } from "react";
2
+ import { CommonProps } from "../../common/CommonProps";
3
+ import { Refable } from "../../common/Refable";
4
+ import type { Styleable } from "../../common/Styleable";
5
+ import { Breakpoints, SidebarItemProps } from "../Sidebar/Sidebar";
6
+ export interface SidebarContentLayoutProps extends CommonProps, Styleable, Refable<HTMLDivElement> {
7
+ /**
8
+ * The sidebar's own navigation items, rendered through the shared `Sidebar` component. Passed as
9
+ * `Sidebar`'s `children` internally - see `Sidebar`'s own docs for the expected item shape.
10
+ */
11
+ sidebar: Array<ReactElement<SidebarItemProps>> | ReactElement<SidebarItemProps>;
12
+ /**
13
+ * The main content, rendered in a bare, unpadded grid cell. Whatever is passed here owns its own
14
+ * padding - this component never mutates or wraps it in a way that would strip a class it applies
15
+ * to itself (see `contentSpace` for this component's own opt-in inset).
16
+ */
17
+ content: ReactNode;
18
+ /**
19
+ * The breakpoint at which sidebar and content switch from stacked to side-by-side. Passed straight
20
+ * through to `Sidebar`'s own `breakpoint` prop, so the grid's own switch and `Sidebar`'s internal
21
+ * tab-bar/vertical-list switch are driven by the identical condition and can never disagree.
22
+ *
23
+ * @default "lg"
24
+ */
25
+ breakpoint?: Breakpoints;
26
+ /**
27
+ * Whether the content cell applies its own `p-content-space` inset. Set to `false` for routes that
28
+ * must render flush against this cell's true edges (e.g. a full-page extension iframe).
29
+ *
30
+ * @default true
31
+ */
32
+ contentSpace?: boolean;
33
+ }
34
+ /**
35
+ * SidebarContentLayout is the shared page shell for any screen built from a persistent navigation
36
+ * sidebar next to a main content area - the pattern behind Site Home, Administration, and Asset
37
+ * Home. It keeps that pairing consistent everywhere it's used: the same breakpoint decides both
38
+ * when the sidebar collapses to a tab bar and when content drops below it, so the two can never
39
+ * disagree, and every page gets identical spacing without reimplementing it by hand.
40
+ *
41
+ * The content slot renders whatever it's given untouched - a routed `Outlet`, a form, a table - and
42
+ * never imposes padding on it from the outside (see `contentSpace` to opt out of this component's
43
+ * own default inset, e.g. for a full-page extension iframe that needs to sit flush to the edges).
44
+ *
45
+ * ### When to use
46
+ * Use as the layout directly inside a `HostPage` for any page that needs a persistent navigation
47
+ * sidebar alongside routed/tabbed content (e.g. Site Home, Administration, Asset Home).
48
+ *
49
+ * ### When not to use
50
+ * Do not use for pages without a persistent sidebar - use `HostPage` directly instead.
51
+ *
52
+ * @example SidebarContentLayout with navigation items and routed content
53
+ * ```tsx
54
+ * import { SidebarContentLayout, SidebarItemProps } from "@trackunit/react-components";
55
+ *
56
+ * const SitePage = () => (
57
+ * <SidebarContentLayout
58
+ * content={<Outlet />}
59
+ * sidebar={
60
+ * <Button id="overview" variant="ghost-neutral">
61
+ * Overview
62
+ * </Button>
63
+ * }
64
+ * />
65
+ * );
66
+ * ```
67
+ * @param {SidebarContentLayoutProps} props - The props for the SidebarContentLayout component
68
+ * @returns {ReactElement} SidebarContentLayout component
69
+ */
70
+ export declare const SidebarContentLayout: ({ breakpoint, className, content, contentSpace, "data-testid": dataTestId, sidebar, style, ref, }: SidebarContentLayoutProps) => ReactElement;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * The root grid. `min-h-0 flex-1` opts this into an ancestor `HostPage`'s height-cascading flex-col
3
+ * fill contract (see `HostPage.tsx`), so it gets a real, bounded height instead of an auto-height
4
+ * block. No `gap-*` utility is applied here, ever: the single visible gap between sidebar and
5
+ * content at the side-by-side breakpoint is contributed entirely by the content cell's own
6
+ * `p-content-space` left padding (see `cvaSidebarContentLayoutContent`) - a grid gap on top of that
7
+ * would silently double it.
8
+ *
9
+ * Below the breakpoint (stacked, single column), `grid-rows-[min-content_minmax(0px,1fr)]` pins the
10
+ * sidebar's row to its own content height and gives the content row every pixel of leftover space.
11
+ * Without an explicit row template, both rows default to `auto` sizing, and this grid's own bounded
12
+ * height (from the `flex-1` fill contract above) is almost always taller than the two rows' combined
13
+ * natural content height - `align-content`'s default `normal` computes to `stretch` for a grid
14
+ * container, so that leftover space gets distributed *proportionally across every `auto` row*, not
15
+ * just content's. That silently inflates the sidebar's row (and thus its box) well past its own
16
+ * content's height, leaving a blank gap below the nav items before content starts - a stacked-only
17
+ * sibling of the `minmax(min-content,1fr)` grid-blowout class of bug already fixed once for Asset
18
+ * Home's own internal grid (GLU-1562/Phase 17). `{breakpoint}:grid-rows-none` resets this back to a
19
+ * single implicit `auto` row at/above the breakpoint, where sidebar and content share one grid row
20
+ * (as two columns) and must both keep stretching to the full available height per `{bp}:h-full`.
21
+ *
22
+ * Each breakpoint's arbitrary-value class is spelled out in full (rather than built via string
23
+ * interpolation) because Tailwind's build-time scanner extracts candidate class names via a static
24
+ * regex over each source file's raw text, not by evaluating JavaScript - an interpolated class name
25
+ * would never appear as a complete literal string anywhere in this file and so would silently fail
26
+ * to generate any CSS at all.
27
+ */
28
+ export declare const cvaSidebarContentLayout: (props?: ({
29
+ breakpoint?: "sm" | "xs" | "md" | "lg" | "xl" | "2xl" | "3xl" | null | undefined;
30
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
31
+ /**
32
+ * The sidebar's own grid cell. `pt-4 pl-4` give it a fixed top/left inset (matching
33
+ * `p-content-space`'s own `spacing-4`) now that there's no ambient ancestor padding left to inherit
34
+ * one from.
35
+ *
36
+ * The right edge is a genuinely different edge depending on the breakpoint, so it's handled
37
+ * accordingly rather than uniformly:
38
+ * - Below the breakpoint, the grid is a single column (see `cvaSidebarContentLayout`), so the
39
+ * sidebar spans the grid's full width and this edge is a true page edge, exactly like the left
40
+ * edge - it gets the same `pr-4` inset.
41
+ * - At/above the breakpoint, this edge instead borders the content column, and *most* of it is
42
+ * owned entirely by content's own `p-content-space` left padding (see module doc comment above and
43
+ * `cvaSidebarContentLayoutContent`) - stacking this cell's own `pr-4` on top of that would silently
44
+ * double the gap. `{breakpoint}:pr-2` cancels *most*, but not all, of it there - see below for the
45
+ * deliberate remainder.
46
+ *
47
+ * `h-full min-h-0 overflow-y-auto` (applied only from the breakpoint upward, matching `Sidebar`'s
48
+ * own vertical-list mode) give the sidebar its own bounded height and local scroll: `Sidebar`'s
49
+ * vertical nav list is unclipped by default, so a long list could otherwise be taller than the
50
+ * grid row's available height, silently growing the row (the classic CSS grid "blowout") instead of
51
+ * scrolling locally.
52
+ *
53
+ * `[scrollbar-gutter:stable]` rides along with that same `overflow-y-auto`, reserving genuine,
54
+ * platform-correct scrollbar space (0 on overlay-scrollbar platforms like macOS, the native width
55
+ * elsewhere) on the same edge, so a long enough nav list's scrollbar never overlaps the items - and
56
+ * so the sidebar's rendered width stays consistent whether or not a *particular* page's item list
57
+ * happens to be long enough to overflow. Supported by every evergreen browser (Chrome/Edge 94+,
58
+ * Firefox 97+, Safari 18.2+); a no-op elsewhere, i.e. today's pre-fix behavior, so no regression risk.
59
+ *
60
+ * That reservation alone butts the items directly against the scrollbar track with zero breathing
61
+ * room, though - `scrollbar-gutter` only guarantees no *overlap*, it isn't a visual gap. `{bp}:pr-2`
62
+ * is the deliberate small remainder of the otherwise-cancelled `pr-4` that supplies that breathing
63
+ * room, sized independently of both the scrollbar's own width (which the browser already reserves via
64
+ * `scrollbar-gutter`) and of the sidebar/content gap above (which is `p-content-space`'s job, not
65
+ * this padding's) - it exists purely so the sidebar's own content doesn't look glued to the sidebar's
66
+ * own scrollbar. Keeping it a small fraction of the full `pr-4` (rather than restoring all of it, or
67
+ * clawing space back from the reserved gutter) avoids meaningfully inflating the sidebar/content gap
68
+ * `p-content-space` already owns - it is deliberately *not* zero, but not enough to look like a second
69
+ * gap either.
70
+ */
71
+ export declare const cvaSidebarContentLayoutSidebar: (props?: ({
72
+ breakpoint?: "sm" | "xs" | "md" | "lg" | "xl" | "2xl" | "3xl" | null | undefined;
73
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
74
+ /**
75
+ * The content's own grid cell: `relative` (a positioning context for a future side panel/drawer to
76
+ * anchor into), a definite height (`h-full`, chained from the root grid's own fill contract) with
77
+ * its own local `overflow-y-auto` (this cell's content may be a third-party iframe, which must
78
+ * contain its own overflow rather than bubbling it up).
79
+ *
80
+ * `p-content-space` is applied here by default (`contentSpace: true`): the padding decision is
81
+ * exposed on the component that owns the content's box, not silently forced by some invisible
82
+ * ancestor. Callers rendering a route that must sit flush (e.g. a full-page extension iframe) opt
83
+ * out with `contentSpace={false}`.
84
+ */
85
+ export declare const cvaSidebarContentLayoutContent: (props?: ({
86
+ contentSpace?: boolean | null | undefined;
87
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
package/src/index.d.ts CHANGED
@@ -89,6 +89,7 @@ export { useSheet } from "./components/Sheet/useSheet";
89
89
  export { useSheetSnap } from "./components/Sheet/useSheetSnap";
90
90
  export * from "./components/Sidebar/Sidebar";
91
91
  export * from "./components/Sidebar/useOverflowItems";
92
+ export * from "./components/SidebarContentLayout/SidebarContentLayout";
92
93
  export { useRandomCSSLengths } from "./components/Skeleton/Skeleton.helpers";
93
94
  export type { CSSLength, CSSLengthUnit } from "./components/Skeleton/Skeleton.helpers";
94
95
  export { SkeletonBlock } from "./components/Skeleton/SkeletonBlock/SkeletonBlock";