mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 06:31:49 +00:00
UI: Drop fumadocs-core/hide-if-empty usage
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-ui': patch
|
||||
---
|
||||
|
||||
Drop `fumadocs-core/hide-if-empty` usage, prefer user-side component overrides for precise control
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: Hello World
|
||||
description: |
|
||||
Your first `document`
|
||||
```ts
|
||||
console.log("Hello World")
|
||||
```
|
||||
---
|
||||
|
||||
Hey there! fsd asfd sdfsfda
|
||||
sfd
|
||||
|
||||
fdsa ff dsaf sdf sda fasd fsd
|
||||
|
||||
## Heading
|
||||
|
||||
Hello World dsasfdafsd
|
||||
|
||||
<Cards>
|
||||
<Card title="Learn more about Next.js" href="https://nextjs.org/docs" />
|
||||
<Card title="Learn more about Fumadocs" href="https://fumadocs.dev" />
|
||||
</Cards>
|
||||
|
||||
### Heading
|
||||
|
||||
#### Heading
|
||||
|
||||
| Head | Description |
|
||||
| ------------------------------- | ----------------------------------- |
|
||||
| `hello` | Hello World |
|
||||
| very **important** | Hey |
|
||||
| _Surprisingly_ | Fumadocs |
|
||||
| very long text that looks weird | hello world hello world hello world |
|
||||
|
||||
<include>./test.mdx</include>
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"root": true
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: Test
|
||||
description: A document to test Fumadocs
|
||||
---
|
||||
|
||||
Hey there!
|
||||
|
||||
## Cards
|
||||
|
||||
<Cards>
|
||||
<Card title="Learn more about Next.js" href="https://nextjs.org/docs" />
|
||||
<Card title="Learn more about Fumadocs" href="https://fumadocs.dev" />
|
||||
</Cards>
|
||||
|
||||
### CodeBlock
|
||||
|
||||
```js
|
||||
console.log('Hello World');
|
||||
```
|
||||
|
||||
#### List
|
||||
|
||||
- Hello
|
||||
- World
|
||||
@@ -49,6 +49,28 @@ function isEmpty(node: HTMLElement) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function disableAnimation() {
|
||||
const css = document.createElement('style');
|
||||
const nonce = document.currentScript?.nonce;
|
||||
if (nonce) css.setAttribute('nonce', nonce);
|
||||
css.appendChild(
|
||||
document.createTextNode(
|
||||
`*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}`,
|
||||
),
|
||||
);
|
||||
document.head.appendChild(css);
|
||||
|
||||
return () => {
|
||||
// Force restyle
|
||||
(() => window.getComputedStyle(document.body))();
|
||||
|
||||
// Wait for next tick before removing
|
||||
setTimeout(() => {
|
||||
document.head.removeChild(css);
|
||||
}, 1);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The built-in CSS `:empty` selector cannot detect if the children is hidden, classes such as `md:hidden` causes it to fail.
|
||||
* This component is an enhancement to `empty:hidden` to fix the issue described above.
|
||||
@@ -80,10 +102,10 @@ export function HideIfEmpty<Props extends HTMLAttributes<HTMLElement>>({
|
||||
|
||||
const init = (id: string) => {
|
||||
const element = getElement(id);
|
||||
if (element) element.hidden = isEmpty(element);
|
||||
|
||||
const script = document.currentScript;
|
||||
if (script) script.parentNode?.removeChild(script);
|
||||
if (element) {
|
||||
disableAnimation();
|
||||
element.hidden = isEmpty(element);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -93,14 +115,13 @@ export function HideIfEmpty<Props extends HTMLAttributes<HTMLElement>>({
|
||||
data-fd-if-empty={id}
|
||||
hidden={empty ?? false}
|
||||
/>
|
||||
{empty === undefined && (
|
||||
<script
|
||||
nonce={nonce}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `{${getElement};${isEmpty};(${init})("${id}")}`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<script
|
||||
suppressHydrationWarning
|
||||
nonce={nonce}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `{${getElement};${isEmpty};${disableAnimation};(${init})("${id}")}`,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
type BaseLayoutProps,
|
||||
BaseLinkItem,
|
||||
getLinks,
|
||||
type IconItemType,
|
||||
type LinkItemType,
|
||||
} from '@/layouts/shared';
|
||||
import {
|
||||
@@ -52,7 +51,6 @@ import {
|
||||
LargeSearchToggle,
|
||||
SearchToggle,
|
||||
} from '@/components/layout/search-toggle';
|
||||
import { HideIfEmpty } from 'fumadocs-core/hide-if-empty';
|
||||
import {
|
||||
getSidebarTabs,
|
||||
type GetSidebarTabsOptions,
|
||||
@@ -138,9 +136,7 @@ export function DocsLayout({
|
||||
} = sidebarProps;
|
||||
if (component) return component;
|
||||
|
||||
const iconLinks = links.filter(
|
||||
(item): item is IconItemType => item.type === 'icon',
|
||||
);
|
||||
const iconLinks = links.filter((item) => item.type === 'icon');
|
||||
|
||||
const viewport = (
|
||||
<SidebarViewport>
|
||||
@@ -244,32 +240,40 @@ export function DocsLayout({
|
||||
{banner}
|
||||
</SidebarHeader>
|
||||
{viewport}
|
||||
<HideIfEmpty as={SidebarFooter}>
|
||||
<div className="flex text-fd-muted-foreground items-center empty:hidden">
|
||||
{i18n ? (
|
||||
<LanguageToggle>
|
||||
<Languages className="size-4.5" />
|
||||
</LanguageToggle>
|
||||
) : null}
|
||||
{iconLinks.map((item, i) => (
|
||||
<BaseLinkItem
|
||||
key={i}
|
||||
item={item}
|
||||
className={cn(
|
||||
buttonVariants({ size: 'icon-sm', color: 'ghost' }),
|
||||
)}
|
||||
aria-label={item.label}
|
||||
>
|
||||
{item.icon}
|
||||
</BaseLinkItem>
|
||||
))}
|
||||
{themeSwitch.enabled !== false &&
|
||||
(themeSwitch.component ?? (
|
||||
<ThemeToggle className="ms-auto p-0" mode={themeSwitch.mode} />
|
||||
{(i18n ||
|
||||
iconLinks.length > 0 ||
|
||||
themeSwitch?.enabled !== false ||
|
||||
footer) && (
|
||||
<SidebarFooter>
|
||||
<div className="flex text-fd-muted-foreground items-center empty:hidden">
|
||||
{i18n && (
|
||||
<LanguageToggle>
|
||||
<Languages className="size-4.5" />
|
||||
</LanguageToggle>
|
||||
)}
|
||||
{iconLinks.map((item, i) => (
|
||||
<BaseLinkItem
|
||||
key={i}
|
||||
item={item}
|
||||
className={cn(
|
||||
buttonVariants({ size: 'icon-sm', color: 'ghost' }),
|
||||
)}
|
||||
aria-label={item.label}
|
||||
>
|
||||
{item.icon}
|
||||
</BaseLinkItem>
|
||||
))}
|
||||
</div>
|
||||
{footer}
|
||||
</HideIfEmpty>
|
||||
{themeSwitch.enabled !== false &&
|
||||
(themeSwitch.component ?? (
|
||||
<ThemeToggle
|
||||
className="ms-auto p-0"
|
||||
mode={themeSwitch.mode}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{footer}
|
||||
</SidebarFooter>
|
||||
)}
|
||||
</SidebarContent>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
type ComponentProps,
|
||||
type FC,
|
||||
Fragment,
|
||||
type HTMLAttributes,
|
||||
type ReactNode,
|
||||
@@ -60,7 +61,6 @@ import {
|
||||
LargeSearchToggle,
|
||||
SearchToggle,
|
||||
} from '@/components/layout/search-toggle';
|
||||
import { HideIfEmpty } from 'fumadocs-core/hide-if-empty';
|
||||
import {
|
||||
getSidebarTabs,
|
||||
type GetSidebarTabsOptions,
|
||||
@@ -89,8 +89,8 @@ interface SidebarOptions
|
||||
*/
|
||||
tabs?: Option[] | GetSidebarTabsOptions | false;
|
||||
|
||||
banner?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
banner?: ReactNode | FC<ComponentProps<typeof SidebarHeader>>;
|
||||
footer?: ReactNode | FC<ComponentProps<typeof SidebarFooter>>;
|
||||
|
||||
/**
|
||||
* Support collapsing the sidebar on desktop mode
|
||||
@@ -137,6 +137,24 @@ export function DocsLayout(props: DocsLayoutProps) {
|
||||
defaultOpenLevel,
|
||||
...rest
|
||||
} = sidebarProps;
|
||||
const Header =
|
||||
typeof banner === 'function'
|
||||
? banner
|
||||
: (props: ComponentProps<typeof SidebarHeader>) => (
|
||||
<SidebarHeader {...props}>
|
||||
{props.children}
|
||||
{banner}
|
||||
</SidebarHeader>
|
||||
);
|
||||
const Footer =
|
||||
typeof footer === 'function'
|
||||
? footer
|
||||
: (props: ComponentProps<typeof SidebarFooter>) => (
|
||||
<SidebarFooter {...props}>
|
||||
{props.children}
|
||||
{footer}
|
||||
</SidebarFooter>
|
||||
);
|
||||
const iconLinks = links.filter((item) => item.type === 'icon');
|
||||
|
||||
const rootToggle = (
|
||||
@@ -150,30 +168,6 @@ export function DocsLayout(props: DocsLayoutProps) {
|
||||
</>
|
||||
);
|
||||
|
||||
const sidebarNav = (
|
||||
<div className="flex justify-between">
|
||||
<Link
|
||||
href={nav.url ?? '/'}
|
||||
className="inline-flex items-center gap-2.5 font-medium"
|
||||
>
|
||||
{nav.title}
|
||||
</Link>
|
||||
{collapsible && (
|
||||
<SidebarCollapseTrigger
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
color: 'ghost',
|
||||
size: 'icon-sm',
|
||||
className: 'mt-px mb-auto text-fd-muted-foreground',
|
||||
}),
|
||||
)}
|
||||
>
|
||||
<SidebarIcon />
|
||||
</SidebarCollapseTrigger>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const viewport = (
|
||||
<SidebarViewport>
|
||||
{links
|
||||
@@ -200,16 +194,39 @@ export function DocsLayout(props: DocsLayoutProps) {
|
||||
rest.className,
|
||||
)}
|
||||
>
|
||||
<HideIfEmpty as={SidebarHeader}>
|
||||
{navMode === 'auto' && sidebarNav}
|
||||
<Header className="empty:hidden">
|
||||
{navMode === 'auto' && (
|
||||
<div className="flex justify-between">
|
||||
<Link
|
||||
href={nav.url ?? '/'}
|
||||
className="inline-flex items-center gap-2.5 font-medium"
|
||||
>
|
||||
{nav.title}
|
||||
</Link>
|
||||
{collapsible && (
|
||||
<SidebarCollapseTrigger
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
color: 'ghost',
|
||||
size: 'icon-sm',
|
||||
className: 'mt-px mb-auto text-fd-muted-foreground',
|
||||
}),
|
||||
)}
|
||||
>
|
||||
<SidebarIcon />
|
||||
</SidebarCollapseTrigger>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{nav.children}
|
||||
{banner}
|
||||
{rootToggle}
|
||||
</HideIfEmpty>
|
||||
</Header>
|
||||
{viewport}
|
||||
<HideIfEmpty
|
||||
as={SidebarFooter}
|
||||
className="flex flex-row text-fd-muted-foreground items-center"
|
||||
<Footer
|
||||
className={cn(
|
||||
'hidden flex-row text-fd-muted-foreground items-center',
|
||||
iconLinks.length > 0 && 'max-lg:flex',
|
||||
)}
|
||||
>
|
||||
{iconLinks.map((item, i) => (
|
||||
<BaseLinkItem
|
||||
@@ -227,14 +244,13 @@ export function DocsLayout(props: DocsLayoutProps) {
|
||||
{item.icon}
|
||||
</BaseLinkItem>
|
||||
))}
|
||||
{footer}
|
||||
</HideIfEmpty>
|
||||
</Footer>
|
||||
</SidebarContent>
|
||||
);
|
||||
|
||||
const mobile = (
|
||||
<SidebarContentMobile {...rest}>
|
||||
<SidebarHeader>
|
||||
<Header>
|
||||
<SidebarTrigger
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
@@ -246,13 +262,15 @@ export function DocsLayout(props: DocsLayoutProps) {
|
||||
>
|
||||
<X />
|
||||
</SidebarTrigger>
|
||||
{banner}
|
||||
{rootToggle}
|
||||
</SidebarHeader>
|
||||
</Header>
|
||||
{viewport}
|
||||
<HideIfEmpty
|
||||
as={SidebarFooter}
|
||||
className="flex flex-row items-center justify-end"
|
||||
<Footer
|
||||
className={cn(
|
||||
'hidden flex-row items-center justify-end',
|
||||
(i18n || themeSwitch.enabled !== false) && 'flex',
|
||||
iconLinks.length > 0 && 'max-lg:flex',
|
||||
)}
|
||||
>
|
||||
{iconLinks.map((item, i) => (
|
||||
<BaseLinkItem
|
||||
@@ -271,17 +289,16 @@ export function DocsLayout(props: DocsLayoutProps) {
|
||||
{item.icon}
|
||||
</BaseLinkItem>
|
||||
))}
|
||||
{i18n ? (
|
||||
{i18n && (
|
||||
<LanguageToggle>
|
||||
<Languages className="size-4.5 text-fd-muted-foreground" />
|
||||
</LanguageToggle>
|
||||
) : null}
|
||||
)}
|
||||
{themeSwitch.enabled !== false &&
|
||||
(themeSwitch.component ?? (
|
||||
<ThemeToggle mode={themeSwitch.mode ?? 'light-dark-system'} />
|
||||
))}
|
||||
{footer}
|
||||
</HideIfEmpty>
|
||||
</Footer>
|
||||
</SidebarContentMobile>
|
||||
);
|
||||
|
||||
@@ -321,16 +338,16 @@ export function DocsLayout(props: DocsLayoutProps) {
|
||||
function DocsNavbar({
|
||||
links,
|
||||
tabs,
|
||||
sidebar: { collapsible: sidebarCollapsible = true } = {},
|
||||
searchToggle = {},
|
||||
themeSwitch = {},
|
||||
nav = {},
|
||||
...props
|
||||
i18n,
|
||||
}: DocsLayoutProps & {
|
||||
links: LinkItemType[];
|
||||
tabs: Option[];
|
||||
}) {
|
||||
const navMode = nav.mode ?? 'auto';
|
||||
const sidebarCollapsible = props.sidebar?.collapsible ?? true;
|
||||
|
||||
return (
|
||||
<Navbar
|
||||
@@ -438,11 +455,11 @@ function DocsNavbar({
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 max-md:hidden">
|
||||
{props.i18n ? (
|
||||
{i18n && (
|
||||
<LanguageToggle>
|
||||
<Languages className="size-4.5 text-fd-muted-foreground" />
|
||||
</LanguageToggle>
|
||||
) : null}
|
||||
)}
|
||||
{themeSwitch.enabled !== false &&
|
||||
(themeSwitch.component ?? (
|
||||
<ThemeToggle mode={themeSwitch.mode ?? 'light-dark-system'} />
|
||||
|
||||
Reference in New Issue
Block a user