@evolve.ui/theme-system 1.1.0 → 1.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.
Files changed (99) hide show
  1. package/{allThemes-CFS_9-tk.cjs → allThemes-3d8nhymo.cjs} +12 -2
  2. package/allThemes-3d8nhymo.cjs.map +1 -0
  3. package/{allThemes-OV-ayn4C.mjs → allThemes-Abn6KDm7.mjs} +7 -3
  4. package/allThemes-Abn6KDm7.mjs.map +1 -0
  5. package/allThemes.cjs +1 -1
  6. package/allThemes.d.cts +1 -0
  7. package/allThemes.d.cts.map +1 -1
  8. package/allThemes.d.mts +1 -0
  9. package/allThemes.d.mts.map +1 -1
  10. package/allThemes.mjs +1 -1
  11. package/brands-CE0wIkKX.d.cts +58 -0
  12. package/brands-CE0wIkKX.d.cts.map +1 -0
  13. package/brands-CE0wIkKX.d.mts +58 -0
  14. package/brands-CE0wIkKX.d.mts.map +1 -0
  15. package/brands.cjs +16 -13
  16. package/brands.cjs.map +1 -1
  17. package/brands.d.cts +2 -2
  18. package/brands.d.mts +2 -2
  19. package/brands.mjs +16 -13
  20. package/brands.mjs.map +1 -1
  21. package/css/EverydayHarbor.css +377 -0
  22. package/css/EverydayHarbor.effects.css +650 -0
  23. package/{default-b-tgfyxR.cjs → default-B6VWF0Js.cjs} +237 -36
  24. package/default-B6VWF0Js.cjs.map +1 -0
  25. package/{default-B2n4PnsJ.mjs → default-BogJDhKE.mjs} +238 -37
  26. package/default-BogJDhKE.mjs.map +1 -0
  27. package/effects.cjs +26 -0
  28. package/effects.cjs.map +1 -0
  29. package/effects.d.cts +22 -0
  30. package/effects.d.cts.map +1 -0
  31. package/effects.d.mts +22 -0
  32. package/effects.d.mts.map +1 -0
  33. package/effects.mjs +22 -0
  34. package/effects.mjs.map +1 -0
  35. package/evolve-ui.entries.json +5 -0
  36. package/index.cjs +12 -5
  37. package/index.cjs.map +1 -1
  38. package/index.d.cts +108 -4
  39. package/index.d.cts.map +1 -1
  40. package/index.d.mts +108 -4
  41. package/index.d.mts.map +1 -1
  42. package/index.mjs +9 -6
  43. package/index.mjs.map +1 -1
  44. package/package.json +5 -1
  45. package/{plugin-DPtvp4lp.cjs → plugin-B3PF4RMI.cjs} +299 -110
  46. package/plugin-B3PF4RMI.cjs.map +1 -0
  47. package/{plugin-D4eCyUJe.mjs → plugin-D6aLCeNB.mjs} +258 -111
  48. package/plugin-D6aLCeNB.mjs.map +1 -0
  49. package/src/effects/__fixtures__/fixtureLayer.ts +88 -0
  50. package/src/effects/brands/EverydayHarbor.ts +392 -0
  51. package/src/effects/classes.ts +19 -0
  52. package/src/effects/emit.ts +314 -0
  53. package/src/effects/hooks.ts +493 -0
  54. package/src/effects/index.ts +2 -0
  55. package/src/effects/layers.ts +18 -0
  56. package/src/effects/rules.ts +90 -0
  57. package/src/effects/rules.typecheck.ts +50 -0
  58. package/src/effects/tokens.ts +148 -0
  59. package/src/effects/validate.ts +683 -0
  60. package/src/effects/vocabulary.ts +32 -0
  61. package/src/index.ts +3 -0
  62. package/src/tailwind/animation.ts +30 -0
  63. package/src/tailwind/borderWidth.ts +14 -0
  64. package/src/tailwind/plugin.ts +230 -239
  65. package/src/tailwind/reducedMotion.ts +164 -0
  66. package/src/tailwind/text.ts +4 -2
  67. package/src/themeValues.snapshot.ts +6735 -0
  68. package/src/themes/EverydayHarbor.ts +147 -0
  69. package/src/themes/allThemes.ts +2 -0
  70. package/src/themes/brandSources.ts +37 -0
  71. package/src/themes/brands.ts +41 -15
  72. package/src/themes/contrastFloors.ts +26 -0
  73. package/src/themes/inkRamps.ts +43 -0
  74. package/src/themes/runtime/buildTheme.ts +6 -19
  75. package/src/themes/runtime/everydayHarbor.ts +3 -0
  76. package/src/themes/semanticColorTokenSteps.ts +5 -2
  77. package/src/themes/themeValues.ts +374 -0
  78. package/src/themes/wcag.ts +48 -0
  79. package/tailwind/plugin.cjs +5 -1
  80. package/tailwind/plugin.d.cts +458 -1
  81. package/tailwind/plugin.d.cts.map +1 -1
  82. package/tailwind/plugin.d.mts +458 -1
  83. package/tailwind/plugin.d.mts.map +1 -1
  84. package/tailwind/plugin.mjs +2 -2
  85. package/themes/runtime/default.cjs +1 -1
  86. package/themes/runtime/default.mjs +1 -1
  87. package/themes/themeType.d.cts +1 -1
  88. package/themes/themeType.d.mts +1 -1
  89. package/allThemes-CFS_9-tk.cjs.map +0 -1
  90. package/allThemes-OV-ayn4C.mjs.map +0 -1
  91. package/brands-n30rolHQ.d.cts +0 -33
  92. package/brands-n30rolHQ.d.cts.map +0 -1
  93. package/brands-n30rolHQ.d.mts +0 -33
  94. package/brands-n30rolHQ.d.mts.map +0 -1
  95. package/default-B2n4PnsJ.mjs.map +0 -1
  96. package/default-b-tgfyxR.cjs.map +0 -1
  97. package/plugin-D4eCyUJe.mjs.map +0 -1
  98. package/plugin-DPtvp4lp.cjs.map +0 -1
  99. package/src/themes/generate.ts +0 -46
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Reduced-motion policy for the animation registry (DS-95 / audit findings SA-02 + SA-03).
3
+ *
4
+ * Every animation Tailwind resolves for this library — the local `theme.extend.animation`
5
+ * entries *and* Tailwind's own defaults — gets exactly one entry here. `reducedMotion.spec.ts`
6
+ * fails if the two key sets ever diverge, so a new animation cannot ship untriaged.
7
+ *
8
+ * ## Why duration-collapse rather than `animation: none`
9
+ *
10
+ * `animation: none` fires no `animationend`. `Modal/ComposableModal` and
11
+ * `Modal/hooks/useCloseModal` both depend on that event to finish closing — `useCloseModal`
12
+ * clears a 450ms watchdog there that otherwise logs an error and rejects an awaited promise. A
13
+ * naive `none` gate would strand the modal open *for reduced-motion users only*, which is worse
14
+ * than the gap it fixes. Collapsing the duration keeps the event, and keeps `forwards`/`both`
15
+ * fill end-states intact (`slideUp`, `slideDown`, `progress-completion`).
16
+ *
17
+ * ## Why the doubled class selector
18
+ *
19
+ * Tailwind emits `base` before `utilities`. A consumer building with `important: true` gets
20
+ * `!important` utilities too, and two equal-specificity `!important` rules resolve by source
21
+ * order — the utility would win. `.animate-x.animate-x` (0-2-0) beats it on specificity instead,
22
+ * and is inert for everyone else.
23
+ *
24
+ * Two known limitations, both external-consumer only:
25
+ * - `prefix` renames the utility classes (`.tw-animate-*`), which these selectors will not match.
26
+ * - `important: '#selector'` (the selector strategy, not `important: true`) emits
27
+ * `#selector .animate-x` at specificity 0-1-0 *plus* an id — which outranks 0-2-0 regardless
28
+ * of `!important`. The doubled class only defends against `important: true`.
29
+ *
30
+ * Components in this repo hardcode unprefixed class strings and no in-repo config sets either
31
+ * option, so neither affects this monorepo.
32
+ */
33
+ export type ReducedMotionTier =
34
+ /** Collapse to ~1ms under reduce. The animation still runs, so `animationend` still fires. */
35
+ | 'instant'
36
+ /** Left alone — infinite indeterminate status indicators, where stopping removes feedback. */
37
+ | 'retain'
38
+ /** Not an animation (`animate-none` is the absence utility). */
39
+ | 'n/a';
40
+
41
+ /**
42
+ * `selectors` is only meaningful on `instant` entries — nothing is emitted for `retain`/`n/a`,
43
+ * so allowing it there would let a future entry declare a selector that silently does nothing.
44
+ * The union makes that a type error rather than a quiet no-op.
45
+ */
46
+ export type ReducedMotionPolicyEntry =
47
+ | {
48
+ /** Why this tier was chosen. Asserted non-empty, so every entry carries a justification. */
49
+ reason: string;
50
+ /**
51
+ * Extra selectors for animations applied through a component class rather than an
52
+ * `animate-*` utility. Emitted verbatim, so they are written at full specificity.
53
+ */
54
+ selectors?: readonly string[];
55
+ tier: 'instant';
56
+ }
57
+ | { reason: string; selectors?: never; tier: 'n/a' | 'retain' };
58
+
59
+ export const REDUCED_MOTION_POLICY: Record<string, ReducedMotionPolicyEntry> = {
60
+ // --- Local registry (theme.extend.animation) ---
61
+ fadeIn: {
62
+ reason: 'Opacity only, but collapsed alongside fadeInScaled so a modal opens the same way in both variants (ComposableModal picks between them on hasContentPager).',
63
+ tier: 'instant',
64
+ },
65
+ fadeInOpacity: {
66
+ reason: 'Opacity and visibility only; collapsing a fade is not a motion regression.',
67
+ tier: 'instant',
68
+ },
69
+ fadeInScaled: { reason: 'Scales 0.7 to 1.', tier: 'instant' },
70
+ fadeOut: {
71
+ reason: 'Scales 1 to 0.7. Modal close depends on its animationend, so it is collapsed rather than removed.',
72
+ tier: 'instant',
73
+ },
74
+ loading: {
75
+ reason: 'Infinite indeterminate loading indicator (LoadingDot). Collapsing it removes the only signal that work is in progress.',
76
+ tier: 'retain',
77
+ },
78
+ 'progress-completion': {
79
+ reason: 'Scales. Zeroing the delay matters here: the composite is "shrink 0.15s both, grow 0.15s ease-in-out 0.15s both", so a duration-only collapse would hold the ring at scale(0.5) for 150ms and then snap.',
80
+ tier: 'instant',
81
+ },
82
+ 'progress-icon-fade-in': {
83
+ reason: 'Opacity only, but its 0.15s delay sequences it after progress-completion. Retaining it would leave a completed ProgressCircle ring with an empty centre for 150ms.',
84
+ tier: 'instant',
85
+ },
86
+ 'progress-wave': {
87
+ reason: 'Infinite indeterminate progress indicator (ProgressCircle). Same essential-feedback rationale as loading.',
88
+ tier: 'retain',
89
+ },
90
+ saveButtonPulse: { reason: 'Scales.', tier: 'instant' },
91
+ saveButtonWiggle: { reason: 'Rotates and scales.', tier: 'instant' },
92
+ saveHeartColorFlash: {
93
+ reason: 'Animates fill only, but it was already gated before DS-95 and must stay gated. Shipped via .hui-fb-color-flash, not the animate-* utility (which has no call sites), so it needs the explicit selector below.',
94
+ selectors: ['.hui-fb-color-flash .hui-fb-icon-frame svg path'],
95
+ tier: 'instant',
96
+ },
97
+ saveHeartPop: { reason: 'Scales.', tier: 'instant' },
98
+ saveHeartTwirlBloom: { reason: 'Rotates 180 degrees at 1.15x scale.', tier: 'instant' },
99
+ saveLabelShimmer: {
100
+ reason: 'Animates background-position. The .hui-fb-shimmer paint block in plugin.ts separately drops the gradient under reduce so the label stays legible.',
101
+ tier: 'instant',
102
+ },
103
+ slideDown: {
104
+ reason: 'Translates on Y. Collapsed rather than removed so CookieConsent still receives animationend.',
105
+ tier: 'instant',
106
+ },
107
+ slideUp: { reason: 'Translates on Y. Same animationend rationale as slideDown.', tier: 'instant' },
108
+
109
+ // --- Tailwind defaults, which survive theme.extend and are reachable by consumers ---
110
+ bounce: { reason: 'Translates on Y.', tier: 'instant' },
111
+ none: { reason: 'Not an animation — animate-none is the absence utility.', tier: 'n/a' },
112
+ ping: { reason: 'Scales and fades. No in-repo call site, but consumers can use it.', tier: 'instant' },
113
+ pulse: {
114
+ reason: 'Infinite, but opacity only — the conventional skeleton-loading indicator. Retained as essential feedback with no motion component.',
115
+ tier: 'retain',
116
+ },
117
+ spin: {
118
+ reason: 'Infinite indeterminate spinner (LoadingSpinner, ProgressCircle). A Tailwind default rather than a local entry, which is exactly why the policy is keyed on the resolved registry.',
119
+ tier: 'retain',
120
+ },
121
+ };
122
+
123
+ const REDUCE_QUERY = '@media (prefers-reduced-motion: reduce)';
124
+
125
+ const COLLAPSED_DECLARATIONS = {
126
+ 'animation-delay': '0ms !important',
127
+ 'animation-duration': '1ms !important',
128
+ 'animation-iteration-count': '1 !important',
129
+ };
130
+
131
+ /**
132
+ * Builds the `@media (prefers-reduced-motion: reduce)` block for `addBase`.
133
+ *
134
+ * Emitted from `addBase` rather than `addComponents` deliberately: `addComponents` is subject to
135
+ * caching discrepancies in this monorepo's Storybook build that stop newly added rules landing.
136
+ */
137
+ export const buildReducedMotionBase = () => {
138
+ const rules: Record<string, typeof COLLAPSED_DECLARATIONS> = {};
139
+
140
+ const addRule = (selector: string) => {
141
+ if (rules[selector]) {
142
+ // Two policy entries claiming one selector would silently overwrite each other.
143
+ // Every rule emits identical declarations today, so this can only fire on a
144
+ // genuine authoring mistake — fail loudly rather than ship half a gate.
145
+ throw new Error(`Duplicate reduced-motion selector: ${selector}`);
146
+ }
147
+
148
+ rules[selector] = { ...COLLAPSED_DECLARATIONS };
149
+ };
150
+
151
+ Object.entries(REDUCED_MOTION_POLICY).forEach(([key, entry]) => {
152
+ if (entry.tier !== 'instant') {
153
+ return;
154
+ }
155
+
156
+ // Doubled class (0-2-0) so an `important: true` consumer's utility cannot win on
157
+ // source order — Tailwind emits `base` before `utilities`.
158
+ addRule(`.animate-${key}.animate-${key}`);
159
+
160
+ entry.selectors?.forEach(addRule);
161
+ });
162
+
163
+ return { [REDUCE_QUERY]: rules };
164
+ };
@@ -117,9 +117,11 @@ export const textComponents = {
117
117
  letterSpacing: wrapVar(letterSpacingH3),
118
118
  lineHeight: '30px',
119
119
  [`@media (width < ${screens.smMax.max})`]: {
120
- fontSize: '16px',
120
+ // 18px/27px, not 16px/24px: at 16px this rule rendered identically to `.hui-text-h4`
121
+ // below the breakpoint, collapsing two adjacent heading levels (DS-97 / SA-04).
122
+ fontSize: '18px',
121
123
  letterSpacing: wrapVar(letterSpacingH3Mobile),
122
- lineHeight: '24px',
124
+ lineHeight: '27px',
123
125
  },
124
126
  },
125
127
  '.hui-text-h4': {