@alkimi.org/ui-kit 0.1.8 → 0.1.9
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/README.github.md +62 -10
- package/dist/components/GlitchLink.d.mts +12 -0
- package/dist/components/GlitchLink.d.ts +12 -0
- package/dist/components/GlitchLink.js +74 -0
- package/dist/components/GlitchLink.js.map +1 -0
- package/dist/components/GlitchLink.mjs +44 -0
- package/dist/components/GlitchLink.mjs.map +1 -0
- package/dist/components/PixelLoad.d.mts +20 -0
- package/dist/components/PixelLoad.d.ts +20 -0
- package/dist/components/PixelLoad.js +182 -0
- package/dist/components/PixelLoad.js.map +1 -0
- package/dist/components/PixelLoad.mjs +148 -0
- package/dist/components/PixelLoad.mjs.map +1 -0
- package/dist/components/TextDecoder.d.mts +15 -0
- package/dist/components/TextDecoder.d.ts +15 -0
- package/dist/components/TextDecoder.js +293 -0
- package/dist/components/TextDecoder.js.map +1 -0
- package/dist/components/TextDecoder.mjs +265 -0
- package/dist/components/TextDecoder.mjs.map +1 -0
- package/dist/components/button.d.mts +14 -0
- package/dist/components/button.d.ts +14 -0
- package/dist/components/button.js +95 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/button.mjs +60 -0
- package/dist/components/button.mjs.map +1 -0
- package/dist/components/card.d.mts +10 -0
- package/dist/components/card.d.ts +10 -0
- package/dist/components/card.js +115 -0
- package/dist/components/card.js.map +1 -0
- package/dist/components/card.mjs +76 -0
- package/dist/components/card.mjs.map +1 -0
- package/dist/index.css +172 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +12 -24
- package/dist/index.d.ts +12 -24
- package/dist/index.js +420 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +423 -0
- package/dist/index.mjs.map +1 -1
- package/dist/lib/utils.d.mts +5 -0
- package/dist/lib/utils.d.ts +5 -0
- package/dist/lib/utils.js +36 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/utils.mjs +12 -0
- package/dist/lib/utils.mjs.map +1 -0
- package/dist/styles.css +172 -0
- package/dist/styles.css.map +1 -1
- package/package.json +41 -12
package/README.github.md
CHANGED
|
@@ -140,10 +140,59 @@ Or if you prefer, add the CSS variables to your own CSS file:
|
|
|
140
140
|
|
|
141
141
|
## Usage
|
|
142
142
|
|
|
143
|
+
You can import components in two ways:
|
|
144
|
+
|
|
145
|
+
### Option 1: Import from Main Package (All Components)
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
import { Button, Card } from "@alkimi.org/ui-kit"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Option 2: Import Individual Components (Tree-Shakeable)
|
|
152
|
+
|
|
153
|
+
For better tree-shaking and smaller bundle sizes, import components individually:
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
// Import only the Button component
|
|
157
|
+
import { Button } from "@alkimi.org/ui-kit/button"
|
|
158
|
+
|
|
159
|
+
// Import only the Card components
|
|
160
|
+
import { Card, CardHeader, CardTitle } from "@alkimi.org/ui-kit/card"
|
|
161
|
+
|
|
162
|
+
// Import utilities
|
|
163
|
+
import { cn } from "@alkimi.org/ui-kit/utils"
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Available Component Paths
|
|
167
|
+
|
|
168
|
+
- `@alkimi.org/ui-kit/button` - Button component
|
|
169
|
+
- `@alkimi.org/ui-kit/card` - Card components
|
|
170
|
+
- `@alkimi.org/ui-kit/text-decoder` - TextDecoder component
|
|
171
|
+
- `@alkimi.org/ui-kit/glitch-link` - GlitchLink component (requires Next.js)
|
|
172
|
+
- `@alkimi.org/ui-kit/pixel-load` - PixelLoad component (requires Next.js)
|
|
173
|
+
- `@alkimi.org/ui-kit/utils` - Utility functions
|
|
174
|
+
|
|
175
|
+
### Next.js Components
|
|
176
|
+
|
|
177
|
+
Some components require Next.js to be installed:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
npm install next
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Then you can use:
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
import GlitchLink from "@alkimi.org/ui-kit/glitch-link"
|
|
187
|
+
import { PixelLoad } from "@alkimi.org/ui-kit/pixel-load"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
> **Note:** If you're not using Next.js, you can still use all other components. Next.js is marked as an optional peer dependency.
|
|
191
|
+
|
|
143
192
|
### Button Component
|
|
144
193
|
|
|
145
194
|
```tsx
|
|
146
|
-
import { Button } from "@alkimi/ui-kit"
|
|
195
|
+
import { Button } from "@alkimi.org/ui-kit/button"
|
|
147
196
|
|
|
148
197
|
function App() {
|
|
149
198
|
return (
|
|
@@ -171,8 +220,8 @@ import {
|
|
|
171
220
|
CardDescription,
|
|
172
221
|
CardContent,
|
|
173
222
|
CardFooter,
|
|
174
|
-
|
|
175
|
-
} from "@alkimi/ui-kit"
|
|
223
|
+
} from "@alkimi.org/ui-kit/card"
|
|
224
|
+
import { Button } from "@alkimi.org/ui-kit/button"
|
|
176
225
|
|
|
177
226
|
function App() {
|
|
178
227
|
return (
|
|
@@ -212,8 +261,8 @@ To add more shadcn/ui components to this library:
|
|
|
212
261
|
|
|
213
262
|
```tsx
|
|
214
263
|
// stories/ComponentName.stories.tsx
|
|
215
|
-
import type { Meta, StoryObj } from "@storybook/react"
|
|
216
|
-
import { ComponentName } from "../src/components/component-name"
|
|
264
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
265
|
+
import { ComponentName } from "../src/components/component-name"
|
|
217
266
|
|
|
218
267
|
const meta = {
|
|
219
268
|
title: "Components/ComponentName",
|
|
@@ -222,24 +271,26 @@ To add more shadcn/ui components to this library:
|
|
|
222
271
|
layout: "centered",
|
|
223
272
|
},
|
|
224
273
|
tags: ["autodocs"],
|
|
225
|
-
} satisfies Meta<typeof ComponentName
|
|
274
|
+
} satisfies Meta<typeof ComponentName>
|
|
226
275
|
|
|
227
|
-
export default meta
|
|
228
|
-
type Story = StoryObj<typeof meta
|
|
276
|
+
export default meta
|
|
277
|
+
type Story = StoryObj<typeof meta>
|
|
229
278
|
|
|
230
279
|
export const Default: Story = {
|
|
231
280
|
args: {
|
|
232
281
|
// your component props
|
|
233
282
|
},
|
|
234
|
-
}
|
|
283
|
+
}
|
|
235
284
|
```
|
|
236
285
|
|
|
237
286
|
4. Test locally with Storybook:
|
|
287
|
+
|
|
238
288
|
```bash
|
|
239
289
|
npm run storybook
|
|
240
290
|
```
|
|
241
291
|
|
|
242
292
|
5. Commit and push to main:
|
|
293
|
+
|
|
243
294
|
```bash
|
|
244
295
|
git add .
|
|
245
296
|
git commit -m "feat: add ComponentName"
|
|
@@ -247,6 +298,7 @@ To add more shadcn/ui components to this library:
|
|
|
247
298
|
```
|
|
248
299
|
|
|
249
300
|
6. Update the Storybook deployment:
|
|
301
|
+
|
|
250
302
|
```bash
|
|
251
303
|
git checkout storybook
|
|
252
304
|
git merge main
|
|
@@ -275,7 +327,7 @@ npm install
|
|
|
275
327
|
npm run dev
|
|
276
328
|
```
|
|
277
329
|
|
|
278
|
-
Then open [http://localhost:
|
|
330
|
+
Then open [http://localhost:3000](http://localhost:3000) in your browser.
|
|
279
331
|
|
|
280
332
|
The demo showcases all available components with interactive examples, including:
|
|
281
333
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { RefAttributes, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type GlitchLinkProps = RefAttributes<HTMLAnchorElement> & {
|
|
5
|
+
href: string;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
symbols?: string[];
|
|
8
|
+
className?: string;
|
|
9
|
+
};
|
|
10
|
+
declare const GlitchLink: ({ href, children, symbols, className, ...props }: GlitchLinkProps) => react_jsx_runtime.JSX.Element;
|
|
11
|
+
|
|
12
|
+
export { GlitchLink as default };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { RefAttributes, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type GlitchLinkProps = RefAttributes<HTMLAnchorElement> & {
|
|
5
|
+
href: string;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
symbols?: string[];
|
|
8
|
+
className?: string;
|
|
9
|
+
};
|
|
10
|
+
declare const GlitchLink: ({ href, children, symbols, className, ...props }: GlitchLinkProps) => react_jsx_runtime.JSX.Element;
|
|
11
|
+
|
|
12
|
+
export { GlitchLink as default };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
"use strict";
|
|
3
|
+
"use client";
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __export = (target, all) => {
|
|
11
|
+
for (var name in all)
|
|
12
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
23
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
24
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
25
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
26
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
27
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
28
|
+
mod
|
|
29
|
+
));
|
|
30
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
31
|
+
|
|
32
|
+
// src/components/GlitchLink.tsx
|
|
33
|
+
var GlitchLink_exports = {};
|
|
34
|
+
__export(GlitchLink_exports, {
|
|
35
|
+
default: () => GlitchLink_default
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(GlitchLink_exports);
|
|
38
|
+
var import_link = __toESM(require("next/link"));
|
|
39
|
+
var import_react = require("react");
|
|
40
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
41
|
+
var DEFAULT_SYMBOLS = ["/", "#", "*"];
|
|
42
|
+
var GlitchLink = ({
|
|
43
|
+
href,
|
|
44
|
+
children,
|
|
45
|
+
symbols = DEFAULT_SYMBOLS,
|
|
46
|
+
className = "underline",
|
|
47
|
+
...props
|
|
48
|
+
}) => {
|
|
49
|
+
const [isHovering, setIsHovering] = (0, import_react.useState)(false);
|
|
50
|
+
const text = typeof children === "string" ? children : "";
|
|
51
|
+
const getRandomGlitch = () => {
|
|
52
|
+
if (!isHovering || !text) return children;
|
|
53
|
+
return text.split(" ").map((word) => {
|
|
54
|
+
const randomLetterIndex = Math.floor(Math.random() * word.length);
|
|
55
|
+
return word.replace(
|
|
56
|
+
word[randomLetterIndex],
|
|
57
|
+
symbols[Math.floor(Math.random() * symbols.length)]
|
|
58
|
+
);
|
|
59
|
+
}).join(" ");
|
|
60
|
+
};
|
|
61
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
62
|
+
import_link.default,
|
|
63
|
+
{
|
|
64
|
+
href,
|
|
65
|
+
className,
|
|
66
|
+
onMouseEnter: () => setIsHovering(true),
|
|
67
|
+
onMouseLeave: () => setIsHovering(false),
|
|
68
|
+
...props,
|
|
69
|
+
children: text ? isHovering ? getRandomGlitch() : children : children
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
var GlitchLink_default = GlitchLink;
|
|
74
|
+
//# sourceMappingURL=GlitchLink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/GlitchLink.tsx"],"sourcesContent":["\"use client\"\nimport Link from \"next/link\"\nimport { RefAttributes, useState, ReactNode } from \"react\"\n\ntype GlitchLinkProps = RefAttributes<HTMLAnchorElement> & {\n href: string\n children: ReactNode\n symbols?: string[]\n className?: string\n}\n\nconst DEFAULT_SYMBOLS = [\"/\", \"#\", \"*\"]\n\nconst GlitchLink = ({\n href,\n children,\n symbols = DEFAULT_SYMBOLS,\n className = \"underline\",\n ...props\n}: GlitchLinkProps) => {\n const [isHovering, setIsHovering] = useState(false)\n\n const text = typeof children === \"string\" ? children : \"\"\n\n const getRandomGlitch = () => {\n if (!isHovering || !text) return children\n return text\n .split(\" \")\n .map((word: string) => {\n // take one random letter from the word\n const randomLetterIndex = Math.floor(Math.random() * word.length)\n\n // replace the letter with a random symbol\n return word.replace(\n word[randomLetterIndex],\n symbols[Math.floor(Math.random() * symbols.length)]\n )\n })\n .join(\" \")\n }\n\n return (\n <Link\n href={href}\n className={className}\n onMouseEnter={() => setIsHovering(true)}\n onMouseLeave={() => setIsHovering(false)}\n {...props}\n >\n {text ? (isHovering ? getRandomGlitch() : children) : children}\n </Link>\n )\n}\n\nexport default GlitchLink\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,kBAAiB;AACjB,mBAAmD;AAwC/C;AA/BJ,IAAM,kBAAkB,CAAC,KAAK,KAAK,GAAG;AAEtC,IAAM,aAAa,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,GAAG;AACL,MAAuB;AACrB,QAAM,CAAC,YAAY,aAAa,QAAI,uBAAS,KAAK;AAElD,QAAM,OAAO,OAAO,aAAa,WAAW,WAAW;AAEvD,QAAM,kBAAkB,MAAM;AAC5B,QAAI,CAAC,cAAc,CAAC,KAAM,QAAO;AACjC,WAAO,KACJ,MAAM,GAAG,EACT,IAAI,CAAC,SAAiB;AAErB,YAAM,oBAAoB,KAAK,MAAM,KAAK,OAAO,IAAI,KAAK,MAAM;AAGhE,aAAO,KAAK;AAAA,QACV,KAAK,iBAAiB;AAAA,QACtB,QAAQ,KAAK,MAAM,KAAK,OAAO,IAAI,QAAQ,MAAM,CAAC;AAAA,MACpD;AAAA,IACF,CAAC,EACA,KAAK,GAAG;AAAA,EACb;AAEA,SACE;AAAA,IAAC,YAAAA;AAAA,IAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA,cAAc,MAAM,cAAc,IAAI;AAAA,MACtC,cAAc,MAAM,cAAc,KAAK;AAAA,MACtC,GAAG;AAAA,MAEH,iBAAQ,aAAa,gBAAgB,IAAI,WAAY;AAAA;AAAA,EACxD;AAEJ;AAEA,IAAO,qBAAQ;","names":["Link"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/components/GlitchLink.tsx
|
|
5
|
+
import Link from "next/link";
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
var DEFAULT_SYMBOLS = ["/", "#", "*"];
|
|
9
|
+
var GlitchLink = ({
|
|
10
|
+
href,
|
|
11
|
+
children,
|
|
12
|
+
symbols = DEFAULT_SYMBOLS,
|
|
13
|
+
className = "underline",
|
|
14
|
+
...props
|
|
15
|
+
}) => {
|
|
16
|
+
const [isHovering, setIsHovering] = useState(false);
|
|
17
|
+
const text = typeof children === "string" ? children : "";
|
|
18
|
+
const getRandomGlitch = () => {
|
|
19
|
+
if (!isHovering || !text) return children;
|
|
20
|
+
return text.split(" ").map((word) => {
|
|
21
|
+
const randomLetterIndex = Math.floor(Math.random() * word.length);
|
|
22
|
+
return word.replace(
|
|
23
|
+
word[randomLetterIndex],
|
|
24
|
+
symbols[Math.floor(Math.random() * symbols.length)]
|
|
25
|
+
);
|
|
26
|
+
}).join(" ");
|
|
27
|
+
};
|
|
28
|
+
return /* @__PURE__ */ jsx(
|
|
29
|
+
Link,
|
|
30
|
+
{
|
|
31
|
+
href,
|
|
32
|
+
className,
|
|
33
|
+
onMouseEnter: () => setIsHovering(true),
|
|
34
|
+
onMouseLeave: () => setIsHovering(false),
|
|
35
|
+
...props,
|
|
36
|
+
children: text ? isHovering ? getRandomGlitch() : children : children
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
var GlitchLink_default = GlitchLink;
|
|
41
|
+
export {
|
|
42
|
+
GlitchLink_default as default
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=GlitchLink.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/GlitchLink.tsx"],"sourcesContent":["\"use client\"\nimport Link from \"next/link\"\nimport { RefAttributes, useState, ReactNode } from \"react\"\n\ntype GlitchLinkProps = RefAttributes<HTMLAnchorElement> & {\n href: string\n children: ReactNode\n symbols?: string[]\n className?: string\n}\n\nconst DEFAULT_SYMBOLS = [\"/\", \"#\", \"*\"]\n\nconst GlitchLink = ({\n href,\n children,\n symbols = DEFAULT_SYMBOLS,\n className = \"underline\",\n ...props\n}: GlitchLinkProps) => {\n const [isHovering, setIsHovering] = useState(false)\n\n const text = typeof children === \"string\" ? children : \"\"\n\n const getRandomGlitch = () => {\n if (!isHovering || !text) return children\n return text\n .split(\" \")\n .map((word: string) => {\n // take one random letter from the word\n const randomLetterIndex = Math.floor(Math.random() * word.length)\n\n // replace the letter with a random symbol\n return word.replace(\n word[randomLetterIndex],\n symbols[Math.floor(Math.random() * symbols.length)]\n )\n })\n .join(\" \")\n }\n\n return (\n <Link\n href={href}\n className={className}\n onMouseEnter={() => setIsHovering(true)}\n onMouseLeave={() => setIsHovering(false)}\n {...props}\n >\n {text ? (isHovering ? getRandomGlitch() : children) : children}\n </Link>\n )\n}\n\nexport default GlitchLink\n"],"mappings":";;;;AACA,OAAO,UAAU;AACjB,SAAwB,gBAA2B;AAwC/C;AA/BJ,IAAM,kBAAkB,CAAC,KAAK,KAAK,GAAG;AAEtC,IAAM,aAAa,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,GAAG;AACL,MAAuB;AACrB,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAElD,QAAM,OAAO,OAAO,aAAa,WAAW,WAAW;AAEvD,QAAM,kBAAkB,MAAM;AAC5B,QAAI,CAAC,cAAc,CAAC,KAAM,QAAO;AACjC,WAAO,KACJ,MAAM,GAAG,EACT,IAAI,CAAC,SAAiB;AAErB,YAAM,oBAAoB,KAAK,MAAM,KAAK,OAAO,IAAI,KAAK,MAAM;AAGhE,aAAO,KAAK;AAAA,QACV,KAAK,iBAAiB;AAAA,QACtB,QAAQ,KAAK,MAAM,KAAK,OAAO,IAAI,QAAQ,MAAM,CAAC;AAAA,MACpD;AAAA,IACF,CAAC,EACA,KAAK,GAAG;AAAA,EACb;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA,cAAc,MAAM,cAAc,IAAI;AAAA,MACtC,cAAc,MAAM,cAAc,KAAK;AAAA,MACtC,GAAG;AAAA,MAEH,iBAAQ,aAAa,gBAAgB,IAAI,WAAY;AAAA;AAAA,EACxD;AAEJ;AAEA,IAAO,qBAAQ;","names":[]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { StaticImageData } from 'next/image';
|
|
2
|
+
import { JSX } from 'react';
|
|
3
|
+
|
|
4
|
+
type ImageSrc = string | StaticImageData;
|
|
5
|
+
interface PixelLoadProps {
|
|
6
|
+
src: ImageSrc;
|
|
7
|
+
alt: string;
|
|
8
|
+
duration?: number;
|
|
9
|
+
steps?: number;
|
|
10
|
+
className?: string;
|
|
11
|
+
onAnimationComplete?: () => void;
|
|
12
|
+
priority?: boolean;
|
|
13
|
+
quality?: number;
|
|
14
|
+
placeholder?: "blur" | "empty";
|
|
15
|
+
blurDataURL?: string;
|
|
16
|
+
objectFit?: "contain" | "cover" | "fill" | "none" | "scale-down";
|
|
17
|
+
}
|
|
18
|
+
declare function PixelLoad({ src, alt, duration, steps, className, onAnimationComplete, priority, quality, placeholder, blurDataURL, objectFit, }: PixelLoadProps): JSX.Element;
|
|
19
|
+
|
|
20
|
+
export { PixelLoad, type PixelLoadProps };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { StaticImageData } from 'next/image';
|
|
2
|
+
import { JSX } from 'react';
|
|
3
|
+
|
|
4
|
+
type ImageSrc = string | StaticImageData;
|
|
5
|
+
interface PixelLoadProps {
|
|
6
|
+
src: ImageSrc;
|
|
7
|
+
alt: string;
|
|
8
|
+
duration?: number;
|
|
9
|
+
steps?: number;
|
|
10
|
+
className?: string;
|
|
11
|
+
onAnimationComplete?: () => void;
|
|
12
|
+
priority?: boolean;
|
|
13
|
+
quality?: number;
|
|
14
|
+
placeholder?: "blur" | "empty";
|
|
15
|
+
blurDataURL?: string;
|
|
16
|
+
objectFit?: "contain" | "cover" | "fill" | "none" | "scale-down";
|
|
17
|
+
}
|
|
18
|
+
declare function PixelLoad({ src, alt, duration, steps, className, onAnimationComplete, priority, quality, placeholder, blurDataURL, objectFit, }: PixelLoadProps): JSX.Element;
|
|
19
|
+
|
|
20
|
+
export { PixelLoad, type PixelLoadProps };
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
"use strict";
|
|
3
|
+
"use client";
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __export = (target, all) => {
|
|
11
|
+
for (var name in all)
|
|
12
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
23
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
24
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
25
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
26
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
27
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
28
|
+
mod
|
|
29
|
+
));
|
|
30
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
31
|
+
|
|
32
|
+
// src/components/PixelLoad.tsx
|
|
33
|
+
var PixelLoad_exports = {};
|
|
34
|
+
__export(PixelLoad_exports, {
|
|
35
|
+
PixelLoad: () => PixelLoad
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(PixelLoad_exports);
|
|
38
|
+
var import_image = __toESM(require("next/image"));
|
|
39
|
+
var import_react = require("react");
|
|
40
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
41
|
+
function isStaticImageData(src) {
|
|
42
|
+
return typeof src === "object" && "src" in src;
|
|
43
|
+
}
|
|
44
|
+
function getImageSrc(src) {
|
|
45
|
+
return isStaticImageData(src) ? src.src : src;
|
|
46
|
+
}
|
|
47
|
+
function PixelLoad({
|
|
48
|
+
src,
|
|
49
|
+
alt,
|
|
50
|
+
duration = 600,
|
|
51
|
+
steps = 15,
|
|
52
|
+
className,
|
|
53
|
+
onAnimationComplete,
|
|
54
|
+
priority,
|
|
55
|
+
quality,
|
|
56
|
+
placeholder = "empty",
|
|
57
|
+
blurDataURL,
|
|
58
|
+
objectFit = "cover"
|
|
59
|
+
}) {
|
|
60
|
+
const containerRef = (0, import_react.useRef)(null);
|
|
61
|
+
const canvasRef = (0, import_react.useRef)(null);
|
|
62
|
+
const [status, setStatus] = (0, import_react.useState)("loading");
|
|
63
|
+
const [dimensions, setDimensions] = (0, import_react.useState)(null);
|
|
64
|
+
const imageSrc = getImageSrc(src);
|
|
65
|
+
const drawPixelated = (0, import_react.useCallback)(
|
|
66
|
+
(ctx, img, pixelSize, w, h) => {
|
|
67
|
+
ctx.imageSmoothingEnabled = false;
|
|
68
|
+
const sw = Math.max(1, Math.floor(w / pixelSize));
|
|
69
|
+
const sh = Math.max(1, Math.floor(h / pixelSize));
|
|
70
|
+
ctx.clearRect(0, 0, w, h);
|
|
71
|
+
ctx.drawImage(img, 0, 0, sw, sh);
|
|
72
|
+
ctx.drawImage(canvasRef.current, 0, 0, sw, sh, 0, 0, w, h);
|
|
73
|
+
},
|
|
74
|
+
[]
|
|
75
|
+
);
|
|
76
|
+
const runAnimation = (0, import_react.useCallback)(
|
|
77
|
+
(ctx, img, w, h) => {
|
|
78
|
+
const minPixelSize = Math.max(w, h) / 4;
|
|
79
|
+
const intervalMs = duration / steps;
|
|
80
|
+
let currentStep = 0;
|
|
81
|
+
setStatus("animating");
|
|
82
|
+
const animate = () => {
|
|
83
|
+
const progress = currentStep / steps;
|
|
84
|
+
const pixelSize = Math.max(1, Math.floor(minPixelSize * (1 - progress)));
|
|
85
|
+
drawPixelated(ctx, img, pixelSize, w, h);
|
|
86
|
+
currentStep++;
|
|
87
|
+
if (currentStep <= steps) {
|
|
88
|
+
window.setTimeout(animate, intervalMs);
|
|
89
|
+
} else {
|
|
90
|
+
setStatus("complete");
|
|
91
|
+
onAnimationComplete?.();
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
animate();
|
|
95
|
+
},
|
|
96
|
+
[duration, steps, drawPixelated, onAnimationComplete]
|
|
97
|
+
);
|
|
98
|
+
(0, import_react.useEffect)(() => {
|
|
99
|
+
const container = containerRef.current;
|
|
100
|
+
if (!container) return;
|
|
101
|
+
const updateDimensions = () => {
|
|
102
|
+
const { width, height } = container.getBoundingClientRect();
|
|
103
|
+
setDimensions({ width: Math.floor(width), height: Math.floor(height) });
|
|
104
|
+
};
|
|
105
|
+
const resizeObserver = new ResizeObserver(updateDimensions);
|
|
106
|
+
resizeObserver.observe(container);
|
|
107
|
+
updateDimensions();
|
|
108
|
+
return () => {
|
|
109
|
+
resizeObserver.disconnect();
|
|
110
|
+
};
|
|
111
|
+
}, []);
|
|
112
|
+
(0, import_react.useEffect)(() => {
|
|
113
|
+
if (!dimensions) return;
|
|
114
|
+
if (dimensions.width <= 0 || dimensions.height <= 0) return;
|
|
115
|
+
const canvas = canvasRef.current;
|
|
116
|
+
if (!canvas) return;
|
|
117
|
+
const ctx = canvas.getContext("2d");
|
|
118
|
+
if (!ctx) return;
|
|
119
|
+
canvas.width = dimensions.width;
|
|
120
|
+
canvas.height = dimensions.height;
|
|
121
|
+
const img = new window.Image();
|
|
122
|
+
img.crossOrigin = "anonymous";
|
|
123
|
+
img.onload = () => {
|
|
124
|
+
const minPixelSize = Math.max(dimensions.width, dimensions.height) / 4;
|
|
125
|
+
drawPixelated(ctx, img, minPixelSize, dimensions.width, dimensions.height);
|
|
126
|
+
runAnimation(ctx, img, dimensions.width, dimensions.height);
|
|
127
|
+
};
|
|
128
|
+
img.onerror = () => {
|
|
129
|
+
console.error("PixelLoad: Failed to load image");
|
|
130
|
+
};
|
|
131
|
+
img.src = imageSrc;
|
|
132
|
+
return () => {
|
|
133
|
+
img.onload = null;
|
|
134
|
+
img.onerror = null;
|
|
135
|
+
};
|
|
136
|
+
}, [imageSrc, dimensions, runAnimation, drawPixelated]);
|
|
137
|
+
const isComplete = status === "complete";
|
|
138
|
+
const isAnimating = status === "animating" || status === "loading";
|
|
139
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
140
|
+
"div",
|
|
141
|
+
{
|
|
142
|
+
ref: containerRef,
|
|
143
|
+
className: `relative w-full h-full ${className || ""}`,
|
|
144
|
+
children: [
|
|
145
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
146
|
+
"canvas",
|
|
147
|
+
{
|
|
148
|
+
ref: canvasRef,
|
|
149
|
+
className: "absolute top-0 left-0 w-full h-full",
|
|
150
|
+
style: {
|
|
151
|
+
objectFit,
|
|
152
|
+
opacity: isComplete ? 0 : 1,
|
|
153
|
+
zIndex: isAnimating ? 2 : 1
|
|
154
|
+
},
|
|
155
|
+
"aria-hidden": isComplete
|
|
156
|
+
}
|
|
157
|
+
),
|
|
158
|
+
isComplete && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
159
|
+
import_image.default,
|
|
160
|
+
{
|
|
161
|
+
src,
|
|
162
|
+
alt,
|
|
163
|
+
fill: true,
|
|
164
|
+
priority,
|
|
165
|
+
quality,
|
|
166
|
+
placeholder,
|
|
167
|
+
blurDataURL,
|
|
168
|
+
className: "opacity-100 transition-opacity duration-150 ease-out z-[1]",
|
|
169
|
+
style: {
|
|
170
|
+
objectFit
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
179
|
+
0 && (module.exports = {
|
|
180
|
+
PixelLoad
|
|
181
|
+
});
|
|
182
|
+
//# sourceMappingURL=PixelLoad.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/PixelLoad.tsx"],"sourcesContent":["\"use client\"\n\nimport Image, { type StaticImageData } from \"next/image\"\nimport { JSX, useCallback, useEffect, useRef, useState } from \"react\"\n\ntype PixelLoadStatus = \"loading\" | \"animating\" | \"complete\"\n\ntype ImageSrc = string | StaticImageData\n\nexport interface PixelLoadProps {\n src: ImageSrc\n alt: string\n duration?: number\n steps?: number\n className?: string\n onAnimationComplete?: () => void\n priority?: boolean\n quality?: number\n placeholder?: \"blur\" | \"empty\"\n blurDataURL?: string\n objectFit?: \"contain\" | \"cover\" | \"fill\" | \"none\" | \"scale-down\"\n}\n\nfunction isStaticImageData(src: ImageSrc): src is StaticImageData {\n return typeof src === \"object\" && \"src\" in src\n}\n\nfunction getImageSrc(src: ImageSrc): string {\n return isStaticImageData(src) ? src.src : src\n}\n\nexport function PixelLoad({\n src,\n alt,\n duration = 600,\n steps = 15,\n className,\n onAnimationComplete,\n priority,\n quality,\n placeholder = \"empty\",\n blurDataURL,\n objectFit = \"cover\",\n}: PixelLoadProps): JSX.Element {\n const containerRef = useRef<HTMLDivElement>(null)\n const canvasRef = useRef<HTMLCanvasElement>(null)\n const [status, setStatus] = useState<PixelLoadStatus>(\"loading\")\n const [dimensions, setDimensions] = useState<{\n width: number\n height: number\n } | null>(null)\n\n const imageSrc = getImageSrc(src)\n\n const drawPixelated = useCallback(\n (\n ctx: CanvasRenderingContext2D,\n img: HTMLImageElement,\n pixelSize: number,\n w: number,\n h: number\n ): void => {\n ctx.imageSmoothingEnabled = false\n\n const sw = Math.max(1, Math.floor(w / pixelSize))\n const sh = Math.max(1, Math.floor(h / pixelSize))\n\n ctx.clearRect(0, 0, w, h)\n ctx.drawImage(img, 0, 0, sw, sh)\n ctx.drawImage(canvasRef.current!, 0, 0, sw, sh, 0, 0, w, h)\n },\n []\n )\n\n const runAnimation = useCallback(\n (\n ctx: CanvasRenderingContext2D,\n img: HTMLImageElement,\n w: number,\n h: number\n ): void => {\n const minPixelSize = Math.max(w, h) / 4\n const intervalMs = duration / steps\n let currentStep = 0\n\n setStatus(\"animating\")\n\n const animate = (): void => {\n const progress = currentStep / steps\n const pixelSize = Math.max(1, Math.floor(minPixelSize * (1 - progress)))\n\n drawPixelated(ctx, img, pixelSize, w, h)\n\n currentStep++\n\n if (currentStep <= steps) {\n window.setTimeout(animate, intervalMs)\n } else {\n setStatus(\"complete\")\n onAnimationComplete?.()\n }\n }\n\n animate()\n },\n [duration, steps, drawPixelated, onAnimationComplete]\n )\n\n useEffect(() => {\n const container = containerRef.current\n if (!container) return\n\n const updateDimensions = (): void => {\n const { width, height } = container.getBoundingClientRect()\n setDimensions({ width: Math.floor(width), height: Math.floor(height) })\n }\n\n const resizeObserver = new ResizeObserver(updateDimensions)\n resizeObserver.observe(container)\n updateDimensions()\n\n return (): void => {\n resizeObserver.disconnect()\n }\n }, [])\n\n useEffect(() => {\n if (!dimensions) return\n\n // Guard against invalid dimensions\n if (dimensions.width <= 0 || dimensions.height <= 0) return\n\n const canvas = canvasRef.current\n if (!canvas) return\n\n const ctx = canvas.getContext(\"2d\")\n if (!ctx) return\n\n // Set canvas dimensions immediately to ensure it covers the area\n canvas.width = dimensions.width\n canvas.height = dimensions.height\n\n const img = new window.Image()\n img.crossOrigin = \"anonymous\"\n\n img.onload = (): void => {\n // Draw first frame immediately to prevent original image from showing\n const minPixelSize = Math.max(dimensions.width, dimensions.height) / 4\n drawPixelated(ctx, img, minPixelSize, dimensions.width, dimensions.height)\n // Then start animation\n runAnimation(ctx, img, dimensions.width, dimensions.height)\n }\n\n img.onerror = (): void => {\n console.error(\"PixelLoad: Failed to load image\")\n }\n\n img.src = imageSrc\n\n return (): void => {\n img.onload = null\n img.onerror = null\n }\n }, [imageSrc, dimensions, runAnimation, drawPixelated])\n\n const isComplete = status === \"complete\"\n const isAnimating = status === \"animating\" || status === \"loading\"\n\n return (\n <div\n ref={containerRef}\n className={`relative w-full h-full ${className || \"\"}`}\n >\n <canvas\n ref={canvasRef}\n className=\"absolute top-0 left-0 w-full h-full\"\n style={{\n objectFit,\n opacity: isComplete ? 0 : 1,\n zIndex: isAnimating ? 2 : 1,\n }}\n aria-hidden={isComplete}\n />\n {isComplete && (\n <Image\n src={src}\n alt={alt}\n fill\n priority={priority}\n quality={quality}\n placeholder={placeholder}\n blurDataURL={blurDataURL}\n className=\"opacity-100 transition-opacity duration-150 ease-out z-[1]\"\n style={{\n objectFit,\n }}\n />\n )}\n </div>\n )\n}\n"],"mappings":"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","names":["Image"]}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/components/PixelLoad.tsx
|
|
5
|
+
import Image from "next/image";
|
|
6
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
function isStaticImageData(src) {
|
|
9
|
+
return typeof src === "object" && "src" in src;
|
|
10
|
+
}
|
|
11
|
+
function getImageSrc(src) {
|
|
12
|
+
return isStaticImageData(src) ? src.src : src;
|
|
13
|
+
}
|
|
14
|
+
function PixelLoad({
|
|
15
|
+
src,
|
|
16
|
+
alt,
|
|
17
|
+
duration = 600,
|
|
18
|
+
steps = 15,
|
|
19
|
+
className,
|
|
20
|
+
onAnimationComplete,
|
|
21
|
+
priority,
|
|
22
|
+
quality,
|
|
23
|
+
placeholder = "empty",
|
|
24
|
+
blurDataURL,
|
|
25
|
+
objectFit = "cover"
|
|
26
|
+
}) {
|
|
27
|
+
const containerRef = useRef(null);
|
|
28
|
+
const canvasRef = useRef(null);
|
|
29
|
+
const [status, setStatus] = useState("loading");
|
|
30
|
+
const [dimensions, setDimensions] = useState(null);
|
|
31
|
+
const imageSrc = getImageSrc(src);
|
|
32
|
+
const drawPixelated = useCallback(
|
|
33
|
+
(ctx, img, pixelSize, w, h) => {
|
|
34
|
+
ctx.imageSmoothingEnabled = false;
|
|
35
|
+
const sw = Math.max(1, Math.floor(w / pixelSize));
|
|
36
|
+
const sh = Math.max(1, Math.floor(h / pixelSize));
|
|
37
|
+
ctx.clearRect(0, 0, w, h);
|
|
38
|
+
ctx.drawImage(img, 0, 0, sw, sh);
|
|
39
|
+
ctx.drawImage(canvasRef.current, 0, 0, sw, sh, 0, 0, w, h);
|
|
40
|
+
},
|
|
41
|
+
[]
|
|
42
|
+
);
|
|
43
|
+
const runAnimation = useCallback(
|
|
44
|
+
(ctx, img, w, h) => {
|
|
45
|
+
const minPixelSize = Math.max(w, h) / 4;
|
|
46
|
+
const intervalMs = duration / steps;
|
|
47
|
+
let currentStep = 0;
|
|
48
|
+
setStatus("animating");
|
|
49
|
+
const animate = () => {
|
|
50
|
+
const progress = currentStep / steps;
|
|
51
|
+
const pixelSize = Math.max(1, Math.floor(minPixelSize * (1 - progress)));
|
|
52
|
+
drawPixelated(ctx, img, pixelSize, w, h);
|
|
53
|
+
currentStep++;
|
|
54
|
+
if (currentStep <= steps) {
|
|
55
|
+
window.setTimeout(animate, intervalMs);
|
|
56
|
+
} else {
|
|
57
|
+
setStatus("complete");
|
|
58
|
+
onAnimationComplete?.();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
animate();
|
|
62
|
+
},
|
|
63
|
+
[duration, steps, drawPixelated, onAnimationComplete]
|
|
64
|
+
);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const container = containerRef.current;
|
|
67
|
+
if (!container) return;
|
|
68
|
+
const updateDimensions = () => {
|
|
69
|
+
const { width, height } = container.getBoundingClientRect();
|
|
70
|
+
setDimensions({ width: Math.floor(width), height: Math.floor(height) });
|
|
71
|
+
};
|
|
72
|
+
const resizeObserver = new ResizeObserver(updateDimensions);
|
|
73
|
+
resizeObserver.observe(container);
|
|
74
|
+
updateDimensions();
|
|
75
|
+
return () => {
|
|
76
|
+
resizeObserver.disconnect();
|
|
77
|
+
};
|
|
78
|
+
}, []);
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (!dimensions) return;
|
|
81
|
+
if (dimensions.width <= 0 || dimensions.height <= 0) return;
|
|
82
|
+
const canvas = canvasRef.current;
|
|
83
|
+
if (!canvas) return;
|
|
84
|
+
const ctx = canvas.getContext("2d");
|
|
85
|
+
if (!ctx) return;
|
|
86
|
+
canvas.width = dimensions.width;
|
|
87
|
+
canvas.height = dimensions.height;
|
|
88
|
+
const img = new window.Image();
|
|
89
|
+
img.crossOrigin = "anonymous";
|
|
90
|
+
img.onload = () => {
|
|
91
|
+
const minPixelSize = Math.max(dimensions.width, dimensions.height) / 4;
|
|
92
|
+
drawPixelated(ctx, img, minPixelSize, dimensions.width, dimensions.height);
|
|
93
|
+
runAnimation(ctx, img, dimensions.width, dimensions.height);
|
|
94
|
+
};
|
|
95
|
+
img.onerror = () => {
|
|
96
|
+
console.error("PixelLoad: Failed to load image");
|
|
97
|
+
};
|
|
98
|
+
img.src = imageSrc;
|
|
99
|
+
return () => {
|
|
100
|
+
img.onload = null;
|
|
101
|
+
img.onerror = null;
|
|
102
|
+
};
|
|
103
|
+
}, [imageSrc, dimensions, runAnimation, drawPixelated]);
|
|
104
|
+
const isComplete = status === "complete";
|
|
105
|
+
const isAnimating = status === "animating" || status === "loading";
|
|
106
|
+
return /* @__PURE__ */ jsxs(
|
|
107
|
+
"div",
|
|
108
|
+
{
|
|
109
|
+
ref: containerRef,
|
|
110
|
+
className: `relative w-full h-full ${className || ""}`,
|
|
111
|
+
children: [
|
|
112
|
+
/* @__PURE__ */ jsx(
|
|
113
|
+
"canvas",
|
|
114
|
+
{
|
|
115
|
+
ref: canvasRef,
|
|
116
|
+
className: "absolute top-0 left-0 w-full h-full",
|
|
117
|
+
style: {
|
|
118
|
+
objectFit,
|
|
119
|
+
opacity: isComplete ? 0 : 1,
|
|
120
|
+
zIndex: isAnimating ? 2 : 1
|
|
121
|
+
},
|
|
122
|
+
"aria-hidden": isComplete
|
|
123
|
+
}
|
|
124
|
+
),
|
|
125
|
+
isComplete && /* @__PURE__ */ jsx(
|
|
126
|
+
Image,
|
|
127
|
+
{
|
|
128
|
+
src,
|
|
129
|
+
alt,
|
|
130
|
+
fill: true,
|
|
131
|
+
priority,
|
|
132
|
+
quality,
|
|
133
|
+
placeholder,
|
|
134
|
+
blurDataURL,
|
|
135
|
+
className: "opacity-100 transition-opacity duration-150 ease-out z-[1]",
|
|
136
|
+
style: {
|
|
137
|
+
objectFit
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
export {
|
|
146
|
+
PixelLoad
|
|
147
|
+
};
|
|
148
|
+
//# sourceMappingURL=PixelLoad.mjs.map
|