ai-design-system 0.1.112 → 0.1.114

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.
@@ -22,12 +22,21 @@ export const SectionLayout = React.memo<SectionLayoutProps>(
22
22
  dragHandleColor = "border",
23
23
  className,
24
24
  padded = true,
25
+ mobileBehavior = "tabs",
26
+ hideMobileTabs = false,
27
+ activeSectionId,
28
+ onActiveSectionChange,
25
29
  ...props
26
30
  }) => {
27
- // Transform sections to include headers
31
+ // Transform sections to include headers and resolve labels
28
32
  const transformedSections = sections.map(section => ({
29
33
  ...section,
30
34
  resizable,
35
+ label:
36
+ section.label ||
37
+ section.title ||
38
+ (typeof section.header?.title === "string" ? section.header.title : undefined) ||
39
+ section.header?.tabs?.[0]?.label,
31
40
  content: (
32
41
  <div className="h-full min-h-0 flex flex-col overflow-hidden">
33
42
  {section.header && (
@@ -49,6 +58,10 @@ export const SectionLayout = React.memo<SectionLayoutProps>(
49
58
  dragHandleColor={dragHandleColor}
50
59
  className={className}
51
60
  padded={padded}
61
+ mobileBehavior={mobileBehavior}
62
+ hideMobileTabs={hideMobileTabs}
63
+ activeSectionId={activeSectionId}
64
+ onActiveSectionChange={onActiveSectionChange}
52
65
  {...props}
53
66
  />
54
67
  )
@@ -4,6 +4,8 @@ import type { AppHeaderProps } from "@/components/composites/AppHeader/interface
4
4
  export interface SectionLayoutSection {
5
5
  id: string;
6
6
  content: React.ReactNode;
7
+ label?: string;
8
+ title?: string;
7
9
  fixedSize?: string;
8
10
  defaultSize?: number;
9
11
  minSize?: number;
@@ -22,4 +24,8 @@ export interface SectionLayoutProps extends React.ComponentPropsWithoutRef<"div"
22
24
  resizable?: boolean;
23
25
  dragHandleColor?: "primary" | "secondary" | "accent" | "border" | "muted";
24
26
  padded?: boolean;
27
+ mobileBehavior?: "tabs" | "stack" | "none";
28
+ hideMobileTabs?: boolean;
29
+ activeSectionId?: string;
30
+ onActiveSectionChange?: (sectionId: string) => void;
25
31
  }
@@ -95,10 +95,19 @@ export const Default: Story = {
95
95
  * Simple 2-panel layout with resizable divider.
96
96
  */
97
97
  export const TwoPanels: Story = {
98
+ parameters: {
99
+ docs: {
100
+ description: {
101
+ story:
102
+ 'Responsive 2-panel layout. On desktop viewports (>= 768px), panels sit side-by-side with a resizable divider. On mobile viewports (< 768px), panels adapt into an accessible tabbed segmented view giving full width and height to each panel.',
103
+ },
104
+ },
105
+ },
98
106
  render: () => {
99
107
  const sections: AdjustableLayoutSection[] = [
100
108
  {
101
109
  id: 'main',
110
+ label: 'Main Content',
102
111
  content: (
103
112
  <Card className="h-full">
104
113
  <CardHeader>
@@ -113,6 +122,7 @@ export const TwoPanels: Story = {
113
122
  },
114
123
  {
115
124
  id: 'sidebar',
125
+ label: 'Sidebar',
116
126
  content: (
117
127
  <Card className="h-full">
118
128
  <CardHeader>
@@ -1,9 +1,12 @@
1
1
  import * as React from "react"
2
2
  import { cn } from "@/lib/utils"
3
+ import { Tabs, TabsList, TabsTrigger } from "@/components/primitives/Tabs"
3
4
 
4
5
  export interface AdjustableLayoutSection {
5
6
  id: string
6
7
  content: React.ReactNode
8
+ label?: string
9
+ title?: string
7
10
  fixedSize?: string // CSS size value, e.g. "16rem"
8
11
  defaultSize?: number // percentage (0-100)
9
12
  minSize?: number // minimum percentage
@@ -20,6 +23,37 @@ export interface AdjustableLayoutProps extends React.ComponentPropsWithoutRef<"d
20
23
  onSectionResize?: (sectionId: string, newSize: number) => void
21
24
  dragHandleColor?: "primary" | "secondary" | "accent" | "border" | "muted"
22
25
  padded?: boolean
26
+ /**
27
+ * Layout behavior on mobile screens (< 768px).
28
+ * - 'tabs': Renders a mobile tab bar/segmented control to toggle between panels (default)
29
+ * - 'stack': Stacks panels vertically (flex-col)
30
+ * - 'none': Keeps standard orientation without mobile adaptation
31
+ * @default 'tabs'
32
+ */
33
+ mobileBehavior?: "tabs" | "stack" | "none"
34
+ /**
35
+ * Whether to hide the mobile tab bar when mobileBehavior is "tabs".
36
+ * Used when switching between panels is driven by internal controls (e.g. Overview / Show Chat).
37
+ * @default false
38
+ */
39
+ hideMobileTabs?: boolean
40
+ /**
41
+ * Active section ID in mobile tabs mode (for controlled usage).
42
+ */
43
+ activeSectionId?: string
44
+ /**
45
+ * Callback when active section changes in mobile tabs mode.
46
+ */
47
+ onActiveSectionChange?: (sectionId: string) => void
48
+ }
49
+
50
+ function formatSectionTitle(id: string): string {
51
+ if (!id) return "Panel"
52
+ return id
53
+ .split(/[-_]/)
54
+ .filter(Boolean)
55
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
56
+ .join(" ")
23
57
  }
24
58
 
25
59
  /**
@@ -38,6 +72,10 @@ export const AdjustableLayout = React.memo<AdjustableLayoutProps>(
38
72
  dragHandleColor = "border",
39
73
  className,
40
74
  padded = false,
75
+ mobileBehavior = "tabs",
76
+ hideMobileTabs = false,
77
+ activeSectionId,
78
+ onActiveSectionChange,
41
79
  ...props
42
80
  }) => {
43
81
  // Color mapping for drag handles
@@ -46,21 +84,57 @@ export const AdjustableLayout = React.memo<AdjustableLayoutProps>(
46
84
  secondary: "bg-secondary hover:bg-secondary/80",
47
85
  accent: "bg-accent hover:bg-accent/80",
48
86
  border: "bg-border hover:bg-border/80",
49
- muted: "bg-muted hover:bg-muted/80"
87
+ muted: "bg-muted hover:bg-muted/80",
50
88
  }
51
89
 
52
- const hoverColorMap = {
53
- primary: "group-hover:bg-primary/30",
54
- secondary: "group-hover:bg-secondary/30",
55
- accent: "group-hover:bg-accent/30",
56
- border: "group-hover:bg-border/30",
57
- muted: "group-hover:bg-muted/30"
58
- }
59
90
  const containerRef = React.useRef<HTMLDivElement>(null)
60
91
 
92
+ // Responsive mobile detection
93
+ const [isMobile, setIsMobile] = React.useState(false)
94
+
95
+ React.useEffect(() => {
96
+ const checkMobile = () => {
97
+ const winMobile = typeof window !== "undefined" && window.innerWidth < 768
98
+ const containerWidth = containerRef.current?.getBoundingClientRect().width ?? 0
99
+ const containerMobile = containerWidth > 0 && containerWidth < 768
100
+ setIsMobile(winMobile || containerMobile)
101
+ }
102
+
103
+ checkMobile()
104
+ window.addEventListener("resize", checkMobile)
105
+ return () => window.removeEventListener("resize", checkMobile)
106
+ }, [])
107
+
108
+ // Active section state for mobile tabs mode
109
+ const [internalActiveId, setInternalActiveId] = React.useState<string>(
110
+ sections[0]?.id ?? ""
111
+ )
112
+
113
+ React.useEffect(() => {
114
+ if (sections.length > 0 && !sections.some((s) => s.id === internalActiveId)) {
115
+ setInternalActiveId(sections[0].id)
116
+ }
117
+ }, [sections, internalActiveId])
118
+
119
+ const currentActiveId =
120
+ (activeSectionId && sections.some((s) => s.id === activeSectionId)
121
+ ? activeSectionId
122
+ : undefined) ??
123
+ (sections.some((s) => s.id === internalActiveId)
124
+ ? internalActiveId
125
+ : (sections[0]?.id ?? ""))
126
+
127
+ const handleTabChange = React.useCallback(
128
+ (newId: string) => {
129
+ setInternalActiveId(newId)
130
+ onActiveSectionChange?.(newId)
131
+ },
132
+ [onActiveSectionChange]
133
+ )
134
+
61
135
  // Compute default sizes (server-safe — no localStorage access)
62
136
  const defaultSizes = React.useMemo(() => {
63
- const raw = sections.map((section) => section.defaultSize ?? (100 / sections.length))
137
+ const raw = sections.map((section) => section.defaultSize ?? 100 / sections.length)
64
138
  const total = raw.reduce((sum, size) => sum + size, 0)
65
139
  return raw.map((size) => (size / total) * 100)
66
140
  }, [sections])
@@ -107,99 +181,136 @@ export const AdjustableLayout = React.memo<AdjustableLayoutProps>(
107
181
  }
108
182
 
109
183
  updateContainerSize()
110
- window.addEventListener('resize', updateContainerSize)
111
- return () => window.removeEventListener('resize', updateContainerSize)
184
+ window.addEventListener("resize", updateContainerSize)
185
+ return () => window.removeEventListener("resize", updateContainerSize)
112
186
  }, [orientation])
113
187
 
114
188
  // Save to localStorage when sizes change (only in browser)
115
189
  React.useEffect(() => {
116
- if (storageKey && typeof window !== 'undefined') {
190
+ if (storageKey && typeof window !== "undefined") {
117
191
  localStorage.setItem(storageKey, JSON.stringify(sizes))
118
192
  }
119
193
  }, [sizes, storageKey])
120
194
 
121
- const handleMouseDown = React.useCallback((index: number, e: React.MouseEvent) => {
122
- e.preventDefault()
123
- setDraggingIndex(index)
124
- setStartX(orientation === "horizontal" ? e.clientX : e.clientY)
125
- setStartSizes([...sizes])
126
- }, [sizes, orientation])
127
-
128
- const handleMouseMove = React.useCallback((e: MouseEvent) => {
129
- if (draggingIndex === null || containerSize === 0) return
130
-
131
- const currentX = orientation === "horizontal" ? e.clientX : e.clientY
132
- const deltaX = currentX - startX
133
-
134
- const deltaPercent = (deltaX / containerSize) * 100
135
-
136
- const p1 = sections[draggingIndex]
137
- const p2 = sections[draggingIndex + 1]
138
-
139
- const p1Start = startSizes[draggingIndex]
140
- const p2Start = startSizes[draggingIndex + 1]
141
-
142
- const p1Min = p1.minSize ?? 10
143
- const p1Max = p1.maxSize ?? 80
144
- const p2Min = p2.minSize ?? 10
145
- const p2Max = p2.maxSize ?? 80
146
-
147
- // Calculate how much we can actually change panel 1
148
- // If deltaPercent > 0, we are growing p1 and shrinking p2
149
- const maxPositiveDelta = Math.max(0, Math.min(
150
- p1Max - p1Start, // Space p1 has to grow
151
- p2Start - p2Min // Space p2 has to shrink
152
- ))
153
-
154
- // If deltaPercent < 0, we are shrinking p1 and growing p2
155
- const maxNegativeDelta = Math.min(0, Math.max(
156
- p1Min - p1Start, // Space p1 has to shrink (negative)
157
- p2Start - p2Max // Space p2 has to grow (negative)
158
- ))
159
-
160
- // Clamp the delta
161
- let clampedDelta = deltaPercent
162
- if (clampedDelta > 0) {
163
- clampedDelta = Math.min(clampedDelta, maxPositiveDelta)
164
- } else {
165
- clampedDelta = Math.max(clampedDelta, maxNegativeDelta)
166
- }
195
+ const handleMouseDown = React.useCallback(
196
+ (index: number, e: React.MouseEvent) => {
197
+ e.preventDefault()
198
+ setDraggingIndex(index)
199
+ setStartX(orientation === "horizontal" ? e.clientX : e.clientY)
200
+ setStartSizes([...sizes])
201
+ },
202
+ [sizes, orientation]
203
+ )
204
+
205
+ const handleTouchStart = React.useCallback(
206
+ (index: number, e: React.TouchEvent) => {
207
+ if (e.touches.length !== 1) return
208
+ setDraggingIndex(index)
209
+ setStartX(orientation === "horizontal" ? e.touches[0].clientX : e.touches[0].clientY)
210
+ setStartSizes([...sizes])
211
+ },
212
+ [sizes, orientation]
213
+ )
167
214
 
168
- const newSizes = [...startSizes]
169
- newSizes[draggingIndex] = p1Start + clampedDelta
170
- newSizes[draggingIndex + 1] = p2Start - clampedDelta
215
+ const applyDelta = React.useCallback(
216
+ (deltaPixels: number) => {
217
+ if (draggingIndex === null || containerSize === 0) return
218
+
219
+ const deltaPercent = (deltaPixels / containerSize) * 100
220
+
221
+ const p1 = sections[draggingIndex]
222
+ const p2 = sections[draggingIndex + 1]
223
+
224
+ const p1Start = startSizes[draggingIndex]
225
+ const p2Start = startSizes[draggingIndex + 1]
226
+
227
+ const p1Min = p1.minSize ?? 10
228
+ const p1Max = p1.maxSize ?? 80
229
+ const p2Min = p2.minSize ?? 10
230
+ const p2Max = p2.maxSize ?? 80
231
+
232
+ // Calculate how much we can actually change panel 1
233
+ const maxPositiveDelta = Math.max(
234
+ 0,
235
+ Math.min(
236
+ p1Max - p1Start, // Space p1 has to grow
237
+ p2Start - p2Min // Space p2 has to shrink
238
+ )
239
+ )
240
+
241
+ const maxNegativeDelta = Math.min(
242
+ 0,
243
+ Math.max(
244
+ p1Min - p1Start, // Space p1 has to shrink (negative)
245
+ p2Start - p2Max // Space p2 has to grow (negative)
246
+ )
247
+ )
248
+
249
+ // Clamp the delta
250
+ let clampedDelta = deltaPercent
251
+ if (clampedDelta > 0) {
252
+ clampedDelta = Math.min(clampedDelta, maxPositiveDelta)
253
+ } else {
254
+ clampedDelta = Math.max(clampedDelta, maxNegativeDelta)
255
+ }
171
256
 
172
- // Normalize to 100% just in case of floating point drift
173
- const total = newSizes.reduce((sum, size) => sum + size, 0)
174
- const normalizedSizes = newSizes.map(size => (size / total) * 100)
257
+ const newSizes = [...startSizes]
258
+ newSizes[draggingIndex] = p1Start + clampedDelta
259
+ newSizes[draggingIndex + 1] = p2Start - clampedDelta
175
260
 
176
- setSizes(normalizedSizes)
261
+ // Normalize to 100% just in case of floating point drift
262
+ const total = newSizes.reduce((sum, size) => sum + size, 0)
263
+ const normalizedSizes = newSizes.map((size) => (size / total) * 100)
177
264
 
178
- // Notify parent
179
- if (onSectionResize) {
180
- onSectionResize(sections[draggingIndex].id, normalizedSizes[draggingIndex])
181
- onSectionResize(sections[draggingIndex + 1].id, normalizedSizes[draggingIndex + 1])
182
- }
183
- }, [draggingIndex, startX, startSizes, sections, orientation, containerSize, onSectionResize])
265
+ setSizes(normalizedSizes)
266
+
267
+ // Notify parent
268
+ if (onSectionResize) {
269
+ onSectionResize(sections[draggingIndex].id, normalizedSizes[draggingIndex])
270
+ onSectionResize(sections[draggingIndex + 1].id, normalizedSizes[draggingIndex + 1])
271
+ }
272
+ },
273
+ [draggingIndex, containerSize, sections, startSizes, onSectionResize]
274
+ )
184
275
 
185
- const handleMouseUp = React.useCallback(() => {
276
+ const handleMouseMove = React.useCallback(
277
+ (e: MouseEvent) => {
278
+ const currentPos = orientation === "horizontal" ? e.clientX : e.clientY
279
+ applyDelta(currentPos - startX)
280
+ },
281
+ [orientation, startX, applyDelta]
282
+ )
283
+
284
+ const handleTouchMove = React.useCallback(
285
+ (e: TouchEvent) => {
286
+ if (e.touches.length !== 1) return
287
+ const currentPos = orientation === "horizontal" ? e.touches[0].clientX : e.touches[0].clientY
288
+ applyDelta(currentPos - startX)
289
+ },
290
+ [orientation, startX, applyDelta]
291
+ )
292
+
293
+ const handleEnd = React.useCallback(() => {
186
294
  setDraggingIndex(null)
187
295
  }, [])
188
296
 
189
297
  React.useEffect(() => {
190
298
  if (draggingIndex !== null) {
191
299
  document.addEventListener("mousemove", handleMouseMove)
192
- document.addEventListener("mouseup", handleMouseUp)
300
+ document.addEventListener("mouseup", handleEnd)
301
+ document.addEventListener("touchmove", handleTouchMove, { passive: false })
302
+ document.addEventListener("touchend", handleEnd)
193
303
  return () => {
194
304
  document.removeEventListener("mousemove", handleMouseMove)
195
- document.removeEventListener("mouseup", handleMouseUp)
305
+ document.removeEventListener("mouseup", handleEnd)
306
+ document.removeEventListener("touchmove", handleTouchMove)
307
+ document.removeEventListener("touchend", handleEnd)
196
308
  }
197
309
  }
198
- }, [draggingIndex, handleMouseMove, handleMouseUp])
310
+ }, [draggingIndex, handleMouseMove, handleTouchMove, handleEnd])
199
311
 
200
312
  const renderPanel = (section: AdjustableLayoutSection, size: number, index: number) => {
201
313
  const nextSection = sections[index + 1]
202
- // Drag handles should be between panels, so only show for panels that aren't the last one
203
314
  const isResizable =
204
315
  section.resizable !== false &&
205
316
  sections.length > 1 &&
@@ -230,25 +341,23 @@ export const AdjustableLayout = React.memo<AdjustableLayoutProps>(
230
341
  {section.content}
231
342
  </div>
232
343
 
233
- {/* Show drag handle or spacer after this panel if it's not the last one */}
234
344
  {index < sections.length - 1 && (
235
345
  isResizable ? (
236
346
  <div
237
347
  className={cn(
238
- "flex-shrink-0 flex items-center justify-center relative group",
348
+ "flex-shrink-0 flex items-center justify-center relative group touch-none select-none",
239
349
  orientation === "vertical"
240
350
  ? "cursor-row-resize h-2 w-full"
241
351
  : "cursor-col-resize w-2 h-full"
242
352
  )}
243
353
  onMouseDown={(e) => handleMouseDown(index, e)}
354
+ onTouchStart={(e) => handleTouchStart(index, e)}
244
355
  >
245
356
  {/* Visible pill */}
246
357
  <div
247
358
  className={cn(
248
359
  `${colorMap[dragHandleColor]} transition-colors duration-200 rounded-full`,
249
- orientation === "vertical"
250
- ? "h-1 w-8"
251
- : "w-1 h-8"
360
+ orientation === "vertical" ? "h-1 w-8" : "w-1 h-8"
252
361
  )}
253
362
  />
254
363
  {/* Invisible large hit area */}
@@ -274,6 +383,97 @@ export const AdjustableLayout = React.memo<AdjustableLayoutProps>(
274
383
  )
275
384
  }
276
385
 
386
+ const isMobileMode = isMobile && mobileBehavior !== "none"
387
+
388
+ if (isMobileMode && mobileBehavior === "tabs") {
389
+ return (
390
+ <div
391
+ ref={containerRef}
392
+ className={cn(
393
+ "flex flex-col overflow-hidden h-full min-w-0 w-full",
394
+ padded && (!hideMobileTabs ? "p-2 sm:p-4 gap-2" : "p-2 sm:p-4"),
395
+ className
396
+ )}
397
+ {...props}
398
+ >
399
+ {sections.length > 1 && !hideMobileTabs && (
400
+ <div className="flex-none pb-1">
401
+ <Tabs
402
+ value={currentActiveId}
403
+ onValueChange={handleTabChange}
404
+ className="w-full"
405
+ >
406
+ <TabsList
407
+ className="grid w-full h-9 bg-muted/60 p-1 rounded-lg"
408
+ style={{
409
+ gridTemplateColumns: `repeat(${sections.length}, minmax(0, 1fr))`,
410
+ }}
411
+ >
412
+ {sections.map((section) => (
413
+ <TabsTrigger
414
+ key={section.id}
415
+ value={section.id}
416
+ className="text-xs sm:text-sm font-medium truncate px-2 py-1 data-[state=active]:bg-background data-[state=active]:shadow-sm rounded-md"
417
+ >
418
+ {section.label || section.title || formatSectionTitle(section.id)}
419
+ </TabsTrigger>
420
+ ))}
421
+ </TabsList>
422
+ </Tabs>
423
+ </div>
424
+ )}
425
+
426
+ <div className="flex-1 min-h-0 relative overflow-hidden">
427
+ {sections.map((section) => {
428
+ const isActive = section.id === currentActiveId
429
+ return (
430
+ <div
431
+ key={section.id}
432
+ className={cn(
433
+ "h-full w-full min-h-0 overflow-hidden",
434
+ !isActive && "hidden",
435
+ (section.variant ?? "default") === "default" &&
436
+ "bg-card border border-border rounded-xl",
437
+ section.className
438
+ )}
439
+ >
440
+ {section.content}
441
+ </div>
442
+ )
443
+ })}
444
+ </div>
445
+ </div>
446
+ )
447
+ }
448
+
449
+ if (isMobileMode && mobileBehavior === "stack") {
450
+ return (
451
+ <div
452
+ ref={containerRef}
453
+ className={cn(
454
+ "flex flex-col overflow-y-auto h-full gap-2 min-w-0 w-full",
455
+ padded && "p-2 sm:p-4",
456
+ className
457
+ )}
458
+ {...props}
459
+ >
460
+ {sections.map((section) => (
461
+ <div
462
+ key={section.id}
463
+ className={cn(
464
+ "min-h-[200px] overflow-hidden w-full flex-shrink-0",
465
+ (section.variant ?? "default") === "default" &&
466
+ "bg-card border border-border rounded-xl",
467
+ section.className
468
+ )}
469
+ >
470
+ {section.content}
471
+ </div>
472
+ ))}
473
+ </div>
474
+ )
475
+ }
476
+
277
477
  return (
278
478
  <div
279
479
  ref={containerRef}
@@ -45,6 +45,11 @@ export interface DocumentTabBarProps {
45
45
  * Additional CSS classes
46
46
  */
47
47
  className?: string
48
+
49
+ /**
50
+ * Callback to open/toggle the file explorer (e.g. mobile drawer)
51
+ */
52
+ onToggleExplorer?: () => void
48
53
  }
49
54
 
50
55
  /**
@@ -63,8 +68,8 @@ export interface DocumentTabBarProps {
63
68
  * - Dirty state indicator
64
69
  */
65
70
  export const DocumentTabBar = React.memo<DocumentTabBarProps>(
66
- ({ tabs, activeTabId, onTabSelect, onTabClose, className }) => {
67
- if (tabs.length === 0) {
71
+ ({ tabs, activeTabId, onTabSelect, onTabClose, onToggleExplorer, className }) => {
72
+ if (tabs.length === 0 && !onToggleExplorer) {
68
73
  return null
69
74
  }
70
75
 
@@ -76,12 +81,24 @@ export const DocumentTabBar = React.memo<DocumentTabBarProps>(
76
81
  )}
77
82
  data-slot="document-tab-bar"
78
83
  >
79
- <ScrollArea className="flex-1">
80
- <Tabs
81
- value={activeTabId || tabs[0]?.id}
82
- onValueChange={onTabSelect}
83
- className="h-auto flex-1"
84
+ {onToggleExplorer && (
85
+ <Button
86
+ variant="ghost"
87
+ size="icon-sm"
88
+ onClick={onToggleExplorer}
89
+ className="h-10 w-10 shrink-0 rounded-none border-r border-border/50 text-muted-foreground hover:text-foreground md:hidden"
90
+ aria-label="Toggle file explorer"
84
91
  >
92
+ <Icon name="folder" size="xs" />
93
+ </Button>
94
+ )}
95
+ {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
+ >
85
102
  <TabsList className="h-10 rounded-none border-none bg-transparent p-0 w-full justify-start gap-0">
86
103
  {tabs.map((tab) => (
87
104
  <div
@@ -93,13 +110,13 @@ export const DocumentTabBar = React.memo<DocumentTabBarProps>(
93
110
  className={cn(
94
111
  'data-[state=inactive]:bg-muted/20 data-[state=inactive]:text-muted-foreground',
95
112
  'rounded-none border-b-2 border-transparent data-[state=active]:border-primary',
96
- 'px-3 py-2 text-sm font-medium whitespace-nowrap',
97
- 'flex items-center gap-2 h-10',
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',
98
115
  'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0',
99
116
  'transition-colors'
100
117
  )}
101
118
  >
102
- <div className="flex items-center gap-2">
119
+ <div className="flex items-center gap-1.5 sm:gap-2">
103
120
  {/* Dirty indicator */}
104
121
  {tab.isDirty && (
105
122
  <Icon
@@ -109,7 +126,7 @@ export const DocumentTabBar = React.memo<DocumentTabBarProps>(
109
126
  />
110
127
  )}
111
128
  {/* Tab name */}
112
- <span className="truncate max-w-[200px]">{tab.name}</span>
129
+ <span className="truncate max-w-[120px] sm:max-w-[200px]">{tab.name}</span>
113
130
  </div>
114
131
  </TabsTrigger>
115
132
 
@@ -136,6 +153,7 @@ export const DocumentTabBar = React.memo<DocumentTabBarProps>(
136
153
  </TabsList>
137
154
  </Tabs>
138
155
  </ScrollArea>
156
+ )}
139
157
  </div>
140
158
  )
141
159
  }