Skip to content

Commit 4eeebf4

Browse files
committed
Refactor code structure for improved readability and maintainability
1 parent 1e9f9cf commit 4eeebf4

File tree

5 files changed

+607
-446
lines changed

5 files changed

+607
-446
lines changed

app/docs/[plugin]/[version]/[...slug]/page.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { Metadata } from 'next';
66
import { createCustomRelativeLink } from '@/lib/mdx-link';
77
import Link from 'next/link';
88
import { ViewOptions, generateMetadataForPlugin } from '@/app/docs/components';
9+
import { Footer } from '@/app/docs/components/footer'
910

1011
type CurrentPageProps = PageProps<'/docs/[plugin]/[version]/[...slug]'>;
1112

@@ -36,7 +37,7 @@ export default async function Page(props: CurrentPageProps) {
3637
const latestUpdate = page.data.lastUpdated ? new Date(page.data.lastUpdated) : null;
3738

3839
return (
39-
<DocsPage toc={page.data.toc} full={page.data.full}>
40+
<DocsPage toc={page.data.toc} full={page.data.full} footer={{ component: <Footer /> }}>
4041
<DocsTitle>{page.data.title}</DocsTitle>
4142
<DocsDescription>{page.data.description}</DocsDescription>
4243
{latestUpdate || githubRepoUrl ? (
@@ -100,4 +101,5 @@ export async function generateMetadata(props: CurrentPageProps): Promise<Metadat
100101
// ],
101102
// },
102103
// };
103-
}
104+
}
105+

app/docs/components/footer.tsx

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
'use client';
2+
3+
import { useFooterItems } from 'fumadocs-ui/utils/use-footer-items';
4+
import { usePathname } from 'fumadocs-core/framework';
5+
import Link from 'fumadocs-core/link';
6+
import type * as PageTree from 'fumadocs-core/page-tree';
7+
import { ChevronLeft, ChevronRight } from 'lucide-react';
8+
import { type ComponentProps, useMemo } from 'react';
9+
import { cn } from '@/lib/utils';
10+
import { useI18n } from 'fumadocs-ui/contexts/i18n';
11+
import { config } from '@/lib/config';
12+
import { Badge } from '@/components/badge';
13+
14+
type Item = Pick<PageTree.Item, 'name' | 'description' | 'url'> & {
15+
title?: string|null;
16+
version?: string|null;
17+
};
18+
19+
export interface FooterProps extends ComponentProps<'div'> {
20+
/**
21+
* Items including information for the next and previous page
22+
*/
23+
items?: {
24+
previous?: Item;
25+
next?: Item;
26+
};
27+
}
28+
29+
export function Footer({ items, children, className, ...props }: FooterProps) {
30+
const footerList = getFooterItems();
31+
const pathname = usePathname();
32+
const { previous, next } = useMemo(() => {
33+
if (items) return items;
34+
35+
const idx = footerList.findIndex((item) => isActive(item.url, pathname));
36+
37+
if (idx === -1) return {};
38+
return {
39+
previous: footerList[idx - 1],
40+
next: footerList[idx + 1],
41+
};
42+
}, [footerList, items, pathname]);
43+
44+
return (
45+
<>
46+
<div
47+
className={cn(
48+
'@container grid gap-4',
49+
previous && next ? 'grid-cols-2' : 'grid-cols-1',
50+
className,
51+
)}
52+
{...props}
53+
>
54+
{previous && <FooterItem item={previous} index={0} />}
55+
{next && <FooterItem item={next} index={1} />}
56+
</div>
57+
{children}
58+
</>
59+
);
60+
}
61+
62+
function FooterItem({ item, index }: { item: Item; index: 0 | 1 }) {
63+
const { text } = useI18n();
64+
const Icon = index === 0 ? ChevronLeft : ChevronRight;
65+
66+
return (
67+
<Link
68+
href={item.url}
69+
className={cn(
70+
'flex flex-col gap-2 rounded-lg border p-4 text-sm transition-colors hover:bg-fd-accent/80 hover:text-fd-accent-foreground @max-lg:col-span-full',
71+
index === 1 && 'text-end',
72+
)}
73+
>
74+
<div
75+
className={cn(
76+
'inline-flex items-center gap-1.5 font-medium',
77+
index === 1 && 'flex-row-reverse',
78+
)}
79+
>
80+
<Icon className="-mx-1 size-4 shrink-0 rtl:rotate-180" />
81+
82+
<p className="truncate">
83+
{item.title && (
84+
<span className="font-bold">{item.title} - </span>
85+
)}
86+
{item.name}
87+
{item.version && (
88+
<Badge size="sm" color='gray' className='ml-1 rounded-md'>{item.version}</Badge>
89+
)}
90+
</p>
91+
92+
</div>
93+
<p className="text-fd-muted-foreground truncate">
94+
{item.description ?? (index === 0 ? text.previousPage : text.nextPage)}
95+
</p>
96+
</Link>
97+
);
98+
}
99+
100+
function isActive(url: string, pathname: string): unknown {
101+
// Exact match or pathname starts with url followed by a slash
102+
return url === pathname || pathname.startsWith(url + '/');
103+
}
104+
105+
function getFooterItems() {
106+
const items = useFooterItems();
107+
const allPlugins = config.plugins;
108+
109+
return items.map(item => {
110+
const itemPluginId = item.url.split('/')[2];
111+
const itemVersionId = item.url.split('/')[3];
112+
const match = allPlugins.find(p => p.id == itemPluginId);
113+
114+
let version = null;
115+
let pluginName = null;
116+
117+
if (match) {
118+
pluginName = match.title;
119+
const versionMatch = match.versions.find(v => v.version == itemVersionId);
120+
if (versionMatch) {
121+
version = versionMatch.version;
122+
}
123+
}
124+
125+
return {
126+
...item,
127+
title: pluginName,
128+
version: version,
129+
};
130+
});
131+
}

components/badge.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { cn } from "@/lib/utils";
2+
3+
type BadgeProps = {
4+
children: React.ReactNode;
5+
className?: string;
6+
size?: 'sm' | 'md' | 'lg';
7+
color?: 'primary' | 'gray' | 'red' | 'green' | 'blue' | 'purple' | 'orange' | 'amber';
8+
};
9+
10+
export const Badge: React.FC<BadgeProps> = ({ children, className, size = 'md', color = 'primary' }) => {
11+
return (
12+
<span className={cn(
13+
"rounded-full",
14+
"border",
15+
size === 'sm' && "px-1 py-0.5 text-xs",
16+
size === 'md' && "px-2 py-1 text-xs",
17+
size === 'lg' && `px-3 py-1 text-sm`,
18+
color === 'primary' && `bg-primary/10 text-primary text-sm border-primary/20`,
19+
color === 'green' && `bg-green-500/10 text-green-600 dark:text-green-400 border-green-500/20`,
20+
color === 'purple' && `bg-purple-500/10 text-purple-600 dark:text-purple-400 border-purple-500/20`,
21+
color === 'orange' && `bg-orange-500/10 text-orange-600 dark:text-orange-400 border-orange-500/20`,
22+
color === 'red' && `bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/20`,
23+
color === 'blue' && `bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20`,
24+
color === 'amber' && "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20",
25+
color === 'gray' || !color ? `bg-gray-500/10 text-gray-600 dark:text-gray-400 border-gray-500/20` : '',
26+
className,
27+
)}>
28+
{children}
29+
</span>
30+
);
31+
}

components/partials/plugin-cards.tsx

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { config } from '@/lib/config';
55
import { GithubIcon } from 'lucide-react';
66
import { getPluginGithubRepoUrl } from '@/lib/source';
77
import React from 'react';
8+
import { Badge } from '../badge';
89

910
export const PluginCards = () => {
1011
return (
@@ -17,9 +18,9 @@ export const PluginCards = () => {
1718
href={`/docs/${plugin.id}`}
1819
>
1920
<div className="flex items-center justify-between">
20-
<span className="px-3 py-1 bg-primary/10 text-primary text-sm rounded-full border border-primary/20">
21+
<Badge color="primary" size="lg">
2122
v{plugin.latestVersion}
22-
</span>
23+
</Badge>
2324
<div className="text-xs text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity">
2425
View Docs →
2526
</div>
@@ -93,19 +94,7 @@ const Card = ({
9394
<CardTitle>{title}</CardTitle>
9495
): title}
9596
{badge && (
96-
<span className={cn(
97-
"px-2 py-1 text-xs rounded-full",
98-
"border",
99-
badge.color === 'green' && `bg-green-500/10 text-green-600 dark:text-green-400 border-green-500/20`,
100-
badge.color === 'purple' && `bg-purple-500/10 text-purple-600 dark:text-purple-400 border-purple-500/20`,
101-
badge.color === 'orange' && `bg-orange-500/10 text-orange-600 dark:text-orange-400 border-orange-500/20`,
102-
badge.color === 'red' && `bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/20`,
103-
badge.color === 'blue' && `bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20`,
104-
badge.color === 'amber' && "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20",
105-
badge.color === 'gray' || !badge.color ? `bg-gray-500/10 text-gray-600 dark:text-gray-400 border-gray-500/20` : '',
106-
)}>
107-
{badge.text}
108-
</span>
97+
<Badge color={badge.color as React.ComponentProps<typeof Badge>['color']}>{badge.text}</Badge>
10998
)}
11099
</div>
111100
)}

0 commit comments

Comments
 (0)