@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,150 @@
1
+ export { C as CaptionCue, u as useCaptions } from '../use-captions-AkKlJhov.js';
2
+ import * as React from 'react';
3
+
4
+ /**
5
+ * Design system breakpoint values in pixels.
6
+ * These match the primitive breakpoint tokens from packages/tokens.
7
+ */
8
+ declare const BREAKPOINTS: {
9
+ readonly sm: 320;
10
+ readonly md: 768;
11
+ readonly lg: 1440;
12
+ };
13
+ type Breakpoint = keyof typeof BREAKPOINTS;
14
+ /**
15
+ * Hook to get the current responsive breakpoint.
16
+ * Returns the active breakpoint name based on viewport width.
17
+ *
18
+ * @returns The current breakpoint: 'sm' | 'md' | 'lg'
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * function Component() {
23
+ * const breakpoint = useBreakpoint();
24
+ *
25
+ * return (
26
+ * <div>
27
+ * {breakpoint === 'sm' && <MobileLayout />}
28
+ * {breakpoint === 'md' && <TabletLayout />}
29
+ * {breakpoint === 'lg' && <DesktopLayout />}
30
+ * </div>
31
+ * );
32
+ * }
33
+ * ```
34
+ */
35
+ declare function useBreakpoint(): Breakpoint;
36
+ /**
37
+ * Hook to check if viewport is at or above a specific breakpoint.
38
+ *
39
+ * @param breakpoint - The minimum breakpoint to check against
40
+ * @returns boolean indicating if viewport is at or above the breakpoint
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * function Component() {
45
+ * const isDesktop = useMinBreakpoint('lg');
46
+ * const isTabletUp = useMinBreakpoint('md');
47
+ *
48
+ * return isDesktop ? <DesktopView /> : <MobileView />;
49
+ * }
50
+ * ```
51
+ */
52
+ declare function useMinBreakpoint(breakpoint: Breakpoint): boolean;
53
+ /**
54
+ * Hook to check if viewport is below a specific breakpoint.
55
+ *
56
+ * @param breakpoint - The breakpoint to check against
57
+ * @returns boolean indicating if viewport is below the breakpoint
58
+ *
59
+ * @example
60
+ * ```tsx
61
+ * function Component() {
62
+ * const isMobile = useMaxBreakpoint('md'); // Below tablet
63
+ *
64
+ * return isMobile ? <MobileNav /> : <DesktopNav />;
65
+ * }
66
+ * ```
67
+ */
68
+ declare function useMaxBreakpoint(breakpoint: Breakpoint): boolean;
69
+
70
+ type EventMap<T> = T extends Window ? WindowEventMap : T extends Document ? DocumentEventMap : T extends HTMLElement ? HTMLElementEventMap : never;
71
+ /**
72
+ * Custom hook that attaches an event listener to a specified element.
73
+ * Automatically handles cleanup on unmount and when dependencies change.
74
+ *
75
+ * @param eventName - The name of the event to listen for
76
+ * @param handler - The callback function to execute when the event fires
77
+ * @param element - The element to attach the listener to (defaults to window)
78
+ * @param options - Optional event listener options
79
+ *
80
+ * @example
81
+ * ```tsx
82
+ * // Listen to window resize
83
+ * useEventListener('resize', handleResize);
84
+ *
85
+ * // Listen to element scroll
86
+ * useEventListener('scroll', handleScroll, containerRef.current);
87
+ *
88
+ * // With options
89
+ * useEventListener('scroll', handleScroll, window, { passive: true });
90
+ * ```
91
+ */
92
+ declare function useEventListener<T extends Window | Document | HTMLElement, K extends keyof EventMap<T>>(eventName: K, handler: (event: EventMap<T>[K]) => void, element?: T | null, options?: boolean | AddEventListenerOptions): void;
93
+
94
+ interface UseVideoKeyboardOptions {
95
+ /** Ref to the video element */
96
+ videoRef: React.RefObject<HTMLVideoElement | null>;
97
+ /** Whether keyboard handling is enabled (default: true) */
98
+ enabled?: boolean;
99
+ /** Seek amount in seconds for arrow keys (default: 5) */
100
+ seekAmount?: number;
101
+ /** Volume change amount for arrow keys (default: 0.1) */
102
+ volumeStep?: number;
103
+ /** Callback when play/pause is toggled */
104
+ onTogglePlay?: () => void;
105
+ /** Callback when fullscreen is toggled */
106
+ onToggleFullscreen?: () => void;
107
+ /** Callback when captions are toggled */
108
+ onToggleCaptions?: () => void;
109
+ /** Callback when controls should be shown */
110
+ onShowControls?: () => void;
111
+ }
112
+ interface UseVideoKeyboardReturn {
113
+ /** Key down handler to attach to container element */
114
+ handleKeyDown: (e: React.KeyboardEvent) => void;
115
+ /** Props to spread on the container element */
116
+ containerProps: {
117
+ onKeyDown: (e: React.KeyboardEvent) => void;
118
+ tabIndex: number;
119
+ role: string;
120
+ "aria-label": string;
121
+ };
122
+ }
123
+ /**
124
+ * Hook for handling keyboard shortcuts in a video player.
125
+ *
126
+ * Supported shortcuts:
127
+ * - Space: Play/pause
128
+ * - Left Arrow: Seek backward
129
+ * - Right Arrow: Seek forward
130
+ * - Up Arrow: Volume up
131
+ * - Down Arrow: Volume down
132
+ * - M: Toggle mute
133
+ * - F: Toggle fullscreen
134
+ * - C: Toggle captions
135
+ *
136
+ * @example
137
+ * ```tsx
138
+ * const { containerProps } = useVideoKeyboard({
139
+ * videoRef,
140
+ * onTogglePlay: () => video.paused ? video.play() : video.pause(),
141
+ * onToggleFullscreen: () => toggleFullscreen(),
142
+ * onShowControls: () => setControlsVisible(true),
143
+ * });
144
+ *
145
+ * return <div {...containerProps}>...</div>;
146
+ * ```
147
+ */
148
+ declare function useVideoKeyboard({ videoRef, enabled, seekAmount, volumeStep, onTogglePlay, onToggleFullscreen, onToggleCaptions, onShowControls, }: UseVideoKeyboardOptions): UseVideoKeyboardReturn;
149
+
150
+ export { BREAKPOINTS, type Breakpoint, type UseVideoKeyboardOptions, type UseVideoKeyboardReturn, useBreakpoint, useEventListener, useMaxBreakpoint, useMinBreakpoint, useVideoKeyboard };
@@ -0,0 +1,339 @@
1
+ "use client";
2
+ import * as React2 from 'react';
3
+
4
+ // src/hooks/use-breakpoint.ts
5
+ var BREAKPOINTS = {
6
+ sm: 320,
7
+ md: 768,
8
+ lg: 1440
9
+ };
10
+ function getCurrentBreakpoint(width) {
11
+ if (width >= BREAKPOINTS.lg) return "lg";
12
+ if (width >= BREAKPOINTS.md) return "md";
13
+ return "sm";
14
+ }
15
+ function useBreakpoint() {
16
+ const [breakpoint, setBreakpoint] = React2.useState(() => {
17
+ if (typeof window === "undefined") return "sm";
18
+ return getCurrentBreakpoint(window.innerWidth);
19
+ });
20
+ React2.useEffect(() => {
21
+ const handleResize = () => {
22
+ setBreakpoint(getCurrentBreakpoint(window.innerWidth));
23
+ };
24
+ handleResize();
25
+ window.addEventListener("resize", handleResize);
26
+ return () => window.removeEventListener("resize", handleResize);
27
+ }, []);
28
+ return breakpoint;
29
+ }
30
+ function useMinBreakpoint(breakpoint) {
31
+ const [matches, setMatches] = React2.useState(() => {
32
+ if (typeof window === "undefined") return false;
33
+ return window.innerWidth >= BREAKPOINTS[breakpoint];
34
+ });
35
+ React2.useEffect(() => {
36
+ const mediaQuery = window.matchMedia(
37
+ `(min-width: ${BREAKPOINTS[breakpoint]}px)`
38
+ );
39
+ setMatches(mediaQuery.matches);
40
+ const handleChange = (event) => {
41
+ setMatches(event.matches);
42
+ };
43
+ mediaQuery.addEventListener("change", handleChange);
44
+ return () => mediaQuery.removeEventListener("change", handleChange);
45
+ }, [breakpoint]);
46
+ return matches;
47
+ }
48
+ function useMaxBreakpoint(breakpoint) {
49
+ const [matches, setMatches] = React2.useState(() => {
50
+ if (typeof window === "undefined") return false;
51
+ return window.innerWidth < BREAKPOINTS[breakpoint];
52
+ });
53
+ React2.useEffect(() => {
54
+ const mediaQuery = window.matchMedia(
55
+ `(max-width: ${BREAKPOINTS[breakpoint] - 1}px)`
56
+ );
57
+ setMatches(mediaQuery.matches);
58
+ const handleChange = (event) => {
59
+ setMatches(event.matches);
60
+ };
61
+ mediaQuery.addEventListener("change", handleChange);
62
+ return () => mediaQuery.removeEventListener("change", handleChange);
63
+ }, [breakpoint]);
64
+ return matches;
65
+ }
66
+ function parseVttTimestamp(timestamp) {
67
+ const parts = timestamp.trim().split(":");
68
+ let hours = 0;
69
+ let minutes = 0;
70
+ let seconds = 0;
71
+ if (parts.length === 3) {
72
+ hours = Number.parseInt(parts[0], 10);
73
+ minutes = Number.parseInt(parts[1], 10);
74
+ seconds = Number.parseFloat(parts[2]);
75
+ } else if (parts.length === 2) {
76
+ minutes = Number.parseInt(parts[0], 10);
77
+ seconds = Number.parseFloat(parts[1]);
78
+ }
79
+ return hours * 3600 + minutes * 60 + seconds;
80
+ }
81
+ function parseVtt(vttContent) {
82
+ const cues = [];
83
+ const lines = vttContent.trim().split("\n");
84
+ let i = 0;
85
+ while (i < lines.length && !lines[i].includes("-->")) {
86
+ i++;
87
+ }
88
+ while (i < lines.length) {
89
+ const line = lines[i].trim();
90
+ if (line.includes("-->")) {
91
+ const [startStr, endStr] = line.split("-->").map((s) => s.trim());
92
+ const endParts = endStr.split(" ");
93
+ const endTime = parseVttTimestamp(endParts[0]);
94
+ const startTime = parseVttTimestamp(startStr);
95
+ const textLines = [];
96
+ i++;
97
+ while (i < lines.length && lines[i].trim() !== "" && !lines[i].includes("-->")) {
98
+ if (!/^\d+$/.test(lines[i].trim())) {
99
+ textLines.push(lines[i].trim());
100
+ }
101
+ i++;
102
+ }
103
+ if (textLines.length > 0) {
104
+ cues.push({
105
+ id: `cue-${cues.length}`,
106
+ startTime,
107
+ endTime,
108
+ text: textLines.join("\n")
109
+ });
110
+ }
111
+ } else {
112
+ i++;
113
+ }
114
+ }
115
+ return cues;
116
+ }
117
+ function stripVttTags(text) {
118
+ return text.replace(/<\/?[^>]+(>|$)/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").trim();
119
+ }
120
+ function useCaptions(options = {}) {
121
+ const { src, content, stripTags = true, currentTime: externalTime } = options;
122
+ const [cues, setCues] = React2.useState([]);
123
+ const [internalTime, setInternalTime] = React2.useState(0);
124
+ const [isLoading, setIsLoading] = React2.useState(false);
125
+ const [error, setError] = React2.useState(null);
126
+ const currentTime = externalTime ?? internalTime;
127
+ React2.useEffect(() => {
128
+ if (content) {
129
+ const parsed = parseVtt(content);
130
+ setCues(
131
+ stripTags ? parsed.map((cue) => ({ ...cue, text: stripVttTags(cue.text) })) : parsed
132
+ );
133
+ return;
134
+ }
135
+ if (!src) {
136
+ setCues([]);
137
+ return;
138
+ }
139
+ setIsLoading(true);
140
+ setError(null);
141
+ fetch(src).then((response) => {
142
+ if (!response.ok) {
143
+ throw new Error(`Failed to fetch captions: ${response.status}`);
144
+ }
145
+ return response.text();
146
+ }).then((vttContent) => {
147
+ const parsed = parseVtt(vttContent);
148
+ setCues(
149
+ stripTags ? parsed.map((cue) => ({ ...cue, text: stripVttTags(cue.text) })) : parsed
150
+ );
151
+ }).catch((err) => {
152
+ setError(err instanceof Error ? err : new Error(String(err)));
153
+ }).finally(() => {
154
+ setIsLoading(false);
155
+ });
156
+ }, [src, content, stripTags]);
157
+ const activeCue = React2.useMemo(() => {
158
+ return cues.find(
159
+ (cue) => currentTime >= cue.startTime && currentTime <= cue.endTime
160
+ ) ?? null;
161
+ }, [cues, currentTime]);
162
+ const handleSetCurrentTime = React2.useCallback((time) => {
163
+ setInternalTime(time);
164
+ }, []);
165
+ return {
166
+ cues,
167
+ activeCue,
168
+ setCurrentTime: handleSetCurrentTime,
169
+ isLoading,
170
+ error
171
+ };
172
+ }
173
+ function useEventListener(eventName, handler, element, options) {
174
+ const savedHandler = React2.useRef(handler);
175
+ React2.useLayoutEffect(() => {
176
+ savedHandler.current = handler;
177
+ }, [handler]);
178
+ React2.useEffect(() => {
179
+ const targetElement = element ?? window;
180
+ if (!targetElement?.addEventListener) {
181
+ return;
182
+ }
183
+ const eventListener = (event) => {
184
+ savedHandler.current(event);
185
+ };
186
+ targetElement.addEventListener(eventName, eventListener, options);
187
+ return () => {
188
+ targetElement.removeEventListener(
189
+ eventName,
190
+ eventListener,
191
+ options
192
+ );
193
+ };
194
+ }, [eventName, element, options]);
195
+ }
196
+ function useVideoKeyboard({
197
+ videoRef,
198
+ enabled = true,
199
+ seekAmount = 5,
200
+ volumeStep = 0.1,
201
+ onTogglePlay,
202
+ onToggleFullscreen,
203
+ onToggleCaptions,
204
+ onShowControls
205
+ }) {
206
+ const handleKeyDown = React2.useCallback(
207
+ (e) => {
208
+ if (!enabled) return;
209
+ const video = videoRef.current;
210
+ if (!video) return;
211
+ const target = e.target;
212
+ if (target.tagName === "BUTTON" || target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.closest("button") || target.closest("input") || target.closest("[role='slider']")) {
213
+ return;
214
+ }
215
+ let handled = false;
216
+ switch (e.key) {
217
+ // Play/Pause
218
+ case " ":
219
+ case "Spacebar":
220
+ case "k":
221
+ if (onTogglePlay) {
222
+ onTogglePlay();
223
+ } else {
224
+ if (video.paused) {
225
+ video.play().catch(() => {
226
+ });
227
+ } else {
228
+ video.pause();
229
+ }
230
+ }
231
+ handled = true;
232
+ break;
233
+ // Seek backward
234
+ case "ArrowLeft":
235
+ case "j":
236
+ video.currentTime = Math.max(0, video.currentTime - seekAmount);
237
+ handled = true;
238
+ break;
239
+ // Seek forward
240
+ case "ArrowRight":
241
+ case "l":
242
+ video.currentTime = Math.min(
243
+ video.duration || 0,
244
+ video.currentTime + seekAmount
245
+ );
246
+ handled = true;
247
+ break;
248
+ // Volume up
249
+ case "ArrowUp":
250
+ video.volume = Math.min(1, video.volume + volumeStep);
251
+ video.muted = false;
252
+ handled = true;
253
+ break;
254
+ // Volume down
255
+ case "ArrowDown":
256
+ video.volume = Math.max(0, video.volume - volumeStep);
257
+ handled = true;
258
+ break;
259
+ // Toggle mute
260
+ case "m":
261
+ case "M":
262
+ video.muted = !video.muted;
263
+ handled = true;
264
+ break;
265
+ // Toggle fullscreen
266
+ case "f":
267
+ case "F":
268
+ onToggleFullscreen?.();
269
+ handled = true;
270
+ break;
271
+ // Toggle captions
272
+ case "c":
273
+ case "C":
274
+ onToggleCaptions?.();
275
+ handled = true;
276
+ break;
277
+ // Jump to start
278
+ case "Home":
279
+ video.currentTime = 0;
280
+ handled = true;
281
+ break;
282
+ // Jump to end
283
+ case "End":
284
+ video.currentTime = video.duration || 0;
285
+ handled = true;
286
+ break;
287
+ // Number keys for percentage seeking (0-9)
288
+ case "0":
289
+ case "1":
290
+ case "2":
291
+ case "3":
292
+ case "4":
293
+ case "5":
294
+ case "6":
295
+ case "7":
296
+ case "8":
297
+ case "9":
298
+ if (video.duration) {
299
+ const percent = parseInt(e.key, 10) / 10;
300
+ video.currentTime = video.duration * percent;
301
+ handled = true;
302
+ }
303
+ break;
304
+ }
305
+ if (handled) {
306
+ e.preventDefault();
307
+ e.stopPropagation();
308
+ onShowControls?.();
309
+ }
310
+ },
311
+ [
312
+ enabled,
313
+ videoRef,
314
+ seekAmount,
315
+ volumeStep,
316
+ onTogglePlay,
317
+ onToggleFullscreen,
318
+ onToggleCaptions,
319
+ onShowControls
320
+ ]
321
+ );
322
+ const containerProps = React2.useMemo(
323
+ () => ({
324
+ onKeyDown: handleKeyDown,
325
+ tabIndex: 0,
326
+ role: "application",
327
+ "aria-label": "Video player, press space to play or pause"
328
+ }),
329
+ [handleKeyDown]
330
+ );
331
+ return {
332
+ handleKeyDown,
333
+ containerProps
334
+ };
335
+ }
336
+
337
+ export { BREAKPOINTS, useBreakpoint, useCaptions, useEventListener, useMaxBreakpoint, useMinBreakpoint, useVideoKeyboard };
338
+ //# sourceMappingURL=index.js.map
339
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/use-breakpoint.ts","../../src/hooks/use-captions.ts","../../src/hooks/use-event-listener.ts","../../src/hooks/use-video-keyboard.ts"],"names":["React","React3","React4"],"mappings":"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","file":"index.js","sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * Design system breakpoint values in pixels.\n * These match the primitive breakpoint tokens from packages/tokens.\n */\nexport const BREAKPOINTS = {\n\tsm: 320,\n\tmd: 768,\n\tlg: 1440,\n} as const;\n\nexport type Breakpoint = keyof typeof BREAKPOINTS;\n\n/**\n * Get the current breakpoint based on viewport width.\n */\nfunction getCurrentBreakpoint(width: number): Breakpoint {\n\tif (width >= BREAKPOINTS.lg) return \"lg\";\n\tif (width >= BREAKPOINTS.md) return \"md\";\n\treturn \"sm\";\n}\n\n/**\n * Hook to get the current responsive breakpoint.\n * Returns the active breakpoint name based on viewport width.\n *\n * @returns The current breakpoint: 'sm' | 'md' | 'lg'\n *\n * @example\n * ```tsx\n * function Component() {\n * const breakpoint = useBreakpoint();\n *\n * return (\n * <div>\n * {breakpoint === 'sm' && <MobileLayout />}\n * {breakpoint === 'md' && <TabletLayout />}\n * {breakpoint === 'lg' && <DesktopLayout />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useBreakpoint(): Breakpoint {\n\tconst [breakpoint, setBreakpoint] = React.useState<Breakpoint>(() => {\n\t\tif (typeof window === \"undefined\") return \"sm\";\n\t\treturn getCurrentBreakpoint(window.innerWidth);\n\t});\n\n\tReact.useEffect(() => {\n\t\tconst handleResize = () => {\n\t\t\tsetBreakpoint(getCurrentBreakpoint(window.innerWidth));\n\t\t};\n\n\t\t// Set initial value\n\t\thandleResize();\n\n\t\twindow.addEventListener(\"resize\", handleResize);\n\t\treturn () => window.removeEventListener(\"resize\", handleResize);\n\t}, []);\n\n\treturn breakpoint;\n}\n\n/**\n * Hook to check if viewport is at or above a specific breakpoint.\n *\n * @param breakpoint - The minimum breakpoint to check against\n * @returns boolean indicating if viewport is at or above the breakpoint\n *\n * @example\n * ```tsx\n * function Component() {\n * const isDesktop = useMinBreakpoint('lg');\n * const isTabletUp = useMinBreakpoint('md');\n *\n * return isDesktop ? <DesktopView /> : <MobileView />;\n * }\n * ```\n */\nexport function useMinBreakpoint(breakpoint: Breakpoint): boolean {\n\tconst [matches, setMatches] = React.useState<boolean>(() => {\n\t\tif (typeof window === \"undefined\") return false;\n\t\treturn window.innerWidth >= BREAKPOINTS[breakpoint];\n\t});\n\n\tReact.useEffect(() => {\n\t\tconst mediaQuery = window.matchMedia(\n\t\t\t`(min-width: ${BREAKPOINTS[breakpoint]}px)`,\n\t\t);\n\n\t\tsetMatches(mediaQuery.matches);\n\n\t\tconst handleChange = (event: MediaQueryListEvent) => {\n\t\t\tsetMatches(event.matches);\n\t\t};\n\n\t\tmediaQuery.addEventListener(\"change\", handleChange);\n\t\treturn () => mediaQuery.removeEventListener(\"change\", handleChange);\n\t}, [breakpoint]);\n\n\treturn matches;\n}\n\n/**\n * Hook to check if viewport is below a specific breakpoint.\n *\n * @param breakpoint - The breakpoint to check against\n * @returns boolean indicating if viewport is below the breakpoint\n *\n * @example\n * ```tsx\n * function Component() {\n * const isMobile = useMaxBreakpoint('md'); // Below tablet\n *\n * return isMobile ? <MobileNav /> : <DesktopNav />;\n * }\n * ```\n */\nexport function useMaxBreakpoint(breakpoint: Breakpoint): boolean {\n\tconst [matches, setMatches] = React.useState<boolean>(() => {\n\t\tif (typeof window === \"undefined\") return false;\n\t\treturn window.innerWidth < BREAKPOINTS[breakpoint];\n\t});\n\n\tReact.useEffect(() => {\n\t\tconst mediaQuery = window.matchMedia(\n\t\t\t`(max-width: ${BREAKPOINTS[breakpoint] - 1}px)`,\n\t\t);\n\n\t\tsetMatches(mediaQuery.matches);\n\n\t\tconst handleChange = (event: MediaQueryListEvent) => {\n\t\t\tsetMatches(event.matches);\n\t\t};\n\n\t\tmediaQuery.addEventListener(\"change\", handleChange);\n\t\treturn () => mediaQuery.removeEventListener(\"change\", handleChange);\n\t}, [breakpoint]);\n\n\treturn matches;\n}\n","\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * Represents a single caption cue parsed from VTT.\n */\nexport interface CaptionCue {\n\t/** Unique identifier for the cue */\n\tid: string;\n\t/** Start time in seconds */\n\tstartTime: number;\n\t/** End time in seconds */\n\tendTime: number;\n\t/** Caption text content */\n\ttext: string;\n}\n\n/**\n * Parse VTT timestamp to seconds.\n * Handles formats: HH:MM:SS.mmm or MM:SS.mmm\n */\nfunction parseVttTimestamp(timestamp: string): number {\n\tconst parts = timestamp.trim().split(\":\");\n\tlet hours = 0;\n\tlet minutes = 0;\n\tlet seconds = 0;\n\n\tif (parts.length === 3) {\n\t\thours = Number.parseInt(parts[0], 10);\n\t\tminutes = Number.parseInt(parts[1], 10);\n\t\tseconds = Number.parseFloat(parts[2]);\n\t} else if (parts.length === 2) {\n\t\tminutes = Number.parseInt(parts[0], 10);\n\t\tseconds = Number.parseFloat(parts[1]);\n\t}\n\n\treturn hours * 3600 + minutes * 60 + seconds;\n}\n\n/**\n * Parse VTT content string into an array of caption cues.\n */\nfunction parseVtt(vttContent: string): CaptionCue[] {\n\tconst cues: CaptionCue[] = [];\n\tconst lines = vttContent.trim().split(\"\\n\");\n\n\tlet i = 0;\n\n\t// Skip WEBVTT header and any metadata\n\twhile (i < lines.length && !lines[i].includes(\"-->\")) {\n\t\ti++;\n\t}\n\n\twhile (i < lines.length) {\n\t\tconst line = lines[i].trim();\n\n\t\t// Look for timestamp line (contains -->)\n\t\tif (line.includes(\"-->\")) {\n\t\t\tconst [startStr, endStr] = line.split(\"-->\").map((s) => s.trim());\n\n\t\t\t// Handle optional cue settings after timestamp\n\t\t\tconst endParts = endStr.split(\" \");\n\t\t\tconst endTime = parseVttTimestamp(endParts[0]);\n\t\t\tconst startTime = parseVttTimestamp(startStr);\n\n\t\t\t// Collect text lines until empty line or next timestamp\n\t\t\tconst textLines: string[] = [];\n\t\t\ti++;\n\t\t\twhile (\n\t\t\t\ti < lines.length &&\n\t\t\t\tlines[i].trim() !== \"\" &&\n\t\t\t\t!lines[i].includes(\"-->\")\n\t\t\t) {\n\t\t\t\t// Skip cue identifier lines (numbers only)\n\t\t\t\tif (!/^\\d+$/.test(lines[i].trim())) {\n\t\t\t\t\ttextLines.push(lines[i].trim());\n\t\t\t\t}\n\t\t\t\ti++;\n\t\t\t}\n\n\t\t\tif (textLines.length > 0) {\n\t\t\t\tcues.push({\n\t\t\t\t\tid: `cue-${cues.length}`,\n\t\t\t\t\tstartTime,\n\t\t\t\t\tendTime,\n\t\t\t\t\ttext: textLines.join(\"\\n\"),\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\ti++;\n\t\t}\n\t}\n\n\treturn cues;\n}\n\n/**\n * Strip VTT formatting tags from text.\n * Removes <v>, <c>, <i>, <b>, <u>, etc.\n */\nfunction stripVttTags(text: string): string {\n\treturn text\n\t\t.replace(/<\\/?[^>]+(>|$)/g, \"\") // Remove HTML-like tags\n\t\t.replace(/&nbsp;/g, \" \")\n\t\t.replace(/&amp;/g, \"&\")\n\t\t.replace(/&lt;/g, \"<\")\n\t\t.replace(/&gt;/g, \">\")\n\t\t.trim();\n}\n\ninterface UseCaptionsOptions {\n\t/** VTT file URL to fetch */\n\tsrc?: string;\n\t/** Pre-loaded VTT content string */\n\tcontent?: string;\n\t/** Strip VTT formatting tags from caption text */\n\tstripTags?: boolean;\n\t/** Current playback time in seconds (alternative to setCurrentTime) */\n\tcurrentTime?: number;\n}\n\ninterface UseCaptionsReturn {\n\t/** All parsed caption cues */\n\tcues: CaptionCue[];\n\t/** Currently active cue based on current time */\n\tactiveCue: CaptionCue | null;\n\t/** Update the current playback time to get active cue */\n\tsetCurrentTime: (time: number) => void;\n\t/** Loading state */\n\tisLoading: boolean;\n\t/** Error state */\n\terror: Error | null;\n}\n\n/**\n * Hook for parsing VTT captions and tracking the active cue.\n *\n * @param options - Caption source options\n * @returns Parsed cues, active cue, and state\n *\n * @example\n * ```tsx\n * function VideoPlayer() {\n * const videoRef = React.useRef<HTMLVideoElement>(null);\n * const { activeCue, setCurrentTime } = useCaptions({\n * src: '/captions.vtt',\n * });\n *\n * // Sync with video time\n * React.useEffect(() => {\n * const video = videoRef.current;\n * if (!video) return;\n *\n * const handleTimeUpdate = () => setCurrentTime(video.currentTime);\n * video.addEventListener('timeupdate', handleTimeUpdate);\n * return () => video.removeEventListener('timeupdate', handleTimeUpdate);\n * }, [setCurrentTime]);\n *\n * return (\n * <div>\n * <video ref={videoRef} src=\"/video.mp4\" />\n * {activeCue && <div className=\"caption\">{activeCue.text}</div>}\n * </div>\n * );\n * }\n * ```\n */\nexport function useCaptions(\n\toptions: UseCaptionsOptions = {},\n): UseCaptionsReturn {\n\tconst { src, content, stripTags = true, currentTime: externalTime } = options;\n\n\tconst [cues, setCues] = React.useState<CaptionCue[]>([]);\n\tconst [internalTime, setInternalTime] = React.useState(0);\n\tconst [isLoading, setIsLoading] = React.useState(false);\n\tconst [error, setError] = React.useState<Error | null>(null);\n\n\t// Use external time if provided, otherwise use internal state\n\tconst currentTime = externalTime ?? internalTime;\n\n\t// Parse content or fetch from URL\n\tReact.useEffect(() => {\n\t\tif (content) {\n\t\t\t// Use provided content directly\n\t\t\tconst parsed = parseVtt(content);\n\t\t\tsetCues(\n\t\t\t\tstripTags\n\t\t\t\t\t? parsed.map((cue) => ({ ...cue, text: stripVttTags(cue.text) }))\n\t\t\t\t\t: parsed,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!src) {\n\t\t\tsetCues([]);\n\t\t\treturn;\n\t\t}\n\n\t\tsetIsLoading(true);\n\t\tsetError(null);\n\n\t\tfetch(src)\n\t\t\t.then((response) => {\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthrow new Error(`Failed to fetch captions: ${response.status}`);\n\t\t\t\t}\n\t\t\t\treturn response.text();\n\t\t\t})\n\t\t\t.then((vttContent) => {\n\t\t\t\tconst parsed = parseVtt(vttContent);\n\t\t\t\tsetCues(\n\t\t\t\t\tstripTags\n\t\t\t\t\t\t? parsed.map((cue) => ({ ...cue, text: stripVttTags(cue.text) }))\n\t\t\t\t\t\t: parsed,\n\t\t\t\t);\n\t\t\t})\n\t\t\t.catch((err) => {\n\t\t\t\tsetError(err instanceof Error ? err : new Error(String(err)));\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tsetIsLoading(false);\n\t\t\t});\n\t}, [src, content, stripTags]);\n\n\t// Find active cue for current time\n\tconst activeCue = React.useMemo(() => {\n\t\treturn (\n\t\t\tcues.find(\n\t\t\t\t(cue) => currentTime >= cue.startTime && currentTime <= cue.endTime,\n\t\t\t) ?? null\n\t\t);\n\t}, [cues, currentTime]);\n\n\t// Memoize setCurrentTime to avoid unnecessary re-renders\n\tconst handleSetCurrentTime = React.useCallback((time: number) => {\n\t\tsetInternalTime(time);\n\t}, []);\n\n\treturn {\n\t\tcues,\n\t\tactiveCue,\n\t\tsetCurrentTime: handleSetCurrentTime,\n\t\tisLoading,\n\t\terror,\n\t};\n}\n","\"use client\";\n\nimport * as React from \"react\";\n\ntype EventMap<T> = T extends Window\n\t? WindowEventMap\n\t: T extends Document\n\t\t? DocumentEventMap\n\t\t: T extends HTMLElement\n\t\t\t? HTMLElementEventMap\n\t\t\t: never;\n\n/**\n * Custom hook that attaches an event listener to a specified element.\n * Automatically handles cleanup on unmount and when dependencies change.\n *\n * @param eventName - The name of the event to listen for\n * @param handler - The callback function to execute when the event fires\n * @param element - The element to attach the listener to (defaults to window)\n * @param options - Optional event listener options\n *\n * @example\n * ```tsx\n * // Listen to window resize\n * useEventListener('resize', handleResize);\n *\n * // Listen to element scroll\n * useEventListener('scroll', handleScroll, containerRef.current);\n *\n * // With options\n * useEventListener('scroll', handleScroll, window, { passive: true });\n * ```\n */\nexport function useEventListener<\n\tT extends Window | Document | HTMLElement,\n\tK extends keyof EventMap<T>,\n>(\n\teventName: K,\n\thandler: (event: EventMap<T>[K]) => void,\n\telement?: T | null,\n\toptions?: boolean | AddEventListenerOptions,\n): void {\n\t// Store the handler in a ref to avoid re-subscribing on every render\n\tconst savedHandler = React.useRef(handler);\n\n\t// Update ref when handler changes\n\tReact.useLayoutEffect(() => {\n\t\tsavedHandler.current = handler;\n\t}, [handler]);\n\n\tReact.useEffect(() => {\n\t\t// Default to window if no element is provided\n\t\tconst targetElement = element ?? window;\n\n\t\tif (!targetElement?.addEventListener) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst eventListener = (event: Event) => {\n\t\t\tsavedHandler.current(event as EventMap<T>[K]);\n\t\t};\n\n\t\ttargetElement.addEventListener(eventName as string, eventListener, options);\n\n\t\treturn () => {\n\t\t\ttargetElement.removeEventListener(\n\t\t\t\teventName as string,\n\t\t\t\teventListener,\n\t\t\t\toptions,\n\t\t\t);\n\t\t};\n\t}, [eventName, element, options]);\n}\n","import * as React from \"react\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface UseVideoKeyboardOptions {\n\t/** Ref to the video element */\n\tvideoRef: React.RefObject<HTMLVideoElement | null>;\n\t/** Whether keyboard handling is enabled (default: true) */\n\tenabled?: boolean;\n\t/** Seek amount in seconds for arrow keys (default: 5) */\n\tseekAmount?: number;\n\t/** Volume change amount for arrow keys (default: 0.1) */\n\tvolumeStep?: number;\n\t/** Callback when play/pause is toggled */\n\tonTogglePlay?: () => void;\n\t/** Callback when fullscreen is toggled */\n\tonToggleFullscreen?: () => void;\n\t/** Callback when captions are toggled */\n\tonToggleCaptions?: () => void;\n\t/** Callback when controls should be shown */\n\tonShowControls?: () => void;\n}\n\nexport interface UseVideoKeyboardReturn {\n\t/** Key down handler to attach to container element */\n\thandleKeyDown: (e: React.KeyboardEvent) => void;\n\t/** Props to spread on the container element */\n\tcontainerProps: {\n\t\tonKeyDown: (e: React.KeyboardEvent) => void;\n\t\ttabIndex: number;\n\t\trole: string;\n\t\t\"aria-label\": string;\n\t};\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\n/**\n * Hook for handling keyboard shortcuts in a video player.\n *\n * Supported shortcuts:\n * - Space: Play/pause\n * - Left Arrow: Seek backward\n * - Right Arrow: Seek forward\n * - Up Arrow: Volume up\n * - Down Arrow: Volume down\n * - M: Toggle mute\n * - F: Toggle fullscreen\n * - C: Toggle captions\n *\n * @example\n * ```tsx\n * const { containerProps } = useVideoKeyboard({\n * videoRef,\n * onTogglePlay: () => video.paused ? video.play() : video.pause(),\n * onToggleFullscreen: () => toggleFullscreen(),\n * onShowControls: () => setControlsVisible(true),\n * });\n *\n * return <div {...containerProps}>...</div>;\n * ```\n */\nexport function useVideoKeyboard({\n\tvideoRef,\n\tenabled = true,\n\tseekAmount = 5,\n\tvolumeStep = 0.1,\n\tonTogglePlay,\n\tonToggleFullscreen,\n\tonToggleCaptions,\n\tonShowControls,\n}: UseVideoKeyboardOptions): UseVideoKeyboardReturn {\n\tconst handleKeyDown = React.useCallback(\n\t\t(e: React.KeyboardEvent) => {\n\t\t\tif (!enabled) return;\n\n\t\t\tconst video = videoRef.current;\n\t\t\tif (!video) return;\n\n\t\t\t// Don't handle if focus is on interactive elements\n\t\t\tconst target = e.target as HTMLElement;\n\t\t\tif (\n\t\t\t\ttarget.tagName === \"BUTTON\" ||\n\t\t\t\ttarget.tagName === \"INPUT\" ||\n\t\t\t\ttarget.tagName === \"TEXTAREA\" ||\n\t\t\t\ttarget.closest(\"button\") ||\n\t\t\t\ttarget.closest(\"input\") ||\n\t\t\t\ttarget.closest(\"[role='slider']\")\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet handled = false;\n\n\t\t\tswitch (e.key) {\n\t\t\t\t// Play/Pause\n\t\t\t\tcase \" \":\n\t\t\t\tcase \"Spacebar\":\n\t\t\t\tcase \"k\": // YouTube-style shortcut\n\t\t\t\t\tif (onTogglePlay) {\n\t\t\t\t\t\tonTogglePlay();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (video.paused) {\n\t\t\t\t\t\t\tvideo.play().catch(() => {});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tvideo.pause();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\thandled = true;\n\t\t\t\t\tbreak;\n\n\t\t\t\t// Seek backward\n\t\t\t\tcase \"ArrowLeft\":\n\t\t\t\tcase \"j\": // YouTube-style shortcut\n\t\t\t\t\tvideo.currentTime = Math.max(0, video.currentTime - seekAmount);\n\t\t\t\t\thandled = true;\n\t\t\t\t\tbreak;\n\n\t\t\t\t// Seek forward\n\t\t\t\tcase \"ArrowRight\":\n\t\t\t\tcase \"l\": // YouTube-style shortcut\n\t\t\t\t\tvideo.currentTime = Math.min(\n\t\t\t\t\t\tvideo.duration || 0,\n\t\t\t\t\t\tvideo.currentTime + seekAmount,\n\t\t\t\t\t);\n\t\t\t\t\thandled = true;\n\t\t\t\t\tbreak;\n\n\t\t\t\t// Volume up\n\t\t\t\tcase \"ArrowUp\":\n\t\t\t\t\tvideo.volume = Math.min(1, video.volume + volumeStep);\n\t\t\t\t\tvideo.muted = false;\n\t\t\t\t\thandled = true;\n\t\t\t\t\tbreak;\n\n\t\t\t\t// Volume down\n\t\t\t\tcase \"ArrowDown\":\n\t\t\t\t\tvideo.volume = Math.max(0, video.volume - volumeStep);\n\t\t\t\t\thandled = true;\n\t\t\t\t\tbreak;\n\n\t\t\t\t// Toggle mute\n\t\t\t\tcase \"m\":\n\t\t\t\tcase \"M\":\n\t\t\t\t\tvideo.muted = !video.muted;\n\t\t\t\t\thandled = true;\n\t\t\t\t\tbreak;\n\n\t\t\t\t// Toggle fullscreen\n\t\t\t\tcase \"f\":\n\t\t\t\tcase \"F\":\n\t\t\t\t\tonToggleFullscreen?.();\n\t\t\t\t\thandled = true;\n\t\t\t\t\tbreak;\n\n\t\t\t\t// Toggle captions\n\t\t\t\tcase \"c\":\n\t\t\t\tcase \"C\":\n\t\t\t\t\tonToggleCaptions?.();\n\t\t\t\t\thandled = true;\n\t\t\t\t\tbreak;\n\n\t\t\t\t// Jump to start\n\t\t\t\tcase \"Home\":\n\t\t\t\t\tvideo.currentTime = 0;\n\t\t\t\t\thandled = true;\n\t\t\t\t\tbreak;\n\n\t\t\t\t// Jump to end\n\t\t\t\tcase \"End\":\n\t\t\t\t\tvideo.currentTime = video.duration || 0;\n\t\t\t\t\thandled = true;\n\t\t\t\t\tbreak;\n\n\t\t\t\t// Number keys for percentage seeking (0-9)\n\t\t\t\tcase \"0\":\n\t\t\t\tcase \"1\":\n\t\t\t\tcase \"2\":\n\t\t\t\tcase \"3\":\n\t\t\t\tcase \"4\":\n\t\t\t\tcase \"5\":\n\t\t\t\tcase \"6\":\n\t\t\t\tcase \"7\":\n\t\t\t\tcase \"8\":\n\t\t\t\tcase \"9\":\n\t\t\t\t\tif (video.duration) {\n\t\t\t\t\t\tconst percent = parseInt(e.key, 10) / 10;\n\t\t\t\t\t\tvideo.currentTime = video.duration * percent;\n\t\t\t\t\t\thandled = true;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (handled) {\n\t\t\t\te.preventDefault();\n\t\t\t\te.stopPropagation();\n\t\t\t\tonShowControls?.();\n\t\t\t}\n\t\t},\n\t\t[\n\t\t\tenabled,\n\t\t\tvideoRef,\n\t\t\tseekAmount,\n\t\t\tvolumeStep,\n\t\t\tonTogglePlay,\n\t\t\tonToggleFullscreen,\n\t\t\tonToggleCaptions,\n\t\t\tonShowControls,\n\t\t],\n\t);\n\n\tconst containerProps = React.useMemo(\n\t\t() => ({\n\t\t\tonKeyDown: handleKeyDown,\n\t\t\ttabIndex: 0,\n\t\t\trole: \"application\" as const,\n\t\t\t\"aria-label\": \"Video player, press space to play or pause\",\n\t\t}),\n\t\t[handleKeyDown],\n\t);\n\n\treturn {\n\t\thandleKeyDown,\n\t\tcontainerProps,\n\t};\n}\n"]}