@leanspec/ui 0.2.6-dev.20251125070051 → 0.2.6-dev.20251125092039
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.
- package/.next/standalone/packages/ui/.next/BUILD_ID +1 -1
- package/.next/standalone/packages/ui/.next/build-manifest.json +2 -2
- package/.next/standalone/packages/ui/.next/prerender-manifest.json +3 -3
- package/.next/standalone/packages/ui/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/packages/ui/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_not-found.html +2 -2
- package/.next/standalone/packages/ui/.next/server/app/_not-found.rsc +17 -17
- package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_full.segment.rsc +17 -17
- package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
- package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +3 -3
- package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_tree.segment.rsc +5 -5
- package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/specs/[spec]/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/specs/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/stats/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/revalidate/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/dependency-graph/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/status/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/subspecs/[file]/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/stats/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/[specId]/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/[specId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects.html +2 -2
- package/.next/standalone/packages/ui/.next/server/app/projects.rsc +11 -11
- package/.next/standalone/packages/ui/.next/server/app/projects.segments/_full.segment.rsc +11 -11
- package/.next/standalone/packages/ui/.next/server/app/projects.segments/_index.segment.rsc +9 -9
- package/.next/standalone/packages/ui/.next/server/app/projects.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/packages/ui/.next/server/app/projects.segments/projects/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/packages/ui/.next/server/app/projects.segments/projects.segment.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/specs/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/specs/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/stats/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/stats/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__1d0c2012._.js +1 -1
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_14118969._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_497c8b73._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_c2f54661._.js +1 -1
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_app_specs_specs-client_tsx_0bb8f8f8._.js +1 -1
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_components_spec-detail-loading-shell_tsx_f7136fb6._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_components_specs-nav-sidebar_tsx_8237ed13._.js +1 -1
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_components_ui_select_tsx_7e21d40f._.js +1 -1
- package/.next/standalone/packages/ui/.next/server/pages/404.html +2 -2
- package/.next/standalone/packages/ui/.next/server/pages/500.html +2 -2
- package/.next/standalone/packages/ui/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/packages/ui/.next/static/chunks/15ea4a571afd2593.css +1 -0
- package/.next/standalone/packages/ui/.next/static/chunks/{a055d4b2866ff8d4.js → 1c1c8333e0a19d47.js} +1 -1
- package/.next/standalone/packages/ui/.next/static/chunks/1fe09300b3bcaa23.js +1 -0
- package/.next/standalone/packages/ui/.next/static/chunks/3f4207bc02becc30.js +1 -0
- package/.next/standalone/packages/ui/.next/static/chunks/{afb7c5a4255f4081.js → 43bc30cd222b1e74.js} +1 -1
- package/.next/standalone/packages/ui/.next/static/chunks/5f0014bb843d2f45.js +1 -0
- package/.next/{static/chunks/17471c87082c392c.js → standalone/packages/ui/.next/static/chunks/86bf3f94f2b0b8fc.js} +2 -2
- package/.next/standalone/packages/ui/.next/static/chunks/96339d2504976e86.js +1 -0
- package/.next/standalone/packages/ui/.next/static/chunks/d4de5562fb8f6e76.js +1 -0
- package/.next/standalone/packages/ui/leanspec.db +0 -0
- package/.next/standalone/packages/ui/package.json +1 -1
- package/.next/standalone/packages/ui/src/app/globals.css +25 -0
- package/.next/standalone/packages/ui/src/app/specs/specs-client.tsx +180 -140
- package/.next/standalone/packages/ui/src/components/main-sidebar.tsx +3 -3
- package/.next/standalone/packages/ui/src/components/spec-detail-client.tsx +25 -7
- package/.next/standalone/packages/ui/src/components/specs-nav-sidebar.tsx +3 -13
- package/.next/standalone/packages/ui/tsconfig.tsbuildinfo +1 -1
- package/.next/static/chunks/15ea4a571afd2593.css +1 -0
- package/.next/static/chunks/{a055d4b2866ff8d4.js → 1c1c8333e0a19d47.js} +1 -1
- package/.next/static/chunks/1fe09300b3bcaa23.js +1 -0
- package/.next/static/chunks/3f4207bc02becc30.js +1 -0
- package/.next/static/chunks/{afb7c5a4255f4081.js → 43bc30cd222b1e74.js} +1 -1
- package/.next/static/chunks/5f0014bb843d2f45.js +1 -0
- package/.next/{standalone/packages/ui/.next/static/chunks/17471c87082c392c.js → static/chunks/86bf3f94f2b0b8fc.js} +2 -2
- package/.next/static/chunks/96339d2504976e86.js +1 -0
- package/.next/static/chunks/d4de5562fb8f6e76.js +1 -0
- package/package.json +1 -1
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_196b5a83._.js +0 -3
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_6e4dd8b6._.js +0 -3
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_f1e82b43._.js +0 -3
- package/.next/standalone/packages/ui/.next/static/chunks/35ccd3286e538bd2.js +0 -1
- package/.next/standalone/packages/ui/.next/static/chunks/5a6fc30cd64085b4.css +0 -1
- package/.next/standalone/packages/ui/.next/static/chunks/843e1dfdc28e1385.js +0 -1
- package/.next/standalone/packages/ui/.next/static/chunks/9dad7fd31627df80.js +0 -1
- package/.next/standalone/packages/ui/.next/static/chunks/f9db24028d8c058e.js +0 -1
- package/.next/standalone/packages/ui/.next/static/chunks/fb640f50455ba34f.js +0 -1
- package/.next/static/chunks/35ccd3286e538bd2.js +0 -1
- package/.next/static/chunks/5a6fc30cd64085b4.css +0 -1
- package/.next/static/chunks/843e1dfdc28e1385.js +0 -1
- package/.next/static/chunks/9dad7fd31627df80.js +0 -1
- package/.next/static/chunks/f9db24028d8c058e.js +0 -1
- package/.next/static/chunks/fb640f50455ba34f.js +0 -1
- /package/.next/standalone/packages/ui/.next/static/{mihnqErRVLjaEzoU8MqEA → XaKCij6ONZQYRwoNl_cx7}/_buildManifest.js +0 -0
- /package/.next/standalone/packages/ui/.next/static/{mihnqErRVLjaEzoU8MqEA → XaKCij6ONZQYRwoNl_cx7}/_clientMiddlewareManifest.json +0 -0
- /package/.next/standalone/packages/ui/.next/static/{mihnqErRVLjaEzoU8MqEA → XaKCij6ONZQYRwoNl_cx7}/_ssgManifest.js +0 -0
- /package/.next/static/{mihnqErRVLjaEzoU8MqEA → XaKCij6ONZQYRwoNl_cx7}/_buildManifest.js +0 -0
- /package/.next/static/{mihnqErRVLjaEzoU8MqEA → XaKCij6ONZQYRwoNl_cx7}/_clientMiddlewareManifest.json +0 -0
- /package/.next/static/{mihnqErRVLjaEzoU8MqEA → XaKCij6ONZQYRwoNl_cx7}/_ssgManifest.js +0 -0
|
@@ -26,7 +26,9 @@ import {
|
|
|
26
26
|
FileText,
|
|
27
27
|
GitBranch,
|
|
28
28
|
Maximize2,
|
|
29
|
-
Minimize2
|
|
29
|
+
Minimize2,
|
|
30
|
+
ChevronDown,
|
|
31
|
+
ChevronRight
|
|
30
32
|
} from 'lucide-react';
|
|
31
33
|
import { StatusBadge } from '@/components/status-badge';
|
|
32
34
|
import { PriorityBadge } from '@/components/priority-badge';
|
|
@@ -254,41 +256,44 @@ export function SpecsClient({ initialSpecs, projectId }: SpecsClientProps) {
|
|
|
254
256
|
}, [specs, searchQuery, statusFilter, priorityFilter, sortBy, viewMode]);
|
|
255
257
|
|
|
256
258
|
return (
|
|
257
|
-
<div className="h-[calc(100vh-3.5rem)] flex flex-col overflow-hidden bg-background p-4">
|
|
259
|
+
<div className="h-[calc(100vh-3.5rem)] flex flex-col overflow-hidden bg-background p-2 sm:p-4">
|
|
258
260
|
<div className={cn(
|
|
259
261
|
"flex flex-col h-full mx-auto transition-all duration-300",
|
|
260
262
|
isWideMode ? "w-full" : "max-w-7xl w-full"
|
|
261
263
|
)}>
|
|
262
264
|
{/* Unified Compact Header */}
|
|
263
|
-
<div className="flex-none mb-4">
|
|
264
|
-
<div className="flex flex-col gap-4">
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
<
|
|
265
|
+
<div className="flex-none mb-3 sm:mb-4">
|
|
266
|
+
<div className="flex flex-col gap-2 sm:gap-3 md:gap-4">
|
|
267
|
+
{/* Title and Controls Row */}
|
|
268
|
+
<div className="flex items-center justify-between gap-2">
|
|
269
|
+
<div className="min-w-0">
|
|
270
|
+
<h1 className="text-lg sm:text-xl md:text-2xl font-bold tracking-tight truncate">Specifications</h1>
|
|
271
|
+
<p className="text-xs sm:text-sm text-muted-foreground">
|
|
269
272
|
{filteredAndSortedSpecs.length} specs
|
|
270
273
|
</p>
|
|
271
274
|
</div>
|
|
272
275
|
|
|
273
|
-
<div className="flex items-center gap-2">
|
|
274
|
-
<div className="flex items-center gap-
|
|
276
|
+
<div className="flex items-center gap-1.5 sm:gap-2 flex-shrink-0">
|
|
277
|
+
<div className="flex items-center gap-0.5 sm:gap-1 bg-muted/50 p-0.5 sm:p-1 rounded-lg">
|
|
275
278
|
<Button
|
|
276
279
|
variant={viewMode === 'list' ? 'secondary' : 'ghost'}
|
|
277
280
|
size="sm"
|
|
278
281
|
onClick={() => setViewMode('list')}
|
|
279
|
-
className="h-8 px-2
|
|
282
|
+
className="h-7 sm:h-8 px-2 sm:px-3"
|
|
283
|
+
title="List view"
|
|
280
284
|
>
|
|
281
|
-
<ListIcon className="h-
|
|
282
|
-
<span className="hidden lg:inline">List</span>
|
|
285
|
+
<ListIcon className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
|
|
286
|
+
<span className="hidden lg:inline ml-2">List</span>
|
|
283
287
|
</Button>
|
|
284
288
|
<Button
|
|
285
289
|
variant={viewMode === 'board' ? 'secondary' : 'ghost'}
|
|
286
290
|
size="sm"
|
|
287
291
|
onClick={() => setViewMode('board')}
|
|
288
|
-
className="h-8 px-2
|
|
292
|
+
className="h-7 sm:h-8 px-2 sm:px-3"
|
|
293
|
+
title="Board view"
|
|
289
294
|
>
|
|
290
|
-
<LayoutGrid className="h-
|
|
291
|
-
<span className="hidden lg:inline">Board</span>
|
|
295
|
+
<LayoutGrid className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
|
|
296
|
+
<span className="hidden lg:inline ml-2">Board</span>
|
|
292
297
|
</Button>
|
|
293
298
|
</div>
|
|
294
299
|
|
|
@@ -296,62 +301,67 @@ export function SpecsClient({ initialSpecs, projectId }: SpecsClientProps) {
|
|
|
296
301
|
variant="ghost"
|
|
297
302
|
size="icon"
|
|
298
303
|
onClick={() => setIsWideMode(!isWideMode)}
|
|
299
|
-
className="h-10 w-10 text-muted-foreground hover:text-foreground"
|
|
304
|
+
className="hidden md:flex h-8 w-8 sm:h-10 sm:w-10 text-muted-foreground hover:text-foreground"
|
|
300
305
|
title={isWideMode ? "Exit wide mode" : "Enter wide mode"}
|
|
301
306
|
>
|
|
302
|
-
{isWideMode ? <Minimize2 className="h-4 w-4" /> : <Maximize2 className="h-4 w-4" />}
|
|
307
|
+
{isWideMode ? <Minimize2 className="h-3.5 w-3.5 sm:h-4 sm:w-4" /> : <Maximize2 className="h-3.5 w-3.5 sm:h-4 sm:w-4" />}
|
|
303
308
|
</Button>
|
|
304
309
|
</div>
|
|
305
310
|
</div>
|
|
306
311
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
312
|
+
{/* Search and Filters Row */}
|
|
313
|
+
<div className="flex flex-col gap-2">
|
|
314
|
+
{/* Search Bar - Full width on mobile */}
|
|
315
|
+
<div className="relative w-full">
|
|
316
|
+
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 sm:h-4 sm:w-4 text-muted-foreground pointer-events-none" />
|
|
310
317
|
<Input
|
|
311
318
|
placeholder="Search specs..."
|
|
312
319
|
value={searchQuery}
|
|
313
320
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
314
|
-
className="pl-9 h-9"
|
|
321
|
+
className="pl-8 sm:pl-9 h-9 sm:h-10 w-full text-sm"
|
|
315
322
|
/>
|
|
316
323
|
</div>
|
|
317
324
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
<
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
<
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
<
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
325
|
+
{/* Filters - Horizontal scroll on mobile with snap points */}
|
|
326
|
+
<div className="flex items-center gap-2 overflow-x-auto snap-x snap-mandatory pb-1 -mx-2 px-2 sm:mx-0 sm:px-0 sm:pb-0 scrollbar-thin">
|
|
327
|
+
<Select value={statusFilter} onValueChange={(value) => setStatusFilter(value as SpecStatus | 'all')}>
|
|
328
|
+
<SelectTrigger className="w-[110px] sm:w-[130px] h-9 sm:h-10 flex-shrink-0 snap-start text-xs sm:text-sm">
|
|
329
|
+
<SelectValue placeholder="Status" />
|
|
330
|
+
</SelectTrigger>
|
|
331
|
+
<SelectContent>
|
|
332
|
+
<SelectItem value="all">All Status</SelectItem>
|
|
333
|
+
<SelectItem value="planned">Planned</SelectItem>
|
|
334
|
+
<SelectItem value="in-progress">In Progress</SelectItem>
|
|
335
|
+
<SelectItem value="complete">Complete</SelectItem>
|
|
336
|
+
<SelectItem value="archived">Archived</SelectItem>
|
|
337
|
+
</SelectContent>
|
|
338
|
+
</Select>
|
|
339
|
+
|
|
340
|
+
<Select value={priorityFilter} onValueChange={setPriorityFilter}>
|
|
341
|
+
<SelectTrigger className="w-[110px] sm:w-[130px] h-9 sm:h-10 flex-shrink-0 snap-start text-xs sm:text-sm">
|
|
342
|
+
<SelectValue placeholder="Priority" />
|
|
343
|
+
</SelectTrigger>
|
|
344
|
+
<SelectContent>
|
|
345
|
+
<SelectItem value="all">All Priority</SelectItem>
|
|
346
|
+
<SelectItem value="critical">Critical</SelectItem>
|
|
347
|
+
<SelectItem value="high">High</SelectItem>
|
|
348
|
+
<SelectItem value="medium">Medium</SelectItem>
|
|
349
|
+
<SelectItem value="low">Low</SelectItem>
|
|
350
|
+
</SelectContent>
|
|
351
|
+
</Select>
|
|
352
|
+
|
|
353
|
+
<Select value={sortBy} onValueChange={(v) => setSortBy(v as SortBy)}>
|
|
354
|
+
<SelectTrigger className="w-[130px] sm:w-[170px] h-9 sm:h-10 flex-shrink-0 snap-start text-xs sm:text-sm">
|
|
355
|
+
<SelectValue placeholder="Sort by" />
|
|
356
|
+
</SelectTrigger>
|
|
357
|
+
<SelectContent>
|
|
358
|
+
<SelectItem value="id-desc">Newest First</SelectItem>
|
|
359
|
+
<SelectItem value="id-asc">Oldest First</SelectItem>
|
|
360
|
+
<SelectItem value="updated-desc">Recently Updated</SelectItem>
|
|
361
|
+
<SelectItem value="title-asc">Title (A-Z)</SelectItem>
|
|
362
|
+
</SelectContent>
|
|
363
|
+
</Select>
|
|
364
|
+
</div>
|
|
355
365
|
</div>
|
|
356
366
|
</div>
|
|
357
367
|
</div>
|
|
@@ -382,7 +392,7 @@ export function SpecsClient({ initialSpecs, projectId }: SpecsClientProps) {
|
|
|
382
392
|
|
|
383
393
|
function ListView({ specs }: { specs: Spec[] }) {
|
|
384
394
|
return (
|
|
385
|
-
<div className="grid grid-cols-1 gap-4 pb-8">
|
|
395
|
+
<div className="grid grid-cols-1 gap-2 sm:gap-3 md:gap-4 pb-2 sm:pb-4 md:pb-8">
|
|
386
396
|
{specs.map(spec => {
|
|
387
397
|
const priorityColors = {
|
|
388
398
|
'critical': 'border-l-red-500',
|
|
@@ -398,78 +408,83 @@ function ListView({ specs }: { specs: Spec[] }) {
|
|
|
398
408
|
<Card
|
|
399
409
|
key={spec.id}
|
|
400
410
|
className={cn(
|
|
401
|
-
"hover:shadow-lg transition-all duration-150 hover:scale-[1.01] border-l-4 cursor-pointer",
|
|
411
|
+
"hover:shadow-lg active:shadow-xl transition-all duration-150 hover:scale-[1.01] active:scale-[0.99] border-l-4 cursor-pointer touch-manipulation",
|
|
402
412
|
borderColor
|
|
403
413
|
)}
|
|
404
414
|
onClick={() => window.location.href = `/specs/${spec.specNumber || spec.id}`}
|
|
405
415
|
>
|
|
406
|
-
|
|
407
|
-
|
|
416
|
+
{/* Mobile-optimized layout */}
|
|
417
|
+
<CardHeader className="pb-2 sm:pb-2.5 md:pb-3 px-3 sm:px-4 md:px-6 pt-3 sm:pt-4 md:pt-6">
|
|
418
|
+
<div className="flex flex-col gap-2 md:flex-row md:items-start md:justify-between md:gap-4">
|
|
408
419
|
<div className="flex-1 min-w-0">
|
|
409
420
|
<Link href={`/specs/${spec.specNumber || spec.id}`}>
|
|
410
|
-
<CardTitle className="text-lg font-semibold hover:text-primary transition-colors flex items-
|
|
421
|
+
<CardTitle className="text-sm sm:text-base md:text-lg font-semibold hover:text-primary transition-colors flex items-start flex-wrap gap-1.5 sm:gap-2 leading-snug sm:leading-normal">
|
|
411
422
|
{spec.specNumber ? (
|
|
412
|
-
<span className="font-mono text-base font-normal text-muted-foreground
|
|
423
|
+
<span className="font-mono text-xs sm:text-sm md:text-base font-normal text-muted-foreground flex-shrink-0">
|
|
413
424
|
#{spec.specNumber.toString().padStart(3, '0')}
|
|
414
425
|
</span>
|
|
415
426
|
) : null}
|
|
416
|
-
{spec.title || spec.specName}
|
|
427
|
+
<span className="flex-1 break-words">{spec.title || spec.specName}</span>
|
|
417
428
|
</CardTitle>
|
|
418
429
|
</Link>
|
|
419
430
|
{spec.title && spec.title !== spec.specName && (
|
|
420
|
-
<p className="text-xs font-mono text-muted-foreground mt-1.5 truncate">{spec.specName}</p>
|
|
431
|
+
<p className="text-[11px] sm:text-xs font-mono text-muted-foreground mt-1 sm:mt-1.5 truncate">{spec.specName}</p>
|
|
421
432
|
)}
|
|
422
433
|
</div>
|
|
423
|
-
|
|
434
|
+
{/* Badges: responsive layout */}
|
|
435
|
+
<div className="flex gap-1.5 sm:gap-2 flex-wrap md:shrink-0">
|
|
424
436
|
{spec.status && <StatusBadge status={spec.status} />}
|
|
425
437
|
{spec.priority && <PriorityBadge priority={spec.priority} />}
|
|
426
438
|
</div>
|
|
427
439
|
</div>
|
|
428
440
|
</CardHeader>
|
|
429
441
|
|
|
430
|
-
<CardContent className="
|
|
431
|
-
{/* Metadata
|
|
432
|
-
<div className="flex
|
|
433
|
-
{
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
<
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
<
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
<
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
442
|
+
<CardContent className="px-3 sm:px-4 md:px-6 pb-2.5 sm:pb-3 md:pb-6 pt-0">
|
|
443
|
+
{/* Metadata and Tags - Stack on mobile */}
|
|
444
|
+
<div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between md:gap-4">
|
|
445
|
+
{/* Metadata (Left) */}
|
|
446
|
+
<div className="flex items-center gap-2 sm:gap-3 md:gap-4 text-[11px] sm:text-xs md:text-sm text-muted-foreground flex-wrap">
|
|
447
|
+
{(spec.updatedAt || hasSubSpecs || hasDependencies) ? (
|
|
448
|
+
<>
|
|
449
|
+
{spec.updatedAt && (
|
|
450
|
+
<div className="flex items-center gap-1 sm:gap-1.5">
|
|
451
|
+
<Clock className="h-3 w-3 sm:h-3.5 sm:w-3.5 flex-shrink-0" />
|
|
452
|
+
<span className="whitespace-nowrap">Updated {formatRelativeTime(spec.updatedAt)}</span>
|
|
453
|
+
</div>
|
|
454
|
+
)}
|
|
455
|
+
{hasSubSpecs && (
|
|
456
|
+
<div className="flex items-center gap-1 sm:gap-1.5">
|
|
457
|
+
<FileText className="h-3 w-3 sm:h-3.5 sm:w-3.5 flex-shrink-0" />
|
|
458
|
+
<span className="whitespace-nowrap">+{spec.subSpecsCount} files</span>
|
|
459
|
+
</div>
|
|
460
|
+
)}
|
|
461
|
+
{hasDependencies && (
|
|
462
|
+
<div className="flex items-center gap-1 sm:gap-1.5">
|
|
463
|
+
<GitBranch className="h-3 w-3 sm:h-3.5 sm:w-3.5 flex-shrink-0" />
|
|
464
|
+
<span className="whitespace-nowrap">
|
|
465
|
+
{spec.relationships!.dependsOn.length > 0 && `${spec.relationships!.dependsOn.length} deps`}
|
|
466
|
+
{spec.relationships!.dependsOn.length > 0 && spec.relationships!.related.length > 0 && ', '}
|
|
467
|
+
{spec.relationships!.related.length > 0 && `${spec.relationships!.related.length} related`}
|
|
468
|
+
</span>
|
|
469
|
+
</div>
|
|
470
|
+
)}
|
|
471
|
+
</>
|
|
472
|
+
) : (
|
|
473
|
+
<span className="invisible hidden md:inline">No metadata</span> /* Keep height consistent on desktop */
|
|
474
|
+
)}
|
|
475
|
+
</div>
|
|
476
|
+
|
|
477
|
+
{/* Tags (Right on desktop, below on mobile) */}
|
|
478
|
+
{spec.tags && spec.tags.length > 0 && (
|
|
479
|
+
<div className="flex flex-wrap gap-1 sm:gap-1.5 md:gap-2 md:justify-end md:shrink-0">
|
|
480
|
+
{spec.tags.map(tag => (
|
|
481
|
+
<Badge key={tag} variant="outline" className="text-[10px] sm:text-xs font-mono text-muted-foreground hover:text-foreground transition-colors h-5 sm:h-auto px-1.5 sm:px-2">
|
|
482
|
+
{tag}
|
|
483
|
+
</Badge>
|
|
484
|
+
))}
|
|
485
|
+
</div>
|
|
460
486
|
)}
|
|
461
487
|
</div>
|
|
462
|
-
|
|
463
|
-
{/* Tags (Right) */}
|
|
464
|
-
{spec.tags && spec.tags.length > 0 && (
|
|
465
|
-
<div className="flex flex-wrap gap-2 justify-end shrink-0">
|
|
466
|
-
{spec.tags.map(tag => (
|
|
467
|
-
<Badge key={tag} variant="outline" className="text-xs font-mono text-muted-foreground hover:text-foreground transition-colors">
|
|
468
|
-
{tag}
|
|
469
|
-
</Badge>
|
|
470
|
-
))}
|
|
471
|
-
</div>
|
|
472
|
-
)}
|
|
473
488
|
</CardContent>
|
|
474
489
|
</Card>
|
|
475
490
|
);
|
|
@@ -489,6 +504,14 @@ interface BoardViewProps {
|
|
|
489
504
|
function BoardView({ specs, onStatusChange, pendingSpecIds, showArchived, onToggleArchived }: BoardViewProps) {
|
|
490
505
|
const [draggingId, setDraggingId] = useState<string | null>(null);
|
|
491
506
|
const [activeDropZone, setActiveDropZone] = useState<SpecStatus | null>(null);
|
|
507
|
+
const [collapsedColumns, setCollapsedColumns] = useState<Record<string, boolean>>({});
|
|
508
|
+
|
|
509
|
+
const toggleColumn = (status: string) => {
|
|
510
|
+
setCollapsedColumns(prev => ({
|
|
511
|
+
...prev,
|
|
512
|
+
[status]: !prev[status]
|
|
513
|
+
}));
|
|
514
|
+
};
|
|
492
515
|
|
|
493
516
|
const columns = useMemo(() => {
|
|
494
517
|
// Always show all columns, including archived (it will be rendered as collapsed bar when showArchived=false)
|
|
@@ -548,51 +571,68 @@ function BoardView({ specs, onStatusChange, pendingSpecIds, showArchived, onTogg
|
|
|
548
571
|
}, [draggingId, handleDragEnd, onStatusChange, specLookup]);
|
|
549
572
|
|
|
550
573
|
return (
|
|
551
|
-
<div className="flex gap-6 h-full pb-2">
|
|
574
|
+
<div className="flex flex-col md:flex-row gap-3 sm:gap-4 md:gap-6 h-full pb-2 md:snap-x md:snap-mandatory overflow-y-auto md:overflow-y-hidden md:overflow-x-auto">
|
|
552
575
|
{columns.map(column => {
|
|
553
576
|
const Icon = column.config.icon;
|
|
554
577
|
const isArchivedColumn = column.status === 'archived';
|
|
578
|
+
const isCollapsed = collapsedColumns[column.status];
|
|
555
579
|
|
|
556
580
|
return (
|
|
557
581
|
<div key={column.status} className={cn(
|
|
558
|
-
"flex flex-col
|
|
559
|
-
|
|
582
|
+
"flex flex-col flex-1 snap-start",
|
|
583
|
+
"h-auto md:h-full w-full md:w-auto flex-shrink-0",
|
|
584
|
+
isArchivedColumn && !showArchived ? "md:w-12 md:sm:w-14 md:min-w-[3rem] md:sm:min-w-[3.5rem] flex-none" : "md:min-w-[260px] md:sm:min-w-[280px] md:md:min-w-[300px]"
|
|
560
585
|
)}>
|
|
561
586
|
<div className={cn(
|
|
562
|
-
'flex-none mb-4 rounded-lg border-2 bg-background transition-all',
|
|
587
|
+
'flex-none mb-3 sm:mb-4 rounded-lg border-2 bg-background transition-all touch-manipulation',
|
|
563
588
|
column.config.bgClass,
|
|
564
589
|
column.config.borderClass,
|
|
565
|
-
isArchivedColumn ? 'cursor-pointer hover:opacity-80' : '',
|
|
566
|
-
isArchivedColumn && !showArchived ? 'py-6 px-2' : 'p-3'
|
|
590
|
+
isArchivedColumn ? 'cursor-pointer hover:opacity-80 active:opacity-70' : '',
|
|
591
|
+
isArchivedColumn && !showArchived ? 'py-4 sm:py-6 px-1.5 sm:px-2' : 'p-2.5 sm:p-3',
|
|
592
|
+
// Mobile collapsible header styling
|
|
593
|
+
'md:cursor-default cursor-pointer'
|
|
567
594
|
)}
|
|
568
|
-
onClick={
|
|
595
|
+
onClick={() => {
|
|
596
|
+
if (isArchivedColumn) {
|
|
597
|
+
onToggleArchived();
|
|
598
|
+
} else {
|
|
599
|
+
// Only toggle collapse on mobile
|
|
600
|
+
if (window.innerWidth < 768) {
|
|
601
|
+
toggleColumn(column.status);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}}
|
|
569
605
|
>
|
|
570
606
|
<h2 className={cn(
|
|
571
|
-
'text-lg font-semibold flex items-center gap-2',
|
|
607
|
+
'text-base sm:text-lg font-semibold flex items-center gap-1.5 sm:gap-2',
|
|
572
608
|
column.config.colorClass,
|
|
573
|
-
isArchivedColumn && !showArchived && 'flex-col text-sm gap-3'
|
|
609
|
+
isArchivedColumn && !showArchived && 'flex-col text-xs sm:text-sm gap-2 sm:gap-3'
|
|
574
610
|
)}>
|
|
575
|
-
<Icon className="h-5 w-5" />
|
|
611
|
+
<Icon className="h-4 w-4 sm:h-5 sm:w-5 flex-shrink-0" />
|
|
576
612
|
{isArchivedColumn && !showArchived ? (
|
|
577
613
|
<>
|
|
578
|
-
<span className="vertical-text text-sm whitespace-nowrap">
|
|
614
|
+
<span className="vertical-text text-xs sm:text-sm whitespace-nowrap">
|
|
579
615
|
{column.config.title}
|
|
580
616
|
</span>
|
|
581
|
-
<Badge variant="outline" className="text-xs">{column.specs.length}</Badge>
|
|
617
|
+
<Badge variant="outline" className="text-[10px] sm:text-xs px-1 sm:px-2 h-4 sm:h-5">{column.specs.length}</Badge>
|
|
582
618
|
</>
|
|
583
619
|
) : (
|
|
584
620
|
<>
|
|
585
|
-
{column.config.title}
|
|
586
|
-
<Badge variant="outline" className="
|
|
621
|
+
<span className="truncate flex-1">{column.config.title}</span>
|
|
622
|
+
<Badge variant="outline" className="text-[10px] sm:text-xs px-1.5 sm:px-2 h-4 sm:h-5 flex-shrink-0">{column.specs.length}</Badge>
|
|
623
|
+
{/* Mobile collapse indicator */}
|
|
624
|
+
<div className="md:hidden ml-2 text-muted-foreground/50">
|
|
625
|
+
{isCollapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
|
626
|
+
</div>
|
|
587
627
|
</>
|
|
588
628
|
)}
|
|
589
629
|
</h2>
|
|
590
630
|
</div>
|
|
591
631
|
|
|
592
|
-
{(!isArchivedColumn || showArchived) && (
|
|
632
|
+
{(!isArchivedColumn || showArchived) && !isCollapsed && (
|
|
593
633
|
<div
|
|
594
634
|
className={cn(
|
|
595
|
-
'space-y-3 flex-1 rounded-xl border border-transparent p-1 transition-colors overflow-y-auto min-h-0',
|
|
635
|
+
'space-y-2 sm:space-y-2.5 md:space-y-3 flex-1 rounded-xl border border-transparent p-1 transition-colors overflow-y-auto min-h-0 scrollbar-thin',
|
|
596
636
|
draggingId && 'border-dashed border-muted-foreground/40',
|
|
597
637
|
draggingId && activeDropZone === column.status && 'bg-muted/40 border-primary/50'
|
|
598
638
|
)}
|
|
@@ -624,49 +664,49 @@ function BoardView({ specs, onStatusChange, pendingSpecIds, showArchived, onTogg
|
|
|
624
664
|
onDragEnd={handleDragEnd}
|
|
625
665
|
aria-disabled={isUpdating}
|
|
626
666
|
className={cn(
|
|
627
|
-
'relative hover:shadow-lg transition-all duration-150 hover:scale-[1.02] border-l-4 cursor-pointer group flex flex-col',
|
|
667
|
+
'relative hover:shadow-lg active:shadow-xl transition-all duration-150 hover:scale-[1.02] active:scale-[0.99] border-l-4 cursor-pointer group flex flex-col touch-manipulation',
|
|
628
668
|
borderColor,
|
|
629
669
|
isUpdating && 'opacity-60 cursor-wait'
|
|
630
670
|
)}
|
|
631
671
|
onClick={() => window.location.href = `/specs/${spec.specNumber || spec.id}`}
|
|
632
672
|
>
|
|
633
673
|
{isUpdating && (
|
|
634
|
-
<div className="absolute inset-0 rounded-lg bg-background/80 flex items-center justify-center text-xs font-medium z-10">
|
|
674
|
+
<div className="absolute inset-0 rounded-lg bg-background/80 flex items-center justify-center text-xs sm:text-sm font-medium z-10">
|
|
635
675
|
Updating...
|
|
636
676
|
</div>
|
|
637
677
|
)}
|
|
638
|
-
<CardHeader className="p-4 pb-2 space-y-1.5">
|
|
678
|
+
<CardHeader className="p-3 sm:p-4 pb-1.5 sm:pb-2 space-y-1 sm:space-y-1.5">
|
|
639
679
|
<div className="flex items-center justify-between">
|
|
640
|
-
<span className="font-mono text-xs text-muted-foreground/70 group-hover:text-primary/60 transition-colors">
|
|
680
|
+
<span className="font-mono text-[10px] sm:text-xs text-muted-foreground/70 group-hover:text-primary/60 transition-colors">
|
|
641
681
|
{spec.specNumber ? `#${spec.specNumber}` : ''}
|
|
642
682
|
</span>
|
|
643
683
|
</div>
|
|
644
684
|
<Link href={`/specs/${spec.specNumber || spec.id}`} className="block">
|
|
645
|
-
<CardTitle className="text-sm font-semibold leading-snug hover:text-primary transition-colors line-clamp-3">
|
|
685
|
+
<CardTitle className="text-xs sm:text-sm font-semibold leading-snug hover:text-primary transition-colors line-clamp-3">
|
|
646
686
|
{spec.title || spec.specName}
|
|
647
687
|
</CardTitle>
|
|
648
688
|
</Link>
|
|
649
689
|
</CardHeader>
|
|
650
|
-
<CardContent className="p-4 pt-2 flex-1 flex flex-col justify-end">
|
|
651
|
-
<div className="flex flex-col gap-3">
|
|
690
|
+
<CardContent className="p-3 sm:p-4 pt-1.5 sm:pt-2 flex-1 flex flex-col justify-end">
|
|
691
|
+
<div className="flex flex-col gap-2 sm:gap-3">
|
|
652
692
|
{spec.title && spec.title !== spec.specName && (
|
|
653
|
-
<p className="text-xs font-mono text-muted-foreground truncate opacity-70">
|
|
693
|
+
<p className="text-[10px] sm:text-xs font-mono text-muted-foreground truncate opacity-70">
|
|
654
694
|
{spec.specName}
|
|
655
695
|
</p>
|
|
656
696
|
)}
|
|
657
697
|
|
|
658
|
-
<div className="flex items-center justify-between gap-2 pt-1">
|
|
698
|
+
<div className="flex items-center justify-between gap-1.5 sm:gap-2 pt-0.5 sm:pt-1">
|
|
659
699
|
{spec.priority ? <PriorityBadge priority={spec.priority} /> : <div />}
|
|
660
700
|
|
|
661
701
|
{spec.tags && spec.tags.length > 0 && (
|
|
662
|
-
<div className="flex flex-wrap gap-1 justify-end">
|
|
702
|
+
<div className="flex flex-wrap gap-0.5 sm:gap-1 justify-end">
|
|
663
703
|
{spec.tags.slice(0, 2).map(tag => (
|
|
664
|
-
<Badge key={tag} variant="outline" className="text-[10px] px-1.5 h-5 font-mono text-muted-foreground/80">
|
|
704
|
+
<Badge key={tag} variant="outline" className="text-[9px] sm:text-[10px] px-1 sm:px-1.5 h-4 sm:h-5 font-mono text-muted-foreground/80">
|
|
665
705
|
{tag}
|
|
666
706
|
</Badge>
|
|
667
707
|
))}
|
|
668
708
|
{spec.tags.length > 2 && (
|
|
669
|
-
<Badge variant="outline" className="text-[10px] px-1.5 h-5 font-mono text-muted-foreground/80">
|
|
709
|
+
<Badge variant="outline" className="text-[9px] sm:text-[10px] px-1 sm:px-1.5 h-4 sm:h-5 font-mono text-muted-foreground/80">
|
|
670
710
|
+{spec.tags.length - 2}
|
|
671
711
|
</Badge>
|
|
672
712
|
)}
|
|
@@ -681,9 +721,9 @@ function BoardView({ specs, onStatusChange, pendingSpecIds, showArchived, onTogg
|
|
|
681
721
|
|
|
682
722
|
{column.specs.length === 0 && (
|
|
683
723
|
<Card className="border-dashed border-gray-300 dark:border-gray-700 bg-transparent">
|
|
684
|
-
<CardContent className="py-8 text-center">
|
|
685
|
-
<Icon className={cn('mx-auto h-8 w-8 mb-2', column.config.colorClass, 'opacity-50')} />
|
|
686
|
-
<p className="text-sm text-muted-foreground">Drop here to move specs</p>
|
|
724
|
+
<CardContent className="py-6 sm:py-8 text-center px-2">
|
|
725
|
+
<Icon className={cn('mx-auto h-6 w-6 sm:h-8 sm:w-8 mb-1.5 sm:mb-2', column.config.colorClass, 'opacity-50')} />
|
|
726
|
+
<p className="text-xs sm:text-sm text-muted-foreground">Drop here to move specs</p>
|
|
687
727
|
</CardContent>
|
|
688
728
|
</Card>
|
|
689
729
|
)}
|
|
@@ -98,12 +98,12 @@ export function MainSidebar() {
|
|
|
98
98
|
{/* Sidebar */}
|
|
99
99
|
<aside
|
|
100
100
|
className={cn(
|
|
101
|
-
"
|
|
101
|
+
"border-r border-border bg-background transition-all duration-300 flex-shrink-0",
|
|
102
102
|
// Desktop behavior
|
|
103
|
-
"hidden lg:flex",
|
|
103
|
+
"hidden lg:flex lg:sticky lg:top-14 lg:h-[calc(100vh-3.5rem)]",
|
|
104
104
|
mounted && isCollapsed ? "lg:w-[60px]" : "lg:w-[240px]",
|
|
105
105
|
// Mobile behavior - show as overlay when open
|
|
106
|
-
mobileOpen && "fixed
|
|
106
|
+
mobileOpen && "fixed inset-y-0 left-0 z-[60] flex w-[280px]"
|
|
107
107
|
)}
|
|
108
108
|
>
|
|
109
109
|
<div className="flex flex-col h-full w-full">
|
|
@@ -43,7 +43,8 @@ import {
|
|
|
43
43
|
Home,
|
|
44
44
|
TrendingUp,
|
|
45
45
|
Clock,
|
|
46
|
-
Maximize2
|
|
46
|
+
Maximize2,
|
|
47
|
+
List as ListIcon
|
|
47
48
|
} from 'lucide-react';
|
|
48
49
|
import type { Plugin } from 'unified';
|
|
49
50
|
import { visit } from 'unist-util-visit';
|
|
@@ -216,12 +217,29 @@ export function SpecDetailClient({ initialSpec, initialSubSpec }: SpecDetailClie
|
|
|
216
217
|
<header ref={headerRef} className="lg:sticky lg:top-14 lg:z-20 border-b bg-card">
|
|
217
218
|
<div className="px-3 sm:px-6 py-3 sm:py-4">
|
|
218
219
|
{/* Line 1: Spec number + H1 Title */}
|
|
219
|
-
<
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
220
|
+
<div className="flex items-start justify-between gap-2 mb-2 sm:mb-3">
|
|
221
|
+
<h1 className="text-xl sm:text-2xl font-bold tracking-tight">
|
|
222
|
+
{spec.specNumber && (
|
|
223
|
+
<span className="text-muted-foreground">#{spec.specNumber.toString().padStart(3, '0')} </span>
|
|
224
|
+
)}
|
|
225
|
+
{displayTitle}
|
|
226
|
+
</h1>
|
|
227
|
+
|
|
228
|
+
{/* Mobile Specs List Toggle */}
|
|
229
|
+
<Button
|
|
230
|
+
variant="ghost"
|
|
231
|
+
size="icon"
|
|
232
|
+
className="lg:hidden h-8 w-8 -mr-2 shrink-0 text-muted-foreground"
|
|
233
|
+
onClick={() => {
|
|
234
|
+
if (typeof window !== 'undefined' && window.toggleSpecsSidebar) {
|
|
235
|
+
window.toggleSpecsSidebar();
|
|
236
|
+
}
|
|
237
|
+
}}
|
|
238
|
+
>
|
|
239
|
+
<ListIcon className="h-5 w-5" />
|
|
240
|
+
<span className="sr-only">Toggle specs list</span>
|
|
241
|
+
</Button>
|
|
242
|
+
</div>
|
|
225
243
|
|
|
226
244
|
{/* Line 2: Status, Priority, Tags, Actions */}
|
|
227
245
|
<div className="flex flex-wrap items-center gap-2">
|
|
@@ -395,12 +395,12 @@ export function SpecsNavSidebar({ initialSpecs = [], currentSpecId, currentSubSp
|
|
|
395
395
|
|
|
396
396
|
<div className="relative flex-shrink-0">
|
|
397
397
|
<aside className={cn(
|
|
398
|
-
"
|
|
398
|
+
"border-r border-border bg-background flex flex-col overflow-hidden transition-all duration-300",
|
|
399
399
|
// Desktop behavior
|
|
400
|
-
"hidden lg:flex",
|
|
400
|
+
"hidden lg:flex lg:sticky lg:top-14 lg:h-[calc(100vh-3.5rem)]",
|
|
401
401
|
mounted && isCollapsed ? "lg:w-0 lg:border-r-0" : "lg:w-[280px]",
|
|
402
402
|
// Mobile behavior - show as overlay when open
|
|
403
|
-
mobileOpen && "fixed
|
|
403
|
+
mobileOpen && "fixed inset-y-0 left-0 z-[60] flex w-[280px]"
|
|
404
404
|
)}>
|
|
405
405
|
<div className="p-4 border-b border-border">
|
|
406
406
|
<div className="flex items-center justify-between mb-3">
|
|
@@ -545,16 +545,6 @@ export function SpecsNavSidebar({ initialSpecs = [], currentSpecId, currentSubSp
|
|
|
545
545
|
<ChevronRight className="h-4 w-4" />
|
|
546
546
|
</Button>
|
|
547
547
|
)}
|
|
548
|
-
|
|
549
|
-
{/* Mobile floating toggle button - matches BackToTop/TOC style */}
|
|
550
|
-
<Button
|
|
551
|
-
onClick={() => setMobileOpen(true)}
|
|
552
|
-
size="icon"
|
|
553
|
-
className="lg:hidden fixed bottom-6 left-6 h-12 w-12 rounded-full shadow-lg z-40 hover:scale-110 transition-transform"
|
|
554
|
-
aria-label="Show specifications list"
|
|
555
|
-
>
|
|
556
|
-
<ListIconLucide className="h-5 w-5" />
|
|
557
|
-
</Button>
|
|
558
548
|
</div>
|
|
559
549
|
</TooltipProvider>
|
|
560
550
|
);
|