@ghostly-ui/core 0.2.1 → 0.2.2

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.
Files changed (2) hide show
  1. package/dist/ghostly.css +119 -97
  2. package/package.json +1 -1
package/dist/ghostly.css CHANGED
@@ -1,8 +1,9 @@
1
1
  /* ============================================================
2
2
  GHOSTLY — Zero-config skeleton loaders
3
3
 
4
- Usage: add data-ghostly="shimmer|pulse|wave" to any container.
5
- All leaf elements inside become skeleton blocks automatically.
4
+ Philosophy: NEVER override layout properties. Only hide content
5
+ and add skeleton background. The component's own CSS defines
6
+ all sizing, spacing, and border-radius.
6
7
  ============================================================ */
7
8
 
8
9
  /* --- Custom properties (override these to customize) --- */
@@ -31,25 +32,7 @@
31
32
  }
32
33
 
33
34
  /* ============================================================
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
35
+ 1. CONTAINERBlocks interaction while loading
53
36
  ============================================================ */
54
37
 
55
38
  [data-ghostly] {
@@ -59,7 +42,8 @@
59
42
  }
60
43
 
61
44
  /* ============================================================
62
- 3. TEXT ELEMENTS — Become opaque skeleton blocks
45
+ 2. TEXT ELEMENTS — Hide text, show skeleton background
46
+ Only override visual properties. NEVER touch layout.
63
47
  ============================================================ */
64
48
 
65
49
  [data-ghostly] :where(
@@ -74,81 +58,137 @@
74
58
  color: transparent !important;
75
59
  background-color: var(--ghostly-color) !important;
76
60
  background-image: none !important;
77
- border-radius: var(--ghostly-radius) !important;
78
61
  border-color: transparent !important;
79
62
  box-shadow: none !important;
80
63
  text-decoration: none !important;
64
+ text-shadow: none !important;
81
65
  outline: none !important;
66
+ /* Prevent empty elements from collapsing vertically */
82
67
  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;
68
+ /* Inline elements need display block/inline-block to respect min-height */
69
+ display: var(--_ghostly-display, revert);
102
70
  }
103
71
 
104
- /* Buttons keep their shape */
105
- [data-ghostly] :where(button) {
106
- min-height: 2.25rem;
107
- min-width: 5rem;
72
+ /* Make inline elements (span, a, em, etc.) respect min-height/width */
73
+ [data-ghostly] :where(
74
+ span, a, em, strong, small, mark, code, abbr,
75
+ cite, q, sub, sup, del, ins, time, label
76
+ ) {
77
+ --_ghostly-display: inline-block;
108
78
  }
109
79
 
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; }
80
+ /* ── Prevent text elements from collapsing horizontally ──
81
+ Since text is transparent, the element's width comes from the text
82
+ content. When content is empty ('') the element collapses to 0 width.
83
+ We use min-width to prevent this. When real (visible) content is loaded,
84
+ the content width will naturally exceed the min-width. */
85
+ [data-ghostly] :where(h1) { min-width: 60%; }
86
+ [data-ghostly] :where(h2) { min-width: 50%; }
87
+ [data-ghostly] :where(h3) { min-width: 45%; }
88
+ [data-ghostly] :where(h4, h5, h6) { min-width: 40%; }
89
+ [data-ghostly] :where(p) { min-width: 80%; }
90
+ [data-ghostly] :where(span, a, em, strong, small, label, time, code) { min-width: 3rem; }
91
+ [data-ghostly] :where(button) { min-width: 5rem; }
92
+ [data-ghostly] :where(input, textarea, select) { min-width: 6rem; }
93
+
94
+ /* Headings: proportional min-height */
95
+ [data-ghostly] :where(h1) { min-height: 1.75em; }
96
+ [data-ghostly] :where(h2) { min-height: 1.5em; }
97
+ [data-ghostly] :where(h3) { min-height: 1.3em; }
98
+ [data-ghostly] :where(h4, h5, h6) { min-height: 1.15em; }
99
+
100
+ /* Paragraphs: taller to simulate multi-line text */
101
+ [data-ghostly] :where(p) { min-height: 3em; }
102
+
103
+ /* Code blocks: taller */
104
+ [data-ghostly] :where(pre) { min-height: 5em; }
105
+
106
+ /* Interactive elements: reasonable minimums */
107
+ [data-ghostly] :where(input, textarea, select) { min-height: 2.5rem; }
108
+ [data-ghostly] :where(button) { min-height: 2.25rem; }
122
109
 
123
110
  /* ============================================================
124
- 5. MEDIA ELEMENTS — Become solid skeleton blocks
111
+ 3. MEDIA ELEMENTS — Hide content, show skeleton block
112
+ Preserves ALL existing dimensions and border-radius.
125
113
  ============================================================ */
126
114
 
127
115
  [data-ghostly] :where(img, svg, video, canvas, picture, iframe) {
128
- color: transparent !important;
129
116
  background-color: var(--ghostly-color) !important;
130
- border-radius: var(--ghostly-radius) !important;
131
117
  border-color: transparent !important;
132
118
  box-shadow: none !important;
133
119
  }
134
120
 
135
- /* Hide actual content (images, video frames) */
121
+ /* Hide image content without changing dimensions */
136
122
  [data-ghostly] :where(img) {
137
- object-position: -9999px;
123
+ color: transparent !important;
124
+ object-position: -9999px !important;
138
125
  }
139
126
 
140
127
  [data-ghostly] :where(video, iframe) {
141
128
  opacity: 0;
129
+ }
130
+
131
+ /* SVG: hide strokes/fills but keep size */
132
+ [data-ghostly] :where(svg) {
133
+ color: transparent !important;
134
+ fill: transparent !important;
135
+ stroke: transparent !important;
136
+ }
137
+
138
+ /* Only add min-size for SVG icons that might be empty */
139
+ [data-ghostly] :where(svg:empty) {
140
+ min-height: 1.5rem;
141
+ min-width: 1.5rem;
142
+ }
143
+
144
+ /* ============================================================
145
+ 4. EMPTY CONTAINERS — Style divs that act as placeholders
146
+ When a component renders an empty div instead of an img
147
+ (e.g. avatar placeholder), it should still show skeleton.
148
+ ============================================================ */
149
+
150
+ [data-ghostly] :where(div:empty) {
142
151
  background-color: var(--ghostly-color) !important;
143
152
  }
144
153
 
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%;
154
+ /* ============================================================
155
+ 5. BORDER RADIUS — Use computed value, fallback to default
156
+ Never override existing border-radius from the component.
157
+ ============================================================ */
158
+
159
+ /* Apply default radius ONLY to elements that don't have one set.
160
+ We use a very low specificity so any component CSS wins. */
161
+ @layer ghostly-defaults {
162
+ [data-ghostly] h1, [data-ghostly] h2, [data-ghostly] h3,
163
+ [data-ghostly] h4, [data-ghostly] h5, [data-ghostly] h6,
164
+ [data-ghostly] p, [data-ghostly] span, [data-ghostly] a,
165
+ [data-ghostly] li, [data-ghostly] td, [data-ghostly] th,
166
+ [data-ghostly] button, [data-ghostly] input,
167
+ [data-ghostly] textarea, [data-ghostly] select,
168
+ [data-ghostly] code, [data-ghostly] pre,
169
+ [data-ghostly] img, [data-ghostly] svg,
170
+ [data-ghostly] video, [data-ghostly] canvas {
171
+ border-radius: var(--ghostly-radius);
172
+ }
149
173
  }
150
174
 
151
- /* Custom aspect ratio via data attribute */
175
+ /* ============================================================
176
+ 6. CUSTOM LINE COUNT — data-ghostly-lines="N"
177
+ ============================================================ */
178
+
179
+ [data-ghostly] :where([data-ghostly-lines="1"]) { min-height: 1em; }
180
+ [data-ghostly] :where([data-ghostly-lines="2"]) { min-height: 2em; }
181
+ [data-ghostly] :where([data-ghostly-lines="3"]) { min-height: 3em; }
182
+ [data-ghostly] :where([data-ghostly-lines="4"]) { min-height: 4em; }
183
+ [data-ghostly] :where([data-ghostly-lines="5"]) { min-height: 5em; }
184
+ [data-ghostly] :where([data-ghostly-lines="6"]) { min-height: 6em; }
185
+ [data-ghostly] :where([data-ghostly-lines="7"]) { min-height: 7em; }
186
+ [data-ghostly] :where([data-ghostly-lines="8"]) { min-height: 8em; }
187
+
188
+ /* ============================================================
189
+ 7. CUSTOM ASPECT RATIO — data-ghostly-ratio
190
+ ============================================================ */
191
+
152
192
  [data-ghostly] :where([data-ghostly-ratio="1/1"]) { aspect-ratio: 1/1 !important; }
153
193
  [data-ghostly] :where([data-ghostly-ratio="4/3"]) { aspect-ratio: 4/3 !important; }
154
194
  [data-ghostly] :where([data-ghostly-ratio="16/9"]) { aspect-ratio: 16/9 !important; }
@@ -156,35 +196,16 @@
156
196
  [data-ghostly] :where([data-ghostly-ratio="3/4"]) { aspect-ratio: 3/4 !important; }
157
197
  [data-ghostly] :where([data-ghostly-ratio="9/16"]) { aspect-ratio: 9/16 !important; }
158
198
 
159
- /* SVG icons (typically small) */
160
- [data-ghostly] :where(svg) {
161
- min-height: 1.5rem;
162
- min-width: 1.5rem;
163
- }
164
-
165
199
  /* ============================================================
166
- 6. DECORATIVE — Strip visual noise
200
+ 8. DECORATIVE — Strip visual noise
167
201
  ============================================================ */
168
202
 
169
203
  [data-ghostly] :where(hr) {
170
204
  border-color: var(--ghostly-color) !important;
171
205
  }
172
206
 
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
207
  /* ============================================================
187
- 7. EXCLUSIONS — data-ghostly-ignore restores original styles
208
+ 9. EXCLUSIONS — data-ghostly-ignore restores original styles
188
209
  ============================================================ */
189
210
 
190
211
  [data-ghostly] [data-ghostly-ignore],
@@ -198,17 +219,15 @@
198
219
  pointer-events: auto;
199
220
  user-select: auto;
200
221
  min-height: initial;
201
- min-width: initial;
202
222
  animation: none !important;
203
223
  }
204
224
 
205
225
  /* ============================================================
206
- 8. ANIMATIONS — Applied via --_ghostly-target marker
226
+ 10. ANIMATIONS
207
227
  ============================================================ */
208
228
 
209
229
  /* --- Shimmer — gradient sweep left to right --- */
210
230
 
211
- [data-ghostly='shimmer'] :where([style*="--_ghostly-target"]),
212
231
  [data-ghostly='shimmer'] :where(
213
232
  h1, h2, h3, h4, h5, h6,
214
233
  p, span, a, li, td, th, dt, dd,
@@ -217,7 +236,8 @@
217
236
  button, input, textarea, select, option,
218
237
  fieldset, dialog, details, address,
219
238
  time, abbr, sub, sup, del, ins,
220
- img, svg, video, canvas, picture, iframe
239
+ img, svg, video, canvas, picture, iframe,
240
+ div:empty
221
241
  ) {
222
242
  background: linear-gradient(
223
243
  90deg,
@@ -246,7 +266,8 @@
246
266
  button, input, textarea, select, option,
247
267
  fieldset, dialog, details, address,
248
268
  time, abbr, sub, sup, del, ins,
249
- img, svg, video, canvas, picture, iframe
269
+ img, svg, video, canvas, picture, iframe,
270
+ div:empty
250
271
  ) {
251
272
  animation: ghostly-pulse var(--ghostly-speed) ease-in-out infinite !important;
252
273
  }
@@ -266,7 +287,8 @@
266
287
  button, input, textarea, select, option,
267
288
  fieldset, dialog, details, address,
268
289
  time, abbr, sub, sup, del, ins,
269
- img, svg, video, canvas, picture, iframe
290
+ img, svg, video, canvas, picture, iframe,
291
+ div:empty
270
292
  ) {
271
293
  animation: ghostly-wave var(--ghostly-speed) ease-in-out infinite !important;
272
294
  }
@@ -295,7 +317,7 @@
295
317
  [data-ghostly='wave'] > :nth-child(n+13) { animation-delay: 960ms !important; }
296
318
 
297
319
  /* ============================================================
298
- 9. SMOOTH TRANSITION — fade out when loading ends
320
+ 11. SMOOTH TRANSITION — fade out when loading ends
299
321
  ============================================================ */
300
322
 
301
323
  [data-ghostly-smooth] :where(
@@ -316,7 +338,7 @@
316
338
  }
317
339
 
318
340
  /* ============================================================
319
- 10. ACCESSIBILITY
341
+ 12. ACCESSIBILITY
320
342
  ============================================================ */
321
343
 
322
344
  @media (prefers-reduced-motion: reduce) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostly-ui/core",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Zero-config skeleton loaders. Wrap your component, done.",
5
5
  "license": "MIT",
6
6
  "type": "module",