@titan-design/react-ui 0.10.0 → 0.11.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 (84) hide show
  1. package/dist/index.d.mts +399 -4
  2. package/dist/index.d.ts +399 -4
  3. package/dist/index.js +2295 -895
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +2279 -905
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/{pages-DyEx-lBf.d.ts → pages-B1hqnGIC.d.ts} +149 -15
  8. package/dist/{pages-D7hOD4Vj.d.mts → pages-CvsFqNtx.d.mts} +149 -15
  9. package/dist/pages.d.mts +1 -1
  10. package/dist/pages.d.ts +1 -1
  11. package/dist/pages.js +1974 -1740
  12. package/dist/pages.js.map +1 -1
  13. package/dist/pages.mjs +1975 -1741
  14. package/dist/pages.mjs.map +1 -1
  15. package/package.json +8 -3
  16. package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +258 -0
  17. package/src/components/custom/Fatigue/DualGhostSpark.test.tsx +138 -0
  18. package/src/components/custom/Fatigue/DualGhostSpark.tsx +208 -0
  19. package/src/components/custom/Fatigue/FatigueLights.stories.tsx +74 -0
  20. package/src/components/custom/Fatigue/FatigueLights.test.tsx +33 -0
  21. package/src/components/custom/Fatigue/FatigueLights.tsx +83 -0
  22. package/src/components/custom/Fatigue/GhostBand.test.tsx +235 -0
  23. package/src/components/custom/Fatigue/GhostBand.tsx +246 -0
  24. package/src/components/custom/Fatigue/GhostBloom.tsx +132 -0
  25. package/src/components/custom/Fatigue/GhostSpark.stories.tsx +175 -0
  26. package/src/components/custom/Fatigue/GhostSpark.test.tsx +45 -0
  27. package/src/components/custom/Fatigue/GhostSpark.tsx +126 -0
  28. package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +126 -0
  29. package/src/components/custom/Fatigue/LiveFatigueCard.test.tsx +28 -0
  30. package/src/components/custom/Fatigue/LiveFatigueCard.tsx +86 -0
  31. package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +95 -0
  32. package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +151 -0
  33. package/src/components/custom/Fatigue/README.md +93 -0
  34. package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +94 -0
  35. package/src/components/custom/Fatigue/RomProgressionChart.test.tsx +31 -0
  36. package/src/components/custom/Fatigue/RomProgressionChart.tsx +132 -0
  37. package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +226 -0
  38. package/src/components/custom/Fatigue/VelocityHero.stories.tsx +70 -0
  39. package/src/components/custom/Fatigue/VelocityHero.test.tsx +26 -0
  40. package/src/components/custom/Fatigue/VelocityHero.tsx +46 -0
  41. package/src/components/custom/Fatigue/VerdictHero.stories.tsx +59 -0
  42. package/src/components/custom/Fatigue/VerdictHero.test.tsx +49 -0
  43. package/src/components/custom/Fatigue/VerdictHero.tsx +79 -0
  44. package/src/components/custom/Fatigue/fatigue-mock.test.ts +59 -0
  45. package/src/components/custom/Fatigue/fatigue-mock.ts +268 -0
  46. package/src/components/custom/Fatigue/fatigue-model.ts +129 -0
  47. package/src/components/custom/Fatigue/fatigue-tokens.test.ts +70 -0
  48. package/src/components/custom/Fatigue/fatigue-tokens.ts +180 -0
  49. package/src/components/custom/Fatigue/index.ts +41 -0
  50. package/src/components/custom/Fatigue/tempo-pacing.test.ts +176 -0
  51. package/src/components/custom/Fatigue/tempo-pacing.ts +112 -0
  52. package/src/components/custom/Workout/DualVelocityStrip.test.tsx +685 -0
  53. package/src/components/custom/Workout/ExerciseCard.test.tsx +4 -4
  54. package/src/components/custom/Workout/ExerciseCard.tsx +3 -1
  55. package/src/components/custom/Workout/ExerciseDetailPage.tsx +7 -1
  56. package/src/components/custom/Workout/README.md +62 -16
  57. package/src/components/custom/Workout/SegmentedBar.stories.tsx +3 -3
  58. package/src/components/custom/Workout/SessionRail.test.tsx +43 -0
  59. package/src/components/custom/Workout/SessionRail.tsx +50 -13
  60. package/src/components/custom/Workout/SetRow.test.tsx +6 -6
  61. package/src/components/custom/Workout/SetRow.tsx +9 -2
  62. package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +191 -0
  63. package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +186 -0
  64. package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +296 -0
  65. package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +259 -0
  66. package/src/components/custom/Workout/VelocityStrip.stories.tsx +217 -250
  67. package/src/components/custom/Workout/VelocityStrip.test.tsx +196 -79
  68. package/src/components/custom/Workout/VelocityStrip.tsx +953 -609
  69. package/src/components/custom/Workout/index.ts +3 -0
  70. package/src/components/custom/Workout/velocity-story-kit.tsx +438 -0
  71. package/src/components/custom/charts/SetBarChart.test.tsx +275 -0
  72. package/src/components/custom/charts/SetBarChart.tsx +651 -0
  73. package/src/components/custom/charts/live-rep-growth.ts +91 -0
  74. package/src/components/custom/index.ts +1 -0
  75. package/src/test/raw-color-patterns.test.ts +71 -0
  76. package/src/theme/ColorPalettes.stories.tsx +871 -0
  77. package/src/theme/ColorPrimitives.stories.tsx +300 -0
  78. package/src/theme/bar-paper.ts +59 -0
  79. package/src/theme/color-stories.coverage.test.ts +130 -0
  80. package/src/theme/color-story-kit.tsx +170 -0
  81. package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +0 -254
  82. package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +0 -145
  83. package/src/theme/ColorSystem.stories.tsx +0 -508
  84. package/src/theme/Colors.stories.tsx +0 -323
@@ -0,0 +1,91 @@
1
+ // The newest-rep grow-from-bottom entrance, shared by SetBarChart (hero / dual / ROM)
2
+ // and VelocityStrip's framed + bare expanded charts. Promoted out of VelocityStrip so
3
+ // every value-height bar family animates the live rep identically instead of re-rolling
4
+ // the hook.
5
+ import { useEffect, useState } from 'react'
6
+ import { Animated, Easing } from 'react-native'
7
+
8
+ const ANIMATION_EASING = Easing.bezier(0.22, 1, 0.36, 1)
9
+
10
+ /**
11
+ * Growth-factor overshoot for a new-peak bar: it grows past its full height then
12
+ * settles back to 1 (a small bounce), rather than the plain 0→1 grow every other
13
+ * bar gets. Modest (12%) since it stretches actual bar HEIGHT, not a uniform scale
14
+ * — a large overshoot here reads as a much bigger jump than the same factor did
15
+ * against the old whole-bar scale pop.
16
+ */
17
+ const PEAK_OVERSHOOT = 1.12
18
+
19
+ function getReducedMotionPreference(): boolean {
20
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false
21
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches
22
+ }
23
+
24
+ /** Track the OS "reduce motion" preference; falls back to `false` (jsdom/SSR). */
25
+ export function usePrefersReducedMotion(): boolean {
26
+ const [reduced, setReduced] = useState(getReducedMotionPreference)
27
+ useEffect(() => {
28
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return
29
+ const mq = window.matchMedia('(prefers-reduced-motion: reduce)')
30
+ const handler = () => setReduced(mq.matches)
31
+ handler()
32
+ mq.addEventListener?.('change', handler)
33
+ return () => mq.removeEventListener?.('change', handler)
34
+ }, [])
35
+ return reduced
36
+ }
37
+
38
+ /**
39
+ * The newest-rep entrance shared by SetBarChart and the framed / bare `expanded` charts:
40
+ * the bar GROWS UP FROM THE BASELINE (0 → full height), reading as though it's tracking
41
+ * the rep as it lands. A new set peak overshoots past full height then settles (a small
42
+ * bounce) instead of the plain grow. Honors reduced motion (jumps straight to full
43
+ * height, no animation). Returns a 0→1(+overshoot) growth-factor `Animated.Value`;
44
+ * callers interpolate it against their own target height. Inert while `active` is
45
+ * false or no live rep is present.
46
+ */
47
+ export function useLiveRepGrowth(
48
+ active: boolean,
49
+ liveRepIndex: number | undefined,
50
+ liveVelocity: number | undefined,
51
+ isNewPeak: boolean
52
+ ): Animated.Value {
53
+ const prefersReducedMotion = usePrefersReducedMotion()
54
+ const [liveGrowth] = useState(() => new Animated.Value(1))
55
+ useEffect(() => {
56
+ if (!active || liveRepIndex == null || liveVelocity == null) return
57
+ if (prefersReducedMotion) {
58
+ liveGrowth.setValue(1)
59
+ return
60
+ }
61
+ liveGrowth.setValue(0)
62
+ if (isNewPeak) {
63
+ Animated.sequence([
64
+ Animated.timing(liveGrowth, {
65
+ toValue: PEAK_OVERSHOOT,
66
+ duration: 220,
67
+ easing: Easing.out(Easing.quad),
68
+ useNativeDriver: false,
69
+ }),
70
+ Animated.spring(liveGrowth, {
71
+ toValue: 1,
72
+ friction: 5,
73
+ tension: 140,
74
+ useNativeDriver: false,
75
+ }),
76
+ ]).start()
77
+ } else {
78
+ // Plain entrance: a clean ease-out grow with no overshoot (ANIMATION_EASING has
79
+ // no control point above y=1) — it reads as tracking the rep, not bouncing.
80
+ Animated.timing(liveGrowth, {
81
+ toValue: 1,
82
+ duration: 300,
83
+ easing: ANIMATION_EASING,
84
+ useNativeDriver: false,
85
+ }).start()
86
+ }
87
+ }, [active, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveGrowth])
88
+ return liveGrowth
89
+ }
90
+
91
+ export { ANIMATION_EASING }
@@ -9,6 +9,7 @@ export * from './TimerReadout'
9
9
  export * from './CircularTimer'
10
10
  export * from './Metric'
11
11
  export * from './Workout'
12
+ export * from './Fatigue'
12
13
  export * from './Treemap'
13
14
  export * from './Scatter'
14
15
  export * from './Gauge'
@@ -0,0 +1,71 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { createRequire } from 'node:module'
3
+
4
+ const { extractRawColors } = createRequire(import.meta.url)(
5
+ '../../eslint-rules/raw-color-patterns.js'
6
+ ) as { extractRawColors: (text: string) => Array<{ value: string; id: string }> }
7
+
8
+ const ids = (text: string) => extractRawColors(text).map((c) => c.id)
9
+ const values = (text: string) => extractRawColors(text).map((c) => c.value)
10
+
11
+ /**
12
+ * Detection for the `no-raw-color` ratchet. The rule and its baseline generator
13
+ * share this module, so a gap here silently widens the allowance rather than
14
+ * failing loudly — worth pinning the notations directly.
15
+ */
16
+ describe('extractRawColors', () => {
17
+ it('detects every hex length', () => {
18
+ expect(ids('#fff')).toEqual(['hex'])
19
+ expect(ids('#ffff')).toEqual(['hex'])
20
+ expect(ids('#1C1916')).toEqual(['hex'])
21
+ expect(ids('#1C1916FF')).toEqual(['hex'])
22
+ })
23
+
24
+ it('detects functional notations, including ones with no occurrences today', () => {
25
+ for (const text of [
26
+ 'rgb(1,2,3)',
27
+ 'rgba(1,2,3,0.5)',
28
+ 'hsl(1 2% 3%)',
29
+ 'hsla(1,2%,3%,0.5)',
30
+ 'oklch(0.5 0.2 30)',
31
+ 'lab(50% 40 59)',
32
+ 'color-mix(in oklch, red, blue)',
33
+ ]) {
34
+ expect(ids(text), text).toContain('functional')
35
+ }
36
+ })
37
+
38
+ it('detects Tailwind colour utilities but not layout classes', () => {
39
+ expect(ids('bg-red-500')).toEqual(['twPalette'])
40
+ expect(ids('text-slate-200')).toEqual(['twPalette'])
41
+ expect(ids('bg-white')).toEqual(['twAchromatic'])
42
+ expect(ids('bg-[#1C1C1C]')).toEqual(['twArbitrary'])
43
+ expect(ids('flex flex-row gap-2 p-4 rounded-lg')).toEqual([])
44
+ // Semantic utilities are the desired output, not violations.
45
+ expect(ids('bg-surface-raised text-text-primary')).toEqual([])
46
+ })
47
+
48
+ it('treats a bare keyword as a colour only when it is the whole value', () => {
49
+ expect(ids('white')).toEqual(['named'])
50
+ expect(ids(' black ')).toEqual(['named'])
51
+ // Prose and identifiers must not match, or every comment becomes a violation.
52
+ expect(ids('white text on a black plane')).toEqual([])
53
+ expect(ids('whiteboard')).toEqual([])
54
+ })
55
+
56
+ it('does not flag absence or inheritance', () => {
57
+ // `transparent` encodes absence and `currentColor` defers to the cascade;
58
+ // neither has a token equivalent.
59
+ expect(ids('transparent')).toEqual([])
60
+ expect(ids('currentColor')).toEqual([])
61
+ })
62
+
63
+ it('normalises case so #FFF and #fff are the same debt', () => {
64
+ expect(values('#ABCDEF')).toEqual(['#abcdef'])
65
+ })
66
+
67
+ it('extracts every colour in a multi-colour literal', () => {
68
+ // The baseline funds each one, so both have to come back.
69
+ expect(values('linear-gradient(#111111, #222222)')).toEqual(['#111111', '#222222'])
70
+ })
71
+ })