mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
Core: Rename sidebar primitive minWidth prop to blockScrollingWidth
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'fumadocs-core': minor
|
||||
'fumadocs-ui': minor
|
||||
---
|
||||
|
||||
Rename sidebar primitive `minWidth` prop to `blockScrollingWidth`
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,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';
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user