mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-24 20:09:02 +00:00
the docs shell names the row that absorbs the leftover, and the footer reflows on its column
This commit is contained in:
@@ -197,7 +197,13 @@ export function PageTOCPopoverContent(props: ComponentProps<'div'>) {
|
||||
<CollapsibleContent
|
||||
data-toc-popover-content=""
|
||||
{...props}
|
||||
className={cn('flex flex-col px-4 max-h-[50vh] md:px-6', props.className)}
|
||||
className={cn(
|
||||
// Rows may shrink below their content so the scroller inside takes the
|
||||
// overflow; the max-h is what makes that reachable. Mirrors
|
||||
// layouts/docs/page/slots/toc.tsx.
|
||||
'grid grid-cols-[minmax(0,1fr)] auto-rows-[minmax(0,auto)] px-4 max-h-[50vh] md:px-6',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
<div>{props.children}</div>
|
||||
</CollapsibleContent>
|
||||
@@ -254,8 +260,10 @@ export function PageFooter({ items, children, className, ...props }: FooterProps
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
'@container grid gap-4',
|
||||
previous && next ? 'grid-cols-2' : 'grid-cols-1',
|
||||
// See layouts/docs/page/slots/footer.tsx: auto-fit measures the
|
||||
// column, so the container query, the JS column count and the
|
||||
// card's col-span override all collapse into this one track.
|
||||
'grid gap-4 grid-cols-[repeat(auto-fit,minmax(min(100%,16rem),1fr))]',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -276,7 +284,9 @@ function FooterItem({ item, index }: { item: Item; index: 0 | 1 }) {
|
||||
<Link
|
||||
href={item.url}
|
||||
className={cn(
|
||||
'flex flex-col gap-2 rounded-lg border p-4 text-sm transition-colors hover:bg-fd-accent/80 hover:text-fd-accent-foreground @max-lg:col-span-full',
|
||||
// The column is stated so the track cannot be floored at its content's
|
||||
// min-content width. See layouts/docs/page/slots/footer.tsx.
|
||||
'grid grid-cols-[minmax(0,1fr)] gap-2 rounded-lg border p-4 text-sm transition-colors hover:bg-fd-accent/80 hover:text-fd-accent-foreground',
|
||||
index === 1 && 'text-end',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -13,6 +13,22 @@ export function Container(props: ComponentProps<'article'>) {
|
||||
data-full={full}
|
||||
{...props}
|
||||
className={cn(
|
||||
// STILL FLEX, deliberately — the rest of this layout is grid now.
|
||||
//
|
||||
// The shell gives `main` a 1fr row against a min-height of the viewport,
|
||||
// so on a short page this article is TALLER than its content, and the
|
||||
// thing that decides where the leftover goes is `flex-1` on the prose
|
||||
// body (layouts/docs/page/index.tsx) — that is what holds the footer at
|
||||
// the bottom instead of letting it ride up under the last paragraph.
|
||||
//
|
||||
// Under grid that `flex-1` is inert, and the row that should be 1fr
|
||||
// cannot be named from here: the children are whatever the CONSUMER
|
||||
// composes into DocsPage (breadcrumb, title, description, body, footer,
|
||||
// any of them absent), so there is no stable index to put the 1fr on.
|
||||
// Converting it means moving the growth contract into the public slot
|
||||
// API, which is a change to what consumers pass, not a change of layout.
|
||||
//
|
||||
// Left as it is on purpose. Do not swap the class alone.
|
||||
'flex flex-col w-full max-w-[900px] mx-auto [grid-area:main] px-4 py-6 gap-4 md:px-6 md:pt-8 xl:px-8 xl:pt-14',
|
||||
full && 'max-w-[1168px]',
|
||||
props.className,
|
||||
|
||||
@@ -41,8 +41,18 @@ export function Footer({ items, children, className, ...props }: FooterProps) {
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
'@container grid gap-4',
|
||||
previous && next ? 'grid-cols-2' : 'grid-cols-1',
|
||||
// One track statement replaces three rules that all said the same
|
||||
// thing in different places: a container query on this element, a
|
||||
// column count picked in JS from how many links exist, and a
|
||||
// `@max-lg:col-span-full` on the card to undo the count again when
|
||||
// the container got narrow.
|
||||
//
|
||||
// auto-fit measures the COLUMN, so it needs none of them. Two links
|
||||
// sit side by side wherever two 16rem columns fit and stack where
|
||||
// they do not; one link collapses the empty track and takes the full
|
||||
// width on its own. `min(100%,16rem)` keeps the floor from
|
||||
// overflowing a container narrower than the floor itself.
|
||||
'grid gap-4 grid-cols-[repeat(auto-fit,minmax(min(100%,16rem),1fr))]',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -63,7 +73,15 @@ function FooterItem({ item, index }: { item: Item; index: 0 | 1 }) {
|
||||
<Link
|
||||
href={item.url}
|
||||
className={cn(
|
||||
'flex flex-col gap-2 rounded-lg border p-4 text-sm transition-colors hover:bg-fd-accent/80 hover:text-fd-accent-foreground @max-lg:col-span-full',
|
||||
// The column is stated rather than left implicit, so the track cannot be
|
||||
// floored at the min-content width of whatever a caller puts in here.
|
||||
//
|
||||
// It is NOT what saves the truncating description below — measured, a
|
||||
// bare `grid` ellipsises that line just as well, because `truncate`
|
||||
// carries `overflow: hidden` and a scroll container contributes zero
|
||||
// min-content width. The guard is for the siblings that have no
|
||||
// overflow of their own.
|
||||
'grid grid-cols-[minmax(0,1fr)] gap-2 rounded-lg border p-4 text-sm transition-colors hover:bg-fd-accent/80 hover:text-fd-accent-foreground',
|
||||
index === 1 && 'text-end',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -61,7 +61,14 @@ export function TOC({ container, header, footer, style = 'normal', list }: TOCPr
|
||||
id="nd-toc"
|
||||
{...container}
|
||||
className={cn(
|
||||
'sticky top-(--fd-docs-row-1) h-[calc(var(--fd-docs-height)-var(--fd-docs-row-1))] flex flex-col [grid-area:toc] w-(--fd-toc-width) pt-12 pe-4 pb-2 xl:layout:[--fd-toc-width:268px] max-xl:hidden',
|
||||
// The rail has a definite height, so its rows have to be allowed to be
|
||||
// SHORTER than their content or the scroller inside never scrolls —
|
||||
// `auto-rows-[minmax(0,auto)]` is that permission, stated once on the
|
||||
// container. As a flex column the same thing happened implicitly, via
|
||||
// every child's default flex-shrink, which is a rule you have to know
|
||||
// rather than one you can read. The header and footer slots are
|
||||
// optional, so the rows are auto-placed rather than a fixed template.
|
||||
'sticky top-(--fd-docs-row-1) h-[calc(var(--fd-docs-height)-var(--fd-docs-row-1))] grid grid-cols-[minmax(0,1fr)] auto-rows-[minmax(0,auto)] [grid-area:toc] w-(--fd-toc-width) pt-12 pe-4 pb-2 xl:layout:[--fd-toc-width:268px] max-xl:hidden',
|
||||
container?.className,
|
||||
)}
|
||||
>
|
||||
@@ -304,7 +311,12 @@ function ProgressCircle({
|
||||
function PageTOCPopoverContent(props: ComponentProps<'div'>) {
|
||||
return (
|
||||
<CollapsibleContent data-toc-popover-content="" {...props}>
|
||||
<div className="flex flex-col px-4 max-h-[50vh] md:px-6">{props.children}</div>
|
||||
{/* Bounded by max-h rather than a definite height, but the row model is
|
||||
the same as the rail's: rows may shrink below their content so the
|
||||
scroller inside takes the overflow. */}
|
||||
<div className="grid grid-cols-[minmax(0,1fr)] auto-rows-[minmax(0,auto)] px-4 max-h-[50vh] md:px-6">
|
||||
{props.children}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ export function Sidebar({ footer, banner, collapsible = true, components, ...res
|
||||
<Base.SidebarViewport>
|
||||
{/* Marked so a sidebar filter can stand in for the tree while typing,
|
||||
instead of appearing above it and pushing it off screen. */}
|
||||
<div className="flex flex-col gap-0.5" data-sidebar-tree>
|
||||
<div className="grid grid-cols-[minmax(0,1fr)] gap-0.5" data-sidebar-tree>
|
||||
{menuItems
|
||||
.filter((v) => v.type !== 'icon')
|
||||
.map((item, i, list) => (
|
||||
@@ -83,7 +83,7 @@ export function Sidebar({ footer, banner, collapsible = true, components, ...res
|
||||
{/* gap-2, not the drawer's gap-3: this rail has a fixed height budget
|
||||
and the drawer does not, so a point spent here is a point the tree
|
||||
below never gets. */}
|
||||
<div className="flex flex-col gap-2 p-4 pb-2">
|
||||
<div className="grid grid-cols-[minmax(0,1fr)] gap-2 p-4 pb-2">
|
||||
<div className="flex">
|
||||
{slots.navTitle && (
|
||||
<slots.navTitle className="inline-flex text-[0.9375rem] items-center gap-2.5 font-medium me-auto" />
|
||||
@@ -114,7 +114,7 @@ export function Sidebar({ footer, banner, collapsible = true, components, ...res
|
||||
</div>
|
||||
{viewport}
|
||||
{(slots.languageSelect || iconLinks.length > 0 || slots.themeSwitch || footer) && (
|
||||
<div className="flex flex-col p-4 pt-2">
|
||||
<div className="grid grid-cols-[minmax(0,1fr)] p-4 pt-2">
|
||||
{slots.languageSelect && (
|
||||
<slots.languageSelect.root
|
||||
variant="secondary"
|
||||
@@ -156,7 +156,7 @@ export function Sidebar({ footer, banner, collapsible = true, components, ...res
|
||||
)}
|
||||
</SidebarContent>
|
||||
<SidebarDrawer>
|
||||
<div className="flex flex-col gap-3 p-4 pb-2">
|
||||
<div className="grid grid-cols-[minmax(0,1fr)] gap-3 p-4 pb-2">
|
||||
<div className="flex text-fd-muted-foreground items-center gap-1.5">
|
||||
<div className="flex flex-1">
|
||||
{iconLinks.map((item, i) => (
|
||||
@@ -199,7 +199,9 @@ export function Sidebar({ footer, banner, collapsible = true, components, ...res
|
||||
{banner}
|
||||
</div>
|
||||
{viewport}
|
||||
<div className="flex flex-col border-t p-4 pt-2 empty:hidden">{footer}</div>
|
||||
<div className="grid grid-cols-[minmax(0,1fr)] border-t p-4 pt-2 empty:hidden">
|
||||
{footer}
|
||||
</div>
|
||||
</SidebarDrawer>
|
||||
</>
|
||||
);
|
||||
@@ -239,7 +241,36 @@ function SidebarContent({ ref: refProp, className, children, ...props }: Compone
|
||||
// so the nav fills it. `items-end` used to push the nav to the
|
||||
// inner edge of a column that grew with the viewport, which is
|
||||
// what left a widening empty strip inside the rail's own card.
|
||||
'absolute flex flex-col w-full inset-s-0 inset-y-0 bg-fd-card text-sm border-e duration-250 *:w-(--fd-sidebar-width)',
|
||||
//
|
||||
// THREE rows: the banner, the tree, the footer. The rail has a
|
||||
// definite height (inset-y-0), and the tree is the one region
|
||||
// that absorbs whatever the other two leave — so it is the only
|
||||
// row that is 1fr, and minmax(0,…) lets it be shorter than its
|
||||
// own content, which is what makes its scroller scroll.
|
||||
//
|
||||
// As a flex column this was stated on the CHILD instead:
|
||||
// SidebarViewport carries `min-h-0 flex-1`. That reads as a
|
||||
// property of the viewport when it is really a fact about this
|
||||
// rail, and it is invisible from here — the row model says it
|
||||
// where the rows are. The `flex-1` is inert under grid and stays
|
||||
// only because SidebarViewport is shared with the notebook and
|
||||
// flux layouts, which are still flex columns; `min-h-0` is still
|
||||
// load-bearing there and harmless here.
|
||||
//
|
||||
// The footer row is conditional (it renders only when there is a
|
||||
// language select, icon links, a theme switch or a footer). An
|
||||
// absent third child leaves row 3 at `auto`, which is zero — the
|
||||
// first two rows are unaffected either way.
|
||||
//
|
||||
// The rows have to be NAMED. Left implicit, all three are `auto`
|
||||
// and grid's default `align-content: stretch` splits the rail's
|
||||
// leftover height equally between them — measured at 600px with
|
||||
// a three-item tree, 193/213/193 instead of 44/512/44, so the
|
||||
// banner and the footer each stand four times their own content.
|
||||
// It only misbehaves when the tree is SHORT: at 40 items the two
|
||||
// spellings are identical, which is how it would have passed a
|
||||
// casual look and shipped.
|
||||
'absolute grid grid-cols-[minmax(0,1fr)] grid-rows-[auto_minmax(0,1fr)_auto] w-full inset-s-0 inset-y-0 bg-fd-card text-sm border-e duration-250 *:w-(--fd-sidebar-width)',
|
||||
collapsed && [
|
||||
'inset-y-2 rounded-xl transition-transform border w-(--fd-sidebar-width)',
|
||||
hovered
|
||||
@@ -297,7 +328,11 @@ function SidebarDrawer({
|
||||
<Base.SidebarDrawerOverlay className="fixed z-40 inset-0 backdrop-blur-xs data-[state=open]:animate-fd-fade-in data-[state=closed]:animate-fd-fade-out" />
|
||||
<Base.SidebarDrawerContent
|
||||
className={cn(
|
||||
'fixed text-[0.9375rem] flex flex-col shadow-lg border-s inset-e-0 inset-y-0 w-[85%] max-w-[380px] z-40 bg-fd-background data-[state=open]:animate-fd-sidebar-in data-[state=closed]:animate-fd-sidebar-out',
|
||||
// Same three rows as the desktop rail, and for the same reason: a
|
||||
// definite height (inset-y-0) with one region — the tree — absorbing
|
||||
// the leftover. Here all three children always render, so the row
|
||||
// model is exact.
|
||||
'fixed text-[0.9375rem] grid grid-cols-[minmax(0,1fr)] grid-rows-[auto_minmax(0,1fr)_auto] shadow-lg border-s inset-e-0 inset-y-0 w-[85%] max-w-[380px] z-40 bg-fd-background data-[state=open]:animate-fd-sidebar-in data-[state=closed]:animate-fd-sidebar-out',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -420,7 +455,7 @@ function SidebarFolderContent({
|
||||
<Base.SidebarFolderContent
|
||||
className={(state) =>
|
||||
cn(
|
||||
'relative flex flex-col gap-0.5 pt-0.5',
|
||||
'relative grid grid-cols-[minmax(0,1fr)] gap-0.5 pt-0.5',
|
||||
depth === 1 &&
|
||||
"before:content-[''] before:absolute before:w-px before:inset-y-1 before:bg-fd-border before:inset-s-2.5",
|
||||
typeof className === 'function' ? className(state) : className,
|
||||
@@ -482,7 +517,7 @@ function SidebarTabsDropdown({
|
||||
<ChevronsUpDown className="shrink-0 ms-auto size-4 text-fd-muted-foreground" />
|
||||
</PopoverTrigger>
|
||||
)}
|
||||
<PopoverContent className="flex flex-col gap-1 w-(--anchor-width) p-1 fd-scroll-container">
|
||||
<PopoverContent className="grid grid-cols-[minmax(0,1fr)] gap-1 w-(--anchor-width) p-1 fd-scroll-container">
|
||||
{tabs.map((item) => {
|
||||
const isActive = selected && item.url === selected.url;
|
||||
if (!isActive && item.unlisted) return;
|
||||
|
||||
@@ -197,7 +197,13 @@ export function PageTOCPopoverContent(props: ComponentProps<'div'>) {
|
||||
<CollapsibleContent
|
||||
data-toc-popover-content=""
|
||||
{...props}
|
||||
className={cn('flex flex-col px-4 max-h-[50vh] md:px-6', props.className)}
|
||||
className={cn(
|
||||
// Rows may shrink below their content so the scroller inside takes the
|
||||
// overflow; the max-h is what makes that reachable. Mirrors
|
||||
// layouts/docs/page/slots/toc.tsx.
|
||||
'grid grid-cols-[minmax(0,1fr)] auto-rows-[minmax(0,auto)] px-4 max-h-[50vh] md:px-6',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</CollapsibleContent>
|
||||
@@ -253,8 +259,10 @@ export function PageFooter({ items, children, className, ...props }: FooterProps
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
'@container grid gap-4',
|
||||
previous && next ? 'grid-cols-2' : 'grid-cols-1',
|
||||
// See layouts/docs/page/slots/footer.tsx: auto-fit measures the
|
||||
// column, so the container query, the JS column count and the
|
||||
// card's col-span override all collapse into this one track.
|
||||
'grid gap-4 grid-cols-[repeat(auto-fit,minmax(min(100%,16rem),1fr))]',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -275,7 +283,9 @@ function FooterItem({ item, index }: { item: Item; index: 0 | 1 }) {
|
||||
<Link
|
||||
href={item.url}
|
||||
className={cn(
|
||||
'flex flex-col gap-2 rounded-lg border p-4 text-sm transition-colors hover:bg-fd-accent/80 hover:text-fd-accent-foreground @max-lg:col-span-full',
|
||||
// The column is stated so the track cannot be floored at its content's
|
||||
// min-content width. See layouts/docs/page/slots/footer.tsx.
|
||||
'grid grid-cols-[minmax(0,1fr)] gap-2 rounded-lg border p-4 text-sm transition-colors hover:bg-fd-accent/80 hover:text-fd-accent-foreground',
|
||||
index === 1 && 'text-end',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -13,6 +13,22 @@ export function Container(props: ComponentProps<'article'>) {
|
||||
data-full={full}
|
||||
{...props}
|
||||
className={cn(
|
||||
// STILL FLEX, deliberately — the rest of this layout is grid now.
|
||||
//
|
||||
// The shell gives `main` a 1fr row against a min-height of the viewport,
|
||||
// so on a short page this article is TALLER than its content, and the
|
||||
// thing that decides where the leftover goes is `flex-1` on the prose
|
||||
// body (layouts/docs/page/index.tsx) — that is what holds the footer at
|
||||
// the bottom instead of letting it ride up under the last paragraph.
|
||||
//
|
||||
// Under grid that `flex-1` is inert, and the row that should be 1fr
|
||||
// cannot be named from here: the children are whatever the CONSUMER
|
||||
// composes into DocsPage (breadcrumb, title, description, body, footer,
|
||||
// any of them absent), so there is no stable index to put the 1fr on.
|
||||
// Converting it means moving the growth contract into the public slot
|
||||
// API, which is a change to what consumers pass, not a change of layout.
|
||||
//
|
||||
// Left as it is on purpose. Do not swap the class alone.
|
||||
'flex flex-col w-full max-w-[900px] mx-auto [grid-area:main] px-4 py-6 gap-4 md:px-6 md:pt-8 xl:px-8 xl:pt-14',
|
||||
full && 'max-w-[1168px]',
|
||||
props.className,
|
||||
|
||||
@@ -41,8 +41,18 @@ export function Footer({ items, children, className, ...props }: FooterProps) {
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
'@container grid gap-4',
|
||||
previous && next ? 'grid-cols-2' : 'grid-cols-1',
|
||||
// One track statement replaces three rules that all said the same
|
||||
// thing in different places: a container query on this element, a
|
||||
// column count picked in JS from how many links exist, and a
|
||||
// `@max-lg:col-span-full` on the card to undo the count again when
|
||||
// the container got narrow.
|
||||
//
|
||||
// auto-fit measures the COLUMN, so it needs none of them. Two links
|
||||
// sit side by side wherever two 16rem columns fit and stack where
|
||||
// they do not; one link collapses the empty track and takes the full
|
||||
// width on its own. `min(100%,16rem)` keeps the floor from
|
||||
// overflowing a container narrower than the floor itself.
|
||||
'grid gap-4 grid-cols-[repeat(auto-fit,minmax(min(100%,16rem),1fr))]',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -63,7 +73,15 @@ function FooterItem({ item, index }: { item: Item; index: 0 | 1 }) {
|
||||
<Link
|
||||
href={item.url}
|
||||
className={cn(
|
||||
'flex flex-col gap-2 rounded-lg border p-4 text-sm transition-colors hover:bg-fd-accent/80 hover:text-fd-accent-foreground @max-lg:col-span-full',
|
||||
// The column is stated rather than left implicit, so the track cannot be
|
||||
// floored at the min-content width of whatever a caller puts in here.
|
||||
//
|
||||
// It is NOT what saves the truncating description below — measured, a
|
||||
// bare `grid` ellipsises that line just as well, because `truncate`
|
||||
// carries `overflow: hidden` and a scroll container contributes zero
|
||||
// min-content width. The guard is for the siblings that have no
|
||||
// overflow of their own.
|
||||
'grid grid-cols-[minmax(0,1fr)] gap-2 rounded-lg border p-4 text-sm transition-colors hover:bg-fd-accent/80 hover:text-fd-accent-foreground',
|
||||
index === 1 && 'text-end',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -61,7 +61,14 @@ export function TOC({ container, header, footer, style = 'normal', list }: TOCPr
|
||||
id="nd-toc"
|
||||
{...container}
|
||||
className={cn(
|
||||
'sticky top-(--fd-docs-row-1) h-[calc(var(--fd-docs-height)-var(--fd-docs-row-1))] flex flex-col [grid-area:toc] w-(--fd-toc-width) pt-12 pe-4 pb-2 xl:layout:[--fd-toc-width:268px] max-xl:hidden',
|
||||
// The rail has a definite height, so its rows have to be allowed to be
|
||||
// SHORTER than their content or the scroller inside never scrolls —
|
||||
// `auto-rows-[minmax(0,auto)]` is that permission, stated once on the
|
||||
// container. As a flex column the same thing happened implicitly, via
|
||||
// every child's default flex-shrink, which is a rule you have to know
|
||||
// rather than one you can read. The header and footer slots are
|
||||
// optional, so the rows are auto-placed rather than a fixed template.
|
||||
'sticky top-(--fd-docs-row-1) h-[calc(var(--fd-docs-height)-var(--fd-docs-row-1))] grid grid-cols-[minmax(0,1fr)] auto-rows-[minmax(0,auto)] [grid-area:toc] w-(--fd-toc-width) pt-12 pe-4 pb-2 xl:layout:[--fd-toc-width:268px] max-xl:hidden',
|
||||
container?.className,
|
||||
)}
|
||||
>
|
||||
@@ -304,7 +311,12 @@ function ProgressCircle({
|
||||
function PageTOCPopoverContent(props: ComponentProps<'div'>) {
|
||||
return (
|
||||
<CollapsibleContent data-toc-popover-content="" {...props}>
|
||||
<div className="flex flex-col px-4 max-h-[50vh] md:px-6">{props.children}</div>
|
||||
{/* Bounded by max-h rather than a definite height, but the row model is
|
||||
the same as the rail's: rows may shrink below their content so the
|
||||
scroller inside takes the overflow. */}
|
||||
<div className="grid grid-cols-[minmax(0,1fr)] auto-rows-[minmax(0,auto)] px-4 max-h-[50vh] md:px-6">
|
||||
{props.children}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ export function Sidebar({ footer, banner, collapsible = true, components, ...res
|
||||
return (
|
||||
<>
|
||||
<SidebarContent {...rest}>
|
||||
<div className="flex flex-col gap-3 p-4 pb-2">
|
||||
<div className="grid grid-cols-[minmax(0,1fr)] gap-3 p-4 pb-2">
|
||||
<div className="flex">
|
||||
{slots.navTitle && (
|
||||
<slots.navTitle className="inline-flex text-[0.9375rem] items-center gap-2.5 font-medium me-auto" />
|
||||
@@ -103,7 +103,7 @@ export function Sidebar({ footer, banner, collapsible = true, components, ...res
|
||||
</div>
|
||||
{viewport}
|
||||
{(slots.languageSelect || iconLinks.length > 0 || slots.themeSwitch || footer) && (
|
||||
<div className="flex flex-col p-4 pt-2">
|
||||
<div className="grid grid-cols-[minmax(0,1fr)] p-4 pt-2">
|
||||
{slots.languageSelect && (
|
||||
<slots.languageSelect.root
|
||||
variant="secondary"
|
||||
@@ -134,7 +134,7 @@ export function Sidebar({ footer, banner, collapsible = true, components, ...res
|
||||
)}
|
||||
</SidebarContent>
|
||||
<SidebarDrawer>
|
||||
<div className="flex flex-col gap-3 p-4 pb-2">
|
||||
<div className="grid grid-cols-[minmax(0,1fr)] gap-3 p-4 pb-2">
|
||||
<div className="flex text-fd-muted-foreground items-center gap-1.5">
|
||||
<div className="flex flex-1">
|
||||
{iconLinks.map((item, i) => (
|
||||
@@ -177,7 +177,9 @@ export function Sidebar({ footer, banner, collapsible = true, components, ...res
|
||||
{banner}
|
||||
</div>
|
||||
{viewport}
|
||||
<div className="flex flex-col border-t p-4 pt-2 empty:hidden">{footer}</div>
|
||||
<div className="grid grid-cols-[minmax(0,1fr)] border-t p-4 pt-2 empty:hidden">
|
||||
{footer}
|
||||
</div>
|
||||
</SidebarDrawer>
|
||||
</>
|
||||
);
|
||||
@@ -217,7 +219,36 @@ function SidebarContent({ ref: refProp, className, children, ...props }: Compone
|
||||
// so the nav fills it. `items-end` used to push the nav to the
|
||||
// inner edge of a column that grew with the viewport, which is
|
||||
// what left a widening empty strip inside the rail's own card.
|
||||
'absolute flex flex-col w-full inset-s-0 inset-y-0 bg-fd-card text-sm border-e duration-250 *:w-(--fd-sidebar-width)',
|
||||
//
|
||||
// THREE rows: the banner, the tree, the footer. The rail has a
|
||||
// definite height (inset-y-0), and the tree is the one region
|
||||
// that absorbs whatever the other two leave — so it is the only
|
||||
// row that is 1fr, and minmax(0,…) lets it be shorter than its
|
||||
// own content, which is what makes its scroller scroll.
|
||||
//
|
||||
// As a flex column this was stated on the CHILD instead:
|
||||
// SidebarViewport carries `min-h-0 flex-1`. That reads as a
|
||||
// property of the viewport when it is really a fact about this
|
||||
// rail, and it is invisible from here — the row model says it
|
||||
// where the rows are. The `flex-1` is inert under grid and stays
|
||||
// only because SidebarViewport is shared with the notebook and
|
||||
// flux layouts, which are still flex columns; `min-h-0` is still
|
||||
// load-bearing there and harmless here.
|
||||
//
|
||||
// The footer row is conditional (it renders only when there is a
|
||||
// language select, icon links, a theme switch or a footer). An
|
||||
// absent third child leaves row 3 at `auto`, which is zero — the
|
||||
// first two rows are unaffected either way.
|
||||
//
|
||||
// The rows have to be NAMED. Left implicit, all three are `auto`
|
||||
// and grid's default `align-content: stretch` splits the rail's
|
||||
// leftover height equally between them — measured at 600px with
|
||||
// a three-item tree, 193/213/193 instead of 44/512/44, so the
|
||||
// banner and the footer each stand four times their own content.
|
||||
// It only misbehaves when the tree is SHORT: at 40 items the two
|
||||
// spellings are identical, which is how it would have passed a
|
||||
// casual look and shipped.
|
||||
'absolute grid grid-cols-[minmax(0,1fr)] grid-rows-[auto_minmax(0,1fr)_auto] w-full inset-s-0 inset-y-0 bg-fd-card text-sm border-e duration-250 *:w-(--fd-sidebar-width)',
|
||||
collapsed && [
|
||||
'inset-y-2 rounded-xl transition-transform border w-(--fd-sidebar-width)',
|
||||
hovered
|
||||
@@ -271,7 +302,11 @@ function SidebarDrawer({
|
||||
<Base.SidebarDrawerOverlay className="fixed z-40 inset-0 backdrop-blur-xs data-[state=open]:animate-fd-fade-in data-[state=closed]:animate-fd-fade-out" />
|
||||
<Base.SidebarDrawerContent
|
||||
className={cn(
|
||||
'fixed text-[0.9375rem] flex flex-col shadow-lg border-s inset-e-0 inset-y-0 w-[85%] max-w-[380px] z-40 bg-fd-background data-[state=open]:animate-fd-sidebar-in data-[state=closed]:animate-fd-sidebar-out',
|
||||
// Same three rows as the desktop rail, and for the same reason: a
|
||||
// definite height (inset-y-0) with one region — the tree — absorbing
|
||||
// the leftover. Here all three children always render, so the row
|
||||
// model is exact.
|
||||
'fixed text-[0.9375rem] grid grid-cols-[minmax(0,1fr)] grid-rows-[auto_minmax(0,1fr)_auto] shadow-lg border-s inset-e-0 inset-y-0 w-[85%] max-w-[380px] z-40 bg-fd-background data-[state=open]:animate-fd-sidebar-in data-[state=closed]:animate-fd-sidebar-out',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -384,7 +419,7 @@ function SidebarFolderContent({
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex flex-col gap-0.5 pt-0.5">{children}</div>
|
||||
<div className="grid grid-cols-[minmax(0,1fr)] gap-0.5 pt-0.5">{children}</div>
|
||||
</Base.SidebarFolderContent>
|
||||
);
|
||||
}
|
||||
@@ -438,7 +473,7 @@ function SidebarTabsDropdown({
|
||||
<ChevronsUpDown className="shrink-0 ms-auto size-4 text-fd-muted-foreground" />
|
||||
</PopoverTrigger>
|
||||
)}
|
||||
<PopoverContent className="flex flex-col gap-1 w-(--radix-popover-trigger-width) p-1 fd-scroll-container">
|
||||
<PopoverContent className="grid grid-cols-[minmax(0,1fr)] gap-1 w-(--radix-popover-trigger-width) p-1 fd-scroll-container">
|
||||
{tabs.map((item) => {
|
||||
const isActive = selected && item.url === selected.url;
|
||||
if (!isActive && item.unlisted) return;
|
||||
|
||||
Reference in New Issue
Block a user