@vite-mf-monorepo/ui 0.3.0 → 0.4.1
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/{MovieCard.utils-CpNUUs8O.d.ts → MovieCard.utils-D8i4d7qA.d.ts} +2 -0
- package/dist/{chunk-JDBRVX5O.js → chunk-2EQIYKZF.js} +5 -188
- package/dist/chunk-2EQIYKZF.js.map +1 -0
- package/dist/chunk-3GCDFSJB.js +228 -0
- package/dist/chunk-3GCDFSJB.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/next/index.d.ts +16 -4
- package/dist/next/index.js +169 -21
- package/dist/next/index.js.map +1 -1
- package/dist/react-router/index.d.ts +1 -1
- package/dist/react-router/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-5NW3IDX2.js +0 -42
- package/dist/chunk-5NW3IDX2.js.map +0 -1
- package/dist/chunk-JDBRVX5O.js.map +0 -1
|
@@ -45,6 +45,8 @@ interface MovieCardBaseProps {
|
|
|
45
45
|
className?: string;
|
|
46
46
|
/** Image loading strategy ('lazy' | 'eager'). Default: 'lazy' */
|
|
47
47
|
imageLoading?: ImageLoading;
|
|
48
|
+
/** Base64 blur data URL for next/image placeholder="blur" */
|
|
49
|
+
blurDataURL?: string;
|
|
48
50
|
}
|
|
49
51
|
interface MovieCardAsCard extends MovieCardBaseProps {
|
|
50
52
|
as?: 'card' | undefined;
|
|
@@ -1,126 +1,8 @@
|
|
|
1
|
-
import { Card_default } from './chunk-JI3OVXCK.js';
|
|
2
1
|
import { Icon_default } from './chunk-JHRISQQJ.js';
|
|
3
|
-
import { getBlurDataUrl } from '@vite-mf-monorepo/shared';
|
|
4
2
|
import clsx from 'clsx';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
import { createElement } from 'react';
|
|
7
5
|
|
|
8
|
-
function Image({
|
|
9
|
-
src,
|
|
10
|
-
alt,
|
|
11
|
-
blurDataUrl,
|
|
12
|
-
autoBlur = false,
|
|
13
|
-
blurSize = 16,
|
|
14
|
-
blurQuality = 0.3,
|
|
15
|
-
aspectRatio = "2/3",
|
|
16
|
-
fallback,
|
|
17
|
-
className,
|
|
18
|
-
onLoad,
|
|
19
|
-
onError,
|
|
20
|
-
loading = "eager",
|
|
21
|
-
...rest
|
|
22
|
-
}) {
|
|
23
|
-
const imgRef = useRef(null);
|
|
24
|
-
const containerRef = useRef(null);
|
|
25
|
-
const [state, setState] = useState("loading");
|
|
26
|
-
const [generatedBlur, setGeneratedBlur] = useState(
|
|
27
|
-
void 0
|
|
28
|
-
);
|
|
29
|
-
const [blurReady, setBlurReady] = useState(!autoBlur || !!blurDataUrl);
|
|
30
|
-
const [isVisible, setIsVisible] = useState(loading === "eager");
|
|
31
|
-
const effectiveBlur = blurDataUrl ?? generatedBlur;
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
if (loading === "eager" || !containerRef.current) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
const observer = new IntersectionObserver(
|
|
37
|
-
([entry]) => {
|
|
38
|
-
if (entry.isIntersecting) {
|
|
39
|
-
setIsVisible(true);
|
|
40
|
-
observer.unobserve(entry.target);
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
{ rootMargin: "50px" }
|
|
44
|
-
);
|
|
45
|
-
observer.observe(containerRef.current);
|
|
46
|
-
return () => {
|
|
47
|
-
observer.disconnect();
|
|
48
|
-
};
|
|
49
|
-
}, [loading]);
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
if (!autoBlur || blurDataUrl) {
|
|
52
|
-
setBlurReady(true);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
setBlurReady(false);
|
|
56
|
-
getBlurDataUrl(src, blurSize, blurQuality).then((base64) => {
|
|
57
|
-
setGeneratedBlur(base64);
|
|
58
|
-
setBlurReady(true);
|
|
59
|
-
}).catch(() => {
|
|
60
|
-
setBlurReady(true);
|
|
61
|
-
});
|
|
62
|
-
}, [autoBlur, src, blurDataUrl, blurSize, blurQuality]);
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
setState("loading");
|
|
65
|
-
setGeneratedBlur(void 0);
|
|
66
|
-
if (imgRef.current?.complete && imgRef.current.naturalHeight !== 0) {
|
|
67
|
-
setState("loaded");
|
|
68
|
-
}
|
|
69
|
-
}, [src]);
|
|
70
|
-
const handleLoad = useCallback(() => {
|
|
71
|
-
setState("loaded");
|
|
72
|
-
onLoad?.();
|
|
73
|
-
}, [onLoad]);
|
|
74
|
-
const handleError = useCallback(() => {
|
|
75
|
-
setState("error");
|
|
76
|
-
onError?.();
|
|
77
|
-
}, [onError]);
|
|
78
|
-
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(
|
|
79
|
-
Icon_default,
|
|
80
|
-
{
|
|
81
|
-
name: "Photo",
|
|
82
|
-
size: 48,
|
|
83
|
-
className: "ui:text-muted-foreground",
|
|
84
|
-
"aria-hidden": "true"
|
|
85
|
-
}
|
|
86
|
-
) });
|
|
87
|
-
return /* @__PURE__ */ jsx(
|
|
88
|
-
"div",
|
|
89
|
-
{
|
|
90
|
-
ref: containerRef,
|
|
91
|
-
className: clsx("ui:relative ui:overflow-hidden ui:bg-muted", className),
|
|
92
|
-
style: aspectRatio ? { aspectRatio } : void 0,
|
|
93
|
-
"data-state": state,
|
|
94
|
-
children: state === "error" ? fallback ?? defaultFallback : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
95
|
-
effectiveBlur && state === "loading" && /* @__PURE__ */ jsx(
|
|
96
|
-
"img",
|
|
97
|
-
{
|
|
98
|
-
src: effectiveBlur,
|
|
99
|
-
alt: "",
|
|
100
|
-
"aria-hidden": "true",
|
|
101
|
-
className: "ui:absolute ui:inset-0 ui:h-full ui:w-full ui:scale-105 ui:object-cover"
|
|
102
|
-
}
|
|
103
|
-
),
|
|
104
|
-
blurReady && isVisible && /* @__PURE__ */ jsx(
|
|
105
|
-
"img",
|
|
106
|
-
{
|
|
107
|
-
ref: imgRef,
|
|
108
|
-
src,
|
|
109
|
-
alt,
|
|
110
|
-
onLoad: handleLoad,
|
|
111
|
-
onError: handleError,
|
|
112
|
-
className: clsx(
|
|
113
|
-
"ui:absolute ui:inset-0 ui:h-full ui:w-full ui:object-cover ui:transition-opacity ui:duration-300",
|
|
114
|
-
state === "loaded" ? "ui:opacity-100" : "ui:opacity-0"
|
|
115
|
-
),
|
|
116
|
-
...rest
|
|
117
|
-
}
|
|
118
|
-
)
|
|
119
|
-
] })
|
|
120
|
-
}
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
var Image_default = Image;
|
|
124
6
|
var circleSizeMap = {
|
|
125
7
|
sm: { size: 32, stroke: 3, fontSize: "ui:text-xs" },
|
|
126
8
|
md: { size: 48, stroke: 4, fontSize: "ui:text-sm" },
|
|
@@ -326,72 +208,7 @@ function getMovieCardClasses(isInteractive, className) {
|
|
|
326
208
|
function getMovieCardLinkClasses() {
|
|
327
209
|
return "ui:block ui:no-underline ui:text-inherit";
|
|
328
210
|
}
|
|
329
|
-
function MovieCardContent({
|
|
330
|
-
id,
|
|
331
|
-
title,
|
|
332
|
-
posterUrl,
|
|
333
|
-
voteAverage,
|
|
334
|
-
year,
|
|
335
|
-
className,
|
|
336
|
-
imageLoading = "lazy",
|
|
337
|
-
isInteractive,
|
|
338
|
-
onClick
|
|
339
|
-
}) {
|
|
340
|
-
return /* @__PURE__ */ jsxs(
|
|
341
|
-
Card_default,
|
|
342
|
-
{
|
|
343
|
-
variant: "ghost",
|
|
344
|
-
className: getMovieCardClasses(isInteractive, className),
|
|
345
|
-
onClick,
|
|
346
|
-
"data-testid": `movie-card-${String(id)}`,
|
|
347
|
-
children: [
|
|
348
|
-
/* @__PURE__ */ jsxs("div", { className: "ui:relative ui:aspect-[2/3] ui:w-full ui:overflow-hidden ui:rounded-md ui:bg-gray-200", children: [
|
|
349
|
-
/* @__PURE__ */ jsx(
|
|
350
|
-
Image_default,
|
|
351
|
-
{
|
|
352
|
-
src: posterUrl,
|
|
353
|
-
alt: title,
|
|
354
|
-
loading: imageLoading,
|
|
355
|
-
aspectRatio: void 0,
|
|
356
|
-
className: "ui:h-full ui:w-full ui:object-cover"
|
|
357
|
-
}
|
|
358
|
-
),
|
|
359
|
-
/* @__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(
|
|
360
|
-
Rating_default,
|
|
361
|
-
{
|
|
362
|
-
value: voteAverage,
|
|
363
|
-
size: "sm",
|
|
364
|
-
variant: "circle",
|
|
365
|
-
trackClassName: "ui:text-gray-200",
|
|
366
|
-
className: "ui:drop-shadow"
|
|
367
|
-
}
|
|
368
|
-
) })
|
|
369
|
-
] }),
|
|
370
|
-
/* @__PURE__ */ jsxs("div", { className: "ui:mt-2 ui:flex ui:flex-col ui:gap-0.5 ui:px-1", children: [
|
|
371
|
-
/* @__PURE__ */ jsx(
|
|
372
|
-
Typography_default,
|
|
373
|
-
{
|
|
374
|
-
variant: "label",
|
|
375
|
-
as: "h3",
|
|
376
|
-
className: "ui:line-clamp-2",
|
|
377
|
-
title,
|
|
378
|
-
children: title
|
|
379
|
-
}
|
|
380
|
-
),
|
|
381
|
-
year && /* @__PURE__ */ jsx(
|
|
382
|
-
Typography_default,
|
|
383
|
-
{
|
|
384
|
-
variant: "caption-xs",
|
|
385
|
-
className: "ui:[.media-section:nth-of-type(odd)_&]:text-badge-foreground",
|
|
386
|
-
children: year
|
|
387
|
-
}
|
|
388
|
-
)
|
|
389
|
-
] })
|
|
390
|
-
]
|
|
391
|
-
}
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
211
|
|
|
395
|
-
export {
|
|
396
|
-
//# sourceMappingURL=chunk-
|
|
397
|
-
//# sourceMappingURL=chunk-
|
|
212
|
+
export { Rating_default, Typography_default, getMovieCardClasses, getMovieCardLinkClasses };
|
|
213
|
+
//# sourceMappingURL=chunk-2EQIYKZF.js.map
|
|
214
|
+
//# sourceMappingURL=chunk-2EQIYKZF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/Rating/CircleRating.tsx","../src/Rating/StarsRating.tsx","../src/Rating/Rating.tsx","../src/Typography/Typography.tsx","../src/MovieCard/MovieCard.utils.ts"],"names":["jsxs","jsx","clsx"],"mappings":";;;;;AAIA,IAAM,aAAA,GAGF;AAAA,EACF,IAAI,EAAE,IAAA,EAAM,IAAI,MAAA,EAAQ,CAAA,EAAG,UAAU,YAAA,EAAa;AAAA,EAClD,IAAI,EAAE,IAAA,EAAM,IAAI,MAAA,EAAQ,CAAA,EAAG,UAAU,YAAA,EAAa;AAAA,EAClD,IAAI,EAAE,IAAA,EAAM,IAAI,MAAA,EAAQ,CAAA,EAAG,UAAU,cAAA;AACvC,CAAA;AAEA,IAAM,aAAA,GAAgB,CAAC,OAAA,KAA4B;AACjD,EAAA,IAAI,OAAA,IAAW,IAAI,OAAO,mBAAA;AAC1B,EAAA,IAAI,OAAA,IAAW,IAAI,OAAO,mBAAA;AAC1B,EAAA,OAAO,iBAAA;AACT,CAAA;AAiBA,SAAS,YAAA,CAAa;AAAA,EACpB,OAAA;AAAA,EACA,IAAA;AAAA,EACA,SAAA;AAAA,EACA,KAAA;AAAA,EACA,GAAA;AAAA,EACA;AACF,CAAA,EAAgC;AAC9B,EAAA,MAAM,EAAE,IAAA,EAAM,OAAA,EAAS,QAAQ,QAAA,EAAS,GAAI,cAAc,IAAI,CAAA;AAC9D,EAAA,MAAM,MAAA,GAAA,CAAU,UAAU,MAAA,IAAU,CAAA;AACpC,EAAA,MAAM,aAAA,GAAgB,CAAA,GAAI,IAAA,CAAK,EAAA,GAAK,MAAA;AACpC,EAAA,MAAM,MAAA,GAAS,aAAA,GAAiB,OAAA,GAAU,GAAA,GAAO,aAAA;AAEjD,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,8DAAA,EACb,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAC,SAAI,KAAA,EAAO,OAAA,EAAS,MAAA,EAAQ,OAAA,EAAS,WAAU,eAAA,EAC9C,QAAA,EAAA;AAAA,sBAAA,GAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,IAAI,OAAA,GAAU,CAAA;AAAA,UACd,IAAI,OAAA,GAAU,CAAA;AAAA,UACd,CAAA,EAAG,MAAA;AAAA,UACH,IAAA,EAAK,MAAA;AAAA,UACL,MAAA,EAAO,cAAA;AAAA,UACP,WAAA,EAAa,MAAA;AAAA,UACb,WAAW,cAAA,IAAkB;AAAA;AAAA,OAC/B;AAAA,sBACA,GAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,IAAI,OAAA,GAAU,CAAA;AAAA,UACd,IAAI,OAAA,GAAU,CAAA;AAAA,UACd,CAAA,EAAG,MAAA;AAAA,UACH,IAAA,EAAK,MAAA;AAAA,UACL,MAAA,EAAO,cAAA;AAAA,UACP,WAAA,EAAa,MAAA;AAAA,UACb,aAAA,EAAc,OAAA;AAAA,UACd,eAAA,EAAiB,aAAA;AAAA,UACjB,gBAAA,EAAkB,MAAA;AAAA,UAClB,SAAA,EAAW,IAAA;AAAA,YACT,mCAAA;AAAA,YACA,cAAc,OAAO;AAAA;AACvB;AAAA;AACF,KAAA,EACF,CAAA;AAAA,IACC,SAAA,oBACC,GAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAW,IAAA;AAAA,UACT,0BAAA;AAAA,UACA,QAAA;AAAA,UACA,cAAc,OAAO;AAAA,SACvB;AAAA,QAEC,QAAA,EAAA,GAAA,KAAQ,MAAM,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAC;AAAA;AAAA;AACtD,GAAA,EAEJ,CAAA;AAEJ;AAEA,IAAO,oBAAA,GAAQ,YAAA;ACnFf,IAAM,YAAA,GAA6C;AAAA,EACjD,EAAA,EAAI,EAAA;AAAA,EACJ,EAAA,EAAI,EAAA;AAAA,EACJ,EAAA,EAAI;AACN,CAAA;AAeA,SAAS,WAAA,CAAY;AAAA,EACnB,OAAA;AAAA,EACA,IAAA;AAAA,EACA,SAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAAA,EAA+B;AAC7B,EAAA,MAAM,QAAA,GAAW,aAAa,IAAI,CAAA;AAElC,EAAA,uBACEA,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,yCAAA,EACb,QAAA,EAAA;AAAA,oBAAAA,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,aAAA,EAEb,QAAA,EAAA;AAAA,sBAAAC,GAAAA,CAAC,SAAI,SAAA,EAAU,0BAAA,EACZ,WAAC,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA,CAAE,IAAI,CAAC,CAAA,qBACpBA,GAAAA,CAAC,YAAA,EAAA,EAAa,IAAA,EAAK,QAAO,IAAA,EAAM,QAAA,EAAA,EAArB,CAA+B,CAC3C,CAAA,EACH,CAAA;AAAA,sBAEAA,GAAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,SAAA,EAAU,mDAAA;AAAA,UACV,KAAA,EAAO,EAAE,QAAA,EAAU,CAAA,QAAA,EAAW,OAAO,GAAA,GAAM,OAAO,CAAC,CAAA,MAAA,CAAA,EAAS;AAAA,UAE3D,WAAC,CAAA,EAAG,CAAA,EAAG,GAAG,CAAA,EAAG,CAAC,EAAE,GAAA,CAAI,CAAC,CAAA,qBACpBA,IAAC,YAAA,EAAA,EAAa,IAAA,EAAK,QAAO,IAAA,EAAM,QAAA,EAAA,EAArB,CAA+B,CAC3C;AAAA;AAAA;AACH,KAAA,EACF,CAAA;AAAA,IACC,6BACCA,GAAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAWC,IAAAA;AAAA,UACT,yCAAA;AAAA,UACA,SAAS,IAAA,IAAQ,YAAA;AAAA,UACjB,SAAS,IAAA,IAAQ,YAAA;AAAA,UACjB,SAAS,IAAA,IAAQ;AAAA,SACnB;AAAA,QAEC,QAAA,EAAA,GAAA,KAAQ,OAAO,KAAA,GAAQ,EAAA,EAAI,QAAQ,CAAC,CAAA,GAAI,KAAA,CAAM,OAAA,CAAQ,CAAC;AAAA;AAAA;AAC1D,GAAA,EAEJ,CAAA;AAEJ;AAEA,IAAO,mBAAA,GAAQ,WAAA;AC7Cf,SAAS,MAAA,CAAO;AAAA,EACd,KAAA;AAAA,EACA,GAAA,GAAM,EAAA;AAAA,EACN,OAAA,GAAU,QAAA;AAAA,EACV,IAAA,GAAO,IAAA;AAAA,EACP,SAAA,GAAY,IAAA;AAAA,EACZ,cAAA;AAAA,EACA;AACF,CAAA,EAA0B;AACxB,EAAA,MAAM,YAAA,GAAe,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,KAAA,EAAO,GAAG,CAAC,CAAA;AACrD,EAAA,MAAM,OAAA,GAAW,eAAe,GAAA,GAAO,GAAA;AAEvC,EAAA,uBACED,GAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAWC,IAAAA,CAAK,kBAAkB,SAAS,CAAA,EAC7C,QAAA,EAAA,OAAA,KAAY,QAAA,mBACXD,GAAAA;AAAA,IAAC,oBAAA;AAAA,IAAA;AAAA,MACC,OAAA;AAAA,MACA,IAAA;AAAA,MACA,SAAA;AAAA,MACA,KAAA,EAAO,YAAA;AAAA,MACP,GAAA;AAAA,MACA;AAAA;AAAA,sBAGFA,GAAAA;AAAA,IAAC,mBAAA;AAAA,IAAA;AAAA,MACC,OAAA;AAAA,MACA,IAAA;AAAA,MACA,SAAA;AAAA,MACA,KAAA,EAAO,YAAA;AAAA,MACP;AAAA;AAAA,GACF,EAEJ,CAAA;AAEJ;AAEA,IAAO,cAAA,GAAQ;AC5Bf,IAAM,aAAA,GAAmD;AAAA,EACvD,EAAA,EAAI,yHAAA;AAAA,EACJ,EAAA,EAAI,wHAAA;AAAA,EACJ,EAAA,EAAI,4HAAA;AAAA,EACJ,EAAA,EAAI,2HAAA;AAAA,EACJ,EAAA,EAAI,6GAAA;AAAA,EACJ,EAAA,EAAI,6GAAA;AAAA,EACJ,IAAA,EAAM,8FAAA;AAAA,EACN,SAAA,EACE,8EAAA;AAAA,EACF,SAAA,EACE,8FAAA;AAAA,EACF,IAAA,EAAM,kHAAA;AAAA,EACN,OAAA,EAAS,iEAAA;AAAA,EACT,YAAA,EAAc,mDAAA;AAAA,EACd,KAAA,EACE,0EAAA;AAAA,EACF,KAAA,EAAO,iEAAA;AAAA,EACP,UAAA,EACE;AACJ,CAAA;AAEA,IAAM,YAAA,GAAuD;AAAA,EAC3D,EAAA,EAAI,IAAA;AAAA,EACJ,EAAA,EAAI,IAAA;AAAA,EACJ,EAAA,EAAI,IAAA;AAAA,EACJ,EAAA,EAAI,IAAA;AAAA,EACJ,EAAA,EAAI,IAAA;AAAA,EACJ,EAAA,EAAI,IAAA;AAAA,EACJ,IAAA,EAAM,GAAA;AAAA,EACN,SAAA,EAAW,GAAA;AAAA,EACX,SAAA,EAAW,GAAA;AAAA,EACX,IAAA,EAAM,GAAA;AAAA,EACN,OAAA,EAAS,MAAA;AAAA,EACT,YAAA,EAAc,MAAA;AAAA,EACd,KAAA,EAAO,OAAA;AAAA,EACP,KAAA,EAAO,GAAA;AAAA,EACP,UAAA,EAAY;AACd,CAAA;AAEA,SAAS,UAAA,CAAW;AAAA,EAClB,OAAA;AAAA,EACA,EAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,GAAG;AACL,CAAA,EAA8B;AAC5B,EAAA,MAAM,SAAA,GAAY,EAAA,IAAM,YAAA,CAAa,OAAO,CAAA;AAE5C,EAAA,OAAO,aAAA;AAAA,IACL,SAAA;AAAA,IACA,EAAE,WAAWC,IAAAA,CAAK,aAAA,CAAc,OAAO,CAAA,EAAG,SAAS,CAAA,EAAG,GAAG,IAAA,EAAK;AAAA,IAC9D;AAAA,GACF;AACF;AAEA,IAAO,kBAAA,GAAQ;ACxDR,SAAS,mBAAA,CACd,eACA,SAAA,EACA;AACA,EAAA,OAAOA,IAAAA;AAAA,IACL,6DAAA;AAAA,IACA,aAAA,IAAiB;AAAA,MACf,mBAAA;AAAA,MACA,yCAAA;AAAA,MACA;AAAA,KACF;AAAA,IACA;AAAA,GACF;AACF;AAEO,SAAS,uBAAA,GAA0B;AACxC,EAAA,OAAO,0CAAA;AACT","file":"chunk-2EQIYKZF.js","sourcesContent":["import clsx from 'clsx'\n\nimport type { RatingSize } from './Rating'\n\nconst circleSizeMap: Record<\n RatingSize,\n { size: number; stroke: number; fontSize: string }\n> = {\n sm: { size: 32, stroke: 3, fontSize: 'ui:text-xs' },\n md: { size: 48, stroke: 4, fontSize: 'ui:text-sm' },\n lg: { size: 64, stroke: 5, fontSize: 'ui:text-base' },\n}\n\nconst getColorClass = (percent: number): string => {\n if (percent >= 70) return 'ui:text-green-600'\n if (percent >= 40) return 'ui:text-amber-600'\n return 'ui:text-red-500'\n}\n\nexport interface CircleRatingProps {\n /** Percentage value (0-100) for the progress ring */\n percent: number\n /** Size of the rating circle */\n size: RatingSize\n /** Whether to display the numeric value in the center */\n showValue: boolean\n /** The actual rating value (used for display) */\n value: number\n /** Maximum rating value (used for display formatting) */\n max: number\n /** Custom class for the background track circle */\n trackClassName?: string\n}\n\nfunction CircleRating({\n percent,\n size,\n showValue,\n value,\n max,\n trackClassName,\n}: Readonly<CircleRatingProps>) {\n const { size: svgSize, stroke, fontSize } = circleSizeMap[size]\n const radius = (svgSize - stroke) / 2\n const circumference = 2 * Math.PI * radius\n const offset = circumference - (percent / 100) * circumference\n\n return (\n <div className=\"ui:relative ui:inline-flex ui:items-center ui:justify-center\">\n <svg width={svgSize} height={svgSize} className=\"ui:-rotate-90\">\n <circle\n cx={svgSize / 2}\n cy={svgSize / 2}\n r={radius}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={stroke}\n className={trackClassName ?? 'ui:text-gray-200'}\n />\n <circle\n cx={svgSize / 2}\n cy={svgSize / 2}\n r={radius}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={stroke}\n strokeLinecap=\"round\"\n strokeDasharray={circumference}\n strokeDashoffset={offset}\n className={clsx(\n 'ui:transition-all ui:duration-500',\n getColorClass(percent)\n )}\n />\n </svg>\n {showValue && (\n <span\n className={clsx(\n 'ui:absolute ui:font-bold',\n fontSize,\n getColorClass(percent)\n )}\n >\n {max === 100 ? Math.round(percent) : value.toFixed(1)}\n </span>\n )}\n </div>\n )\n}\n\nexport default CircleRating\n","import clsx from 'clsx'\n\nimport { Icon } from '../Icon'\n\nimport type { IconSize } from '../Icon'\nimport type { RatingSize } from './Rating'\n\nconst starsSizeMap: Record<RatingSize, IconSize> = {\n sm: 16,\n md: 20,\n lg: 24,\n}\n\nexport interface StarsRatingProps {\n /** Percentage value (0-100) for the star fill */\n percent: number\n /** Size of the stars */\n size: RatingSize\n /** Whether to display the numeric value next to stars */\n showValue: boolean\n /** The actual rating value (used for display) */\n value: number\n /** Maximum rating value (used for display formatting) */\n max: number\n}\n\nfunction StarsRating({\n percent,\n size,\n showValue,\n value,\n max,\n}: Readonly<StarsRatingProps>) {\n const iconSize = starsSizeMap[size]\n\n return (\n <div className=\"ui:inline-flex ui:items-center ui:gap-1\">\n <div className=\"ui:relative\">\n {/* Empty stars */}\n <div className=\"ui:flex ui:text-gray-300\">\n {[1, 2, 3, 4, 5].map((i) => (\n <Icon key={i} name=\"Star\" size={iconSize} />\n ))}\n </div>\n {/* Filled stars with clip */}\n <div\n className=\"ui:absolute ui:inset-0 ui:flex ui:text-yellow-400\"\n style={{ clipPath: `inset(0 ${String(100 - percent)}% 0 0)` }}\n >\n {[1, 2, 3, 4, 5].map((i) => (\n <Icon key={i} name=\"Star\" size={iconSize} />\n ))}\n </div>\n </div>\n {showValue && (\n <span\n className={clsx(\n 'ui:font-medium ui:text-muted-foreground',\n size === 'sm' && 'ui:text-xs',\n size === 'md' && 'ui:text-sm',\n size === 'lg' && 'ui:text-base'\n )}\n >\n {max === 100 ? (value / 10).toFixed(1) : value.toFixed(1)}\n </span>\n )}\n </div>\n )\n}\n\nexport default StarsRating\n","import clsx from 'clsx'\n\nimport CircleRating from './CircleRating'\nimport StarsRating from './StarsRating'\n\nexport type RatingVariant = 'circle' | 'stars'\nexport type RatingSize = 'sm' | 'md' | 'lg'\n\nexport interface RatingProps {\n /** Rating value (0-10 by default, or 0-100 if max=100) */\n value: number\n /** Maximum value (default: 10 for TMDB scores) */\n max?: number\n /** Visual variant */\n variant?: RatingVariant\n /** Size of the rating */\n size?: RatingSize\n /** Show the numeric value */\n showValue?: boolean\n /** Custom class for the background track circle (circle variant only) */\n trackClassName?: string\n /** Additional class name */\n className?: string\n}\n\nfunction Rating({\n value,\n max = 10,\n variant = 'circle',\n size = 'md',\n showValue = true,\n trackClassName,\n className,\n}: Readonly<RatingProps>) {\n const clampedValue = Math.max(0, Math.min(value, max))\n const percent = (clampedValue / max) * 100\n\n return (\n <div className={clsx('ui:inline-flex', className)}>\n {variant === 'circle' ? (\n <CircleRating\n percent={percent}\n size={size}\n showValue={showValue}\n value={clampedValue}\n max={max}\n trackClassName={trackClassName}\n />\n ) : (\n <StarsRating\n percent={percent}\n size={size}\n showValue={showValue}\n value={clampedValue}\n max={max}\n />\n )}\n </div>\n )\n}\n\nexport default Rating\n","import clsx from 'clsx'\nimport { createElement } from 'react'\n\nimport type { ElementType, HTMLAttributes, ReactNode } from 'react'\n\nexport type TypographyVariant =\n | 'h1'\n | 'h2'\n | 'h3'\n | 'h4'\n | 'h5'\n | 'h6'\n | 'body'\n | 'body-sm'\n | 'body-lg'\n | 'lead'\n | 'caption'\n | 'caption-xs'\n | 'label'\n | 'muted'\n | 'blockquote'\n\nexport interface TypographyProps extends HTMLAttributes<HTMLElement> {\n /** Visual style variant */\n variant: TypographyVariant\n /** Override semantic HTML tag */\n as?: ElementType\n /** Additional CSS classes */\n className?: string\n /** Content */\n children: ReactNode\n}\n\nconst variantStyles: Record<TypographyVariant, string> = {\n 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',\n 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',\n 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',\n 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',\n h5: 'ui:font-roboto ui:text-sm ui:sm:text-base ui:md:text-lg ui:font-medium ui:leading-normal ui:text-foreground',\n h6: 'ui:font-roboto ui:text-xs ui:sm:text-sm ui:md:text-base ui:font-medium ui:leading-normal ui:text-foreground',\n body: 'ui:font-inter ui:text-xs ui:sm:text-sm ui:md:text-base ui:leading-relaxed ui:text-foreground',\n 'body-sm':\n 'ui:font-inter ui:text-xs ui:sm:text-sm ui:leading-relaxed ui:text-foreground',\n 'body-lg':\n 'ui:font-inter ui:text-sm ui:sm:text-base ui:md:text-lg ui:leading-relaxed ui:text-foreground',\n 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',\n caption: 'ui:font-inter ui:text-xs ui:sm:text-sm ui:text-muted-foreground',\n 'caption-xs': 'ui:font-inter ui:text-xs ui:text-muted-foreground',\n label:\n 'ui:font-inter ui:text-xs ui:sm:text-sm ui:font-medium ui:text-foreground',\n muted: 'ui:font-inter ui:text-xs ui:sm:text-sm ui:text-muted-foreground',\n blockquote:\n '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',\n}\n\nconst variantToTag: Record<TypographyVariant, ElementType> = {\n h1: 'h1',\n h2: 'h2',\n h3: 'h3',\n h4: 'h4',\n h5: 'h5',\n h6: 'h6',\n body: 'p',\n 'body-sm': 'p',\n 'body-lg': 'p',\n lead: 'p',\n caption: 'span',\n 'caption-xs': 'span',\n label: 'label',\n muted: 'p',\n blockquote: 'blockquote',\n}\n\nfunction Typography({\n variant,\n as,\n className,\n children,\n ...rest\n}: Readonly<TypographyProps>) {\n const Component = as ?? variantToTag[variant]\n\n return createElement(\n Component,\n { className: clsx(variantStyles[variant], className), ...rest },\n children\n )\n}\n\nexport default Typography\n","import clsx from 'clsx'\n\nimport type { ImageLoading } from '../Image'\n\nexport interface MovieCardBaseProps {\n /** Media ID */\n id: number\n /** Media title */\n title: string\n /** Poster URL (full URL or TMDB path) */\n posterUrl: string\n /** Vote average (0-10) */\n voteAverage: number\n /** Release year */\n year?: number | null\n /** Additional class name */\n className?: string\n /** Image loading strategy ('lazy' | 'eager'). Default: 'lazy' */\n imageLoading?: ImageLoading\n /** Base64 blur data URL for next/image placeholder=\"blur\" */\n blurDataURL?: string\n}\n\nexport interface MovieCardAsCard extends MovieCardBaseProps {\n as?: 'card' | undefined\n onClick?: never\n}\n\nexport interface MovieCardAsButton extends MovieCardBaseProps {\n as: 'button'\n onClick: () => void\n}\n\nexport function getMovieCardClasses(\n isInteractive: boolean,\n className?: string\n) {\n return clsx(\n 'ui:group ui:relative ui:flex ui:flex-col ui:overflow-hidden',\n isInteractive && [\n 'ui:cursor-pointer',\n 'ui:transition-transform ui:duration-200',\n 'hover:ui:scale-[1.02]',\n ],\n className\n )\n}\n\nexport function getMovieCardLinkClasses() {\n return 'ui:block ui:no-underline ui:text-inherit'\n}\n"]}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { getMovieCardLinkClasses, Rating_default, Typography_default, getMovieCardClasses } from './chunk-2EQIYKZF.js';
|
|
2
|
+
import { Card_default } from './chunk-JI3OVXCK.js';
|
|
3
|
+
import { Icon_default } from './chunk-JHRISQQJ.js';
|
|
4
|
+
import { getBlurDataUrl } from '@vite-mf-monorepo/shared';
|
|
5
|
+
import clsx from 'clsx';
|
|
6
|
+
import { useRef, useState, useEffect, useCallback } from 'react';
|
|
7
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
|
+
import { Link } from 'react-router-dom';
|
|
9
|
+
|
|
10
|
+
function Image({
|
|
11
|
+
src,
|
|
12
|
+
alt,
|
|
13
|
+
blurDataUrl,
|
|
14
|
+
autoBlur = false,
|
|
15
|
+
blurSize = 16,
|
|
16
|
+
blurQuality = 0.3,
|
|
17
|
+
aspectRatio = "2/3",
|
|
18
|
+
fallback,
|
|
19
|
+
className,
|
|
20
|
+
onLoad,
|
|
21
|
+
onError,
|
|
22
|
+
loading = "eager",
|
|
23
|
+
...rest
|
|
24
|
+
}) {
|
|
25
|
+
const imgRef = useRef(null);
|
|
26
|
+
const containerRef = useRef(null);
|
|
27
|
+
const [state, setState] = useState("loading");
|
|
28
|
+
const [generatedBlur, setGeneratedBlur] = useState(
|
|
29
|
+
void 0
|
|
30
|
+
);
|
|
31
|
+
const [blurReady, setBlurReady] = useState(!autoBlur || !!blurDataUrl);
|
|
32
|
+
const [isVisible, setIsVisible] = useState(loading === "eager");
|
|
33
|
+
const effectiveBlur = blurDataUrl ?? generatedBlur;
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (loading === "eager" || !containerRef.current) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const observer = new IntersectionObserver(
|
|
39
|
+
([entry]) => {
|
|
40
|
+
if (entry.isIntersecting) {
|
|
41
|
+
setIsVisible(true);
|
|
42
|
+
observer.unobserve(entry.target);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{ rootMargin: "50px" }
|
|
46
|
+
);
|
|
47
|
+
observer.observe(containerRef.current);
|
|
48
|
+
return () => {
|
|
49
|
+
observer.disconnect();
|
|
50
|
+
};
|
|
51
|
+
}, [loading]);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!autoBlur || blurDataUrl) {
|
|
54
|
+
setBlurReady(true);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
setBlurReady(false);
|
|
58
|
+
getBlurDataUrl(src, blurSize, blurQuality).then((base64) => {
|
|
59
|
+
setGeneratedBlur(base64);
|
|
60
|
+
setBlurReady(true);
|
|
61
|
+
}).catch(() => {
|
|
62
|
+
setBlurReady(true);
|
|
63
|
+
});
|
|
64
|
+
}, [autoBlur, src, blurDataUrl, blurSize, blurQuality]);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
setState("loading");
|
|
67
|
+
setGeneratedBlur(void 0);
|
|
68
|
+
if (imgRef.current?.complete && imgRef.current.naturalHeight !== 0) {
|
|
69
|
+
setState("loaded");
|
|
70
|
+
}
|
|
71
|
+
}, [src]);
|
|
72
|
+
const handleLoad = useCallback(() => {
|
|
73
|
+
setState("loaded");
|
|
74
|
+
onLoad?.();
|
|
75
|
+
}, [onLoad]);
|
|
76
|
+
const handleError = useCallback(() => {
|
|
77
|
+
setState("error");
|
|
78
|
+
onError?.();
|
|
79
|
+
}, [onError]);
|
|
80
|
+
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(
|
|
81
|
+
Icon_default,
|
|
82
|
+
{
|
|
83
|
+
name: "Photo",
|
|
84
|
+
size: 48,
|
|
85
|
+
className: "ui:text-muted-foreground",
|
|
86
|
+
"aria-hidden": "true"
|
|
87
|
+
}
|
|
88
|
+
) });
|
|
89
|
+
return /* @__PURE__ */ jsx(
|
|
90
|
+
"div",
|
|
91
|
+
{
|
|
92
|
+
ref: containerRef,
|
|
93
|
+
className: clsx("ui:relative ui:overflow-hidden ui:bg-muted", className),
|
|
94
|
+
style: aspectRatio ? { aspectRatio } : void 0,
|
|
95
|
+
"data-state": state,
|
|
96
|
+
children: state === "error" ? fallback ?? defaultFallback : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
97
|
+
effectiveBlur && state === "loading" && /* @__PURE__ */ jsx(
|
|
98
|
+
"img",
|
|
99
|
+
{
|
|
100
|
+
src: effectiveBlur,
|
|
101
|
+
alt: "",
|
|
102
|
+
"aria-hidden": "true",
|
|
103
|
+
className: "ui:absolute ui:inset-0 ui:h-full ui:w-full ui:scale-105 ui:object-cover"
|
|
104
|
+
}
|
|
105
|
+
),
|
|
106
|
+
blurReady && isVisible && /* @__PURE__ */ jsx(
|
|
107
|
+
"img",
|
|
108
|
+
{
|
|
109
|
+
ref: imgRef,
|
|
110
|
+
src,
|
|
111
|
+
alt,
|
|
112
|
+
onLoad: handleLoad,
|
|
113
|
+
onError: handleError,
|
|
114
|
+
className: clsx(
|
|
115
|
+
"ui:absolute ui:inset-0 ui:h-full ui:w-full ui:object-cover ui:transition-opacity ui:duration-300",
|
|
116
|
+
state === "loaded" ? "ui:opacity-100" : "ui:opacity-0"
|
|
117
|
+
),
|
|
118
|
+
...rest
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
] })
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
var Image_default = Image;
|
|
126
|
+
function MovieCardContent({
|
|
127
|
+
id,
|
|
128
|
+
title,
|
|
129
|
+
posterUrl,
|
|
130
|
+
voteAverage,
|
|
131
|
+
year,
|
|
132
|
+
className,
|
|
133
|
+
imageLoading = "lazy",
|
|
134
|
+
isInteractive,
|
|
135
|
+
onClick
|
|
136
|
+
}) {
|
|
137
|
+
return /* @__PURE__ */ jsxs(
|
|
138
|
+
Card_default,
|
|
139
|
+
{
|
|
140
|
+
variant: "ghost",
|
|
141
|
+
className: getMovieCardClasses(isInteractive, className),
|
|
142
|
+
onClick,
|
|
143
|
+
"data-testid": `movie-card-${String(id)}`,
|
|
144
|
+
children: [
|
|
145
|
+
/* @__PURE__ */ jsxs("div", { className: "ui:relative ui:aspect-[2/3] ui:w-full ui:overflow-hidden ui:rounded-md ui:bg-gray-200", children: [
|
|
146
|
+
/* @__PURE__ */ jsx(
|
|
147
|
+
Image_default,
|
|
148
|
+
{
|
|
149
|
+
src: posterUrl,
|
|
150
|
+
alt: title,
|
|
151
|
+
loading: imageLoading,
|
|
152
|
+
aspectRatio: void 0,
|
|
153
|
+
className: "ui:h-full ui:w-full ui:object-cover"
|
|
154
|
+
}
|
|
155
|
+
),
|
|
156
|
+
/* @__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(
|
|
157
|
+
Rating_default,
|
|
158
|
+
{
|
|
159
|
+
value: voteAverage,
|
|
160
|
+
size: "sm",
|
|
161
|
+
variant: "circle",
|
|
162
|
+
trackClassName: "ui:text-gray-200",
|
|
163
|
+
className: "ui:drop-shadow"
|
|
164
|
+
}
|
|
165
|
+
) })
|
|
166
|
+
] }),
|
|
167
|
+
/* @__PURE__ */ jsxs("div", { className: "ui:mt-2 ui:flex ui:flex-col ui:gap-0.5 ui:px-1", children: [
|
|
168
|
+
/* @__PURE__ */ jsx(
|
|
169
|
+
Typography_default,
|
|
170
|
+
{
|
|
171
|
+
variant: "label",
|
|
172
|
+
as: "h3",
|
|
173
|
+
className: "ui:line-clamp-2",
|
|
174
|
+
title,
|
|
175
|
+
children: title
|
|
176
|
+
}
|
|
177
|
+
),
|
|
178
|
+
year && /* @__PURE__ */ jsx(
|
|
179
|
+
Typography_default,
|
|
180
|
+
{
|
|
181
|
+
variant: "caption-xs",
|
|
182
|
+
className: "ui:[.media-section:nth-of-type(odd)_&]:text-badge-foreground",
|
|
183
|
+
children: year
|
|
184
|
+
}
|
|
185
|
+
)
|
|
186
|
+
] })
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
function MovieCard({
|
|
192
|
+
id,
|
|
193
|
+
title,
|
|
194
|
+
posterUrl,
|
|
195
|
+
voteAverage,
|
|
196
|
+
year,
|
|
197
|
+
className,
|
|
198
|
+
imageLoading = "lazy",
|
|
199
|
+
as = "card",
|
|
200
|
+
...rest
|
|
201
|
+
}) {
|
|
202
|
+
const to = "to" in rest ? rest.to : void 0;
|
|
203
|
+
const onClick = "onClick" in rest ? rest.onClick : void 0;
|
|
204
|
+
const isInteractive = as === "link" || as === "button";
|
|
205
|
+
const cardContent = /* @__PURE__ */ jsx(
|
|
206
|
+
MovieCardContent,
|
|
207
|
+
{
|
|
208
|
+
id,
|
|
209
|
+
title,
|
|
210
|
+
posterUrl,
|
|
211
|
+
voteAverage,
|
|
212
|
+
year,
|
|
213
|
+
className,
|
|
214
|
+
imageLoading,
|
|
215
|
+
isInteractive,
|
|
216
|
+
onClick: as === "button" ? onClick : void 0
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
if (as === "link" && to) {
|
|
220
|
+
return /* @__PURE__ */ jsx(Link, { to, className: getMovieCardLinkClasses(), children: cardContent });
|
|
221
|
+
}
|
|
222
|
+
return cardContent;
|
|
223
|
+
}
|
|
224
|
+
var MovieCard_default = MovieCard;
|
|
225
|
+
|
|
226
|
+
export { Image_default, MovieCard_default };
|
|
227
|
+
//# sourceMappingURL=chunk-3GCDFSJB.js.map
|
|
228
|
+
//# sourceMappingURL=chunk-3GCDFSJB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/Image/Image.tsx","../src/MovieCard/MovieCardContent.tsx","../src/react-router/MovieCard/MovieCard.tsx"],"names":["jsxs","jsx"],"mappings":"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","file":"chunk-3GCDFSJB.js","sourcesContent":["import { getBlurDataUrl } from '@vite-mf-monorepo/shared'\nimport clsx from 'clsx'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nimport { Icon } from '../Icon'\n\nimport type { ImgHTMLAttributes, ReactNode } from 'react'\n\nexport type ImageState = 'loading' | 'loaded' | 'error'\n\nexport type AspectRatio = '2/3' | '16/9' | '1/1' | '4/3' | '3/2'\n\nexport type ImageLoading = 'lazy' | 'eager'\n\nexport interface ImageProps\n extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'placeholder'> {\n /** Image source URL */\n src: string\n /** Alt text for accessibility */\n alt: string\n /** Pre-generated blur data URL (base64) */\n blurDataUrl?: string\n /** Auto-generate blur placeholder from src using Canvas API */\n autoBlur?: boolean\n /** Size of blur canvas (smaller = more blur). Default: 16 */\n blurSize?: number\n /** JPEG quality for blur (0-1). Default: 0.3 */\n blurQuality?: number\n /** Aspect ratio of the image container */\n aspectRatio?: AspectRatio | (string & {})\n /** Fallback content when image fails to load */\n fallback?: ReactNode\n /** Callback when image loads successfully */\n onLoad?: () => void\n /** Callback when image fails to load */\n onError?: () => void\n /** Load strategy: 'lazy' waits for viewport visibility, 'eager' loads immediately. Default: 'eager' */\n loading?: ImageLoading\n}\n\nfunction Image({\n src,\n alt,\n blurDataUrl,\n autoBlur = false,\n blurSize = 16,\n blurQuality = 0.3,\n aspectRatio = '2/3',\n fallback,\n className,\n onLoad,\n onError,\n loading = 'eager',\n ...rest\n}: Readonly<ImageProps>) {\n const imgRef = useRef<HTMLImageElement>(null)\n const containerRef = useRef<HTMLDivElement>(null)\n const [state, setState] = useState<ImageState>('loading')\n const [generatedBlur, setGeneratedBlur] = useState<string | undefined>(\n undefined\n )\n const [blurReady, setBlurReady] = useState(!autoBlur || !!blurDataUrl)\n const [isVisible, setIsVisible] = useState(loading === 'eager')\n\n const effectiveBlur = blurDataUrl ?? generatedBlur\n\n // Lazy load with IntersectionObserver\n useEffect(() => {\n if (loading === 'eager' || !containerRef.current) {\n return\n }\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setIsVisible(true)\n observer.unobserve(entry.target)\n }\n },\n { rootMargin: '50px' }\n )\n\n observer.observe(containerRef.current)\n\n return () => {\n observer.disconnect()\n }\n }, [loading])\n\n useEffect(() => {\n if (!autoBlur || blurDataUrl) {\n setBlurReady(true)\n return\n }\n\n setBlurReady(false)\n\n getBlurDataUrl(src, blurSize, blurQuality)\n .then((base64) => {\n setGeneratedBlur(base64)\n setBlurReady(true)\n })\n .catch(() => {\n setBlurReady(true)\n })\n }, [autoBlur, src, blurDataUrl, blurSize, blurQuality])\n\n useEffect(() => {\n setState('loading')\n setGeneratedBlur(undefined)\n\n // Check if image is already loaded from cache\n if (imgRef.current?.complete && imgRef.current.naturalHeight !== 0) {\n setState('loaded')\n }\n }, [src])\n\n const handleLoad = useCallback(() => {\n setState('loaded')\n onLoad?.()\n }, [onLoad])\n\n const handleError = useCallback(() => {\n setState('error')\n onError?.()\n }, [onError])\n\n const defaultFallback = (\n <div className=\"ui:flex ui:h-full ui:w-full ui:items-center ui:justify-center ui:bg-muted\">\n <Icon\n name=\"Photo\"\n size={48}\n className=\"ui:text-muted-foreground\"\n aria-hidden=\"true\"\n />\n </div>\n )\n\n return (\n <div\n ref={containerRef}\n className={clsx('ui:relative ui:overflow-hidden ui:bg-muted', className)}\n style={aspectRatio ? { aspectRatio } : undefined}\n data-state={state}\n >\n {state === 'error' ? (\n (fallback ?? defaultFallback)\n ) : (\n <>\n {effectiveBlur && state === 'loading' && (\n <img\n src={effectiveBlur}\n alt=\"\"\n aria-hidden=\"true\"\n className=\"ui:absolute ui:inset-0 ui:h-full ui:w-full ui:scale-105 ui:object-cover\"\n />\n )}\n\n {blurReady && isVisible && (\n <img\n ref={imgRef}\n src={src}\n alt={alt}\n onLoad={handleLoad}\n onError={handleError}\n className={clsx(\n 'ui:absolute ui:inset-0 ui:h-full ui:w-full ui:object-cover ui:transition-opacity ui:duration-300',\n state === 'loaded' ? 'ui:opacity-100' : 'ui:opacity-0'\n )}\n {...rest}\n />\n )}\n </>\n )}\n </div>\n )\n}\n\nexport default Image\n","import { Card } from '../Card'\nimport { Image } from '../Image'\nimport { Rating } from '../Rating'\nimport { Typography } from '../Typography'\n\nimport { getMovieCardClasses } from './MovieCard.utils'\n\nimport type { ImageLoading } from '../Image'\nimport type { ReactNode } from 'react'\n\ninterface MovieCardContentProps {\n id: number\n title: string\n posterUrl: string\n voteAverage: number\n year?: number | null\n className?: string\n imageLoading?: ImageLoading\n isInteractive: boolean\n onClick?: (() => void) | undefined\n}\n\nexport default function MovieCardContent({\n id,\n title,\n posterUrl,\n voteAverage,\n year,\n className,\n imageLoading = 'lazy',\n isInteractive,\n onClick,\n}: Readonly<MovieCardContentProps>): ReactNode {\n return (\n <Card\n variant=\"ghost\"\n className={getMovieCardClasses(isInteractive, className)}\n onClick={onClick}\n data-testid={`movie-card-${String(id)}`}\n >\n <div className=\"ui:relative ui:aspect-[2/3] ui:w-full ui:overflow-hidden ui:rounded-md ui:bg-gray-200\">\n <Image\n src={posterUrl}\n alt={title}\n loading={imageLoading}\n aspectRatio={undefined}\n className=\"ui:h-full ui:w-full ui:object-cover\"\n />\n <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\">\n <Rating\n value={voteAverage}\n size=\"sm\"\n variant=\"circle\"\n trackClassName=\"ui:text-gray-200\"\n className=\"ui:drop-shadow\"\n />\n </div>\n </div>\n\n <div className=\"ui:mt-2 ui:flex ui:flex-col ui:gap-0.5 ui:px-1\">\n <Typography\n variant=\"label\"\n as=\"h3\"\n className=\"ui:line-clamp-2\"\n title={title}\n >\n {title}\n </Typography>\n {year && (\n <Typography\n variant=\"caption-xs\"\n className=\"ui:[.media-section:nth-of-type(odd)_&]:text-badge-foreground\"\n >\n {year}\n </Typography>\n )}\n </div>\n </Card>\n )\n}\n","import { Link } from 'react-router-dom'\n\nimport { getMovieCardLinkClasses } from '../../MovieCard/MovieCard.utils'\nimport MovieCardContent from '../../MovieCard/MovieCardContent'\n\nimport type { MovieCardProps } from './MovieCard.types'\n\nfunction MovieCard({\n id,\n title,\n posterUrl,\n voteAverage,\n year,\n className,\n imageLoading = 'lazy',\n as = 'card',\n ...rest\n}: Readonly<MovieCardProps>) {\n const to = 'to' in rest ? rest.to : undefined\n const onClick = 'onClick' in rest ? rest.onClick : undefined\n\n const isInteractive = as === 'link' || as === 'button'\n\n const cardContent = (\n <MovieCardContent\n id={id}\n title={title}\n posterUrl={posterUrl}\n voteAverage={voteAverage}\n year={year}\n className={className}\n imageLoading={imageLoading}\n isInteractive={isInteractive}\n onClick={as === 'button' ? onClick : undefined}\n />\n )\n\n if (as === 'link' && to) {\n return (\n <Link to={to} className={getMovieCardLinkClasses()}>\n {cardContent}\n </Link>\n )\n }\n\n return cardContent\n}\n\nexport default MovieCard\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { IconName } from './Icon/index.js';
|
|
|
5
5
|
export { Icon, IconProps, IconSize } from './Icon/index.js';
|
|
6
6
|
export { Button, ButtonProps } from './Button/index.js';
|
|
7
7
|
export { Card, CardProps, CardVariant } from './Card/index.js';
|
|
8
|
-
export { A as AspectRatio, I as Image, c as ImageProps, d as ImageState } from './MovieCard.utils-
|
|
8
|
+
export { A as AspectRatio, I as Image, c as ImageProps, d as ImageState } from './MovieCard.utils-D8i4d7qA.js';
|
|
9
9
|
export { MovieCard, MovieCardProps } from './react-router/index.js';
|
|
10
10
|
import 'react-router-dom';
|
|
11
11
|
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import './chunk-Y5GP5OWN.js';
|
|
2
2
|
import { Skeleton_default } from './chunk-JPJYJLAP.js';
|
|
3
3
|
export { Skeleton_default as Skeleton } from './chunk-JPJYJLAP.js';
|
|
4
|
-
export { MovieCard_default as MovieCard } from './chunk-
|
|
4
|
+
export { Image_default as Image, MovieCard_default as MovieCard } from './chunk-3GCDFSJB.js';
|
|
5
5
|
import { Button_default } from './chunk-WYIIOTWJ.js';
|
|
6
6
|
export { Button_default as Button } from './chunk-WYIIOTWJ.js';
|
|
7
|
-
import { Typography_default } from './chunk-
|
|
8
|
-
export {
|
|
7
|
+
import { Typography_default } from './chunk-2EQIYKZF.js';
|
|
8
|
+
export { Rating_default as Rating, Typography_default as Typography } from './chunk-2EQIYKZF.js';
|
|
9
9
|
import './chunk-FDLKS7BI.js';
|
|
10
10
|
export { Card_default as Card } from './chunk-JI3OVXCK.js';
|
|
11
11
|
import { Icon_default } from './chunk-JHRISQQJ.js';
|
package/dist/next/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { IconName } from '../Icon/index.js';
|
|
3
3
|
import { LinkProps } from 'next/link';
|
|
4
|
-
import { ButtonHTMLAttributes } from 'react';
|
|
5
|
-
import {
|
|
4
|
+
import { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
5
|
+
import { ImageProps } from 'next/image';
|
|
6
|
+
import { M as MovieCardAsCard, a as MovieCardBaseProps, b as MovieCardAsButton } from '../MovieCard.utils-D8i4d7qA.js';
|
|
6
7
|
|
|
7
8
|
interface ButtonVisualProps {
|
|
8
9
|
/** Visual variant of the button */
|
|
@@ -34,6 +35,17 @@ interface NextHeroImageProps {
|
|
|
34
35
|
}
|
|
35
36
|
declare function HeroImage({ backdropPath, title }: Readonly<NextHeroImageProps>): react_jsx_runtime.JSX.Element;
|
|
36
37
|
|
|
38
|
+
interface NextImageProps extends Omit<ImageProps, 'placeholder'> {
|
|
39
|
+
/** Custom fallback content on error (default: Photo icon) */
|
|
40
|
+
fallback?: ReactNode;
|
|
41
|
+
/** Container aspect ratio (e.g. '2/3', '16/9') */
|
|
42
|
+
aspectRatio?: string;
|
|
43
|
+
/** Base64 blur data URL — auto-sets placeholder="blur" when provided */
|
|
44
|
+
blurDataURL?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
declare function NextImage({ fallback, aspectRatio, blurDataURL, className, onLoad, onError, style, ...rest }: Readonly<NextImageProps>): react_jsx_runtime.JSX.Element;
|
|
48
|
+
|
|
37
49
|
interface NextMovieCardAsLink extends MovieCardBaseProps {
|
|
38
50
|
/** Render as Next.js Link */
|
|
39
51
|
as: 'link';
|
|
@@ -43,6 +55,6 @@ interface NextMovieCardAsLink extends MovieCardBaseProps {
|
|
|
43
55
|
}
|
|
44
56
|
type NextMovieCardProps = MovieCardAsCard | NextMovieCardAsLink | MovieCardAsButton;
|
|
45
57
|
|
|
46
|
-
declare function MovieCard({ id, title, posterUrl, voteAverage, year, className, imageLoading, as, ...rest }: Readonly<NextMovieCardProps>): react_jsx_runtime.JSX.Element;
|
|
58
|
+
declare function MovieCard({ id, title, posterUrl, voteAverage, year, className, imageLoading, blurDataURL, as, ...rest }: Readonly<NextMovieCardProps>): react_jsx_runtime.JSX.Element;
|
|
47
59
|
|
|
48
|
-
export { Button, HeroImage, MovieCard, MovieCardAsButton, MovieCardAsCard, MovieCardBaseProps, type NextButtonAsButton, type NextButtonAsLink, type NextButtonProps, type NextHeroImageProps, type NextMovieCardAsLink, type NextMovieCardProps };
|
|
60
|
+
export { Button, HeroImage, MovieCard, MovieCardAsButton, MovieCardAsCard, MovieCardBaseProps, type NextButtonAsButton, type NextButtonAsLink, type NextButtonProps, type NextHeroImageProps, NextImage, type NextImageProps, type NextMovieCardAsLink, type NextMovieCardProps };
|
package/dist/next/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Skeleton_default } from '../chunk-JPJYJLAP.js';
|
|
2
|
-
import { getMovieCardLinkClasses,
|
|
2
|
+
import { getMovieCardLinkClasses, Rating_default, Typography_default, getMovieCardClasses } from '../chunk-2EQIYKZF.js';
|
|
3
3
|
import { getButtonClasses, iconSizeMap, getButtonDisabledClasses } from '../chunk-FDLKS7BI.js';
|
|
4
|
-
import '../chunk-JI3OVXCK.js';
|
|
4
|
+
import { Card_default } from '../chunk-JI3OVXCK.js';
|
|
5
5
|
import { Icon_default } from '../chunk-JHRISQQJ.js';
|
|
6
6
|
import Link from 'next/link';
|
|
7
7
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
8
|
+
import clsx from 'clsx';
|
|
8
9
|
import Image from 'next/image';
|
|
9
|
-
import { useState } from 'react';
|
|
10
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
10
11
|
|
|
11
12
|
function Button(props) {
|
|
12
13
|
const {
|
|
@@ -48,11 +49,101 @@ function Button(props) {
|
|
|
48
49
|
return /* @__PURE__ */ jsx("button", { className: getButtonDisabledClasses(classes), ...buttonProps, children: content });
|
|
49
50
|
}
|
|
50
51
|
var Button_default = Button;
|
|
52
|
+
function NextImage({
|
|
53
|
+
fallback,
|
|
54
|
+
aspectRatio,
|
|
55
|
+
blurDataURL,
|
|
56
|
+
className,
|
|
57
|
+
onLoad,
|
|
58
|
+
onError,
|
|
59
|
+
style,
|
|
60
|
+
...rest
|
|
61
|
+
}) {
|
|
62
|
+
const [state, setState] = useState("loading");
|
|
63
|
+
const imgRef = useRef(null);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (imgRef.current?.complete && imgRef.current.naturalWidth > 0) {
|
|
66
|
+
console.warn("loaded from cache");
|
|
67
|
+
setState("loaded");
|
|
68
|
+
}
|
|
69
|
+
}, []);
|
|
70
|
+
const handleLoad = useCallback(
|
|
71
|
+
(e) => {
|
|
72
|
+
setState("loaded");
|
|
73
|
+
console.warn("handleLoad::load");
|
|
74
|
+
if (typeof onLoad === "function") {
|
|
75
|
+
onLoad(e);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
[onLoad]
|
|
79
|
+
);
|
|
80
|
+
const handleError = useCallback(
|
|
81
|
+
(e) => {
|
|
82
|
+
setState("error");
|
|
83
|
+
if (typeof onError === "function") {
|
|
84
|
+
onError(e);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
[onError]
|
|
88
|
+
);
|
|
89
|
+
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(
|
|
90
|
+
Icon_default,
|
|
91
|
+
{
|
|
92
|
+
name: "Photo",
|
|
93
|
+
size: 48,
|
|
94
|
+
className: "ui:text-muted-foreground",
|
|
95
|
+
"aria-hidden": "true"
|
|
96
|
+
}
|
|
97
|
+
) });
|
|
98
|
+
return /* @__PURE__ */ jsx(
|
|
99
|
+
"div",
|
|
100
|
+
{
|
|
101
|
+
className: clsx("ui:relative ui:overflow-hidden ui:bg-muted", className),
|
|
102
|
+
style: aspectRatio ? { aspectRatio, ...style } : style,
|
|
103
|
+
"data-state": state,
|
|
104
|
+
children: state === "error" ? fallback ?? defaultFallback : /* @__PURE__ */ jsx(
|
|
105
|
+
Image,
|
|
106
|
+
{
|
|
107
|
+
...rest,
|
|
108
|
+
ref: imgRef,
|
|
109
|
+
className: clsx(
|
|
110
|
+
"ui:transition-opacity ui:duration-300",
|
|
111
|
+
state === "loaded" ? "ui:opacity-100" : "ui:opacity-0"
|
|
112
|
+
),
|
|
113
|
+
onLoad: handleLoad,
|
|
114
|
+
onError: handleError,
|
|
115
|
+
placeholder: blurDataURL ? "blur" : "empty",
|
|
116
|
+
blurDataURL
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
var NextImage_default = NextImage;
|
|
51
123
|
function HeroImage({ backdropPath, title }) {
|
|
52
|
-
const [loading, setLoading] = useState(true);
|
|
53
124
|
const src = backdropPath ? `https://image.tmdb.org/t/p/original${backdropPath}` : void 0;
|
|
54
125
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
55
|
-
|
|
126
|
+
src ? /* @__PURE__ */ jsx(
|
|
127
|
+
NextImage_default,
|
|
128
|
+
{
|
|
129
|
+
src,
|
|
130
|
+
alt: title ?? "Unknown",
|
|
131
|
+
fill: true,
|
|
132
|
+
preload: true,
|
|
133
|
+
sizes: "100vw",
|
|
134
|
+
className: "ui:h-full ui:w-full",
|
|
135
|
+
fallback: /* @__PURE__ */ jsx(
|
|
136
|
+
Skeleton_default,
|
|
137
|
+
{
|
|
138
|
+
"data-testid": "hero-image-skeleton",
|
|
139
|
+
variant: "rectangle",
|
|
140
|
+
width: "ui:relative ui:w-full ui:h-full ui:hero-height ui:z-0",
|
|
141
|
+
aspectRatio: "21/9",
|
|
142
|
+
rounded: false
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
) : /* @__PURE__ */ jsx(
|
|
56
147
|
Skeleton_default,
|
|
57
148
|
{
|
|
58
149
|
"data-testid": "hero-image-skeleton",
|
|
@@ -62,24 +153,79 @@ function HeroImage({ backdropPath, title }) {
|
|
|
62
153
|
rounded: false
|
|
63
154
|
}
|
|
64
155
|
),
|
|
65
|
-
src && /* @__PURE__ */ jsx(
|
|
66
|
-
Image,
|
|
67
|
-
{
|
|
68
|
-
src,
|
|
69
|
-
alt: title ?? "Unknown",
|
|
70
|
-
fill: true,
|
|
71
|
-
priority: true,
|
|
72
|
-
sizes: "100vw",
|
|
73
|
-
className: "ui:relative ui:h-full ui:w-full ui:object-cover ui:object-center ui:z-0",
|
|
74
|
-
onLoad: () => {
|
|
75
|
-
setLoading(false);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
),
|
|
79
156
|
/* @__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" })
|
|
80
157
|
] });
|
|
81
158
|
}
|
|
82
159
|
var HeroImage_default = HeroImage;
|
|
160
|
+
function MovieCardContent({
|
|
161
|
+
id,
|
|
162
|
+
title,
|
|
163
|
+
posterUrl,
|
|
164
|
+
voteAverage,
|
|
165
|
+
year,
|
|
166
|
+
className,
|
|
167
|
+
imageLoading = "lazy",
|
|
168
|
+
isInteractive,
|
|
169
|
+
onClick,
|
|
170
|
+
blurDataURL
|
|
171
|
+
}) {
|
|
172
|
+
const src = `https://image.tmdb.org/t/p/w342${posterUrl}`;
|
|
173
|
+
return /* @__PURE__ */ jsxs(
|
|
174
|
+
Card_default,
|
|
175
|
+
{
|
|
176
|
+
variant: "ghost",
|
|
177
|
+
className: getMovieCardClasses(isInteractive, className),
|
|
178
|
+
onClick,
|
|
179
|
+
"data-testid": `movie-card-${String(id)}`,
|
|
180
|
+
children: [
|
|
181
|
+
/* @__PURE__ */ jsxs("div", { className: "ui:relative ui:aspect-[2/3] ui:w-full ui:overflow-hidden ui:rounded-md ui:bg-gray-200", children: [
|
|
182
|
+
/* @__PURE__ */ jsx(
|
|
183
|
+
NextImage_default,
|
|
184
|
+
{
|
|
185
|
+
src,
|
|
186
|
+
alt: title,
|
|
187
|
+
width: 150,
|
|
188
|
+
height: 225,
|
|
189
|
+
loading: imageLoading,
|
|
190
|
+
blurDataURL,
|
|
191
|
+
className: "ui:h-full ui:w-full"
|
|
192
|
+
}
|
|
193
|
+
),
|
|
194
|
+
/* @__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(
|
|
195
|
+
Rating_default,
|
|
196
|
+
{
|
|
197
|
+
value: voteAverage,
|
|
198
|
+
size: "sm",
|
|
199
|
+
variant: "circle",
|
|
200
|
+
trackClassName: "ui:text-gray-200",
|
|
201
|
+
className: "ui:drop-shadow"
|
|
202
|
+
}
|
|
203
|
+
) })
|
|
204
|
+
] }),
|
|
205
|
+
/* @__PURE__ */ jsxs("div", { className: "ui:mt-2 ui:flex ui:flex-col ui:gap-0.5 ui:px-1", children: [
|
|
206
|
+
/* @__PURE__ */ jsx(
|
|
207
|
+
Typography_default,
|
|
208
|
+
{
|
|
209
|
+
variant: "label",
|
|
210
|
+
as: "h3",
|
|
211
|
+
className: "ui:line-clamp-2",
|
|
212
|
+
title,
|
|
213
|
+
children: title
|
|
214
|
+
}
|
|
215
|
+
),
|
|
216
|
+
year && /* @__PURE__ */ jsx(
|
|
217
|
+
Typography_default,
|
|
218
|
+
{
|
|
219
|
+
variant: "caption-xs",
|
|
220
|
+
className: "ui:[.media-section:nth-of-type(odd)_&]:text-badge-foreground",
|
|
221
|
+
children: year
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
] })
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
}
|
|
83
229
|
function MovieCard({
|
|
84
230
|
id,
|
|
85
231
|
title,
|
|
@@ -88,6 +234,7 @@ function MovieCard({
|
|
|
88
234
|
year,
|
|
89
235
|
className,
|
|
90
236
|
imageLoading = "lazy",
|
|
237
|
+
blurDataURL,
|
|
91
238
|
as = "card",
|
|
92
239
|
...rest
|
|
93
240
|
}) {
|
|
@@ -105,7 +252,8 @@ function MovieCard({
|
|
|
105
252
|
className,
|
|
106
253
|
imageLoading,
|
|
107
254
|
isInteractive,
|
|
108
|
-
onClick: as === "button" ? onClick : void 0
|
|
255
|
+
onClick: as === "button" ? onClick : void 0,
|
|
256
|
+
blurDataURL
|
|
109
257
|
}
|
|
110
258
|
);
|
|
111
259
|
if (as === "link" && href) {
|
|
@@ -115,6 +263,6 @@ function MovieCard({
|
|
|
115
263
|
}
|
|
116
264
|
var MovieCard_default = MovieCard;
|
|
117
265
|
|
|
118
|
-
export { Button_default as Button, HeroImage_default as HeroImage, MovieCard_default as MovieCard };
|
|
266
|
+
export { Button_default as Button, HeroImage_default as HeroImage, MovieCard_default as MovieCard, NextImage_default as NextImage };
|
|
119
267
|
//# sourceMappingURL=index.js.map
|
|
120
268
|
//# sourceMappingURL=index.js.map
|
package/dist/next/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/next/Button/Button.tsx","../../src/next/HeroImage/HeroImage.tsx","../../src/next/MovieCard/MovieCard.tsx"],"names":["_","_v","_s","_i","_ip","_c","_ch","jsxs","Fragment","jsx","Link"],"mappings":"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","file":"index.js","sourcesContent":["import Link from 'next/link'\n\nimport {\n getButtonClasses,\n getButtonDisabledClasses,\n iconSizeMap,\n} from '../../Button/Button.utils'\nimport { Icon } from '../../Icon'\n\nimport type { NextButtonProps } from './Button.types'\n\nfunction Button(props: Readonly<NextButtonProps>) {\n const {\n variant = 'primary',\n size = 'md',\n icon,\n iconPosition = 'left',\n className,\n children,\n } = props\n\n const classes = getButtonClasses({ variant, size, iconPosition, className })\n\n const content = (\n <>\n {icon && <Icon name={icon} size={iconSizeMap[size]} />}\n {children}\n </>\n )\n\n if (props.as === 'link') {\n const {\n as: _,\n variant: _v,\n size: _s,\n icon: _i,\n iconPosition: _ip,\n className: _c,\n children: _ch,\n ...linkProps\n } = props\n\n return (\n <Link className={classes} {...linkProps}>\n {content}\n </Link>\n )\n }\n\n const {\n as: _,\n variant: _v,\n size: _s,\n icon: _i,\n iconPosition: _ip,\n className: _c,\n children: _ch,\n ...buttonProps\n } = props\n\n return (\n <button className={getButtonDisabledClasses(classes)} {...buttonProps}>\n {content}\n </button>\n )\n}\n\nexport default Button\n","import Image from 'next/image'\nimport { useState } from 'react'\n\nimport { Skeleton } from '../../Skeleton'\n\nexport interface NextHeroImageProps {\n /** Backdrop path from TMDB API (e.g. \"/abc123.jpg\") */\n backdropPath?: string | null\n /** Alt text for the image */\n title?: string | null\n}\n\nfunction HeroImage({ backdropPath, title }: Readonly<NextHeroImageProps>) {\n const [loading, setLoading] = useState(true)\n\n const src = backdropPath\n ? `https://image.tmdb.org/t/p/original${backdropPath}`\n : undefined\n\n return (\n <>\n {loading && (\n <Skeleton\n data-testid=\"hero-image-skeleton\"\n variant=\"rectangle\"\n width=\"ui:relative ui:w-full ui:h-full ui:hero-height ui:z-0\"\n aspectRatio=\"21/9\"\n rounded={false}\n />\n )}\n {src && (\n <Image\n src={src}\n alt={title ?? 'Unknown'}\n fill\n priority\n sizes=\"100vw\"\n className=\"ui:relative ui:h-full ui:w-full ui:object-cover ui:object-center ui:z-0\"\n onLoad={() => {\n setLoading(false)\n }}\n />\n )}\n {/* Gradient Overlay */}\n <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\" />\n </>\n )\n}\n\nexport default HeroImage\n","import Link from 'next/link'\n\nimport { getMovieCardLinkClasses } from '../../MovieCard/MovieCard.utils'\nimport MovieCardContent from '../../MovieCard/MovieCardContent'\n\nimport type { NextMovieCardProps } from './MovieCard.types'\n\nfunction MovieCard({\n id,\n title,\n posterUrl,\n voteAverage,\n year,\n className,\n imageLoading = 'lazy',\n as = 'card',\n ...rest\n}: Readonly<NextMovieCardProps>) {\n const href = 'href' in rest ? rest.href : undefined\n const onClick = 'onClick' in rest ? rest.onClick : undefined\n\n const isInteractive = as === 'link' || as === 'button'\n\n const cardContent = (\n <MovieCardContent\n id={id}\n title={title}\n posterUrl={posterUrl}\n voteAverage={voteAverage}\n year={year}\n className={className}\n imageLoading={imageLoading}\n isInteractive={isInteractive}\n onClick={as === 'button' ? onClick : undefined}\n />\n )\n\n if (as === 'link' && href) {\n return (\n <Link href={href} className={getMovieCardLinkClasses()}>\n {cardContent}\n </Link>\n )\n }\n\n return cardContent\n}\n\nexport default MovieCard\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/next/Button/Button.tsx","../../src/next/Image/NextImage.tsx","../../src/next/HeroImage/HeroImage.tsx","../../src/next/MovieCard/MovieCardContent.tsx","../../src/next/MovieCard/MovieCard.tsx"],"names":["_","_v","_s","_i","_ip","_c","_ch","jsx","jsxs","Fragment","Link"],"mappings":"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","file":"index.js","sourcesContent":["import Link from 'next/link'\n\nimport {\n getButtonClasses,\n getButtonDisabledClasses,\n iconSizeMap,\n} from '../../Button/Button.utils'\nimport { Icon } from '../../Icon'\n\nimport type { NextButtonProps } from './Button.types'\n\nfunction Button(props: Readonly<NextButtonProps>) {\n const {\n variant = 'primary',\n size = 'md',\n icon,\n iconPosition = 'left',\n className,\n children,\n } = props\n\n const classes = getButtonClasses({ variant, size, iconPosition, className })\n\n const content = (\n <>\n {icon && <Icon name={icon} size={iconSizeMap[size]} />}\n {children}\n </>\n )\n\n if (props.as === 'link') {\n const {\n as: _,\n variant: _v,\n size: _s,\n icon: _i,\n iconPosition: _ip,\n className: _c,\n children: _ch,\n ...linkProps\n } = props\n\n return (\n <Link className={classes} {...linkProps}>\n {content}\n </Link>\n )\n }\n\n const {\n as: _,\n variant: _v,\n size: _s,\n icon: _i,\n iconPosition: _ip,\n className: _c,\n children: _ch,\n ...buttonProps\n } = props\n\n return (\n <button className={getButtonDisabledClasses(classes)} {...buttonProps}>\n {content}\n </button>\n )\n}\n\nexport default Button\n","'use client'\n\nimport clsx from 'clsx'\nimport Image from 'next/image'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nimport { Icon } from '../../Icon'\n\nimport type { NextImageProps } from './NextImage.types'\nimport type { ImageState } from '../../Image'\nimport type { SyntheticEvent } from 'react'\n\nfunction NextImage({\n fallback,\n aspectRatio,\n blurDataURL,\n className,\n onLoad,\n onError,\n style,\n ...rest\n}: Readonly<NextImageProps>) {\n const [state, setState] = useState<ImageState>('loading')\n\n // Add a ref and useEffect to catch already-loaded images\n const imgRef = useRef<HTMLImageElement>(null)\n\n useEffect(() => {\n if (imgRef.current?.complete && imgRef.current.naturalWidth > 0) {\n console.warn('loaded from cache')\n setState('loaded')\n }\n }, [])\n\n const handleLoad = useCallback(\n (e: SyntheticEvent<HTMLImageElement>) => {\n setState('loaded')\n console.warn('handleLoad::load')\n if (typeof onLoad === 'function') {\n onLoad(e as Parameters<typeof onLoad>[0])\n }\n },\n [onLoad]\n )\n\n const handleError = useCallback(\n (e: SyntheticEvent<HTMLImageElement>) => {\n setState('error')\n if (typeof onError === 'function') {\n onError(e as Parameters<typeof onError>[0])\n }\n },\n [onError]\n )\n\n const defaultFallback = (\n <div className=\"ui:flex ui:h-full ui:w-full ui:items-center ui:justify-center ui:bg-muted\">\n <Icon\n name=\"Photo\"\n size={48}\n className=\"ui:text-muted-foreground\"\n aria-hidden=\"true\"\n />\n </div>\n )\n\n return (\n <div\n className={clsx('ui:relative ui:overflow-hidden ui:bg-muted', className)}\n style={aspectRatio ? { aspectRatio, ...style } : style}\n data-state={state}\n >\n {state === 'error' ? (\n (fallback ?? defaultFallback)\n ) : (\n <Image\n {...rest}\n ref={imgRef}\n className={clsx(\n 'ui:transition-opacity ui:duration-300',\n state === 'loaded' ? 'ui:opacity-100' : 'ui:opacity-0'\n )}\n onLoad={handleLoad}\n onError={handleError}\n placeholder={blurDataURL ? 'blur' : 'empty'}\n blurDataURL={blurDataURL}\n />\n )}\n </div>\n )\n}\n\nexport default NextImage\n","import { Skeleton } from '../../Skeleton'\nimport { NextImage } from '../Image'\n\nexport interface NextHeroImageProps {\n /** Backdrop path from TMDB API (e.g. \"/abc123.jpg\") */\n backdropPath?: string | null\n /** Alt text for the image */\n title?: string | null\n}\n\nfunction HeroImage({ backdropPath, title }: Readonly<NextHeroImageProps>) {\n const src = backdropPath\n ? `https://image.tmdb.org/t/p/original${backdropPath}`\n : undefined\n\n return (\n <>\n {src ? (\n <NextImage\n src={src}\n alt={title ?? 'Unknown'}\n fill\n preload\n sizes=\"100vw\"\n className=\"ui:h-full ui:w-full\"\n fallback={\n <Skeleton\n data-testid=\"hero-image-skeleton\"\n variant=\"rectangle\"\n width=\"ui:relative ui:w-full ui:h-full ui:hero-height ui:z-0\"\n aspectRatio=\"21/9\"\n rounded={false}\n />\n }\n />\n ) : (\n <Skeleton\n data-testid=\"hero-image-skeleton\"\n variant=\"rectangle\"\n width=\"ui:relative ui:w-full ui:h-full ui:hero-height ui:z-0\"\n aspectRatio=\"21/9\"\n rounded={false}\n />\n )}\n {/* Gradient Overlay */}\n <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\" />\n </>\n )\n}\n\nexport default HeroImage\n","import { Card } from '../../Card'\nimport { getMovieCardClasses } from '../../MovieCard/MovieCard.utils'\nimport { Rating } from '../../Rating'\nimport { Typography } from '../../Typography'\nimport { NextImage } from '../Image'\n\nimport type { ImageLoading } from '../../Image/Image'\nimport type { ReactNode } from 'react'\n\ninterface NextMovieCardContentProps {\n id: number\n title: string\n posterUrl: string\n voteAverage: number\n year?: number | null\n className?: string\n imageLoading?: ImageLoading\n isInteractive: boolean\n onClick?: (() => void) | undefined\n blurDataURL?: string\n}\n\nexport default function MovieCardContent({\n id,\n title,\n posterUrl,\n voteAverage,\n year,\n className,\n imageLoading = 'lazy',\n isInteractive,\n onClick,\n blurDataURL,\n}: Readonly<NextMovieCardContentProps>): ReactNode {\n const src = `https://image.tmdb.org/t/p/w342${posterUrl}`\n\n return (\n <Card\n variant=\"ghost\"\n className={getMovieCardClasses(isInteractive, className)}\n onClick={onClick}\n data-testid={`movie-card-${String(id)}`}\n >\n <div className=\"ui:relative ui:aspect-[2/3] ui:w-full ui:overflow-hidden ui:rounded-md ui:bg-gray-200\">\n <NextImage\n src={src}\n alt={title}\n width={150}\n height={225}\n loading={imageLoading}\n blurDataURL={blurDataURL}\n className=\"ui:h-full ui:w-full\"\n />\n <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\">\n <Rating\n value={voteAverage}\n size=\"sm\"\n variant=\"circle\"\n trackClassName=\"ui:text-gray-200\"\n className=\"ui:drop-shadow\"\n />\n </div>\n </div>\n\n <div className=\"ui:mt-2 ui:flex ui:flex-col ui:gap-0.5 ui:px-1\">\n <Typography\n variant=\"label\"\n as=\"h3\"\n className=\"ui:line-clamp-2\"\n title={title}\n >\n {title}\n </Typography>\n {year && (\n <Typography\n variant=\"caption-xs\"\n className=\"ui:[.media-section:nth-of-type(odd)_&]:text-badge-foreground\"\n >\n {year}\n </Typography>\n )}\n </div>\n </Card>\n )\n}\n","import Link from 'next/link'\n\nimport { getMovieCardLinkClasses } from '../../MovieCard/MovieCard.utils'\n\nimport MovieCardContent from './MovieCardContent'\n\nimport type { NextMovieCardProps } from './MovieCard.types'\n\nfunction MovieCard({\n id,\n title,\n posterUrl,\n voteAverage,\n year,\n className,\n imageLoading = 'lazy',\n blurDataURL,\n as = 'card',\n ...rest\n}: Readonly<NextMovieCardProps>) {\n const href = 'href' in rest ? rest.href : undefined\n const onClick = 'onClick' in rest ? rest.onClick : undefined\n\n const isInteractive = as === 'link' || as === 'button'\n\n const cardContent = (\n <MovieCardContent\n id={id}\n title={title}\n posterUrl={posterUrl}\n voteAverage={voteAverage}\n year={year}\n className={className}\n imageLoading={imageLoading}\n isInteractive={isInteractive}\n onClick={as === 'button' ? onClick : undefined}\n blurDataURL={blurDataURL}\n />\n )\n\n if (as === 'link' && href) {\n return (\n <Link href={href} className={getMovieCardLinkClasses()}>\n {cardContent}\n </Link>\n )\n }\n\n return cardContent\n}\n\nexport default MovieCard\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { Button, ButtonAsButton, ButtonAsLink, ButtonProps } from '../Button/index.js';
|
|
2
2
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
-
import { M as MovieCardAsCard, a as MovieCardBaseProps, b as MovieCardAsButton } from '../MovieCard.utils-
|
|
3
|
+
import { M as MovieCardAsCard, a as MovieCardBaseProps, b as MovieCardAsButton } from '../MovieCard.utils-D8i4d7qA.js';
|
|
4
4
|
import '../Icon/index.js';
|
|
5
5
|
import 'react';
|
|
6
6
|
import 'react-router-dom';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { MovieCard_default as MovieCard } from '../chunk-
|
|
1
|
+
export { MovieCard_default as MovieCard } from '../chunk-3GCDFSJB.js';
|
|
2
2
|
export { Button_default as Button } from '../chunk-WYIIOTWJ.js';
|
|
3
|
-
import '../chunk-
|
|
3
|
+
import '../chunk-2EQIYKZF.js';
|
|
4
4
|
import '../chunk-FDLKS7BI.js';
|
|
5
5
|
import '../chunk-JI3OVXCK.js';
|
|
6
6
|
import '../chunk-JHRISQQJ.js';
|
package/package.json
CHANGED
package/dist/chunk-5NW3IDX2.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { getMovieCardLinkClasses, MovieCardContent } from './chunk-JDBRVX5O.js';
|
|
2
|
-
import { Link } from 'react-router-dom';
|
|
3
|
-
import { jsx } from 'react/jsx-runtime';
|
|
4
|
-
|
|
5
|
-
function MovieCard({
|
|
6
|
-
id,
|
|
7
|
-
title,
|
|
8
|
-
posterUrl,
|
|
9
|
-
voteAverage,
|
|
10
|
-
year,
|
|
11
|
-
className,
|
|
12
|
-
imageLoading = "lazy",
|
|
13
|
-
as = "card",
|
|
14
|
-
...rest
|
|
15
|
-
}) {
|
|
16
|
-
const to = "to" in rest ? rest.to : void 0;
|
|
17
|
-
const onClick = "onClick" in rest ? rest.onClick : void 0;
|
|
18
|
-
const isInteractive = as === "link" || as === "button";
|
|
19
|
-
const cardContent = /* @__PURE__ */ jsx(
|
|
20
|
-
MovieCardContent,
|
|
21
|
-
{
|
|
22
|
-
id,
|
|
23
|
-
title,
|
|
24
|
-
posterUrl,
|
|
25
|
-
voteAverage,
|
|
26
|
-
year,
|
|
27
|
-
className,
|
|
28
|
-
imageLoading,
|
|
29
|
-
isInteractive,
|
|
30
|
-
onClick: as === "button" ? onClick : void 0
|
|
31
|
-
}
|
|
32
|
-
);
|
|
33
|
-
if (as === "link" && to) {
|
|
34
|
-
return /* @__PURE__ */ jsx(Link, { to, className: getMovieCardLinkClasses(), children: cardContent });
|
|
35
|
-
}
|
|
36
|
-
return cardContent;
|
|
37
|
-
}
|
|
38
|
-
var MovieCard_default = MovieCard;
|
|
39
|
-
|
|
40
|
-
export { MovieCard_default };
|
|
41
|
-
//# sourceMappingURL=chunk-5NW3IDX2.js.map
|
|
42
|
-
//# sourceMappingURL=chunk-5NW3IDX2.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react-router/MovieCard/MovieCard.tsx"],"names":[],"mappings":";;;;AAOA,SAAS,SAAA,CAAU;AAAA,EACjB,EAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAA;AAAA,EACA,WAAA;AAAA,EACA,IAAA;AAAA,EACA,SAAA;AAAA,EACA,YAAA,GAAe,MAAA;AAAA,EACf,EAAA,GAAK,MAAA;AAAA,EACL,GAAG;AACL,CAAA,EAA6B;AAC3B,EAAA,MAAM,EAAA,GAAK,IAAA,IAAQ,IAAA,GAAO,IAAA,CAAK,EAAA,GAAK,MAAA;AACpC,EAAA,MAAM,OAAA,GAAU,SAAA,IAAa,IAAA,GAAO,IAAA,CAAK,OAAA,GAAU,MAAA;AAEnD,EAAA,MAAM,aAAA,GAAgB,EAAA,KAAO,MAAA,IAAU,EAAA,KAAO,QAAA;AAE9C,EAAA,MAAM,WAAA,mBACJ,GAAA;AAAA,IAAC,gBAAA;AAAA,IAAA;AAAA,MACC,EAAA;AAAA,MACA,KAAA;AAAA,MACA,SAAA;AAAA,MACA,WAAA;AAAA,MACA,IAAA;AAAA,MACA,SAAA;AAAA,MACA,YAAA;AAAA,MACA,aAAA;AAAA,MACA,OAAA,EAAS,EAAA,KAAO,QAAA,GAAW,OAAA,GAAU;AAAA;AAAA,GACvC;AAGF,EAAA,IAAI,EAAA,KAAO,UAAU,EAAA,EAAI;AACvB,IAAA,2BACG,IAAA,EAAA,EAAK,EAAA,EAAQ,SAAA,EAAW,uBAAA,IACtB,QAAA,EAAA,WAAA,EACH,CAAA;AAAA,EAEJ;AAEA,EAAA,OAAO,WAAA;AACT;AAEA,IAAO,iBAAA,GAAQ","file":"chunk-5NW3IDX2.js","sourcesContent":["import { Link } from 'react-router-dom'\n\nimport { getMovieCardLinkClasses } from '../../MovieCard/MovieCard.utils'\nimport MovieCardContent from '../../MovieCard/MovieCardContent'\n\nimport type { MovieCardProps } from './MovieCard.types'\n\nfunction MovieCard({\n id,\n title,\n posterUrl,\n voteAverage,\n year,\n className,\n imageLoading = 'lazy',\n as = 'card',\n ...rest\n}: Readonly<MovieCardProps>) {\n const to = 'to' in rest ? rest.to : undefined\n const onClick = 'onClick' in rest ? rest.onClick : undefined\n\n const isInteractive = as === 'link' || as === 'button'\n\n const cardContent = (\n <MovieCardContent\n id={id}\n title={title}\n posterUrl={posterUrl}\n voteAverage={voteAverage}\n year={year}\n className={className}\n imageLoading={imageLoading}\n isInteractive={isInteractive}\n onClick={as === 'button' ? onClick : undefined}\n />\n )\n\n if (as === 'link' && to) {\n return (\n <Link to={to} className={getMovieCardLinkClasses()}>\n {cardContent}\n </Link>\n )\n }\n\n return cardContent\n}\n\nexport default MovieCard\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/Image/Image.tsx","../src/Rating/CircleRating.tsx","../src/Rating/StarsRating.tsx","../src/Rating/Rating.tsx","../src/Typography/Typography.tsx","../src/MovieCard/MovieCard.utils.ts","../src/MovieCard/MovieCardContent.tsx"],"names":["jsxs","jsx","clsx"],"mappings":"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","file":"chunk-JDBRVX5O.js","sourcesContent":["import { getBlurDataUrl } from '@vite-mf-monorepo/shared'\nimport clsx from 'clsx'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nimport { Icon } from '../Icon'\n\nimport type { ImgHTMLAttributes, ReactNode } from 'react'\n\nexport type ImageState = 'loading' | 'loaded' | 'error'\n\nexport type AspectRatio = '2/3' | '16/9' | '1/1' | '4/3' | '3/2'\n\nexport type ImageLoading = 'lazy' | 'eager'\n\nexport interface ImageProps\n extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'placeholder'> {\n /** Image source URL */\n src: string\n /** Alt text for accessibility */\n alt: string\n /** Pre-generated blur data URL (base64) */\n blurDataUrl?: string\n /** Auto-generate blur placeholder from src using Canvas API */\n autoBlur?: boolean\n /** Size of blur canvas (smaller = more blur). Default: 16 */\n blurSize?: number\n /** JPEG quality for blur (0-1). Default: 0.3 */\n blurQuality?: number\n /** Aspect ratio of the image container */\n aspectRatio?: AspectRatio | (string & {})\n /** Fallback content when image fails to load */\n fallback?: ReactNode\n /** Callback when image loads successfully */\n onLoad?: () => void\n /** Callback when image fails to load */\n onError?: () => void\n /** Load strategy: 'lazy' waits for viewport visibility, 'eager' loads immediately. Default: 'eager' */\n loading?: ImageLoading\n}\n\nfunction Image({\n src,\n alt,\n blurDataUrl,\n autoBlur = false,\n blurSize = 16,\n blurQuality = 0.3,\n aspectRatio = '2/3',\n fallback,\n className,\n onLoad,\n onError,\n loading = 'eager',\n ...rest\n}: Readonly<ImageProps>) {\n const imgRef = useRef<HTMLImageElement>(null)\n const containerRef = useRef<HTMLDivElement>(null)\n const [state, setState] = useState<ImageState>('loading')\n const [generatedBlur, setGeneratedBlur] = useState<string | undefined>(\n undefined\n )\n const [blurReady, setBlurReady] = useState(!autoBlur || !!blurDataUrl)\n const [isVisible, setIsVisible] = useState(loading === 'eager')\n\n const effectiveBlur = blurDataUrl ?? generatedBlur\n\n // Lazy load with IntersectionObserver\n useEffect(() => {\n if (loading === 'eager' || !containerRef.current) {\n return\n }\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setIsVisible(true)\n observer.unobserve(entry.target)\n }\n },\n { rootMargin: '50px' }\n )\n\n observer.observe(containerRef.current)\n\n return () => {\n observer.disconnect()\n }\n }, [loading])\n\n useEffect(() => {\n if (!autoBlur || blurDataUrl) {\n setBlurReady(true)\n return\n }\n\n setBlurReady(false)\n\n getBlurDataUrl(src, blurSize, blurQuality)\n .then((base64) => {\n setGeneratedBlur(base64)\n setBlurReady(true)\n })\n .catch(() => {\n setBlurReady(true)\n })\n }, [autoBlur, src, blurDataUrl, blurSize, blurQuality])\n\n useEffect(() => {\n setState('loading')\n setGeneratedBlur(undefined)\n\n // Check if image is already loaded from cache\n if (imgRef.current?.complete && imgRef.current.naturalHeight !== 0) {\n setState('loaded')\n }\n }, [src])\n\n const handleLoad = useCallback(() => {\n setState('loaded')\n onLoad?.()\n }, [onLoad])\n\n const handleError = useCallback(() => {\n setState('error')\n onError?.()\n }, [onError])\n\n const defaultFallback = (\n <div className=\"ui:flex ui:h-full ui:w-full ui:items-center ui:justify-center ui:bg-muted\">\n <Icon\n name=\"Photo\"\n size={48}\n className=\"ui:text-muted-foreground\"\n aria-hidden=\"true\"\n />\n </div>\n )\n\n return (\n <div\n ref={containerRef}\n className={clsx('ui:relative ui:overflow-hidden ui:bg-muted', className)}\n style={aspectRatio ? { aspectRatio } : undefined}\n data-state={state}\n >\n {state === 'error' ? (\n (fallback ?? defaultFallback)\n ) : (\n <>\n {effectiveBlur && state === 'loading' && (\n <img\n src={effectiveBlur}\n alt=\"\"\n aria-hidden=\"true\"\n className=\"ui:absolute ui:inset-0 ui:h-full ui:w-full ui:scale-105 ui:object-cover\"\n />\n )}\n\n {blurReady && isVisible && (\n <img\n ref={imgRef}\n src={src}\n alt={alt}\n onLoad={handleLoad}\n onError={handleError}\n className={clsx(\n 'ui:absolute ui:inset-0 ui:h-full ui:w-full ui:object-cover ui:transition-opacity ui:duration-300',\n state === 'loaded' ? 'ui:opacity-100' : 'ui:opacity-0'\n )}\n {...rest}\n />\n )}\n </>\n )}\n </div>\n )\n}\n\nexport default Image\n","import clsx from 'clsx'\n\nimport type { RatingSize } from './Rating'\n\nconst circleSizeMap: Record<\n RatingSize,\n { size: number; stroke: number; fontSize: string }\n> = {\n sm: { size: 32, stroke: 3, fontSize: 'ui:text-xs' },\n md: { size: 48, stroke: 4, fontSize: 'ui:text-sm' },\n lg: { size: 64, stroke: 5, fontSize: 'ui:text-base' },\n}\n\nconst getColorClass = (percent: number): string => {\n if (percent >= 70) return 'ui:text-green-600'\n if (percent >= 40) return 'ui:text-amber-600'\n return 'ui:text-red-500'\n}\n\nexport interface CircleRatingProps {\n /** Percentage value (0-100) for the progress ring */\n percent: number\n /** Size of the rating circle */\n size: RatingSize\n /** Whether to display the numeric value in the center */\n showValue: boolean\n /** The actual rating value (used for display) */\n value: number\n /** Maximum rating value (used for display formatting) */\n max: number\n /** Custom class for the background track circle */\n trackClassName?: string\n}\n\nfunction CircleRating({\n percent,\n size,\n showValue,\n value,\n max,\n trackClassName,\n}: Readonly<CircleRatingProps>) {\n const { size: svgSize, stroke, fontSize } = circleSizeMap[size]\n const radius = (svgSize - stroke) / 2\n const circumference = 2 * Math.PI * radius\n const offset = circumference - (percent / 100) * circumference\n\n return (\n <div className=\"ui:relative ui:inline-flex ui:items-center ui:justify-center\">\n <svg width={svgSize} height={svgSize} className=\"ui:-rotate-90\">\n <circle\n cx={svgSize / 2}\n cy={svgSize / 2}\n r={radius}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={stroke}\n className={trackClassName ?? 'ui:text-gray-200'}\n />\n <circle\n cx={svgSize / 2}\n cy={svgSize / 2}\n r={radius}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={stroke}\n strokeLinecap=\"round\"\n strokeDasharray={circumference}\n strokeDashoffset={offset}\n className={clsx(\n 'ui:transition-all ui:duration-500',\n getColorClass(percent)\n )}\n />\n </svg>\n {showValue && (\n <span\n className={clsx(\n 'ui:absolute ui:font-bold',\n fontSize,\n getColorClass(percent)\n )}\n >\n {max === 100 ? Math.round(percent) : value.toFixed(1)}\n </span>\n )}\n </div>\n )\n}\n\nexport default CircleRating\n","import clsx from 'clsx'\n\nimport { Icon } from '../Icon'\n\nimport type { IconSize } from '../Icon'\nimport type { RatingSize } from './Rating'\n\nconst starsSizeMap: Record<RatingSize, IconSize> = {\n sm: 16,\n md: 20,\n lg: 24,\n}\n\nexport interface StarsRatingProps {\n /** Percentage value (0-100) for the star fill */\n percent: number\n /** Size of the stars */\n size: RatingSize\n /** Whether to display the numeric value next to stars */\n showValue: boolean\n /** The actual rating value (used for display) */\n value: number\n /** Maximum rating value (used for display formatting) */\n max: number\n}\n\nfunction StarsRating({\n percent,\n size,\n showValue,\n value,\n max,\n}: Readonly<StarsRatingProps>) {\n const iconSize = starsSizeMap[size]\n\n return (\n <div className=\"ui:inline-flex ui:items-center ui:gap-1\">\n <div className=\"ui:relative\">\n {/* Empty stars */}\n <div className=\"ui:flex ui:text-gray-300\">\n {[1, 2, 3, 4, 5].map((i) => (\n <Icon key={i} name=\"Star\" size={iconSize} />\n ))}\n </div>\n {/* Filled stars with clip */}\n <div\n className=\"ui:absolute ui:inset-0 ui:flex ui:text-yellow-400\"\n style={{ clipPath: `inset(0 ${String(100 - percent)}% 0 0)` }}\n >\n {[1, 2, 3, 4, 5].map((i) => (\n <Icon key={i} name=\"Star\" size={iconSize} />\n ))}\n </div>\n </div>\n {showValue && (\n <span\n className={clsx(\n 'ui:font-medium ui:text-muted-foreground',\n size === 'sm' && 'ui:text-xs',\n size === 'md' && 'ui:text-sm',\n size === 'lg' && 'ui:text-base'\n )}\n >\n {max === 100 ? (value / 10).toFixed(1) : value.toFixed(1)}\n </span>\n )}\n </div>\n )\n}\n\nexport default StarsRating\n","import clsx from 'clsx'\n\nimport CircleRating from './CircleRating'\nimport StarsRating from './StarsRating'\n\nexport type RatingVariant = 'circle' | 'stars'\nexport type RatingSize = 'sm' | 'md' | 'lg'\n\nexport interface RatingProps {\n /** Rating value (0-10 by default, or 0-100 if max=100) */\n value: number\n /** Maximum value (default: 10 for TMDB scores) */\n max?: number\n /** Visual variant */\n variant?: RatingVariant\n /** Size of the rating */\n size?: RatingSize\n /** Show the numeric value */\n showValue?: boolean\n /** Custom class for the background track circle (circle variant only) */\n trackClassName?: string\n /** Additional class name */\n className?: string\n}\n\nfunction Rating({\n value,\n max = 10,\n variant = 'circle',\n size = 'md',\n showValue = true,\n trackClassName,\n className,\n}: Readonly<RatingProps>) {\n const clampedValue = Math.max(0, Math.min(value, max))\n const percent = (clampedValue / max) * 100\n\n return (\n <div className={clsx('ui:inline-flex', className)}>\n {variant === 'circle' ? (\n <CircleRating\n percent={percent}\n size={size}\n showValue={showValue}\n value={clampedValue}\n max={max}\n trackClassName={trackClassName}\n />\n ) : (\n <StarsRating\n percent={percent}\n size={size}\n showValue={showValue}\n value={clampedValue}\n max={max}\n />\n )}\n </div>\n )\n}\n\nexport default Rating\n","import clsx from 'clsx'\nimport { createElement } from 'react'\n\nimport type { ElementType, HTMLAttributes, ReactNode } from 'react'\n\nexport type TypographyVariant =\n | 'h1'\n | 'h2'\n | 'h3'\n | 'h4'\n | 'h5'\n | 'h6'\n | 'body'\n | 'body-sm'\n | 'body-lg'\n | 'lead'\n | 'caption'\n | 'caption-xs'\n | 'label'\n | 'muted'\n | 'blockquote'\n\nexport interface TypographyProps extends HTMLAttributes<HTMLElement> {\n /** Visual style variant */\n variant: TypographyVariant\n /** Override semantic HTML tag */\n as?: ElementType\n /** Additional CSS classes */\n className?: string\n /** Content */\n children: ReactNode\n}\n\nconst variantStyles: Record<TypographyVariant, string> = {\n 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',\n 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',\n 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',\n 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',\n h5: 'ui:font-roboto ui:text-sm ui:sm:text-base ui:md:text-lg ui:font-medium ui:leading-normal ui:text-foreground',\n h6: 'ui:font-roboto ui:text-xs ui:sm:text-sm ui:md:text-base ui:font-medium ui:leading-normal ui:text-foreground',\n body: 'ui:font-inter ui:text-xs ui:sm:text-sm ui:md:text-base ui:leading-relaxed ui:text-foreground',\n 'body-sm':\n 'ui:font-inter ui:text-xs ui:sm:text-sm ui:leading-relaxed ui:text-foreground',\n 'body-lg':\n 'ui:font-inter ui:text-sm ui:sm:text-base ui:md:text-lg ui:leading-relaxed ui:text-foreground',\n 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',\n caption: 'ui:font-inter ui:text-xs ui:sm:text-sm ui:text-muted-foreground',\n 'caption-xs': 'ui:font-inter ui:text-xs ui:text-muted-foreground',\n label:\n 'ui:font-inter ui:text-xs ui:sm:text-sm ui:font-medium ui:text-foreground',\n muted: 'ui:font-inter ui:text-xs ui:sm:text-sm ui:text-muted-foreground',\n blockquote:\n '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',\n}\n\nconst variantToTag: Record<TypographyVariant, ElementType> = {\n h1: 'h1',\n h2: 'h2',\n h3: 'h3',\n h4: 'h4',\n h5: 'h5',\n h6: 'h6',\n body: 'p',\n 'body-sm': 'p',\n 'body-lg': 'p',\n lead: 'p',\n caption: 'span',\n 'caption-xs': 'span',\n label: 'label',\n muted: 'p',\n blockquote: 'blockquote',\n}\n\nfunction Typography({\n variant,\n as,\n className,\n children,\n ...rest\n}: Readonly<TypographyProps>) {\n const Component = as ?? variantToTag[variant]\n\n return createElement(\n Component,\n { className: clsx(variantStyles[variant], className), ...rest },\n children\n )\n}\n\nexport default Typography\n","import clsx from 'clsx'\n\nimport type { ImageLoading } from '../Image'\n\nexport interface MovieCardBaseProps {\n /** Media ID */\n id: number\n /** Media title */\n title: string\n /** Poster URL (full URL or TMDB path) */\n posterUrl: string\n /** Vote average (0-10) */\n voteAverage: number\n /** Release year */\n year?: number | null\n /** Additional class name */\n className?: string\n /** Image loading strategy ('lazy' | 'eager'). Default: 'lazy' */\n imageLoading?: ImageLoading\n}\n\nexport interface MovieCardAsCard extends MovieCardBaseProps {\n as?: 'card' | undefined\n onClick?: never\n}\n\nexport interface MovieCardAsButton extends MovieCardBaseProps {\n as: 'button'\n onClick: () => void\n}\n\nexport function getMovieCardClasses(\n isInteractive: boolean,\n className?: string\n) {\n return clsx(\n 'ui:group ui:relative ui:flex ui:flex-col ui:overflow-hidden',\n isInteractive && [\n 'ui:cursor-pointer',\n 'ui:transition-transform ui:duration-200',\n 'hover:ui:scale-[1.02]',\n ],\n className\n )\n}\n\nexport function getMovieCardLinkClasses() {\n return 'ui:block ui:no-underline ui:text-inherit'\n}\n","import { Card } from '../Card'\nimport { Image } from '../Image'\nimport { Rating } from '../Rating'\nimport { Typography } from '../Typography'\n\nimport { getMovieCardClasses } from './MovieCard.utils'\n\nimport type { ImageLoading } from '../Image'\nimport type { ReactNode } from 'react'\n\ninterface MovieCardContentProps {\n id: number\n title: string\n posterUrl: string\n voteAverage: number\n year?: number | null\n className?: string\n imageLoading?: ImageLoading\n isInteractive: boolean\n onClick?: (() => void) | undefined\n}\n\nexport default function MovieCardContent({\n id,\n title,\n posterUrl,\n voteAverage,\n year,\n className,\n imageLoading = 'lazy',\n isInteractive,\n onClick,\n}: Readonly<MovieCardContentProps>): ReactNode {\n return (\n <Card\n variant=\"ghost\"\n className={getMovieCardClasses(isInteractive, className)}\n onClick={onClick}\n data-testid={`movie-card-${String(id)}`}\n >\n <div className=\"ui:relative ui:aspect-[2/3] ui:w-full ui:overflow-hidden ui:rounded-md ui:bg-gray-200\">\n <Image\n src={posterUrl}\n alt={title}\n loading={imageLoading}\n aspectRatio={undefined}\n className=\"ui:h-full ui:w-full ui:object-cover\"\n />\n <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\">\n <Rating\n value={voteAverage}\n size=\"sm\"\n variant=\"circle\"\n trackClassName=\"ui:text-gray-200\"\n className=\"ui:drop-shadow\"\n />\n </div>\n </div>\n\n <div className=\"ui:mt-2 ui:flex ui:flex-col ui:gap-0.5 ui:px-1\">\n <Typography\n variant=\"label\"\n as=\"h3\"\n className=\"ui:line-clamp-2\"\n title={title}\n >\n {title}\n </Typography>\n {year && (\n <Typography\n variant=\"caption-xs\"\n className=\"ui:[.media-section:nth-of-type(odd)_&]:text-badge-foreground\"\n >\n {year}\n </Typography>\n )}\n </div>\n </Card>\n )\n}\n"]}
|