aural-ui 4.2.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.
@@ -1,4 +1,5 @@
1
1
  import * as React from "react"
2
+ import { useId } from "react"
2
3
  import { cn } from "@lib/utils"
3
4
  import * as TabsPrimitive from "@radix-ui/react-tabs"
4
5
  import { cva } from "class-variance-authority"
@@ -16,23 +17,30 @@ const tabsVariants = cva("", {
16
17
  },
17
18
  })
18
19
 
19
- // Create context for tabs size
20
+ // Create context for tabs size and baseId
20
21
  const TabsContext = React.createContext<{
21
22
  size: "sm" | "md" | "lg"
23
+ baseId: string
22
24
  }>({
23
25
  size: "md",
26
+ baseId: "",
24
27
  })
25
28
 
26
29
  interface TabsProps extends React.ComponentProps<typeof TabsPrimitive.Root> {
27
30
  size?: "sm" | "md" | "lg"
31
+ /** Provide a stable id in SSR environments to avoid hydration mismatches. */
32
+ id?: string
28
33
  }
29
34
 
30
- function Tabs({ className, size = "md", ...props }: TabsProps) {
35
+ function Tabs({ className, size = "md", id, ...props }: TabsProps) {
36
+ const generatedId = useId()
37
+ const baseId = id ?? generatedId
31
38
  return (
32
- <TabsContext.Provider value={{ size }}>
39
+ <TabsContext.Provider value={{ size, baseId }}>
33
40
  <TabsPrimitive.Root
34
41
  data-slot="tabs"
35
42
  className={cn("flex flex-col gap-2", className)}
43
+ id={baseId}
36
44
  {...props}
37
45
  />
38
46
  </TabsContext.Provider>
@@ -55,7 +63,7 @@ function TabsList({
55
63
  )
56
64
  }
57
65
 
58
- type GlowDirection = "bottom" | "top"
66
+ type GlowDirection = "bottom" | "top" | "left" | "right"
59
67
 
60
68
  interface TabsTriggerProps extends React.ComponentProps<
61
69
  typeof TabsPrimitive.Trigger
@@ -63,17 +71,33 @@ interface TabsTriggerProps extends React.ComponentProps<
63
71
  className?: string
64
72
  size?: "sm" | "md" | "lg"
65
73
  glowDirection?: GlowDirection
74
+ /** Provide a stable id in SSR environments to avoid hydration mismatches */
75
+ id?: string
66
76
  }
67
77
 
68
78
  function TabsTrigger({
69
79
  className,
70
80
  size: sizeProp,
71
81
  glowDirection = "bottom",
82
+ id: idProp,
72
83
  ...props
73
84
  }: TabsTriggerProps) {
74
- const { size: contextSize } = React.useContext(TabsContext)
85
+ const { size: contextSize, baseId } = React.useContext(TabsContext)
75
86
  const size = sizeProp || contextSize
76
87
 
88
+ const generatedId = useId()
89
+ const sanitizedId = idProp?.replace(/\s+/g, "-") ?? generatedId
90
+ const gradientId = `gradient-${sanitizedId}`
91
+ const filterId = `filter-${sanitizedId}`
92
+
93
+ const sanitizedValue = props.value?.replace(/\s+/g, "-")
94
+ const triggerId = sanitizedValue
95
+ ? `${baseId}-trigger-${sanitizedValue}`
96
+ : sanitizedId
97
+ const contentId = sanitizedValue
98
+ ? `${baseId}-content-${sanitizedValue}`
99
+ : undefined
100
+
77
101
  const getGlowConfig = (direction: GlowDirection) => {
78
102
  switch (direction) {
79
103
  case "top":
@@ -91,6 +115,36 @@ function TabsTrigger({
91
115
  edgeGradientTransform: "matrix(0 -4.03571 32.0526 0 32 0)",
92
116
  }
93
117
 
118
+ case "left":
119
+ return {
120
+ containerClass: "absolute left-0 top-0 h-full w-1/2",
121
+ ellipse: { cx: "15", cy: "44", rx: "4", ry: "32" },
122
+ gradientTransform: "matrix(-16.1429 0 0 32.0526 1 44)",
123
+ viewBox: "0 0 25 92",
124
+ dimensions: { width: "25", height: "92" },
125
+ filter: { x: "-15", y: "-4", width: "40", height: "96" },
126
+ edgeClass: "absolute left-0 top-0 h-full w-[3px]",
127
+ edgePath: "M1 0S-.173 19.503 .022 32 1 64 1 64h1L1.995 0z",
128
+ edgeViewBox: "0 0 2 64",
129
+ edgeDimensions: { width: "2", height: "64" },
130
+ edgeGradientTransform: "matrix(-4.03571 0 0 32.0526 0 32)",
131
+ }
132
+
133
+ case "right":
134
+ return {
135
+ containerClass: "absolute right-0 top-0 h-full w-1/2",
136
+ ellipse: { cx: "15", cy: "44", rx: "4", ry: "32" },
137
+ gradientTransform: "matrix(-16.1429 0 0 32.0526 1 44)",
138
+ viewBox: "0 0 25 92",
139
+ dimensions: { width: "25", height: "92" },
140
+ filter: { x: "0", y: "-4", width: "40", height: "96" },
141
+ edgeClass: "absolute -right-0.5 top-0 h-full",
142
+ edgePath: "M1 0S-.173 19.503 .022 32 1 64 1 64h1L1.995 0z",
143
+ edgeViewBox: "0 0 2 64",
144
+ edgeDimensions: { width: "2", height: "64" },
145
+ edgeGradientTransform: "matrix(4.03571 0 0 -32.0526 2 32)",
146
+ }
147
+
94
148
  case "bottom":
95
149
  default:
96
150
  return {
@@ -118,16 +172,18 @@ function TabsTrigger({
118
172
  "group text-fm-tertiary relative inline-flex flex-1 items-center justify-center border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow]",
119
173
  "focus-visible:ring-fm-primary focus-visible:ring-offset-fm-contrast outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
120
174
  "disabled:pointer-events-none disabled:opacity-50",
121
- "data-[state=active]:text-fm-primary hover:text-fm-secondary",
175
+ "data-[state=active]:text-fm-primary hover:text-fm-primary",
122
176
  tabsVariants({ size }),
123
177
  className
124
178
  )}
125
179
  {...props}
180
+ id={triggerId}
181
+ aria-controls={contentId}
126
182
  >
127
183
  {props.children}
128
184
  <div
129
185
  className={cn(
130
- "group-data-[state=active]:animate-fm-fadeIn group-hover:!animate-fm-fadeIn group-data-[state=inactive]:animate-fm-fadeOut pointer-events-none absolute top-0 right-0 -bottom-0.5 left-0 z-10"
186
+ "group-data-[state=active]:animate-fm-fadeIn pointer-events-none absolute top-0 right-0 -bottom-0.5 left-0 z-10 group-data-[state=inactive]:hidden sm:-right-3"
131
187
  )}
132
188
  >
133
189
  {/* Main glow ellipse */}
@@ -136,22 +192,24 @@ function TabsTrigger({
136
192
  width={config.dimensions.width}
137
193
  height={config.dimensions.height}
138
194
  viewBox={config.viewBox}
195
+ aria-hidden={true}
196
+ focusable={false}
139
197
  fill="none"
140
198
  className={config.containerClass}
141
199
  >
142
- <g filter="url(#b)">
200
+ <g filter={`url(#${filterId})`}>
143
201
  <ellipse
144
202
  cx={config.ellipse.cx}
145
203
  cy={config.ellipse.cy}
146
204
  rx={config.ellipse.rx}
147
205
  ry={config.ellipse.ry}
148
- fill="url(#c)"
206
+ fill={`url(#${gradientId})`}
149
207
  style={{ mixBlendMode: "screen" }}
150
208
  />
151
209
  </g>
152
210
  <defs>
153
211
  <radialGradient
154
- id="c"
212
+ id={gradientId}
155
213
  cx="0"
156
214
  cy="0"
157
215
  r="1"
@@ -163,7 +221,7 @@ function TabsTrigger({
163
221
  <stop offset="1" stopColor="var(--color-fm-neutral-50)" />
164
222
  </radialGradient>
165
223
  <filter
166
- id="b"
224
+ id={filterId}
167
225
  x={config.filter.x}
168
226
  y={config.filter.y}
169
227
  width={config.filter.width}
@@ -179,7 +237,7 @@ function TabsTrigger({
179
237
  />
180
238
  <feGaussianBlur
181
239
  stdDeviation="8"
182
- result="effect1_foregroundBlur_4031_2537"
240
+ result="effect1_foregroundBlur_default"
183
241
  />
184
242
  </filter>
185
243
  </defs>
@@ -190,18 +248,20 @@ function TabsTrigger({
190
248
  xmlns="http://www.w3.org/2000/svg"
191
249
  width={config.edgeDimensions.width}
192
250
  height={config.edgeDimensions.height}
251
+ aria-hidden={true}
252
+ focusable={false}
193
253
  viewBox={config.edgeViewBox}
194
254
  fill="none"
195
255
  className={config.edgeClass}
196
256
  >
197
257
  <path
198
258
  d={config.edgePath}
199
- fill="url(#a)"
259
+ fill={`url(#${gradientId}-edge)`}
200
260
  style={{ mixBlendMode: "screen" }}
201
261
  />
202
262
  <defs>
203
263
  <radialGradient
204
- id="a"
264
+ id={`${gradientId}-edge`}
205
265
  cx="0"
206
266
  cy="0"
207
267
  r="1"
@@ -210,7 +270,11 @@ function TabsTrigger({
210
270
  >
211
271
  <stop stopColor="var(--color-fm-primary-600)" />
212
272
  <stop offset=".61" stopColor="var(--color-fm-secondary-300)" />
213
- <stop offset="1" stopColor="var(--color-fm-neutral-50)" />
273
+ <stop
274
+ offset="1"
275
+ stopColor="var(--color-fm-neutral-50)"
276
+ stopOpacity="0.2"
277
+ />
214
278
  </radialGradient>
215
279
  </defs>
216
280
  </svg>
@@ -219,15 +283,35 @@ function TabsTrigger({
219
283
  )
220
284
  }
221
285
 
286
+ interface TabsContentProps extends React.ComponentProps<
287
+ typeof TabsPrimitive.Content
288
+ > {
289
+ id?: string
290
+ }
291
+
222
292
  function TabsContent({
223
293
  className,
294
+ id: idProp,
295
+ value,
224
296
  ...props
225
- }: React.ComponentProps<typeof TabsPrimitive.Content>) {
297
+ }: TabsContentProps) {
298
+ const { baseId } = React.useContext(TabsContext)
299
+ const sanitizedValue = value?.replace(/\s+/g, "-")
300
+ const contentId =
301
+ idProp ??
302
+ (sanitizedValue ? `${baseId}-content-${sanitizedValue}` : undefined)
303
+ const triggerId = sanitizedValue
304
+ ? `${baseId}-trigger-${sanitizedValue}`
305
+ : undefined
306
+
226
307
  return (
227
308
  <TabsPrimitive.Content
228
309
  data-slot="tabs-content"
229
310
  className={cn("flex-1 outline-none", className)}
230
311
  {...props}
312
+ id={contentId}
313
+ value={value}
314
+ aria-labelledby={triggerId}
231
315
  />
232
316
  )
233
317
  }