UI: simplify layout offset logic

This commit is contained in:
Fuma Nama
2025-08-04 21:20:48 +08:00
parent 42ddc2ccad
commit f2b22ca218
10 changed files with 126 additions and 110 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-ui': patch
---
simplify layout offset logic
+2
View File
@@ -1,5 +1,7 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
.idea
# dependencies
node_modules
/.pnp
+31 -33
View File
@@ -4,10 +4,8 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="edacdf1b-58dd-4ffe-9166-8e463dc8fa23" name="Changes" comment="Core: remove waku &amp; tanstack peer dependency temporarily">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/apps/docs/content/docs/ui/meta.json" beforeDir="false" afterPath="$PROJECT_DIR$/apps/docs/content/docs/ui/meta.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/packages/ui/src/components/layout/root-toggle.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/packages/ui/src/components/layout/root-toggle.tsx" afterDir="false" />
<list default="true" id="edacdf1b-58dd-4ffe-9166-8e463dc8fa23" name="Changes" comment="UI: simplify layout offset logic">
<change beforePath="$PROJECT_DIR$/apps/docs/app/docs/layout.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/apps/docs/app/docs/layout.tsx" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -696,31 +694,7 @@
<workItem from="1754130248722" duration="3671000" />
<workItem from="1754204737729" duration="2422000" />
<workItem from="1754274652897" duration="8851000" />
<workItem from="1754302800743" duration="487000" />
</task>
<task id="LOCAL-01786" summary="UI: Improve type table link icon">
<option name="closed" value="true" />
<created>1753460003717</created>
<option name="number" value="01786" />
<option name="presentableId" value="LOCAL-01786" />
<option name="project" value="LOCAL" />
<updated>1753460003717</updated>
</task>
<task id="LOCAL-01787" summary="UI: Improve type table link icon">
<option name="closed" value="true" />
<created>1753460016944</created>
<option name="number" value="01787" />
<option name="presentableId" value="LOCAL-01787" />
<option name="project" value="LOCAL" />
<updated>1753460016944</updated>
</task>
<task id="LOCAL-01788" summary="UI: Improve type table link icon">
<option name="closed" value="true" />
<created>1753460266868</created>
<option name="number" value="01788" />
<option name="presentableId" value="LOCAL-01788" />
<option name="project" value="LOCAL" />
<updated>1753460266868</updated>
<workItem from="1754302800743" duration="8815000" />
</task>
<task id="LOCAL-01789" summary="UI: improve codeblock diff styles">
<option name="closed" value="true" />
@@ -1090,7 +1064,31 @@
<option name="project" value="LOCAL" />
<updated>1754300929054</updated>
</task>
<option name="localTasksCounter" value="1835" />
<task id="LOCAL-01835" summary="UI: fixed root toggle menu size">
<option name="closed" value="true" />
<created>1754303427696</created>
<option name="number" value="01835" />
<option name="presentableId" value="LOCAL-01835" />
<option name="project" value="LOCAL" />
<updated>1754303427696</updated>
</task>
<task id="LOCAL-01836" summary="UI: simplify layout offset logic">
<option name="closed" value="true" />
<created>1754312936753</created>
<option name="number" value="01836" />
<option name="presentableId" value="LOCAL-01836" />
<option name="project" value="LOCAL" />
<updated>1754312936753</updated>
</task>
<task id="LOCAL-01837" summary="UI: simplify layout offset logic">
<option name="closed" value="true" />
<created>1754312963539</created>
<option name="number" value="01837" />
<option name="presentableId" value="LOCAL-01837" />
<option name="project" value="LOCAL" />
<updated>1754312963539</updated>
</task>
<option name="localTasksCounter" value="1838" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -1141,8 +1139,6 @@
</component>
<component name="VcsManagerConfiguration">
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
<MESSAGE value="UI: improve codeblock diff styles" />
<MESSAGE value="Core: Support to override `&lt;HideIfEmpty /&gt;` scripts nonce with `&lt;HideIfEmptyProvider /&gt;`" />
<MESSAGE value="UI: Move sidebar toolbar to top on mobile view" />
<MESSAGE value="UI: improve details" />
<MESSAGE value="React Router: add example pre-render script" />
@@ -1166,6 +1162,8 @@
<MESSAGE value="Upgrade Tanstack Start to fix upstream bugs" />
<MESSAGE value="fix bugs &amp; add showcase" />
<MESSAGE value="Core: remove waku &amp; tanstack peer dependency temporarily" />
<option name="LAST_COMMIT_MESSAGE" value="Core: remove waku &amp; tanstack peer dependency temporarily" />
<MESSAGE value="UI: fixed root toggle menu size" />
<MESSAGE value="UI: simplify layout offset logic" />
<option name="LAST_COMMIT_MESSAGE" value="UI: simplify layout offset logic" />
</component>
</project>
@@ -410,6 +410,23 @@ export function SidebarFolderContent(props: CollapsibleContentProps) {
);
}
export function SidebarTrigger({
children,
...props
}: ComponentProps<'button'>) {
const { setOpen } = useSidebar();
return (
<button
{...props}
aria-label="Open Sidebar"
onClick={() => setOpen((prev) => !prev)}
>
{children}
</button>
);
}
export function SidebarCollapseTrigger(props: ComponentProps<'button'>) {
const { collapsed, setCollapsed } = useSidebar();
+5 -21
View File
@@ -35,14 +35,15 @@ export function LayoutBody(props: ComponentProps<'main'>) {
id="nd-docs-layout"
{...props}
className={cn(
'flex flex-1 flex-col transition-[margin]',
'flex flex-1 flex-col pt-(--fd-nav-height) transition-[padding]',
props.className,
)}
style={{
...props.style,
marginInlineStart: collapsed
? 'max(0px, min(calc(100vw - var(--fd-page-width)), var(--fd-sidebar-width)))'
: 'var(--fd-sidebar-width)',
paddingInlineStart: collapsed
? 'min(calc(100vw - var(--fd-page-width)), var(--fd-sidebar-width))'
: 'calc(var(--fd-sidebar-width) + var(--fd-layout-offset))',
paddingInlineEnd: collapsed ? '0px' : 'var(--fd-layout-offset)',
}}
>
{props.children}
@@ -50,23 +51,6 @@ export function LayoutBody(props: ComponentProps<'main'>) {
);
}
export function SidebarTrigger({
children,
...props
}: ComponentProps<'button'>) {
const { setOpen } = useSidebar();
return (
<button
{...props}
aria-label="Open Sidebar"
onClick={() => setOpen((prev) => !prev)}
>
{children}
</button>
);
}
export function CollapsibleControl() {
const { collapsed } = useSidebar();
+12 -15
View File
@@ -16,6 +16,7 @@ import {
SidebarFooter,
SidebarHeader,
SidebarPageTree,
SidebarTrigger,
SidebarViewport,
} from '@/components/layout/sidebar';
import {
@@ -29,12 +30,7 @@ import {
LanguageToggle,
LanguageToggleText,
} from '@/components/layout/language-toggle';
import {
CollapsibleControl,
LayoutBody,
Navbar,
SidebarTrigger,
} from '@/layouts/docs-client';
import { CollapsibleControl, LayoutBody, Navbar } from '@/layouts/docs-client';
import { TreeContextProvider } from '@/contexts/tree';
import { ThemeToggle } from '@/components/layout/theme-toggle';
import {
@@ -84,14 +80,8 @@ export function DocsLayout({
[sidebarTabs, props.tree],
);
const links = getLinks(props.links ?? [], props.githubUrl);
const variables = cn(
sidebarEnabled &&
'md:[--fd-sidebar-width:268px] lg:[--fd-sidebar-width:286px]',
'xl:[--fd-toc-width:286px]',
!nav.component && nav.enabled !== false
? '[--fd-nav-height:56px] md:[--fd-nav-height:0px]'
: undefined,
const sidebarVariables = cn(
'md:[--fd-sidebar-width:268px] lg:[--fd-sidebar-width:286px]',
);
function sidebar() {
@@ -290,7 +280,14 @@ export function DocsLayout({
))}
<LayoutBody
{...props.containerProps}
className={cn(variables, props.containerProps?.className)}
className={cn(
'xl:[--fd-toc-width:286px]',
sidebarEnabled && sidebarVariables,
!nav.component &&
nav.enabled !== false &&
'[--fd-nav-height:56px] md:[--fd-nav-height:0px]',
props.containerProps?.className,
)}
>
{sidebarEnabled && sidebar()}
{children}
+11 -31
View File
@@ -28,7 +28,7 @@ import {
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import { useSidebar } from '@/contexts/sidebar';
import { TOCProvider, useTOCItems } from '@/components/layout/toc';
import { useTOCItems } from '@/components/layout/toc';
import { type AnchorProviderProps, useActiveAnchor } from 'fumadocs-core/toc';
const TocPopoverContext = createContext<{
@@ -52,14 +52,14 @@ export function PageTOCPopoverTrigger(props: ComponentProps<'button'>) {
<CollapsibleTrigger
{...props}
className={cn(
'flex w-full h-(--fd-tocnav-height) items-center text-sm text-fd-muted-foreground gap-2.5 px-4 py-2.5 text-start focus-visible:outline-none [&_svg]:shrink-0 [&_svg]:size-4 md:px-6',
'flex w-full h-(--fd-tocnav-height) items-center text-sm text-fd-muted-foreground gap-2.5 px-4 py-2.5 text-start focus-visible:outline-none [&_svg]:size-4 md:px-6',
props.className,
)}
>
<ProgressCircle
value={(selected + 1) / Math.max(1, items.length)}
max={1}
className={cn(open && 'text-fd-primary')}
className={cn('shrink-0', open && 'text-fd-primary')}
/>
<span className="grid flex-1 *:my-auto *:row-start-1 *:col-start-1">
<span
@@ -81,7 +81,10 @@ export function PageTOCPopoverTrigger(props: ComponentProps<'button'>) {
</span>
</span>
<ChevronDown
className={cn('transition-transform mx-0.5', open && 'rotate-180')}
className={cn(
'shrink-0 transition-transform mx-0.5',
open && 'rotate-180',
)}
/>
</CollapsibleTrigger>
);
@@ -218,29 +221,6 @@ export interface RootProps extends ComponentProps<'div'> {
toc: Omit<AnchorProviderProps, 'children'>;
}
export function PageRoot({ toc, children, ...props }: RootProps) {
const { collapsed } = useSidebar();
return (
<TOCProvider {...toc}>
<div
id="nd-page"
{...props}
className={cn('flex flex-1 mx-auto w-full', props.className)}
style={{
paddingTop: 'calc(var(--fd-nav-height) + var(--fd-tocnav-height))',
maxWidth: collapsed
? 'var(--fd-page-width)'
: 'min(var(--fd-page-width),calc(var(--fd-layout-width) - var(--fd-sidebar-width)))',
...props.style,
}}
>
{children}
</div>
</TOCProvider>
);
}
export function PageLastUpdate({
date: value,
...props
@@ -295,7 +275,7 @@ function scanNavigationList(tree: PageTree.Node[]) {
return list;
}
const listCache = new WeakMap<PageTree.Root, PageTree.Item[]>();
const listCache = new Map<string, PageTree.Item[]>();
export function PageFooter({ items, ...props }: FooterProps) {
const { root } = useTreeContext();
@@ -304,9 +284,9 @@ export function PageFooter({ items, ...props }: FooterProps) {
const { previous, next } = useMemo(() => {
if (items) return items;
const cached = listCache.get(root);
const cached = listCache.get(root.$id);
const list = cached ?? scanNavigationList(root.children);
listCache.set(root, list);
listCache.set(root.$id, list);
const idx = list.findIndex((item) => isActive(item.url, pathname, false));
@@ -396,7 +376,7 @@ export function PageBreadcrumb({
return (
<Fragment key={i}>
{i !== 0 && <span className="text-fd-foreground/30">/</span>}
{i !== 0 && <ChevronRight className="size-3.5 shrink-0" />}
{item.url ? (
<Link
href={item.url}
+18 -3
View File
@@ -6,14 +6,13 @@ import {
PageBreadcrumb,
PageFooter,
PageLastUpdate,
PageRoot,
PageTOC,
PageTOCPopover,
PageTOCPopoverContent,
PageTOCPopoverTrigger,
type RootProps,
} from './page-client';
import { TOCItems, TOCScrollArea } from '@/components/layout/toc';
import { TOCItems, TOCProvider, TOCScrollArea } from '@/components/layout/toc';
import { Text } from 'lucide-react';
import { I18nLabel } from '@/contexts/i18n';
import ClerkTOCItems from '@/components/layout/toc-clerk';
@@ -80,8 +79,24 @@ export function PageArticle(props: ComponentProps<'article'>) {
);
}
export function PageRoot({ toc, children, ...props }: RootProps) {
return (
<TOCProvider {...toc}>
<div
id="nd-page"
{...props}
className={cn(
'flex flex-1 w-full mx-auto max-w-(--fd-page-width) pt-(--fd-tocnav-height)',
props.className,
)}
>
{children}
</div>
</TOCProvider>
);
}
export {
PageRoot,
PageBreadcrumb,
PageFooter,
PageLastUpdate,
+5 -4
View File
@@ -43,14 +43,15 @@ export function LayoutBody(props: ComponentProps<'main'>) {
id="nd-docs-layout"
{...props}
className={cn(
'flex flex-1 flex-col transition-[margin] fd-notebook-layout',
'flex flex-1 flex-col transition-[padding] pt-(--fd-nav-height) fd-notebook-layout',
props.className,
)}
style={{
...props.style,
marginInlineStart: collapsed
? 'max(0px, min(calc(100vw - var(--fd-page-width)), var(--fd-sidebar-width)))'
: 'var(--fd-sidebar-width)',
paddingInlineStart: collapsed
? 'min(calc(100vw - var(--fd-page-width)), var(--fd-sidebar-width))'
: 'calc(var(--fd-sidebar-width) + var(--fd-layout-offset))',
paddingInlineEnd: collapsed ? '0px' : 'var(--fd-layout-offset)',
}}
>
{props.children}
+20 -3
View File
@@ -13,12 +13,18 @@ import {
SidebarFooter,
SidebarHeader,
SidebarPageTree,
SidebarTrigger,
SidebarViewport,
} from '@/components/layout/sidebar';
import { TreeContextProvider } from '@/contexts/tree';
import { cn } from '@/utils/cn';
import { buttonVariants } from '@/components/ui/button';
import { ChevronDown, Languages, Sidebar as SidebarIcon } from 'lucide-react';
import {
ChevronDown,
Languages,
Sidebar as SidebarIcon,
X,
} from 'lucide-react';
import { BaseLinkItem, type LinkItemType } from '@/layouts/links';
import { LanguageToggle } from '@/components/layout/language-toggle';
import { ThemeToggle } from '@/components/layout/theme-toggle';
@@ -190,10 +196,21 @@ export function DocsLayout(props: DocsLayoutProps) {
const mobile = (
<SidebarContentMobile {...rest}>
<HideIfEmpty as={SidebarHeader}>
<SidebarHeader>
<SidebarTrigger
className={cn(
buttonVariants({
size: 'icon-sm',
color: 'ghost',
className: 'ms-auto text-fd-muted-foreground',
}),
)}
>
<X />
</SidebarTrigger>
{banner}
{rootToggle}
</HideIfEmpty>
</SidebarHeader>
{viewport}
<HideIfEmpty
as={SidebarFooter}