aural-ui 4.1.0 → 4.2.3

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/README.md CHANGED
@@ -114,6 +114,13 @@ aural-ui update
114
114
 
115
115
  The **aural-ui MCP server** exposes the design system by reading from aural-ui source (components, icons, source and story files). No Storybook process is required.
116
116
 
117
+ ### Run the MCP server locally
118
+
119
+ 1. Copy [`mcp/.env.example`](mcp/.env.example) to `mcp/.env` and fill in the values (Google OAuth client, redirect URL, issuer base URL, JWT secret). That file is the canonical list of variables; it stays next to the server code so it does not drift.
120
+ 2. From the repo root: `npx tsx mcp/src/index.ts` (listens on port `3000` by default).
121
+
122
+ For OAuth flow details, see [`mcp/docs/auth.md`](mcp/docs/auth.md).
123
+
117
124
  ### Setup in Cursor
118
125
 
119
126
  Add the server in `.cursor/mcp.json` (or Cursor Settings → MCP). Start the MCP server first (e.g. `npx tsx mcp/src/index.ts` from the aural-ui repo root, or run the Docker image), then point Cursor at the streamable HTTP endpoint:
@@ -131,7 +138,7 @@ Add the server in `.cursor/mcp.json` (or Cursor Settings → MCP). Start the MCP
131
138
 
132
139
  The server identifies as **aural-ui** so agents match user phrases like "add aural-ui btn". Use the same URL when the server runs in Docker or elsewhere (e.g. `http://your-host:3000/mcp`).
133
140
 
134
- - **Config:** All MCP server settings are in `mcp/src/lib/env.ts` (no .env). You can set `AURAL_UI_SRC` there to the aural-ui repo `src` path if the workspace is not the aural-ui repo.
141
+ - **Config:** Non-secret defaults and limits live in `mcp/src/lib/env.ts`. OAuth and JWT values must come from the environment; for local dev, use `mcp/.env` (start from [`mcp/.env.example`](mcp/.env.example)).
135
142
 
136
143
  **HTTP / Docker:** When running the MCP server over HTTP (e.g. in Docker):
137
144
  - **GET /health** — Returns `200` and `{ "ok": true, "server": "aural-ui" }` for load balancers and orchestrators (no MCP session required).
@@ -29,22 +29,22 @@ const meta: Meta<typeof Tabs> = {
29
29
  docs: {
30
30
  description: {
31
31
  component:
32
- "A compound tabs component built on Radix UI Tabs with gradient glow effects on active triggers, smooth fade animations, and three size variants. Supports bottom and top glow directions and per-trigger size overrides via a shared size context.",
32
+ "A compound tabs component built on Radix UI Tabs with gradient glow effects on active triggers, smooth fade animations, and three size variants. Supports four glow directions (bottom, top, left, right) and per-trigger size overrides via a shared size context.",
33
33
  },
34
34
  page: () => (
35
35
  <AuralComponentDocsPage
36
36
  features={[
37
37
  {
38
- title: "Gradient Glow",
39
- description: "Active trigger effect",
38
+ title: "Accessible by Default",
39
+ description: "Auto ARIA wiring",
40
40
  },
41
41
  {
42
- title: "3 Sizes",
43
- description: "sm, md, lg via context",
42
+ title: "4 Glow Directions",
43
+ description: "bottom, top, left, right",
44
44
  },
45
45
  {
46
- title: "Fade Animation",
47
- description: "Smooth content switch",
46
+ title: "3 Sizes",
47
+ description: "sm, md, lg via context",
48
48
  },
49
49
  ]}
50
50
  />
@@ -154,24 +154,58 @@ export const AllVariants: Story = {
154
154
  </div>
155
155
 
156
156
  <div className="space-y-2 text-center">
157
- <div className="pt-8">
158
- <Tabs defaultValue="active" size="md">
159
- <TabsList>
160
- <TabsTrigger value="active" glowDirection="top">
161
- Active
162
- </TabsTrigger>
163
- <TabsTrigger value="other" glowDirection="top">
164
- Inactive
165
- </TabsTrigger>
166
- </TabsList>
167
- <TabsContent value="active" />
168
- <TabsContent value="other" />
169
- </Tabs>
170
- </div>
157
+ <Tabs defaultValue="active" size="md">
158
+ <TabsList>
159
+ <TabsTrigger value="active" glowDirection="top">
160
+ Active
161
+ </TabsTrigger>
162
+ <TabsTrigger value="other" glowDirection="top">
163
+ Inactive
164
+ </TabsTrigger>
165
+ </TabsList>
166
+ <TabsContent value="active" />
167
+ <TabsContent value="other" />
168
+ </Tabs>
171
169
  <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
172
170
  Glow Top
173
171
  </p>
174
172
  </div>
173
+
174
+ <div className="space-y-2 text-center">
175
+ <Tabs defaultValue="active" size="md">
176
+ <TabsList>
177
+ <TabsTrigger value="active" glowDirection="left">
178
+ Active
179
+ </TabsTrigger>
180
+ <TabsTrigger value="other" glowDirection="left">
181
+ Inactive
182
+ </TabsTrigger>
183
+ </TabsList>
184
+ <TabsContent value="active" />
185
+ <TabsContent value="other" />
186
+ </Tabs>
187
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
188
+ Glow Left
189
+ </p>
190
+ </div>
191
+
192
+ <div className="space-y-2 text-center">
193
+ <Tabs defaultValue="active" size="md">
194
+ <TabsList className="gap-6">
195
+ <TabsTrigger value="active" glowDirection="right">
196
+ Active
197
+ </TabsTrigger>
198
+ <TabsTrigger value="other" glowDirection="right">
199
+ Inactive
200
+ </TabsTrigger>
201
+ </TabsList>
202
+ <TabsContent value="active" />
203
+ <TabsContent value="other" />
204
+ </Tabs>
205
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
206
+ Glow Right
207
+ </p>
208
+ </div>
175
209
  </div>
176
210
  </div>
177
211
  </div>
@@ -180,7 +214,7 @@ export const AllVariants: Story = {
180
214
  docs: {
181
215
  description: {
182
216
  story:
183
- "All three size variants (sm, md, lg) and both glow direction options (bottom, top) shown as labeled item cards.",
217
+ "All three size variants (sm, md, lg) and all four glow direction options (bottom, top, left, right) shown as labeled item cards.",
184
218
  },
185
219
  },
186
220
  },
@@ -287,6 +321,39 @@ export const Configurations: Story = {
287
321
  </Tabs>
288
322
  </div>
289
323
 
324
+ {/* Hover state */}
325
+ <div className="space-y-4">
326
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
327
+ Hover State
328
+ </h4>
329
+ <Tabs defaultValue="songs" size="md" className="w-full max-w-lg">
330
+ <TabsList>
331
+ <TabsTrigger value="songs">Songs</TabsTrigger>
332
+ <TabsTrigger value="albums">Albums</TabsTrigger>
333
+ <TabsTrigger value="artists">Artists</TabsTrigger>
334
+ </TabsList>
335
+ <TabsContent value="songs" className="mt-4">
336
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
337
+ <p className="text-fm-secondary font-fm-text text-fm-md leading-fm-xl">
338
+ Hover over inactive tabs to see the distinct hover state — text
339
+ shifts from{" "}
340
+ <code className="text-fm-primary font-(--font-fm-mono)">
341
+ text-fm-tertiary
342
+ </code>{" "}
343
+ to{" "}
344
+ <code className="text-fm-primary font-(--font-fm-mono)">
345
+ text-fm-primary
346
+ </code>
347
+ , providing a clear visual cue that is distinct from both the
348
+ default and active states.
349
+ </p>
350
+ </div>
351
+ </TabsContent>
352
+ <TabsContent value="albums" className="mt-4" />
353
+ <TabsContent value="artists" className="mt-4" />
354
+ </Tabs>
355
+ </div>
356
+
290
357
  {/* Different content panels */}
291
358
  <div className="space-y-4">
292
359
  <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
@@ -393,7 +460,7 @@ export const Configurations: Story = {
393
460
  docs: {
394
461
  description: {
395
462
  story:
396
- "Configuration axes: tabs with icons, individual per-trigger size overrides against a parent size context, and tabs with rich differentiated content panels.",
463
+ "Configuration axes: hover state behavior, tabs with icons, individual per-trigger size overrides, and rich differentiated content panels.",
397
464
  },
398
465
  },
399
466
  },
@@ -434,170 +501,147 @@ export const Interactive: Story = {
434
501
 
435
502
  return (
436
503
  <div className="w-full p-8">
437
- <div className="mx-auto max-w-3xl space-y-6">
438
- <div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
439
- {/* Controls panel */}
440
- <div className="border-fm-divider-secondary bg-fm-surface-secondary space-y-5 rounded-xl border p-5">
441
- <p className="text-fm-primary font-fm-brand text-fm-sm leading-fm-sm font-semibold tracking-widest uppercase">
442
- Active Tab
443
- </p>
444
- <div className="space-y-2">
445
- {tabs.map((tab) => {
446
- const Icon = tab.icon
447
- return (
448
- <button
449
- key={tab.value}
450
- onClick={() => setActiveTab(tab.value)}
451
- className={`font-fm-text text-fm-sm leading-fm-sm flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left transition-colors ${
452
- activeTab === tab.value
453
- ? "bg-fm-surface-primary text-fm-primary"
454
- : "text-fm-secondary hover:text-fm-primary"
455
- }`}
456
- >
457
- <Icon className="h-4 w-4 shrink-0" />
458
- {tab.label}
459
- </button>
460
- )
461
- })}
462
- </div>
463
- <div className="border-fm-divider-secondary border-t pt-4" />
464
- <div className="border-fm-divider-secondary bg-fm-surface-primary rounded-lg border px-4 py-3">
465
- <code className="text-fm-secondary text-fm-md leading-fm-md font-(--font-fm-mono)">
466
- value=&quot;{activeTab}&quot;
467
- </code>
468
- </div>
504
+ <div className="mx-auto max-w-xl space-y-4">
505
+ <div className="flex items-center justify-between">
506
+ <p className="text-fm-primary font-fm-brand text-fm-md leading-fm-md font-semibold">
507
+ My Library
508
+ </p>
509
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border px-3 py-1.5">
510
+ <code className="text-fm-secondary text-fm-sm leading-fm-sm font-(--font-fm-mono)">
511
+ value=&quot;{activeTab}&quot;
512
+ </code>
469
513
  </div>
514
+ </div>
470
515
 
471
- {/* Preview stage */}
472
- <div className="flex flex-col gap-3 lg:col-span-2">
473
- <Tabs
474
- value={activeTab}
475
- onValueChange={setActiveTab}
476
- size="md"
477
- className="w-full"
478
- >
479
- <TabsList>
480
- {tabs.map((tab) => {
481
- const Icon = tab.icon
482
- return (
483
- <TabsTrigger
484
- key={tab.value}
485
- value={tab.value}
486
- className="gap-2"
487
- >
488
- <Icon className="h-4 w-4" />
489
- {tab.label}
490
- </TabsTrigger>
491
- )
492
- })}
493
- </TabsList>
516
+ <Tabs
517
+ value={activeTab}
518
+ onValueChange={setActiveTab}
519
+ size="md"
520
+ className="w-full"
521
+ >
522
+ <TabsList className="w-full">
523
+ {tabs.map((tab) => {
524
+ const Icon = tab.icon
525
+ return (
526
+ <TabsTrigger
527
+ key={tab.value}
528
+ value={tab.value}
529
+ className="gap-1.5"
530
+ >
531
+ <Icon className="h-4 w-4" />
532
+ {tab.label}
533
+ </TabsTrigger>
534
+ )
535
+ })}
536
+ </TabsList>
494
537
 
495
- <TabsContent value="songs" className="mt-4">
496
- <div className="border-fm-divider-secondary bg-fm-surface-secondary space-y-2 rounded-lg border p-4">
497
- {songs.map((song) => (
498
- <div
499
- key={song.title}
500
- className="hover:bg-fm-surface-primary flex items-center justify-between rounded-md px-2 py-2 transition-colors"
501
- >
502
- <div className="flex items-center gap-3">
503
- <div className="bg-fm-surface-primary border-fm-divider-secondary flex h-9 w-9 shrink-0 items-center justify-center rounded-md border">
504
- <MusicalNoteIcon className="text-fm-secondary h-4 w-4" />
505
- </div>
506
- <div>
507
- <p className="text-fm-primary font-fm-text text-fm-md leading-fm-md">
508
- {song.title}
509
- </p>
510
- <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
511
- {song.artist}
512
- </p>
513
- </div>
514
- </div>
515
- <span className="text-fm-tertiary font-fm-text text-fm-sm leading-fm-sm">
516
- {song.duration}
517
- </span>
538
+ <TabsContent value="songs" className="mt-4">
539
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary space-y-1 rounded-lg border p-2">
540
+ {songs.map((song) => (
541
+ <div
542
+ key={song.title}
543
+ className="hover:bg-fm-surface-primary flex items-center justify-between rounded-md px-3 py-2 transition-colors"
544
+ >
545
+ <div className="flex items-center gap-3">
546
+ <div className="bg-fm-surface-primary border-fm-divider-secondary flex h-9 w-9 shrink-0 items-center justify-center rounded-md border">
547
+ <MusicalNoteIcon className="text-fm-secondary h-4 w-4" />
548
+ </div>
549
+ <div>
550
+ <p className="text-fm-primary font-fm-text text-fm-md leading-fm-md">
551
+ {song.title}
552
+ </p>
553
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
554
+ {song.artist}
555
+ </p>
518
556
  </div>
519
- ))}
557
+ </div>
558
+ <span className="text-fm-tertiary font-fm-text text-fm-sm leading-fm-sm tabular-nums">
559
+ {song.duration}
560
+ </span>
520
561
  </div>
521
- </TabsContent>
562
+ ))}
563
+ </div>
564
+ </TabsContent>
522
565
 
523
- <TabsContent value="albums" className="mt-4">
524
- <div className="border-fm-divider-secondary bg-fm-surface-secondary space-y-2 rounded-lg border p-4">
525
- {albums.map((album) => (
526
- <div
527
- key={album.title}
528
- className="hover:bg-fm-surface-primary flex items-center justify-between rounded-md px-2 py-2 transition-colors"
529
- >
530
- <div className="flex items-center gap-3">
531
- <div className="bg-fm-surface-primary border-fm-divider-secondary flex h-12 w-12 shrink-0 items-center justify-center rounded-md border">
532
- <StarIcon className="text-fm-secondary h-5 w-5" />
533
- </div>
534
- <div>
535
- <p className="text-fm-primary font-fm-text text-fm-md leading-fm-md">
536
- {album.title}
537
- </p>
538
- <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
539
- {album.artist}
540
- </p>
541
- </div>
542
- </div>
543
- <div className="text-right">
544
- <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
545
- {album.tracks} tracks
546
- </p>
547
- <p className="text-fm-tertiary font-fm-text text-fm-sm leading-fm-sm">
548
- {album.year}
549
- </p>
550
- </div>
566
+ <TabsContent value="albums" className="mt-4">
567
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary space-y-1 rounded-lg border p-2">
568
+ {albums.map((album) => (
569
+ <div
570
+ key={album.title}
571
+ className="hover:bg-fm-surface-primary flex items-center justify-between rounded-md px-3 py-2 transition-colors"
572
+ >
573
+ <div className="flex items-center gap-3">
574
+ <div className="bg-fm-surface-primary border-fm-divider-secondary flex h-9 w-9 shrink-0 items-center justify-center rounded-md border">
575
+ <StarIcon className="text-fm-secondary h-4 w-4" />
576
+ </div>
577
+ <div>
578
+ <p className="text-fm-primary font-fm-text text-fm-md leading-fm-md">
579
+ {album.title}
580
+ </p>
581
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
582
+ {album.artist}
583
+ </p>
551
584
  </div>
552
- ))}
585
+ </div>
586
+ <span className="text-fm-tertiary font-fm-text text-fm-sm leading-fm-sm tabular-nums">
587
+ {album.tracks} tracks
588
+ </span>
553
589
  </div>
554
- </TabsContent>
590
+ ))}
591
+ </div>
592
+ </TabsContent>
555
593
 
556
- <TabsContent value="artists" className="mt-4">
557
- <div className="border-fm-divider-secondary bg-fm-surface-secondary space-y-2 rounded-lg border p-4">
558
- {artists.map((artist) => (
559
- <div
560
- key={artist.name}
561
- className="hover:bg-fm-surface-primary flex items-center justify-between rounded-md px-2 py-2 transition-colors"
562
- >
563
- <div className="flex items-center gap-3">
564
- <div className="bg-fm-surface-primary border-fm-divider-secondary flex h-10 w-10 shrink-0 items-center justify-center rounded-full border">
565
- <HeartIcon className="text-fm-secondary h-4 w-4" />
566
- </div>
567
- <div>
568
- <p className="text-fm-primary font-fm-text text-fm-md leading-fm-md">
569
- {artist.name}
570
- </p>
571
- <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
572
- {artist.genre}
573
- </p>
574
- </div>
575
- </div>
576
- <span className="text-fm-tertiary font-fm-text text-fm-sm leading-fm-sm">
577
- {artist.listeners}
578
- </span>
594
+ <TabsContent value="artists" className="mt-4">
595
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary space-y-1 rounded-lg border p-2">
596
+ {artists.map((artist) => (
597
+ <div
598
+ key={artist.name}
599
+ className="hover:bg-fm-surface-primary flex items-center justify-between rounded-md px-3 py-2 transition-colors"
600
+ >
601
+ <div className="flex items-center gap-3">
602
+ <div className="bg-fm-surface-primary border-fm-divider-secondary flex h-9 w-9 shrink-0 items-center justify-center rounded-full border">
603
+ <HeartIcon className="text-fm-secondary h-4 w-4" />
604
+ </div>
605
+ <div>
606
+ <p className="text-fm-primary font-fm-text text-fm-md leading-fm-md">
607
+ {artist.name}
608
+ </p>
609
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
610
+ {artist.genre}
611
+ </p>
579
612
  </div>
580
- ))}
613
+ </div>
614
+ <span className="text-fm-tertiary font-fm-text text-fm-sm leading-fm-sm tabular-nums">
615
+ {artist.listeners}
616
+ </span>
581
617
  </div>
582
- </TabsContent>
618
+ ))}
619
+ </div>
620
+ </TabsContent>
583
621
 
584
- <TabsContent value="about" className="mt-4">
585
- <div className="border-fm-divider-secondary bg-fm-surface-secondary space-y-4 rounded-lg border p-4">
586
- <p className="text-fm-secondary font-fm-text text-fm-md leading-fm-xl">
587
- Aural is a music streaming platform designed for deep
588
- listeners. Discover new artists, curate playlists, and
589
- explore lossless audio quality.
622
+ <TabsContent value="about" className="mt-4">
623
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary space-y-1 rounded-lg border p-2">
624
+ {[
625
+ { label: "Version", value: "2.4.1" },
626
+ { label: "Last updated", value: "April 2026" },
627
+ { label: "Audio quality", value: "Lossless" },
628
+ { label: "Cache size", value: "1.2 GB" },
629
+ ].map((row) => (
630
+ <div
631
+ key={row.label}
632
+ className="flex items-center justify-between rounded-md px-3 py-2"
633
+ >
634
+ <p className="text-fm-secondary font-fm-text text-fm-md leading-fm-md">
635
+ {row.label}
636
+ </p>
637
+ <p className="text-fm-primary font-fm-text text-fm-md leading-fm-md">
638
+ {row.value}
590
639
  </p>
591
- <div className="border-fm-divider-secondary bg-fm-surface-primary rounded-lg border px-4 py-3">
592
- <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-xl">
593
- Version 2.4.1 · Last updated April 2026
594
- </p>
595
- </div>
596
640
  </div>
597
- </TabsContent>
598
- </Tabs>
599
- </div>
600
- </div>
641
+ ))}
642
+ </div>
643
+ </TabsContent>
644
+ </Tabs>
601
645
  </div>
602
646
  </div>
603
647
  )