skeleton-ghost-loader 1.0.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/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # skeletonloader
2
+
3
+ A **Tailwind CSS–based, structure-preserving skeleton loader for React**.
4
+
5
+ `skeletonloader` automatically converts your existing JSX into skeleton placeholders **while keeping the exact layout and styling**, by reusing the same `className` (Tailwind utilities).
6
+
7
+ ✅ No DOM measurement
8
+ ✅ No layout shift
9
+ ✅ SSR / Next.js safe
10
+ ✅ Zero config
11
+ ✅ Tailwind-native
12
+
13
+ ---
14
+
15
+ ## ✨ Features
16
+
17
+ - 🧱 **Preserves structure** – DOM tree stays the same
18
+ - 🎨 **Uses Tailwind classes** – width, height, radius come from `className`
19
+ - 🌗 **Dark mode support** – works with `dark:` utilities
20
+ - ⏸ **Disable animation** – optional pulse control
21
+ - ⛔ **Skip skeleton** – exclude specific components
22
+ - ⚡ **Lightweight** – no extra dependencies
23
+
24
+ ---
25
+
26
+ ## 📦 Installation
27
+
28
+ ```bash
29
+ npm install skeletonloader
package/index.js ADDED
@@ -0,0 +1 @@
1
+ export { default } from "./src";
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "skeleton-ghost-loader",
3
+ "version": "1.0.0",
4
+ "description": "Tailwind-based structure preserving skeleton loader for React",
5
+ "main": "index.js",
6
+ "files": [
7
+ "index.js",
8
+ "src"
9
+ ],
10
+ "keywords": [
11
+ "react",
12
+ "skeleton",
13
+ "tailwind",
14
+ "loader",
15
+ "ui"
16
+ ],
17
+ "license": "ISC",
18
+ "peerDependencies": {
19
+ "react": ">=16.8",
20
+ "react-dom": ">=16.8"
21
+ }
22
+ }
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ import { replaceWithSkeleton } from "../utils/replaceWithSkeleton";
3
+ import Skip from "./Skip";
4
+
5
+ const Skeleton = ({ loading, children, pulse = true }) => {
6
+ if (!loading) return children;
7
+
8
+ return (
9
+ <>
10
+ {replaceWithSkeleton(children, { pulse })}
11
+ </>
12
+ );
13
+ };
14
+
15
+
16
+ Skeleton.Skip = Skip;
17
+
18
+ export default Skeleton;
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+
3
+ const SkeletonItem = ({ className = "", pulse }) => {
4
+ return (
5
+ <div
6
+ aria-hidden="true"
7
+ className={`
8
+ ${className}
9
+ bg-gray-200 dark:bg-gray-700
10
+ rounded
11
+ ${pulse ? "animate-pulse" : ""}
12
+ `}
13
+ />
14
+ );
15
+ };
16
+
17
+ export default SkeletonItem;
@@ -0,0 +1,7 @@
1
+ const Skip = ({ children }) => {
2
+ return children;
3
+ };
4
+
5
+ Skip.displayName = "SkeletonSkip";
6
+
7
+ export default Skip;
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import Skeleton from "./components/Skeleton";
2
+
3
+ export default Skeleton;
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import SkeletonItem from "../components/SkeletonItem";
3
+
4
+ export function replaceWithSkeleton(node, options) {
5
+ const { pulse } = options;
6
+
7
+ // Text node
8
+ if (typeof node === "string" || typeof node === "number") {
9
+ return (
10
+ <div
11
+ className={`h-4 w-full rounded bg-gray-200 dark:bg-gray-700 ${
12
+ pulse ? "animate-pulse" : ""
13
+ }`}
14
+ />
15
+ );
16
+ }
17
+
18
+ if (!React.isValidElement(node)) return node;
19
+
20
+ // Skip skeleton
21
+ if (node.type?.displayName === "SkeletonSkip") {
22
+ return node.props.children;
23
+ }
24
+
25
+ const { className = "", children } = node.props;
26
+
27
+ // Leaf node
28
+ if (!children) {
29
+ return (
30
+ <SkeletonItem
31
+ className={className}
32
+ pulse={pulse}
33
+ />
34
+ );
35
+ }
36
+
37
+ // Container node
38
+ return React.cloneElement(node, {
39
+ className,
40
+ children: React.Children.map(children, (child) =>
41
+ replaceWithSkeleton(child, options)
42
+ ),
43
+ });
44
+ }