ai-design-system 0.1.115 → 0.1.116

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,10 +5,11 @@
5
5
  * Displays open documents as closeable tabs with dirty indicators.
6
6
  */
7
7
 
8
- import React from 'react'
8
+ import React, { useCallback, useEffect, useRef, useState } from 'react'
9
9
  import { Tabs, TabsList, TabsTrigger, Button, ScrollArea } from '@/components/primitives'
10
10
  import { Icon } from '@/components/primitives/Icon'
11
11
  import { cn } from '@/lib/utils'
12
+
12
13
  export interface DocumentFile {
13
14
  id: string
14
15
  name: string
@@ -50,6 +51,12 @@ export interface DocumentTabBarProps {
50
51
  * Callback to open/toggle the file explorer (e.g. mobile drawer)
51
52
  */
52
53
  onToggleExplorer?: () => void
54
+
55
+ /**
56
+ * Whether to show navigation scroll arrows when tabs overflow
57
+ * @default true
58
+ */
59
+ showScrollArrows?: boolean
53
60
  }
54
61
 
55
62
  /**
@@ -62,13 +69,103 @@ export interface DocumentTabBarProps {
62
69
  * - Close button (X)
63
70
  *
64
71
  * Features:
65
- * - Scrollable when tabs overflow horizontally
72
+ * - Scrollable when tabs overflow horizontally with multi-device support
73
+ * - Mouse wheel vertical-to-horizontal scroll translation
74
+ * - Auto-scroll active tab into view
75
+ * - Overflow indicators (edge gradients & navigation chevrons)
66
76
  * - Accessible tab navigation via Radix UI Tabs
67
77
  * - Close button to remove tabs
68
78
  * - Dirty state indicator
69
79
  */
70
80
  export const DocumentTabBar = React.memo<DocumentTabBarProps>(
71
- ({ tabs, activeTabId, onTabSelect, onTabClose, onToggleExplorer, className }) => {
81
+ ({
82
+ tabs,
83
+ activeTabId,
84
+ onTabSelect,
85
+ onTabClose,
86
+ onToggleExplorer,
87
+ className,
88
+ showScrollArrows = true,
89
+ }) => {
90
+ const scrollContainerRef = useRef<HTMLDivElement>(null)
91
+ const [canScrollLeft, setCanScrollLeft] = useState(false)
92
+ const [canScrollRight, setCanScrollRight] = useState(false)
93
+
94
+ const getViewport = useCallback((): HTMLElement | null => {
95
+ return (
96
+ scrollContainerRef.current?.querySelector<HTMLElement>(
97
+ '[data-slot="scroll-area-viewport"]'
98
+ ) ?? null
99
+ )
100
+ }, [])
101
+
102
+ const updateScrollState = useCallback(() => {
103
+ const viewport = getViewport()
104
+ if (!viewport) return
105
+ const { scrollLeft, scrollWidth, clientWidth } = viewport
106
+ setCanScrollLeft(scrollLeft > 2)
107
+ setCanScrollRight(scrollLeft + clientWidth < scrollWidth - 2)
108
+ }, [getViewport])
109
+
110
+ useEffect(() => {
111
+ const viewport = getViewport()
112
+ if (!viewport) return
113
+
114
+ updateScrollState()
115
+ viewport.addEventListener('scroll', updateScrollState, { passive: true })
116
+
117
+ const resizeObserver = new ResizeObserver(() => {
118
+ updateScrollState()
119
+ })
120
+ resizeObserver.observe(viewport)
121
+
122
+ return () => {
123
+ viewport.removeEventListener('scroll', updateScrollState)
124
+ resizeObserver.disconnect()
125
+ }
126
+ }, [getViewport, updateScrollState, tabs])
127
+
128
+ // Convert mouse wheel vertical scroll to horizontal scroll
129
+ const handleWheel = useCallback(
130
+ (e: React.WheelEvent) => {
131
+ if (e.deltaY === 0) return
132
+ const viewport = getViewport()
133
+ if (viewport) {
134
+ viewport.scrollLeft += e.deltaY
135
+ }
136
+ },
137
+ [getViewport]
138
+ )
139
+
140
+ // Auto-scroll active tab into view
141
+ useEffect(() => {
142
+ if (!activeTabId || !scrollContainerRef.current) return
143
+ const activeEl = scrollContainerRef.current.querySelector<HTMLElement>(
144
+ '[data-state="active"]'
145
+ )
146
+ if (activeEl) {
147
+ activeEl.scrollIntoView({
148
+ behavior: 'smooth',
149
+ block: 'nearest',
150
+ inline: 'nearest',
151
+ })
152
+ }
153
+ }, [activeTabId, tabs])
154
+
155
+ const handleScrollLeft = useCallback(() => {
156
+ const viewport = getViewport()
157
+ if (viewport) {
158
+ viewport.scrollBy({ left: -200, behavior: 'smooth' })
159
+ }
160
+ }, [getViewport])
161
+
162
+ const handleScrollRight = useCallback(() => {
163
+ const viewport = getViewport()
164
+ if (viewport) {
165
+ viewport.scrollBy({ left: 200, behavior: 'smooth' })
166
+ }
167
+ }, [getViewport])
168
+
72
169
  if (tabs.length === 0 && !onToggleExplorer) {
73
170
  return null
74
171
  }
@@ -76,7 +173,7 @@ export const DocumentTabBar = React.memo<DocumentTabBarProps>(
76
173
  return (
77
174
  <div
78
175
  className={cn(
79
- 'flex items-center border-b border-border bg-background',
176
+ 'flex items-center border-b border-border bg-background relative',
80
177
  className
81
178
  )}
82
179
  data-slot="document-tab-bar"
@@ -92,67 +189,114 @@ export const DocumentTabBar = React.memo<DocumentTabBarProps>(
92
189
  <Icon name="folder" size="xs" />
93
190
  </Button>
94
191
  )}
192
+
95
193
  {tabs.length > 0 && (
96
- <ScrollArea className="flex-1">
97
- <Tabs
98
- value={activeTabId || tabs[0]?.id}
99
- onValueChange={onTabSelect}
100
- className="h-auto flex-1"
101
- >
102
- <TabsList className="h-10 rounded-none border-none bg-transparent p-0 w-full justify-start gap-0">
103
- {tabs.map((tab) => (
194
+ <div
195
+ ref={scrollContainerRef}
196
+ onWheel={handleWheel}
197
+ className="flex-1 min-w-0 flex items-center relative"
198
+ >
199
+ {showScrollArrows && canScrollLeft && (
200
+ <Button
201
+ variant="ghost"
202
+ size="icon-sm"
203
+ onClick={handleScrollLeft}
204
+ className="h-10 w-7 shrink-0 rounded-none border-r border-border/50 text-muted-foreground hover:text-foreground transition-colors z-10"
205
+ aria-label="Scroll tabs left"
206
+ >
207
+ <Icon name="chevron-left" size="xs" />
208
+ </Button>
209
+ )}
210
+
211
+ <div className="flex-1 min-w-0 relative h-10">
212
+ {canScrollLeft && (
104
213
  <div
105
- key={tab.id}
106
- className="flex items-center border-r border-border/50 last:border-r-0"
214
+ className="pointer-events-none absolute left-0 top-0 bottom-0 w-5 bg-gradient-to-r from-background to-transparent z-10"
215
+ aria-hidden="true"
216
+ />
217
+ )}
218
+
219
+ <ScrollArea orientation="horizontal" className="w-full h-10">
220
+ <Tabs
221
+ value={activeTabId || tabs[0]?.id}
222
+ onValueChange={onTabSelect}
223
+ className="h-auto flex-1"
107
224
  >
108
- <TabsTrigger
109
- value={tab.id}
110
- className={cn(
111
- 'data-[state=inactive]:bg-muted/20 data-[state=inactive]:text-muted-foreground',
112
- 'rounded-none border-b-2 border-transparent data-[state=active]:border-primary',
113
- 'px-2.5 sm:px-3 py-2 text-xs sm:text-sm font-medium whitespace-nowrap',
114
- 'flex items-center gap-1.5 sm:gap-2 h-10',
115
- 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0',
116
- 'transition-colors'
117
- )}
118
- >
119
- <div className="flex items-center gap-1.5 sm:gap-2">
120
- {/* Dirty indicator */}
121
- {tab.isDirty && (
122
- <Icon
123
- name="circle"
124
- className="size-2 fill-primary text-primary"
125
- aria-label="unsaved changes"
126
- />
127
- )}
128
- {/* Tab name */}
129
- <span className="truncate max-w-[120px] sm:max-w-[200px]">{tab.name}</span>
130
- </div>
131
- </TabsTrigger>
132
-
133
- {/* Close button */}
134
- <Button
135
- variant="ghost"
136
- size="icon-sm"
137
- onClick={(e) => {
138
- e.stopPropagation()
139
- onTabClose?.(tab.id)
140
- }}
141
- className={cn(
142
- 'h-8 w-8 mr-0.5',
143
- 'hover:bg-destructive/10 hover:text-destructive',
144
- 'focus-visible:ring-2 focus-visible:ring-ring',
145
- 'transition-colors'
146
- )}
147
- aria-label={`Close ${tab.name}`}
148
- >
149
- <Icon name="x" size="xs" />
150
- </Button>
151
- </div>
152
- ))}
153
- </TabsList>
154
- </Tabs>
155
- </ScrollArea>
225
+ <TabsList className="h-10 rounded-none border-none bg-transparent p-0 w-full justify-start gap-0">
226
+ {tabs.map((tab) => (
227
+ <div
228
+ key={tab.id}
229
+ className="flex items-center shrink-0 border-r border-border/50 last:border-r-0"
230
+ >
231
+ <TabsTrigger
232
+ value={tab.id}
233
+ className={cn(
234
+ 'data-[state=inactive]:bg-muted/20 data-[state=inactive]:text-muted-foreground',
235
+ 'rounded-none border-b-2 border-transparent data-[state=active]:border-primary',
236
+ 'px-2.5 sm:px-3 py-2 text-xs sm:text-sm font-medium whitespace-nowrap',
237
+ 'flex items-center gap-1.5 sm:gap-2 h-10',
238
+ 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0',
239
+ 'transition-colors'
240
+ )}
241
+ >
242
+ <div className="flex items-center gap-1.5 sm:gap-2">
243
+ {/* Dirty indicator */}
244
+ {tab.isDirty && (
245
+ <Icon
246
+ name="circle"
247
+ className="size-2 fill-primary text-primary"
248
+ aria-label="unsaved changes"
249
+ />
250
+ )}
251
+ {/* Tab name */}
252
+ <span className="truncate max-w-[120px] sm:max-w-[200px]">{tab.name}</span>
253
+ </div>
254
+ </TabsTrigger>
255
+
256
+ {/* Close button */}
257
+ <Button
258
+ variant="ghost"
259
+ size="icon-sm"
260
+ onClick={(e) => {
261
+ e.stopPropagation()
262
+ onTabClose?.(tab.id)
263
+ }}
264
+ className={cn(
265
+ 'h-8 w-8 mr-0.5',
266
+ 'hover:bg-destructive/10 hover:text-destructive',
267
+ 'focus-visible:ring-2 focus-visible:ring-ring',
268
+ 'transition-colors'
269
+ )}
270
+ aria-label={`Close ${tab.name}`}
271
+ >
272
+ <Icon name="x" size="xs" />
273
+ </Button>
274
+ </div>
275
+ ))}
276
+ </TabsList>
277
+ </Tabs>
278
+ </ScrollArea>
279
+
280
+ {canScrollRight && (
281
+ <div
282
+ className="pointer-events-none absolute right-0 top-0 bottom-0 w-5 bg-gradient-to-l from-background to-transparent z-10"
283
+ aria-hidden="true"
284
+ />
285
+ )}
286
+ </div>
287
+
288
+ {showScrollArrows && canScrollRight && (
289
+ <Button
290
+ variant="ghost"
291
+ size="icon-sm"
292
+ onClick={handleScrollRight}
293
+ className="h-10 w-7 shrink-0 rounded-none border-l border-border/50 text-muted-foreground hover:text-foreground transition-colors z-10"
294
+ aria-label="Scroll tabs right"
295
+ >
296
+ <Icon name="chevron-right" size="xs" />
297
+ </Button>
298
+ )}
299
+ </div>
156
300
  )}
157
301
  </div>
158
302
  )
@@ -160,3 +304,4 @@ export const DocumentTabBar = React.memo<DocumentTabBarProps>(
160
304
  )
161
305
 
162
306
  DocumentTabBar.displayName = 'DocumentTabBar'
307
+
@@ -1,2 +1,2 @@
1
1
  export { DocumentTabBar } from './DocumentTabBar'
2
- export type { DocumentTabBarProps } from './DocumentTabBar'
2
+ export type { DocumentTabBarProps, DocumentFile } from './DocumentTabBar'
@@ -75,7 +75,7 @@ export type { ModeToggleProps } from './ModeToggle'
75
75
 
76
76
  // DocumentTabBar Composite
77
77
  export { DocumentTabBar } from './DocumentTabBar'
78
- export type { DocumentTabBarProps } from './DocumentTabBar'
78
+ export type { DocumentTabBarProps, DocumentFile } from './DocumentTabBar'
79
79
 
80
80
  // DefaultSwitcher Composite
81
81
  export { DefaultSwitcher } from './DefaultSwitcher'
@@ -5,11 +5,17 @@ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
5
5
 
6
6
  import { cn } from "@/lib/utils"
7
7
 
8
+ export interface ScrollAreaProps
9
+ extends React.ComponentProps<typeof ScrollAreaPrimitive.Root> {
10
+ orientation?: "vertical" | "horizontal" | "both"
11
+ }
12
+
8
13
  function ScrollArea({
9
14
  className,
10
15
  children,
16
+ orientation = "vertical",
11
17
  ...props
12
- }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
18
+ }: ScrollAreaProps) {
13
19
  return (
14
20
  <ScrollAreaPrimitive.Root
15
21
  data-slot="scroll-area"
@@ -22,7 +28,12 @@ function ScrollArea({
22
28
  >
23
29
  {children}
24
30
  </ScrollAreaPrimitive.Viewport>
25
- <ScrollBar />
31
+ {(orientation === "vertical" || orientation === "both") && (
32
+ <ScrollBar orientation="vertical" />
33
+ )}
34
+ {(orientation === "horizontal" || orientation === "both") && (
35
+ <ScrollBar orientation="horizontal" className="h-1.5 border-t border-t-transparent" />
36
+ )}
26
37
  <ScrollAreaPrimitive.Corner />
27
38
  </ScrollAreaPrimitive.Root>
28
39
  )
package/dist/index.cjs CHANGED
@@ -4996,10 +4996,12 @@ Confirmation2.displayName = "Confirmation";
4996
4996
  function ScrollArea(_a) {
4997
4997
  var _b = _a, {
4998
4998
  className,
4999
- children
4999
+ children,
5000
+ orientation = "vertical"
5000
5001
  } = _b, props = __objRest(_b, [
5001
5002
  "className",
5002
- "children"
5003
+ "children",
5004
+ "orientation"
5003
5005
  ]);
5004
5006
  return /* @__PURE__ */ jsxRuntime.jsxs(
5005
5007
  ScrollAreaPrimitive__namespace.Root,
@@ -5016,7 +5018,8 @@ function ScrollArea(_a) {
5016
5018
  children
5017
5019
  }
5018
5020
  ),
5019
- /* @__PURE__ */ jsxRuntime.jsx(ScrollBar, {}),
5021
+ (orientation === "vertical" || orientation === "both") && /* @__PURE__ */ jsxRuntime.jsx(ScrollBar, { orientation: "vertical" }),
5022
+ (orientation === "horizontal" || orientation === "both") && /* @__PURE__ */ jsxRuntime.jsx(ScrollBar, { orientation: "horizontal", className: "h-1.5 border-t border-t-transparent" }),
5020
5023
  /* @__PURE__ */ jsxRuntime.jsx(ScrollAreaPrimitive__namespace.Corner, {})
5021
5024
  ]
5022
5025
  })
@@ -11058,8 +11061,81 @@ var ModeToggle = React3__namespace.memo(({ className }) => {
11058
11061
  });
11059
11062
  ModeToggle.displayName = "ModeToggle";
11060
11063
  var DocumentTabBar = React3__namespace.default.memo(
11061
- ({ tabs, activeTabId, onTabSelect, onTabClose, onToggleExplorer, className }) => {
11064
+ ({
11065
+ tabs,
11066
+ activeTabId,
11067
+ onTabSelect,
11068
+ onTabClose,
11069
+ onToggleExplorer,
11070
+ className,
11071
+ showScrollArrows = true
11072
+ }) => {
11062
11073
  var _a;
11074
+ const scrollContainerRef = React3.useRef(null);
11075
+ const [canScrollLeft, setCanScrollLeft] = React3.useState(false);
11076
+ const [canScrollRight, setCanScrollRight] = React3.useState(false);
11077
+ const getViewport = React3.useCallback(() => {
11078
+ var _a2, _b;
11079
+ return (_b = (_a2 = scrollContainerRef.current) == null ? void 0 : _a2.querySelector(
11080
+ '[data-slot="scroll-area-viewport"]'
11081
+ )) != null ? _b : null;
11082
+ }, []);
11083
+ const updateScrollState = React3.useCallback(() => {
11084
+ const viewport = getViewport();
11085
+ if (!viewport) return;
11086
+ const { scrollLeft, scrollWidth, clientWidth } = viewport;
11087
+ setCanScrollLeft(scrollLeft > 2);
11088
+ setCanScrollRight(scrollLeft + clientWidth < scrollWidth - 2);
11089
+ }, [getViewport]);
11090
+ React3.useEffect(() => {
11091
+ const viewport = getViewport();
11092
+ if (!viewport) return;
11093
+ updateScrollState();
11094
+ viewport.addEventListener("scroll", updateScrollState, { passive: true });
11095
+ const resizeObserver = new ResizeObserver(() => {
11096
+ updateScrollState();
11097
+ });
11098
+ resizeObserver.observe(viewport);
11099
+ return () => {
11100
+ viewport.removeEventListener("scroll", updateScrollState);
11101
+ resizeObserver.disconnect();
11102
+ };
11103
+ }, [getViewport, updateScrollState, tabs]);
11104
+ const handleWheel = React3.useCallback(
11105
+ (e) => {
11106
+ if (e.deltaY === 0) return;
11107
+ const viewport = getViewport();
11108
+ if (viewport) {
11109
+ viewport.scrollLeft += e.deltaY;
11110
+ }
11111
+ },
11112
+ [getViewport]
11113
+ );
11114
+ React3.useEffect(() => {
11115
+ if (!activeTabId || !scrollContainerRef.current) return;
11116
+ const activeEl = scrollContainerRef.current.querySelector(
11117
+ '[data-state="active"]'
11118
+ );
11119
+ if (activeEl) {
11120
+ activeEl.scrollIntoView({
11121
+ behavior: "smooth",
11122
+ block: "nearest",
11123
+ inline: "nearest"
11124
+ });
11125
+ }
11126
+ }, [activeTabId, tabs]);
11127
+ const handleScrollLeft = React3.useCallback(() => {
11128
+ const viewport = getViewport();
11129
+ if (viewport) {
11130
+ viewport.scrollBy({ left: -200, behavior: "smooth" });
11131
+ }
11132
+ }, [getViewport]);
11133
+ const handleScrollRight = React3.useCallback(() => {
11134
+ const viewport = getViewport();
11135
+ if (viewport) {
11136
+ viewport.scrollBy({ left: 200, behavior: "smooth" });
11137
+ }
11138
+ }, [getViewport]);
11063
11139
  if (tabs.length === 0 && !onToggleExplorer) {
11064
11140
  return null;
11065
11141
  }
@@ -11067,7 +11143,7 @@ var DocumentTabBar = React3__namespace.default.memo(
11067
11143
  "div",
11068
11144
  {
11069
11145
  className: cn(
11070
- "flex items-center border-b border-border bg-background",
11146
+ "flex items-center border-b border-border bg-background relative",
11071
11147
  className
11072
11148
  ),
11073
11149
  "data-slot": "document-tab-bar",
@@ -11083,67 +11159,115 @@ var DocumentTabBar = React3__namespace.default.memo(
11083
11159
  children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "folder", size: "xs" })
11084
11160
  }
11085
11161
  ),
11086
- tabs.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ScrollArea2, { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
11087
- Tabs2,
11162
+ tabs.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
11163
+ "div",
11088
11164
  {
11089
- value: activeTabId || ((_a = tabs[0]) == null ? void 0 : _a.id),
11090
- onValueChange: onTabSelect,
11091
- className: "h-auto flex-1",
11092
- children: /* @__PURE__ */ jsxRuntime.jsx(TabsList, { className: "h-10 rounded-none border-none bg-transparent p-0 w-full justify-start gap-0", children: tabs.map((tab) => /* @__PURE__ */ jsxRuntime.jsxs(
11093
- "div",
11094
- {
11095
- className: "flex items-center border-r border-border/50 last:border-r-0",
11096
- children: [
11097
- /* @__PURE__ */ jsxRuntime.jsx(
11098
- TabsTrigger,
11099
- {
11100
- value: tab.id,
11101
- className: cn(
11102
- "data-[state=inactive]:bg-muted/20 data-[state=inactive]:text-muted-foreground",
11103
- "rounded-none border-b-2 border-transparent data-[state=active]:border-primary",
11104
- "px-2.5 sm:px-3 py-2 text-xs sm:text-sm font-medium whitespace-nowrap",
11105
- "flex items-center gap-1.5 sm:gap-2 h-10",
11106
- "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0",
11107
- "transition-colors"
11108
- ),
11109
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 sm:gap-2", children: [
11110
- tab.isDirty && /* @__PURE__ */ jsxRuntime.jsx(
11111
- Icon,
11112
- {
11113
- name: "circle",
11114
- className: "size-2 fill-primary text-primary",
11115
- "aria-label": "unsaved changes"
11116
- }
11117
- ),
11118
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate max-w-[120px] sm:max-w-[200px]", children: tab.name })
11119
- ] })
11120
- }
11121
- ),
11122
- /* @__PURE__ */ jsxRuntime.jsx(
11123
- Button2,
11124
- {
11125
- variant: "ghost",
11126
- size: "icon-sm",
11127
- onClick: (e) => {
11128
- e.stopPropagation();
11129
- onTabClose == null ? void 0 : onTabClose(tab.id);
11165
+ ref: scrollContainerRef,
11166
+ onWheel: handleWheel,
11167
+ className: "flex-1 min-w-0 flex items-center relative",
11168
+ children: [
11169
+ showScrollArrows && canScrollLeft && /* @__PURE__ */ jsxRuntime.jsx(
11170
+ Button2,
11171
+ {
11172
+ variant: "ghost",
11173
+ size: "icon-sm",
11174
+ onClick: handleScrollLeft,
11175
+ className: "h-10 w-7 shrink-0 rounded-none border-r border-border/50 text-muted-foreground hover:text-foreground transition-colors z-10",
11176
+ "aria-label": "Scroll tabs left",
11177
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron-left", size: "xs" })
11178
+ }
11179
+ ),
11180
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 relative h-10", children: [
11181
+ canScrollLeft && /* @__PURE__ */ jsxRuntime.jsx(
11182
+ "div",
11183
+ {
11184
+ className: "pointer-events-none absolute left-0 top-0 bottom-0 w-5 bg-gradient-to-r from-background to-transparent z-10",
11185
+ "aria-hidden": "true"
11186
+ }
11187
+ ),
11188
+ /* @__PURE__ */ jsxRuntime.jsx(ScrollArea2, { orientation: "horizontal", className: "w-full h-10", children: /* @__PURE__ */ jsxRuntime.jsx(
11189
+ Tabs2,
11190
+ {
11191
+ value: activeTabId || ((_a = tabs[0]) == null ? void 0 : _a.id),
11192
+ onValueChange: onTabSelect,
11193
+ className: "h-auto flex-1",
11194
+ children: /* @__PURE__ */ jsxRuntime.jsx(TabsList, { className: "h-10 rounded-none border-none bg-transparent p-0 w-full justify-start gap-0", children: tabs.map((tab) => /* @__PURE__ */ jsxRuntime.jsxs(
11195
+ "div",
11196
+ {
11197
+ className: "flex items-center shrink-0 border-r border-border/50 last:border-r-0",
11198
+ children: [
11199
+ /* @__PURE__ */ jsxRuntime.jsx(
11200
+ TabsTrigger,
11201
+ {
11202
+ value: tab.id,
11203
+ className: cn(
11204
+ "data-[state=inactive]:bg-muted/20 data-[state=inactive]:text-muted-foreground",
11205
+ "rounded-none border-b-2 border-transparent data-[state=active]:border-primary",
11206
+ "px-2.5 sm:px-3 py-2 text-xs sm:text-sm font-medium whitespace-nowrap",
11207
+ "flex items-center gap-1.5 sm:gap-2 h-10",
11208
+ "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0",
11209
+ "transition-colors"
11210
+ ),
11211
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 sm:gap-2", children: [
11212
+ tab.isDirty && /* @__PURE__ */ jsxRuntime.jsx(
11213
+ Icon,
11214
+ {
11215
+ name: "circle",
11216
+ className: "size-2 fill-primary text-primary",
11217
+ "aria-label": "unsaved changes"
11218
+ }
11219
+ ),
11220
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate max-w-[120px] sm:max-w-[200px]", children: tab.name })
11221
+ ] })
11222
+ }
11223
+ ),
11224
+ /* @__PURE__ */ jsxRuntime.jsx(
11225
+ Button2,
11226
+ {
11227
+ variant: "ghost",
11228
+ size: "icon-sm",
11229
+ onClick: (e) => {
11230
+ e.stopPropagation();
11231
+ onTabClose == null ? void 0 : onTabClose(tab.id);
11232
+ },
11233
+ className: cn(
11234
+ "h-8 w-8 mr-0.5",
11235
+ "hover:bg-destructive/10 hover:text-destructive",
11236
+ "focus-visible:ring-2 focus-visible:ring-ring",
11237
+ "transition-colors"
11238
+ ),
11239
+ "aria-label": `Close ${tab.name}`,
11240
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "x", size: "xs" })
11241
+ }
11242
+ )
11243
+ ]
11130
11244
  },
11131
- className: cn(
11132
- "h-8 w-8 mr-0.5",
11133
- "hover:bg-destructive/10 hover:text-destructive",
11134
- "focus-visible:ring-2 focus-visible:ring-ring",
11135
- "transition-colors"
11136
- ),
11137
- "aria-label": `Close ${tab.name}`,
11138
- children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "x", size: "xs" })
11139
- }
11140
- )
11141
- ]
11142
- },
11143
- tab.id
11144
- )) })
11245
+ tab.id
11246
+ )) })
11247
+ }
11248
+ ) }),
11249
+ canScrollRight && /* @__PURE__ */ jsxRuntime.jsx(
11250
+ "div",
11251
+ {
11252
+ className: "pointer-events-none absolute right-0 top-0 bottom-0 w-5 bg-gradient-to-l from-background to-transparent z-10",
11253
+ "aria-hidden": "true"
11254
+ }
11255
+ )
11256
+ ] }),
11257
+ showScrollArrows && canScrollRight && /* @__PURE__ */ jsxRuntime.jsx(
11258
+ Button2,
11259
+ {
11260
+ variant: "ghost",
11261
+ size: "icon-sm",
11262
+ onClick: handleScrollRight,
11263
+ className: "h-10 w-7 shrink-0 rounded-none border-l border-border/50 text-muted-foreground hover:text-foreground transition-colors z-10",
11264
+ "aria-label": "Scroll tabs right",
11265
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron-right", size: "xs" })
11266
+ }
11267
+ )
11268
+ ]
11145
11269
  }
11146
- ) })
11270
+ )
11147
11271
  ]
11148
11272
  }
11149
11273
  );