@@ -36,7 +36,13 @@ export type NavbarItem = {
3636 end ?: boolean ;
3737} ;
3838
39- function isNavbarItemActive ( item : NavbarItem , pathname : string ) : boolean {
39+ export type NavbarHome = {
40+ href : string ;
41+ /** When true, only mark active on exact href match (e.g. home "/"). */
42+ end ?: boolean ;
43+ } ;
44+
45+ function isNavbarItemActive ( item : Pick < NavbarItem , "href" | "end" > , pathname : string ) : boolean {
4046 if ( item . end ) {
4147 return pathname === item . href ;
4248 }
@@ -52,13 +58,27 @@ export interface NavbarLinkRenderProps {
5258 onNavigate : ( ) => void ;
5359}
5460
61+ export interface NavbarHomeLinkRenderProps {
62+ home : NavbarHome ;
63+ className : ( isActive : boolean ) => string ;
64+ children : React . ReactNode ;
65+ onNavigate : ( ) => void ;
66+ }
67+
5568export interface NavbarProps extends React . HTMLAttributes < HTMLElement > {
5669 title : string ;
5770 items : NavbarItem [ ] ;
71+ /**
72+ * Logo + title link target. Defaults to `{ href: "/", end: true }`.
73+ * Pass `false` to render a non-interactive brand area.
74+ */
75+ home ?: NavbarHome | false ;
5876 /** Current path for default anchor links and closing the mobile menu on navigation. */
5977 pathname ?: string ;
6078 /** Custom link renderer (e.g. React Router `NavLink`). Defaults to `<a href>`. */
6179 renderLink ?: ( props : NavbarLinkRenderProps ) => React . ReactNode ;
80+ /** Custom home/brand link renderer. Defaults to `<a href>`. */
81+ renderHomeLink ?: ( props : NavbarHomeLinkRenderProps ) => React . ReactNode ;
6282 logo ?: React . ReactNode ;
6383 mobileMenuAriaLabel ?: { open : string ; close : string } ;
6484 navAriaLabel ?: string ;
@@ -70,11 +90,23 @@ export interface NavbarProps extends React.HTMLAttributes<HTMLElement> {
7090 variant ?: NavbarVariant ;
7191}
7292
93+ function homeLinkClassName ( isLight : boolean ) {
94+ return cn (
95+ "inline-flex min-w-0 max-w-full items-center gap-1 rounded-md outline-none transition-opacity md:gap-2" ,
96+ "focus-visible:ring-2 focus-visible:ring-offset-2" ,
97+ isLight
98+ ? "text-foreground hover:opacity-80 focus-visible:ring-ring focus-visible:ring-offset-background"
99+ : "text-secondary-foreground hover:opacity-90 focus-visible:ring-white/50 focus-visible:ring-offset-transparent" ,
100+ ) ;
101+ }
102+
73103export function Navbar ( {
74104 title,
75105 items,
106+ home : homeProp ,
76107 pathname = "" ,
77108 renderLink,
109+ renderHomeLink,
78110 logo = < Logo size = "sm" className = "shrink-0 text-primary" /> ,
79111 mobileMenuAriaLabel = { open : "เปิดเมนู" , close : "ปิดเมนู" } ,
80112 navAriaLabel = "เมนูหลัก" ,
@@ -112,6 +144,50 @@ export function Navbar({
112144
113145 const linkRenderer = renderLink ?? defaultRenderLink ;
114146
147+ const resolvedHome : NavbarHome | false = homeProp === false ? false : ( homeProp ?? { href : "/" , end : true } ) ;
148+
149+ const brand = (
150+ < >
151+ { logo }
152+ < h1 className = "min-w-0 truncate font-heading text-base font-medium md:text-xl" > { title } </ h1 >
153+ </ >
154+ ) ;
155+
156+ const defaultRenderHomeLink = ( {
157+ home,
158+ className : homeClassName ,
159+ children,
160+ onNavigate,
161+ } : NavbarHomeLinkRenderProps ) => {
162+ const active = isNavbarItemActive ( home , pathname ) ;
163+ return (
164+ < a
165+ href = { home . href }
166+ className = { homeClassName ( active ) }
167+ onClick = { onNavigate }
168+ aria-current = { active ? "page" : undefined }
169+ >
170+ { children }
171+ </ a >
172+ ) ;
173+ } ;
174+
175+ const homeLinkRenderer = renderHomeLink ?? defaultRenderHomeLink ;
176+
177+ const brandBlock =
178+ resolvedHome === false ? (
179+ < Stack gap = "xs" className = "min-w-0 flex-row items-center md:gap-2" >
180+ { brand }
181+ </ Stack >
182+ ) : (
183+ homeLinkRenderer ( {
184+ home : resolvedHome ,
185+ className : ( ) => homeLinkClassName ( isLight ) ,
186+ children : brand ,
187+ onNavigate : closeMenu ,
188+ } )
189+ ) ;
190+
115191 const renderNavItem = ( item : NavbarItem , mobile : boolean ) =>
116192 linkRenderer ( {
117193 item,
@@ -135,12 +211,7 @@ export function Navbar({
135211 className = { isLight ? "max-md:py-2 md:py-4" : "max-md:py-4 md:py-6" }
136212 >
137213 < Inline justify = "between" align = "center" className = "w-full" >
138- < Stack gap = "xs" className = "min-w-0 flex-row items-center md:gap-2" >
139- { logo }
140- < div className = "min-w-0" >
141- < h1 className = "truncate font-heading text-base font-medium md:text-xl" > { title } </ h1 >
142- </ div >
143- </ Stack >
214+ { brandBlock }
144215 < button
145216 type = "button"
146217 className = { cn (
0 commit comments