@titan-design/react-ui 0.13.0 → 0.14.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 +14 -0
- package/dist/bodymap.js +129 -76
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +129 -76
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +117 -26
- package/dist/index.d.ts +117 -26
- package/dist/index.js +677 -450
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +673 -451
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-_3Ajpiqu.d.ts → pages-CsjsUn0G.d.ts} +30 -13
- package/dist/{pages-CpW3OB3g.d.mts → pages-ZbT_pD_U.d.mts} +30 -13
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +676 -701
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +676 -701
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +31 -7
- package/dist/theme/index.d.ts +31 -7
- package/dist/theme/index.js +84 -20
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +83 -21
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +84 -20
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +84 -20
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/dist/theme/tokens.d.mts +56 -7
- package/dist/theme/tokens.d.ts +56 -7
- package/dist/theme/tokens.js +72 -20
- package/dist/theme/tokens.js.map +1 -1
- package/dist/theme/tokens.mjs +71 -21
- package/dist/theme/tokens.mjs.map +1 -1
- package/docs/render-testing-pattern.md +130 -0
- package/package.json +1 -1
- package/src/arch/Architecture.stories.tsx +3 -1
- package/src/arch/arch-graph.json +74 -72
- package/src/components/custom/ActiveWork/InitiativeBrief.tsx +2 -1
- package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +8 -3
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.stories.tsx +4 -1
- package/src/components/custom/Gauge/Gauge.tsx +3 -1
- package/src/components/custom/Scatter/Scatter.stories.tsx +10 -7
- package/src/components/custom/Scatter/Scatter.tsx +6 -4
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
- package/src/components/custom/Table/Table.stories.tsx +1 -1
- package/src/components/custom/Table/Table.tsx +2 -2
- package/src/components/custom/Treemap/Treemap.stories.tsx +13 -10
- package/src/components/custom/Treemap/Treemap.tsx +4 -1
- package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +2 -1
- package/src/components/custom/Workout/BaseBadge.stories.tsx +11 -7
- package/src/components/custom/Workout/BaseBadge.tsx +3 -2
- package/src/components/custom/Workout/BodyMap.tsx +5 -3
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +13 -3
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +5 -3
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +2 -1
- package/src/components/custom/Workout/CapacityBandChart.tsx +5 -3
- package/src/components/custom/Workout/DeviationBar.test.tsx +5 -2
- package/src/components/custom/Workout/DeviationBar.tsx +5 -2
- package/src/components/custom/Workout/DualVelocityStrip.stories.tsx +7 -6
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +29 -16
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +8 -8
- package/src/components/custom/Workout/ExerciseCard.test.tsx +39 -19
- package/src/components/custom/Workout/ExerciseCard.tsx +92 -167
- package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +89 -6
- package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +189 -1
- package/src/components/custom/Workout/ExerciseCardHeading.tsx +167 -18
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +3 -2
- package/src/components/custom/Workout/ExerciseHeading.tsx +192 -47
- package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +6 -5
- package/src/components/custom/Workout/InputBar.tsx +4 -2
- package/src/components/custom/Workout/IntensityBar.tsx +4 -1
- package/src/components/custom/Workout/MesoCard.tsx +2 -1
- package/src/components/custom/Workout/MesoStatusCard.tsx +11 -10
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +8 -3
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +12 -2
- package/src/components/custom/Workout/README.md +58 -12
- package/src/components/custom/Workout/ReadinessCheck.tsx +4 -2
- package/src/components/custom/Workout/RestTimer.stories.tsx +13 -2
- package/src/components/custom/Workout/RestTimer.tsx +4 -2
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +16 -12
- package/src/components/custom/Workout/SegmentedBar.tsx +4 -3
- package/src/components/custom/Workout/SetStrip.stories.tsx +3 -2
- package/src/components/custom/Workout/SetsRepsLoad.tsx +23 -9
- package/src/components/custom/Workout/Sparkline.stories.tsx +13 -10
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +2 -1
- package/src/components/custom/Workout/StrengthTrendChart.tsx +8 -6
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +4 -3
- package/src/components/custom/Workout/TempoDisplay.tsx +2 -0
- package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +3 -2
- package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +1 -1
- package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +40 -0
- package/src/components/custom/Workout/VelocityStrip.test.tsx +48 -6
- package/src/components/custom/Workout/VelocityStrip.tsx +23 -14
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +3 -1
- package/src/components/custom/Workout/exerciseRowState.ts +29 -0
- package/src/components/custom/Workout/index.ts +12 -2
- package/src/components/custom/Workout/metricText.stories.tsx +9 -8
- package/src/components/custom/Workout/metricText.tsx +4 -2
- package/src/components/custom/Workout/setHeadingKit.tsx +12 -10
- package/src/components/custom/Workout/velocity-story-kit.tsx +2 -2
- package/src/components/custom/charts/README.md +5 -0
- package/src/components/custom/charts/SetBarChart.test.tsx +16 -0
- package/src/components/custom/charts/SetBarChart.tsx +14 -5
- package/src/components/custom/charts/flatBarGeometry.test.ts +41 -0
- package/src/components/custom/charts/flatBarGeometry.ts +32 -0
- package/src/components/custom/stepper/Stepper.tsx +8 -6
- package/src/components/ui/alert/Alert.stories.tsx +14 -4
- package/src/components/ui/alert/Alert.tsx +24 -3
- package/src/components/ui/button/Button.tsx +12 -12
- package/src/components/ui/checkbox/Checkbox.tsx +2 -2
- package/src/components/ui/data-row/DataRow.stories.tsx +6 -2
- package/src/components/ui/drawer/Drawer.tsx +4 -1
- package/src/components/ui/icon-box/IconBox.tsx +8 -6
- package/src/components/ui/indicator/Indicator.stories.tsx +4 -3
- package/src/components/ui/list-item/ListItem.stories.tsx +7 -4
- package/src/components/ui/modal/Modal.tsx +2 -0
- package/src/components/ui/pill/Pill.stories.tsx +53 -0
- package/src/components/ui/pill/Pill.tsx +10 -6
- package/src/components/ui/popover/Popover.stories.tsx +1 -1
- package/src/components/ui/progress/Progress.tsx +9 -6
- package/src/components/ui/radio/Radio.tsx +1 -1
- package/src/components/ui/section/Section.stories.tsx +10 -10
- package/src/components/ui/select/Select.tsx +2 -1
- package/src/components/ui/spinner/Spinner.tsx +3 -1
- package/src/components/ui/stack/Stack.stories.tsx +7 -7
- package/src/components/ui/surface/Surface.stories.tsx +75 -28
- package/src/components/ui/switch/Switch.tsx +1 -1
- package/src/components/ui/tabs/Tabs.tsx +4 -2
- package/src/components/ui/toast/Toast.tsx +6 -4
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +13 -6
- package/src/stories/PresetShowcase.stories.tsx +1 -1
- package/src/test/no-raw-color.test.ts +39 -0
- package/src/theme/Alpha.stories.tsx +103 -0
- package/src/theme/ProposedTokensVW82.stories.tsx +414 -0
- package/src/theme/config.ts +12 -0
- package/src/theme/global.css +15 -3
- package/src/theme/subtle-tone-weight.test.ts +163 -0
- package/src/theme/tailwind-var-opacity.test.ts +163 -0
- package/src/theme/tokens/primitives.ts +33 -0
- package/src/theme/tokens/semantic-hex-guard.test.ts +322 -0
- package/src/theme/tokens/semantic.ts +56 -20
- package/src/utils/colors.alpha-adoption.test.ts +408 -0
- package/tailwind.config.js +50 -9
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { describe, it, expect } from 'vitest'
|
|
5
|
+
import { alpha } from './colors'
|
|
6
|
+
import { getSemanticColors } from '../theme/tokens/semantic'
|
|
7
|
+
import { primitiveColors } from '../theme/tokens/primitives'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* VW-78: every `rgba(...)` literal swapped for `alpha(<resolved token>, opacity)`
|
|
11
|
+
* across the functional-colour baseline files must resolve to the exact same
|
|
12
|
+
* colour it replaced, AND the raw literal must actually be gone from the
|
|
13
|
+
* touched source file — two separate checks, because they catch different
|
|
14
|
+
* regressions:
|
|
15
|
+
*
|
|
16
|
+
* - "resolves to the same colour" (below) re-derives the swap independently
|
|
17
|
+
* from the token and `alpha()`, so it catches a bad token value or a bug in
|
|
18
|
+
* `alpha()` itself.
|
|
19
|
+
* - "no longer contains the swapped literal" reads the ACTUAL component file
|
|
20
|
+
* and catches someone reverting a call site back to a raw literal, or
|
|
21
|
+
* copy-pasting the wrong token into it — neither of which the first check
|
|
22
|
+
* can see, since it never looks at the file the swap lives in.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
function parseRgba(value: string): [number, number, number, number] {
|
|
26
|
+
const m = value.match(/rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:[,\s]+([\d.]+))?\s*\)/)
|
|
27
|
+
if (!m) throw new Error(`Not an rgb()/rgba() value: ${value}`)
|
|
28
|
+
return [Number(m[1]), Number(m[2]), Number(m[3]), m[4] === undefined ? 1 : Number(m[4])]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `alphaTolerance` accommodates `.toFixed(3)`-rounded originals (MesoCard's
|
|
33
|
+
* dynamic heatmap opacity): the rounding itself can differ from the exact
|
|
34
|
+
* value by up to 0.0005, which is not a colour difference.
|
|
35
|
+
*/
|
|
36
|
+
function expectSameColor(actual: string, original: string, alphaTolerance = 1e-6) {
|
|
37
|
+
const [ar, ag, ab, aa] = parseRgba(actual)
|
|
38
|
+
const [or_, og, ob, oa] = parseRgba(original)
|
|
39
|
+
expect([ar, ag, ab]).toEqual([or_, og, ob])
|
|
40
|
+
expect(Math.abs(aa - oa)).toBeLessThanOrEqual(alphaTolerance)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const dark = getSemanticColors('dark')
|
|
44
|
+
const BRAND_PRIMARY = dark['brand-primary']
|
|
45
|
+
|
|
46
|
+
describe('alpha() adoption (VW-78) — swapped literal parity', () => {
|
|
47
|
+
const staticCases: Array<{ label: string; original: string; swapped: string }> = [
|
|
48
|
+
// Gauge.tsx
|
|
49
|
+
{
|
|
50
|
+
label: 'Gauge TRACK',
|
|
51
|
+
original: 'rgba(255,255,255,0.08)',
|
|
52
|
+
swapped: alpha(primitiveColors.white, 0.08),
|
|
53
|
+
},
|
|
54
|
+
// Scatter.tsx
|
|
55
|
+
{
|
|
56
|
+
label: 'Scatter GRID_LINE',
|
|
57
|
+
original: 'rgba(255,255,255,0.07)',
|
|
58
|
+
swapped: alpha(primitiveColors.white, 0.07),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
label: 'Scatter AXIS_LINE',
|
|
62
|
+
original: 'rgba(255,255,255,0.18)',
|
|
63
|
+
swapped: alpha(primitiveColors.white, 0.18),
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
label: 'Scatter DIAGONAL_LINE',
|
|
67
|
+
original: 'rgba(255,255,255,0.28)',
|
|
68
|
+
swapped: alpha(primitiveColors.white, 0.28),
|
|
69
|
+
},
|
|
70
|
+
// BaseBadge.tsx
|
|
71
|
+
{
|
|
72
|
+
label: 'BaseBadge pr.bg',
|
|
73
|
+
original: 'rgba(255, 121, 0, 0.12)',
|
|
74
|
+
swapped: alpha(dark['brand-primary'], 0.12),
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
label: 'BaseBadge pr.border',
|
|
78
|
+
original: 'rgba(255, 121, 0, 0.3)',
|
|
79
|
+
swapped: alpha(dark['brand-primary'], 0.3),
|
|
80
|
+
},
|
|
81
|
+
// BodyMap.tsx
|
|
82
|
+
{
|
|
83
|
+
label: 'BodyMap OUTLINE_FILL',
|
|
84
|
+
original: 'rgba(255,255,255,0.08)',
|
|
85
|
+
swapped: alpha(primitiveColors.white, 0.08),
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
label: 'BodyMap OUTLINE_BORDER',
|
|
89
|
+
original: 'rgba(255,255,255,0.12)',
|
|
90
|
+
swapped: alpha(primitiveColors.white, 0.12),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
label: 'BodyMap toggle active bg',
|
|
94
|
+
original: 'rgba(255,121,0,0.16)',
|
|
95
|
+
swapped: alpha(BRAND_PRIMARY, 0.16),
|
|
96
|
+
},
|
|
97
|
+
// BodyMapDetailPanel.tsx
|
|
98
|
+
{
|
|
99
|
+
label: 'BodyMapDetailPanel bg',
|
|
100
|
+
original: 'rgba(255,121,0,0.12)',
|
|
101
|
+
swapped: alpha(BRAND_PRIMARY, 0.12),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
label: 'BodyMapDetailPanel border',
|
|
105
|
+
original: 'rgba(255,121,0,0.3)',
|
|
106
|
+
swapped: alpha(BRAND_PRIMARY, 0.3),
|
|
107
|
+
},
|
|
108
|
+
// CapacityBandChart.tsx
|
|
109
|
+
{
|
|
110
|
+
label: 'CapacityBandChart BAND_FILL',
|
|
111
|
+
original: 'rgba(46,213,115,0.1)',
|
|
112
|
+
swapped: alpha(dark['status-success'], 0.1),
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
label: 'CapacityBandChart BAND_EDGE',
|
|
116
|
+
original: 'rgba(46,213,115,0.45)',
|
|
117
|
+
swapped: alpha(dark['status-success'], 0.45),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
label: 'CapacityBandChart PROJECTION_FILL',
|
|
121
|
+
original: 'rgba(46,213,115,0.05)',
|
|
122
|
+
swapped: alpha(dark['status-success'], 0.05),
|
|
123
|
+
},
|
|
124
|
+
// DeviationBar.tsx (grey stop at 50% has no token match and stays raw — not listed here)
|
|
125
|
+
{
|
|
126
|
+
label: 'DeviationBar gradient success stop',
|
|
127
|
+
original: 'rgba(46,213,115,0.25)',
|
|
128
|
+
swapped: alpha(dark['status-success'], 0.25),
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
label: 'DeviationBar gradient warning stop',
|
|
132
|
+
original: 'rgba(249,180,21,0.25)',
|
|
133
|
+
swapped: alpha(dark['status-warning'], 0.25),
|
|
134
|
+
},
|
|
135
|
+
// ExerciseDetailPage.tsx
|
|
136
|
+
{
|
|
137
|
+
label: 'ExerciseDetailPage e1rm bg',
|
|
138
|
+
original: 'rgba(255,121,0,0.10)',
|
|
139
|
+
swapped: alpha(BRAND_PRIMARY, 0.1),
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
label: 'ExerciseDetailPage e1rm border',
|
|
143
|
+
original: 'rgba(255,121,0,0.25)',
|
|
144
|
+
swapped: alpha(BRAND_PRIMARY, 0.25),
|
|
145
|
+
},
|
|
146
|
+
// IntensityBar.tsx
|
|
147
|
+
{
|
|
148
|
+
label: 'IntensityBar TARGET_LINE_COLOR',
|
|
149
|
+
original: 'rgba(33, 150, 243, 0.5)',
|
|
150
|
+
swapped: alpha(dark['status-info'], 0.5),
|
|
151
|
+
},
|
|
152
|
+
// MesoStatusCard.tsx
|
|
153
|
+
{
|
|
154
|
+
label: 'MesoStatusCard gradient success',
|
|
155
|
+
original: 'rgba(46,213,115,0.25)',
|
|
156
|
+
swapped: alpha(dark['status-success'], 0.25),
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
label: 'MesoStatusCard gradient warning',
|
|
160
|
+
original: 'rgba(249,180,21,0.25)',
|
|
161
|
+
swapped: alpha(dark['status-warning'], 0.25),
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
label: 'MesoStatusCard gradient error',
|
|
165
|
+
original: 'rgba(209,67,67,0.25)',
|
|
166
|
+
swapped: alpha(dark['status-error'], 0.25),
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
label: 'MesoStatusCard success.bg',
|
|
170
|
+
original: 'rgba(46,213,115,0.15)',
|
|
171
|
+
swapped: alpha(dark['status-success'], 0.15),
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
label: 'MesoStatusCard success.border',
|
|
175
|
+
original: 'rgba(46,213,115,0.3)',
|
|
176
|
+
swapped: alpha(dark['status-success'], 0.3),
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
label: 'MesoStatusCard warning.bg',
|
|
180
|
+
original: 'rgba(249,180,21,0.15)',
|
|
181
|
+
swapped: alpha(dark['status-warning'], 0.15),
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
label: 'MesoStatusCard warning.border',
|
|
185
|
+
original: 'rgba(249,180,21,0.3)',
|
|
186
|
+
swapped: alpha(dark['status-warning'], 0.3),
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
label: 'MesoStatusCard error.bg',
|
|
190
|
+
original: 'rgba(209,67,67,0.15)',
|
|
191
|
+
swapped: alpha(dark['status-error'], 0.15),
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
label: 'MesoStatusCard error.border',
|
|
195
|
+
original: 'rgba(209,67,67,0.25)',
|
|
196
|
+
swapped: alpha(dark['status-error'], 0.25),
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
label: 'MesoStatusCard gauge center line',
|
|
200
|
+
original: 'rgba(255,255,255,0.3)',
|
|
201
|
+
swapped: alpha(primitiveColors.white, 0.3),
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
label: 'MesoStatusCard coaching bg',
|
|
205
|
+
original: 'rgba(249,180,21,0.06)',
|
|
206
|
+
swapped: alpha(dark['status-warning'], 0.06),
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
label: 'MesoStatusCard coaching border',
|
|
210
|
+
original: 'rgba(249,180,21,0.15)',
|
|
211
|
+
swapped: alpha(dark['status-warning'], 0.15),
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
label: 'MesoStatusCard next-target bg',
|
|
215
|
+
original: 'rgba(46,213,115,0.06)',
|
|
216
|
+
swapped: alpha(dark['status-success'], 0.06),
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
label: 'MesoStatusCard next-target border',
|
|
220
|
+
original: 'rgba(46,213,115,0.2)',
|
|
221
|
+
swapped: alpha(dark['status-success'], 0.2),
|
|
222
|
+
},
|
|
223
|
+
// ReadinessCheck.tsx
|
|
224
|
+
{
|
|
225
|
+
label: 'ReadinessCheck BRAND_PRIMARY_SUBTLE',
|
|
226
|
+
original: 'rgba(255,121,0,0.12)',
|
|
227
|
+
swapped: alpha(BRAND_PRIMARY, 0.12),
|
|
228
|
+
},
|
|
229
|
+
// RestTimer.tsx
|
|
230
|
+
{
|
|
231
|
+
label: 'RestTimer add-time bg',
|
|
232
|
+
original: 'rgba(255,255,255,0.06)',
|
|
233
|
+
swapped: alpha(primitiveColors.white, 0.06),
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
label: 'RestTimer skip bg',
|
|
237
|
+
original: 'rgba(255,121,0,0.12)',
|
|
238
|
+
swapped: alpha(BRAND_PRIMARY, 0.12),
|
|
239
|
+
},
|
|
240
|
+
// StrengthTrendChart.tsx
|
|
241
|
+
{
|
|
242
|
+
label: 'StrengthTrendChart GRID_LINE',
|
|
243
|
+
original: 'rgba(255,255,255,0.06)',
|
|
244
|
+
swapped: alpha(primitiveColors.white, 0.06),
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
label: 'StrengthTrendChart SUCCESS_PILL_BG',
|
|
248
|
+
original: 'rgba(46,213,115,0.10)',
|
|
249
|
+
swapped: alpha(dark['status-success'], 0.1),
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
label: 'StrengthTrendChart SUCCESS_PILL_BORDER',
|
|
253
|
+
original: 'rgba(46,213,115,0.20)',
|
|
254
|
+
swapped: alpha(dark['status-success'], 0.2),
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
label: 'StrengthTrendChart ERROR_PILL_BG',
|
|
258
|
+
original: 'rgba(209,67,67,0.10)',
|
|
259
|
+
swapped: alpha(dark['status-error'], 0.1),
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
label: 'StrengthTrendChart ERROR_PILL_BORDER',
|
|
263
|
+
original: 'rgba(209,67,67,0.20)',
|
|
264
|
+
swapped: alpha(dark['status-error'], 0.2),
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
label: 'StrengthTrendChart tooltip borderLeftColor',
|
|
268
|
+
original: 'rgba(255,255,255,0.10)',
|
|
269
|
+
swapped: alpha(primitiveColors.white, 0.1),
|
|
270
|
+
},
|
|
271
|
+
// ToolbarButton.tsx
|
|
272
|
+
{
|
|
273
|
+
label: 'ToolbarButton disabledBg',
|
|
274
|
+
original: 'rgba(255, 255, 255, 0.12)',
|
|
275
|
+
swapped: alpha(primitiveColors.white, 0.12),
|
|
276
|
+
},
|
|
277
|
+
]
|
|
278
|
+
|
|
279
|
+
it.each(staticCases)(
|
|
280
|
+
'$label resolves to the same colour it replaced',
|
|
281
|
+
({ original, swapped }) => {
|
|
282
|
+
expectSameColor(swapped, original)
|
|
283
|
+
}
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
it('covers all 43 static swaps (the 44th, MesoCard, is dynamic and tested below)', () => {
|
|
287
|
+
expect(staticCases).toHaveLength(43)
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
describe('MesoCard heatmapColor (dynamic opacity)', () => {
|
|
291
|
+
// Mirrors the original `rgba(255,121,0,${opacity.toFixed(3)})` formula this
|
|
292
|
+
// replaced, at representative percentages.
|
|
293
|
+
function originalHeatmapColor(percentage: number): string {
|
|
294
|
+
const clamped = Math.max(0, Math.min(100, percentage))
|
|
295
|
+
const opacity = 0.12 + (clamped / 100) * 0.78
|
|
296
|
+
return `rgba(255,121,0,${opacity.toFixed(3)})`
|
|
297
|
+
}
|
|
298
|
+
function swappedHeatmapColor(percentage: number): string {
|
|
299
|
+
const clamped = Math.max(0, Math.min(100, percentage))
|
|
300
|
+
const opacity = 0.12 + (clamped / 100) * 0.78
|
|
301
|
+
return alpha(BRAND_PRIMARY, opacity)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
it.each([0, 1, 25, 50, 63, 80, 99, 100])('matches at %i%%', (pct) => {
|
|
305
|
+
expectSameColor(swappedHeatmapColor(pct), originalHeatmapColor(pct), 0.0006)
|
|
306
|
+
})
|
|
307
|
+
})
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
const SRC_ROOT = path.dirname(fileURLToPath(import.meta.url))
|
|
311
|
+
|
|
312
|
+
function readSource(relativeToSrc: string): string {
|
|
313
|
+
return readFileSync(path.join(SRC_ROOT, '..', relativeToSrc), 'utf8')
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
describe('touched source files no longer contain the swapped raw literals', () => {
|
|
317
|
+
const fileCases: Array<{ file: string; removedLiterals: string[] }> = [
|
|
318
|
+
{ file: 'components/custom/Gauge/Gauge.tsx', removedLiterals: ['rgba(255,255,255,0.08)'] },
|
|
319
|
+
{
|
|
320
|
+
file: 'components/custom/Scatter/Scatter.tsx',
|
|
321
|
+
removedLiterals: [
|
|
322
|
+
'rgba(255,255,255,0.07)',
|
|
323
|
+
'rgba(255,255,255,0.18)',
|
|
324
|
+
'rgba(255,255,255,0.28)',
|
|
325
|
+
],
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
file: 'components/custom/Workout/BaseBadge.tsx',
|
|
329
|
+
removedLiterals: ['rgba(255, 121, 0, 0.12)', 'rgba(255, 121, 0, 0.3)'],
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
file: 'components/custom/Workout/BodyMap.tsx',
|
|
333
|
+
removedLiterals: ['rgba(255,255,255,0.08)', 'rgba(255,255,255,0.12)', 'rgba(255,121,0,0.16)'],
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
file: 'components/custom/Workout/BodyMapDetailPanel.tsx',
|
|
337
|
+
removedLiterals: ['rgba(255,121,0,0.12)', 'rgba(255,121,0,0.3)'],
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
file: 'components/custom/Workout/CapacityBandChart.tsx',
|
|
341
|
+
removedLiterals: ['rgba(46,213,115,0.1)', 'rgba(46,213,115,0.45)', 'rgba(46,213,115,0.05)'],
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
file: 'components/custom/Workout/DeviationBar.tsx',
|
|
345
|
+
removedLiterals: ['rgba(46,213,115,0.25)', 'rgba(249,180,21,0.25)'],
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
file: 'components/custom/Workout/ExerciseDetailPage.tsx',
|
|
349
|
+
removedLiterals: ['rgba(255,121,0,0.10)', 'rgba(255,121,0,0.25)'],
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
file: 'components/custom/Workout/IntensityBar.tsx',
|
|
353
|
+
removedLiterals: ['rgba(33, 150, 243, 0.5)'],
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
file: 'components/custom/Workout/MesoCard.tsx',
|
|
357
|
+
removedLiterals: ['rgba(255,121,0,${opacity.toFixed(3)})'],
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
file: 'components/custom/Workout/MesoStatusCard.tsx',
|
|
361
|
+
removedLiterals: [
|
|
362
|
+
'rgba(46,213,115,0.25)',
|
|
363
|
+
'rgba(249,180,21,0.25)',
|
|
364
|
+
'rgba(209,67,67,0.25)',
|
|
365
|
+
'rgba(46,213,115,0.15)',
|
|
366
|
+
'rgba(46,213,115,0.3)',
|
|
367
|
+
'rgba(249,180,21,0.15)',
|
|
368
|
+
'rgba(249,180,21,0.3)',
|
|
369
|
+
'rgba(209,67,67,0.15)',
|
|
370
|
+
'rgba(255,255,255,0.3)',
|
|
371
|
+
'rgba(249,180,21,0.06)',
|
|
372
|
+
'rgba(46,213,115,0.06)',
|
|
373
|
+
'rgba(46,213,115,0.2)',
|
|
374
|
+
],
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
file: 'components/custom/Workout/ReadinessCheck.tsx',
|
|
378
|
+
removedLiterals: ['rgba(255,121,0,0.12)'],
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
file: 'components/custom/Workout/RestTimer.tsx',
|
|
382
|
+
removedLiterals: ['rgba(255,255,255,0.06)', 'rgba(255,121,0,0.12)'],
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
file: 'components/custom/Workout/StrengthTrendChart.tsx',
|
|
386
|
+
removedLiterals: [
|
|
387
|
+
'rgba(255,255,255,0.06)',
|
|
388
|
+
'rgba(46,213,115,0.10)',
|
|
389
|
+
'rgba(46,213,115,0.20)',
|
|
390
|
+
'rgba(209,67,67,0.10)',
|
|
391
|
+
'rgba(209,67,67,0.20)',
|
|
392
|
+
'rgba(255,255,255,0.10)',
|
|
393
|
+
],
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
file: 'components/ui/toolbar-button/ToolbarButton.tsx',
|
|
397
|
+
removedLiterals: ['rgba(255, 255, 255, 0.12)'],
|
|
398
|
+
},
|
|
399
|
+
]
|
|
400
|
+
|
|
401
|
+
it.each(fileCases)('$file no longer has its swapped literal(s)', ({ file, removedLiterals }) => {
|
|
402
|
+
const text = readSource(file)
|
|
403
|
+
expect(text).toContain('alpha(')
|
|
404
|
+
for (const literal of removedLiterals) {
|
|
405
|
+
expect(text).not.toContain(literal)
|
|
406
|
+
}
|
|
407
|
+
})
|
|
408
|
+
})
|
package/tailwind.config.js
CHANGED
|
@@ -6,13 +6,23 @@ module.exports = {
|
|
|
6
6
|
theme: {
|
|
7
7
|
extend: {
|
|
8
8
|
colors: {
|
|
9
|
-
//
|
|
9
|
+
// Every leaf here is a `var(--color-*)` reference, which is what makes
|
|
10
|
+
// light/dark switching work — and also why an opacity modifier
|
|
11
|
+
// (`bg-brand-primary/10`) emits NO rule at all under Tailwind v3 and
|
|
12
|
+
// renders as nothing. Reach for a wash rung instead; see VW-308 and
|
|
13
|
+
// `titan/no-var-color-opacity`.
|
|
14
|
+
//
|
|
15
|
+
// The wash ladder (`-subtle`/`-muted`/`-strong`) is the translucency
|
|
16
|
+
// vocabulary. All three rungs are published as classes so a component
|
|
17
|
+
// never has to hand-mix alpha.
|
|
10
18
|
brand: {
|
|
11
19
|
primary: {
|
|
12
20
|
DEFAULT: 'var(--color-brand-primary)',
|
|
13
21
|
light: 'var(--color-brand-primary-light)',
|
|
14
22
|
dark: 'var(--color-brand-primary-dark)',
|
|
15
23
|
subtle: 'var(--color-brand-primary-subtle)',
|
|
24
|
+
muted: 'var(--color-brand-primary-muted)',
|
|
25
|
+
strong: 'var(--color-brand-primary-strong)',
|
|
16
26
|
hover: 'var(--color-brand-primary-hover)',
|
|
17
27
|
active: 'var(--color-brand-primary-active)',
|
|
18
28
|
},
|
|
@@ -21,14 +31,23 @@ module.exports = {
|
|
|
21
31
|
light: 'var(--color-brand-secondary-light)',
|
|
22
32
|
dark: 'var(--color-brand-secondary-dark)',
|
|
23
33
|
subtle: 'var(--color-brand-secondary-subtle)',
|
|
34
|
+
muted: 'var(--color-brand-secondary-muted)',
|
|
35
|
+
strong: 'var(--color-brand-secondary-strong)',
|
|
24
36
|
hover: 'var(--color-brand-secondary-hover)',
|
|
25
37
|
active: 'var(--color-brand-secondary-active)',
|
|
26
38
|
},
|
|
27
39
|
},
|
|
28
|
-
// Text on brand backgrounds
|
|
40
|
+
// Text on brand backgrounds. `-subtle` is text ON the `-subtle` fill, a
|
|
41
|
+
// separate role from the white label a solid fill carries.
|
|
29
42
|
'on-brand': {
|
|
30
|
-
primary:
|
|
31
|
-
|
|
43
|
+
primary: {
|
|
44
|
+
DEFAULT: 'var(--color-on-brand-primary)',
|
|
45
|
+
subtle: 'var(--color-on-brand-primary-subtle)',
|
|
46
|
+
},
|
|
47
|
+
secondary: {
|
|
48
|
+
DEFAULT: 'var(--color-on-brand-secondary)',
|
|
49
|
+
subtle: 'var(--color-on-brand-secondary-subtle)',
|
|
50
|
+
},
|
|
32
51
|
},
|
|
33
52
|
// Status colors
|
|
34
53
|
status: {
|
|
@@ -37,6 +56,8 @@ module.exports = {
|
|
|
37
56
|
light: 'var(--color-status-success-light)',
|
|
38
57
|
dark: 'var(--color-status-success-dark)',
|
|
39
58
|
subtle: 'var(--color-status-success-subtle)',
|
|
59
|
+
muted: 'var(--color-status-success-muted)',
|
|
60
|
+
strong: 'var(--color-status-success-strong)',
|
|
40
61
|
},
|
|
41
62
|
// Live-session accent — own role, decoupled from success
|
|
42
63
|
live: {
|
|
@@ -48,32 +69,52 @@ module.exports = {
|
|
|
48
69
|
light: 'var(--color-status-error-light)',
|
|
49
70
|
dark: 'var(--color-status-error-dark)',
|
|
50
71
|
subtle: 'var(--color-status-error-subtle)',
|
|
72
|
+
muted: 'var(--color-status-error-muted)',
|
|
73
|
+
strong: 'var(--color-status-error-strong)',
|
|
51
74
|
},
|
|
52
75
|
'error-vivid': {
|
|
53
76
|
DEFAULT: 'var(--color-status-error-vivid)',
|
|
54
77
|
light: 'var(--color-status-error-vivid-light)',
|
|
55
78
|
dark: 'var(--color-status-error-vivid-dark)',
|
|
56
79
|
subtle: 'var(--color-status-error-vivid-subtle)',
|
|
80
|
+
muted: 'var(--color-status-error-vivid-muted)',
|
|
81
|
+
strong: 'var(--color-status-error-vivid-strong)',
|
|
57
82
|
},
|
|
58
83
|
warning: {
|
|
59
84
|
DEFAULT: 'var(--color-status-warning)',
|
|
60
85
|
light: 'var(--color-status-warning-light)',
|
|
61
86
|
dark: 'var(--color-status-warning-dark)',
|
|
62
87
|
subtle: 'var(--color-status-warning-subtle)',
|
|
88
|
+
muted: 'var(--color-status-warning-muted)',
|
|
89
|
+
strong: 'var(--color-status-warning-strong)',
|
|
63
90
|
},
|
|
64
91
|
info: {
|
|
65
92
|
DEFAULT: 'var(--color-status-info)',
|
|
66
93
|
light: 'var(--color-status-info-light)',
|
|
67
94
|
dark: 'var(--color-status-info-dark)',
|
|
68
95
|
subtle: 'var(--color-status-info-subtle)',
|
|
96
|
+
muted: 'var(--color-status-info-muted)',
|
|
97
|
+
strong: 'var(--color-status-info-strong)',
|
|
69
98
|
},
|
|
70
99
|
},
|
|
71
|
-
// Text on status backgrounds
|
|
100
|
+
// Text on status backgrounds. `-subtle` is text ON the `-subtle` fill.
|
|
72
101
|
'on-status': {
|
|
73
|
-
success:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
102
|
+
success: {
|
|
103
|
+
DEFAULT: 'var(--color-on-status-success)',
|
|
104
|
+
subtle: 'var(--color-on-status-success-subtle)',
|
|
105
|
+
},
|
|
106
|
+
error: {
|
|
107
|
+
DEFAULT: 'var(--color-on-status-error)',
|
|
108
|
+
subtle: 'var(--color-on-status-error-subtle)',
|
|
109
|
+
},
|
|
110
|
+
warning: {
|
|
111
|
+
DEFAULT: 'var(--color-on-status-warning)',
|
|
112
|
+
subtle: 'var(--color-on-status-warning-subtle)',
|
|
113
|
+
},
|
|
114
|
+
info: {
|
|
115
|
+
DEFAULT: 'var(--color-on-status-info)',
|
|
116
|
+
subtle: 'var(--color-on-status-info-subtle)',
|
|
117
|
+
},
|
|
77
118
|
},
|
|
78
119
|
// Result/outcome colors
|
|
79
120
|
result: {
|