mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
UI: Fix i18n on Notebook layout
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-mdx': patch
|
||||
---
|
||||
|
||||
Improve performance
|
||||
@@ -139,7 +139,49 @@ export default function Layout({ children }: { children: ReactNode }) {
|
||||
}
|
||||
```
|
||||
|
||||
### Replacing Navbar
|
||||
### Nav Mode
|
||||
|
||||
Customise the position of navbar.
|
||||
|
||||
```tsx title="layout.tsx"
|
||||
import { DocsLayout } from 'fumadocs-ui/layouts/notebook';
|
||||
import { baseOptions } from '@/app/layout.config';
|
||||
import { source } from '@/lib/source';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<DocsLayout
|
||||
{...baseOptions}
|
||||
nav={{ ...baseOptions, mode: 'top' }}
|
||||
tree={source.pageTree}
|
||||
>
|
||||
{children}
|
||||
</DocsLayout>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Tab Mode
|
||||
|
||||
Customise the position of tabs (Sidebar Tabs).
|
||||
|
||||
```tsx title="layout.tsx"
|
||||
import { DocsLayout } from 'fumadocs-ui/layouts/notebook';
|
||||
import { baseOptions } from '@/app/layout.config';
|
||||
import { source } from '@/lib/source';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<DocsLayout {...baseOptions} tabMode="navbar" tree={source.pageTree}>
|
||||
{children}
|
||||
</DocsLayout>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Replacing Navbar
|
||||
|
||||
Fumadocs uses CSS Variables to share the size of layout components, and fit each layout component into appropriate position.
|
||||
|
||||
|
||||
@@ -4,16 +4,9 @@ import * as Primitive from 'fumadocs-core/toc';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { TocThumb } from '@/components/layout/toc-thumb';
|
||||
import { ScrollArea, ScrollViewport } from '../ui/scroll-area';
|
||||
import { TocItemsEmpty } from '@/components/layout/toc';
|
||||
|
||||
export default function ClerkTOCItems({
|
||||
items,
|
||||
}: {
|
||||
items: TOCItemType[];
|
||||
isMenu?: boolean;
|
||||
}) {
|
||||
const viewRef = useRef<HTMLDivElement>(null);
|
||||
export default function ClerkTOCItems({ items }: { items: TOCItemType[] }) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [svg, setSvg] = useState<{
|
||||
@@ -71,42 +64,38 @@ export default function ClerkTOCItems({
|
||||
if (items.length === 0) return <TocItemsEmpty />;
|
||||
|
||||
return (
|
||||
<ScrollArea className="flex flex-col ps-px">
|
||||
<ScrollViewport className="relative min-h-0" ref={viewRef}>
|
||||
{svg ? (
|
||||
<div
|
||||
className="absolute start-0 top-0 rtl:-scale-x-100"
|
||||
style={{
|
||||
width: svg.width,
|
||||
height: svg.height,
|
||||
maskImage: `url("data:image/svg+xml,${
|
||||
// Inline SVG
|
||||
encodeURIComponent(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${svg.width} ${svg.height}"><path d="${svg.path}" stroke="black" stroke-width="1" fill="none" /></svg>`,
|
||||
)
|
||||
}")`,
|
||||
}}
|
||||
>
|
||||
<TocThumb
|
||||
containerRef={containerRef}
|
||||
className="mt-(--fd-top) h-(--fd-height) bg-fd-primary transition-all"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<Primitive.ScrollProvider containerRef={viewRef}>
|
||||
<div className="flex flex-col" ref={containerRef}>
|
||||
{items.map((item, i) => (
|
||||
<TOCItem
|
||||
key={item.url}
|
||||
item={item}
|
||||
upper={items[i - 1]?.depth}
|
||||
lower={items[i + 1]?.depth}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Primitive.ScrollProvider>
|
||||
</ScrollViewport>
|
||||
</ScrollArea>
|
||||
<>
|
||||
{svg ? (
|
||||
<div
|
||||
className="absolute start-0 top-0 rtl:-scale-x-100"
|
||||
style={{
|
||||
width: svg.width,
|
||||
height: svg.height,
|
||||
maskImage: `url("data:image/svg+xml,${
|
||||
// Inline SVG
|
||||
encodeURIComponent(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${svg.width} ${svg.height}"><path d="${svg.path}" stroke="black" stroke-width="1" fill="none" /></svg>`,
|
||||
)
|
||||
}")`,
|
||||
}}
|
||||
>
|
||||
<TocThumb
|
||||
containerRef={containerRef}
|
||||
className="mt-(--fd-top) h-(--fd-height) bg-fd-primary transition-all"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex flex-col" ref={containerRef}>
|
||||
{items.map((item, i) => (
|
||||
<TOCItem
|
||||
key={item.url}
|
||||
item={item}
|
||||
upper={items[i - 1]?.depth}
|
||||
lower={items[i + 1]?.depth}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -77,46 +77,55 @@ export function TocItemsEmpty() {
|
||||
);
|
||||
}
|
||||
|
||||
export function TOCItems({
|
||||
items,
|
||||
export function TOCScrollArea({
|
||||
isMenu,
|
||||
}: {
|
||||
items: TOCItemType[];
|
||||
isMenu?: boolean;
|
||||
}) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
...props
|
||||
}: ComponentProps<typeof ScrollArea> & { isMenu?: boolean }) {
|
||||
const viewRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
if (items.length === 0) return <TocItemsEmpty />;
|
||||
|
||||
return (
|
||||
<ScrollArea className="flex flex-col ps-px">
|
||||
<ScrollArea
|
||||
{...props}
|
||||
className={cn('flex flex-col ps-px', props.className)}
|
||||
>
|
||||
<Primitive.ScrollProvider containerRef={viewRef}>
|
||||
<ScrollViewport
|
||||
className={cn(
|
||||
'relative min-h-0 text-sm',
|
||||
'relative min-h-0 text-sm',
|
||||
isMenu && 'mt-2 mb-4 mx-4 md:mx-6',
|
||||
)}
|
||||
ref={viewRef}
|
||||
>
|
||||
<TocThumb
|
||||
containerRef={containerRef}
|
||||
className="absolute start-0 mt-(--fd-top) h-(--fd-height) w-px bg-fd-primary transition-all"
|
||||
/>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="flex flex-col border-s border-fd-foreground/10"
|
||||
>
|
||||
{items.map((item) => (
|
||||
<TOCItem key={item.url} item={item} />
|
||||
))}
|
||||
</div>
|
||||
{props.children}
|
||||
</ScrollViewport>
|
||||
</Primitive.ScrollProvider>
|
||||
</ScrollArea>
|
||||
);
|
||||
}
|
||||
|
||||
export function TOCItems({ items }: { items: TOCItemType[] }) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
if (items.length === 0) return <TocItemsEmpty />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TocThumb
|
||||
containerRef={containerRef}
|
||||
className="absolute start-0 mt-(--fd-top) h-(--fd-height) w-px bg-fd-primary transition-all"
|
||||
/>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="flex flex-col border-s border-fd-foreground/10"
|
||||
>
|
||||
{items.map((item) => (
|
||||
<TOCItem key={item.url} item={item} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function TOCItem({ item }: { item: TOCItemType }) {
|
||||
return (
|
||||
<Primitive.TOCItem
|
||||
@@ -178,7 +187,7 @@ export function TocPopoverTrigger({
|
||||
<CollapsibleTrigger
|
||||
{...props}
|
||||
className={cn(
|
||||
'inline-flex items-center text-sm gap-2 text-nowrap px-4 py-2 text-start md:px-6 md:py-3',
|
||||
'inline-flex items-center text-sm gap-2 text-nowrap px-4 py-2 text-start md:px-6 md:py-3 focus-visible:outline-none',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -15,6 +15,7 @@ export const buttonVariants = cva(
|
||||
size: {
|
||||
sm: 'gap-1 p-0.5 text-xs',
|
||||
icon: 'p-1.5 [&_svg]:size-5',
|
||||
'icon-sm': 'p-1.5 [&_svg]:size-4.5',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -203,9 +203,9 @@ function SidebarHeaderItems({
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
color: 'ghost',
|
||||
size: 'icon',
|
||||
size: 'icon-sm',
|
||||
}),
|
||||
'ms-auto text-fd-muted-foreground [&_svg]:size-4.5 max-md:hidden',
|
||||
'ms-auto text-fd-muted-foreground max-md:hidden',
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
@@ -245,7 +245,7 @@ function SidebarFooterItems({
|
||||
<div role="separator" className="flex-1" />
|
||||
{i18n ? (
|
||||
<LanguageToggle className="me-1.5">
|
||||
<Languages className="size-5" />
|
||||
<Languages className="size-4.5" />
|
||||
<LanguageToggleText className="md:hidden" />
|
||||
</LanguageToggle>
|
||||
) : null}
|
||||
|
||||
@@ -151,12 +151,19 @@ export function DocsLayout({
|
||||
|
||||
<SidebarPageTree components={sidebarComponents} />
|
||||
</SidebarViewport>
|
||||
<SidebarFooter className={cn(!sidebarFooter && 'md:hidden')}>
|
||||
<SidebarFooter
|
||||
className={cn(
|
||||
'flex flex-row items-center',
|
||||
!sidebarFooter && 'md:hidden',
|
||||
)}
|
||||
>
|
||||
{i18n ? (
|
||||
<LanguageToggle className="me-auto md:hidden">
|
||||
<Languages className="size-5 text-fd-muted-foreground" />
|
||||
</LanguageToggle>
|
||||
) : null}
|
||||
{!props.disableThemeSwitch ? (
|
||||
<ThemeToggle
|
||||
className="w-fit md:hidden"
|
||||
mode="light-dark-system"
|
||||
/>
|
||||
<ThemeToggle className="md:hidden" mode="light-dark-system" />
|
||||
) : null}
|
||||
{sidebarFooter}
|
||||
</SidebarFooter>
|
||||
@@ -234,7 +241,7 @@ function DocsNavbar({
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex flex-1 flex-row items-center justify-end md:gap-2">
|
||||
<div className="flex flex-1 flex-row items-center justify-end">
|
||||
<div className="flex flex-row items-center gap-6 px-4 empty:hidden max-lg:hidden">
|
||||
{links
|
||||
.filter((item) => item.type !== 'icon')
|
||||
@@ -256,8 +263,8 @@ function DocsNavbar({
|
||||
key={i}
|
||||
item={item}
|
||||
className={cn(
|
||||
buttonVariants({ size: 'icon', color: 'ghost' }),
|
||||
'text-fd-muted-foreground [&_svg]:size-4.5 max-lg:hidden',
|
||||
buttonVariants({ size: 'icon-sm', color: 'ghost' }),
|
||||
'text-fd-muted-foreground max-lg:hidden',
|
||||
)}
|
||||
aria-label={item.label}
|
||||
>
|
||||
@@ -265,11 +272,14 @@ function DocsNavbar({
|
||||
</BaseLinkItem>
|
||||
))}
|
||||
{i18n ? (
|
||||
<LanguageToggle>
|
||||
<Languages className="size-5" />
|
||||
<LanguageToggle className="max-md:hidden">
|
||||
<Languages className="size-4.5 text-fd-muted-foreground" />
|
||||
</LanguageToggle>
|
||||
) : null}
|
||||
<ThemeToggle className="max-md:hidden" mode="light-dark-system" />
|
||||
<ThemeToggle
|
||||
className="ms-2 max-md:hidden"
|
||||
mode="light-dark-system"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{tabs.length > 0 ? (
|
||||
|
||||
@@ -230,7 +230,7 @@ export function Breadcrumb(options: BreadcrumbProps) {
|
||||
if (items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="flex flex-row items-center gap-1.5 text-[15px] text-fd-muted-foreground -mb-1.5">
|
||||
<div className="flex flex-row items-center gap-1.5 text-[15px] text-fd-muted-foreground">
|
||||
{items.map((item, i) => {
|
||||
const className = cn(
|
||||
'truncate',
|
||||
|
||||
+15
-10
@@ -26,6 +26,7 @@ import {
|
||||
TocPopoverTrigger,
|
||||
TocPopoverContent,
|
||||
type TOCProps,
|
||||
TOCScrollArea,
|
||||
} from '@/components/layout/toc';
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
import { Edit, Text } from 'lucide-react';
|
||||
@@ -158,11 +159,13 @@ export function DocsPage({
|
||||
<TocPopoverTrigger className="w-full" items={toc} />
|
||||
<TocPopoverContent>
|
||||
{tocPopoverOptions.header}
|
||||
{tocPopoverOptions.style === 'clerk' ? (
|
||||
<ClerkTOCItems items={toc} isMenu />
|
||||
) : (
|
||||
<TOCItems items={toc} isMenu />
|
||||
)}
|
||||
<TOCScrollArea isMenu>
|
||||
{tocPopoverOptions.style === 'clerk' ? (
|
||||
<ClerkTOCItems items={toc} />
|
||||
) : (
|
||||
<TOCItems items={toc} />
|
||||
)}
|
||||
</TOCScrollArea>
|
||||
{tocPopoverOptions.footer}
|
||||
</TocPopoverContent>
|
||||
</TocPopoverHeader>,
|
||||
@@ -206,11 +209,13 @@ export function DocsPage({
|
||||
<Text className="size-4" />
|
||||
<I18nLabel label="toc" />
|
||||
</h3>
|
||||
{tocOptions.style === 'clerk' ? (
|
||||
<ClerkTOCItems items={toc} />
|
||||
) : (
|
||||
<TOCItems items={toc} />
|
||||
)}
|
||||
<TOCScrollArea>
|
||||
{tocOptions.style === 'clerk' ? (
|
||||
<ClerkTOCItems items={toc} />
|
||||
) : (
|
||||
<TOCItems items={toc} />
|
||||
)}
|
||||
</TOCScrollArea>
|
||||
{tocOptions.footer}
|
||||
</Toc>,
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user