@vite-mf-monorepo/ui 0.0.2

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/index.js ADDED
@@ -0,0 +1,1514 @@
1
+ import { Button_default } from './chunk-IXEILQNO.js';
2
+ export { Button_default as Button } from './chunk-IXEILQNO.js';
3
+ import { Card_default } from './chunk-YKNVY2QQ.js';
4
+ export { Card_default as Card } from './chunk-YKNVY2QQ.js';
5
+ import { Icon_default } from './chunk-Z3E2P4JR.js';
6
+ export { Icon_default as Icon } from './chunk-Z3E2P4JR.js';
7
+ import clsx15 from 'clsx';
8
+ import { createContext, useState, useRef, useEffect, useCallback, createElement, useLayoutEffect, useContext } from 'react';
9
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
10
+ import { getBlurDataUrl, getOptimizedImageUrl } from '@vite-mf-monorepo/shared';
11
+ import { Link } from 'react-router-dom';
12
+
13
+ var sizeMap = {
14
+ xs: { container: "ui:h-6 ui:w-6", icon: 12, text: "ui:text-xs" },
15
+ sm: { container: "ui:h-8 ui:w-8", icon: 16, text: "ui:text-sm" },
16
+ md: { container: "ui:h-10 ui:w-10", icon: 20, text: "ui:text-base" },
17
+ lg: { container: "ui:h-12 ui:w-12", icon: 24, text: "ui:text-lg" },
18
+ xl: { container: "ui:h-16 ui:w-16", icon: 32, text: "ui:text-xl" },
19
+ "2xl": { container: "ui:h-24 ui:w-24", icon: 48, text: "ui:text-2xl" },
20
+ "3xl": { container: "ui:h-32 ui:w-32", icon: 64, text: "ui:text-3xl" }
21
+ };
22
+ var Avatar = ({
23
+ className,
24
+ src,
25
+ alt,
26
+ size = "md",
27
+ initials,
28
+ testId,
29
+ ...rest
30
+ }) => {
31
+ const [hasError, setHasError] = useState(false);
32
+ const { container, icon, text } = sizeMap[size];
33
+ const showImage = src && !hasError;
34
+ const showInitials = !showImage && initials;
35
+ const showFallback = !showImage && !initials;
36
+ const handleError = () => {
37
+ setHasError(true);
38
+ };
39
+ return /* @__PURE__ */ jsxs(
40
+ "div",
41
+ {
42
+ className: clsx15(
43
+ "ui:relative ui:inline-flex ui:items-center ui:justify-center ui:overflow-hidden ui:rounded-full",
44
+ "ui:bg-muted ui:text-muted-foreground",
45
+ container,
46
+ className
47
+ ),
48
+ children: [
49
+ showImage && /* @__PURE__ */ jsx(
50
+ "img",
51
+ {
52
+ src,
53
+ alt,
54
+ onError: handleError,
55
+ className: "ui:h-full ui:w-full ui:object-cover",
56
+ "data-testid": testId,
57
+ ...rest
58
+ }
59
+ ),
60
+ showInitials && /* @__PURE__ */ jsx("span", { className: clsx15("ui:font-medium ui:uppercase", text), children: initials.slice(0, 2) }),
61
+ showFallback && /* @__PURE__ */ jsx(Icon_default, { name: "User", size: icon })
62
+ ]
63
+ }
64
+ );
65
+ };
66
+ var Avatar_default = Avatar;
67
+ var sizeMap2 = {
68
+ sm: { padding: "ui:px-2 ui:py-0.5", text: "ui:text-xs", iconSize: 16 },
69
+ md: { padding: "ui:px-2.5 ui:py-0.5", text: "ui:text-sm", iconSize: 16 },
70
+ lg: { padding: "ui:px-3 ui:py-1", text: "ui:text-sm", iconSize: 16 }
71
+ };
72
+ var Badge = ({
73
+ children,
74
+ variant = "default",
75
+ size = "md",
76
+ icon,
77
+ textClassName,
78
+ className
79
+ }) => {
80
+ const { padding, text, iconSize } = sizeMap2[size];
81
+ return /* @__PURE__ */ jsxs(
82
+ "span",
83
+ {
84
+ className: clsx15(
85
+ "ui:inline-flex ui:items-center ui:gap-1 ui:rounded-full ui:font-medium",
86
+ padding,
87
+ text,
88
+ {
89
+ "ui:bg-primary": variant === "default",
90
+ "ui:text-primary-foreground": variant === "default" && !textClassName,
91
+ "ui:bg-secondary": variant === "secondary",
92
+ "ui:text-secondary-foreground": variant === "secondary" && !textClassName,
93
+ "ui:border ui:border-input ui:bg-transparent": variant === "outline",
94
+ "ui:bg-destructive": variant === "destructive",
95
+ "ui:text-destructive-foreground": variant === "destructive" && !textClassName
96
+ },
97
+ textClassName,
98
+ className
99
+ ),
100
+ children: [
101
+ icon && /* @__PURE__ */ jsx(Icon_default, { name: icon, size: iconSize }),
102
+ children
103
+ ]
104
+ }
105
+ );
106
+ };
107
+ var Badge_default = Badge;
108
+ var sizeMap3 = {
109
+ sm: { button: "ui:h-8 ui:w-8", icon: 16 },
110
+ md: { button: "ui:h-10 ui:w-10", icon: 20 },
111
+ lg: { button: "ui:h-12 ui:w-12", icon: 24 }
112
+ };
113
+ var IconButton = ({
114
+ className,
115
+ icon,
116
+ variant = "ghost",
117
+ size = "md",
118
+ disabled,
119
+ ...rest
120
+ }) => {
121
+ const { button: buttonSize, icon: iconSize } = sizeMap3[size];
122
+ return /* @__PURE__ */ jsx(
123
+ "button",
124
+ {
125
+ className: clsx15(
126
+ "ui:inline-flex ui:items-center ui:justify-center ui:cursor-pointer ui:rounded-full ui:transition-colors",
127
+ "ui:focus:outline-none ui:focus:ring-2 ui:focus:ring-ring ui:focus:ring-offset-2",
128
+ "ui:disabled:pointer-events-none ui:disabled:opacity-50",
129
+ {
130
+ "ui:bg-primary ui:text-primary-foreground ui:hover:bg-primary/90": variant === "primary",
131
+ "ui:bg-secondary ui:text-secondary-foreground ui:hover:bg-secondary/80": variant === "secondary",
132
+ "ui:hover:bg-accent ui:hover:text-accent-foreground": variant === "ghost",
133
+ "ui:border ui:border-input ui:bg-background ui:hover:bg-accent ui:hover:text-accent-foreground": variant === "outline"
134
+ },
135
+ buttonSize,
136
+ className
137
+ ),
138
+ disabled,
139
+ ...rest,
140
+ children: /* @__PURE__ */ jsx(Icon_default, { name: icon, size: iconSize })
141
+ }
142
+ );
143
+ };
144
+ var IconButton_default = IconButton;
145
+ var Image = ({
146
+ src,
147
+ alt,
148
+ blurDataUrl,
149
+ autoBlur = false,
150
+ blurSize = 16,
151
+ blurQuality = 0.3,
152
+ aspectRatio = "2/3",
153
+ fallback,
154
+ className,
155
+ onLoad,
156
+ onError,
157
+ loading = "eager",
158
+ ...rest
159
+ }) => {
160
+ const imgRef = useRef(null);
161
+ const containerRef = useRef(null);
162
+ const [state, setState] = useState("loading");
163
+ const [generatedBlur, setGeneratedBlur] = useState(
164
+ void 0
165
+ );
166
+ const [blurReady, setBlurReady] = useState(!autoBlur || !!blurDataUrl);
167
+ const [isVisible, setIsVisible] = useState(loading === "eager");
168
+ const effectiveBlur = blurDataUrl ?? generatedBlur;
169
+ useEffect(() => {
170
+ if (loading === "eager" || !containerRef.current) {
171
+ return;
172
+ }
173
+ const observer = new IntersectionObserver(
174
+ ([entry]) => {
175
+ if (entry.isIntersecting) {
176
+ setIsVisible(true);
177
+ observer.unobserve(entry.target);
178
+ }
179
+ },
180
+ { rootMargin: "50px" }
181
+ );
182
+ observer.observe(containerRef.current);
183
+ return () => {
184
+ observer.disconnect();
185
+ };
186
+ }, [loading]);
187
+ useEffect(() => {
188
+ if (!autoBlur || blurDataUrl) {
189
+ setBlurReady(true);
190
+ return;
191
+ }
192
+ setBlurReady(false);
193
+ getBlurDataUrl(src, blurSize, blurQuality).then((base64) => {
194
+ setGeneratedBlur(base64);
195
+ setBlurReady(true);
196
+ }).catch(() => {
197
+ setBlurReady(true);
198
+ });
199
+ }, [autoBlur, src, blurDataUrl, blurSize, blurQuality]);
200
+ useEffect(() => {
201
+ setState("loading");
202
+ setGeneratedBlur(void 0);
203
+ if (imgRef.current?.complete && imgRef.current.naturalHeight !== 0) {
204
+ setState("loaded");
205
+ }
206
+ }, [src]);
207
+ const handleLoad = useCallback(() => {
208
+ setState("loaded");
209
+ onLoad?.();
210
+ }, [onLoad]);
211
+ const handleError = useCallback(() => {
212
+ setState("error");
213
+ onError?.();
214
+ }, [onError]);
215
+ const defaultFallback = /* @__PURE__ */ jsx("div", { className: "ui:flex ui:h-full ui:w-full ui:items-center ui:justify-center ui:bg-muted", children: /* @__PURE__ */ jsx(
216
+ Icon_default,
217
+ {
218
+ name: "Photo",
219
+ size: 48,
220
+ className: "ui:text-muted-foreground",
221
+ "aria-hidden": "true"
222
+ }
223
+ ) });
224
+ return /* @__PURE__ */ jsx(
225
+ "div",
226
+ {
227
+ ref: containerRef,
228
+ className: clsx15("ui:relative ui:overflow-hidden ui:bg-muted", className),
229
+ style: aspectRatio ? { aspectRatio } : void 0,
230
+ "data-state": state,
231
+ children: state === "error" ? fallback ?? defaultFallback : /* @__PURE__ */ jsxs(Fragment, { children: [
232
+ effectiveBlur && state === "loading" && /* @__PURE__ */ jsx(
233
+ "img",
234
+ {
235
+ src: effectiveBlur,
236
+ alt: "",
237
+ "aria-hidden": "true",
238
+ className: "ui:absolute ui:inset-0 ui:h-full ui:w-full ui:scale-105 ui:object-cover"
239
+ }
240
+ ),
241
+ blurReady && isVisible && /* @__PURE__ */ jsx(
242
+ "img",
243
+ {
244
+ ref: imgRef,
245
+ src,
246
+ alt,
247
+ onLoad: handleLoad,
248
+ onError: handleError,
249
+ className: clsx15(
250
+ "ui:absolute ui:inset-0 ui:h-full ui:w-full ui:object-cover ui:transition-opacity ui:duration-300",
251
+ state === "loaded" ? "ui:opacity-100" : "ui:opacity-0"
252
+ ),
253
+ ...rest
254
+ }
255
+ )
256
+ ] })
257
+ }
258
+ );
259
+ };
260
+ var Image_default = Image;
261
+ var Skeleton = ({
262
+ variant = "rectangle",
263
+ width,
264
+ height,
265
+ aspectRatio,
266
+ rounded = true,
267
+ className,
268
+ ...rest
269
+ }) => {
270
+ return /* @__PURE__ */ jsx(
271
+ "div",
272
+ {
273
+ className: clsx15(
274
+ "ui:relative ui:overflow-hidden ui:bg-muted",
275
+ "ui-skeleton-shimmer",
276
+ {
277
+ "ui:rounded-lg": variant === "rectangle" && rounded,
278
+ "ui:rounded-full": variant === "circle",
279
+ "ui:rounded": variant === "line" && rounded
280
+ },
281
+ width,
282
+ height,
283
+ className
284
+ ),
285
+ style: aspectRatio ? { aspectRatio } : void 0,
286
+ ...rest
287
+ }
288
+ );
289
+ };
290
+ var Skeleton_default = Skeleton;
291
+ var HeroImage = ({ backdropPath, title }) => {
292
+ const [loading, setLoading] = useState(true);
293
+ const backdropPathMobile = backdropPath ? getOptimizedImageUrl(backdropPath, "w300", 60) : void 0;
294
+ const backdropPathTablet = backdropPath ? getOptimizedImageUrl(backdropPath, "w300", 60) : void 0;
295
+ const backdropPathDesktop = backdropPath ? getOptimizedImageUrl(backdropPath, "w780", 60) : void 0;
296
+ const backdropPathUltraWide = backdropPath ? getOptimizedImageUrl(backdropPath, "w1280", 60) : void 0;
297
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
298
+ loading && /* @__PURE__ */ jsx(
299
+ Skeleton_default,
300
+ {
301
+ "data-testid": "hero-image-skeleton",
302
+ variant: "rectangle",
303
+ width: "ui:relative ui:w-full ui:h-full ui:hero-height ui:z-0",
304
+ aspectRatio: "21/9",
305
+ rounded: false
306
+ }
307
+ ),
308
+ /* @__PURE__ */ jsxs("picture", { children: [
309
+ backdropPathMobile && /* @__PURE__ */ jsx("source", { media: "(max-width: 639px)", srcSet: backdropPathMobile }),
310
+ backdropPathTablet && /* @__PURE__ */ jsx("source", { media: "(max-width: 1023px)", srcSet: backdropPathTablet }),
311
+ backdropPathDesktop && /* @__PURE__ */ jsx("source", { media: "(max-width: 1535px)", srcSet: backdropPathDesktop }),
312
+ backdropPathUltraWide && /* @__PURE__ */ jsx("source", { media: "(min-width: 1536px)", srcSet: backdropPathUltraWide }),
313
+ backdropPathMobile && /* @__PURE__ */ jsx(
314
+ "img",
315
+ {
316
+ src: backdropPathMobile,
317
+ fetchPriority: "high",
318
+ onLoad: () => {
319
+ setLoading(false);
320
+ },
321
+ alt: title ?? "Unknown",
322
+ className: "ui:relative ui:h-full ui:w-full ui:object-cover ui:object-center ui:z-0"
323
+ }
324
+ )
325
+ ] }),
326
+ /* @__PURE__ */ jsx("div", { className: "ui:absolute ui:inset-0 ui:bg-gradient-to-t ui:from-black/80 ui:via-black/40 ui:to-transparent ui:z-1 ui:top-0 ui:left-0 ui:right-0 ui:bottom-0" })
327
+ ] });
328
+ };
329
+ var HeroImage_default = HeroImage;
330
+ var Modal = ({
331
+ isOpen,
332
+ onClose,
333
+ children,
334
+ "aria-label": ariaLabel,
335
+ className,
336
+ onOverlayClick
337
+ }) => {
338
+ const ref = useRef(null);
339
+ useEffect(() => {
340
+ const dialog = ref.current;
341
+ if (!dialog) return;
342
+ if (isOpen) {
343
+ dialog.showModal();
344
+ document.body.style.overflow = "hidden";
345
+ } else {
346
+ dialog.close();
347
+ document.body.style.overflow = "";
348
+ }
349
+ return () => {
350
+ document.body.style.overflow = "";
351
+ };
352
+ }, [isOpen]);
353
+ const handleClick = (e) => {
354
+ if (e.target === ref.current) (onOverlayClick ?? onClose)();
355
+ };
356
+ const handleClose = () => {
357
+ onClose();
358
+ };
359
+ return /* @__PURE__ */ jsx(
360
+ "dialog",
361
+ {
362
+ ref,
363
+ "aria-label": ariaLabel,
364
+ "aria-modal": "true",
365
+ onClick: handleClick,
366
+ onClose: handleClose,
367
+ className: clsx15(
368
+ "ui:backdrop:bg-black/80",
369
+ "ui:bg-transparent ui:border-0 ui:p-0",
370
+ "ui:max-w-none ui:max-h-none ui:w-full ui:h-full",
371
+ className
372
+ ),
373
+ children
374
+ }
375
+ );
376
+ };
377
+ var Modal_default = Modal;
378
+ var circleSizeMap = {
379
+ sm: { size: 32, stroke: 3, fontSize: "ui:text-xs" },
380
+ md: { size: 48, stroke: 4, fontSize: "ui:text-sm" },
381
+ lg: { size: 64, stroke: 5, fontSize: "ui:text-base" }
382
+ };
383
+ var getColorClass = (percent) => {
384
+ if (percent >= 70) return "ui:text-green-600";
385
+ if (percent >= 40) return "ui:text-amber-600";
386
+ return "ui:text-red-500";
387
+ };
388
+ var CircleRating = ({
389
+ percent,
390
+ size,
391
+ showValue,
392
+ value,
393
+ max,
394
+ trackClassName
395
+ }) => {
396
+ const { size: svgSize, stroke, fontSize } = circleSizeMap[size];
397
+ const radius = (svgSize - stroke) / 2;
398
+ const circumference = 2 * Math.PI * radius;
399
+ const offset = circumference - percent / 100 * circumference;
400
+ return /* @__PURE__ */ jsxs("div", { className: "ui:relative ui:inline-flex ui:items-center ui:justify-center", children: [
401
+ /* @__PURE__ */ jsxs("svg", { width: svgSize, height: svgSize, className: "ui:-rotate-90", children: [
402
+ /* @__PURE__ */ jsx(
403
+ "circle",
404
+ {
405
+ cx: svgSize / 2,
406
+ cy: svgSize / 2,
407
+ r: radius,
408
+ fill: "none",
409
+ stroke: "currentColor",
410
+ strokeWidth: stroke,
411
+ className: trackClassName ?? "ui:text-gray-200"
412
+ }
413
+ ),
414
+ /* @__PURE__ */ jsx(
415
+ "circle",
416
+ {
417
+ cx: svgSize / 2,
418
+ cy: svgSize / 2,
419
+ r: radius,
420
+ fill: "none",
421
+ stroke: "currentColor",
422
+ strokeWidth: stroke,
423
+ strokeLinecap: "round",
424
+ strokeDasharray: circumference,
425
+ strokeDashoffset: offset,
426
+ className: clsx15(
427
+ "ui:transition-all ui:duration-500",
428
+ getColorClass(percent)
429
+ )
430
+ }
431
+ )
432
+ ] }),
433
+ showValue && /* @__PURE__ */ jsx(
434
+ "span",
435
+ {
436
+ className: clsx15(
437
+ "ui:absolute ui:font-bold",
438
+ fontSize,
439
+ getColorClass(percent)
440
+ ),
441
+ children: max === 100 ? Math.round(percent) : value.toFixed(1)
442
+ }
443
+ )
444
+ ] });
445
+ };
446
+ var CircleRating_default = CircleRating;
447
+ var starsSizeMap = {
448
+ sm: 16,
449
+ md: 20,
450
+ lg: 24
451
+ };
452
+ var StarsRating = ({
453
+ percent,
454
+ size,
455
+ showValue,
456
+ value,
457
+ max
458
+ }) => {
459
+ const iconSize = starsSizeMap[size];
460
+ return /* @__PURE__ */ jsxs("div", { className: "ui:inline-flex ui:items-center ui:gap-1", children: [
461
+ /* @__PURE__ */ jsxs("div", { className: "ui:relative", children: [
462
+ /* @__PURE__ */ jsx("div", { className: "ui:flex ui:text-gray-300", children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsx(Icon_default, { name: "Star", size: iconSize }, i)) }),
463
+ /* @__PURE__ */ jsx(
464
+ "div",
465
+ {
466
+ className: "ui:absolute ui:inset-0 ui:flex ui:text-yellow-400",
467
+ style: { clipPath: `inset(0 ${String(100 - percent)}% 0 0)` },
468
+ children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsx(Icon_default, { name: "Star", size: iconSize }, i))
469
+ }
470
+ )
471
+ ] }),
472
+ showValue && /* @__PURE__ */ jsx(
473
+ "span",
474
+ {
475
+ className: clsx15(
476
+ "ui:font-medium ui:text-muted-foreground",
477
+ size === "sm" && "ui:text-xs",
478
+ size === "md" && "ui:text-sm",
479
+ size === "lg" && "ui:text-base"
480
+ ),
481
+ children: max === 100 ? (value / 10).toFixed(1) : value.toFixed(1)
482
+ }
483
+ )
484
+ ] });
485
+ };
486
+ var StarsRating_default = StarsRating;
487
+ var Rating = ({
488
+ value,
489
+ max = 10,
490
+ variant = "circle",
491
+ size = "md",
492
+ showValue = true,
493
+ trackClassName,
494
+ className
495
+ }) => {
496
+ const clampedValue = Math.max(0, Math.min(value, max));
497
+ const percent = clampedValue / max * 100;
498
+ return /* @__PURE__ */ jsx("div", { className: clsx15("ui:inline-flex", className), children: variant === "circle" ? /* @__PURE__ */ jsx(
499
+ CircleRating_default,
500
+ {
501
+ percent,
502
+ size,
503
+ showValue,
504
+ value: clampedValue,
505
+ max,
506
+ trackClassName
507
+ }
508
+ ) : /* @__PURE__ */ jsx(
509
+ StarsRating_default,
510
+ {
511
+ percent,
512
+ size,
513
+ showValue,
514
+ value: clampedValue,
515
+ max
516
+ }
517
+ ) });
518
+ };
519
+ var Rating_default = Rating;
520
+ var variantStyles = {
521
+ h1: "ui:font-roboto ui:text-xl ui:sm:text-2xl ui:md:text-3xl ui:lg:text-4xl ui:font-bold ui:leading-tight ui:text-foreground",
522
+ h2: "ui:font-roboto ui:text-lg ui:sm:text-xl ui:md:text-2xl ui:lg:text-3xl ui:font-bold ui:leading-tight ui:text-foreground",
523
+ h3: "ui:font-roboto ui:text-base ui:sm:text-lg ui:md:text-xl ui:lg:text-2xl ui:font-semibold ui:leading-snug ui:text-foreground",
524
+ h4: "ui:font-roboto ui:text-sm ui:sm:text-base ui:md:text-lg ui:lg:text-xl ui:font-semibold ui:leading-snug ui:text-foreground",
525
+ h5: "ui:font-roboto ui:text-sm ui:sm:text-base ui:md:text-lg ui:font-medium ui:leading-normal ui:text-foreground",
526
+ h6: "ui:font-roboto ui:text-xs ui:sm:text-sm ui:md:text-base ui:font-medium ui:leading-normal ui:text-foreground",
527
+ body: "ui:font-inter ui:text-xs ui:sm:text-sm ui:md:text-base ui:leading-relaxed ui:text-foreground",
528
+ "body-sm": "ui:font-inter ui:text-xs ui:sm:text-sm ui:leading-relaxed ui:text-foreground",
529
+ "body-lg": "ui:font-inter ui:text-sm ui:sm:text-base ui:md:text-lg ui:leading-relaxed ui:text-foreground",
530
+ lead: "ui:font-inter ui:text-sm ui:sm:text-base ui:md:text-lg ui:lg:text-xl ui:leading-relaxed ui:text-muted-foreground",
531
+ caption: "ui:font-inter ui:text-xs ui:sm:text-sm ui:text-muted-foreground",
532
+ "caption-xs": "ui:font-inter ui:text-xs ui:text-muted-foreground",
533
+ label: "ui:font-inter ui:text-xs ui:sm:text-sm ui:font-medium ui:text-foreground",
534
+ muted: "ui:font-inter ui:text-xs ui:sm:text-sm ui:text-muted-foreground",
535
+ blockquote: "ui:font-inter ui:text-xs ui:sm:text-sm ui:md:text-base ui:border-l-4 ui:border-border ui:pl-4 ui:italic ui:text-muted-foreground"
536
+ };
537
+ var variantToTag = {
538
+ h1: "h1",
539
+ h2: "h2",
540
+ h3: "h3",
541
+ h4: "h4",
542
+ h5: "h5",
543
+ h6: "h6",
544
+ body: "p",
545
+ "body-sm": "p",
546
+ "body-lg": "p",
547
+ lead: "p",
548
+ caption: "span",
549
+ "caption-xs": "span",
550
+ label: "label",
551
+ muted: "p",
552
+ blockquote: "blockquote"
553
+ };
554
+ var Typography = ({
555
+ variant,
556
+ as,
557
+ className,
558
+ children,
559
+ ...rest
560
+ }) => {
561
+ const Component = as ?? variantToTag[variant];
562
+ return createElement(
563
+ Component,
564
+ { className: clsx15(variantStyles[variant], className), ...rest },
565
+ children
566
+ );
567
+ };
568
+ var Typography_default = Typography;
569
+ var MovieCard = ({
570
+ id,
571
+ title,
572
+ posterUrl,
573
+ voteAverage,
574
+ year,
575
+ className,
576
+ imageLoading = "lazy",
577
+ as = "card",
578
+ ...rest
579
+ }) => {
580
+ const to = "to" in rest ? rest.to : void 0;
581
+ const onClick = "onClick" in rest ? rest.onClick : void 0;
582
+ const isInteractive = as === "link" || as === "button";
583
+ const cardContent = /* @__PURE__ */ jsxs(
584
+ Card_default,
585
+ {
586
+ variant: "ghost",
587
+ className: clsx15(
588
+ "ui:group ui:relative ui:flex ui:flex-col ui:overflow-hidden",
589
+ isInteractive && [
590
+ "ui:cursor-pointer",
591
+ "ui:transition-transform ui:duration-200",
592
+ "hover:ui:scale-[1.02]"
593
+ ],
594
+ className
595
+ ),
596
+ onClick: as === "button" ? onClick : void 0,
597
+ "data-testid": `movie-card-${String(id)}`,
598
+ children: [
599
+ /* @__PURE__ */ jsxs("div", { className: "ui:relative ui:aspect-[2/3] ui:w-full ui:overflow-hidden ui:rounded-md ui:bg-gray-200", children: [
600
+ /* @__PURE__ */ jsx(
601
+ Image_default,
602
+ {
603
+ src: posterUrl,
604
+ alt: title,
605
+ loading: imageLoading,
606
+ aspectRatio: void 0,
607
+ className: "ui:h-full ui:w-full ui:object-cover"
608
+ }
609
+ ),
610
+ /* @__PURE__ */ jsx("div", { className: "ui:absolute ui:bottom-2 ui:right-2 ui:flex ui:items-center ui:justify-center ui:rounded-full ui:bg-white/80 ui:p-1", children: /* @__PURE__ */ jsx(
611
+ Rating_default,
612
+ {
613
+ value: voteAverage,
614
+ size: "sm",
615
+ variant: "circle",
616
+ trackClassName: "ui:text-gray-200",
617
+ className: "ui:drop-shadow"
618
+ }
619
+ ) })
620
+ ] }),
621
+ /* @__PURE__ */ jsxs("div", { className: "ui:mt-2 ui:flex ui:flex-col ui:gap-0.5 ui:px-1", children: [
622
+ /* @__PURE__ */ jsx(
623
+ Typography_default,
624
+ {
625
+ variant: "label",
626
+ as: "h3",
627
+ className: "ui:line-clamp-2",
628
+ title,
629
+ children: title
630
+ }
631
+ ),
632
+ year && /* @__PURE__ */ jsx(
633
+ Typography_default,
634
+ {
635
+ variant: "caption-xs",
636
+ className: "ui:[.media-section:nth-of-type(odd)_&]:text-badge-foreground",
637
+ children: year
638
+ }
639
+ )
640
+ ] })
641
+ ]
642
+ }
643
+ );
644
+ if (as === "link" && to) {
645
+ return /* @__PURE__ */ jsx(Link, { to, className: "ui:block ui:no-underline ui:text-inherit", children: cardContent });
646
+ }
647
+ return cardContent;
648
+ };
649
+ var MovieCard_default = MovieCard;
650
+ var TrailerCard = ({
651
+ videoKey,
652
+ title,
653
+ type = "Trailer",
654
+ className
655
+ }) => {
656
+ const [isPlaying, setIsPlaying] = useState(false);
657
+ const thumbnailUrl = `https://img.youtube.com/vi/${videoKey}/hqdefault.jpg`;
658
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
659
+ /* @__PURE__ */ jsxs(
660
+ "button",
661
+ {
662
+ className: clsx15(
663
+ "ui:group ui:relative ui:flex ui:aspect-video ui:w-full ui:cursor-pointer ui:overflow-hidden ui:rounded-lg ui:bg-gray-200",
664
+ "ui:transition-transform ui:duration-200 hover:ui:scale-[1.02]",
665
+ className
666
+ ),
667
+ onClick: () => {
668
+ setIsPlaying(true);
669
+ },
670
+ tabIndex: 0,
671
+ "aria-label": `Play ${title}`,
672
+ children: [
673
+ /* @__PURE__ */ jsx(
674
+ "img",
675
+ {
676
+ src: thumbnailUrl,
677
+ alt: title,
678
+ className: "ui:h-full ui:w-full ui:object-cover",
679
+ loading: "lazy"
680
+ }
681
+ ),
682
+ /* @__PURE__ */ jsx("div", { className: "ui:absolute ui:inset-0 ui:flex ui:items-center ui:justify-center ui:bg-black/30 ui:opacity-0 ui:transition-opacity ui:duration-200 group-hover:ui:opacity-100", children: /* @__PURE__ */ jsx("div", { className: "ui:flex ui:items-center ui:justify-center ui:h-16 ui:w-16 ui:rounded-full ui:bg-white/90", children: /* @__PURE__ */ jsx("span", { className: "ui:text-2xl ui:ml-1", children: "\u25B6" }) }) }),
683
+ type && /* @__PURE__ */ jsx("div", { className: "ui:absolute ui:bottom-2 ui:left-2 ui:rounded ui:bg-black/80 ui:px-2 ui:py-1 ui:text-xs ui:font-semibold ui:text-white", children: type })
684
+ ]
685
+ }
686
+ ),
687
+ /* @__PURE__ */ jsx(
688
+ Modal_default,
689
+ {
690
+ isOpen: isPlaying,
691
+ onClose: () => {
692
+ setIsPlaying(false);
693
+ },
694
+ "aria-label": `Play ${title}`,
695
+ children: /* @__PURE__ */ jsxs("div", { className: "ui:flex ui:h-full ui:w-full ui:items-center ui:justify-center ui:p-4", children: [
696
+ /* @__PURE__ */ jsx(
697
+ Button_default,
698
+ {
699
+ icon: "XMark",
700
+ size: "sm",
701
+ variant: "ghost",
702
+ iconPosition: "left",
703
+ onClick: () => {
704
+ setIsPlaying(false);
705
+ },
706
+ className: "ui:absolute ui:top-4 ui:right-4 ui:text-white hover:ui:bg-white/10 ui:z-10 ui:focus:border-none",
707
+ "aria-label": "Close video",
708
+ children: "Close video"
709
+ }
710
+ ),
711
+ isPlaying && /* @__PURE__ */ jsx(
712
+ "iframe",
713
+ {
714
+ className: "ui:w-full ui:max-w-4xl ui:aspect-video ui:rounded-lg",
715
+ src: `https://www.youtube.com/embed/${videoKey}`,
716
+ title,
717
+ allow: "accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
718
+ allowFullScreen: true
719
+ }
720
+ )
721
+ ] })
722
+ }
723
+ )
724
+ ] });
725
+ };
726
+ var TrailerCard_default = TrailerCard;
727
+ var CarouselCounter = ({
728
+ current,
729
+ total,
730
+ className
731
+ }) => {
732
+ return /* @__PURE__ */ jsxs(
733
+ "div",
734
+ {
735
+ className: clsx15(
736
+ "ui:bg-black/50 ui:rounded-full ui:px-3 ui:py-1",
737
+ "ui:text-white/90 ui:text-sm ui:font-medium ui:tabular-nums",
738
+ className
739
+ ),
740
+ children: [
741
+ current + 1,
742
+ " / ",
743
+ total
744
+ ]
745
+ }
746
+ );
747
+ };
748
+ var CarouselCounter_default = CarouselCounter;
749
+ var CarouselError = ({
750
+ message = "Failed to load data"
751
+ }) => {
752
+ return /* @__PURE__ */ jsxs("div", { className: "ui:flex ui:flex-col ui:items-center ui:justify-center ui:gap-3 ui:rounded-lg ui:border ui:border-red-200 ui:bg-red-50 ui:p-8 ui:text-center", children: [
753
+ /* @__PURE__ */ jsx(Icon_default, { name: "ExclamationTriangle", size: 48, className: "ui:text-red-500" }),
754
+ /* @__PURE__ */ jsxs("div", { className: "ui:flex ui:flex-col ui:gap-1", children: [
755
+ /* @__PURE__ */ jsx("p", { className: "ui:text-sm ui:font-semibold ui:text-red-900", children: "Failed to fetch data" }),
756
+ /* @__PURE__ */ jsx("p", { className: "ui:text-sm ui:text-red-700", children: message })
757
+ ] })
758
+ ] });
759
+ };
760
+ var CarouselError_default = CarouselError;
761
+ var CarouselNavigation = ({
762
+ onPrev,
763
+ onNext,
764
+ canPrev,
765
+ canNext,
766
+ size = "sm",
767
+ position = "inline",
768
+ iconVariant = "secondary",
769
+ className
770
+ }) => {
771
+ if (position === "sides") {
772
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
773
+ /* @__PURE__ */ jsx("div", { className: "ui:absolute ui:left-0 ui:top-1/2 ui:-translate-x-1/2 ui:-translate-y-1/2", children: /* @__PURE__ */ jsx(
774
+ IconButton_default,
775
+ {
776
+ icon: "ChevronLeft",
777
+ variant: iconVariant,
778
+ size,
779
+ onClick: onPrev,
780
+ disabled: !canPrev,
781
+ "aria-label": "Previous"
782
+ }
783
+ ) }),
784
+ /* @__PURE__ */ jsx("div", { className: "ui:absolute ui:right-0 ui:top-1/2 ui:-translate-y-1/2 ui:translate-x-1/2", children: /* @__PURE__ */ jsx(
785
+ IconButton_default,
786
+ {
787
+ icon: "ChevronRight",
788
+ variant: iconVariant,
789
+ size,
790
+ onClick: onNext,
791
+ disabled: !canNext,
792
+ "aria-label": "Next"
793
+ }
794
+ ) })
795
+ ] });
796
+ }
797
+ if (position === "sides-inset") {
798
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
799
+ /* @__PURE__ */ jsx("div", { className: "ui:absolute ui:left-4 ui:top-1/2 ui:-translate-y-1/2 ui:z-10 ui:text-white", children: /* @__PURE__ */ jsx(
800
+ IconButton_default,
801
+ {
802
+ icon: "ChevronLeft",
803
+ variant: iconVariant,
804
+ size,
805
+ onClick: onPrev,
806
+ disabled: !canPrev,
807
+ "aria-label": "Previous",
808
+ className: "ui:bg-white/20 ui:hover:bg-white/55"
809
+ }
810
+ ) }),
811
+ /* @__PURE__ */ jsx("div", { className: "ui:absolute ui:right-4 ui:top-1/2 ui:-translate-y-1/2 ui:z-10 ui:text-white", children: /* @__PURE__ */ jsx(
812
+ IconButton_default,
813
+ {
814
+ icon: "ChevronRight",
815
+ variant: iconVariant,
816
+ size,
817
+ onClick: onNext,
818
+ disabled: !canNext,
819
+ "aria-label": "Next",
820
+ className: "ui:bg-white/20 ui:hover:bg-white/55"
821
+ }
822
+ ) })
823
+ ] });
824
+ }
825
+ return /* @__PURE__ */ jsxs("div", { className: clsx15("ui:flex ui:gap-2", className), children: [
826
+ /* @__PURE__ */ jsx(
827
+ IconButton_default,
828
+ {
829
+ icon: "ChevronLeft",
830
+ variant: iconVariant,
831
+ size,
832
+ onClick: onPrev,
833
+ disabled: !canPrev,
834
+ "aria-label": "Previous"
835
+ }
836
+ ),
837
+ /* @__PURE__ */ jsx(
838
+ IconButton_default,
839
+ {
840
+ icon: "ChevronRight",
841
+ variant: iconVariant,
842
+ size,
843
+ onClick: onNext,
844
+ disabled: !canNext,
845
+ "aria-label": "Next"
846
+ }
847
+ )
848
+ ] });
849
+ };
850
+ var CarouselNavigation_default = CarouselNavigation;
851
+ var CarouselPagination = ({
852
+ total,
853
+ current,
854
+ light = false,
855
+ className
856
+ }) => {
857
+ if (total === 0) return null;
858
+ return /* @__PURE__ */ jsx("div", { className: clsx15("ui:flex ui:items-center ui:gap-2", className), children: Array.from({ length: total }).map((_, index) => /* @__PURE__ */ jsx(
859
+ "div",
860
+ {
861
+ "aria-hidden": "true",
862
+ className: clsx15(
863
+ "ui:rounded-full ui:transition-all ui:duration-300",
864
+ index === current ? clsx15("ui:h-2 ui:w-6", light ? "ui:bg-white" : "ui:bg-primary") : clsx15(
865
+ "ui:h-2 ui:w-2",
866
+ light ? "ui:bg-white/50 hover:ui:bg-white/70" : "ui:bg-gray-400 hover:ui:bg-gray-500"
867
+ )
868
+ )
869
+ },
870
+ index
871
+ )) });
872
+ };
873
+ var CarouselPagination_default = CarouselPagination;
874
+ var Carousel = ({
875
+ children,
876
+ variant = "standard",
877
+ showPagination = true,
878
+ showArrows = true,
879
+ arrowPosition = "sides",
880
+ gap = 16,
881
+ className,
882
+ heroControlsClassName,
883
+ errorMessage,
884
+ rounded = true,
885
+ initialIndex,
886
+ onPrev,
887
+ onNext,
888
+ disableAnimation = false,
889
+ disableScroll = false
890
+ }) => {
891
+ const scrollRef = useRef(null);
892
+ const [totalPositions, setTotalPositions] = useState(1);
893
+ const [scrollIndex, setScrollIndex] = useState(initialIndex ?? 0);
894
+ const currentIndex = disableScroll ? initialIndex ?? 0 : scrollIndex;
895
+ const isHero = variant === "hero";
896
+ const isLightbox = variant === "lightbox";
897
+ const isFullWidth = isHero || isLightbox;
898
+ const calculatePositions = useCallback(() => {
899
+ const container = scrollRef.current;
900
+ if (!container) return;
901
+ const items = container.children;
902
+ if (isFullWidth) {
903
+ setTotalPositions(items.length);
904
+ return;
905
+ }
906
+ const firstItem = items[0];
907
+ if (!firstItem) {
908
+ setTotalPositions(1);
909
+ return;
910
+ }
911
+ const itemWidth = firstItem.offsetWidth;
912
+ const maxScrollLeft = container.scrollWidth - container.offsetWidth;
913
+ if (maxScrollLeft <= 0) {
914
+ setTotalPositions(1);
915
+ } else if (itemWidth > 0) {
916
+ const positions = Math.round(maxScrollLeft / (itemWidth + gap)) + 1;
917
+ setTotalPositions(Math.max(1, positions));
918
+ }
919
+ }, [gap, isFullWidth]);
920
+ useEffect(() => {
921
+ calculatePositions();
922
+ }, [children, calculatePositions]);
923
+ useEffect(() => {
924
+ const container = scrollRef.current;
925
+ if (!container) return;
926
+ const resizeObserver = new ResizeObserver(() => {
927
+ calculatePositions();
928
+ });
929
+ resizeObserver.observe(container);
930
+ return () => {
931
+ resizeObserver.disconnect();
932
+ };
933
+ }, [calculatePositions]);
934
+ const initialScrollRef = useRef({
935
+ index: initialIndex ?? 0,
936
+ isFullWidth,
937
+ gap
938
+ });
939
+ useLayoutEffect(() => {
940
+ const { index, isFullWidth: fw, gap: g } = initialScrollRef.current;
941
+ if (index <= 0) return;
942
+ const container = scrollRef.current;
943
+ if (!container) return;
944
+ const firstItem = container.children[0];
945
+ const itemWidth = fw ? container.offsetWidth : firstItem?.offsetWidth ?? 0;
946
+ container.style.scrollBehavior = "auto";
947
+ container.scrollLeft = index * (itemWidth + (fw ? 0 : g));
948
+ container.style.scrollBehavior = "";
949
+ }, []);
950
+ const handleScroll = useCallback(() => {
951
+ const container = scrollRef.current;
952
+ if (!container) return;
953
+ const scrollLeft = container.scrollLeft;
954
+ const firstItem = container.children[0];
955
+ const itemWidth = isFullWidth ? container.offsetWidth : firstItem?.offsetWidth ?? 0;
956
+ if (itemWidth > 0) {
957
+ const index = Math.round(scrollLeft / (itemWidth + gap));
958
+ setScrollIndex(Math.min(index, totalPositions - 1));
959
+ }
960
+ }, [gap, isFullWidth, totalPositions]);
961
+ useEffect(() => {
962
+ if (disableScroll) return;
963
+ const container = scrollRef.current;
964
+ if (!container) return;
965
+ container.addEventListener("scroll", handleScroll);
966
+ return () => {
967
+ container.removeEventListener("scroll", handleScroll);
968
+ };
969
+ }, [disableScroll, handleScroll]);
970
+ useEffect(() => {
971
+ if (!disableScroll) return;
972
+ const container = scrollRef.current;
973
+ if (!container) return;
974
+ const preventScroll = (e) => {
975
+ e.preventDefault();
976
+ };
977
+ container.addEventListener("wheel", preventScroll, { passive: false });
978
+ return () => {
979
+ container.removeEventListener("wheel", preventScroll);
980
+ };
981
+ }, [disableScroll]);
982
+ const scrollTo = useCallback(
983
+ (index) => {
984
+ const container = scrollRef.current;
985
+ if (!container) return;
986
+ const firstItem = container.children[0];
987
+ const itemWidth = isFullWidth ? container.offsetWidth : firstItem?.offsetWidth ?? 0;
988
+ const isLastPosition = index === totalPositions - 1;
989
+ const scrollLeft = isLastPosition ? container.scrollWidth - container.offsetWidth : index * (itemWidth + gap);
990
+ container.scrollTo({
991
+ left: scrollLeft,
992
+ behavior: disableAnimation ? "auto" : "smooth"
993
+ });
994
+ },
995
+ [gap, isFullWidth, totalPositions, disableAnimation]
996
+ );
997
+ const scrollPrev = useCallback(() => {
998
+ if (currentIndex > 0) {
999
+ scrollTo(currentIndex - 1);
1000
+ }
1001
+ }, [currentIndex, scrollTo]);
1002
+ const scrollNext = useCallback(() => {
1003
+ if (currentIndex < totalPositions - 1) {
1004
+ scrollTo(currentIndex + 1);
1005
+ }
1006
+ }, [currentIndex, totalPositions, scrollTo]);
1007
+ const handlePrev = onPrev ?? scrollPrev;
1008
+ const handleNext = onNext ?? scrollNext;
1009
+ const handlePrevRef = useRef(handlePrev);
1010
+ const handleNextRef = useRef(handleNext);
1011
+ useEffect(() => {
1012
+ handlePrevRef.current = handlePrev;
1013
+ handleNextRef.current = handleNext;
1014
+ });
1015
+ useEffect(() => {
1016
+ const handleKeyDown = (e) => {
1017
+ if (e.key === "ArrowLeft") handlePrevRef.current();
1018
+ if (e.key === "ArrowRight") handleNextRef.current();
1019
+ };
1020
+ document.addEventListener("keydown", handleKeyDown);
1021
+ return () => {
1022
+ document.removeEventListener("keydown", handleKeyDown);
1023
+ };
1024
+ }, []);
1025
+ if (errorMessage) {
1026
+ return /* @__PURE__ */ jsx(CarouselError_default, { message: errorMessage });
1027
+ }
1028
+ const canScrollPrev = currentIndex > 0;
1029
+ const canScrollNext = currentIndex < totalPositions - 1;
1030
+ const showControls = totalPositions > 1;
1031
+ return /* @__PURE__ */ jsxs("div", { className: clsx15("ui:relative", isLightbox && "ui:h-full", className), children: [
1032
+ /* @__PURE__ */ jsx(
1033
+ "div",
1034
+ {
1035
+ ref: scrollRef,
1036
+ className: clsx15(
1037
+ "ui:flex ui:overflow-x-auto ui:scroll-smooth ui:scrollbar-none",
1038
+ isFullWidth && "ui:snap-x ui:snap-mandatory",
1039
+ isLightbox && "ui:h-full",
1040
+ rounded && "ui:rounded-lg ui:overflow-hidden"
1041
+ ),
1042
+ style: { gap: `${String(gap)}px` },
1043
+ children
1044
+ }
1045
+ ),
1046
+ showControls && showArrows && arrowPosition === "sides" && !isHero && !isLightbox && /* @__PURE__ */ jsx(
1047
+ CarouselNavigation_default,
1048
+ {
1049
+ onPrev: handlePrev,
1050
+ onNext: handleNext,
1051
+ canPrev: canScrollPrev,
1052
+ canNext: canScrollNext,
1053
+ size: "md",
1054
+ position: "sides"
1055
+ }
1056
+ ),
1057
+ showControls && isHero && (showPagination || showArrows) && /* @__PURE__ */ jsxs(
1058
+ "div",
1059
+ {
1060
+ className: clsx15(
1061
+ "ui:absolute ui:bottom-4 ui:left-1/2 ui:-translate-x-1/2 ui:w-full ui:z-10 ui:flex ui:items-end",
1062
+ heroControlsClassName
1063
+ ),
1064
+ children: [
1065
+ /* @__PURE__ */ jsx("div", { className: "ui:flex-1" }),
1066
+ showPagination && /* @__PURE__ */ jsx(
1067
+ CarouselPagination_default,
1068
+ {
1069
+ total: totalPositions,
1070
+ current: currentIndex,
1071
+ light: true
1072
+ }
1073
+ ),
1074
+ /* @__PURE__ */ jsx("div", { className: "ui:flex-1 ui:flex ui:justify-end", children: showArrows && /* @__PURE__ */ jsx(
1075
+ CarouselNavigation_default,
1076
+ {
1077
+ onPrev: handlePrev,
1078
+ onNext: handleNext,
1079
+ canPrev: canScrollPrev,
1080
+ canNext: canScrollNext,
1081
+ size: "sm"
1082
+ }
1083
+ ) })
1084
+ ]
1085
+ }
1086
+ ),
1087
+ showControls && !isHero && !isLightbox && (showArrows && arrowPosition === "bottom-right" || showPagination) ? /* @__PURE__ */ jsxs(
1088
+ "div",
1089
+ {
1090
+ className: clsx15(
1091
+ "ui:mt-4 ui:flex ui:items-center",
1092
+ arrowPosition === "bottom-right" ? "ui:justify-end ui:gap-4" : "ui:justify-center"
1093
+ ),
1094
+ children: [
1095
+ showPagination && /* @__PURE__ */ jsx(CarouselPagination_default, { total: totalPositions, current: currentIndex }),
1096
+ showArrows && arrowPosition === "bottom-right" && /* @__PURE__ */ jsx(
1097
+ CarouselNavigation_default,
1098
+ {
1099
+ onPrev: handlePrev,
1100
+ onNext: handleNext,
1101
+ canPrev: canScrollPrev,
1102
+ canNext: canScrollNext,
1103
+ size: "sm"
1104
+ }
1105
+ )
1106
+ ]
1107
+ }
1108
+ ) : null,
1109
+ showControls && isLightbox && /* @__PURE__ */ jsx("div", { className: "ui:absolute ui:top-4 ui:right-4 ui:z-10", children: /* @__PURE__ */ jsx(CarouselCounter_default, { current: currentIndex, total: totalPositions }) }),
1110
+ showControls && isLightbox && showArrows && /* @__PURE__ */ jsx(
1111
+ CarouselNavigation_default,
1112
+ {
1113
+ onPrev: handlePrev,
1114
+ onNext: handleNext,
1115
+ canPrev: canScrollPrev,
1116
+ canNext: canScrollNext,
1117
+ size: "md",
1118
+ position: "sides-inset",
1119
+ iconVariant: "ghost"
1120
+ }
1121
+ )
1122
+ ] });
1123
+ };
1124
+ var Carousel_default = Carousel;
1125
+ var CarouselItem = ({
1126
+ children,
1127
+ isHero = false,
1128
+ isLightbox = false,
1129
+ className,
1130
+ ...rest
1131
+ }) => {
1132
+ return /* @__PURE__ */ jsx(
1133
+ "div",
1134
+ {
1135
+ className: clsx15(
1136
+ "ui:flex-shrink-0",
1137
+ (isHero || isLightbox) && "ui:w-full ui:snap-center",
1138
+ isLightbox && "ui:flex ui:items-center ui:justify-center ui:h-full",
1139
+ className
1140
+ ),
1141
+ ...rest,
1142
+ children
1143
+ }
1144
+ );
1145
+ };
1146
+ var CarouselItem_default = CarouselItem;
1147
+ var CarouselLoading = ({
1148
+ count = 6,
1149
+ cardWidth = 150,
1150
+ cardHeight = 225,
1151
+ showTitle = true,
1152
+ showSubtitle = true,
1153
+ rounded = true
1154
+ }) => {
1155
+ return /* @__PURE__ */ jsx("div", { className: "ui:overflow-x-auto ui:flex ui:gap-4", children: Array.from({ length: count }).map((_, i) => /* @__PURE__ */ jsxs(
1156
+ "div",
1157
+ {
1158
+ style: { width: cardWidth, minWidth: cardWidth, maxWidth: cardWidth },
1159
+ children: [
1160
+ /* @__PURE__ */ jsx("div", { style: { width: cardWidth, height: cardHeight }, children: /* @__PURE__ */ jsx(
1161
+ Skeleton_default,
1162
+ {
1163
+ variant: "rectangle",
1164
+ width: "ui:w-full",
1165
+ height: "ui:h-full",
1166
+ rounded
1167
+ }
1168
+ ) }),
1169
+ (showTitle || showSubtitle) && /* @__PURE__ */ jsxs(
1170
+ "div",
1171
+ {
1172
+ style: {
1173
+ marginTop: 8,
1174
+ display: "flex",
1175
+ flexDirection: "column",
1176
+ gap: 8
1177
+ },
1178
+ children: [
1179
+ showTitle && /* @__PURE__ */ jsx("div", { style: { width: cardWidth, height: 16 }, children: /* @__PURE__ */ jsx(
1180
+ Skeleton_default,
1181
+ {
1182
+ variant: "line",
1183
+ width: "ui:w-full",
1184
+ height: "ui:h-full"
1185
+ }
1186
+ ) }),
1187
+ showSubtitle && /* @__PURE__ */ jsx("div", { style: { width: cardWidth * 0.75, height: 12 }, children: /* @__PURE__ */ jsx(
1188
+ Skeleton_default,
1189
+ {
1190
+ variant: "line",
1191
+ width: "ui:w-full",
1192
+ height: "ui:h-full"
1193
+ }
1194
+ ) })
1195
+ ]
1196
+ }
1197
+ )
1198
+ ]
1199
+ },
1200
+ i
1201
+ )) });
1202
+ };
1203
+ var CarouselLoading_default = CarouselLoading;
1204
+ var TabsContext = createContext(
1205
+ void 0
1206
+ );
1207
+ var useTabsContext = () => {
1208
+ const context = useContext(TabsContext);
1209
+ if (!context) {
1210
+ throw new Error("Tabs compound components must be used within <Tabs>");
1211
+ }
1212
+ return context;
1213
+ };
1214
+ var TabsListContext = createContext(
1215
+ void 0
1216
+ );
1217
+ var useTabsListContext = () => {
1218
+ const context = useContext(TabsListContext);
1219
+ if (!context) {
1220
+ throw new Error("TabsTrigger must be used within <Tabs.List>");
1221
+ }
1222
+ return context;
1223
+ };
1224
+ var TabsList = ({ className, children, ...rest }) => {
1225
+ const { variant } = useTabsContext();
1226
+ const triggersRef = useRef([]);
1227
+ const disabledRef = useRef(/* @__PURE__ */ new Set());
1228
+ const registerTrigger = (value, disabled) => {
1229
+ if (!triggersRef.current.includes(value)) {
1230
+ triggersRef.current.push(value);
1231
+ }
1232
+ if (disabled) {
1233
+ disabledRef.current.add(value);
1234
+ } else {
1235
+ disabledRef.current.delete(value);
1236
+ }
1237
+ };
1238
+ const unregisterTrigger = (value) => {
1239
+ triggersRef.current = triggersRef.current.filter((v) => v !== value);
1240
+ disabledRef.current.delete(value);
1241
+ };
1242
+ const getTriggers = () => triggersRef.current;
1243
+ const isDisabled = (value) => disabledRef.current.has(value);
1244
+ return /* @__PURE__ */ jsx(
1245
+ TabsListContext.Provider,
1246
+ {
1247
+ value: { registerTrigger, unregisterTrigger, getTriggers, isDisabled },
1248
+ children: /* @__PURE__ */ jsx(
1249
+ "div",
1250
+ {
1251
+ className: clsx15(
1252
+ "ui:flex ui:gap-1",
1253
+ variant === "underline" && "ui:border-b ui:border-border",
1254
+ variant === "pills" && "ui:[.media-section:nth-of-type(odd)_&]:bg-white ui:bg-muted ui:p-1 ui:rounded-lg ui:w-fit",
1255
+ className
1256
+ ),
1257
+ role: "tablist",
1258
+ ...rest,
1259
+ children
1260
+ }
1261
+ )
1262
+ }
1263
+ );
1264
+ };
1265
+ var TabsList_default = TabsList;
1266
+ var TabsPanel = ({ value, children, ...rest }) => {
1267
+ const { value: activeValue, prefix } = useTabsContext();
1268
+ const isActive = value === activeValue;
1269
+ const getTabId = (val) => prefix ? `tab-${prefix}-${val}` : `tab-${val}`;
1270
+ const getTabPanelId = (val) => prefix ? `tabpanel-${prefix}-${val}` : `tabpanel-${val}`;
1271
+ return /* @__PURE__ */ jsx(
1272
+ "div",
1273
+ {
1274
+ role: "tabpanel",
1275
+ id: getTabPanelId(value),
1276
+ "aria-labelledby": getTabId(value),
1277
+ hidden: !isActive,
1278
+ ...rest,
1279
+ className: clsx15("ui:mt-4", rest.className),
1280
+ children
1281
+ }
1282
+ );
1283
+ };
1284
+ var TabsPanel_default = TabsPanel;
1285
+ var TabsTrigger = ({
1286
+ value,
1287
+ icon,
1288
+ disabled,
1289
+ className,
1290
+ children,
1291
+ ...rest
1292
+ }) => {
1293
+ const {
1294
+ value: activeValue,
1295
+ onValueChange,
1296
+ variant,
1297
+ prefix
1298
+ } = useTabsContext();
1299
+ const { registerTrigger, unregisterTrigger, getTriggers, isDisabled } = useTabsListContext();
1300
+ const isActive = value === activeValue;
1301
+ const getTabId = (val) => prefix ? `tab-${prefix}-${val}` : `tab-${val}`;
1302
+ const getTabPanelId = (val) => prefix ? `tabpanel-${prefix}-${val}` : `tabpanel-${val}`;
1303
+ useEffect(() => {
1304
+ registerTrigger(value, disabled);
1305
+ return () => {
1306
+ unregisterTrigger(value);
1307
+ };
1308
+ }, [value, disabled, registerTrigger, unregisterTrigger]);
1309
+ const handleClick = () => {
1310
+ if (!disabled) {
1311
+ onValueChange(value);
1312
+ }
1313
+ };
1314
+ const findNextEnabledTab = (triggers, startIndex, direction) => {
1315
+ const length = triggers.length;
1316
+ let index = startIndex;
1317
+ for (let i = 0; i < length; i++) {
1318
+ index = (index + direction + length) % length;
1319
+ if (!isDisabled(triggers[index])) {
1320
+ return index;
1321
+ }
1322
+ }
1323
+ return startIndex;
1324
+ };
1325
+ const handleKeyDown = (event) => {
1326
+ if (disabled) return;
1327
+ const triggers = getTriggers();
1328
+ const currentIndex = triggers.indexOf(value);
1329
+ let newIndex = currentIndex;
1330
+ switch (event.key) {
1331
+ case "ArrowLeft":
1332
+ event.preventDefault();
1333
+ newIndex = findNextEnabledTab(triggers, currentIndex, -1);
1334
+ break;
1335
+ case "ArrowRight":
1336
+ event.preventDefault();
1337
+ newIndex = findNextEnabledTab(triggers, currentIndex, 1);
1338
+ break;
1339
+ case "Home":
1340
+ event.preventDefault();
1341
+ newIndex = 0;
1342
+ while (newIndex < triggers.length && isDisabled(triggers[newIndex])) {
1343
+ newIndex++;
1344
+ }
1345
+ break;
1346
+ case "End":
1347
+ event.preventDefault();
1348
+ newIndex = triggers.length - 1;
1349
+ while (newIndex >= 0 && isDisabled(triggers[newIndex])) {
1350
+ newIndex--;
1351
+ }
1352
+ break;
1353
+ default:
1354
+ return;
1355
+ }
1356
+ const newValue = triggers[newIndex];
1357
+ if (newValue && newValue !== value) {
1358
+ onValueChange(newValue);
1359
+ }
1360
+ };
1361
+ return /* @__PURE__ */ jsxs(
1362
+ "button",
1363
+ {
1364
+ type: "button",
1365
+ role: "tab",
1366
+ "aria-selected": isActive ? "true" : "false",
1367
+ "aria-controls": getTabPanelId(value),
1368
+ id: getTabId(value),
1369
+ tabIndex: isActive ? 0 : -1,
1370
+ disabled,
1371
+ className: clsx15(
1372
+ "ui:px-4 ui:py-2 ui:font-roboto ui:text-sm ui:font-medium",
1373
+ "ui:flex ui:items-center ui:gap-2",
1374
+ "ui:transition-colors ui:duration-200",
1375
+ "focus-visible:ui:outline-none focus-visible:ui:ring-2 focus-visible:ui:ring-ring focus-visible:ui:ring-offset-2",
1376
+ !disabled && "ui:cursor-pointer",
1377
+ variant === "underline" && [
1378
+ "ui:relative ui:border-b-2 ui:border-transparent ui:-mb-px",
1379
+ isActive ? "ui:text-primary ui:border-primary" : "ui:text-foreground hover:ui:text-foreground",
1380
+ disabled && "ui:text-muted-foreground/50 ui:cursor-not-allowed hover:ui:text-muted-foreground/50"
1381
+ ],
1382
+ variant === "pills" && [
1383
+ "ui:rounded-md",
1384
+ isActive ? "ui:bg-primary ui:shadow-sm" : "ui:text-foreground hover:ui:text-foreground",
1385
+ disabled && "ui:text-muted-foreground/50 ui:cursor-not-allowed hover:ui:text-muted-foreground/50"
1386
+ ],
1387
+ className
1388
+ ),
1389
+ onClick: handleClick,
1390
+ onKeyDown: handleKeyDown,
1391
+ ...rest,
1392
+ children: [
1393
+ icon && icon,
1394
+ children
1395
+ ]
1396
+ }
1397
+ );
1398
+ };
1399
+ var TabsTrigger_default = TabsTrigger;
1400
+ var Tabs = ({
1401
+ defaultValue = "",
1402
+ value,
1403
+ onValueChange,
1404
+ variant = "underline",
1405
+ prefix,
1406
+ className,
1407
+ children,
1408
+ ...rest
1409
+ }) => {
1410
+ const [internalValue, setInternalValue] = useState(defaultValue);
1411
+ const activeValue = value ?? internalValue;
1412
+ const isControlled = value !== void 0;
1413
+ const handleValueChange = (newValue) => {
1414
+ if (!isControlled) {
1415
+ setInternalValue(newValue);
1416
+ }
1417
+ onValueChange?.(newValue);
1418
+ };
1419
+ return /* @__PURE__ */ jsx(
1420
+ TabsContext.Provider,
1421
+ {
1422
+ value: {
1423
+ value: activeValue,
1424
+ onValueChange: handleValueChange,
1425
+ variant,
1426
+ prefix
1427
+ },
1428
+ children: /* @__PURE__ */ jsx("div", { className: clsx15("ui:w-full", className), ...rest, children })
1429
+ }
1430
+ );
1431
+ };
1432
+ Tabs.List = TabsList_default;
1433
+ Tabs.Trigger = TabsTrigger_default;
1434
+ Tabs.Panel = TabsPanel_default;
1435
+ var Tabs_default = Tabs;
1436
+ var Talent = ({
1437
+ name,
1438
+ role,
1439
+ imageSrc,
1440
+ variant = "vertical",
1441
+ size = "lg",
1442
+ className,
1443
+ ...rest
1444
+ }) => {
1445
+ return /* @__PURE__ */ jsxs(
1446
+ "div",
1447
+ {
1448
+ className: clsx15(
1449
+ "ui:flex ui:items-center",
1450
+ {
1451
+ "ui:flex-col ui:text-center ui:gap-3": variant === "vertical",
1452
+ "ui:flex-row ui:gap-4": variant === "horizontal"
1453
+ },
1454
+ className
1455
+ ),
1456
+ "data-testid": "talent",
1457
+ ...rest,
1458
+ children: [
1459
+ /* @__PURE__ */ jsx(
1460
+ Avatar_default,
1461
+ {
1462
+ testId: "avatar",
1463
+ src: imageSrc,
1464
+ alt: name ?? "Unknown",
1465
+ size
1466
+ }
1467
+ ),
1468
+ /* @__PURE__ */ jsxs(
1469
+ "div",
1470
+ {
1471
+ className: clsx15({ "ui:flex ui:flex-col": variant === "horizontal" }),
1472
+ children: [
1473
+ /* @__PURE__ */ jsx(
1474
+ Typography_default,
1475
+ {
1476
+ variant: "body",
1477
+ className: "ui:font-semibold ui:text-foreground",
1478
+ children: name ?? "Unknown"
1479
+ }
1480
+ ),
1481
+ /* @__PURE__ */ jsx(
1482
+ Typography_default,
1483
+ {
1484
+ variant: "caption",
1485
+ className: "ui:text-muted-foreground ui:[.media-section:nth-of-type(odd)_&]:text-badge-foreground",
1486
+ children: role ?? "N/A"
1487
+ }
1488
+ )
1489
+ ]
1490
+ }
1491
+ )
1492
+ ]
1493
+ }
1494
+ );
1495
+ };
1496
+ var Talent_default = Talent;
1497
+ var Spinner = ({ className }) => {
1498
+ return /* @__PURE__ */ jsx(
1499
+ "div",
1500
+ {
1501
+ role: "status",
1502
+ "aria-label": "Loading",
1503
+ className: clsx15(
1504
+ "ui:size-12 ui:rounded-full ui:border-4 ui:border-white/20 ui:border-t-white ui:animate-spin",
1505
+ className
1506
+ )
1507
+ }
1508
+ );
1509
+ };
1510
+ var Spinner_default = Spinner;
1511
+
1512
+ export { Avatar_default as Avatar, Badge_default as Badge, Carousel_default as Carousel, CarouselCounter_default as CarouselCounter, CarouselItem_default as CarouselItem, CarouselLoading_default as CarouselLoading, CarouselNavigation_default as CarouselNavigation, CarouselPagination_default as CarouselPagination, HeroImage_default as HeroImage, IconButton_default as IconButton, Image_default as Image, Modal_default as Modal, MovieCard_default as MovieCard, Rating_default as Rating, Skeleton_default as Skeleton, Spinner_default as Spinner, Tabs_default as Tabs, Talent_default as Talent, TrailerCard_default as TrailerCard, Typography_default as Typography };
1513
+ //# sourceMappingURL=index.js.map
1514
+ //# sourceMappingURL=index.js.map