From 5b7ccf0b671e2999b62befc729a3e517a0433728 Mon Sep 17 00:00:00 2001 From: Bertrand Yuan Date: Mon, 15 Dec 2025 23:48:10 +0800 Subject: 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. --- src/components/sections/header/index.tsx | 174 ++++++++++++++++++++++++++++++ src/components/sections/header/menu.tsx | 120 +++++++++++++++++++++ src/components/sections/header/navbar.tsx | 55 ++++++++++ 3 files changed, 349 insertions(+) create mode 100644 src/components/sections/header/index.tsx create mode 100644 src/components/sections/header/menu.tsx create mode 100644 src/components/sections/header/navbar.tsx (limited to 'src/components/sections/header') diff --git a/src/components/sections/header/index.tsx b/src/components/sections/header/index.tsx new file mode 100644 index 0000000..4280e01 --- /dev/null +++ b/src/components/sections/header/index.tsx @@ -0,0 +1,174 @@ +import Link from 'fumadocs-core/link'; +import { + LanguageToggle, + LanguageToggleText, +} from 'fumadocs-ui/components/layout/language-toggle'; +import { + LargeSearchToggle, + SearchToggle, +} from 'fumadocs-ui/components/layout/search-toggle'; +import { NavigationMenuList } from 'fumadocs-ui/components/ui/navigation-menu'; +import type { HomeLayoutProps } from 'fumadocs-ui/layouts/home'; +import { + NavbarLink, + NavbarMenu, + NavbarMenuContent, + NavbarMenuTrigger, +} from 'fumadocs-ui/layouts/home/navbar'; +import type { LinkItemType } from 'fumadocs-ui/layouts/links'; +import { SearchOnly } from 'fumadocs-ui/provider'; +import { ChevronDown, Languages } from 'lucide-react'; +import { ThemeToggle } from '../../theme-toggle'; +import { Menu, MenuContent, MenuLinkItem, MenuTrigger } from './menu'; +import { Navbar, NavbarMenuLink } from './navbar'; + +export const Header = ({ + nav: { enableSearch = true, ...nav } = {}, + i18n = false, + finalLinks, +}: HomeLayoutProps & { + finalLinks: LinkItemType[]; +}) => { + const navItems = finalLinks.filter((item) => + ['nav', 'all'].includes(item.on ?? 'all'), + ); + const menuItems = finalLinks.filter((item) => + ['menu', 'all'].includes(item.on ?? 'all'), + ); + + return ( + + + {nav.title} + + {nav.children} + + {navItems + .filter((item) => !isSecondary(item)) + .map((item, i) => ( + + ))} + +
+ {enableSearch ? ( + + + + + ) : null} + + {navItems.filter(isSecondary).map((item, i) => ( + + ))} + + + + + + {menuItems + .filter((item) => !isSecondary(item)) + .map((item, i) => ( + + ))} +
+ {menuItems.filter(isSecondary).map((item, i) => ( + + ))} +
+ {i18n ? ( + + + + + + ) : null} + +
+ +
+
+
+ ); +}; + +const NavbarLinkItem = ({ + item, + ...props +}: { + item: LinkItemType; + className?: string; +}) => { + if (item.type === 'custom') return
{item.children}
; + + if (item.type === 'menu') { + const children = item.items.map((child, j) => { + if (child.type === 'custom') + return
{child.children}
; + + const { banner, footer, ...rest } = child.menu ?? {}; + + return ( + + {banner ?? + (child.icon ? ( +
+ {child.icon} +
+ ) : null)} +

{child.text}

+ {child.description ? ( +

+ {child.description} +

+ ) : null} + {footer} +
+ ); + }); + + return ( + + + {item.url ? {item.text} : item.text} + + {children} + + ); + } + + return ( + + {item.type === 'icon' ? item.icon : item.text} + + ); +}; + +const isSecondary = (item: LinkItemType): boolean => { + return ( + ('secondary' in item && item.secondary === true) || item.type === 'icon' + ); +}; 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
{item.children}
; + + if (item.type === 'menu') { + const header = ( + <> + {item.icon} + {item.text} + + ); + + return ( +
+

+ {item.url ? ( + + {header} + + ) : ( + header + )} +

+ {item.items.map((child, i) => ( + + ))} +
+ ); + } + + return ( + + + {item.icon} + {item.type === 'icon' ? undefined : item.text} + + + ); +}; + +export const Menu = NavigationMenuItem; + +export const MenuTrigger = ({ + ...props +}: ComponentPropsWithoutRef & {}) => { + return ( + + {props.children} + + ); +}; + +export const MenuContent = ( + props: ComponentPropsWithoutRef, +) => { + return ( + + {props.children} + + ); +}; diff --git a/src/components/sections/header/navbar.tsx b/src/components/sections/header/navbar.tsx new file mode 100644 index 0000000..25850a0 --- /dev/null +++ b/src/components/sections/header/navbar.tsx @@ -0,0 +1,55 @@ +'use client'; + +import Link, { type LinkProps } from 'fumadocs-core/link'; +import { cn } from 'fumadocs-ui/components/api'; +import { + NavigationMenu, + NavigationMenuLink, + NavigationMenuViewport, +} from 'fumadocs-ui/components/ui/navigation-menu'; +import { type HTMLAttributes, useState } from 'react'; + +export const Navbar = (props: HTMLAttributes) => { + const [value, setValue] = useState(''); + + return ( + +
0 ? 'shadow-lg' : 'shadow-xs', + props.className, + )} + > +
+ {props.children} +
+ +
+
+ ); +}; + +export const NavbarMenuLink = (props: LinkProps) => { + return ( + + + {props.children} + + + ); +}; -- cgit v1.2.3