Documentation generator for React component libraries with live demos, API tables, and theming support
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
React hooks and components for building custom themes and accessing site data, navigation, and component metadata.
Access site-wide data and metadata through React hooks.
/**
* Access site data context including configuration and metadata
* @returns Site data object with theme config and global settings
*/
function useSiteData(): {
themeConfig: IThemeConfig;
components: Record<string, AtomComponentAsset>;
locales: ILocalesConfig;
_2_level_nav_available: boolean;
[key: string]: any;
};
/**
* Get component asset metadata for current route
* @returns Asset metadata for components, functions, and other atoms
*/
function useAtomAssets(): {
components: Record<string, AtomComponentAsset>;
};
/**
* Get navigation data for the current locale
* @returns Navigation items array with titles, links, and hierarchy
*/
function useNavData(): INavItems;
/**
* Get sidebar data for the current route
* @returns Sidebar groups and items for current location
*/
function useSidebarData(): ISidebarGroup[];
/**
* Get full sidebar data including all routes
* @returns Complete sidebar structure for entire site
*/
function useFullSidebarData(): Record<string, ISidebarGroup[]>;
/**
* Get current route metadata including frontmatter and content
* @returns Route metadata with frontmatter, TOC, and text content
*/
function useRouteMeta(): IRouteMeta;
/**
* Get matched route information
* @returns Current matched route object
*/
function useMatchedRoute(): {
id: string;
path: string;
parentId?: string;
[key: string]: any;
};Usage:
import {
useSiteData,
useNavData,
useSidebarData,
useRouteMeta
} from "dumi/theme";
function CustomHeader() {
const { themeConfig } = useSiteData();
const nav = useNavData();
return (
<header>
<h1>{themeConfig.name}</h1>
<nav>
{nav.map(item => (
<a key={item.link} href={item.link}>
{item.title}
</a>
))}
</nav>
</header>
);
}
function CustomSidebar() {
const sidebar = useSidebarData();
const routeMeta = useRouteMeta();
return (
<aside>
<h2>{routeMeta.frontmatter.title}</h2>
{sidebar.map(group => (
<div key={group.title}>
<h3>{group.title}</h3>
{group.children.map(item => (
<a key={item.link} href={item.link}>
{item.title}
</a>
))}
</div>
))}
</aside>
);
}Manage user interface state and preferences.
/**
* Get current locale information
* @returns Current locale data
*/
function useLocale(): ILocale;
/**
* Get color scheme preference and switching utilities
* @returns Current color scheme and toggle function
*/
function usePrefersColor(): {
color: 'light' | 'dark' | 'auto';
setColor: (color: 'light' | 'dark' | 'auto') => void;
};
/**
* Get tab metadata for tabbed content pages
* @returns Current tab information and switching utilities
*/
function useTabMeta(): {
tab: string;
setTab: (tab: string) => void;
tabs: {
key: string;
title?: string;
titleIntlId?: string;
components: {
default: ReactComponentType;
Extra: ReactComponentType;
Action: ReactComponentType;
};
}[];
};Usage:
import { useLocale, usePrefersColor, useTabMeta } from "dumi/theme";
function LocaleDisplay() {
const locale = useLocale();
return (
<div>
<span>Current locale: {locale.name} ({locale.id})</span>
</div>
);
}
function ColorModeToggle() {
const { color, setColor } = usePrefersColor();
return (
<button onClick={() => setColor(color === 'dark' ? 'light' : 'dark')}>
{color === 'dark' ? '☀️' : '🌙'}
</button>
);
}Work with live demos and component rendering.
/**
* Get live demo compilation and rendering utilities
* @param id - Demo ID
* @param opts - Live demo options
* @returns Functions for compiling and rendering live code demos
*/
function useLiveDemo(id: string, opts?: {
containerRef?: RefObject<HTMLElement>;
iframe?: boolean;
}): {
/** Demo React node */
node: ReactNode;
/** Loading state */
loading: boolean;
/** Error state */
error: Error | null;
/** Set demo source code */
setSource: (source: Record<string, string>) => Promise<void>;
};
/**
* Get site search functionality and data
* @returns Search utilities and indexed content
*/
function useSiteSearch(): {
/** Search through site content */
search: (query: string) => Promise<Array<{
title: string;
content: string;
path: string;
}>>;
/** Current search results */
results: Array<any>;
/** Search loading state */
loading: boolean;
};Usage:
import { useLiveDemo, useSiteSearch } from "dumi/theme";
function LiveCodeEditor({ initialCode }: { initialCode: string }) {
const { renderDemo } = useLiveDemo();
const [code, setCode] = useState(initialCode);
const [demo, setDemo] = useState<React.ComponentType | null>(null);
useEffect(() => {
renderDemo(code, { filename: 'demo.tsx' })
.then(setDemo)
.catch(console.error);
}, [code, renderDemo]);
return (
<div>
<textarea
value={code}
onChange={(e) => setCode(e.target.value)}
/>
{demo && <demo />}
</div>
);
}Pre-built React components for common documentation needs.
/**
* Demo preview component with code display and live rendering
*/
interface DumiDemo {
(props: IPreviewerProps): JSX.Element;
}
interface IPreviewerProps {
/** Demo title */
title?: string;
/** Demo description */
description?: string;
/** Source filename */
filename?: string;
/** Render in iframe */
iframe?: boolean | number;
/** Debug mode flag */
debug?: boolean;
/** Show code by default */
defaultShowCode?: boolean;
/** Demo URL for standalone viewing */
demoUrl: string;
/** Disable padding */
compact?: boolean;
/** Apply transform for positioning */
transform?: boolean;
/** Background color */
background?: string;
/** Asset metadata */
asset: ExampleBlockAsset;
/** React children (the demo component) */
children: ReactNode;
}
/**
* Component for rendering component atoms/examples
*/
interface AtomRenderer {
(props: { atom: string }): JSX.Element;
}
/**
* Demo grid layout component
*/
interface DumiDemoGrid {
(props: {
items: IDemoData[];
cols?: number;
}): JSX.Element;
}
/**
* Page wrapper component
*/
interface DumiPage {
(props: {
children: ReactNode;
route: any;
}): JSX.Element;
}Usage:
import { DumiDemo, AtomRenderer, DumiDemoGrid } from "dumi/theme";
function CustomPreviewer(props: IPreviewerProps) {
return (
<div className="custom-demo">
<h3>{props.title}</h3>
<p>{props.description}</p>
<DumiDemo {...props} />
</div>
);
}
function ComponentShowcase({ atom }: { atom: string }) {
return (
<div className="showcase">
<AtomRenderer atom={atom} />
</div>
);
}Helper functions for external integrations and demo management.
/**
* Open current demo in CodeSandbox
* @param opts - Configuration for CodeSandbox integration
*/
function openCodeSandbox(opts: {
/** Demo code */
code: string;
/** Demo dependencies */
deps?: Record<string, string>;
/** Demo title */
title?: string;
/** Demo description */
description?: string;
}): void;
/**
* Open current demo in StackBlitz
* @param opts - Configuration for StackBlitz integration
*/
function openStackBlitz(opts: {
/** Demo code */
code: string;
/** Demo dependencies */
deps?: Record<string, string>;
/** Demo title */
title?: string;
/** Demo description */
description?: string;
}): void;Usage:
import { openCodeSandbox, openStackBlitz } from "dumi/theme";
function DemoActions({ code, deps }: {
code: string;
deps: Record<string, string>;
}) {
const handleOpenCodeSandbox = () => {
openCodeSandbox({
code,
deps,
title: "Component Demo",
description: "Generated from dumi",
});
};
const handleOpenStackBlitz = () => {
openStackBlitz({
code,
deps,
title: "Component Demo",
});
};
return (
<div>
<button onClick={handleOpenCodeSandbox}>
Open in CodeSandbox
</button>
<button onClick={handleOpenStackBlitz}>
Open in StackBlitz
</button>
</div>
);
}React-intl components for internationalized documentation.
// Re-exported from react-intl
function FormattedMessage(props: {
id: string;
defaultMessage?: string;
values?: Record<string, any>;
}): JSX.Element;
function FormattedDate(props: {
value: Date | number;
year?: 'numeric' | '2-digit';
month?: 'numeric' | '2-digit' | 'long' | 'short' | 'narrow';
day?: 'numeric' | '2-digit';
}): JSX.Element;
function FormattedNumber(props: {
value: number;
style?: 'decimal' | 'currency' | 'percent';
currency?: string;
}): JSX.Element;
function IntlProvider(props: {
locale: string;
messages: Record<string, string>;
children: ReactNode;
}): JSX.Element;
function useIntl(): {
formatMessage: (descriptor: { id: string; defaultMessage?: string }, values?: Record<string, any>) => string;
formatDate: (value: Date | number, options?: any) => string;
formatNumber: (value: number, options?: any) => string;
};Usage:
import { FormattedMessage, useIntl } from "dumi/theme";
function InternationalizedContent() {
const intl = useIntl();
return (
<div>
<h1>
<FormattedMessage
id="title"
defaultMessage="Welcome"
/>
</h1>
<p>
{intl.formatMessage(
{ id: "description", defaultMessage: "This is a demo" },
{ name: "Component" }
)}
</p>
</div>
);
}Create custom themes by organizing components and hooks:
// src/theme/components/Header.tsx
import { useSiteData, useNavData } from "dumi/theme";
export function Header() {
const { themeConfig } = useSiteData();
const nav = useNavData();
return (
<header>
<img src={themeConfig.logo} alt={themeConfig.name} />
<nav>
{nav.map(item => (
<a key={item.link} href={item.link}>
{item.title}
</a>
))}
</nav>
</header>
);
}Integrate with dumi's plugin system for advanced customization:
// Theme plugin that modifies theme data
export default (api: IApi) => {
api.modifyTheme((memo) => {
// Add custom components
memo.builtins.CustomComponent = require.resolve('./CustomComponent');
// Modify internationalization
memo.locales.en.messages.customKey = 'Custom Message';
return memo;
});
};Install with Tessl CLI
npx tessl i tessl/npm-dumi