scrimr
Version:
🎲 A simple React shimmer component that displays animated random text while loading. Lightweight alternative to skeleton screens.
120 lines (116 loc) • 4.22 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
Scrimr: () => Scrimr,
default: () => Scrimr_default
});
module.exports = __toCommonJS(index_exports);
// src/Scrimr.tsx
var import_react = require("react");
// src/lib/utils.ts
var import_clsx = require("clsx");
var import_tailwind_merge = require("tailwind-merge");
function cn(...inputs) {
return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
}
// src/Scrimr.tsx
var import_jsx_runtime = require("react/jsx-runtime");
var DEFAULT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&* ";
var clamp = (n, min, max) => Math.min(max, Math.max(min, n));
var pick = (chars) => chars[Math.floor(Math.random() * chars.length)];
function mutateText(text, chars, ratio) {
if (!text) return text;
const arr = text.split("");
const changes = Math.max(1, Math.floor(arr.length * ratio));
for (let i = 0; i < changes; i++) {
const idx = Math.floor(Math.random() * arr.length);
arr[idx] = pick(chars);
}
return arr.join("");
}
var Scrimr = ({
isLoading,
children,
length = 20,
speed = 30,
chars = DEFAULT_CHARS,
className,
placeholderLabel = "Loading content",
partialUpdateRatio = 0.8
}) => {
const safeLength = clamp(Math.floor(length), 1, 5e3);
const hasChars = typeof chars === "string" && chars.length > 0;
const pool = hasChars ? chars : DEFAULT_CHARS;
const [text, setText] = (0, import_react.useState)("");
const timerRef = (0, import_react.useRef)(void 0);
const prefersReducedMotion = (0, import_react.useMemo)(() => {
if (typeof window === "undefined" || !("matchMedia" in window)) return false;
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}, []);
(0, import_react.useEffect)(() => {
if (!isLoading) {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = void 0;
}
return;
}
setText(Array.from({ length: safeLength }, () => pick(pool)).join(""));
const interval = prefersReducedMotion ? 400 : speed;
const tick = () => {
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
setText((prev) => {
if (!prev) {
return Array.from({ length: safeLength }, () => pick(pool)).join("");
}
return mutateText(prev, pool, clamp(partialUpdateRatio, 0.05, 1));
});
};
timerRef.current = setInterval(tick, interval);
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = void 0;
}
};
}, [isLoading, safeLength, pool, speed, partialUpdateRatio, prefersReducedMotion]);
if (!isLoading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"span",
{
className: cn(
"block animate-pulse text-gray-400 select-none font-mono truncate",
className
),
role: "status",
"aria-live": "polite",
"aria-label": placeholderLabel,
"aria-busy": "true",
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: text })
}
);
};
var Scrimr_default = Scrimr;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Scrimr
});
//# sourceMappingURL=index.js.map