@vite-mf-monorepo/ui 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{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/chunk-JPJYJLAP.js +38 -0
- package/dist/chunk-JPJYJLAP.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +31 -59
- package/dist/index.js.map +1 -1
- package/dist/next/index.d.ts +24 -4
- package/dist/next/index.js +178 -4
- package/dist/next/index.js.map +1 -1
- package/dist/react-router/index.d.ts +1 -1
- package/dist/react-router/index.js +3 -3
- 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"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/Skeleton/Skeleton.tsx
|
|
5
|
+
function Skeleton({
|
|
6
|
+
variant = "rectangle",
|
|
7
|
+
width,
|
|
8
|
+
height,
|
|
9
|
+
aspectRatio,
|
|
10
|
+
rounded = true,
|
|
11
|
+
className,
|
|
12
|
+
...rest
|
|
13
|
+
}) {
|
|
14
|
+
return /* @__PURE__ */ jsx(
|
|
15
|
+
"div",
|
|
16
|
+
{
|
|
17
|
+
className: clsx(
|
|
18
|
+
"ui:relative ui:overflow-hidden ui:bg-muted",
|
|
19
|
+
"ui-skeleton-shimmer",
|
|
20
|
+
{
|
|
21
|
+
"ui:rounded-lg": variant === "rectangle" && rounded,
|
|
22
|
+
"ui:rounded-full": variant === "circle",
|
|
23
|
+
"ui:rounded": variant === "line" && rounded
|
|
24
|
+
},
|
|
25
|
+
width,
|
|
26
|
+
height,
|
|
27
|
+
className
|
|
28
|
+
),
|
|
29
|
+
style: aspectRatio ? { aspectRatio } : void 0,
|
|
30
|
+
...rest
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
var Skeleton_default = Skeleton;
|
|
35
|
+
|
|
36
|
+
export { Skeleton_default };
|
|
37
|
+
//# sourceMappingURL=chunk-JPJYJLAP.js.map
|
|
38
|
+
//# sourceMappingURL=chunk-JPJYJLAP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/Skeleton/Skeleton.tsx"],"names":[],"mappings":";;;;AAsBA,SAAS,QAAA,CAAS;AAAA,EAChB,OAAA,GAAU,WAAA;AAAA,EACV,KAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA;AAAA,EACA,OAAA,GAAU,IAAA;AAAA,EACV,SAAA;AAAA,EACA,GAAG;AACL,CAAA,EAA4B;AAC1B,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,IAAA;AAAA,QACT,4CAAA;AAAA,QACA,qBAAA;AAAA,QACA;AAAA,UACE,eAAA,EAAiB,YAAY,WAAA,IAAe,OAAA;AAAA,UAC5C,mBAAmB,OAAA,KAAY,QAAA;AAAA,UAC/B,YAAA,EAAc,YAAY,MAAA,IAAU;AAAA,SACtC;AAAA,QACA,KAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,KAAA,EAAO,WAAA,GAAc,EAAE,WAAA,EAAY,GAAI,MAAA;AAAA,MACtC,GAAG;AAAA;AAAA,GACN;AAEJ;AAEA,IAAO,gBAAA,GAAQ","file":"chunk-JPJYJLAP.js","sourcesContent":["import clsx from 'clsx'\n\nimport type { ComponentProps } from 'react'\n\nexport interface SkeletonProps extends ComponentProps<'div'> {\n /** Shape variant */\n variant?: 'rectangle' | 'circle' | 'line'\n /** Width (Tailwind class or custom value) */\n width?: string\n /** Height (Tailwind class or custom value) */\n height?: string\n /** Aspect ratio (e.g., \"2/3\", \"16/9\", \"1/1\") */\n aspectRatio?: string\n /** Apply rounded corners (default: true for rectangle/line, always true for circle) */\n rounded?: boolean\n}\n\n/**\n * Skeleton - Atomic loading placeholder component\n *\n * Composable primitive for building loading states with shimmer effect.\n */\nfunction Skeleton({\n variant = 'rectangle',\n width,\n height,\n aspectRatio,\n rounded = true,\n className,\n ...rest\n}: Readonly<SkeletonProps>) {\n return (\n <div\n className={clsx(\n 'ui:relative ui:overflow-hidden ui:bg-muted',\n 'ui-skeleton-shimmer',\n {\n 'ui:rounded-lg': variant === 'rectangle' && rounded,\n 'ui:rounded-full': variant === 'circle',\n 'ui:rounded': variant === 'line' && rounded,\n },\n width,\n height,\n className\n )}\n style={aspectRatio ? { aspectRatio } : undefined}\n {...rest}\n />\n )\n}\n\nexport default Skeleton\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
|
|