@dowel-ui/themes 0.2.0 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dowel-ui/themes",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Two-tier OKLCH design tokens in plain CSS for Dowel, with seven presets that all pass WCAG AA contrast in light and dark.",
6
6
  "keywords": [
@@ -53,7 +53,7 @@
53
53
  "tailwindcss": "4.3.3",
54
54
  "tsdown": "0.22.14",
55
55
  "typescript": "6.0.3",
56
- "@dowel-ui/config": "0.2.0"
56
+ "@dowel-ui/config": "0.3.0"
57
57
  },
58
58
  "scripts": {
59
59
  "build": "tsdown",
package/src/base.css CHANGED
@@ -164,14 +164,36 @@
164
164
  }
165
165
 
166
166
  /* Respect the OS setting globally (design spec §14). Individual components do
167
- * not need to re-implement this. */
167
+ * not need to re-implement this.
168
+ *
169
+ * Two layers, because "reduce motion" is not "remove all movement". The scale
170
+ * collapses every token-driven duration, which covers everything this library
171
+ * animates. The blanket below still catches animation written by the consumer
172
+ * that does not use the tokens — but it exempts elements marked
173
+ * data-motion="indicator", so a spinner and a streaming caret keep reporting
174
+ * that something is happening. A frozen spinner is not a gentler experience;
175
+ * it says the application has hung. */
168
176
  @media (prefers-reduced-motion: reduce) {
169
- *,
170
- ::before,
171
- ::after {
177
+ :root {
178
+ /* Sub-millisecond rather than zero: the overlay primitives wait on
179
+ * animationend to unmount, and a zero-duration animation can resolve
180
+ * before the listener is attached. */
181
+ --motion-scale: 0.001;
182
+
183
+ /* Slowed, not stopped. Halving the rate removes most of the movement
184
+ * while the indicator still reads as active. */
185
+ --motion-scale-indicator: 2;
186
+ }
187
+
188
+ *:not([data-motion="indicator"]),
189
+ *:not([data-motion="indicator"])::before,
190
+ *:not([data-motion="indicator"])::after {
172
191
  animation-duration: 0.01ms !important;
173
192
  animation-iteration-count: 1 !important;
174
193
  transition-duration: 0.01ms !important;
194
+ }
195
+
196
+ * {
175
197
  scroll-behavior: auto !important;
176
198
  }
177
199
  }
package/src/tokens.css CHANGED
@@ -102,7 +102,7 @@
102
102
  --ease-in-out-quint: cubic-bezier(0.65, 0, 0.35, 1);
103
103
  --ease-overshoot: cubic-bezier(0.34, 1.4, 0.64, 1);
104
104
 
105
- --animate-spin: spin 0.7s linear infinite;
105
+ --animate-spin: spin calc(0.7s * var(--motion-scale-indicator)) linear infinite;
106
106
  --animate-pulse-soft: pulse-soft 1.8s var(--ease-in-out-quint) infinite;
107
107
 
108
108
  /* Overlay surfaces. Exits are deliberately faster than entrances: an opening
@@ -131,7 +131,12 @@
131
131
 
132
132
  /* The caret that trails streaming text. Steps rather than a fade, so it reads
133
133
  * as a terminal cursor rather than as something loading. */
134
- --animate-caret: caret 1s steps(2, start) infinite;
134
+ --animate-caret: caret calc(1s * var(--motion-scale-indicator)) steps(2, start) infinite;
135
+
136
+ /* A streamed value that has finished arriving. Distinct from appearing:
137
+ * "here is a token" and "this field is final" are different facts, and in a
138
+ * streamed object they are otherwise indistinguishable. */
139
+ --animate-settle: settle var(--duration-slow) var(--ease-out-quint);
135
140
 
136
141
  --animate-accordion-open: accordion-open var(--duration-normal) var(--ease-out-quint);
137
142
  --animate-accordion-close: accordion-close var(--duration-fast) var(--ease-in-quint);
@@ -208,6 +213,13 @@
208
213
  }
209
214
  }
210
215
 
216
+ @keyframes settle {
217
+ from {
218
+ opacity: 0;
219
+ transform: translateY(3px);
220
+ }
221
+ }
222
+
211
223
  @keyframes accordion-open {
212
224
  from {
213
225
  height: 0;
@@ -252,11 +264,25 @@
252
264
  * consumed via var() or arbitrary values (e.g. z-[var(--z-popover)]).
253
265
  */
254
266
  :root {
255
- --duration-instant: 75ms;
256
- --duration-fast: 130ms;
257
- --duration-normal: 200ms;
258
- --duration-slow: 320ms;
259
- --duration-slower: 480ms;
267
+ /* One multiplier re-times the whole system, the way --radius-scale
268
+ * re-proportions every corner. 0 stops choreography outright; 1.4 makes it
269
+ * deliberate. Set it per surface if a dense admin table wants to feel brisker
270
+ * than a marketing page.
271
+ *
272
+ * Reduced motion drives this rather than blanket-overriding every duration,
273
+ * which is what lets indicators keep reporting — see base.css. */
274
+ --motion-scale: 1;
275
+
276
+ /* Indicators that report ongoing state — a spinner, a streaming caret. Scaled
277
+ * separately because stopping them is not a reduction, it is a lie: a frozen
278
+ * spinner says the application has hung. */
279
+ --motion-scale-indicator: 1;
280
+
281
+ --duration-instant: calc(75ms * var(--motion-scale));
282
+ --duration-fast: calc(130ms * var(--motion-scale));
283
+ --duration-normal: calc(200ms * var(--motion-scale));
284
+ --duration-slow: calc(320ms * var(--motion-scale));
285
+ --duration-slower: calc(480ms * var(--motion-scale));
260
286
 
261
287
  --size-icon-xs: 0.75rem;
262
288
  --size-icon-sm: 0.875rem;