@nationaldesignstudio/react 0.5.5 → 0.6.0
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/dist/component-registry.md +1286 -40
- package/dist/index.d.ts +1858 -139
- package/dist/index.js +2388 -326
- package/dist/index.js.map +1 -1
- package/dist/tokens.css +680 -0
- package/package.json +22 -2
- package/src/components/atoms/blurred-video-backdrop/blurred-video-backdrop.tsx +447 -0
- package/src/components/atoms/button/icon-button.tsx +10 -4
- package/src/components/atoms/select/select.tsx +202 -49
- package/src/components/atoms/video-player/caption-overlay.tsx +107 -0
- package/src/components/atoms/video-player/video-player.tsx +811 -0
- package/src/components/molecules/dialog/dialog.tsx +526 -0
- package/src/components/molecules/video-dialog/video-dialog.tsx +272 -0
- package/src/components/molecules/video-with-backdrop/video-with-backdrop.tsx +383 -0
- package/src/components/organisms/card/card.tsx +87 -12
- package/src/components/sections/hero/hero.tsx +35 -0
- package/src/hooks/index.ts +16 -0
- package/src/hooks/use-breakpoint.ts +145 -0
- package/src/hooks/use-captions.ts +247 -0
- package/src/hooks/use-video-keyboard.ts +230 -0
- package/src/lib/utils.ts +2 -2
- package/src/theme/index.ts +4 -0
- package/src/theme/theme-provider.tsx +48 -8
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { Select as BaseSelect } from "@base-ui-components/react/select";
|
|
4
|
+
import { Check, ChevronDown, ChevronUp } from "lucide-react";
|
|
4
5
|
import * as React from "react";
|
|
5
6
|
import { tv, type VariantProps } from "tailwind-variants";
|
|
6
7
|
import {
|
|
@@ -19,6 +20,7 @@ import { cn } from "@/lib/utils";
|
|
|
19
20
|
* - Focus/Open: Accent border with focus ring
|
|
20
21
|
* - Selected: Has a value selected (darker text)
|
|
21
22
|
* - Disabled: Reduced opacity, not interactive
|
|
23
|
+
* - Invalid: Error border and styling
|
|
22
24
|
*/
|
|
23
25
|
const selectTriggerVariants = tv({
|
|
24
26
|
base: [
|
|
@@ -29,6 +31,8 @@ const selectTriggerVariants = tv({
|
|
|
29
31
|
"data-[disabled]:bg-ui-control-background-disabled data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50",
|
|
30
32
|
// Open state styling
|
|
31
33
|
"data-[popup-open]:border-ui-accent-base data-[popup-open]:ring-4 data-[popup-open]:ring-ui-color-focus data-[popup-open]:bg-ui-control-background",
|
|
34
|
+
// Invalid state (aria-invalid)
|
|
35
|
+
"aria-[invalid=true]:border-ui-error-color aria-[invalid=true]:ring-ui-error-color/20",
|
|
32
36
|
],
|
|
33
37
|
variants: {
|
|
34
38
|
size: formControlSizes,
|
|
@@ -42,16 +46,25 @@ const selectTriggerVariants = tv({
|
|
|
42
46
|
|
|
43
47
|
/**
|
|
44
48
|
* Select popup/menu variants
|
|
49
|
+
*
|
|
50
|
+
* Uses overlay tokens for consistent floating panel styling:
|
|
51
|
+
* - color.overlay.background - Light background
|
|
52
|
+
* - color.overlay.border - Subtle border
|
|
53
|
+
* - surface.overlay.radius - Rounded corners
|
|
45
54
|
*/
|
|
46
55
|
const selectPopupVariants = tv({
|
|
47
56
|
base: [
|
|
48
57
|
// Layout - match trigger width using CSS custom property from Base UI
|
|
49
|
-
"flex flex-col gap-2 p-
|
|
58
|
+
"flex flex-col gap-2 p-8",
|
|
50
59
|
"w-[var(--anchor-width)]",
|
|
51
|
-
// Background
|
|
52
|
-
"bg-
|
|
53
|
-
//
|
|
54
|
-
"
|
|
60
|
+
// Background - uses overlay token
|
|
61
|
+
"bg-overlay-background",
|
|
62
|
+
// Border - uses overlay token for subtle border
|
|
63
|
+
"border border-overlay-border",
|
|
64
|
+
// Border radius - uses overlay token for consistency with other overlays
|
|
65
|
+
"rounded-surface-overlay",
|
|
66
|
+
// Shadow for elevation
|
|
67
|
+
"shadow-lg",
|
|
55
68
|
// Animation
|
|
56
69
|
"origin-[var(--transform-origin)]",
|
|
57
70
|
"transition-[transform,scale,opacity] duration-150",
|
|
@@ -59,6 +72,8 @@ const selectPopupVariants = tv({
|
|
|
59
72
|
"data-[ending-style]:scale-95 data-[ending-style]:opacity-0",
|
|
60
73
|
// Ensure it's above other content
|
|
61
74
|
"z-50",
|
|
75
|
+
// Scrollable support
|
|
76
|
+
"overflow-y-auto max-h-[var(--available-height,300px)]",
|
|
62
77
|
],
|
|
63
78
|
});
|
|
64
79
|
|
|
@@ -95,46 +110,27 @@ const selectOptionVariants = tv({
|
|
|
95
110
|
});
|
|
96
111
|
|
|
97
112
|
/**
|
|
98
|
-
*
|
|
113
|
+
* Select separator variants
|
|
99
114
|
*/
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
viewBox="0 0 16 16"
|
|
104
|
-
fill="none"
|
|
105
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
106
|
-
aria-hidden="true"
|
|
107
|
-
>
|
|
108
|
-
<path
|
|
109
|
-
d="M4 6L8 10L12 6"
|
|
110
|
-
stroke="currentColor"
|
|
111
|
-
strokeWidth="1.5"
|
|
112
|
-
strokeLinecap="round"
|
|
113
|
-
strokeLinejoin="round"
|
|
114
|
-
/>
|
|
115
|
-
</svg>
|
|
116
|
-
);
|
|
115
|
+
const selectSeparatorVariants = tv({
|
|
116
|
+
base: ["h-px my-6 -mx-8", "bg-overlay-border"],
|
|
117
|
+
});
|
|
117
118
|
|
|
118
119
|
/**
|
|
119
|
-
*
|
|
120
|
+
* Select scroll arrow variants (shared by up and down)
|
|
120
121
|
*/
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
strokeLinecap="round"
|
|
134
|
-
strokeLinejoin="round"
|
|
135
|
-
/>
|
|
136
|
-
</svg>
|
|
137
|
-
);
|
|
122
|
+
const selectScrollArrowVariants = tv({
|
|
123
|
+
base: [
|
|
124
|
+
"flex items-center justify-center",
|
|
125
|
+
"py-4",
|
|
126
|
+
"text-text-muted",
|
|
127
|
+
"cursor-default",
|
|
128
|
+
// Sticky positioning for scroll indicators
|
|
129
|
+
"data-[direction=up]:sticky data-[direction=up]:top-0",
|
|
130
|
+
"data-[direction=down]:sticky data-[direction=down]:bottom-0",
|
|
131
|
+
"bg-overlay-background",
|
|
132
|
+
],
|
|
133
|
+
});
|
|
138
134
|
|
|
139
135
|
// ============================================================================
|
|
140
136
|
// Select Root
|
|
@@ -161,17 +157,33 @@ export interface SelectTriggerProps
|
|
|
161
157
|
VariantProps<typeof selectTriggerVariants> {
|
|
162
158
|
className?: string;
|
|
163
159
|
placeholder?: string;
|
|
160
|
+
/**
|
|
161
|
+
* Accessible label for the select trigger.
|
|
162
|
+
* Required for accessibility when no visible label is present.
|
|
163
|
+
*/
|
|
164
|
+
"aria-label"?: string;
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerProps>(
|
|
167
168
|
(
|
|
168
|
-
{
|
|
169
|
+
{
|
|
170
|
+
className,
|
|
171
|
+
size,
|
|
172
|
+
error,
|
|
173
|
+
placeholder = "Select option...",
|
|
174
|
+
"aria-label": ariaLabel,
|
|
175
|
+
...props
|
|
176
|
+
},
|
|
169
177
|
ref,
|
|
170
178
|
) => {
|
|
171
179
|
return (
|
|
172
180
|
<BaseSelect.Trigger
|
|
173
181
|
ref={ref}
|
|
182
|
+
aria-label={ariaLabel ?? placeholder}
|
|
183
|
+
aria-invalid={error || undefined}
|
|
174
184
|
className={cn(selectTriggerVariants({ size, error }), className)}
|
|
185
|
+
data-size={size ?? "default"}
|
|
186
|
+
data-error={error ?? false}
|
|
175
187
|
{...props}
|
|
176
188
|
>
|
|
177
189
|
<BaseSelect.Value>
|
|
@@ -183,8 +195,8 @@ const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerProps>(
|
|
|
183
195
|
)
|
|
184
196
|
}
|
|
185
197
|
</BaseSelect.Value>
|
|
186
|
-
<BaseSelect.Icon>
|
|
187
|
-
<
|
|
198
|
+
<BaseSelect.Icon aria-hidden="true">
|
|
199
|
+
<ChevronDown className="size-16 text-gray-500 shrink-0" />
|
|
188
200
|
</BaseSelect.Icon>
|
|
189
201
|
</BaseSelect.Trigger>
|
|
190
202
|
);
|
|
@@ -193,19 +205,53 @@ const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerProps>(
|
|
|
193
205
|
SelectTrigger.displayName = "SelectTrigger";
|
|
194
206
|
|
|
195
207
|
// ============================================================================
|
|
196
|
-
// Select
|
|
208
|
+
// Select Value (for custom trigger compositions)
|
|
209
|
+
// ============================================================================
|
|
210
|
+
|
|
211
|
+
export interface SelectValueProps
|
|
212
|
+
extends Omit<React.ComponentProps<typeof BaseSelect.Value>, "className"> {
|
|
213
|
+
className?: string;
|
|
214
|
+
placeholder?: string;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const SelectValue = React.forwardRef<HTMLSpanElement, SelectValueProps>(
|
|
218
|
+
({ className, placeholder = "Select option...", ...props }, ref) => {
|
|
219
|
+
return (
|
|
220
|
+
<BaseSelect.Value ref={ref} className={className} {...props}>
|
|
221
|
+
{(value) =>
|
|
222
|
+
value ? value : <span className="text-text-muted">{placeholder}</span>
|
|
223
|
+
}
|
|
224
|
+
</BaseSelect.Value>
|
|
225
|
+
);
|
|
226
|
+
},
|
|
227
|
+
);
|
|
228
|
+
SelectValue.displayName = "SelectValue";
|
|
229
|
+
|
|
230
|
+
// ============================================================================
|
|
231
|
+
// Select Portal & Popup (Content)
|
|
197
232
|
// ============================================================================
|
|
198
233
|
|
|
199
234
|
export interface SelectPopupProps
|
|
200
235
|
extends Omit<React.ComponentProps<typeof BaseSelect.Popup>, "className"> {
|
|
201
236
|
className?: string;
|
|
237
|
+
/**
|
|
238
|
+
* Whether the selected item should align with the trigger.
|
|
239
|
+
* When true (default), the popup positions so the selected item appears over the trigger.
|
|
240
|
+
* When false, the popup aligns to the trigger edge.
|
|
241
|
+
*/
|
|
242
|
+
alignItemWithTrigger?: boolean;
|
|
202
243
|
}
|
|
203
244
|
|
|
204
245
|
const SelectPopup = React.forwardRef<HTMLDivElement, SelectPopupProps>(
|
|
205
|
-
({ className, children, ...props }, ref) => {
|
|
246
|
+
({ className, children, alignItemWithTrigger = true, ...props }, ref) => {
|
|
206
247
|
return (
|
|
207
248
|
<BaseSelect.Portal>
|
|
208
|
-
<BaseSelect.Positioner
|
|
249
|
+
<BaseSelect.Positioner
|
|
250
|
+
side="bottom"
|
|
251
|
+
sideOffset={4}
|
|
252
|
+
align="start"
|
|
253
|
+
alignItemWithTrigger={alignItemWithTrigger}
|
|
254
|
+
>
|
|
209
255
|
<BaseSelect.Popup
|
|
210
256
|
ref={ref}
|
|
211
257
|
className={cn(selectPopupVariants(), className)}
|
|
@@ -220,6 +266,9 @@ const SelectPopup = React.forwardRef<HTMLDivElement, SelectPopupProps>(
|
|
|
220
266
|
);
|
|
221
267
|
SelectPopup.displayName = "SelectPopup";
|
|
222
268
|
|
|
269
|
+
// Alias for shadcn compatibility
|
|
270
|
+
const SelectContent = SelectPopup;
|
|
271
|
+
|
|
223
272
|
// ============================================================================
|
|
224
273
|
// Select Option (wraps Base UI's Select.Item)
|
|
225
274
|
// ============================================================================
|
|
@@ -238,8 +287,8 @@ const SelectOption = React.forwardRef<HTMLDivElement, SelectOptionProps>(
|
|
|
238
287
|
{...props}
|
|
239
288
|
>
|
|
240
289
|
<BaseSelect.ItemText>{children}</BaseSelect.ItemText>
|
|
241
|
-
<BaseSelect.ItemIndicator>
|
|
242
|
-
<
|
|
290
|
+
<BaseSelect.ItemIndicator aria-hidden="true">
|
|
291
|
+
<Check className="size-14 shrink-0" />
|
|
243
292
|
</BaseSelect.ItemIndicator>
|
|
244
293
|
</BaseSelect.Item>
|
|
245
294
|
);
|
|
@@ -247,6 +296,9 @@ const SelectOption = React.forwardRef<HTMLDivElement, SelectOptionProps>(
|
|
|
247
296
|
);
|
|
248
297
|
SelectOption.displayName = "SelectOption";
|
|
249
298
|
|
|
299
|
+
// Alias for shadcn compatibility
|
|
300
|
+
const SelectItem = SelectOption;
|
|
301
|
+
|
|
250
302
|
// ============================================================================
|
|
251
303
|
// Select Group
|
|
252
304
|
// ============================================================================
|
|
@@ -298,27 +350,128 @@ const SelectGroupLabel = React.forwardRef<
|
|
|
298
350
|
});
|
|
299
351
|
SelectGroupLabel.displayName = "SelectGroupLabel";
|
|
300
352
|
|
|
353
|
+
// Alias for shadcn compatibility
|
|
354
|
+
const SelectLabel = SelectGroupLabel;
|
|
355
|
+
|
|
356
|
+
// ============================================================================
|
|
357
|
+
// Select Separator
|
|
358
|
+
// ============================================================================
|
|
359
|
+
|
|
360
|
+
export interface SelectSeparatorProps
|
|
361
|
+
extends Omit<React.ComponentProps<typeof BaseSelect.Separator>, "className"> {
|
|
362
|
+
className?: string;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const SelectSeparator = React.forwardRef<HTMLDivElement, SelectSeparatorProps>(
|
|
366
|
+
({ className, ...props }, ref) => {
|
|
367
|
+
return (
|
|
368
|
+
<BaseSelect.Separator
|
|
369
|
+
ref={ref}
|
|
370
|
+
className={cn(selectSeparatorVariants(), className)}
|
|
371
|
+
{...props}
|
|
372
|
+
/>
|
|
373
|
+
);
|
|
374
|
+
},
|
|
375
|
+
);
|
|
376
|
+
SelectSeparator.displayName = "SelectSeparator";
|
|
377
|
+
|
|
378
|
+
// ============================================================================
|
|
379
|
+
// Select Scroll Up Arrow
|
|
380
|
+
// ============================================================================
|
|
381
|
+
|
|
382
|
+
export interface SelectScrollUpArrowProps
|
|
383
|
+
extends Omit<
|
|
384
|
+
React.ComponentProps<typeof BaseSelect.ScrollUpArrow>,
|
|
385
|
+
"className"
|
|
386
|
+
> {
|
|
387
|
+
className?: string;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const SelectScrollUpArrow = React.forwardRef<
|
|
391
|
+
HTMLDivElement,
|
|
392
|
+
SelectScrollUpArrowProps
|
|
393
|
+
>(({ className, ...props }, ref) => {
|
|
394
|
+
return (
|
|
395
|
+
<BaseSelect.ScrollUpArrow
|
|
396
|
+
ref={ref}
|
|
397
|
+
data-direction="up"
|
|
398
|
+
aria-label="Scroll up"
|
|
399
|
+
className={cn(selectScrollArrowVariants(), className)}
|
|
400
|
+
{...props}
|
|
401
|
+
>
|
|
402
|
+
<ChevronUp className="size-12" aria-hidden="true" />
|
|
403
|
+
</BaseSelect.ScrollUpArrow>
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
SelectScrollUpArrow.displayName = "SelectScrollUpArrow";
|
|
407
|
+
|
|
408
|
+
// ============================================================================
|
|
409
|
+
// Select Scroll Down Arrow
|
|
410
|
+
// ============================================================================
|
|
411
|
+
|
|
412
|
+
export interface SelectScrollDownArrowProps
|
|
413
|
+
extends Omit<
|
|
414
|
+
React.ComponentProps<typeof BaseSelect.ScrollDownArrow>,
|
|
415
|
+
"className"
|
|
416
|
+
> {
|
|
417
|
+
className?: string;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const SelectScrollDownArrow = React.forwardRef<
|
|
421
|
+
HTMLDivElement,
|
|
422
|
+
SelectScrollDownArrowProps
|
|
423
|
+
>(({ className, ...props }, ref) => {
|
|
424
|
+
return (
|
|
425
|
+
<BaseSelect.ScrollDownArrow
|
|
426
|
+
ref={ref}
|
|
427
|
+
data-direction="down"
|
|
428
|
+
aria-label="Scroll down"
|
|
429
|
+
className={cn(selectScrollArrowVariants(), className)}
|
|
430
|
+
{...props}
|
|
431
|
+
>
|
|
432
|
+
<ChevronDown className="size-12" aria-hidden="true" />
|
|
433
|
+
</BaseSelect.ScrollDownArrow>
|
|
434
|
+
);
|
|
435
|
+
});
|
|
436
|
+
SelectScrollDownArrow.displayName = "SelectScrollDownArrow";
|
|
437
|
+
|
|
301
438
|
// ============================================================================
|
|
302
439
|
// Compound Component Export
|
|
303
440
|
// ============================================================================
|
|
304
441
|
|
|
305
442
|
export const Select = Object.assign(SelectRoot, {
|
|
306
443
|
Trigger: SelectTrigger,
|
|
444
|
+
Value: SelectValue,
|
|
307
445
|
Popup: SelectPopup,
|
|
446
|
+
Content: SelectContent,
|
|
308
447
|
Option: SelectOption,
|
|
448
|
+
Item: SelectItem,
|
|
309
449
|
Group: SelectGroup,
|
|
310
450
|
GroupLabel: SelectGroupLabel,
|
|
451
|
+
Label: SelectLabel,
|
|
452
|
+
Separator: SelectSeparator,
|
|
453
|
+
ScrollUpArrow: SelectScrollUpArrow,
|
|
454
|
+
ScrollDownArrow: SelectScrollDownArrow,
|
|
311
455
|
});
|
|
312
456
|
|
|
313
457
|
// Also export individual components for flexibility
|
|
314
458
|
export {
|
|
315
459
|
SelectRoot,
|
|
316
460
|
SelectTrigger,
|
|
461
|
+
SelectValue,
|
|
317
462
|
SelectPopup,
|
|
463
|
+
SelectContent,
|
|
318
464
|
SelectOption,
|
|
465
|
+
SelectItem,
|
|
319
466
|
SelectGroup,
|
|
320
467
|
SelectGroupLabel,
|
|
468
|
+
SelectLabel,
|
|
469
|
+
SelectSeparator,
|
|
470
|
+
SelectScrollUpArrow,
|
|
471
|
+
SelectScrollDownArrow,
|
|
321
472
|
selectTriggerVariants,
|
|
322
473
|
selectPopupVariants,
|
|
323
474
|
selectOptionVariants,
|
|
475
|
+
selectSeparatorVariants,
|
|
476
|
+
selectScrollArrowVariants,
|
|
324
477
|
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
5
|
+
import type { CaptionCue } from "@/hooks/use-captions";
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Caption overlay variants using semantic tokens.
|
|
10
|
+
*/
|
|
11
|
+
const captionOverlayVariants = tv({
|
|
12
|
+
base: [
|
|
13
|
+
// Positioning - absolute at bottom of video container
|
|
14
|
+
"pointer-events-none",
|
|
15
|
+
"absolute right-0 left-0",
|
|
16
|
+
"z-10",
|
|
17
|
+
"flex justify-center",
|
|
18
|
+
"px-4",
|
|
19
|
+
],
|
|
20
|
+
variants: {
|
|
21
|
+
position: {
|
|
22
|
+
bottom: "bottom-64",
|
|
23
|
+
"bottom-sm": "bottom-24",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultVariants: {
|
|
27
|
+
position: "bottom-sm",
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Caption text box variants.
|
|
33
|
+
*/
|
|
34
|
+
const captionTextVariants = tv({
|
|
35
|
+
base: [
|
|
36
|
+
"flex items-center justify-center",
|
|
37
|
+
"w-fit max-w-[80%]",
|
|
38
|
+
"gap-10",
|
|
39
|
+
"px-12 py-6",
|
|
40
|
+
"text-center",
|
|
41
|
+
"font-normal leading-[1.4]",
|
|
42
|
+
"rounded-6",
|
|
43
|
+
"bg-video-player-caption-bg text-video-player-caption-text",
|
|
44
|
+
],
|
|
45
|
+
variants: {
|
|
46
|
+
size: {
|
|
47
|
+
sm: "text-14",
|
|
48
|
+
md: "[font-size:clamp(16px,2vw,24px)]",
|
|
49
|
+
lg: "text-24",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
defaultVariants: {
|
|
53
|
+
size: "md",
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export interface CaptionOverlayProps
|
|
58
|
+
extends React.HTMLAttributes<HTMLOutputElement>,
|
|
59
|
+
VariantProps<typeof captionOverlayVariants>,
|
|
60
|
+
VariantProps<typeof captionTextVariants> {
|
|
61
|
+
/** Caption cue to display */
|
|
62
|
+
cue?: CaptionCue | null;
|
|
63
|
+
/** Caption text to display (alternative to cue) */
|
|
64
|
+
text?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* CaptionOverlay component.
|
|
69
|
+
*
|
|
70
|
+
* Displays caption text overlaid on video content.
|
|
71
|
+
* Styled to match the DGA video player implementation.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```tsx
|
|
75
|
+
* <CaptionOverlay cue={activeCue} />
|
|
76
|
+
*
|
|
77
|
+
* // Or with plain text
|
|
78
|
+
* <CaptionOverlay text="Hello, world!" />
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
const CaptionOverlay = React.forwardRef<HTMLOutputElement, CaptionOverlayProps>(
|
|
82
|
+
({ className, cue, text, position, size, ...props }, ref) => {
|
|
83
|
+
// Use cue text or fallback to text prop
|
|
84
|
+
const displayText = cue?.text ?? text;
|
|
85
|
+
|
|
86
|
+
// Don't render anything if no caption text
|
|
87
|
+
if (!displayText) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<output
|
|
93
|
+
ref={ref}
|
|
94
|
+
className={cn(captionOverlayVariants({ position }), className)}
|
|
95
|
+
aria-live="polite"
|
|
96
|
+
aria-label="Video caption"
|
|
97
|
+
{...props}
|
|
98
|
+
>
|
|
99
|
+
<span className={captionTextVariants({ size })}>{displayText}</span>
|
|
100
|
+
</output>
|
|
101
|
+
);
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
CaptionOverlay.displayName = "CaptionOverlay";
|
|
106
|
+
|
|
107
|
+
export { CaptionOverlay, captionOverlayVariants, captionTextVariants };
|