@nationaldesignstudio/react 0.6.0 → 0.7.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.
Files changed (106) hide show
  1. package/dist/accordion/index.d.ts +95 -0
  2. package/dist/accordion/index.js +143 -0
  3. package/dist/accordion/index.js.map +1 -0
  4. package/dist/background/index.d.ts +149 -0
  5. package/dist/background/index.js +200 -0
  6. package/dist/background/index.js.map +1 -0
  7. package/dist/banner/index.d.ts +101 -0
  8. package/dist/banner/index.js +81 -0
  9. package/dist/banner/index.js.map +1 -0
  10. package/dist/blurred-video-backdrop/index.d.ts +233 -0
  11. package/dist/blurred-video-backdrop/index.js +266 -0
  12. package/dist/blurred-video-backdrop/index.js.map +1 -0
  13. package/dist/button/index.d.ts +180 -0
  14. package/dist/button/index.js +169 -0
  15. package/dist/button/index.js.map +1 -0
  16. package/dist/button-B2g5fH9b.d.ts +152 -0
  17. package/dist/card/index.d.ts +406 -0
  18. package/dist/card/index.js +219 -0
  19. package/dist/card/index.js.map +1 -0
  20. package/dist/card-grid/index.d.ts +90 -0
  21. package/dist/card-grid/index.js +74 -0
  22. package/dist/card-grid/index.js.map +1 -0
  23. package/dist/component-registry.md +136 -2
  24. package/dist/dev-toolbar/index.d.ts +8 -0
  25. package/dist/dev-toolbar/index.js +206 -0
  26. package/dist/dev-toolbar/index.js.map +1 -0
  27. package/dist/dialog/index.d.ts +268 -0
  28. package/dist/dialog/index.js +288 -0
  29. package/dist/dialog/index.js.map +1 -0
  30. package/dist/faq-section/index.d.ts +47 -0
  31. package/dist/faq-section/index.js +152 -0
  32. package/dist/faq-section/index.js.map +1 -0
  33. package/dist/grid-overlay/index.d.ts +10 -0
  34. package/dist/grid-overlay/index.js +38 -0
  35. package/dist/grid-overlay/index.js.map +1 -0
  36. package/dist/hero/index.d.ts +462 -0
  37. package/dist/hero/index.js +494 -0
  38. package/dist/hero/index.js.map +1 -0
  39. package/dist/hooks/index.d.ts +150 -0
  40. package/dist/hooks/index.js +339 -0
  41. package/dist/hooks/index.js.map +1 -0
  42. package/dist/index.d.ts +46 -5339
  43. package/dist/index.js +157 -4080
  44. package/dist/index.js.map +1 -1
  45. package/dist/input/index.d.ts +404 -0
  46. package/dist/input/index.js +393 -0
  47. package/dist/input/index.js.map +1 -0
  48. package/dist/navbar/index.d.ts +68 -0
  49. package/dist/navbar/index.js +227 -0
  50. package/dist/navbar/index.js.map +1 -0
  51. package/dist/ndstudio-footer/index.d.ts +32 -0
  52. package/dist/ndstudio-footer/index.js +35 -0
  53. package/dist/ndstudio-footer/index.js.map +1 -0
  54. package/dist/pager-control/index.d.ts +173 -0
  55. package/dist/pager-control/index.js +267 -0
  56. package/dist/pager-control/index.js.map +1 -0
  57. package/dist/popover/index.d.ts +200 -0
  58. package/dist/popover/index.js +290 -0
  59. package/dist/popover/index.js.map +1 -0
  60. package/dist/prose/index.d.ts +39 -0
  61. package/dist/prose/index.js +56 -0
  62. package/dist/prose/index.js.map +1 -0
  63. package/dist/quote-block/index.d.ts +156 -0
  64. package/dist/quote-block/index.js +321 -0
  65. package/dist/quote-block/index.js.map +1 -0
  66. package/dist/river/index.d.ts +100 -0
  67. package/dist/river/index.js +107 -0
  68. package/dist/river/index.js.map +1 -0
  69. package/dist/select/index.d.ts +188 -0
  70. package/dist/select/index.js +295 -0
  71. package/dist/select/index.js.map +1 -0
  72. package/dist/theme/index.d.ts +149 -0
  73. package/dist/theme/index.js +211 -0
  74. package/dist/theme/index.js.map +1 -0
  75. package/dist/theme-CzBPUlh_.d.ts +332 -0
  76. package/dist/tooltip/index.d.ts +166 -0
  77. package/dist/tooltip/index.js +200 -0
  78. package/dist/tooltip/index.js.map +1 -0
  79. package/dist/tout/index.d.ts +157 -0
  80. package/dist/tout/index.js +315 -0
  81. package/dist/tout/index.js.map +1 -0
  82. package/dist/two-column-section/index.d.ts +122 -0
  83. package/dist/two-column-section/index.js +121 -0
  84. package/dist/two-column-section/index.js.map +1 -0
  85. package/dist/us-gov-banner/index.d.ts +141 -0
  86. package/dist/us-gov-banner/index.js +74 -0
  87. package/dist/us-gov-banner/index.js.map +1 -0
  88. package/dist/use-captions-AkKlJhov.d.ts +71 -0
  89. package/dist/utils/index.d.ts +7 -0
  90. package/dist/utils/index.js +12 -0
  91. package/dist/utils/index.js.map +1 -0
  92. package/dist/video-dialog/index.d.ts +106 -0
  93. package/dist/video-dialog/index.js +1305 -0
  94. package/dist/video-dialog/index.js.map +1 -0
  95. package/dist/video-player/index.d.ts +115 -0
  96. package/dist/video-player/index.js +879 -0
  97. package/dist/video-player/index.js.map +1 -0
  98. package/dist/video-player-qxf-BURH.d.ts +236 -0
  99. package/dist/video-with-backdrop/index.d.ts +267 -0
  100. package/dist/video-with-backdrop/index.js +1284 -0
  101. package/dist/video-with-backdrop/index.js.map +1 -0
  102. package/package.json +13 -2
  103. package/src/components/organisms/us-gov-banner/us-gov-banner.tsx +5 -27
  104. package/src/theme/hooks.ts +2 -0
  105. package/src/theme/index.ts +2 -0
  106. package/src/theme/theme-provider.tsx +2 -0
@@ -0,0 +1,879 @@
1
+ "use client";
2
+ import * as React4 from 'react';
3
+ import { tv, cnBase } from 'tailwind-variants';
4
+ import { clsx } from 'clsx';
5
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
+ import { MediaController, MediaLoadingIndicator, MediaControlBar, MediaPlayButton, MediaMuteButton, MediaVolumeRange, MediaTimeDisplay, MediaTimeRange } from 'media-chrome/react';
7
+
8
+ // src/components/atoms/video-player/caption-overlay.tsx
9
+ function cn(...inputs) {
10
+ return cnBase(clsx(inputs));
11
+ }
12
+ var captionOverlayVariants = tv({
13
+ base: [
14
+ // Positioning - absolute at bottom of video container
15
+ "pointer-events-none",
16
+ "absolute right-0 left-0",
17
+ "z-10",
18
+ "flex justify-center",
19
+ "px-4"
20
+ ],
21
+ variants: {
22
+ position: {
23
+ bottom: "bottom-64",
24
+ "bottom-sm": "bottom-24"
25
+ }
26
+ },
27
+ defaultVariants: {
28
+ position: "bottom-sm"
29
+ }
30
+ });
31
+ var captionTextVariants = tv({
32
+ base: [
33
+ "flex items-center justify-center",
34
+ "w-fit max-w-[80%]",
35
+ "gap-10",
36
+ "px-12 py-6",
37
+ "text-center",
38
+ "font-normal leading-[1.4]",
39
+ "rounded-6",
40
+ "bg-video-player-caption-bg text-video-player-caption-text"
41
+ ],
42
+ variants: {
43
+ size: {
44
+ sm: "text-14",
45
+ md: "[font-size:clamp(16px,2vw,24px)]",
46
+ lg: "text-24"
47
+ }
48
+ },
49
+ defaultVariants: {
50
+ size: "md"
51
+ }
52
+ });
53
+ var CaptionOverlay = React4.forwardRef(
54
+ ({ className, cue, text, position, size, ...props }, ref) => {
55
+ const displayText = cue?.text ?? text;
56
+ if (!displayText) {
57
+ return null;
58
+ }
59
+ return /* @__PURE__ */ jsx(
60
+ "output",
61
+ {
62
+ ref,
63
+ className: cn(captionOverlayVariants({ position }), className),
64
+ "aria-live": "polite",
65
+ "aria-label": "Video caption",
66
+ ...props,
67
+ children: /* @__PURE__ */ jsx("span", { className: captionTextVariants({ size }), children: displayText })
68
+ }
69
+ );
70
+ }
71
+ );
72
+ CaptionOverlay.displayName = "CaptionOverlay";
73
+ function parseVttTimestamp(timestamp) {
74
+ const parts = timestamp.trim().split(":");
75
+ let hours = 0;
76
+ let minutes = 0;
77
+ let seconds = 0;
78
+ if (parts.length === 3) {
79
+ hours = Number.parseInt(parts[0], 10);
80
+ minutes = Number.parseInt(parts[1], 10);
81
+ seconds = Number.parseFloat(parts[2]);
82
+ } else if (parts.length === 2) {
83
+ minutes = Number.parseInt(parts[0], 10);
84
+ seconds = Number.parseFloat(parts[1]);
85
+ }
86
+ return hours * 3600 + minutes * 60 + seconds;
87
+ }
88
+ function parseVtt(vttContent) {
89
+ const cues = [];
90
+ const lines = vttContent.trim().split("\n");
91
+ let i = 0;
92
+ while (i < lines.length && !lines[i].includes("-->")) {
93
+ i++;
94
+ }
95
+ while (i < lines.length) {
96
+ const line = lines[i].trim();
97
+ if (line.includes("-->")) {
98
+ const [startStr, endStr] = line.split("-->").map((s) => s.trim());
99
+ const endParts = endStr.split(" ");
100
+ const endTime = parseVttTimestamp(endParts[0]);
101
+ const startTime = parseVttTimestamp(startStr);
102
+ const textLines = [];
103
+ i++;
104
+ while (i < lines.length && lines[i].trim() !== "" && !lines[i].includes("-->")) {
105
+ if (!/^\d+$/.test(lines[i].trim())) {
106
+ textLines.push(lines[i].trim());
107
+ }
108
+ i++;
109
+ }
110
+ if (textLines.length > 0) {
111
+ cues.push({
112
+ id: `cue-${cues.length}`,
113
+ startTime,
114
+ endTime,
115
+ text: textLines.join("\n")
116
+ });
117
+ }
118
+ } else {
119
+ i++;
120
+ }
121
+ }
122
+ return cues;
123
+ }
124
+ function stripVttTags(text) {
125
+ return text.replace(/<\/?[^>]+(>|$)/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").trim();
126
+ }
127
+ function useCaptions(options = {}) {
128
+ const { src, content, stripTags = true, currentTime: externalTime } = options;
129
+ const [cues, setCues] = React4.useState([]);
130
+ const [internalTime, setInternalTime] = React4.useState(0);
131
+ const [isLoading, setIsLoading] = React4.useState(false);
132
+ const [error, setError] = React4.useState(null);
133
+ const currentTime = externalTime ?? internalTime;
134
+ React4.useEffect(() => {
135
+ if (content) {
136
+ const parsed = parseVtt(content);
137
+ setCues(
138
+ stripTags ? parsed.map((cue) => ({ ...cue, text: stripVttTags(cue.text) })) : parsed
139
+ );
140
+ return;
141
+ }
142
+ if (!src) {
143
+ setCues([]);
144
+ return;
145
+ }
146
+ setIsLoading(true);
147
+ setError(null);
148
+ fetch(src).then((response) => {
149
+ if (!response.ok) {
150
+ throw new Error(`Failed to fetch captions: ${response.status}`);
151
+ }
152
+ return response.text();
153
+ }).then((vttContent) => {
154
+ const parsed = parseVtt(vttContent);
155
+ setCues(
156
+ stripTags ? parsed.map((cue) => ({ ...cue, text: stripVttTags(cue.text) })) : parsed
157
+ );
158
+ }).catch((err) => {
159
+ setError(err instanceof Error ? err : new Error(String(err)));
160
+ }).finally(() => {
161
+ setIsLoading(false);
162
+ });
163
+ }, [src, content, stripTags]);
164
+ const activeCue = React4.useMemo(() => {
165
+ return cues.find(
166
+ (cue) => currentTime >= cue.startTime && currentTime <= cue.endTime
167
+ ) ?? null;
168
+ }, [cues, currentTime]);
169
+ const handleSetCurrentTime = React4.useCallback((time) => {
170
+ setInternalTime(time);
171
+ }, []);
172
+ return {
173
+ cues,
174
+ activeCue,
175
+ setCurrentTime: handleSetCurrentTime,
176
+ isLoading,
177
+ error
178
+ };
179
+ }
180
+ function useVideoKeyboard({
181
+ videoRef,
182
+ enabled = true,
183
+ seekAmount = 5,
184
+ volumeStep = 0.1,
185
+ onTogglePlay,
186
+ onToggleFullscreen,
187
+ onToggleCaptions,
188
+ onShowControls
189
+ }) {
190
+ const handleKeyDown = React4.useCallback(
191
+ (e) => {
192
+ if (!enabled) return;
193
+ const video = videoRef.current;
194
+ if (!video) return;
195
+ const target = e.target;
196
+ if (target.tagName === "BUTTON" || target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.closest("button") || target.closest("input") || target.closest("[role='slider']")) {
197
+ return;
198
+ }
199
+ let handled = false;
200
+ switch (e.key) {
201
+ // Play/Pause
202
+ case " ":
203
+ case "Spacebar":
204
+ case "k":
205
+ if (onTogglePlay) {
206
+ onTogglePlay();
207
+ } else {
208
+ if (video.paused) {
209
+ video.play().catch(() => {
210
+ });
211
+ } else {
212
+ video.pause();
213
+ }
214
+ }
215
+ handled = true;
216
+ break;
217
+ // Seek backward
218
+ case "ArrowLeft":
219
+ case "j":
220
+ video.currentTime = Math.max(0, video.currentTime - seekAmount);
221
+ handled = true;
222
+ break;
223
+ // Seek forward
224
+ case "ArrowRight":
225
+ case "l":
226
+ video.currentTime = Math.min(
227
+ video.duration || 0,
228
+ video.currentTime + seekAmount
229
+ );
230
+ handled = true;
231
+ break;
232
+ // Volume up
233
+ case "ArrowUp":
234
+ video.volume = Math.min(1, video.volume + volumeStep);
235
+ video.muted = false;
236
+ handled = true;
237
+ break;
238
+ // Volume down
239
+ case "ArrowDown":
240
+ video.volume = Math.max(0, video.volume - volumeStep);
241
+ handled = true;
242
+ break;
243
+ // Toggle mute
244
+ case "m":
245
+ case "M":
246
+ video.muted = !video.muted;
247
+ handled = true;
248
+ break;
249
+ // Toggle fullscreen
250
+ case "f":
251
+ case "F":
252
+ onToggleFullscreen?.();
253
+ handled = true;
254
+ break;
255
+ // Toggle captions
256
+ case "c":
257
+ case "C":
258
+ onToggleCaptions?.();
259
+ handled = true;
260
+ break;
261
+ // Jump to start
262
+ case "Home":
263
+ video.currentTime = 0;
264
+ handled = true;
265
+ break;
266
+ // Jump to end
267
+ case "End":
268
+ video.currentTime = video.duration || 0;
269
+ handled = true;
270
+ break;
271
+ // Number keys for percentage seeking (0-9)
272
+ case "0":
273
+ case "1":
274
+ case "2":
275
+ case "3":
276
+ case "4":
277
+ case "5":
278
+ case "6":
279
+ case "7":
280
+ case "8":
281
+ case "9":
282
+ if (video.duration) {
283
+ const percent = parseInt(e.key, 10) / 10;
284
+ video.currentTime = video.duration * percent;
285
+ handled = true;
286
+ }
287
+ break;
288
+ }
289
+ if (handled) {
290
+ e.preventDefault();
291
+ e.stopPropagation();
292
+ onShowControls?.();
293
+ }
294
+ },
295
+ [
296
+ enabled,
297
+ videoRef,
298
+ seekAmount,
299
+ volumeStep,
300
+ onTogglePlay,
301
+ onToggleFullscreen,
302
+ onToggleCaptions,
303
+ onShowControls
304
+ ]
305
+ );
306
+ const containerProps = React4.useMemo(
307
+ () => ({
308
+ onKeyDown: handleKeyDown,
309
+ tabIndex: 0,
310
+ role: "application",
311
+ "aria-label": "Video player, press space to play or pause"
312
+ }),
313
+ [handleKeyDown]
314
+ );
315
+ return {
316
+ handleKeyDown,
317
+ containerProps
318
+ };
319
+ }
320
+ var videoPlayerVariants = tv({
321
+ base: [
322
+ "relative",
323
+ "bg-black",
324
+ "overflow-hidden",
325
+ // Focus styling for keyboard navigation
326
+ "focus:outline-none",
327
+ "focus-visible:ring-2",
328
+ "focus-visible:ring-white/50"
329
+ ],
330
+ variants: {
331
+ aspectRatio: {
332
+ "16/9": "aspect-video",
333
+ "4/3": "aspect-[4/3]",
334
+ "1/1": "aspect-square",
335
+ "9/16": "aspect-[9/16]",
336
+ auto: ""
337
+ },
338
+ rounded: {
339
+ none: "",
340
+ sm: "rounded-4",
341
+ md: "rounded-8",
342
+ lg: "rounded-12"
343
+ }
344
+ },
345
+ defaultVariants: {
346
+ aspectRatio: "16/9",
347
+ rounded: "none"
348
+ }
349
+ });
350
+ var mediaControllerVariants = tv({
351
+ base: [
352
+ "absolute inset-0",
353
+ "w-full h-full",
354
+ // Button styling - transparent base, hover shows background
355
+ "[--media-control-background:transparent]",
356
+ "[--media-control-hover-background:var(--color-video-player-button-bg-hover)]",
357
+ "[--media-control-padding:8px]",
358
+ "[--media-control-height:36px]",
359
+ "[--media-button-icon-width:20px]",
360
+ "[--media-button-icon-height:20px]",
361
+ // Progress bar / range styling
362
+ "[--media-range-track-background:var(--color-video-player-progress-bg)]",
363
+ "[--media-range-bar-color:var(--color-video-player-progress-fill)]",
364
+ "[--media-range-track-height:4px]",
365
+ "[--media-range-track-border-radius:2px]",
366
+ "[--media-range-thumb-background:var(--color-video-player-progress-fill)]",
367
+ "[--media-range-thumb-height:12px]",
368
+ "[--media-range-thumb-width:12px]",
369
+ "[--media-range-thumb-border-radius:50%]",
370
+ // Text/icon colors
371
+ "[--media-icon-color:var(--color-video-player-controls-text)]",
372
+ "[--media-primary-color:var(--color-video-player-controls-text)]",
373
+ "[--media-secondary-color:transparent]",
374
+ "[--media-text-color:var(--color-video-player-controls-text)]",
375
+ "[--media-font-size:14px]",
376
+ // Time display styling
377
+ "[--media-time-display-background:transparent]"
378
+ ]
379
+ });
380
+ var mediaButtonStyles = {
381
+ padding: "8px",
382
+ borderRadius: "50%",
383
+ // Tooltip styling - consistent across all buttons
384
+ "--media-tooltip-background": "var(--color-video-player-tooltip-bg)",
385
+ "--media-tooltip-arrow-display": "none",
386
+ "--media-tooltip-distance": "8px"
387
+ };
388
+ var timeRangeStyles = {
389
+ flex: 1,
390
+ background: "transparent",
391
+ // Preview tooltip styling - consistent with button tooltips
392
+ "--media-box-arrow-display": "none",
393
+ "--media-preview-box-margin": "0 0 8px 0",
394
+ "--media-preview-time-margin": "0 0 8px 0",
395
+ "--media-preview-time-background": "var(--color-video-player-tooltip-bg)"
396
+ };
397
+ var volumeRangeStyles = {
398
+ width: "80px",
399
+ background: "transparent",
400
+ // Tooltip styling - consistent with button tooltips
401
+ "--media-tooltip-background": "var(--color-video-player-tooltip-bg)",
402
+ "--media-tooltip-arrow-display": "none",
403
+ "--media-tooltip-distance": "8px"
404
+ };
405
+ var timeDisplayStyles = {
406
+ background: "transparent",
407
+ fontFamily: "monospace",
408
+ fontSize: "14px",
409
+ color: "white",
410
+ whiteSpace: "nowrap"
411
+ };
412
+ var controlBarVariants = tv({
413
+ base: [
414
+ // Layout handled in inline styles, but we need flex
415
+ "flex items-center",
416
+ // Background using semantic token
417
+ "bg-video-player-controls-bg",
418
+ // Animation
419
+ "transition-all duration-300"
420
+ ],
421
+ variants: {
422
+ visible: {
423
+ true: "opacity-100 translate-y-0",
424
+ false: "opacity-0 translate-y-16 pointer-events-none"
425
+ }
426
+ },
427
+ defaultVariants: {
428
+ visible: true
429
+ }
430
+ });
431
+ var controlButtonVariants = tv({
432
+ base: [
433
+ "flex items-center justify-center",
434
+ "p-8 rounded-full",
435
+ // Transparent by default, background on hover
436
+ "bg-transparent",
437
+ "text-video-player-controls-text",
438
+ "hover:bg-video-player-button-bg-hover",
439
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-video-player-progress-bg",
440
+ "transition-colors duration-150",
441
+ "cursor-pointer"
442
+ ]
443
+ });
444
+ var loadingOverlayVariants = tv({
445
+ base: [
446
+ "absolute inset-0",
447
+ "flex items-center justify-center",
448
+ "bg-black/50",
449
+ "pointer-events-none",
450
+ "z-10"
451
+ ]
452
+ });
453
+ function useHlsInternal(videoRef, src, enabled) {
454
+ const [isLoading, setIsLoading] = React4.useState(true);
455
+ const [error, setError] = React4.useState(null);
456
+ const hlsRef = React4.useRef(null);
457
+ React4.useEffect(() => {
458
+ if (!src || !videoRef.current) {
459
+ return;
460
+ }
461
+ const video = videoRef.current;
462
+ const isHlsSource = src.includes(".m3u8");
463
+ if (!isHlsSource) {
464
+ video.src = src;
465
+ setIsLoading(false);
466
+ return;
467
+ }
468
+ if (video.canPlayType("application/vnd.apple.mpegurl")) {
469
+ video.src = src;
470
+ setIsLoading(false);
471
+ return;
472
+ }
473
+ const loadHls = async () => {
474
+ try {
475
+ const HlsModule = await import('hls.js');
476
+ const Hls = HlsModule.default;
477
+ if (!Hls.isSupported()) {
478
+ video.src = src;
479
+ setIsLoading(false);
480
+ return;
481
+ }
482
+ const hls = new Hls({
483
+ enableWorker: true,
484
+ lowLatencyMode: false
485
+ });
486
+ hls.loadSource(src);
487
+ hls.attachMedia(video);
488
+ hls.on(Hls.Events.MANIFEST_PARSED, () => {
489
+ setIsLoading(false);
490
+ });
491
+ hls.on(
492
+ Hls.Events.ERROR,
493
+ (_event, data) => {
494
+ if (data.fatal) {
495
+ setError(new Error(`HLS error: ${data.details}`));
496
+ setIsLoading(false);
497
+ }
498
+ }
499
+ );
500
+ hlsRef.current = hls;
501
+ } catch {
502
+ video.src = src;
503
+ setIsLoading(false);
504
+ }
505
+ };
506
+ loadHls();
507
+ return () => {
508
+ if (hlsRef.current) {
509
+ hlsRef.current.destroy();
510
+ hlsRef.current = null;
511
+ }
512
+ };
513
+ }, [enabled, src, videoRef]);
514
+ return { isLoading, error };
515
+ }
516
+ var VideoPlayer = React4.forwardRef(
517
+ ({
518
+ className,
519
+ src,
520
+ cloudflare,
521
+ poster,
522
+ captionsSrc,
523
+ autoPlay = false,
524
+ loop = false,
525
+ muted = false,
526
+ controls = true,
527
+ autoHideControls = true,
528
+ autoHideDelay = 3e3,
529
+ captionsEnabled: initialCaptionsEnabled = false,
530
+ aspectRatio,
531
+ rounded,
532
+ onPlay,
533
+ onPause,
534
+ onEnded,
535
+ onTimeUpdate,
536
+ onError,
537
+ videoRef: externalVideoRef,
538
+ ...props
539
+ }, ref) => {
540
+ const containerRef = React4.useRef(null);
541
+ const internalVideoRef = React4.useRef(null);
542
+ const controlsTimeoutRef = React4.useRef(null);
543
+ const [isPlaying, setIsPlaying] = React4.useState(false);
544
+ const [currentTime, setCurrentTime] = React4.useState(0);
545
+ const [controlsVisible, setControlsVisible] = React4.useState(true);
546
+ const [captionsEnabled, setCaptionsEnabled] = React4.useState(
547
+ initialCaptionsEnabled
548
+ );
549
+ const [isFullscreen, setIsFullscreen] = React4.useState(false);
550
+ const videoSrc = React4.useMemo(() => {
551
+ if (cloudflare) {
552
+ return `https://customer-${cloudflare.customerCode}.cloudflarestream.com/${cloudflare.videoId}/manifest/video.m3u8`;
553
+ }
554
+ return src;
555
+ }, [cloudflare, src]);
556
+ const { isLoading, error: hlsError } = useHlsInternal(
557
+ internalVideoRef,
558
+ videoSrc,
559
+ true
560
+ );
561
+ const { activeCue } = useCaptions({
562
+ src: captionsSrc,
563
+ currentTime
564
+ });
565
+ React4.useEffect(() => {
566
+ if (externalVideoRef) {
567
+ externalVideoRef.current = internalVideoRef.current;
568
+ }
569
+ }, [externalVideoRef]);
570
+ React4.useImperativeHandle(
571
+ ref,
572
+ () => containerRef.current
573
+ );
574
+ React4.useEffect(() => {
575
+ if (hlsError && onError) {
576
+ onError(hlsError);
577
+ }
578
+ }, [hlsError, onError]);
579
+ React4.useEffect(() => {
580
+ const video = internalVideoRef.current;
581
+ if (!video) return;
582
+ const handlePlay = () => {
583
+ setIsPlaying(true);
584
+ onPlay?.();
585
+ };
586
+ const handlePause = () => {
587
+ setIsPlaying(false);
588
+ onPause?.();
589
+ };
590
+ const handleEnded = () => {
591
+ setIsPlaying(false);
592
+ onEnded?.();
593
+ };
594
+ const handleTimeUpdate = () => {
595
+ setCurrentTime(video.currentTime);
596
+ onTimeUpdate?.(video.currentTime);
597
+ };
598
+ const handleCanPlay = () => {
599
+ if (autoPlay) {
600
+ video.play().catch(() => {
601
+ });
602
+ }
603
+ };
604
+ video.addEventListener("play", handlePlay);
605
+ video.addEventListener("pause", handlePause);
606
+ video.addEventListener("ended", handleEnded);
607
+ video.addEventListener("timeupdate", handleTimeUpdate);
608
+ video.addEventListener("canplay", handleCanPlay);
609
+ return () => {
610
+ video.removeEventListener("play", handlePlay);
611
+ video.removeEventListener("pause", handlePause);
612
+ video.removeEventListener("ended", handleEnded);
613
+ video.removeEventListener("timeupdate", handleTimeUpdate);
614
+ video.removeEventListener("canplay", handleCanPlay);
615
+ };
616
+ }, [autoPlay, onPlay, onPause, onEnded, onTimeUpdate]);
617
+ React4.useEffect(() => {
618
+ if (!autoHideControls || !isPlaying || !controlsVisible) return;
619
+ controlsTimeoutRef.current = setTimeout(() => {
620
+ setControlsVisible(false);
621
+ }, autoHideDelay);
622
+ return () => {
623
+ if (controlsTimeoutRef.current) {
624
+ clearTimeout(controlsTimeoutRef.current);
625
+ }
626
+ };
627
+ }, [autoHideControls, isPlaying, controlsVisible, autoHideDelay]);
628
+ React4.useEffect(() => {
629
+ const handleFullscreenChange = () => {
630
+ setIsFullscreen(!!document.fullscreenElement);
631
+ };
632
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
633
+ document.addEventListener(
634
+ "webkitfullscreenchange",
635
+ handleFullscreenChange
636
+ );
637
+ return () => {
638
+ document.removeEventListener(
639
+ "fullscreenchange",
640
+ handleFullscreenChange
641
+ );
642
+ document.removeEventListener(
643
+ "webkitfullscreenchange",
644
+ handleFullscreenChange
645
+ );
646
+ };
647
+ }, []);
648
+ const togglePlay = React4.useCallback(() => {
649
+ const video = internalVideoRef.current;
650
+ if (!video) return;
651
+ if (video.paused) {
652
+ video.play().catch(() => {
653
+ });
654
+ } else {
655
+ video.pause();
656
+ }
657
+ }, []);
658
+ const toggleCaptions = React4.useCallback(() => {
659
+ setCaptionsEnabled((prev) => !prev);
660
+ }, []);
661
+ const toggleFullscreen = React4.useCallback(() => {
662
+ if (!document.fullscreenElement) {
663
+ containerRef.current?.requestFullscreen();
664
+ } else {
665
+ document.exitFullscreen();
666
+ }
667
+ }, []);
668
+ const showControls = React4.useCallback(() => {
669
+ setControlsVisible(true);
670
+ if (controlsTimeoutRef.current) {
671
+ clearTimeout(controlsTimeoutRef.current);
672
+ }
673
+ }, []);
674
+ const handleMouseLeave = React4.useCallback(() => {
675
+ if (autoHideControls && isPlaying) {
676
+ setControlsVisible(false);
677
+ }
678
+ }, [autoHideControls, isPlaying]);
679
+ const { containerProps: keyboardProps } = useVideoKeyboard({
680
+ videoRef: internalVideoRef,
681
+ onTogglePlay: togglePlay,
682
+ onToggleFullscreen: toggleFullscreen,
683
+ onToggleCaptions: captionsSrc ? toggleCaptions : void 0,
684
+ onShowControls: showControls
685
+ });
686
+ return (
687
+ // biome-ignore lint/a11y/noStaticElementInteractions: role is applied via keyboardProps spread
688
+ /* @__PURE__ */ jsxs(
689
+ "div",
690
+ {
691
+ ref: containerRef,
692
+ className: cn(videoPlayerVariants({ aspectRatio, rounded }), className),
693
+ onMouseMove: showControls,
694
+ onMouseLeave: handleMouseLeave,
695
+ ...keyboardProps,
696
+ ...props,
697
+ children: [
698
+ controls ? /* @__PURE__ */ jsxs(
699
+ MediaController,
700
+ {
701
+ noAutohide: true,
702
+ className: mediaControllerVariants(),
703
+ children: [
704
+ /* @__PURE__ */ jsx(
705
+ "video",
706
+ {
707
+ ref: internalVideoRef,
708
+ slot: "media",
709
+ poster,
710
+ loop,
711
+ muted,
712
+ playsInline: true,
713
+ crossOrigin: "anonymous",
714
+ className: "w-full h-full object-contain"
715
+ }
716
+ ),
717
+ /* @__PURE__ */ jsx(MediaLoadingIndicator, { slot: "centered-chrome", noAutohide: true }),
718
+ /* @__PURE__ */ jsx(
719
+ "div",
720
+ {
721
+ onClick: togglePlay,
722
+ className: "absolute inset-0 cursor-pointer z-[1]",
723
+ "aria-hidden": "true"
724
+ }
725
+ ),
726
+ /* @__PURE__ */ jsxs(
727
+ MediaControlBar,
728
+ {
729
+ className: controlBarVariants({ visible: controlsVisible }),
730
+ onClick: (e) => e.stopPropagation(),
731
+ style: {
732
+ position: "absolute",
733
+ left: "24px",
734
+ right: "24px",
735
+ bottom: "24px",
736
+ gap: "12px",
737
+ padding: "8px 16px",
738
+ borderRadius: "9999px",
739
+ backdropFilter: "blur(10px)",
740
+ WebkitBackdropFilter: "blur(10px)",
741
+ zIndex: 2
742
+ },
743
+ children: [
744
+ /* @__PURE__ */ jsx(MediaPlayButton, { style: mediaButtonStyles }),
745
+ /* @__PURE__ */ jsx(MediaMuteButton, { style: mediaButtonStyles }),
746
+ /* @__PURE__ */ jsx(MediaVolumeRange, { style: volumeRangeStyles }),
747
+ /* @__PURE__ */ jsx(
748
+ MediaTimeDisplay,
749
+ {
750
+ style: timeDisplayStyles,
751
+ showDuration: true,
752
+ noToggle: true
753
+ }
754
+ ),
755
+ /* @__PURE__ */ jsx(MediaTimeRange, { style: timeRangeStyles }),
756
+ captionsSrc && /* @__PURE__ */ jsx(
757
+ "button",
758
+ {
759
+ type: "button",
760
+ className: controlButtonVariants(),
761
+ onClick: (e) => {
762
+ e.stopPropagation();
763
+ toggleCaptions();
764
+ },
765
+ "aria-label": captionsEnabled ? "Disable captions" : "Enable captions",
766
+ "aria-pressed": captionsEnabled,
767
+ children: /* @__PURE__ */ jsx(CaptionsIcon, { enabled: captionsEnabled })
768
+ }
769
+ ),
770
+ /* @__PURE__ */ jsx(
771
+ "button",
772
+ {
773
+ type: "button",
774
+ className: controlButtonVariants(),
775
+ onClick: (e) => {
776
+ e.stopPropagation();
777
+ toggleFullscreen();
778
+ },
779
+ "aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
780
+ children: /* @__PURE__ */ jsx(FullscreenIcon, { isFullscreen })
781
+ }
782
+ )
783
+ ]
784
+ }
785
+ )
786
+ ]
787
+ }
788
+ ) : (
789
+ /* Video without controls */
790
+ /* @__PURE__ */ jsx(
791
+ "video",
792
+ {
793
+ ref: internalVideoRef,
794
+ poster,
795
+ loop,
796
+ muted,
797
+ playsInline: true,
798
+ crossOrigin: "anonymous",
799
+ className: "w-full h-full object-contain",
800
+ onClick: togglePlay
801
+ }
802
+ )
803
+ ),
804
+ isLoading && /* @__PURE__ */ jsx("div", { className: loadingOverlayVariants(), children: /* @__PURE__ */ jsx("div", { className: "w-40 h-40 border-3 border-white/30 border-t-white rounded-full animate-spin" }) }),
805
+ hlsError && /* @__PURE__ */ jsx("div", { className: loadingOverlayVariants(), children: /* @__PURE__ */ jsxs("div", { className: "text-white text-center px-16", children: [
806
+ /* @__PURE__ */ jsx("p", { className: "typography-body-sm-sm", children: "Failed to load video" }),
807
+ /* @__PURE__ */ jsx("p", { className: "typography-caption text-white/60 mt-4", children: hlsError.message })
808
+ ] }) }),
809
+ captionsEnabled && activeCue && /* @__PURE__ */ jsx(CaptionOverlay, { cue: activeCue })
810
+ ]
811
+ }
812
+ )
813
+ );
814
+ }
815
+ );
816
+ VideoPlayer.displayName = "VideoPlayer";
817
+ var CaptionsIcon = ({ enabled }) => /* @__PURE__ */ jsx(
818
+ "svg",
819
+ {
820
+ className: "w-20 h-20",
821
+ viewBox: "0 0 24 24",
822
+ fill: "currentColor",
823
+ "aria-hidden": "true",
824
+ children: enabled ? (
825
+ // Captions On
826
+ /* @__PURE__ */ jsx("path", { d: "M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z" })
827
+ ) : (
828
+ // Captions Off (with strike-through)
829
+ /* @__PURE__ */ jsxs(Fragment, { children: [
830
+ /* @__PURE__ */ jsx("path", { d: "M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z" }),
831
+ /* @__PURE__ */ jsx(
832
+ "line",
833
+ {
834
+ x1: "4",
835
+ y1: "20",
836
+ x2: "20",
837
+ y2: "4",
838
+ stroke: "currentColor",
839
+ strokeWidth: "2"
840
+ }
841
+ )
842
+ ] })
843
+ )
844
+ }
845
+ );
846
+ var FullscreenIcon = ({ isFullscreen }) => /* @__PURE__ */ jsx(
847
+ "svg",
848
+ {
849
+ className: "w-20 h-20",
850
+ viewBox: "0 0 24 24",
851
+ fill: "none",
852
+ stroke: "currentColor",
853
+ strokeWidth: "2",
854
+ strokeLinecap: "round",
855
+ strokeLinejoin: "round",
856
+ "aria-hidden": "true",
857
+ children: isFullscreen ? (
858
+ // Minimize (exit fullscreen)
859
+ /* @__PURE__ */ jsxs(Fragment, { children: [
860
+ /* @__PURE__ */ jsx("polyline", { points: "4 14 10 14 10 20" }),
861
+ /* @__PURE__ */ jsx("polyline", { points: "20 10 14 10 14 4" }),
862
+ /* @__PURE__ */ jsx("line", { x1: "14", y1: "10", x2: "21", y2: "3" }),
863
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
864
+ ] })
865
+ ) : (
866
+ // Maximize (enter fullscreen)
867
+ /* @__PURE__ */ jsxs(Fragment, { children: [
868
+ /* @__PURE__ */ jsx("polyline", { points: "15 3 21 3 21 9" }),
869
+ /* @__PURE__ */ jsx("polyline", { points: "9 21 3 21 3 15" }),
870
+ /* @__PURE__ */ jsx("line", { x1: "21", y1: "3", x2: "14", y2: "10" }),
871
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
872
+ ] })
873
+ )
874
+ }
875
+ );
876
+
877
+ export { CaptionOverlay, VideoPlayer, captionOverlayVariants, controlBarVariants, controlButtonVariants, loadingOverlayVariants, mediaControllerVariants, videoPlayerVariants };
878
+ //# sourceMappingURL=index.js.map
879
+ //# sourceMappingURL=index.js.map