mirror of
https://github.com/LibreChat-AI/librechat.ai.git
synced 2026-03-27 02:38:32 +07:00
fix(a11y): add missing ARIA attributes and landmark regions to docs (#535)
Patch fumadocs-ui to fix accessibility issues reported by amberhinds, plus additional issues found via WCAG 2.1 audit: Reported issues: - #513: Add aria-label to docs navigation toggle button - #514: Add aria-haspopup="dialog" and aria-label to search buttons - #515: Fix close search button accessible name to include visible text - #516: Add role="status" live region for screen reader search announcements - #517: Change search suggestions from <button> to <a> links with role="option" - #518: Add nav landmarks for sidebar, breadcrumbs, TOC, and pagination Additional fixes found via audit: - Theme toggle button: add aria-label="Toggle theme" (WCAG 4.1.2) - Sidebar <aside>: add aria-label="Docs sidebar" (WCAG 4.1.2) - Collapse sidebar button: dynamic label based on state (WCAG 4.1.2) - Heading anchor link icons: add peer-focus-visible:opacity-100 (WCAG 2.4.7) - Breadcrumbs: use <ol>/<li> structure, aria-hidden on separator SVGs (WCAG 1.3.1) - Feedback textarea: add aria-label (WCAG 4.1.2) Closes #513 Closes #514 Closes #515 Closes #516 Closes #517 Closes #518
This commit is contained in:
@@ -122,6 +122,7 @@ export function Feedback() {
|
||||
<form className="flex flex-col gap-3" onSubmit={submit}>
|
||||
<textarea
|
||||
autoFocus
|
||||
aria-label="Additional feedback"
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
className="resize-none rounded-lg border border-fd-border bg-fd-secondary p-3 text-sm text-fd-secondary-foreground placeholder:text-fd-muted-foreground focus-visible:outline-none"
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
"homepage": "https://www.librechat.ai",
|
||||
"dependencies": {
|
||||
"@glidejs/glide": "^3.6.0",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@mdx-js/loader": "^3.1.1",
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-avatar": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
@@ -69,8 +69,8 @@
|
||||
"@eslint/js": "^9",
|
||||
"@next/bundle-analyzer": "^15.1.4",
|
||||
"@next/eslint-plugin-next": "^15",
|
||||
"@types/mdx": "^2.0.13",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@types/mdx": "^2.0.13",
|
||||
"@types/node": "18.16.0",
|
||||
"@types/react": "^18.3.12",
|
||||
"autoprefixer": "^10.4.19",
|
||||
@@ -103,6 +103,9 @@
|
||||
"diff": ">=5.2.2",
|
||||
"minimatch": ">=3.1.4",
|
||||
"serialize-javascript": ">=7.0.3"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"fumadocs-ui@14.7.7": "patches/fumadocs-ui@14.7.7.patch"
|
||||
}
|
||||
},
|
||||
"nextBundleAnalysis": {
|
||||
|
||||
192
patches/fumadocs-ui@14.7.7.patch
Normal file
192
patches/fumadocs-ui@14.7.7.patch
Normal file
@@ -0,0 +1,192 @@
|
||||
diff --git a/dist/components/dialog/search.js b/dist/components/dialog/search.js
|
||||
index 862cb52de96b0cdea5ddc9aab1acf9ea54c66a9f..0e62869413b468297a50a4e8a2da4c73c756d63d 100644
|
||||
--- a/dist/components/dialog/search.js
|
||||
+++ b/dist/components/dialog/search.js
|
||||
@@ -17,12 +17,25 @@ export function SearchDialog({ open, onOpenChange, footer, links = [], search, o
|
||||
content: name,
|
||||
url: link,
|
||||
})), [links]);
|
||||
- return (_jsxs(Dialog, { open: open, onOpenChange: onOpenChange, children: [_jsx(DialogOverlay, { className: "fixed inset-0 z-50 bg-black/30 backdrop-blur-sm data-[state=closed]:animate-fd-fade-out data-[state=open]:animate-fd-fade-in" }), _jsxs(DialogContent, { "aria-describedby": undefined, className: "fixed left-1/2 top-[10vh] z-50 w-[98vw] max-w-screen-sm origin-left -translate-x-1/2 rounded-lg border bg-fd-popover text-fd-popover-foreground shadow-lg data-[state=closed]:animate-fd-dialog-out data-[state=open]:animate-fd-dialog-in", children: [_jsx(DialogTitle, { className: "hidden", children: text.search }), _jsxs("div", { className: "flex flex-row items-center gap-2 px-3", children: [_jsx(LoadingIndicator, { isLoading: isLoading ?? false }), _jsx("input", { value: search, onChange: (e) => {
|
||||
+ const displayItems = props.results === 'empty' ? defaultItems : props.results;
|
||||
+ const statusMessage = useMemo(() => {
|
||||
+ if (props.results === 'empty' && defaultItems.length > 0) {
|
||||
+ return `${defaultItems.length} suggestion${defaultItems.length === 1 ? '' : 's'} available. Use the up and down arrow keys to browse.`;
|
||||
+ }
|
||||
+ if (props.results !== 'empty' && props.results.length > 0) {
|
||||
+ return `${props.results.length} result${props.results.length === 1 ? '' : 's'} available. Use the up and down arrow keys to browse.`;
|
||||
+ }
|
||||
+ if (props.results !== 'empty' && props.results.length === 0) {
|
||||
+ return text.searchNoResult;
|
||||
+ }
|
||||
+ return '';
|
||||
+ }, [props.results, defaultItems, text.searchNoResult]);
|
||||
+ return (_jsxs(Dialog, { open: open, onOpenChange: onOpenChange, children: [_jsx(DialogOverlay, { className: "fixed inset-0 z-50 bg-black/30 backdrop-blur-sm data-[state=closed]:animate-fd-fade-out data-[state=open]:animate-fd-fade-in" }), _jsxs(DialogContent, { "aria-describedby": undefined, className: "fixed left-1/2 top-[10vh] z-50 w-[98vw] max-w-screen-sm origin-left -translate-x-1/2 rounded-lg border bg-fd-popover text-fd-popover-foreground shadow-lg data-[state=closed]:animate-fd-dialog-out data-[state=open]:animate-fd-dialog-in", children: [_jsx(DialogTitle, { className: "hidden", children: text.search }), _jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", className: "sr-only", style: { position: 'absolute', width: '1px', height: '1px', padding: 0, margin: '-1px', overflow: 'hidden', clip: 'rect(0, 0, 0, 0)', whiteSpace: 'nowrap', borderWidth: 0 }, children: statusMessage }), _jsxs("div", { className: "flex flex-row items-center gap-2 px-3", children: [_jsx(LoadingIndicator, { isLoading: isLoading ?? false }), _jsx("input", { value: search, onChange: (e) => {
|
||||
onSearchChange(e.target.value);
|
||||
- }, placeholder: text.search, className: "w-0 flex-1 bg-transparent py-3 text-base placeholder:text-fd-muted-foreground focus-visible:outline-none" }), _jsx("button", { type: "button", "aria-label": "Close Search", onClick: () => onOpenChange(false), className: cn(buttonVariants({
|
||||
+ }, placeholder: text.search, className: "w-0 flex-1 bg-transparent py-3 text-base placeholder:text-fd-muted-foreground focus-visible:outline-none" }), _jsx("button", { type: "button", "aria-label": "Close Search (Esc)", onClick: () => onOpenChange(false), className: cn(buttonVariants({
|
||||
color: 'outline',
|
||||
className: 'text-xs p-1.5',
|
||||
- })), children: "Esc" })] }), props.results !== 'empty' || defaultItems.length > 0 ? (_jsx(SearchResults, { items: props.results === 'empty' ? defaultItems : props.results, onSelect: () => onOpenChange(false) })) : null, footer ? (_jsx("div", { className: "mt-auto flex flex-col border-t p-3", children: footer })) : null] })] }));
|
||||
+ })), children: "Esc" })] }), props.results !== 'empty' || displayItems.length > 0 ? (_jsx(SearchResults, { items: displayItems, onSelect: () => onOpenChange(false) })) : null, footer ? (_jsx("div", { className: "mt-auto flex flex-col border-t p-3", children: footer })) : null] })] }));
|
||||
}
|
||||
const icons = {
|
||||
text: _jsx(Text, { className: "size-4 text-fd-muted-foreground" }),
|
||||
@@ -69,21 +82,22 @@ function SearchResults({ items, onSelect, ...props }) {
|
||||
window.removeEventListener('keydown', listener);
|
||||
};
|
||||
}, []);
|
||||
- return (_jsxs("div", { ...props, className: cn('flex max-h-[460px] flex-col overflow-y-auto border-t p-2', props.className), children: [items.length === 0 ? (_jsx("div", { className: "py-12 text-center text-sm", children: text.searchNoResult })) : null, items.map((item) => (_jsxs(CommandItem, { value: item.id, active: active, onActiveChange: setActive, onClick: () => {
|
||||
+ return (_jsxs("div", { ...props, role: "listbox", className: cn('flex max-h-[460px] flex-col overflow-y-auto border-t p-2', props.className), children: [items.length === 0 ? (_jsx("div", { className: "py-12 text-center text-sm", children: text.searchNoResult })) : null, items.map((item) => (_jsxs(CommandItem, { value: item.id, active: active, onActiveChange: setActive, href: item.url, onClick: (e) => {
|
||||
+ e.preventDefault();
|
||||
onOpen(item.url);
|
||||
}, children: [item.type !== 'page' ? (_jsx("div", { role: "none", className: "ms-2 h-full min-h-10 w-px bg-fd-border" })) : null, icons[item.type], _jsx("p", { className: "w-0 flex-1 truncate", children: item.content })] }, item.id)))] }));
|
||||
}
|
||||
function LoadingIndicator({ isLoading }) {
|
||||
return (_jsxs("div", { className: "relative size-4", children: [_jsx(Loader2, { className: cn('absolute size-full animate-spin text-fd-primary transition-opacity', !isLoading && 'opacity-0') }), _jsx(SearchIcon, { className: cn('absolute size-full text-fd-muted-foreground transition-opacity', isLoading && 'opacity-0') })] }));
|
||||
}
|
||||
-function CommandItem({ active, onActiveChange, value, ...props }) {
|
||||
- return (_jsx("button", { ref: useCallback((element) => {
|
||||
+function CommandItem({ active, onActiveChange, value, href, ...props }) {
|
||||
+ return (_jsx("a", { ref: useCallback((element) => {
|
||||
if (active === value && element) {
|
||||
element.scrollIntoView({
|
||||
block: 'nearest',
|
||||
});
|
||||
}
|
||||
- }, [active, value]), type: "button", "aria-selected": active === value, onPointerMove: () => onActiveChange(value), ...props, className: cn('flex min-h-10 select-none flex-row items-center gap-2.5 rounded-lg px-2 text-start text-sm', active === value && 'bg-fd-accent text-fd-accent-foreground', props.className), children: props.children }));
|
||||
+ }, [active, value]), href: href, role: "option", "aria-selected": active === value, onPointerMove: () => onActiveChange(value), ...props, className: cn('flex min-h-10 select-none flex-row items-center gap-2.5 rounded-lg px-2 text-start text-sm no-underline', active === value && 'bg-fd-accent text-fd-accent-foreground', props.className), children: props.children }));
|
||||
}
|
||||
const itemVariants = cva('rounded-md border px-2 py-0.5 text-xs font-medium text-fd-muted-foreground transition-colors', {
|
||||
variants: {
|
||||
diff --git a/dist/components/heading.js b/dist/components/heading.js
|
||||
index 49bc76177f8edf726f97376c65a2b76aba5ced8f..075526ee809843633c33f597ad2a9c2c48b70de0 100644
|
||||
--- a/dist/components/heading.js
|
||||
+++ b/dist/components/heading.js
|
||||
@@ -5,5 +5,5 @@ export function Heading({ as, className, ...props }) {
|
||||
const As = as ?? 'h1';
|
||||
if (!props.id)
|
||||
return _jsx(As, { className: className, ...props });
|
||||
- return (_jsxs(As, { className: cn('flex scroll-m-28 flex-row items-center gap-2', className), ...props, children: [_jsx("a", { "data-card": "", href: `#${props.id}`, className: "peer", children: props.children }), _jsx(Link, { "aria-label": "Link to section", className: "size-3.5 shrink-0 text-fd-muted-foreground opacity-0 transition-opacity peer-hover:opacity-100" })] }));
|
||||
+ return (_jsxs(As, { className: cn('flex scroll-m-28 flex-row items-center gap-2', className), ...props, children: [_jsx("a", { "data-card": "", href: `#${props.id}`, className: "peer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-ring rounded", children: props.children }), _jsx(Link, { "aria-label": "Link to section", className: "size-3.5 shrink-0 text-fd-muted-foreground opacity-0 transition-opacity peer-hover:opacity-100 peer-focus-visible:opacity-100" })] }));
|
||||
}
|
||||
diff --git a/dist/components/layout/search-toggle.js b/dist/components/layout/search-toggle.js
|
||||
index 81c8ad71277dc688710bcddb368e714e0bfce172..ac1b5517f743fba17026cf0aab787ef247783fdf 100644
|
||||
--- a/dist/components/layout/search-toggle.js
|
||||
+++ b/dist/components/layout/search-toggle.js
|
||||
@@ -12,7 +12,7 @@ export function SearchToggle({ hideIfDisabled, ...props }) {
|
||||
return (_jsx("button", { type: "button", className: cn(buttonVariants({
|
||||
size: 'icon',
|
||||
color: 'ghost',
|
||||
- }), props.className), "data-search": "", "aria-label": "Open Search", onClick: () => {
|
||||
+ }), props.className), "data-search": "", "aria-label": "Open Search", "aria-haspopup": "dialog", onClick: () => {
|
||||
setOpenSearch(true);
|
||||
}, children: _jsx(SearchIcon, {}) }));
|
||||
}
|
||||
@@ -21,7 +21,7 @@ export function LargeSearchToggle({ hideIfDisabled, ...props }) {
|
||||
const { text } = useI18n();
|
||||
if (hideIfDisabled && !enabled)
|
||||
return null;
|
||||
- return (_jsxs("button", { type: "button", "data-search-full": "", ...props, className: cn('inline-flex items-center gap-2 rounded-full border bg-fd-secondary/50 p-1.5 text-sm text-fd-muted-foreground transition-colors hover:bg-fd-accent hover:text-fd-accent-foreground', props.className), onClick: () => {
|
||||
+ return (_jsxs("button", { type: "button", "data-search-full": "", ...props, "aria-haspopup": "dialog", "aria-label": "Search", className: cn('inline-flex items-center gap-2 rounded-full border bg-fd-secondary/50 p-1.5 text-sm text-fd-muted-foreground transition-colors hover:bg-fd-accent hover:text-fd-accent-foreground', props.className), onClick: () => {
|
||||
setOpenSearch(true);
|
||||
}, children: [_jsx(SearchIcon, { className: "ms-1 size-4" }), text.search, _jsx("div", { className: "ms-auto inline-flex gap-0.5", children: hotKey.map((k, i) => (_jsx("kbd", { className: "rounded-md border bg-fd-background px-1.5", children: k.display }, i))) })] }));
|
||||
}
|
||||
diff --git a/dist/components/layout/theme-toggle.js b/dist/components/layout/theme-toggle.js
|
||||
index f2a453e1e080471f918c10622444d8923a1977fc..a5a961d828f0f28402ac20921c555cc6269c43bb 100644
|
||||
--- a/dist/components/layout/theme-toggle.js
|
||||
+++ b/dist/components/layout/theme-toggle.js
|
||||
@@ -22,7 +22,7 @@ export function ThemeToggle({ className, mode = 'light-dark', ...props }) {
|
||||
const container = cn('inline-flex items-center rounded-full border p-[3px]', className);
|
||||
if (mode === 'light-dark') {
|
||||
const value = mounted ? resolvedTheme : null;
|
||||
- return (_jsxs("button", { className: container, onClick: () => setTheme(value === 'light' ? 'dark' : 'light'), "data-theme-toggle": "", ...props, children: [_jsx(Sun, { className: cn(itemVariants({ active: value === 'light' })) }), _jsx(Moon, { className: cn(itemVariants({ active: value === 'dark' })) })] }));
|
||||
+ return (_jsxs("button", { className: container, "aria-label": "Toggle theme", onClick: () => setTheme(value === 'light' ? 'dark' : 'light'), "data-theme-toggle": "", ...props, children: [_jsx(Sun, { className: cn(itemVariants({ active: value === 'light' })) }), _jsx(Moon, { className: cn(itemVariants({ active: value === 'dark' })) })] }));
|
||||
}
|
||||
const value = mounted ? theme : null;
|
||||
return (_jsx("div", { className: container, "data-theme-toggle": "", ...props, children: [
|
||||
diff --git a/dist/components/layout/toc.js b/dist/components/layout/toc.js
|
||||
index ca2c0deacb9c038162b5e49483d879d21cf54a58..66e89f74e3a3c509962d5431377e2ca74cb93c4f 100644
|
||||
--- a/dist/components/layout/toc.js
|
||||
+++ b/dist/components/layout/toc.js
|
||||
@@ -11,7 +11,7 @@ import { ChevronRight, Text } from 'lucide-react';
|
||||
import { usePageStyles } from '../../contexts/layout';
|
||||
export function Toc(props) {
|
||||
const { toc } = usePageStyles();
|
||||
- return (_jsx("div", { id: "nd-toc", ...props, className: cn('sticky top-fd-layout-top h-[var(--fd-toc-height)] pb-2 pt-12', toc, props.className), style: {
|
||||
+ return (_jsx("nav", { id: "nd-toc", "aria-label": "Table of Contents", ...props, className: cn('sticky top-fd-layout-top h-[var(--fd-toc-height)] pb-2 pt-12', toc, props.className), style: {
|
||||
...props.style,
|
||||
'--fd-toc-height': 'calc(100dvh - var(--fd-banner-height) - var(--fd-nav-height))',
|
||||
}, children: _jsx("div", { className: "flex h-full w-[var(--fd-toc-width)] max-w-full flex-col gap-3 pe-4", children: props.children }) }));
|
||||
diff --git a/dist/layouts/docs/sidebar.js b/dist/layouts/docs/sidebar.js
|
||||
index e88fea6a38c601f51646dc3668e934007c8c0986..1a8bfeabd7d861a2ec59e546a7927abc56bf7e55 100644
|
||||
--- a/dist/layouts/docs/sidebar.js
|
||||
+++ b/dist/layouts/docs/sidebar.js
|
||||
@@ -62,7 +62,7 @@ export function Sidebar({ defaultOpenLevel = 0, prefetch = true, inner, ...props
|
||||
prefetch,
|
||||
};
|
||||
}, [defaultOpenLevel, prefetch]);
|
||||
- return (_jsx(Context.Provider, { value: context, children: _jsx(Base.SidebarList, { id: "nd-sidebar", blockScrollingWidth: 768, ...props, className: cn('fixed top-fd-layout-top z-30 bg-fd-card text-sm md:sticky md:h-[var(--fd-sidebar-height)]', 'max-md:inset-x-0 max-md:bottom-0 max-md:bg-fd-background/80 max-md:text-[15px] max-md:backdrop-blur-lg max-md:data-[open=false]:invisible', props.className), style: {
|
||||
+ return (_jsx(Context.Provider, { value: context, children: _jsx(Base.SidebarList, { id: "nd-sidebar", "aria-label": "Docs sidebar", blockScrollingWidth: 768, ...props, className: cn('fixed top-fd-layout-top z-30 bg-fd-card text-sm md:sticky md:h-[var(--fd-sidebar-height)]', 'max-md:inset-x-0 max-md:bottom-0 max-md:bg-fd-background/80 max-md:text-[15px] max-md:backdrop-blur-lg max-md:data-[open=false]:invisible', props.className), style: {
|
||||
...props.style,
|
||||
'--fd-sidebar-height': 'calc(100dvh - var(--fd-banner-height) - var(--fd-nav-height))',
|
||||
}, children: _jsx("div", { ...inner, className: cn('flex size-full max-w-full flex-col pt-2 md:ms-auto md:w-[var(--fd-sidebar-width)] md:border-e md:pt-4', inner?.className), children: props.children }) }) }));
|
||||
@@ -119,7 +119,7 @@ export function SidebarFolderContent(props) {
|
||||
}
|
||||
export function SidebarCollapseTrigger(props) {
|
||||
const { collapsed, setCollapsed } = useSidebar();
|
||||
- return (_jsx("button", { type: "button", "aria-label": "Collapse Sidebar", "data-collapsed": collapsed, ...props, className: cn(buttonVariants({
|
||||
+ return (_jsx("button", { type: "button", "aria-label": collapsed ? "Expand Sidebar" : "Collapse Sidebar", "data-collapsed": collapsed, ...props, className: cn(buttonVariants({
|
||||
color: 'ghost',
|
||||
size: 'icon',
|
||||
}), props.className), onClick: () => {
|
||||
@@ -143,7 +143,7 @@ function useInternalContext() {
|
||||
*/
|
||||
export function SidebarPageTree(props) {
|
||||
const { root } = useTreeContext();
|
||||
- return useMemo(() => {
|
||||
+ return _jsx("nav", { "aria-label": "Docs", children: useMemo(() => {
|
||||
const { Separator, Item, Folder } = props.components ?? {};
|
||||
function renderSidebarList(items, level) {
|
||||
return items.map((item, i) => {
|
||||
@@ -165,7 +165,7 @@ export function SidebarPageTree(props) {
|
||||
});
|
||||
}
|
||||
return renderSidebarList(root.children, 1);
|
||||
- }, [root, props.components]);
|
||||
+ }, [root, props.components]) });
|
||||
}
|
||||
function PageTreeFolder({ item, children, level, }) {
|
||||
const { defaultOpenLevel } = useInternalContext();
|
||||
diff --git a/dist/layouts/docs.js b/dist/layouts/docs.js
|
||||
index 1157b228b4cbed85c9e23d292f20ef1694349ab7..eaefa72dd598b0118d54ce29d37dd3654dfd10d2 100644
|
||||
--- a/dist/layouts/docs.js
|
||||
+++ b/dist/layouts/docs.js
|
||||
@@ -44,7 +44,7 @@ function SidebarHeaderItems({ links, ...props }) {
|
||||
const isEmpty = !props.title && !props.children && links.length === 0;
|
||||
if (isEmpty)
|
||||
return null;
|
||||
- return (_jsxs("div", { className: "flex flex-row items-center max-md:hidden", children: [props.title ? (_jsx(Link, { href: props.url ?? '/', className: "inline-flex items-center gap-2.5 py-1 font-medium", children: props.title })) : null, props.children, links.length > 0 ? (_jsx(LinksMenu, { items: links, className: cn(buttonVariants({
|
||||
+ return (_jsxs("div", { className: "flex flex-row items-center max-md:hidden", children: [props.title ? (_jsx(Link, { href: props.url ?? '/', className: "inline-flex items-center gap-2.5 py-1 font-medium", children: props.title })) : null, props.children, links.length > 0 ? (_jsx(LinksMenu, { items: links, "aria-label": "LibreChat website navigation", className: cn(buttonVariants({
|
||||
size: 'icon',
|
||||
color: 'ghost',
|
||||
}), 'ms-auto'), children: _jsx(MoreHorizontal, {}) })) : null] }));
|
||||
diff --git a/dist/page.client.js b/dist/page.client.js
|
||||
index 9531d4ce91915ae3c2f4141d656a592a6d48bd88..c1d3d6ba1e79be23f463e894b3970fe811bf43f6 100644
|
||||
--- a/dist/page.client.js
|
||||
+++ b/dist/page.client.js
|
||||
@@ -76,7 +76,7 @@ export function Footer({ items }) {
|
||||
next: list[idx + 1],
|
||||
};
|
||||
}, [items, pathname, root]);
|
||||
- return (_jsxs("div", { className: "grid grid-cols-2 gap-4 pb-6", children: [previous ? (_jsxs(Link, { href: previous.url, className: cn(itemVariants()), children: [_jsxs("div", { className: cn(itemLabel()), children: [_jsx(ChevronLeft, { className: "-ms-1 size-4 shrink-0 rtl:rotate-180" }), _jsx("p", { children: text.previousPage })] }), _jsx("p", { className: "font-medium", children: previous.name })] })) : null, next ? (_jsxs(Link, { href: next.url, className: cn(itemVariants({ className: 'col-start-2 text-end' })), children: [_jsxs("div", { className: cn(itemLabel({ className: 'flex-row-reverse' })), children: [_jsx(ChevronRight, { className: "-me-1 size-4 shrink-0 rtl:rotate-180" }), _jsx("p", { children: text.nextPage })] }), _jsx("p", { className: "font-medium", children: next.name })] })) : null] }));
|
||||
+ return (_jsx("nav", { "aria-label": "Pagination", children: _jsxs("div", { className: "grid grid-cols-2 gap-4 pb-6", children: [previous ? (_jsxs(Link, { href: previous.url, className: cn(itemVariants()), children: [_jsxs("div", { className: cn(itemLabel()), children: [_jsx(ChevronLeft, { className: "-ms-1 size-4 shrink-0 rtl:rotate-180" }), _jsx("p", { children: text.previousPage })] }), _jsx("p", { className: "font-medium", children: previous.name })] })) : null, next ? (_jsxs(Link, { href: next.url, className: cn(itemVariants({ className: 'col-start-2 text-end' })), children: [_jsxs("div", { className: cn(itemLabel({ className: 'flex-row-reverse' })), children: [_jsx(ChevronRight, { className: "-me-1 size-4 shrink-0 rtl:rotate-180" }), _jsx("p", { children: text.nextPage })] }), _jsx("p", { className: "font-medium", children: next.name })] })) : null] }) }));
|
||||
}
|
||||
export function Breadcrumb(options) {
|
||||
const path = useTreePath();
|
||||
@@ -89,5 +89,5 @@ export function Breadcrumb(options) {
|
||||
}, [options, path, root]);
|
||||
if (items.length === 0)
|
||||
return null;
|
||||
- return (_jsx("div", { className: "-mb-3 flex flex-row items-center gap-1 text-sm font-medium text-fd-muted-foreground", children: items.map((item, i) => (_jsxs(Fragment, { children: [i !== 0 && (_jsx(ChevronRight, { className: "size-4 shrink-0 rtl:rotate-180" })), item.url ? (_jsx(Link, { href: item.url, className: "truncate hover:text-fd-accent-foreground", children: item.name })) : (_jsx("span", { className: "truncate", children: item.name }))] }, i))) }));
|
||||
+ return (_jsx("nav", { "aria-label": "Breadcrumbs", children: _jsx("ol", { className: "-mb-3 flex flex-row items-center gap-1 text-sm font-medium text-fd-muted-foreground", children: items.map((item, i) => (_jsx("li", { className: "inline-flex items-center gap-1", children: _jsxs(Fragment, { children: [i !== 0 && (_jsx(ChevronRight, { "aria-hidden": "true", className: "size-4 shrink-0 rtl:rotate-180" })), item.url ? (_jsx(Link, { href: item.url, className: "truncate hover:text-fd-accent-foreground", children: item.name })) : (_jsx("span", { className: "truncate", children: item.name }))] }) }, i))) }) }));
|
||||
}
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -12,6 +12,11 @@ overrides:
|
||||
minimatch: '>=3.1.4'
|
||||
serialize-javascript: '>=7.0.3'
|
||||
|
||||
patchedDependencies:
|
||||
fumadocs-ui@14.7.7:
|
||||
hash: eug5cntrgxatea7cgl6ypztoau
|
||||
path: patches/fumadocs-ui@14.7.7.patch
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
@@ -96,7 +101,7 @@ importers:
|
||||
version: 11.10.1(fumadocs-core@14.7.7(@types/react@18.3.27)(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
fumadocs-ui:
|
||||
specifier: 14.7.7
|
||||
version: 14.7.7(@types/react@18.3.27)(fumadocs-core@14.7.7(@types/react@18.3.27)(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.18(yaml@2.8.2))
|
||||
version: 14.7.7(patch_hash=eug5cntrgxatea7cgl6ypztoau)(@types/react@18.3.27)(fumadocs-core@14.7.7(@types/react@18.3.27)(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.18(yaml@2.8.2))
|
||||
geist:
|
||||
specifier: ^1.3.1
|
||||
version: 1.5.1(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
|
||||
@@ -6422,7 +6427,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
fumadocs-ui@14.7.7(@types/react@18.3.27)(fumadocs-core@14.7.7(@types/react@18.3.27)(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.18(yaml@2.8.2)):
|
||||
fumadocs-ui@14.7.7(patch_hash=eug5cntrgxatea7cgl6ypztoau)(@types/react@18.3.27)(fumadocs-core@14.7.7(@types/react@18.3.27)(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.18(yaml@2.8.2)):
|
||||
dependencies:
|
||||
'@radix-ui/react-accordion': 1.2.12(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-collapsible': 1.1.12(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
||||
Reference in New Issue
Block a user