UI: Support custom max width

This commit is contained in:
fuma-nama
2024-08-10 14:35:55 +08:00
parent d576d79fe5
commit ba588a2296
8 changed files with 114 additions and 53 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-ui': minor
---
Support custom max width
-1
View File
@@ -24,5 +24,4 @@ export const { getPage, getPages, pageTree } = loader({
export const openapi = createOpenAPI({
documentOrPath: path.resolve('./unkey.json'),
generateTypeScriptSchema: false,
});
@@ -73,12 +73,12 @@ export function DynamicSidebar(props: SidebarProps): React.ReactElement {
onPointerLeave: onLeave,
'aria-hidden': Boolean(collapsed && !hover),
className: cn(
'md:transition-[transform,margin]',
'md:transition-all',
collapsed && [
'md:top-1 md:mr-[-240px] md:h-[calc(100dvh-4px)] md:animate-fd-sidebar-collapse md:rounded-xl md:border md:shadow-md xl:mr-[-260px]',
'md:top-1 md:me-fd-sidebar-offset md:h-[calc(100dvh-4px)] md:w-[var(--fd-sidebar-width)] md:animate-fd-sidebar-collapse md:rounded-xl md:border md:ps-0 md:shadow-md',
hover
? 'md:translate-x-1 rtl:md:-translate-x-1'
: 'md:-translate-x-full rtl:md:translate-x-full',
: 'md:translate-x-[calc(var(--fd-sidebar-width)*-1)] rtl:md:translate-x-[var(--fd-sidebar-width)]',
],
),
}}
+23 -13
View File
@@ -53,6 +53,13 @@ export interface SidebarProps {
footer?: React.ReactNode;
footerProps?: HTMLAttributes<HTMLDivElement>;
/**
* Hide search trigger
*
* @defaultValue false
*/
hideSearch?: boolean;
}
interface InternalContext {
@@ -89,6 +96,7 @@ export function Sidebar({
aside?: HTMLAttributes<HTMLElement> & Record<string, unknown>;
}): React.ReactElement {
const search = useSearchContext();
const hasSearch = search.enabled && !props.hideSearch;
const context = useMemo<InternalContext>(
() => ({
defaultOpenLevel,
@@ -105,23 +113,25 @@ export function Sidebar({
blockScrollingWidth={768} // md
{...props.aside}
className={cn(
'fixed z-30 flex shrink-0 flex-col bg-fd-card text-sm md:sticky md:top-0 md:h-dvh md:w-[240px] md:border-e xl:w-[260px]',
'fixed z-30 flex flex-col bg-fd-card text-sm md:sticky md:top-0 md:h-dvh md:w-[var(--fd-c-sidebar)] md:min-w-[var(--fd-sidebar-width)] md:border-e md:ps-[calc(var(--fd-c-sidebar)-var(--fd-sidebar-width))]',
'max-md:inset-0 max-md:bg-fd-background/80 max-md:pt-14 max-md:text-[15px] max-md:backdrop-blur-md max-md:data-[open=false]:hidden',
props.aside?.className,
)}
>
<div
{...props.bannerProps}
className={cn(
'flex flex-col gap-2 px-4 pt-2 md:px-3 md:pt-4',
props.bannerProps?.className,
)}
>
{props.banner}
{search.enabled ? (
<LargeSearchToggle className="rounded-lg max-md:hidden" />
) : null}
</div>
{hasSearch || props.banner ? (
<div
{...props.bannerProps}
className={cn(
'flex flex-col gap-2 px-4 pt-2 md:px-3 md:pt-4',
props.bannerProps?.className,
)}
>
{props.banner}
{hasSearch ? (
<LargeSearchToggle className="rounded-lg max-md:hidden" />
) : null}
</div>
) : null}
<ViewportContent>
{items.length > 0 && (
<div className="flex flex-col md:hidden">
+1 -1
View File
@@ -41,7 +41,7 @@ export function Toc({ items, header, footer }: TOCProps): ReactElement {
return (
<div
data-toc=""
className="sticky top-0 flex h-dvh w-[220px] shrink-0 flex-col gap-4 pr-3 pt-12 max-lg:hidden xl:w-[260px] rtl:pl-3"
className="sticky top-0 flex h-dvh w-[var(--fd-c-toc)] min-w-[var(--fd-toc-width)] flex-col gap-4 pe-[max(calc(var(--fd-c-toc)-var(--fd-toc-width)),0.75rem)] pt-12 max-lg:hidden"
>
{header}
<h3 className="-mb-1 -ms-0.5 inline-flex items-center gap-1.5 text-sm text-fd-muted-foreground">
+45 -32
View File
@@ -63,17 +63,44 @@ export function DocsLayout({
component: sidebarReplace,
...sidebar
} = {},
links = [],
containerProps = {},
tree,
i18n = false,
children,
...props
}: DocsLayoutProps): React.ReactElement {
const finalLinks = getLinks(links, githubUrl);
const links = getLinks(props.links ?? [], githubUrl);
const Aside = collapsible ? DynamicSidebar : Sidebar;
const banner: ReactNode[] = [];
if (nav?.title)
banner.push(
<Link
key="title"
href={nav.url ?? '/'}
className="inline-flex items-center gap-2.5 font-medium"
>
{nav.title}
</Link>,
);
if (links.length > 0)
banner.push(
<LinksMenu
key="links"
items={links}
className={cn(
buttonVariants({
size: 'icon',
color: 'ghost',
className: 'ms-auto',
}),
)}
>
<MoreHorizontal />
</LinksMenu>,
);
return (
<TreeContextProvider tree={tree}>
<TreeContextProvider tree={props.tree}>
{replaceOrDefault(nav, <SubNav {...nav} />)}
<main
id="nd-docs-layout"
@@ -84,39 +111,25 @@ export function DocsLayout({
{ enabled: sidebarEnabled, component: sidebarReplace },
<Aside
{...sidebar}
items={finalLinks}
items={links}
banner={
<>
<div className="flex flex-row items-center justify-between border-b pb-2 max-md:hidden">
<Link
href={nav?.url ?? '/'}
className="inline-flex items-center gap-2.5 font-medium"
>
{nav?.title}
</Link>
{finalLinks.length > 0 && (
<LinksMenu
items={finalLinks}
className={cn(
buttonVariants({
size: 'icon',
color: 'ghost',
}),
)}
>
<MoreHorizontal />
</LinksMenu>
)}
</div>
{sidebar.banner}
</>
banner.length > 0 || sidebar.banner ? (
<>
{banner.length > 0 ? (
<div className="flex flex-row items-center border-b pb-2 max-md:hidden">
{banner}
</div>
) : null}
{sidebar.banner}
</>
) : null
}
bannerProps={{
...sidebar.bannerProps,
className: cn(
!sidebar.banner && 'max-md:hidden',
sidebar.bannerProps?.className,
),
...sidebar.bannerProps,
}}
footer={
<>
@@ -127,7 +140,7 @@ export function DocsLayout({
}
/>,
)}
{children}
{props.children}
</main>
</TreeContextProvider>
);
+1 -1
View File
@@ -93,7 +93,7 @@ export function DocsPage({
</div>
<article
className={cn(
'mx-auto flex w-full max-w-[840px] flex-1 flex-col gap-6 px-4 pt-10 md:px-6 md:pt-12',
'mx-auto flex w-full max-w-[860px] flex-1 flex-col gap-6 px-4 pt-10 md:px-6 md:pt-12',
!tocOptions.enabled && 'max-w-[1200px]',
)}
>
+36 -2
View File
@@ -27,6 +27,13 @@ interface DocsUIOptions {
*/
modifyContainer?: boolean;
/**
* Max width of docs layout
*
* @defaultValue '1600px'
*/
layoutWidth?: string;
/**
* Color preset
*/
@@ -108,13 +115,22 @@ function createTailwindColors(
}
export const docsUi = plugin.withOptions<DocsUIOptions>(
({ cssPrefix = '', preset = 'default' } = {}) => {
({ cssPrefix = '', preset = 'default', layoutWidth = '1600px' } = {}) => {
return ({ addBase, addComponents, addUtilities }) => {
const { light, dark, css } =
typeof preset === 'string' ? presets[preset] : preset;
addBase({
':root': getThemeStyles(cssPrefix, light),
':root': {
...getThemeStyles(cssPrefix, light),
'--fd-sidebar-width': '0px',
'--fd-toc-width': '0px',
'--fd-layout-width': layoutWidth,
// computed
'--fd-doc':
'calc(min(100vw, var(--fd-layout-width)) - var(--fd-toc-width) - var(--fd-sidebar-width))',
'--fd-c-sidebar': 'calc(50vw - var(--fd-doc))',
},
'.dark': getThemeStyles(cssPrefix, dark),
'*': {
'border-color': `theme('colors.fd-border')`,
@@ -123,6 +139,19 @@ export const docsUi = plugin.withOptions<DocsUIOptions>(
'background-color': `theme('colors.fd-background')`,
color: `theme('colors.fd-foreground')`,
},
'@screen md': {
':root': { '--fd-sidebar-width': '240px' },
},
'@screen lg': {
':root': {
'--fd-toc-width': '240px',
'--fd-c-toc': 'calc(50vw - var(--fd-doc) / 2)',
'--fd-c-sidebar': 'calc(50vw - var(--fd-doc) / 2)',
},
},
'@screen xl': {
':root': { '--fd-sidebar-width': '260px', '--fd-toc-width': '260px' },
},
});
if (css) addComponents(css);
@@ -214,6 +243,11 @@ export const docsUi = plugin.withOptions<DocsUIOptions>(
maxWidth: {
container: '1400px',
},
margin: {
// the offset given to docs content when the sidebar is collapsed
'fd-sidebar-offset':
'max(calc(var(--fd-c-sidebar) - 2 * var(--fd-sidebar-width)), var(--fd-sidebar-width) * -1)',
},
colors: createTailwindColors(cssPrefix, addGlobalColors),
...animations,
},