summaryrefslogtreecommitdiff
path: root/src/components/sections/header/menu.tsx
diff options
context:
space:
mode:
authorBertrand Yuan <bert.yuan@outlook.com>2025-12-15 23:48:10 +0800
committerBertrand Yuan <bert.yuan@outlook.com>2025-12-15 23:48:10 +0800
commit5b7ccf0b671e2999b62befc729a3e517a0433728 (patch)
tree8bf476dc7c75914c221042546840dc76267366df /src/components/sections/header/menu.tsx
initial commit -- the front-end prototype
The initial code is base on Anirudh's work. More to see at: https://github.com/techwithanirudh/shadcn-blog Therefore, the code in this commit is under MIT license.
Diffstat (limited to 'src/components/sections/header/menu.tsx')
-rw-r--r--src/components/sections/header/menu.tsx120
1 files changed, 120 insertions, 0 deletions
diff --git a/src/components/sections/header/menu.tsx b/src/components/sections/header/menu.tsx
new file mode 100644
index 0000000..57ddf12
--- /dev/null
+++ b/src/components/sections/header/menu.tsx
@@ -0,0 +1,120 @@
+'use client';
+
+import { cva } from 'class-variance-authority';
+import Link from 'fumadocs-core/link';
+import { cn } from 'fumadocs-ui/components/api';
+import { buttonVariants } from 'fumadocs-ui/components/ui/button';
+import {
+ NavigationMenuContent,
+ NavigationMenuItem,
+ NavigationMenuLink,
+ NavigationMenuTrigger,
+} from 'fumadocs-ui/components/ui/navigation-menu';
+import { BaseLinkItem, type LinkItemType } from 'fumadocs-ui/layouts/links';
+import type { ComponentPropsWithoutRef } from 'react';
+
+const menuItemVariants = cva('', {
+ variants: {
+ variant: {
+ main: 'inline-flex items-center gap-2 py-1.5 transition-colors hover:text-fd-popover-foreground/50 data-[active=true]:font-medium data-[active=true]:text-fd-primary [&_svg]:size-4',
+ icon: buttonVariants({
+ size: 'icon',
+ color: 'ghost',
+ }),
+ button: buttonVariants({
+ color: 'secondary',
+ className: 'gap-1.5 [&_svg]:size-4',
+ }),
+ },
+ },
+ defaultVariants: {
+ variant: 'main',
+ },
+});
+
+export const MenuLinkItem = ({
+ item,
+ ...props
+}: {
+ item: LinkItemType;
+ className?: string;
+}) => {
+ if (item.type === 'custom')
+ return <div className={cn('grid', props.className)}>{item.children}</div>;
+
+ if (item.type === 'menu') {
+ const header = (
+ <>
+ {item.icon}
+ {item.text}
+ </>
+ );
+
+ return (
+ <div className={cn('mb-4 flex flex-col', props.className)}>
+ <p className='mb-1 text-fd-muted-foreground text-sm'>
+ {item.url ? (
+ <NavigationMenuLink asChild>
+ <Link href={item.url}>{header}</Link>
+ </NavigationMenuLink>
+ ) : (
+ header
+ )}
+ </p>
+ {item.items.map((child, i) => (
+ <MenuLinkItem key={i.toString()} item={child} />
+ ))}
+ </div>
+ );
+ }
+
+ return (
+ <NavigationMenuLink asChild>
+ <BaseLinkItem
+ item={item}
+ className={cn(
+ menuItemVariants({ variant: item.type }),
+ props.className,
+ )}
+ aria-label={item.type === 'icon' ? item.label : undefined}
+ >
+ {item.icon}
+ {item.type === 'icon' ? undefined : item.text}
+ </BaseLinkItem>
+ </NavigationMenuLink>
+ );
+};
+
+export const Menu = NavigationMenuItem;
+
+export const MenuTrigger = ({
+ ...props
+}: ComponentPropsWithoutRef<typeof NavigationMenuTrigger> & {}) => {
+ return (
+ <NavigationMenuTrigger
+ {...props}
+ className={cn(
+ buttonVariants({
+ size: 'icon',
+ color: 'ghost',
+ }),
+ props.className,
+ )}
+ >
+ {props.children}
+ </NavigationMenuTrigger>
+ );
+};
+
+export const MenuContent = (
+ props: ComponentPropsWithoutRef<typeof NavigationMenuContent>,
+) => {
+ return (
+ <NavigationMenuContent
+ {...props}
+ className={cn('flex flex-col p-4', props.className)}
+ >
+ {props.children}
+ </NavigationMenuContent>
+ );
+};