@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 +206 -0
- package/index.esm.js +206 -1
- package/package.json +6 -6
- package/src/components/Sidebar/Sidebar.d.ts +6 -0
- package/src/components/SidebarContentLayout/SidebarContentLayout.d.ts +70 -0
- package/src/components/SidebarContentLayout/SidebarContentLayout.variants.d.ts +87 -0
- package/src/index.d.ts +1 -0
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
|
+
"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.
|
|
18
|
-
"@trackunit/css-class-variance-utilities": "1.14.
|
|
19
|
-
"@trackunit/shared-utils": "1.16.
|
|
20
|
-
"@trackunit/ui-icons": "1.14.
|
|
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.
|
|
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";
|