@titan-design/react-ui 0.4.0 → 0.5.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/dist/MesoStatusCard-CQk71hSi.d.mts +77 -0
- package/dist/MesoStatusCard-CQk71hSi.d.ts +77 -0
- package/dist/bodymap.d.mts +246 -3
- package/dist/bodymap.d.ts +246 -3
- package/dist/bodymap.mjs +1911 -6
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +31 -738
- package/dist/index.d.ts +31 -738
- package/dist/index.js +894 -1692
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10287 -8203
- package/dist/index.mjs.map +1 -1
- package/dist/pages-D7Jlssvp.d.ts +791 -0
- package/dist/pages-MO0JCySL.d.mts +791 -0
- package/dist/pages.d.mts +4 -0
- package/dist/pages.d.ts +4 -0
- package/dist/pages.js +6646 -0
- package/dist/pages.js.map +1 -0
- package/dist/pages.mjs +6626 -0
- package/dist/pages.mjs.map +1 -0
- package/dist/theme/index.d.mts +12 -12
- package/dist/theme/index.d.ts +12 -12
- package/dist/theme/index.js +30 -30
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +1880 -2
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.mjs +573 -1
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +9 -7
- package/src/components/custom/Workout/BaseBadge.stories.tsx +3 -3
- package/src/components/custom/Workout/BaseBadge.test.tsx +1 -1
- package/src/components/custom/Workout/BaseBadge.tsx +1 -1
- package/src/components/custom/Workout/ExerciseCard.tsx +5 -1
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +17 -4
- package/src/components/custom/Workout/IntensityBar.test.tsx +112 -60
- package/src/components/custom/Workout/IntensityBar.tsx +109 -58
- package/src/components/custom/Workout/PlaceholderStrip.tsx +5 -9
- package/src/components/custom/Workout/PrBadge.tsx +3 -3
- package/src/components/custom/Workout/PrHistoryModal.tsx +2 -2
- package/src/components/custom/Workout/README.md +50 -0
- package/src/components/custom/Workout/RestTimer.test.tsx +41 -10
- package/src/components/custom/Workout/RestTimer.tsx +48 -43
- package/src/components/custom/Workout/SetRow.tsx +10 -1
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +69 -2
- package/src/components/custom/Workout/TempoDisplay.test.tsx +35 -0
- package/src/components/custom/Workout/TempoDisplay.tsx +104 -1
- package/src/components/custom/Workout/VelocityStrip.test.tsx +89 -2
- package/src/components/custom/Workout/VelocityStrip.tsx +203 -38
- package/src/components/custom/Workout/WeekRow.test.tsx +2 -2
- package/src/components/custom/Workout/WeightBadge.test.tsx +2 -2
- package/src/components/custom/Workout/WeightBadge.tsx +2 -2
- package/src/components/custom/Workout/icons.tsx +68 -0
- package/src/components/custom/Workout/index.ts +38 -47
- package/src/index.ts +3 -2
- package/src/pages.ts +61 -0
- package/src/test/stories-smoke.test.tsx +50 -0
- package/src/theme/barrel.ts +53 -0
- package/src/theme/index.ts +8 -43
- package/src/theme/native-theming-guard.test.ts +117 -0
- package/dist/bodymap-BhG55xHM.d.mts +0 -318
- package/dist/bodymap-BhG55xHM.d.ts +0 -318
- package/dist/chunk-2SISKTWM.mjs +0 -1027
- package/dist/chunk-2SISKTWM.mjs.map +0 -1
- package/dist/chunk-7XPB4NAO.mjs +0 -1013
- package/dist/chunk-7XPB4NAO.mjs.map +0 -1
- package/dist/chunk-SNVKL5WZ.mjs +0 -883
- package/dist/chunk-SNVKL5WZ.mjs.map +0 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render } from '@testing-library/react'
|
|
3
|
+
import { composeStories } from '@storybook/react'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Storybook → render-test bridge (TD-04.11 / VW-20).
|
|
7
|
+
*
|
|
8
|
+
* The design system has a complete `*.stories.tsx` set but no test consumed it.
|
|
9
|
+
* This uses Storybook's portable-stories `composeStories` to mount EVERY story
|
|
10
|
+
* (with its args/decorators applied) through the same react-native-web + jsdom
|
|
11
|
+
* pipeline the unit tests use, asserting each renders without throwing and emits
|
|
12
|
+
* output. It's broad, near-free coverage that catches "this organism/variant
|
|
13
|
+
* crashes on render" regressions across the whole component set — the long tail
|
|
14
|
+
* the hand-written per-organism tests don't enumerate.
|
|
15
|
+
*
|
|
16
|
+
* Project annotations (the preview's theme decorator) are intentionally NOT
|
|
17
|
+
* applied: a smoke test only needs to prove each story renders, and skipping
|
|
18
|
+
* them avoids importing global.css / addon-themes into the test env.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
type StoryModule = Record<string, any>
|
|
23
|
+
|
|
24
|
+
const storyModules = import.meta.glob<StoryModule>('../components/**/*.stories.tsx', {
|
|
25
|
+
eager: true,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
describe('storybook stories render (composeStories smoke)', () => {
|
|
29
|
+
const entries = Object.entries(storyModules)
|
|
30
|
+
|
|
31
|
+
it('discovers a non-trivial number of story modules', () => {
|
|
32
|
+
expect(entries.length).toBeGreaterThan(10)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
for (const [file, mod] of entries) {
|
|
36
|
+
const label = file.replace('../components/', '')
|
|
37
|
+
const composed = composeStories(mod)
|
|
38
|
+
for (const [name, Story] of Object.entries(composed)) {
|
|
39
|
+
it(`${label} › ${name} renders`, () => {
|
|
40
|
+
// Smoke level: every story must MOUNT without throwing. `firstChild` is
|
|
41
|
+
// not asserted — portal/modal organisms (PrHistoryModal) render into
|
|
42
|
+
// document.body, and empty-state stories legitimately render null; the
|
|
43
|
+
// hand-written per-organism tests own the detailed output assertions.
|
|
44
|
+
const result = render(<Story />)
|
|
45
|
+
expect(result.unmount).toBeTypeOf('function')
|
|
46
|
+
result.unmount()
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Theme surface WITHOUT the ThemeProvider value (dep hygiene, titan 0.5.0).
|
|
2
|
+
//
|
|
3
|
+
// `ThemeProvider` imports `nativewind` (`vars()`). Re-exporting it from the root
|
|
4
|
+
// barrel dragged the whole nativewind import chain into every consumer of
|
|
5
|
+
// `@titan-design/react-ui` — the web SPA paid for a native-only provider. This
|
|
6
|
+
// file is the nativewind-free theme surface: the root barrel re-exports THIS,
|
|
7
|
+
// and the `/theme` subpath (`./index.ts`) layers the `ThemeProvider` value on
|
|
8
|
+
// top for the native consumers that deliberately import it.
|
|
9
|
+
export * from './tokens'
|
|
10
|
+
export * from './config'
|
|
11
|
+
export * from './shadows'
|
|
12
|
+
export * from './elevation'
|
|
13
|
+
export * from './manifest'
|
|
14
|
+
export { resolveColor } from './resolve-color'
|
|
15
|
+
|
|
16
|
+
// ThemeProvider TYPES may stay on the root (erased at build — no nativewind
|
|
17
|
+
// pull). Only the ThemeProvider VALUE moves to the `/theme` subpath.
|
|
18
|
+
export type { ThemeProviderProps, ThemeProviderMode } from './ThemeProvider'
|
|
19
|
+
|
|
20
|
+
// Re-export commonly used items
|
|
21
|
+
export { getSemanticColors, type ThemeMode } from './tokens/semantic'
|
|
22
|
+
export { getThemeCSSVars, gluestackConfig } from './config'
|
|
23
|
+
export {
|
|
24
|
+
neumorphicShadows,
|
|
25
|
+
// Color math utilities (HSV-based)
|
|
26
|
+
lighten,
|
|
27
|
+
darken,
|
|
28
|
+
getHoverColors,
|
|
29
|
+
isDark,
|
|
30
|
+
hexToRgb,
|
|
31
|
+
rgbToHsv,
|
|
32
|
+
// Shadow creation functions
|
|
33
|
+
createRaisedShadow,
|
|
34
|
+
createRaisedHoverShadow,
|
|
35
|
+
createPressedShadow,
|
|
36
|
+
createPressedHoverShadow,
|
|
37
|
+
createFlatShadow,
|
|
38
|
+
shadowColors,
|
|
39
|
+
type ShadowIntensity,
|
|
40
|
+
type ShadowSurface,
|
|
41
|
+
} from './shadows'
|
|
42
|
+
export {
|
|
43
|
+
getElevationConfig,
|
|
44
|
+
getElevationSurface,
|
|
45
|
+
getElevationShadow,
|
|
46
|
+
getBaseSurfaceColor,
|
|
47
|
+
getValidatedElevation,
|
|
48
|
+
componentElevationRanges,
|
|
49
|
+
type ElevationLevel,
|
|
50
|
+
type ElevationConfig,
|
|
51
|
+
type ComponentType,
|
|
52
|
+
} from './elevation'
|
|
53
|
+
export * from './presets'
|
package/src/theme/index.ts
CHANGED
|
@@ -1,43 +1,8 @@
|
|
|
1
|
-
// Theme
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export
|
|
8
|
-
export {
|
|
9
|
-
|
|
10
|
-
// Re-export commonly used items
|
|
11
|
-
export { getSemanticColors, type ThemeMode } from './tokens/semantic'
|
|
12
|
-
export { getThemeCSSVars, gluestackConfig } from './config'
|
|
13
|
-
export {
|
|
14
|
-
neumorphicShadows,
|
|
15
|
-
// Color math utilities (HSV-based)
|
|
16
|
-
lighten,
|
|
17
|
-
darken,
|
|
18
|
-
getHoverColors,
|
|
19
|
-
isDark,
|
|
20
|
-
hexToRgb,
|
|
21
|
-
rgbToHsv,
|
|
22
|
-
// Shadow creation functions
|
|
23
|
-
createRaisedShadow,
|
|
24
|
-
createRaisedHoverShadow,
|
|
25
|
-
createPressedShadow,
|
|
26
|
-
createPressedHoverShadow,
|
|
27
|
-
createFlatShadow,
|
|
28
|
-
shadowColors,
|
|
29
|
-
type ShadowIntensity,
|
|
30
|
-
type ShadowSurface,
|
|
31
|
-
} from './shadows'
|
|
32
|
-
export {
|
|
33
|
-
getElevationConfig,
|
|
34
|
-
getElevationSurface,
|
|
35
|
-
getElevationShadow,
|
|
36
|
-
getBaseSurfaceColor,
|
|
37
|
-
getValidatedElevation,
|
|
38
|
-
componentElevationRanges,
|
|
39
|
-
type ElevationLevel,
|
|
40
|
-
type ElevationConfig,
|
|
41
|
-
type ComponentType,
|
|
42
|
-
} from './elevation'
|
|
43
|
-
export * from './presets'
|
|
1
|
+
// Theme subpath entry: @titan-design/react-ui/theme
|
|
2
|
+
//
|
|
3
|
+
// The full theme surface. This is the nativewind-free `./barrel` PLUS the
|
|
4
|
+
// `ThemeProvider` value — which imports `nativewind` and therefore does NOT
|
|
5
|
+
// belong on the root barrel (that split happened in titan 0.5.0). Native
|
|
6
|
+
// consumers wrap their app root in `<ThemeProvider>` and import it from HERE.
|
|
7
|
+
export * from './barrel'
|
|
8
|
+
export { ThemeProvider } from './ThemeProvider'
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { createRequire } from 'node:module'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { darkThemeCSSVars, lightThemeCSSVars } from './config'
|
|
6
|
+
import { getSemanticColors } from './tokens/semantic'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Native-theming guard (TD-04.11 / VW-20).
|
|
10
|
+
*
|
|
11
|
+
* On native, titan resolves colors two ways, and BOTH can silently render black
|
|
12
|
+
* if a token is undefined:
|
|
13
|
+
* 1. `className` (e.g. `text-text-secondary`) → tailwind config → a
|
|
14
|
+
* `var(--color-*)` custom property that `ThemeProvider` registers at runtime
|
|
15
|
+
* via nativewind `vars(darkThemeCSSVars)`. A tailwind color pointing at a var
|
|
16
|
+
* the theme maps don't define is unset on native → black text/fill.
|
|
17
|
+
* 2. `resolveColor(token)` → `getSemanticColors(mode)[token]` hex on native /
|
|
18
|
+
* `var(--color-${token})` on web. A token missing a hex value, or whose
|
|
19
|
+
* `--color-<token>` var is undefined, renders black (native) or unset (web).
|
|
20
|
+
*
|
|
21
|
+
* This is the exact class of bug that shipped in the 09.33a swap and was only
|
|
22
|
+
* caught on-device. `config.completeness.test.ts` proves the theme maps mirror
|
|
23
|
+
* global.css (the definition side); this guard covers the USAGE side — every
|
|
24
|
+
* color token the design system references must resolve to a defined token in
|
|
25
|
+
* both modes. Static (no render), so it runs in the fast project.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const require = createRequire(import.meta.url)
|
|
29
|
+
const uiRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
30
|
+
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
const tailwindConfig = require(path.join(uiRoot, 'tailwind.config.js')) as any
|
|
33
|
+
const colorTree = tailwindConfig.theme?.extend?.colors ?? {}
|
|
34
|
+
|
|
35
|
+
// Flatten the nested tailwind color tree into `class-suffix -> leaf value`.
|
|
36
|
+
// `DEFAULT` collapses onto its parent (tailwind's `brand.primary.DEFAULT` -> the
|
|
37
|
+
// `brand-primary` class).
|
|
38
|
+
function flattenColors(
|
|
39
|
+
node: Record<string, unknown>,
|
|
40
|
+
prefix: string[] = [],
|
|
41
|
+
): Record<string, string> {
|
|
42
|
+
const out: Record<string, string> = {}
|
|
43
|
+
for (const [key, value] of Object.entries(node)) {
|
|
44
|
+
const nextPrefix = key === 'DEFAULT' ? prefix : [...prefix, key]
|
|
45
|
+
if (value && typeof value === 'object') {
|
|
46
|
+
Object.assign(out, flattenColors(value as Record<string, unknown>, nextPrefix))
|
|
47
|
+
} else if (typeof value === 'string') {
|
|
48
|
+
out[nextPrefix.join('-')] = value
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return out
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const colorLeaves = flattenColors(colorTree)
|
|
55
|
+
const definedDark = new Set(Object.keys(darkThemeCSSVars))
|
|
56
|
+
const definedLight = new Set(Object.keys(lightThemeCSSVars))
|
|
57
|
+
|
|
58
|
+
function varReference(value: string): string | null {
|
|
59
|
+
const match = value.match(/var\((--color-[a-z0-9-]+)\)/)
|
|
60
|
+
return match ? match[1] : null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Accept hex, rgb/rgba, and hsl/hsla; reject empty/undefined. (No token should be
|
|
64
|
+
// literally "#000"/"black" in these maps, but that's a design choice, not a
|
|
65
|
+
// resolution bug, so we only assert a *valid, present* color here.)
|
|
66
|
+
const VALID_COLOR = /^(#[0-9a-f]{3,8}|rgba?\([^)]+\)|hsla?\([^)]+\))$/i
|
|
67
|
+
|
|
68
|
+
describe('native theming guard (no color token renders black on native)', () => {
|
|
69
|
+
it('sanity: tailwind colors and theme maps flattened to non-trivial sets', () => {
|
|
70
|
+
expect(Object.keys(colorLeaves).length).toBeGreaterThan(50)
|
|
71
|
+
expect(definedDark.size).toBeGreaterThan(90)
|
|
72
|
+
expect(definedLight.size).toBe(definedDark.size)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('every tailwind color className resolves to a var defined in BOTH native vars maps', () => {
|
|
76
|
+
const undefinedDark: string[] = []
|
|
77
|
+
const undefinedLight: string[] = []
|
|
78
|
+
for (const [suffix, value] of Object.entries(colorLeaves)) {
|
|
79
|
+
const ref = varReference(value)
|
|
80
|
+
if (!ref) continue // literal color (nativewind preset default) — always resolves
|
|
81
|
+
if (!definedDark.has(ref)) undefinedDark.push(`${suffix} -> ${value}`)
|
|
82
|
+
if (!definedLight.has(ref)) undefinedLight.push(`${suffix} -> ${value}`)
|
|
83
|
+
}
|
|
84
|
+
expect(
|
|
85
|
+
undefinedDark,
|
|
86
|
+
'tailwind colors point at vars missing from darkThemeCSSVars (className renders black on native dark)',
|
|
87
|
+
).toEqual([])
|
|
88
|
+
expect(
|
|
89
|
+
undefinedLight,
|
|
90
|
+
'tailwind colors point at vars missing from lightThemeCSSVars (className renders black on native light)',
|
|
91
|
+
).toEqual([])
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('every resolveColor/getSemanticColors token has a valid hex AND a matching --color-<token> var', () => {
|
|
95
|
+
for (const mode of ['dark', 'light'] as const) {
|
|
96
|
+
const colors = getSemanticColors(mode)
|
|
97
|
+
const defined = mode === 'dark' ? definedDark : definedLight
|
|
98
|
+
const invalidValues: string[] = []
|
|
99
|
+
const missingVars: string[] = []
|
|
100
|
+
for (const [token, value] of Object.entries(colors)) {
|
|
101
|
+
if (typeof value !== 'string' || !VALID_COLOR.test(value)) {
|
|
102
|
+
invalidValues.push(`${token} = ${JSON.stringify(value)}`)
|
|
103
|
+
}
|
|
104
|
+
// resolveColor's web path returns `var(--color-${token})`; that var must exist.
|
|
105
|
+
if (!defined.has(`--color-${token}`)) missingVars.push(token)
|
|
106
|
+
}
|
|
107
|
+
expect(
|
|
108
|
+
invalidValues,
|
|
109
|
+
`getSemanticColors('${mode}') has tokens with no valid color (renders black/unset on native)`,
|
|
110
|
+
).toEqual([])
|
|
111
|
+
expect(
|
|
112
|
+
missingVars,
|
|
113
|
+
`resolveColor tokens missing a --color-<token> in ${mode} vars (renders unset on web)`,
|
|
114
|
+
).toEqual([])
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
})
|
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ViewProps } from 'react-native';
|
|
3
|
-
|
|
4
|
-
type MesoStatusBadgeVariant = 'success' | 'warning' | 'error';
|
|
5
|
-
interface MesoStatusBadge {
|
|
6
|
-
/** Short status label, e.g. "On Track". */
|
|
7
|
-
label: string;
|
|
8
|
-
/** Color treatment for the badge. */
|
|
9
|
-
variant: MesoStatusBadgeVariant;
|
|
10
|
-
}
|
|
11
|
-
interface MesoStatusMetric {
|
|
12
|
-
/** Cell label, e.g. "Prescribed". */
|
|
13
|
-
label: string;
|
|
14
|
-
/** Cell value, e.g. "3×8 @ RPE 8". */
|
|
15
|
-
value: string;
|
|
16
|
-
}
|
|
17
|
-
interface MesoStatusGauge {
|
|
18
|
-
/** Gauge label, e.g. "Intensity". */
|
|
19
|
-
label: string;
|
|
20
|
-
/** Fill position 0-1; the 14px marker sits at this level. */
|
|
21
|
-
level: number;
|
|
22
|
-
/** Optional right-aligned sublabel, e.g. "Prescribed". */
|
|
23
|
-
sublabel?: string;
|
|
24
|
-
}
|
|
25
|
-
interface MesoStatusCoaching {
|
|
26
|
-
/** Coaching recommendation copy. */
|
|
27
|
-
text: string;
|
|
28
|
-
/** Substrings within `text` to render in bold. */
|
|
29
|
-
highlights?: string[];
|
|
30
|
-
}
|
|
31
|
-
interface MesoStatusNextTarget {
|
|
32
|
-
/** Leading glyph/emoji, e.g. "→" or "🎯". */
|
|
33
|
-
icon: string;
|
|
34
|
-
/** Next-session target copy. */
|
|
35
|
-
text: string;
|
|
36
|
-
}
|
|
37
|
-
interface MesoStatusCardProps extends ViewProps {
|
|
38
|
-
/** Mesocycle name, e.g. "Hypertrophy Block". */
|
|
39
|
-
mesoName: string;
|
|
40
|
-
/** Context line, e.g. "Week 6 of 8 · Upper/Lower". */
|
|
41
|
-
mesoSubtitle: string;
|
|
42
|
-
/** Status pill in the header. */
|
|
43
|
-
statusBadge: MesoStatusBadge;
|
|
44
|
-
/** Prescription-vs-actual metrics rendered as a 2x2 grid. */
|
|
45
|
-
metrics: MesoStatusMetric[];
|
|
46
|
-
/** Intensity / volume gauges stacked vertically. */
|
|
47
|
-
gauges: MesoStatusGauge[];
|
|
48
|
-
/** Optional coaching recommendation box. */
|
|
49
|
-
coaching?: MesoStatusCoaching;
|
|
50
|
-
/** Optional next-session target box. */
|
|
51
|
-
nextTarget?: MesoStatusNextTarget;
|
|
52
|
-
className?: string;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Mesocycle context card for a specific exercise: prescription vs actual
|
|
56
|
-
* metrics, intensity/volume gauges, and coaching guidance. Composes the titan
|
|
57
|
-
* `Card` plus the Workout `StatusDot`, and renders the §25 gauge (8px track,
|
|
58
|
-
* 50% center line, 14px marker) inline since that visual differs from the
|
|
59
|
-
* IntensityBar/DeviationBar primitives.
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* <MesoStatusCard
|
|
63
|
-
* mesoName="Hypertrophy Block"
|
|
64
|
-
* mesoSubtitle="Week 6 of 8 · Upper/Lower"
|
|
65
|
-
* statusBadge={{ label: 'On Track', variant: 'success' }}
|
|
66
|
-
* metrics={[
|
|
67
|
-
* { label: 'Prescribed', value: '3×8 @ RPE 8' },
|
|
68
|
-
* { label: 'Actual', value: '3×8 @ 185 lbs' },
|
|
69
|
-
* ]}
|
|
70
|
-
* gauges={[{ label: 'Intensity', level: 0.7, sublabel: 'Actual' }]}
|
|
71
|
-
* coaching={{ text: 'Add load next session.', highlights: ['Add load'] }}
|
|
72
|
-
* nextTarget={{ icon: '→', text: '190 lbs × 8' }}
|
|
73
|
-
* />
|
|
74
|
-
*/
|
|
75
|
-
declare function MesoStatusCard({ mesoName, mesoSubtitle, statusBadge, metrics, gauges, coaching, nextTarget, className, ...props }: MesoStatusCardProps): react_jsx_runtime.JSX.Element;
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Full muscle group taxonomy for volume tracking and exercise mapping.
|
|
79
|
-
* 15 groups — the primary unit for volume accounting.
|
|
80
|
-
*/
|
|
81
|
-
declare enum MuscleGroup {
|
|
82
|
-
CHEST = "chest",
|
|
83
|
-
FRONT_DELTS = "front_delts",
|
|
84
|
-
SIDE_DELTS = "side_delts",
|
|
85
|
-
TRICEPS = "triceps",
|
|
86
|
-
LATS = "lats",
|
|
87
|
-
UPPER_BACK = "upper_back",
|
|
88
|
-
REAR_DELTS = "rear_delts",
|
|
89
|
-
BICEPS = "biceps",
|
|
90
|
-
FOREARMS = "forearms",
|
|
91
|
-
QUADS = "quads",
|
|
92
|
-
HAMSTRINGS = "hamstrings",
|
|
93
|
-
GLUTES = "glutes",
|
|
94
|
-
CALVES = "calves",
|
|
95
|
-
ABS = "abs",
|
|
96
|
-
OBLIQUES = "obliques"
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Simplified muscle group view for beginners. Maps to one or more MuscleGroup.
|
|
100
|
-
*/
|
|
101
|
-
declare enum SimpleMuscleGroup {
|
|
102
|
-
CHEST = "chest",
|
|
103
|
-
BACK = "back",
|
|
104
|
-
SHOULDERS = "shoulders",
|
|
105
|
-
BICEPS = "biceps",
|
|
106
|
-
TRICEPS = "triceps",
|
|
107
|
-
LEGS = "legs",
|
|
108
|
-
CORE = "core",
|
|
109
|
-
FOREARMS = "forearms"
|
|
110
|
-
}
|
|
111
|
-
/** Mapping from simplified groups to the full taxonomy. */
|
|
112
|
-
declare const SIMPLE_TO_DETAILED: Record<SimpleMuscleGroup, MuscleGroup[]>;
|
|
113
|
-
/** Reverse map: detailed MuscleGroup -> its SimpleMuscleGroup. */
|
|
114
|
-
declare const DETAILED_TO_SIMPLE: Record<MuscleGroup, SimpleMuscleGroup>;
|
|
115
|
-
/** Movement category for training split organization. */
|
|
116
|
-
type MovementCategory = 'push' | 'pull' | 'legs' | 'core';
|
|
117
|
-
declare const MUSCLE_TO_CATEGORY: Record<MuscleGroup, MovementCategory>;
|
|
118
|
-
/**
|
|
119
|
-
* react-native-body-highlighter slug(s) per muscle group. Phase 1 uses the
|
|
120
|
-
* closest available slug; deltoids share one path and lats/upper-back share
|
|
121
|
-
* `upper-back` (see doc Level 4 "Recommended approach").
|
|
122
|
-
*/
|
|
123
|
-
declare const MUSCLE_TO_SVG_SLUGS: Record<MuscleGroup, string[]>;
|
|
124
|
-
/** Human-readable name per muscle group (used in a11y labels). */
|
|
125
|
-
declare const MUSCLE_DISPLAY_NAMES: Record<MuscleGroup, string>;
|
|
126
|
-
/** Human-readable name per simple group. */
|
|
127
|
-
declare const SIMPLE_DISPLAY_NAMES: Record<SimpleMuscleGroup, string>;
|
|
128
|
-
/** Volume landmarks per muscle group (weekly working sets). */
|
|
129
|
-
interface VolumeLandmarks {
|
|
130
|
-
mev: number;
|
|
131
|
-
mav: number;
|
|
132
|
-
mrv: number;
|
|
133
|
-
}
|
|
134
|
-
declare const DEFAULT_VOLUME_LANDMARKS: Record<MuscleGroup, VolumeLandmarks>;
|
|
135
|
-
/** Weekly-volume status relative to MEV/MAV/MRV landmarks. */
|
|
136
|
-
type VolumeStatus = 'under' | 'maintenance' | 'productive' | 'over';
|
|
137
|
-
/** Readable label per status for a11y descriptions. */
|
|
138
|
-
declare const VOLUME_STATUS_LABELS: Record<VolumeStatus, string>;
|
|
139
|
-
/**
|
|
140
|
-
* Maps a volume status (and intensity, 0-1) to a heatmap fill color. The
|
|
141
|
-
* `approaching` token is used when a productive muscle nears its MRV.
|
|
142
|
-
*/
|
|
143
|
-
declare function getHeatmapColor(status: VolumeStatus | null | undefined, intensity?: number): string;
|
|
144
|
-
|
|
145
|
-
interface BodyMapData {
|
|
146
|
-
muscleGroup: MuscleGroup;
|
|
147
|
-
/** Intensity 0-1 for heatmap color. */
|
|
148
|
-
intensity: number;
|
|
149
|
-
/** Volume status for tooltip / heatmap token. */
|
|
150
|
-
volumeStatus: VolumeStatus;
|
|
151
|
-
/** Weekly effective sets. */
|
|
152
|
-
weeklySets: number;
|
|
153
|
-
}
|
|
154
|
-
interface BodyMapProps extends ViewProps {
|
|
155
|
-
/** Muscle group intensity data. */
|
|
156
|
-
data: BodyMapData[];
|
|
157
|
-
/** Which view to show. */
|
|
158
|
-
view: 'front' | 'back';
|
|
159
|
-
/** Toggle between front/back. */
|
|
160
|
-
onViewChange?: (view: 'front' | 'back') => void;
|
|
161
|
-
/** Called when a muscle group is tapped. */
|
|
162
|
-
onMusclePress?: (muscleGroup: MuscleGroup) => void;
|
|
163
|
-
/** Currently highlighted muscle (from external selection). */
|
|
164
|
-
highlightedMuscle?: MuscleGroup | null;
|
|
165
|
-
/** Show simplified (8 groups) or detailed (15 groups). */
|
|
166
|
-
mode?: 'simple' | 'detailed';
|
|
167
|
-
className?: string;
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Interactive SVG body map with tappable muscle groups and a volume heatmap.
|
|
171
|
-
* Renders the front or back view (toggle via onViewChange), colors each muscle
|
|
172
|
-
* by its weekly-volume status, and exposes an accessible muscle legend wired to
|
|
173
|
-
* onMusclePress. Phase 1 uses react-native-body-highlighter's combined slugs
|
|
174
|
-
* (deltoids as one path, lats/upper-back shared); swipe between views and the
|
|
175
|
-
* delt/lat SVG split are follow-ups.
|
|
176
|
-
*
|
|
177
|
-
* @example
|
|
178
|
-
* <BodyMap
|
|
179
|
-
* data={[{ muscleGroup: MuscleGroup.CHEST, intensity: 0.7, volumeStatus: 'productive', weeklySets: 12 }]}
|
|
180
|
-
* view="front"
|
|
181
|
-
* onViewChange={setView}
|
|
182
|
-
* onMusclePress={setSelected}
|
|
183
|
-
* highlightedMuscle={selected}
|
|
184
|
-
* />
|
|
185
|
-
*/
|
|
186
|
-
declare function BodyMap({ data, view, onViewChange, onMusclePress, highlightedMuscle, mode, className, ...props }: BodyMapProps): react_jsx_runtime.JSX.Element;
|
|
187
|
-
|
|
188
|
-
interface ContributingExercise {
|
|
189
|
-
/** Exercise name. */
|
|
190
|
-
name: string;
|
|
191
|
-
/** Working sets contributed this week. */
|
|
192
|
-
sets: number;
|
|
193
|
-
/** Effective-set multiplier toward this muscle (0-1). */
|
|
194
|
-
contributionWeight: number;
|
|
195
|
-
}
|
|
196
|
-
interface UpcomingExercise {
|
|
197
|
-
/** Exercise name. */
|
|
198
|
-
name: string;
|
|
199
|
-
/** Workout the exercise belongs to. */
|
|
200
|
-
workoutName: string;
|
|
201
|
-
/** Planned sets. */
|
|
202
|
-
sets: number;
|
|
203
|
-
}
|
|
204
|
-
interface BodyMapDetailPanelProps extends ViewProps {
|
|
205
|
-
/** Muscle group whose volume detail is shown. */
|
|
206
|
-
muscleGroup: MuscleGroup;
|
|
207
|
-
/** Human-readable muscle name for the title and a11y label. */
|
|
208
|
-
displayName: string;
|
|
209
|
-
/** Weekly effective sets logged for this muscle. */
|
|
210
|
-
weeklySets: number;
|
|
211
|
-
/** Volume landmarks (weekly working sets). */
|
|
212
|
-
landmarks: VolumeLandmarks;
|
|
213
|
-
/** Weekly-volume status relative to the landmarks. */
|
|
214
|
-
volumeStatus: VolumeStatus;
|
|
215
|
-
/** Last trained label, e.g. "2 days ago". */
|
|
216
|
-
lastTrained?: string;
|
|
217
|
-
/** Volume sparkline data (last 4-8 weeks). */
|
|
218
|
-
weeklyHistory?: number[];
|
|
219
|
-
/** Exercises contributing to this muscle's volume. */
|
|
220
|
-
contributingExercises?: ContributingExercise[];
|
|
221
|
-
/** Upcoming exercises targeting this muscle. */
|
|
222
|
-
upcomingExercises?: UpcomingExercise[];
|
|
223
|
-
/** Whether the bottom sheet is visible. */
|
|
224
|
-
isOpen?: boolean;
|
|
225
|
-
/** Alias for `isOpen` (spec wording: "isOpen/visible"). `isOpen` wins when both are set. */
|
|
226
|
-
visible?: boolean;
|
|
227
|
-
/** Dismiss the panel (backdrop, handle, or close button). */
|
|
228
|
-
onClose: () => void;
|
|
229
|
-
/** Navigate to a filtered exercise list. */
|
|
230
|
-
onViewExercises?: () => void;
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Slide-up bottom-sheet panel with detailed weekly-volume info for a tapped
|
|
234
|
-
* muscle group: a MEV|current|MRV gradient progress bar, the big weekly set
|
|
235
|
-
* count against MRV, an optional volume sparkline, and the contributing /
|
|
236
|
-
* upcoming exercise lists. Composes the titan `Badge` and Workout `Sparkline`.
|
|
237
|
-
*
|
|
238
|
-
* Controlled via `isOpen` (or the `visible` alias); renders nothing when closed
|
|
239
|
-
* and animates in on open — sheet translateY 400->0 (400ms ease-out) and a
|
|
240
|
-
* backdrop fade 0->0.4 (RN Animated, useNativeDriver:false). Backdrop, handle,
|
|
241
|
-
* and the header close button all call `onClose`.
|
|
242
|
-
*
|
|
243
|
-
* @example
|
|
244
|
-
* <BodyMapDetailPanel
|
|
245
|
-
* muscleGroup={MuscleGroup.CHEST}
|
|
246
|
-
* displayName="Chest"
|
|
247
|
-
* weeklySets={14}
|
|
248
|
-
* landmarks={{ mev: 8, mav: 14, mrv: 20 }}
|
|
249
|
-
* volumeStatus="productive"
|
|
250
|
-
* lastTrained="2 days ago"
|
|
251
|
-
* weeklyHistory={[8, 10, 12, 14]}
|
|
252
|
-
* isOpen={open}
|
|
253
|
-
* onClose={() => setOpen(false)}
|
|
254
|
-
* />
|
|
255
|
-
*/
|
|
256
|
-
declare function BodyMapDetailPanel({ muscleGroup, displayName, weeklySets, landmarks, volumeStatus, lastTrained, weeklyHistory, contributingExercises, upcomingExercises, isOpen, visible, onClose, onViewExercises, ...props }: BodyMapDetailPanelProps): react_jsx_runtime.JSX.Element | null;
|
|
257
|
-
|
|
258
|
-
/** One muscle's full volume picture — feeds the body maps and the detail panel. */
|
|
259
|
-
interface TrainingStatusMuscle {
|
|
260
|
-
/** Detailed muscle group (drives the SVG heatmap + panel identity). */
|
|
261
|
-
muscleGroup: MuscleGroup;
|
|
262
|
-
/** Human-readable name for the legend, summary, and panel title. */
|
|
263
|
-
displayName: string;
|
|
264
|
-
/** Which body-map view the muscle belongs to. */
|
|
265
|
-
side: 'front' | 'back';
|
|
266
|
-
/** Heatmap intensity 0-1. */
|
|
267
|
-
intensity: number;
|
|
268
|
-
/** Weekly-volume status relative to the landmarks. */
|
|
269
|
-
volumeStatus: VolumeStatus;
|
|
270
|
-
/** Weekly effective sets logged. */
|
|
271
|
-
weeklySets: number;
|
|
272
|
-
/** Volume landmarks (weekly working sets). */
|
|
273
|
-
landmarks: VolumeLandmarks;
|
|
274
|
-
/** Last trained label, e.g. "2 days ago". */
|
|
275
|
-
lastTrained?: string;
|
|
276
|
-
/** Weekly volume history for the panel sparkline. */
|
|
277
|
-
weeklyHistory?: number[];
|
|
278
|
-
/** Exercises contributing to this muscle's volume. */
|
|
279
|
-
contributingExercises?: ContributingExercise[];
|
|
280
|
-
/** Upcoming exercises targeting this muscle. */
|
|
281
|
-
upcomingExercises?: UpcomingExercise[];
|
|
282
|
-
}
|
|
283
|
-
/** Derived, page-level roll-up of the tracked muscles. */
|
|
284
|
-
interface TrainingStatusSummary {
|
|
285
|
-
/** Total weekly effective sets across all tracked muscles. */
|
|
286
|
-
totalWeeklySets: number;
|
|
287
|
-
/** Number of muscles being tracked. */
|
|
288
|
-
trackedMuscles: number;
|
|
289
|
-
/** Count of muscles per volume status. */
|
|
290
|
-
statusCounts: Record<VolumeStatus, number>;
|
|
291
|
-
}
|
|
292
|
-
interface TrainingStatusPageProps extends ViewProps {
|
|
293
|
-
/** Page heading. */
|
|
294
|
-
title?: string;
|
|
295
|
-
/** Mesocycle context banner shown at the top. */
|
|
296
|
-
meso: MesoStatusCardProps;
|
|
297
|
-
/** Muscles tracked this week. */
|
|
298
|
-
muscles: TrainingStatusMuscle[];
|
|
299
|
-
className?: string;
|
|
300
|
-
}
|
|
301
|
-
/** Roll up per-muscle rows into the page summary (pure, testable). */
|
|
302
|
-
declare function deriveTrainingSummary(muscles: TrainingStatusMuscle[]): TrainingStatusSummary;
|
|
303
|
-
/** Project the muscle rows for one side into `BodyMap` heatmap data. */
|
|
304
|
-
declare function toBodyMapData(muscles: TrainingStatusMuscle[], side: 'front' | 'back'): BodyMapData[];
|
|
305
|
-
/**
|
|
306
|
-
* TrainingStatusPage — a body-map training dashboard that composes the
|
|
307
|
-
* mesocycle context banner (`MesoStatusCard`), side-by-side front/back
|
|
308
|
-
* `BodyMap`s with a shared heatmap legend, per-status summary cards, and a
|
|
309
|
-
* tap-to-detail `BodyMapDetailPanel`. Tapping any muscle (SVG or legend chip)
|
|
310
|
-
* opens the slide-up panel for that group; the summary is derived from the
|
|
311
|
-
* supplied muscle rows.
|
|
312
|
-
*
|
|
313
|
-
* @example
|
|
314
|
-
* <TrainingStatusPage meso={meso} muscles={muscles} />
|
|
315
|
-
*/
|
|
316
|
-
declare function TrainingStatusPage({ title, meso, muscles, className, ...props }: TrainingStatusPageProps): react_jsx_runtime.JSX.Element;
|
|
317
|
-
|
|
318
|
-
export { type BodyMapData as B, type ContributingExercise as C, DEFAULT_VOLUME_LANDMARKS as D, type MesoStatusCardProps as M, SimpleMuscleGroup as S, type TrainingStatusMuscle as T, type UpcomingExercise as U, type VolumeLandmarks as V, type BodyMapDetailPanelProps as a, type BodyMapProps as b, type MesoStatusBadge as c, type MesoStatusBadgeVariant as d, MesoStatusCard as e, type MesoStatusCoaching as f, type MesoStatusGauge as g, type MesoStatusMetric as h, type MesoStatusNextTarget as i, type MovementCategory as j, MuscleGroup as k, type TrainingStatusPageProps as l, type TrainingStatusSummary as m, BodyMap as n, BodyMapDetailPanel as o, DETAILED_TO_SIMPLE as p, MUSCLE_DISPLAY_NAMES as q, MUSCLE_TO_CATEGORY as r, MUSCLE_TO_SVG_SLUGS as s, SIMPLE_DISPLAY_NAMES as t, SIMPLE_TO_DETAILED as u, TrainingStatusPage as v, VOLUME_STATUS_LABELS as w, deriveTrainingSummary as x, getHeatmapColor as y, toBodyMapData as z };
|