@ghostly-ui/core 0.2.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,84 @@
1
+ # @ghostly-ui/core
2
+
3
+ CSS-first skeleton loading engine. Zero dependencies. Framework-agnostic.
4
+
5
+ > Your component IS the skeleton. Ghostly uses CSS to hide text and images, replacing them with animated placeholders -- preserving the exact same layout.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @ghostly-ui/core
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ Import the CSS and add `data-ghostly` to any container:
16
+
17
+ ```css
18
+ @import '@ghostly-ui/core/css';
19
+ ```
20
+
21
+ ```html
22
+ <div data-ghostly="shimmer">
23
+ <h2>This becomes a skeleton</h2>
24
+ <p>So does this</p>
25
+ <img src="photo.jpg" />
26
+ </div>
27
+ ```
28
+
29
+ Remove the attribute to show real content. That's it.
30
+
31
+ ## Features
32
+
33
+ - **3 animations** -- shimmer, pulse, wave (all pure CSS)
34
+ - **Dark mode** -- auto-detects via `.dark`, `data-theme="dark"`, or `prefers-color-scheme`
35
+ - **Accessible** -- `prefers-reduced-motion: reduce` disables animations
36
+ - **Zero specificity** -- uses `:where()` selectors, easy to override
37
+ - **Tiny** -- ~2KB gzipped, zero dependencies
38
+
39
+ ## CSS Custom Properties
40
+
41
+ ```css
42
+ :root {
43
+ --ghostly-color: hsl(220 13% 87%); /* skeleton base color */
44
+ --ghostly-shine: hsl(220 13% 94%); /* shimmer highlight */
45
+ --ghostly-radius: 4px; /* border radius */
46
+ --ghostly-speed: 1.5s; /* animation duration */
47
+ --ghostly-transition: 0.3s; /* fade-out transition */
48
+ }
49
+ ```
50
+
51
+ ## Data Attributes
52
+
53
+ | Attribute | Description |
54
+ |-----------|-------------|
55
+ | `data-ghostly="shimmer\|pulse\|wave"` | Activate skeleton on a container |
56
+ | `data-ghostly-ignore` | Exclude element from skeleton effect |
57
+ | `data-ghostly-lines="1-8"` | Control skeleton line count for text |
58
+ | `data-ghostly-ratio="16/9"` | Set aspect ratio for images |
59
+ | `data-ghostly-smooth` | Enable fade-out transition |
60
+
61
+ ## Tailwind CSS Plugin
62
+
63
+ ```js
64
+ // tailwind.config.js
65
+ import ghostly from '@ghostly-ui/core/tailwind'
66
+
67
+ export default {
68
+ plugins: [ghostly],
69
+ }
70
+ ```
71
+
72
+ Utilities: `ghostly-radius-*`, `ghostly-speed-*`, `ghostly-color-[...]`, `ghostly-shine-[...]`, `ghostly:` variant.
73
+
74
+ ## Framework Adapters
75
+
76
+ - **[@ghostly-ui/react](https://www.npmjs.com/package/@ghostly-ui/react)** -- React components with `<Ghostly>`, `<GhostlyList>`, `<GhostlySuspense>`
77
+
78
+ ## Documentation
79
+
80
+ Full docs at [ghostly.adanulissess.com](https://ghostly.adanulissess.com)
81
+
82
+ ## License
83
+
84
+ MIT
@@ -0,0 +1,329 @@
1
+ /* ============================================================
2
+ GHOSTLY — Zero-config skeleton loaders
3
+
4
+ Usage: add data-ghostly="shimmer|pulse|wave" to any container.
5
+ All leaf elements inside become skeleton blocks automatically.
6
+ ============================================================ */
7
+
8
+ /* --- Custom properties (override these to customize) --- */
9
+
10
+ :root {
11
+ --ghostly-color: hsl(220 13% 87%);
12
+ --ghostly-shine: hsl(220 13% 94%);
13
+ --ghostly-radius: 4px;
14
+ --ghostly-speed: 1.5s;
15
+ --ghostly-transition: 0.3s;
16
+ }
17
+
18
+ /* --- Dark mode (automatic) --- */
19
+
20
+ .dark,
21
+ [data-theme='dark'] {
22
+ --ghostly-color: hsl(220 13% 18%);
23
+ --ghostly-shine: hsl(220 13% 25%);
24
+ }
25
+
26
+ @media (prefers-color-scheme: dark) {
27
+ :root:not(.light):not([data-theme='light']) {
28
+ --ghostly-color: hsl(220 13% 18%);
29
+ --ghostly-shine: hsl(220 13% 25%);
30
+ }
31
+ }
32
+
33
+ /* ============================================================
34
+ 1. SKELETON TARGET — shared base for all skeleton elements
35
+ ============================================================ */
36
+
37
+ /* All targetable elements inside a ghostly container */
38
+ [data-ghostly] :where(
39
+ h1, h2, h3, h4, h5, h6,
40
+ p, span, a, li, td, th, dt, dd,
41
+ label, legend, figcaption, caption, summary,
42
+ blockquote, cite, q, em, strong, small, mark, code, pre,
43
+ button, input, textarea, select, option,
44
+ fieldset, dialog, details, address,
45
+ time, abbr, sub, sup, del, ins,
46
+ img, svg, video, canvas, picture, iframe
47
+ ) {
48
+ --_ghostly-target: 1;
49
+ }
50
+
51
+ /* ============================================================
52
+ 2. CONTAINER — Blocks interaction while loading
53
+ ============================================================ */
54
+
55
+ [data-ghostly] {
56
+ pointer-events: none;
57
+ user-select: none;
58
+ cursor: default;
59
+ }
60
+
61
+ /* ============================================================
62
+ 3. TEXT ELEMENTS — Become opaque skeleton blocks
63
+ ============================================================ */
64
+
65
+ [data-ghostly] :where(
66
+ h1, h2, h3, h4, h5, h6,
67
+ p, span, a, li, td, th, dt, dd,
68
+ label, legend, figcaption, caption, summary,
69
+ blockquote, cite, q, em, strong, small, mark, code, pre,
70
+ button, input, textarea, select, option,
71
+ fieldset, dialog, details, address,
72
+ time, abbr, sub, sup, del, ins
73
+ ) {
74
+ color: transparent !important;
75
+ background-color: var(--ghostly-color) !important;
76
+ background-image: none !important;
77
+ border-radius: var(--ghostly-radius) !important;
78
+ border-color: transparent !important;
79
+ box-shadow: none !important;
80
+ text-decoration: none !important;
81
+ outline: none !important;
82
+ min-height: 1em;
83
+ min-width: 2rem;
84
+ }
85
+
86
+ /* Headings get proportional min-height */
87
+ [data-ghostly] :where(h1) { min-height: 1.75em; min-width: 40%; }
88
+ [data-ghostly] :where(h2) { min-height: 1.5em; min-width: 50%; }
89
+ [data-ghostly] :where(h3) { min-height: 1.3em; min-width: 55%; }
90
+ [data-ghostly] :where(h4, h5, h6) { min-height: 1.15em; min-width: 45%; }
91
+
92
+ /* Paragraphs simulate multi-line text */
93
+ [data-ghostly] :where(p) { min-height: 3em; min-width: 80%; }
94
+
95
+ /* Code blocks need more height */
96
+ [data-ghostly] :where(pre) { min-height: 5em; min-width: 100%; }
97
+
98
+ /* Inputs keep their shape */
99
+ [data-ghostly] :where(input, textarea, select) {
100
+ min-height: 2.5rem;
101
+ min-width: 6rem;
102
+ }
103
+
104
+ /* Buttons keep their shape */
105
+ [data-ghostly] :where(button) {
106
+ min-height: 2.25rem;
107
+ min-width: 5rem;
108
+ }
109
+
110
+ /* ============================================================
111
+ 4. CUSTOM LINE COUNT — data-ghostly-lines="N"
112
+ ============================================================ */
113
+
114
+ [data-ghostly] :where([data-ghostly-lines="1"]) { min-height: 1em; }
115
+ [data-ghostly] :where([data-ghostly-lines="2"]) { min-height: 2em; }
116
+ [data-ghostly] :where([data-ghostly-lines="3"]) { min-height: 3em; }
117
+ [data-ghostly] :where([data-ghostly-lines="4"]) { min-height: 4em; }
118
+ [data-ghostly] :where([data-ghostly-lines="5"]) { min-height: 5em; }
119
+ [data-ghostly] :where([data-ghostly-lines="6"]) { min-height: 6em; }
120
+ [data-ghostly] :where([data-ghostly-lines="7"]) { min-height: 7em; }
121
+ [data-ghostly] :where([data-ghostly-lines="8"]) { min-height: 8em; }
122
+
123
+ /* ============================================================
124
+ 5. MEDIA ELEMENTS — Become solid skeleton blocks
125
+ ============================================================ */
126
+
127
+ [data-ghostly] :where(img, svg, video, canvas, picture, iframe) {
128
+ color: transparent !important;
129
+ background-color: var(--ghostly-color) !important;
130
+ border-radius: var(--ghostly-radius) !important;
131
+ border-color: transparent !important;
132
+ box-shadow: none !important;
133
+ }
134
+
135
+ /* Hide actual content (images, video frames) */
136
+ [data-ghostly] :where(img) {
137
+ object-position: -9999px;
138
+ }
139
+
140
+ [data-ghostly] :where(video, iframe) {
141
+ opacity: 0;
142
+ background-color: var(--ghostly-color) !important;
143
+ }
144
+
145
+ /* Images without explicit dimensions — use aspect-ratio */
146
+ [data-ghostly] :where(img:not([width]):not([style*="width"]), picture:not([width])) {
147
+ aspect-ratio: 16/9;
148
+ width: 100%;
149
+ }
150
+
151
+ /* Custom aspect ratio via data attribute */
152
+ [data-ghostly] :where([data-ghostly-ratio="1/1"]) { aspect-ratio: 1/1 !important; }
153
+ [data-ghostly] :where([data-ghostly-ratio="4/3"]) { aspect-ratio: 4/3 !important; }
154
+ [data-ghostly] :where([data-ghostly-ratio="16/9"]) { aspect-ratio: 16/9 !important; }
155
+ [data-ghostly] :where([data-ghostly-ratio="21/9"]) { aspect-ratio: 21/9 !important; }
156
+ [data-ghostly] :where([data-ghostly-ratio="3/4"]) { aspect-ratio: 3/4 !important; }
157
+ [data-ghostly] :where([data-ghostly-ratio="9/16"]) { aspect-ratio: 9/16 !important; }
158
+
159
+ /* SVG icons (typically small) */
160
+ [data-ghostly] :where(svg) {
161
+ min-height: 1.5rem;
162
+ min-width: 1.5rem;
163
+ }
164
+
165
+ /* ============================================================
166
+ 6. DECORATIVE — Strip visual noise
167
+ ============================================================ */
168
+
169
+ [data-ghostly] :where(hr) {
170
+ border-color: var(--ghostly-color) !important;
171
+ }
172
+
173
+ [data-ghostly] :where(
174
+ [class*="badge"],
175
+ [class*="chip"],
176
+ [class*="tag"],
177
+ [class*="avatar"]
178
+ ) {
179
+ color: transparent !important;
180
+ background-color: var(--ghostly-color) !important;
181
+ background-image: none !important;
182
+ border-color: transparent !important;
183
+ box-shadow: none !important;
184
+ }
185
+
186
+ /* ============================================================
187
+ 7. EXCLUSIONS — data-ghostly-ignore restores original styles
188
+ ============================================================ */
189
+
190
+ [data-ghostly] [data-ghostly-ignore],
191
+ [data-ghostly] [data-ghostly-ignore] * {
192
+ color: inherit !important;
193
+ background-color: inherit !important;
194
+ background-image: initial !important;
195
+ border-color: inherit !important;
196
+ box-shadow: initial !important;
197
+ visibility: visible !important;
198
+ pointer-events: auto;
199
+ user-select: auto;
200
+ min-height: initial;
201
+ min-width: initial;
202
+ animation: none !important;
203
+ }
204
+
205
+ /* ============================================================
206
+ 8. ANIMATIONS — Applied via --_ghostly-target marker
207
+ ============================================================ */
208
+
209
+ /* --- Shimmer — gradient sweep left to right --- */
210
+
211
+ [data-ghostly='shimmer'] :where([style*="--_ghostly-target"]),
212
+ [data-ghostly='shimmer'] :where(
213
+ h1, h2, h3, h4, h5, h6,
214
+ p, span, a, li, td, th, dt, dd,
215
+ label, legend, figcaption, caption, summary,
216
+ blockquote, cite, q, em, strong, small, mark, code, pre,
217
+ button, input, textarea, select, option,
218
+ fieldset, dialog, details, address,
219
+ time, abbr, sub, sup, del, ins,
220
+ img, svg, video, canvas, picture, iframe
221
+ ) {
222
+ background: linear-gradient(
223
+ 90deg,
224
+ var(--ghostly-color) 0%,
225
+ var(--ghostly-color) 33%,
226
+ var(--ghostly-shine) 50%,
227
+ var(--ghostly-color) 66%,
228
+ var(--ghostly-color) 100%
229
+ ) !important;
230
+ background-size: 300% 100% !important;
231
+ animation: ghostly-shimmer var(--ghostly-speed) ease-in-out infinite !important;
232
+ }
233
+
234
+ @keyframes ghostly-shimmer {
235
+ 0% { background-position: 100% 0; }
236
+ 100% { background-position: -100% 0; }
237
+ }
238
+
239
+ /* --- Pulse — opacity fade in/out --- */
240
+
241
+ [data-ghostly='pulse'] :where(
242
+ h1, h2, h3, h4, h5, h6,
243
+ p, span, a, li, td, th, dt, dd,
244
+ label, legend, figcaption, caption, summary,
245
+ blockquote, cite, q, em, strong, small, mark, code, pre,
246
+ button, input, textarea, select, option,
247
+ fieldset, dialog, details, address,
248
+ time, abbr, sub, sup, del, ins,
249
+ img, svg, video, canvas, picture, iframe
250
+ ) {
251
+ animation: ghostly-pulse var(--ghostly-speed) ease-in-out infinite !important;
252
+ }
253
+
254
+ @keyframes ghostly-pulse {
255
+ 0%, 100% { opacity: 1; }
256
+ 50% { opacity: 0.4; }
257
+ }
258
+
259
+ /* --- Wave — cascading pulse with stagger --- */
260
+
261
+ [data-ghostly='wave'] :where(
262
+ h1, h2, h3, h4, h5, h6,
263
+ p, span, a, li, td, th, dt, dd,
264
+ label, legend, figcaption, caption, summary,
265
+ blockquote, cite, q, em, strong, small, mark, code, pre,
266
+ button, input, textarea, select, option,
267
+ fieldset, dialog, details, address,
268
+ time, abbr, sub, sup, del, ins,
269
+ img, svg, video, canvas, picture, iframe
270
+ ) {
271
+ animation: ghostly-wave var(--ghostly-speed) ease-in-out infinite !important;
272
+ }
273
+
274
+ @keyframes ghostly-wave {
275
+ 0% { opacity: 1; }
276
+ 25% { opacity: 0.3; }
277
+ 50% { opacity: 0.8; }
278
+ 75% { opacity: 0.4; }
279
+ 100% { opacity: 1; }
280
+ }
281
+
282
+ /* Stagger direct children for cascading effect */
283
+ [data-ghostly='wave'] > :nth-child(1) { animation-delay: 0ms !important; }
284
+ [data-ghostly='wave'] > :nth-child(2) { animation-delay: 80ms !important; }
285
+ [data-ghostly='wave'] > :nth-child(3) { animation-delay: 160ms !important; }
286
+ [data-ghostly='wave'] > :nth-child(4) { animation-delay: 240ms !important; }
287
+ [data-ghostly='wave'] > :nth-child(5) { animation-delay: 320ms !important; }
288
+ [data-ghostly='wave'] > :nth-child(6) { animation-delay: 400ms !important; }
289
+ [data-ghostly='wave'] > :nth-child(7) { animation-delay: 480ms !important; }
290
+ [data-ghostly='wave'] > :nth-child(8) { animation-delay: 560ms !important; }
291
+ [data-ghostly='wave'] > :nth-child(9) { animation-delay: 640ms !important; }
292
+ [data-ghostly='wave'] > :nth-child(10) { animation-delay: 720ms !important; }
293
+ [data-ghostly='wave'] > :nth-child(11) { animation-delay: 800ms !important; }
294
+ [data-ghostly='wave'] > :nth-child(12) { animation-delay: 880ms !important; }
295
+ [data-ghostly='wave'] > :nth-child(n+13) { animation-delay: 960ms !important; }
296
+
297
+ /* ============================================================
298
+ 9. SMOOTH TRANSITION — fade out when loading ends
299
+ ============================================================ */
300
+
301
+ [data-ghostly-smooth] :where(
302
+ h1, h2, h3, h4, h5, h6,
303
+ p, span, a, li, td, th, dt, dd,
304
+ label, legend, figcaption, caption, summary,
305
+ blockquote, cite, q, em, strong, small, mark, code, pre,
306
+ button, input, textarea, select, option,
307
+ fieldset, dialog, details, address,
308
+ time, abbr, sub, sup, del, ins,
309
+ img, svg, video, canvas, picture, iframe
310
+ ) {
311
+ transition:
312
+ color var(--ghostly-transition) ease-out,
313
+ background-color var(--ghostly-transition) ease-out,
314
+ background-image var(--ghostly-transition) ease-out,
315
+ opacity var(--ghostly-transition) ease-out;
316
+ }
317
+
318
+ /* ============================================================
319
+ 10. ACCESSIBILITY
320
+ ============================================================ */
321
+
322
+ @media (prefers-reduced-motion: reduce) {
323
+ [data-ghostly] *,
324
+ [data-ghostly] *::before,
325
+ [data-ghostly] *::after {
326
+ animation: none !important;
327
+ transition: none !important;
328
+ }
329
+ }
package/dist/index.cjs ADDED
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CSS_VARS: () => CSS_VARS,
24
+ RADIUS_MAP: () => RADIUS_MAP,
25
+ SPEED_MAP: () => SPEED_MAP,
26
+ VALID_ANIMATIONS: () => VALID_ANIMATIONS,
27
+ VALID_RADII: () => VALID_RADII,
28
+ VALID_SPEEDS: () => VALID_SPEEDS,
29
+ validateGhostlyProps: () => validateGhostlyProps
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+
33
+ // src/types.ts
34
+ var CSS_VARS = {
35
+ color: "--ghostly-color",
36
+ shine: "--ghostly-shine",
37
+ radius: "--ghostly-radius",
38
+ speed: "--ghostly-speed"
39
+ };
40
+ var RADIUS_MAP = {
41
+ none: "0px",
42
+ xs: "2px",
43
+ sm: "4px",
44
+ md: "8px",
45
+ lg: "12px",
46
+ full: "9999px"
47
+ };
48
+ var SPEED_MAP = {
49
+ slow: "2s",
50
+ normal: "1.5s",
51
+ fast: "0.8s"
52
+ };
53
+ var VALID_ANIMATIONS = ["shimmer", "pulse", "wave", "none"];
54
+ var VALID_RADII = ["none", "xs", "sm", "md", "lg", "full"];
55
+ var VALID_SPEEDS = ["slow", "normal", "fast"];
56
+ function validateGhostlyProps(component, props) {
57
+ if (process.env.NODE_ENV === "production") return;
58
+ if (props.animation && !VALID_ANIMATIONS.includes(props.animation)) {
59
+ console.warn(
60
+ `[Ghostly] <${component}> received invalid animation="${props.animation}". Valid values: ${VALID_ANIMATIONS.join(", ")}`
61
+ );
62
+ }
63
+ if (props.radius && !VALID_RADII.includes(props.radius)) {
64
+ console.warn(
65
+ `[Ghostly] <${component}> received invalid radius="${props.radius}". Valid values: ${VALID_RADII.join(", ")}`
66
+ );
67
+ }
68
+ if (props.speed && !VALID_SPEEDS.includes(props.speed)) {
69
+ console.warn(
70
+ `[Ghostly] <${component}> received invalid speed="${props.speed}". Valid values: ${VALID_SPEEDS.join(", ")}`
71
+ );
72
+ }
73
+ }
74
+ // Annotate the CommonJS export names for ESM import in node:
75
+ 0 && (module.exports = {
76
+ CSS_VARS,
77
+ RADIUS_MAP,
78
+ SPEED_MAP,
79
+ VALID_ANIMATIONS,
80
+ VALID_RADII,
81
+ VALID_SPEEDS,
82
+ validateGhostlyProps
83
+ });
84
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts"],"sourcesContent":["export type { GhostlyAnimation, GhostlyRadius, GhostlySpeed, GhostlyConfig } from './types'\nexport { CSS_VARS, RADIUS_MAP, SPEED_MAP, VALID_ANIMATIONS, VALID_RADII, VALID_SPEEDS, validateGhostlyProps } from './types'\n","export type GhostlyAnimation = 'shimmer' | 'pulse' | 'wave' | 'none'\n\nexport type GhostlyRadius = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'full'\n\nexport type GhostlySpeed = 'slow' | 'normal' | 'fast'\n\nexport interface GhostlyConfig {\n /** Animation style. Default: 'shimmer' */\n animation?: GhostlyAnimation\n /** Border radius for skeleton blocks. Default: 'sm' */\n radius?: GhostlyRadius\n /** Animation speed. Default: 'normal' */\n speed?: GhostlySpeed\n /** Custom skeleton base color (any CSS color value) */\n color?: string\n /** Custom shimmer highlight color (any CSS color value) */\n shine?: string\n}\n\n/** CSS custom property names used by Ghostly */\nexport const CSS_VARS = {\n color: '--ghostly-color',\n shine: '--ghostly-shine',\n radius: '--ghostly-radius',\n speed: '--ghostly-speed',\n} as const\n\n/** Maps radius prop to CSS value */\nexport const RADIUS_MAP: Record<GhostlyRadius, string> = {\n none: '0px',\n xs: '2px',\n sm: '4px',\n md: '8px',\n lg: '12px',\n full: '9999px',\n}\n\n/** Maps speed prop to CSS duration */\nexport const SPEED_MAP: Record<GhostlySpeed, string> = {\n slow: '2s',\n normal: '1.5s',\n fast: '0.8s',\n}\n\n/** Valid values for each prop (used for dev validation) */\nexport const VALID_ANIMATIONS: readonly string[] = ['shimmer', 'pulse', 'wave', 'none']\nexport const VALID_RADII: readonly string[] = ['none', 'xs', 'sm', 'md', 'lg', 'full']\nexport const VALID_SPEEDS: readonly string[] = ['slow', 'normal', 'fast']\n\n/**\n * Validates Ghostly config props in development.\n * No-op in production builds (tree-shaken via process.env.NODE_ENV check).\n */\nexport function validateGhostlyProps(\n component: string,\n props: Partial<GhostlyConfig>,\n): void {\n if (process.env.NODE_ENV === 'production') return\n\n if (props.animation && !VALID_ANIMATIONS.includes(props.animation)) {\n console.warn(\n `[Ghostly] <${component}> received invalid animation=\"${props.animation}\". ` +\n `Valid values: ${VALID_ANIMATIONS.join(', ')}`,\n )\n }\n if (props.radius && !VALID_RADII.includes(props.radius)) {\n console.warn(\n `[Ghostly] <${component}> received invalid radius=\"${props.radius}\". ` +\n `Valid values: ${VALID_RADII.join(', ')}`,\n )\n }\n if (props.speed && !VALID_SPEEDS.includes(props.speed)) {\n console.warn(\n `[Ghostly] <${component}> received invalid speed=\"${props.speed}\". ` +\n `Valid values: ${VALID_SPEEDS.join(', ')}`,\n )\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACoBO,IAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AACT;AAGO,IAAM,aAA4C;AAAA,EACvD,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,MAAM;AACR;AAGO,IAAM,YAA0C;AAAA,EACrD,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AACR;AAGO,IAAM,mBAAsC,CAAC,WAAW,SAAS,QAAQ,MAAM;AAC/E,IAAM,cAAiC,CAAC,QAAQ,MAAM,MAAM,MAAM,MAAM,MAAM;AAC9E,IAAM,eAAkC,CAAC,QAAQ,UAAU,MAAM;AAMjE,SAAS,qBACd,WACA,OACM;AACN,MAAI,QAAQ,IAAI,aAAa,aAAc;AAE3C,MAAI,MAAM,aAAa,CAAC,iBAAiB,SAAS,MAAM,SAAS,GAAG;AAClE,YAAQ;AAAA,MACN,cAAc,SAAS,iCAAiC,MAAM,SAAS,oBACtD,iBAAiB,KAAK,IAAI,CAAC;AAAA,IAC9C;AAAA,EACF;AACA,MAAI,MAAM,UAAU,CAAC,YAAY,SAAS,MAAM,MAAM,GAAG;AACvD,YAAQ;AAAA,MACN,cAAc,SAAS,8BAA8B,MAAM,MAAM,oBAChD,YAAY,KAAK,IAAI,CAAC;AAAA,IACzC;AAAA,EACF;AACA,MAAI,MAAM,SAAS,CAAC,aAAa,SAAS,MAAM,KAAK,GAAG;AACtD,YAAQ;AAAA,MACN,cAAc,SAAS,6BAA6B,MAAM,KAAK,oBAC9C,aAAa,KAAK,IAAI,CAAC;AAAA,IAC1C;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,37 @@
1
+ type GhostlyAnimation = 'shimmer' | 'pulse' | 'wave' | 'none';
2
+ type GhostlyRadius = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'full';
3
+ type GhostlySpeed = 'slow' | 'normal' | 'fast';
4
+ interface GhostlyConfig {
5
+ /** Animation style. Default: 'shimmer' */
6
+ animation?: GhostlyAnimation;
7
+ /** Border radius for skeleton blocks. Default: 'sm' */
8
+ radius?: GhostlyRadius;
9
+ /** Animation speed. Default: 'normal' */
10
+ speed?: GhostlySpeed;
11
+ /** Custom skeleton base color (any CSS color value) */
12
+ color?: string;
13
+ /** Custom shimmer highlight color (any CSS color value) */
14
+ shine?: string;
15
+ }
16
+ /** CSS custom property names used by Ghostly */
17
+ declare const CSS_VARS: {
18
+ readonly color: "--ghostly-color";
19
+ readonly shine: "--ghostly-shine";
20
+ readonly radius: "--ghostly-radius";
21
+ readonly speed: "--ghostly-speed";
22
+ };
23
+ /** Maps radius prop to CSS value */
24
+ declare const RADIUS_MAP: Record<GhostlyRadius, string>;
25
+ /** Maps speed prop to CSS duration */
26
+ declare const SPEED_MAP: Record<GhostlySpeed, string>;
27
+ /** Valid values for each prop (used for dev validation) */
28
+ declare const VALID_ANIMATIONS: readonly string[];
29
+ declare const VALID_RADII: readonly string[];
30
+ declare const VALID_SPEEDS: readonly string[];
31
+ /**
32
+ * Validates Ghostly config props in development.
33
+ * No-op in production builds (tree-shaken via process.env.NODE_ENV check).
34
+ */
35
+ declare function validateGhostlyProps(component: string, props: Partial<GhostlyConfig>): void;
36
+
37
+ export { CSS_VARS, type GhostlyAnimation, type GhostlyConfig, type GhostlyRadius, type GhostlySpeed, RADIUS_MAP, SPEED_MAP, VALID_ANIMATIONS, VALID_RADII, VALID_SPEEDS, validateGhostlyProps };
@@ -0,0 +1,37 @@
1
+ type GhostlyAnimation = 'shimmer' | 'pulse' | 'wave' | 'none';
2
+ type GhostlyRadius = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'full';
3
+ type GhostlySpeed = 'slow' | 'normal' | 'fast';
4
+ interface GhostlyConfig {
5
+ /** Animation style. Default: 'shimmer' */
6
+ animation?: GhostlyAnimation;
7
+ /** Border radius for skeleton blocks. Default: 'sm' */
8
+ radius?: GhostlyRadius;
9
+ /** Animation speed. Default: 'normal' */
10
+ speed?: GhostlySpeed;
11
+ /** Custom skeleton base color (any CSS color value) */
12
+ color?: string;
13
+ /** Custom shimmer highlight color (any CSS color value) */
14
+ shine?: string;
15
+ }
16
+ /** CSS custom property names used by Ghostly */
17
+ declare const CSS_VARS: {
18
+ readonly color: "--ghostly-color";
19
+ readonly shine: "--ghostly-shine";
20
+ readonly radius: "--ghostly-radius";
21
+ readonly speed: "--ghostly-speed";
22
+ };
23
+ /** Maps radius prop to CSS value */
24
+ declare const RADIUS_MAP: Record<GhostlyRadius, string>;
25
+ /** Maps speed prop to CSS duration */
26
+ declare const SPEED_MAP: Record<GhostlySpeed, string>;
27
+ /** Valid values for each prop (used for dev validation) */
28
+ declare const VALID_ANIMATIONS: readonly string[];
29
+ declare const VALID_RADII: readonly string[];
30
+ declare const VALID_SPEEDS: readonly string[];
31
+ /**
32
+ * Validates Ghostly config props in development.
33
+ * No-op in production builds (tree-shaken via process.env.NODE_ENV check).
34
+ */
35
+ declare function validateGhostlyProps(component: string, props: Partial<GhostlyConfig>): void;
36
+
37
+ export { CSS_VARS, type GhostlyAnimation, type GhostlyConfig, type GhostlyRadius, type GhostlySpeed, RADIUS_MAP, SPEED_MAP, VALID_ANIMATIONS, VALID_RADII, VALID_SPEEDS, validateGhostlyProps };
package/dist/index.js ADDED
@@ -0,0 +1,51 @@
1
+ // src/types.ts
2
+ var CSS_VARS = {
3
+ color: "--ghostly-color",
4
+ shine: "--ghostly-shine",
5
+ radius: "--ghostly-radius",
6
+ speed: "--ghostly-speed"
7
+ };
8
+ var RADIUS_MAP = {
9
+ none: "0px",
10
+ xs: "2px",
11
+ sm: "4px",
12
+ md: "8px",
13
+ lg: "12px",
14
+ full: "9999px"
15
+ };
16
+ var SPEED_MAP = {
17
+ slow: "2s",
18
+ normal: "1.5s",
19
+ fast: "0.8s"
20
+ };
21
+ var VALID_ANIMATIONS = ["shimmer", "pulse", "wave", "none"];
22
+ var VALID_RADII = ["none", "xs", "sm", "md", "lg", "full"];
23
+ var VALID_SPEEDS = ["slow", "normal", "fast"];
24
+ function validateGhostlyProps(component, props) {
25
+ if (process.env.NODE_ENV === "production") return;
26
+ if (props.animation && !VALID_ANIMATIONS.includes(props.animation)) {
27
+ console.warn(
28
+ `[Ghostly] <${component}> received invalid animation="${props.animation}". Valid values: ${VALID_ANIMATIONS.join(", ")}`
29
+ );
30
+ }
31
+ if (props.radius && !VALID_RADII.includes(props.radius)) {
32
+ console.warn(
33
+ `[Ghostly] <${component}> received invalid radius="${props.radius}". Valid values: ${VALID_RADII.join(", ")}`
34
+ );
35
+ }
36
+ if (props.speed && !VALID_SPEEDS.includes(props.speed)) {
37
+ console.warn(
38
+ `[Ghostly] <${component}> received invalid speed="${props.speed}". Valid values: ${VALID_SPEEDS.join(", ")}`
39
+ );
40
+ }
41
+ }
42
+ export {
43
+ CSS_VARS,
44
+ RADIUS_MAP,
45
+ SPEED_MAP,
46
+ VALID_ANIMATIONS,
47
+ VALID_RADII,
48
+ VALID_SPEEDS,
49
+ validateGhostlyProps
50
+ };
51
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["export type GhostlyAnimation = 'shimmer' | 'pulse' | 'wave' | 'none'\n\nexport type GhostlyRadius = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'full'\n\nexport type GhostlySpeed = 'slow' | 'normal' | 'fast'\n\nexport interface GhostlyConfig {\n /** Animation style. Default: 'shimmer' */\n animation?: GhostlyAnimation\n /** Border radius for skeleton blocks. Default: 'sm' */\n radius?: GhostlyRadius\n /** Animation speed. Default: 'normal' */\n speed?: GhostlySpeed\n /** Custom skeleton base color (any CSS color value) */\n color?: string\n /** Custom shimmer highlight color (any CSS color value) */\n shine?: string\n}\n\n/** CSS custom property names used by Ghostly */\nexport const CSS_VARS = {\n color: '--ghostly-color',\n shine: '--ghostly-shine',\n radius: '--ghostly-radius',\n speed: '--ghostly-speed',\n} as const\n\n/** Maps radius prop to CSS value */\nexport const RADIUS_MAP: Record<GhostlyRadius, string> = {\n none: '0px',\n xs: '2px',\n sm: '4px',\n md: '8px',\n lg: '12px',\n full: '9999px',\n}\n\n/** Maps speed prop to CSS duration */\nexport const SPEED_MAP: Record<GhostlySpeed, string> = {\n slow: '2s',\n normal: '1.5s',\n fast: '0.8s',\n}\n\n/** Valid values for each prop (used for dev validation) */\nexport const VALID_ANIMATIONS: readonly string[] = ['shimmer', 'pulse', 'wave', 'none']\nexport const VALID_RADII: readonly string[] = ['none', 'xs', 'sm', 'md', 'lg', 'full']\nexport const VALID_SPEEDS: readonly string[] = ['slow', 'normal', 'fast']\n\n/**\n * Validates Ghostly config props in development.\n * No-op in production builds (tree-shaken via process.env.NODE_ENV check).\n */\nexport function validateGhostlyProps(\n component: string,\n props: Partial<GhostlyConfig>,\n): void {\n if (process.env.NODE_ENV === 'production') return\n\n if (props.animation && !VALID_ANIMATIONS.includes(props.animation)) {\n console.warn(\n `[Ghostly] <${component}> received invalid animation=\"${props.animation}\". ` +\n `Valid values: ${VALID_ANIMATIONS.join(', ')}`,\n )\n }\n if (props.radius && !VALID_RADII.includes(props.radius)) {\n console.warn(\n `[Ghostly] <${component}> received invalid radius=\"${props.radius}\". ` +\n `Valid values: ${VALID_RADII.join(', ')}`,\n )\n }\n if (props.speed && !VALID_SPEEDS.includes(props.speed)) {\n console.warn(\n `[Ghostly] <${component}> received invalid speed=\"${props.speed}\". ` +\n `Valid values: ${VALID_SPEEDS.join(', ')}`,\n )\n }\n}\n"],"mappings":";AAoBO,IAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AACT;AAGO,IAAM,aAA4C;AAAA,EACvD,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,MAAM;AACR;AAGO,IAAM,YAA0C;AAAA,EACrD,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AACR;AAGO,IAAM,mBAAsC,CAAC,WAAW,SAAS,QAAQ,MAAM;AAC/E,IAAM,cAAiC,CAAC,QAAQ,MAAM,MAAM,MAAM,MAAM,MAAM;AAC9E,IAAM,eAAkC,CAAC,QAAQ,UAAU,MAAM;AAMjE,SAAS,qBACd,WACA,OACM;AACN,MAAI,QAAQ,IAAI,aAAa,aAAc;AAE3C,MAAI,MAAM,aAAa,CAAC,iBAAiB,SAAS,MAAM,SAAS,GAAG;AAClE,YAAQ;AAAA,MACN,cAAc,SAAS,iCAAiC,MAAM,SAAS,oBACtD,iBAAiB,KAAK,IAAI,CAAC;AAAA,IAC9C;AAAA,EACF;AACA,MAAI,MAAM,UAAU,CAAC,YAAY,SAAS,MAAM,MAAM,GAAG;AACvD,YAAQ;AAAA,MACN,cAAc,SAAS,8BAA8B,MAAM,MAAM,oBAChD,YAAY,KAAK,IAAI,CAAC;AAAA,IACzC;AAAA,EACF;AACA,MAAI,MAAM,SAAS,CAAC,aAAa,SAAS,MAAM,KAAK,GAAG;AACtD,YAAQ;AAAA,MACN,cAAc,SAAS,6BAA6B,MAAM,KAAK,oBAC9C,aAAa,KAAK,IAAI,CAAC;AAAA,IAC1C;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/tailwind.ts
31
+ var tailwind_exports = {};
32
+ __export(tailwind_exports, {
33
+ default: () => tailwind_default
34
+ });
35
+ module.exports = __toCommonJS(tailwind_exports);
36
+ var import_plugin = __toESM(require("tailwindcss/plugin"), 1);
37
+ var tailwind_default = (0, import_plugin.default)(function ghostlyPlugin({ addUtilities, addVariant, matchUtilities }) {
38
+ addVariant("ghostly", "[data-ghostly] &");
39
+ addUtilities({
40
+ ".ghostly-radius-none": { "--ghostly-radius": "0px" },
41
+ ".ghostly-radius-xs": { "--ghostly-radius": "2px" },
42
+ ".ghostly-radius-sm": { "--ghostly-radius": "4px" },
43
+ ".ghostly-radius-md": { "--ghostly-radius": "8px" },
44
+ ".ghostly-radius-lg": { "--ghostly-radius": "12px" },
45
+ ".ghostly-radius-full": { "--ghostly-radius": "9999px" }
46
+ });
47
+ addUtilities({
48
+ ".ghostly-speed-slow": { "--ghostly-speed": "2s" },
49
+ ".ghostly-speed-normal": { "--ghostly-speed": "1.5s" },
50
+ ".ghostly-speed-fast": { "--ghostly-speed": "0.8s" }
51
+ });
52
+ matchUtilities(
53
+ { "ghostly-color": (value) => ({ "--ghostly-color": value }) },
54
+ { values: {}, type: ["color"] }
55
+ );
56
+ matchUtilities(
57
+ { "ghostly-shine": (value) => ({ "--ghostly-shine": value }) },
58
+ { values: {}, type: ["color"] }
59
+ );
60
+ });
61
+ //# sourceMappingURL=tailwind.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tailwind.ts"],"sourcesContent":["import plugin from 'tailwindcss/plugin'\n\n/**\n * Tailwind CSS plugin for Ghostly skeleton loaders.\n *\n * Adds utilities for configuring ghostly CSS variables\n * and a `ghostly:` variant for conditional skeleton styles.\n *\n * @example\n * ```js\n * // tailwind.config.js\n * import ghostly from '@ghostly-ui/core/tailwind'\n * export default { plugins: [ghostly] }\n * ```\n *\n * Usage in templates:\n * ```html\n * <div class=\"ghostly-radius-lg ghostly-speed-fast\">\n * ...\n * </div>\n * ```\n */\nexport default plugin(function ghostlyPlugin({ addUtilities, addVariant, matchUtilities }) {\n // Variant: `ghostly:` targets elements inside a ghostly container\n addVariant('ghostly', '[data-ghostly] &')\n\n // Radius utilities\n addUtilities({\n '.ghostly-radius-none': { '--ghostly-radius': '0px' },\n '.ghostly-radius-xs': { '--ghostly-radius': '2px' },\n '.ghostly-radius-sm': { '--ghostly-radius': '4px' },\n '.ghostly-radius-md': { '--ghostly-radius': '8px' },\n '.ghostly-radius-lg': { '--ghostly-radius': '12px' },\n '.ghostly-radius-full': { '--ghostly-radius': '9999px' },\n })\n\n // Speed utilities\n addUtilities({\n '.ghostly-speed-slow': { '--ghostly-speed': '2s' },\n '.ghostly-speed-normal': { '--ghostly-speed': '1.5s' },\n '.ghostly-speed-fast': { '--ghostly-speed': '0.8s' },\n })\n\n // Color utilities (arbitrary value via matchUtilities)\n matchUtilities(\n { 'ghostly-color': (value: string) => ({ '--ghostly-color': value }) },\n { values: {}, type: ['color'] },\n )\n\n matchUtilities(\n { 'ghostly-shine': (value: string) => ({ '--ghostly-shine': value }) },\n { values: {}, type: ['color'] },\n )\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAmB;AAsBnB,IAAO,uBAAQ,cAAAA,SAAO,SAAS,cAAc,EAAE,cAAc,YAAY,eAAe,GAAG;AAEzF,aAAW,WAAW,kBAAkB;AAGxC,eAAa;AAAA,IACX,wBAAwB,EAAE,oBAAoB,MAAM;AAAA,IACpD,sBAAsB,EAAE,oBAAoB,MAAM;AAAA,IAClD,sBAAsB,EAAE,oBAAoB,MAAM;AAAA,IAClD,sBAAsB,EAAE,oBAAoB,MAAM;AAAA,IAClD,sBAAsB,EAAE,oBAAoB,OAAO;AAAA,IACnD,wBAAwB,EAAE,oBAAoB,SAAS;AAAA,EACzD,CAAC;AAGD,eAAa;AAAA,IACX,uBAAuB,EAAE,mBAAmB,KAAK;AAAA,IACjD,yBAAyB,EAAE,mBAAmB,OAAO;AAAA,IACrD,uBAAuB,EAAE,mBAAmB,OAAO;AAAA,EACrD,CAAC;AAGD;AAAA,IACE,EAAE,iBAAiB,CAAC,WAAmB,EAAE,mBAAmB,MAAM,GAAG;AAAA,IACrE,EAAE,QAAQ,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE;AAAA,EAChC;AAEA;AAAA,IACE,EAAE,iBAAiB,CAAC,WAAmB,EAAE,mBAAmB,MAAM,GAAG;AAAA,IACrE,EAAE,QAAQ,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE;AAAA,EAChC;AACF,CAAC;","names":["plugin"]}
@@ -0,0 +1,148 @@
1
+ import { N, P } from './resolve-config-QUZ9b-Gn.mjs';
2
+
3
+ /**
4
+ * The source code for one or more nodes in the AST
5
+ *
6
+ * This generally corresponds to a stylesheet
7
+ */
8
+ interface Source {
9
+ /**
10
+ * The path to the file that contains the referenced source code
11
+ *
12
+ * If this references the *output* source code, this is `null`.
13
+ */
14
+ file: string | null;
15
+ /**
16
+ * The referenced source code
17
+ */
18
+ code: string;
19
+ }
20
+ /**
21
+ * The file and offsets within it that this node covers
22
+ *
23
+ * This can represent either:
24
+ * - A location in the original CSS which caused this node to be created
25
+ * - A location in the output CSS where this node resides
26
+ */
27
+ type SourceLocation = [source: Source, start: number, end: number];
28
+ type PluginFn = (api: PluginAPI) => void;
29
+ type PluginWithConfig = {
30
+ handler: PluginFn;
31
+ config?: UserConfig;
32
+ /** @internal */
33
+ reference?: boolean;
34
+ src?: SourceLocation | undefined;
35
+ };
36
+ type PluginWithOptions<T> = {
37
+ (options?: T): PluginWithConfig;
38
+ __isOptionsFunction: true;
39
+ };
40
+ type Plugin = PluginFn | PluginWithConfig | PluginWithOptions<any>;
41
+ type PluginAPI = {
42
+ addBase(base: CssInJs): void;
43
+ addVariant(name: string, variant: string | string[] | CssInJs): void;
44
+ matchVariant<T = string>(name: string, cb: (value: T | string, extra: {
45
+ modifier: string | null;
46
+ }) => string | string[], options?: {
47
+ values?: Record<string, T>;
48
+ sort?(a: {
49
+ value: T | string;
50
+ modifier: string | null;
51
+ }, b: {
52
+ value: T | string;
53
+ modifier: string | null;
54
+ }): number;
55
+ }): void;
56
+ addUtilities(utilities: Record<string, CssInJs | CssInJs[]> | Record<string, CssInJs | CssInJs[]>[], options?: {}): void;
57
+ matchUtilities(utilities: Record<string, (value: string, extra: {
58
+ modifier: string | null;
59
+ }) => CssInJs | CssInJs[]>, options?: Partial<{
60
+ type: string | string[];
61
+ supportsNegativeValues: boolean;
62
+ values: Record<string, string> & {
63
+ __BARE_VALUE__?: (value: N) => string | undefined;
64
+ };
65
+ modifiers: 'any' | Record<string, string>;
66
+ }>): void;
67
+ addComponents(utilities: Record<string, CssInJs> | Record<string, CssInJs>[], options?: {}): void;
68
+ matchComponents(utilities: Record<string, (value: string, extra: {
69
+ modifier: string | null;
70
+ }) => CssInJs>, options?: Partial<{
71
+ type: string | string[];
72
+ supportsNegativeValues: boolean;
73
+ values: Record<string, string> & {
74
+ __BARE_VALUE__?: (value: N) => string | undefined;
75
+ };
76
+ modifiers: 'any' | Record<string, string>;
77
+ }>): void;
78
+ theme(path: string, defaultValue?: any): any;
79
+ config(path?: string, defaultValue?: any): any;
80
+ prefix(className: string): string;
81
+ };
82
+ type CssInJs = {
83
+ [key: string]: string | string[] | CssInJs | CssInJs[];
84
+ };
85
+
86
+ type ResolvableTo<T> = T | ((utils: P) => T);
87
+ type ThemeValue = ResolvableTo<Record<string, unknown>> | null | undefined;
88
+ type ThemeConfig = Record<string, ThemeValue> & {
89
+ extend?: Record<string, ThemeValue>;
90
+ };
91
+ type ContentFile = string | {
92
+ raw: string;
93
+ extension?: string;
94
+ };
95
+ type DarkModeStrategy = false | 'media' | 'class' | ['class', string] | 'selector' | ['selector', string] | ['variant', string | string[]];
96
+ interface UserConfig {
97
+ presets?: UserConfig[];
98
+ theme?: ThemeConfig;
99
+ plugins?: Plugin[];
100
+ }
101
+ interface UserConfig {
102
+ content?: ContentFile[] | {
103
+ relative?: boolean;
104
+ files: ContentFile[];
105
+ };
106
+ }
107
+ interface UserConfig {
108
+ darkMode?: DarkModeStrategy;
109
+ }
110
+ interface UserConfig {
111
+ prefix?: string;
112
+ }
113
+ interface UserConfig {
114
+ blocklist?: string[];
115
+ }
116
+ interface UserConfig {
117
+ important?: boolean | string;
118
+ }
119
+ interface UserConfig {
120
+ future?: 'all' | Record<string, boolean>;
121
+ }
122
+ interface UserConfig {
123
+ experimental?: 'all' | Record<string, boolean>;
124
+ }
125
+
126
+ /**
127
+ * Tailwind CSS plugin for Ghostly skeleton loaders.
128
+ *
129
+ * Adds utilities for configuring ghostly CSS variables
130
+ * and a `ghostly:` variant for conditional skeleton styles.
131
+ *
132
+ * @example
133
+ * ```js
134
+ * // tailwind.config.js
135
+ * import ghostly from '@ghostly-ui/core/tailwind'
136
+ * export default { plugins: [ghostly] }
137
+ * ```
138
+ *
139
+ * Usage in templates:
140
+ * ```html
141
+ * <div class="ghostly-radius-lg ghostly-speed-fast">
142
+ * ...
143
+ * </div>
144
+ * ```
145
+ */
146
+ declare const _default: PluginWithConfig;
147
+
148
+ export { _default as default };
@@ -0,0 +1,148 @@
1
+ import { N, P } from './resolve-config-QUZ9b-Gn.mjs';
2
+
3
+ /**
4
+ * The source code for one or more nodes in the AST
5
+ *
6
+ * This generally corresponds to a stylesheet
7
+ */
8
+ interface Source {
9
+ /**
10
+ * The path to the file that contains the referenced source code
11
+ *
12
+ * If this references the *output* source code, this is `null`.
13
+ */
14
+ file: string | null;
15
+ /**
16
+ * The referenced source code
17
+ */
18
+ code: string;
19
+ }
20
+ /**
21
+ * The file and offsets within it that this node covers
22
+ *
23
+ * This can represent either:
24
+ * - A location in the original CSS which caused this node to be created
25
+ * - A location in the output CSS where this node resides
26
+ */
27
+ type SourceLocation = [source: Source, start: number, end: number];
28
+ type PluginFn = (api: PluginAPI) => void;
29
+ type PluginWithConfig = {
30
+ handler: PluginFn;
31
+ config?: UserConfig;
32
+ /** @internal */
33
+ reference?: boolean;
34
+ src?: SourceLocation | undefined;
35
+ };
36
+ type PluginWithOptions<T> = {
37
+ (options?: T): PluginWithConfig;
38
+ __isOptionsFunction: true;
39
+ };
40
+ type Plugin = PluginFn | PluginWithConfig | PluginWithOptions<any>;
41
+ type PluginAPI = {
42
+ addBase(base: CssInJs): void;
43
+ addVariant(name: string, variant: string | string[] | CssInJs): void;
44
+ matchVariant<T = string>(name: string, cb: (value: T | string, extra: {
45
+ modifier: string | null;
46
+ }) => string | string[], options?: {
47
+ values?: Record<string, T>;
48
+ sort?(a: {
49
+ value: T | string;
50
+ modifier: string | null;
51
+ }, b: {
52
+ value: T | string;
53
+ modifier: string | null;
54
+ }): number;
55
+ }): void;
56
+ addUtilities(utilities: Record<string, CssInJs | CssInJs[]> | Record<string, CssInJs | CssInJs[]>[], options?: {}): void;
57
+ matchUtilities(utilities: Record<string, (value: string, extra: {
58
+ modifier: string | null;
59
+ }) => CssInJs | CssInJs[]>, options?: Partial<{
60
+ type: string | string[];
61
+ supportsNegativeValues: boolean;
62
+ values: Record<string, string> & {
63
+ __BARE_VALUE__?: (value: N) => string | undefined;
64
+ };
65
+ modifiers: 'any' | Record<string, string>;
66
+ }>): void;
67
+ addComponents(utilities: Record<string, CssInJs> | Record<string, CssInJs>[], options?: {}): void;
68
+ matchComponents(utilities: Record<string, (value: string, extra: {
69
+ modifier: string | null;
70
+ }) => CssInJs>, options?: Partial<{
71
+ type: string | string[];
72
+ supportsNegativeValues: boolean;
73
+ values: Record<string, string> & {
74
+ __BARE_VALUE__?: (value: N) => string | undefined;
75
+ };
76
+ modifiers: 'any' | Record<string, string>;
77
+ }>): void;
78
+ theme(path: string, defaultValue?: any): any;
79
+ config(path?: string, defaultValue?: any): any;
80
+ prefix(className: string): string;
81
+ };
82
+ type CssInJs = {
83
+ [key: string]: string | string[] | CssInJs | CssInJs[];
84
+ };
85
+
86
+ type ResolvableTo<T> = T | ((utils: P) => T);
87
+ type ThemeValue = ResolvableTo<Record<string, unknown>> | null | undefined;
88
+ type ThemeConfig = Record<string, ThemeValue> & {
89
+ extend?: Record<string, ThemeValue>;
90
+ };
91
+ type ContentFile = string | {
92
+ raw: string;
93
+ extension?: string;
94
+ };
95
+ type DarkModeStrategy = false | 'media' | 'class' | ['class', string] | 'selector' | ['selector', string] | ['variant', string | string[]];
96
+ interface UserConfig {
97
+ presets?: UserConfig[];
98
+ theme?: ThemeConfig;
99
+ plugins?: Plugin[];
100
+ }
101
+ interface UserConfig {
102
+ content?: ContentFile[] | {
103
+ relative?: boolean;
104
+ files: ContentFile[];
105
+ };
106
+ }
107
+ interface UserConfig {
108
+ darkMode?: DarkModeStrategy;
109
+ }
110
+ interface UserConfig {
111
+ prefix?: string;
112
+ }
113
+ interface UserConfig {
114
+ blocklist?: string[];
115
+ }
116
+ interface UserConfig {
117
+ important?: boolean | string;
118
+ }
119
+ interface UserConfig {
120
+ future?: 'all' | Record<string, boolean>;
121
+ }
122
+ interface UserConfig {
123
+ experimental?: 'all' | Record<string, boolean>;
124
+ }
125
+
126
+ /**
127
+ * Tailwind CSS plugin for Ghostly skeleton loaders.
128
+ *
129
+ * Adds utilities for configuring ghostly CSS variables
130
+ * and a `ghostly:` variant for conditional skeleton styles.
131
+ *
132
+ * @example
133
+ * ```js
134
+ * // tailwind.config.js
135
+ * import ghostly from '@ghostly-ui/core/tailwind'
136
+ * export default { plugins: [ghostly] }
137
+ * ```
138
+ *
139
+ * Usage in templates:
140
+ * ```html
141
+ * <div class="ghostly-radius-lg ghostly-speed-fast">
142
+ * ...
143
+ * </div>
144
+ * ```
145
+ */
146
+ declare const _default: PluginWithConfig;
147
+
148
+ export { _default as default };
@@ -0,0 +1,30 @@
1
+ // src/tailwind.ts
2
+ import plugin from "tailwindcss/plugin";
3
+ var tailwind_default = plugin(function ghostlyPlugin({ addUtilities, addVariant, matchUtilities }) {
4
+ addVariant("ghostly", "[data-ghostly] &");
5
+ addUtilities({
6
+ ".ghostly-radius-none": { "--ghostly-radius": "0px" },
7
+ ".ghostly-radius-xs": { "--ghostly-radius": "2px" },
8
+ ".ghostly-radius-sm": { "--ghostly-radius": "4px" },
9
+ ".ghostly-radius-md": { "--ghostly-radius": "8px" },
10
+ ".ghostly-radius-lg": { "--ghostly-radius": "12px" },
11
+ ".ghostly-radius-full": { "--ghostly-radius": "9999px" }
12
+ });
13
+ addUtilities({
14
+ ".ghostly-speed-slow": { "--ghostly-speed": "2s" },
15
+ ".ghostly-speed-normal": { "--ghostly-speed": "1.5s" },
16
+ ".ghostly-speed-fast": { "--ghostly-speed": "0.8s" }
17
+ });
18
+ matchUtilities(
19
+ { "ghostly-color": (value) => ({ "--ghostly-color": value }) },
20
+ { values: {}, type: ["color"] }
21
+ );
22
+ matchUtilities(
23
+ { "ghostly-shine": (value) => ({ "--ghostly-shine": value }) },
24
+ { values: {}, type: ["color"] }
25
+ );
26
+ });
27
+ export {
28
+ tailwind_default as default
29
+ };
30
+ //# sourceMappingURL=tailwind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tailwind.ts"],"sourcesContent":["import plugin from 'tailwindcss/plugin'\n\n/**\n * Tailwind CSS plugin for Ghostly skeleton loaders.\n *\n * Adds utilities for configuring ghostly CSS variables\n * and a `ghostly:` variant for conditional skeleton styles.\n *\n * @example\n * ```js\n * // tailwind.config.js\n * import ghostly from '@ghostly-ui/core/tailwind'\n * export default { plugins: [ghostly] }\n * ```\n *\n * Usage in templates:\n * ```html\n * <div class=\"ghostly-radius-lg ghostly-speed-fast\">\n * ...\n * </div>\n * ```\n */\nexport default plugin(function ghostlyPlugin({ addUtilities, addVariant, matchUtilities }) {\n // Variant: `ghostly:` targets elements inside a ghostly container\n addVariant('ghostly', '[data-ghostly] &')\n\n // Radius utilities\n addUtilities({\n '.ghostly-radius-none': { '--ghostly-radius': '0px' },\n '.ghostly-radius-xs': { '--ghostly-radius': '2px' },\n '.ghostly-radius-sm': { '--ghostly-radius': '4px' },\n '.ghostly-radius-md': { '--ghostly-radius': '8px' },\n '.ghostly-radius-lg': { '--ghostly-radius': '12px' },\n '.ghostly-radius-full': { '--ghostly-radius': '9999px' },\n })\n\n // Speed utilities\n addUtilities({\n '.ghostly-speed-slow': { '--ghostly-speed': '2s' },\n '.ghostly-speed-normal': { '--ghostly-speed': '1.5s' },\n '.ghostly-speed-fast': { '--ghostly-speed': '0.8s' },\n })\n\n // Color utilities (arbitrary value via matchUtilities)\n matchUtilities(\n { 'ghostly-color': (value: string) => ({ '--ghostly-color': value }) },\n { values: {}, type: ['color'] },\n )\n\n matchUtilities(\n { 'ghostly-shine': (value: string) => ({ '--ghostly-shine': value }) },\n { values: {}, type: ['color'] },\n )\n})\n"],"mappings":";AAAA,OAAO,YAAY;AAsBnB,IAAO,mBAAQ,OAAO,SAAS,cAAc,EAAE,cAAc,YAAY,eAAe,GAAG;AAEzF,aAAW,WAAW,kBAAkB;AAGxC,eAAa;AAAA,IACX,wBAAwB,EAAE,oBAAoB,MAAM;AAAA,IACpD,sBAAsB,EAAE,oBAAoB,MAAM;AAAA,IAClD,sBAAsB,EAAE,oBAAoB,MAAM;AAAA,IAClD,sBAAsB,EAAE,oBAAoB,MAAM;AAAA,IAClD,sBAAsB,EAAE,oBAAoB,OAAO;AAAA,IACnD,wBAAwB,EAAE,oBAAoB,SAAS;AAAA,EACzD,CAAC;AAGD,eAAa;AAAA,IACX,uBAAuB,EAAE,mBAAmB,KAAK;AAAA,IACjD,yBAAyB,EAAE,mBAAmB,OAAO;AAAA,IACrD,uBAAuB,EAAE,mBAAmB,OAAO;AAAA,EACrD,CAAC;AAGD;AAAA,IACE,EAAE,iBAAiB,CAAC,WAAmB,EAAE,mBAAmB,MAAM,GAAG;AAAA,IACrE,EAAE,QAAQ,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE;AAAA,EAChC;AAEA;AAAA,IACE,EAAE,iBAAiB,CAAC,WAAmB,EAAE,mBAAmB,MAAM,GAAG;AAAA,IACrE,EAAE,QAAQ,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE;AAAA,EAChC;AACF,CAAC;","names":[]}
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@ghostly-ui/core",
3
+ "version": "0.2.0",
4
+ "description": "Zero-config skeleton loaders. Wrap your component, done.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./dist/index.d.cts",
18
+ "default": "./dist/index.cjs"
19
+ }
20
+ },
21
+ "./css": "./dist/ghostly.css",
22
+ "./tailwind": {
23
+ "import": {
24
+ "types": "./dist/tailwind.d.ts",
25
+ "default": "./dist/tailwind.js"
26
+ },
27
+ "require": {
28
+ "types": "./dist/tailwind.d.cts",
29
+ "default": "./dist/tailwind.cjs"
30
+ }
31
+ }
32
+ },
33
+ "files": ["dist", "README.md", "LICENSE"],
34
+ "sideEffects": ["*.css"],
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "dev": "tsup --watch"
38
+ },
39
+ "peerDependencies": {
40
+ "tailwindcss": ">=3.0.0"
41
+ },
42
+ "peerDependenciesMeta": {
43
+ "tailwindcss": {
44
+ "optional": true
45
+ }
46
+ },
47
+ "devDependencies": {
48
+ "tailwindcss": "^4.0.0",
49
+ "tsup": "^8.5.0",
50
+ "typescript": "^5.9.3"
51
+ },
52
+ "keywords": [
53
+ "skeleton",
54
+ "loading",
55
+ "placeholder",
56
+ "shimmer",
57
+ "css",
58
+ "zero-config"
59
+ ],
60
+ "repository": {
61
+ "type": "git",
62
+ "url": "https://github.com/AdanSerrano/ghostly",
63
+ "directory": "packages/core"
64
+ },
65
+ "bugs": {
66
+ "url": "https://github.com/AdanSerrano/ghostly/issues"
67
+ },
68
+ "homepage": "https://github.com/AdanSerrano/ghostly#readme"
69
+ }