11'use client'
22
3- import { usePathname } from 'next/navigation'
3+ import { usePathname , useRouter } from 'next/navigation'
44import { memo , type ReactNode } from 'react'
55import { cn } from '@/lib/utils'
66import { HoverPrefetchLink } from '@/ui/hover-prefetch-link'
7+ import {
8+ Select ,
9+ SelectContent ,
10+ SelectItem ,
11+ SelectTrigger ,
12+ } from '@/ui/primitives/select'
713import { Tabs , TabsList , TabsTrigger } from '@/ui/primitives/tabs'
814
9- export interface DashboardTabsListProps {
10- layoutKey : string
11- tabs : DashboardTabItem [ ]
12- className ?: string
13- headerAccessory ?: ReactNode
14- }
15-
1615export interface DashboardTabItem {
1716 id : string
1817 label : string
1918 href : string
2019 icon ?: ReactNode
2120}
2221
22+ export interface DashboardTabsListProps {
23+ layoutKey : string
24+ tabs : DashboardTabItem [ ]
25+ className ?: string
26+ headerAccessory ?: ReactNode
27+ /**
28+ * Controls how the tab bar renders on mobile (`max-md`) viewports.
29+ * - `tabs` (default): horizontal `TabsList`, identical to desktop.
30+ * - `select`: replaces the tab list with a `Select` dropdown and renders
31+ * the optional `headerAccessory` inline on the same row.
32+ *
33+ * Desktop rendering is unchanged regardless of variant.
34+ */
35+ mobileVariant ?: 'tabs' | 'select'
36+ }
37+
2338function DashboardTabsListComponent ( {
2439 layoutKey,
2540 tabs,
2641 className,
2742 headerAccessory,
43+ mobileVariant = 'tabs' ,
2844} : DashboardTabsListProps ) {
2945 const pathname = usePathname ( )
46+ const router = useRouter ( )
3047
3148 const firstTab = tabs [ 0 ]
3249 if ( ! firstTab ) {
3350 return null
3451 }
3552
36- const activeTabId =
37- tabs . find ( ( tab ) => isTabActive ( pathname , tab . href ) ) ?. id ?? firstTab . id
53+ const activeTab =
54+ tabs . find ( ( tab ) => isTabActive ( pathname , tab . href ) ) ?? firstTab
55+ const activeTabId = activeTab . id
3856
3957 const tabTriggers = tabs . map ( ( tab ) => (
4058 < TabsTrigger
@@ -51,6 +69,52 @@ function DashboardTabsListComponent({
5169 </ TabsTrigger >
5270 ) )
5371
72+ if ( mobileVariant === 'select' ) {
73+ const handleSelectChange = ( id : string ) => {
74+ const next = tabs . find ( ( tab ) => tab . id === id )
75+ if ( next ) router . push ( next . href )
76+ }
77+
78+ return (
79+ < Tabs
80+ value = { activeTabId }
81+ className = { cn (
82+ 'bg-bg flex w-full flex-none flex-row items-end' ,
83+ className
84+ ) }
85+ >
86+ < Select value = { activeTabId } onValueChange = { handleSelectChange } >
87+ < SelectTrigger className = "h-9 w-fit border-x-0 border-t-0 border-b border-solid md:hidden" >
88+ < div className = "flex items-center gap-2" >
89+ { activeTab . icon }
90+ { activeTab . label }
91+ </ div >
92+ </ SelectTrigger >
93+ < SelectContent >
94+ { tabs . map ( ( tab ) => (
95+ < SelectItem key = { tab . id } value = { tab . id } >
96+ < span className = "inline-flex items-center gap-2" >
97+ { tab . icon }
98+ { tab . label }
99+ </ span >
100+ </ SelectItem >
101+ ) ) }
102+ </ SelectContent >
103+ </ Select >
104+
105+ < TabsList className = "bg-bg justify-start max-md:hidden md:flex-1" >
106+ { tabTriggers }
107+ </ TabsList >
108+
109+ { headerAccessory && (
110+ < div className = "flex items-end border-b border-solid max-md:flex-1 max-md:justify-end md:px-6" >
111+ { headerAccessory }
112+ </ div >
113+ ) }
114+ </ Tabs >
115+ )
116+ }
117+
54118 return (
55119 < Tabs value = { activeTabId } className = { cn ( 'w-full flex-none' , className ) } >
56120 { headerAccessory ? (
0 commit comments