Core: Rename sidebar primitive minWidth prop to blockScrollingWidth

This commit is contained in:
fuma-nama
2024-05-29 15:16:04 +08:00
parent 2a1211e77b
commit 917d87ff42
9 changed files with 121 additions and 66 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'fumadocs-core': minor
'fumadocs-ui': minor
---
Rename sidebar primitive `minWidth` prop to `blockScrollingWidth`
+3 -3
View File
@@ -101,10 +101,10 @@ Some common questions you may encounter.
The design system is built with Tailwind CSS, see [Themes](/docs/ui/theme).
Components are pre-built elements to enhance your documentation.
There are many pre-built components, and they are both customisable with exposed props like `style` and `className`.
Components are pre-built elements to enhance your documentation,
they are customisable with exposed props like `style` and `className`.
Blocks are essential elements of a docs, providing navigation and basic functionality.
Blocks are essential elements of docs, providing navigation and basic functionality.
We provided many options to customise them, see the **Blocks** section of the docs.
Although the docs didn't explicitly mention, you can always style the elements with CSS selectors.
+19
View File
@@ -75,6 +75,25 @@ To opt-out of document search, disable it from root provider.
</RootProvider>
```
### Hot Keys
You can customise the hot keys to trigger search dialog.
```mdx
<RootProvider
search={{
hotkey: [
{
display: 'K',
key: 'k', // key code, or a function determining whether the key is pressed
},
],
}}
>
{children}
</RootProvider>
```
## Algolia
For the setup guide, see [Integrate Algolia Search](/docs/headless/search/algolia). Make sure you have `algoliasearch` installed on your project.
+41 -27
View File
@@ -1,6 +1,15 @@
import { usePathname } from 'next/navigation';
import type { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';
import { createContext, useContext, useEffect, useState } from 'react';
import {
useCallback,
createContext,
useContext,
useEffect,
useState,
type ElementType,
type ReactNode,
type ReactElement,
type ComponentPropsWithoutRef,
} from 'react';
import { RemoveScroll } from 'react-remove-scroll';
const SidebarContext = createContext<SidebarContextType | undefined>(undefined);
@@ -41,72 +50,77 @@ export function SidebarProvider(
);
}
type WithAs<T extends ElementType, Extend = object> = Omit<
type AsProps<T extends ElementType> = Omit<
ComponentPropsWithoutRef<T>,
'as' | keyof Extend
> &
Extend & {
as?: T;
};
'as'
> & {
as?: T;
};
export type SidebarTriggerProps<T extends ElementType> = WithAs<T>;
export type SidebarTriggerProps<T extends ElementType> = AsProps<T>;
export function SidebarTrigger<T extends ElementType = 'button'>({
as,
...props
}: SidebarTriggerProps<T>): React.ReactElement {
}: SidebarTriggerProps<T>): ReactElement {
const [open, setOpen] = useSidebarContext();
const As = as ?? 'button';
return (
<As
aria-label="Toggle Sidebar"
data-open={open}
onClick={() => {
onClick={useCallback(() => {
setOpen(!open);
}}
}, [open, setOpen])}
{...props}
/>
);
}
export type SidebarContentProps<T extends ElementType> = WithAs<
T,
{
minWidth?: number;
}
>;
export type SidebarContentProps<T extends ElementType> = AsProps<T> & {
/**
* @deprecated Use `blockScrollingWidth` instead
*/
minWidth?: number;
/**
* Disable scroll blocking when the viewport width is larger than a certain number (in pixels)
*/
blockScrollingWidth?: number;
};
// todo: Change in next major
export function SidebarList<T extends ElementType = 'aside'>({
as,
minWidth,
blockScrollingWidth = minWidth,
...props
}: SidebarContentProps<T>): React.ReactElement {
}: SidebarContentProps<T>): ReactElement {
const [open] = useSidebarContext();
const [isMobileLayout, setIsMobileLayout] = useState(false);
const [isBlocking, setIsBlocking] = useState(false);
useEffect(() => {
if (minWidth === undefined) return;
if (!blockScrollingWidth) return;
const mediaQueryList = window.matchMedia(
`(min-width: ${minWidth.toString()}px)`,
`(min-width: ${blockScrollingWidth.toString()}px)`,
);
const handleChange = (): void => {
setIsMobileLayout(!mediaQueryList.matches);
setIsBlocking(!mediaQueryList.matches);
};
handleChange();
mediaQueryList.addEventListener('change', handleChange);
return () => {
mediaQueryList.removeEventListener('change', handleChange);
};
}, [minWidth]);
}, [blockScrollingWidth]);
return (
<RemoveScroll
as={as ?? 'aside'}
data-open={isMobileLayout ? open : false}
enabled={isMobileLayout ? open : false}
data-open={open}
enabled={isBlocking && open}
{...props}
>
{props.children}
+1 -1
View File
@@ -1,5 +1,5 @@
import path from 'node:path';
import { AnyZodObject, z, ZodError } from 'zod';
import type { AnyZodObject, z, ZodError } from 'zod';
import type { VirtualFile } from 'fumadocs-core/source';
import type { MDXExport, MDXPageData } from './types';
import { defaultSchemas } from './utils/schema';
+13 -13
View File
@@ -1,18 +1,18 @@
import { type PointerEventHandler, useCallback, useRef, useState } from 'react';
import { SidebarIcon } from 'lucide-react';
import { Sidebar, SidebarProps } from '@/components/sidebar';
import { Sidebar, type SidebarProps } from '@/components/sidebar';
import { cn } from '@/utils/cn';
import { buttonVariants } from '@/theme/variants';
import { useSidebarCollapse } from '@/contexts/sidebar';
import { useSidebar } from '@/contexts/sidebar';
export function DynamicSidebar(props: SidebarProps): React.ReactElement {
const [open, setOpen] = useSidebarCollapse();
const { collapsed, setCollapsed } = useSidebar();
const [hover, setHover] = useState(false);
const timerRef = useRef(0);
const onCollapse = useCallback(() => {
setOpen(!open);
}, [open, setOpen]);
setCollapsed(!collapsed);
}, [collapsed]);
const onHover: PointerEventHandler = useCallback((e) => {
if (e.pointerType === 'touch') return;
@@ -31,7 +31,7 @@ export function DynamicSidebar(props: SidebarProps): React.ReactElement {
return (
<>
{!open ? (
{collapsed ? (
<div
className="fixed bottom-0 start-0 top-16 max-md:hidden"
onPointerEnter={onHover}
@@ -43,10 +43,10 @@ export function DynamicSidebar(props: SidebarProps): React.ReactElement {
}}
/>
) : null}
{!open ? (
{collapsed ? (
<button
type="button"
aria-label="Trigger Sidebar"
aria-label="Collapse Sidebar"
className={cn(
buttonVariants({
color: 'secondary',
@@ -61,26 +61,26 @@ export function DynamicSidebar(props: SidebarProps): React.ReactElement {
) : null}
<div
id="dynamic-sidebar"
data-open={open}
data-open={!collapsed}
data-hover={hover}
onPointerEnter={onHover}
onPointerLeave={onLeave}
aria-hidden={!open && !hover}
aria-hidden={collapsed && !hover}
className={cn(
'z-40 transition-transform max-md:absolute',
!open &&
collapsed &&
'md:fixed md:bottom-2 md:start-2 md:top-16 md:overflow-hidden md:rounded-xl md:border md:bg-background md:shadow-md',
)}
>
<Sidebar
{...props}
className={cn(!open && 'md:h-full')}
className={cn(collapsed && 'md:h-full')}
footer={
<>
{props.footer}
<button
type="button"
aria-label="Trigger Sidebar"
aria-label="Collapse Sidebar"
className={cn(
buttonVariants({
color: 'ghost',
+2 -2
View File
@@ -95,7 +95,7 @@ export function Sidebar({
return (
<SidebarContext.Provider value={context}>
<Base.SidebarList
minWidth={768} // md
blockScrollingWidth={768} // md
className={cn(
'flex w-full flex-col text-[15px] md:sticky md:top-16 md:h-body md:w-[240px] md:text-sm xl:w-[260px]',
'max-md:fixed max-md:inset-0 max-md:z-40 max-md:pt-16 max-md:data-[open=false]:hidden',
@@ -210,7 +210,7 @@ function FolderNode({
}): React.ReactElement {
const { defaultOpenLevel } = useContext(SidebarContext);
const pathname = usePathname();
const active = index !== undefined && isActive(index.url, pathname, false);
const active = index && isActive(index.url, pathname, false);
const childActive = useMemo(
() => hasActive(children, pathname),
[children, pathname],
+31 -12
View File
@@ -1,27 +1,46 @@
import { createContext, useContext, useState, type ReactNode } from 'react';
import { createContext, useContext, useState, useMemo } from 'react';
import { SidebarProvider as BaseProvider } from 'fumadocs-core/sidebar';
type SidebarCollapseContext = [open: boolean, setOpen: (v: boolean) => void];
type SidebarCollapseContext = {
open: boolean;
setOpen: (v: boolean) => void;
collapsed: boolean;
setCollapsed: (v: boolean) => void;
};
const SidebarCollapseContext = createContext<
SidebarCollapseContext | undefined
>(undefined);
const SidebarContext = createContext<SidebarCollapseContext | undefined>(
undefined,
);
export function useSidebarCollapse(): SidebarCollapseContext {
const ctx = useContext(SidebarCollapseContext);
export function useSidebar(): SidebarCollapseContext {
const ctx = useContext(SidebarContext);
if (!ctx) throw new Error('Missing root provider');
return ctx;
}
export function SidebarCollapseProvider({
export function SidebarProvider({
children,
}: {
children: ReactNode;
children: React.ReactNode;
}): React.ReactElement {
const [open, setOpen] = useState(true);
const [collapsed, setCollapsed] = useState(false);
return (
<SidebarCollapseContext.Provider value={[open, setOpen]}>
{children}
</SidebarCollapseContext.Provider>
<SidebarContext.Provider
value={useMemo(
() => ({
open,
setOpen,
collapsed,
setCollapsed,
}),
[open, collapsed],
)}
>
<BaseProvider open={open} onOpenChange={setOpen}>
{children}
</BaseProvider>
</SidebarContext.Provider>
);
}
+5 -8
View File
@@ -1,12 +1,11 @@
'use client';
import { SidebarProvider } from 'fumadocs-core/sidebar';
import { ThemeProvider } from 'next-themes';
import { type ReactNode } from 'react';
import dynamic from 'next/dynamic';
import { DirectionProvider } from '@radix-ui/react-direction';
import type { ThemeProviderProps } from 'next-themes/dist/types';
import { SidebarCollapseProvider } from '@/contexts/sidebar';
import { SidebarProvider } from '@/contexts/sidebar';
import { DefaultSearchDialogProps } from '@/components/dialog/search-default';
import { SearchProvider, type SearchProviderProps } from './contexts/search';
@@ -68,9 +67,9 @@ export function RootProvider({
search,
}: RootProviderProps): React.ReactElement {
let body = (
<SidebarProvider>
<SidebarCollapseProvider>{children}</SidebarCollapseProvider>
</SidebarProvider>
<DirectionProvider dir={dir ?? 'ltr'}>
<SidebarProvider>{children}</SidebarProvider>
</DirectionProvider>
);
if (search?.enabled !== false)
@@ -93,12 +92,10 @@ export function RootProvider({
</ThemeProvider>
);
if (dir) body = <DirectionProvider dir={dir}>{body}</DirectionProvider>;
return body;
}
export { useI18n } from './contexts/i18n';
export { useSearchContext } from './contexts/search';
export { useSidebarCollapse } from './contexts/sidebar';
export { useSidebar } from './contexts/sidebar';
export { useTreeContext } from './contexts/tree';