react-muscle-stats 1.0.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 (48) hide show
  1. package/README.md +73 -0
  2. package/dist/components/MuscleRadar/index.cjs.js +84 -0
  3. package/dist/components/MuscleRadar/index.cjs.js.map +1 -0
  4. package/dist/components/MuscleRadar/index.d.ts +43 -0
  5. package/dist/components/MuscleRadar/index.d.ts.map +1 -0
  6. package/dist/components/MuscleRadar/index.es.js +83 -0
  7. package/dist/components/MuscleRadar/index.es.js.map +1 -0
  8. package/dist/components/MuscleRadar/normalization.cjs.js +100 -0
  9. package/dist/components/MuscleRadar/normalization.cjs.js.map +1 -0
  10. package/dist/components/MuscleRadar/normalization.d.ts +21 -0
  11. package/dist/components/MuscleRadar/normalization.d.ts.map +1 -0
  12. package/dist/components/MuscleRadar/normalization.es.js +99 -0
  13. package/dist/components/MuscleRadar/normalization.es.js.map +1 -0
  14. package/dist/components/MuscleVisualizer/index.cjs.js +49 -0
  15. package/dist/components/MuscleVisualizer/index.cjs.js.map +1 -0
  16. package/dist/components/MuscleVisualizer/index.d.ts +32 -0
  17. package/dist/components/MuscleVisualizer/index.d.ts.map +1 -0
  18. package/dist/components/MuscleVisualizer/index.es.js +48 -0
  19. package/dist/components/MuscleVisualizer/index.es.js.map +1 -0
  20. package/dist/components/MuscleVisualizer/svgs.cjs.js +2274 -0
  21. package/dist/components/MuscleVisualizer/svgs.cjs.js.map +1 -0
  22. package/dist/components/MuscleVisualizer/svgs.d.ts +2 -0
  23. package/dist/components/MuscleVisualizer/svgs.d.ts.map +1 -0
  24. package/dist/components/MuscleVisualizer/svgs.es.js +2274 -0
  25. package/dist/components/MuscleVisualizer/svgs.es.js.map +1 -0
  26. package/dist/components/OneRepMaxChart/index.cjs.js +132 -0
  27. package/dist/components/OneRepMaxChart/index.cjs.js.map +1 -0
  28. package/dist/components/OneRepMaxChart/index.d.ts +30 -0
  29. package/dist/components/OneRepMaxChart/index.d.ts.map +1 -0
  30. package/dist/components/OneRepMaxChart/index.es.js +127 -0
  31. package/dist/components/OneRepMaxChart/index.es.js.map +1 -0
  32. package/dist/components/VolumeChart/index.cjs.js +155 -0
  33. package/dist/components/VolumeChart/index.cjs.js.map +1 -0
  34. package/dist/components/VolumeChart/index.d.ts +30 -0
  35. package/dist/components/VolumeChart/index.d.ts.map +1 -0
  36. package/dist/components/VolumeChart/index.es.js +150 -0
  37. package/dist/components/VolumeChart/index.es.js.map +1 -0
  38. package/dist/index.cjs.js +16 -0
  39. package/dist/index.d.ts +13 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.es.js +8 -0
  42. package/dist/utils/math.cjs.js +16 -0
  43. package/dist/utils/math.cjs.js.map +1 -0
  44. package/dist/utils/math.d.ts +11 -0
  45. package/dist/utils/math.d.ts.map +1 -0
  46. package/dist/utils/math.es.js +16 -0
  47. package/dist/utils/math.es.js.map +1 -0
  48. package/package.json +87 -0
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # React + TypeScript + Vite
2
+
3
+ This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+ Currently, two official plugins are available:
6
+
7
+ - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
8
+ - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9
+
10
+ ## React Compiler
11
+
12
+ The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13
+
14
+ ## Expanding the ESLint configuration
15
+
16
+ If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
17
+
18
+ ```js
19
+ export default defineConfig([
20
+ globalIgnores(['dist']),
21
+ {
22
+ files: ['**/*.{ts,tsx}'],
23
+ extends: [
24
+ // Other configs...
25
+
26
+ // Remove tseslint.configs.recommended and replace with this
27
+ tseslint.configs.recommendedTypeChecked,
28
+ // Alternatively, use this for stricter rules
29
+ tseslint.configs.strictTypeChecked,
30
+ // Optionally, add this for stylistic rules
31
+ tseslint.configs.stylisticTypeChecked,
32
+
33
+ // Other configs...
34
+ ],
35
+ languageOptions: {
36
+ parserOptions: {
37
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
38
+ tsconfigRootDir: import.meta.dirname,
39
+ },
40
+ // other options...
41
+ },
42
+ },
43
+ ])
44
+ ```
45
+
46
+ You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
47
+
48
+ ```js
49
+ // eslint.config.js
50
+ import reactX from 'eslint-plugin-react-x'
51
+ import reactDom from 'eslint-plugin-react-dom'
52
+
53
+ export default defineConfig([
54
+ globalIgnores(['dist']),
55
+ {
56
+ files: ['**/*.{ts,tsx}'],
57
+ extends: [
58
+ // Other configs...
59
+ // Enable lint rules for React
60
+ reactX.configs['recommended-typescript'],
61
+ // Enable lint rules for React DOM
62
+ reactDom.configs.recommended,
63
+ ],
64
+ languageOptions: {
65
+ parserOptions: {
66
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
67
+ tsconfigRootDir: import.meta.dirname,
68
+ },
69
+ // other options...
70
+ },
71
+ },
72
+ ])
73
+ ```
@@ -0,0 +1,84 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_normalization = require("./normalization.cjs.js");
3
+ let react = require("react");
4
+ let react_jsx_runtime = require("react/jsx-runtime");
5
+ let recharts = require("recharts");
6
+ /**
7
+ * A Recharts-powered radar / spider chart that normalizes raw body
8
+ * measurements into development scores (0–100) and plots them on a
9
+ * radial grid. Optionally overlays a second data set for comparison.
10
+ */
11
+ function MuscleRadar({ gender, measurements, compareMeasurements, color = "#8884d8", compareColor = "#82ca9d", label = "Current", compareLabel = "Comparison", width = "100%", height = 400, style, className, showTooltip = true, showLegend = true, fillOpacity = .35 }) {
12
+ const data = (0, react.useMemo)(() => {
13
+ const standards = require_normalization.MUSCLE_STANDARDS[gender];
14
+ if (!standards) return [];
15
+ return Object.keys(standards).map((muscle) => {
16
+ const { min, max } = standards[muscle];
17
+ const rawValue = measurements[muscle] ?? min;
18
+ const point = {
19
+ muscle,
20
+ score: require_normalization.calculateDevelopmentScore(rawValue, min, max),
21
+ rawValue
22
+ };
23
+ if (compareMeasurements) {
24
+ const compareRaw = compareMeasurements[muscle] ?? min;
25
+ point.compareScore = require_normalization.calculateDevelopmentScore(compareRaw, min, max);
26
+ point.compareRawValue = compareRaw;
27
+ }
28
+ return point;
29
+ });
30
+ }, [
31
+ gender,
32
+ measurements,
33
+ compareMeasurements
34
+ ]);
35
+ if (data.length === 0) return null;
36
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
37
+ className,
38
+ style: {
39
+ width,
40
+ ...style
41
+ },
42
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.ResponsiveContainer, {
43
+ width: "100%",
44
+ height,
45
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(recharts.RadarChart, {
46
+ cx: "50%",
47
+ cy: "50%",
48
+ outerRadius: "80%",
49
+ data,
50
+ children: [
51
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.PolarGrid, {}),
52
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.PolarAngleAxis, {
53
+ dataKey: "muscle",
54
+ tick: { fontSize: 12 }
55
+ }),
56
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.PolarRadiusAxis, {
57
+ angle: 90,
58
+ domain: [0, 100],
59
+ tick: { fontSize: 10 }
60
+ }),
61
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.Radar, {
62
+ name: label,
63
+ dataKey: "score",
64
+ stroke: color,
65
+ fill: color,
66
+ fillOpacity
67
+ }),
68
+ compareMeasurements && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.Radar, {
69
+ name: compareLabel,
70
+ dataKey: "compareScore",
71
+ stroke: compareColor,
72
+ fill: compareColor,
73
+ fillOpacity
74
+ }),
75
+ showTooltip && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.Tooltip, {}),
76
+ showLegend && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.Legend, {})
77
+ ]
78
+ })
79
+ })
80
+ });
81
+ }
82
+ exports.MuscleRadar = MuscleRadar;
83
+
84
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","names":[],"sources":["../../../src/components/MuscleRadar/index.tsx"],"sourcesContent":["import { useMemo, type CSSProperties } from 'react';\r\nimport {\r\n Radar,\r\n RadarChart,\r\n PolarGrid,\r\n PolarAngleAxis,\r\n PolarRadiusAxis,\r\n ResponsiveContainer,\r\n Tooltip,\r\n Legend,\r\n} from 'recharts';\r\nimport {\r\n MUSCLE_STANDARDS,\r\n calculateDevelopmentScore,\r\n type Gender,\r\n} from './normalization';\r\n\r\nexport interface BodyMeasurements {\r\n /** Measurement values in inches, keyed by muscle/body-part name. */\r\n [muscle: string]: number;\r\n}\r\n\r\nexport interface MuscleRadarProps {\r\n /** Gender – determines which min/max standards are used. */\r\n gender: Gender;\r\n /** Raw body measurements in inches. Keys should match `MUSCLE_STANDARDS` keys. */\r\n measurements: BodyMeasurements;\r\n /** Optional second data set to overlay (e.g. goals or previous measurements). */\r\n compareMeasurements?: BodyMeasurements;\r\n /** Fill colour for the primary radar area (default: `'#8884d8'`). */\r\n color?: string;\r\n /** Fill colour for the comparison radar area (default: `'#82ca9d'`). */\r\n compareColor?: string;\r\n /** Label for the primary data set shown in the legend (default: `'Current'`). */\r\n label?: string;\r\n /** Label for the comparison data set (default: `'Comparison'`). */\r\n compareLabel?: string;\r\n /** Width of the chart container (default: `'100%'`). */\r\n width?: string | number;\r\n /** Height of the chart container (default: `400`). */\r\n height?: number;\r\n /** Optional inline styles applied to the outer wrapper. */\r\n style?: CSSProperties;\r\n /** Optional CSS class name for the outer wrapper. */\r\n className?: string;\r\n /** Whether to show the tooltip on hover (default: `true`). */\r\n showTooltip?: boolean;\r\n /** Whether to show the legend (default: `true`). */\r\n showLegend?: boolean;\r\n /** Fill opacity for radar areas (default: `0.35`). */\r\n fillOpacity?: number;\r\n}\r\n\r\ninterface RadarDataPoint {\r\n muscle: string;\r\n score: number;\r\n rawValue: number;\r\n compareScore?: number;\r\n compareRawValue?: number;\r\n}\r\n\r\n/**\r\n * A Recharts-powered radar / spider chart that normalizes raw body\r\n * measurements into development scores (0–100) and plots them on a\r\n * radial grid. Optionally overlays a second data set for comparison.\r\n */\r\nexport function MuscleRadar({\r\n gender,\r\n measurements,\r\n compareMeasurements,\r\n color = '#8884d8',\r\n compareColor = '#82ca9d',\r\n label = 'Current',\r\n compareLabel = 'Comparison',\r\n width = '100%',\r\n height = 400,\r\n style,\r\n className,\r\n showTooltip = true,\r\n showLegend = true,\r\n fillOpacity = 0.35,\r\n}: MuscleRadarProps) {\r\n const data = useMemo<RadarDataPoint[]>(() => {\r\n const standards = MUSCLE_STANDARDS[gender];\r\n if (!standards) return [];\r\n\r\n return Object.keys(standards).map((muscle) => {\r\n const { min, max } = standards[muscle];\r\n const rawValue = measurements[muscle] ?? min;\r\n const score = calculateDevelopmentScore(rawValue, min, max);\r\n\r\n const point: RadarDataPoint = { muscle, score, rawValue };\r\n\r\n if (compareMeasurements) {\r\n const compareRaw = compareMeasurements[muscle] ?? min;\r\n point.compareScore = calculateDevelopmentScore(compareRaw, min, max);\r\n point.compareRawValue = compareRaw;\r\n }\r\n\r\n return point;\r\n });\r\n }, [gender, measurements, compareMeasurements]);\r\n\r\n if (data.length === 0) return null;\r\n\r\n return (\r\n <div className={className} style={{ width, ...style }}>\r\n <ResponsiveContainer width=\"100%\" height={height}>\r\n <RadarChart cx=\"50%\" cy=\"50%\" outerRadius=\"80%\" data={data}>\r\n <PolarGrid />\r\n <PolarAngleAxis\r\n dataKey=\"muscle\"\r\n tick={{ fontSize: 12 }}\r\n />\r\n <PolarRadiusAxis\r\n angle={90}\r\n domain={[0, 100]}\r\n tick={{ fontSize: 10 }}\r\n />\r\n\r\n <Radar\r\n name={label}\r\n dataKey=\"score\"\r\n stroke={color}\r\n fill={color}\r\n fillOpacity={fillOpacity}\r\n />\r\n\r\n {compareMeasurements && (\r\n <Radar\r\n name={compareLabel}\r\n dataKey=\"compareScore\"\r\n stroke={compareColor}\r\n fill={compareColor}\r\n fillOpacity={fillOpacity}\r\n />\r\n )}\r\n\r\n {showTooltip && <Tooltip />}\r\n {showLegend && <Legend />}\r\n </RadarChart>\r\n </ResponsiveContainer>\r\n </div>\r\n );\r\n}\r\n"],"mappings":";;;;;;;;;;AAkEA,SAAgB,YAAY,EAC1B,QACA,cACA,qBACA,QAAQ,WACR,eAAe,WACf,QAAQ,WACR,eAAe,cACf,QAAQ,QACR,SAAS,KACT,OACA,WACA,cAAc,MACd,aAAa,MACb,cAAc,OACK;CACnB,MAAM,QAAA,GAAA,MAAA,eAAuC;EAC3C,MAAM,YAAY,sBAAA,iBAAiB;AACnC,MAAI,CAAC,UAAW,QAAO,EAAE;AAEzB,SAAO,OAAO,KAAK,UAAU,CAAC,KAAK,WAAW;GAC5C,MAAM,EAAE,KAAK,QAAQ,UAAU;GAC/B,MAAM,WAAW,aAAa,WAAW;GAGzC,MAAM,QAAwB;IAAE;IAAQ,OAF1B,sBAAA,0BAA0B,UAAU,KAAK,IAAI;IAEZ;IAAU;AAEzD,OAAI,qBAAqB;IACvB,MAAM,aAAa,oBAAoB,WAAW;AAClD,UAAM,eAAe,sBAAA,0BAA0B,YAAY,KAAK,IAAI;AACpE,UAAM,kBAAkB;;AAG1B,UAAO;IACP;IACD;EAAC;EAAQ;EAAc;EAAoB,CAAC;AAE/C,KAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,QACE,iBAAA,GAAA,kBAAA,KAAC,OAAD;EAAgB;EAAW,OAAO;GAAE;GAAO,GAAG;GAAO;YACnD,iBAAA,GAAA,kBAAA,KAAC,SAAA,qBAAD;GAAqB,OAAM;GAAe;aACxC,iBAAA,GAAA,kBAAA,MAAC,SAAA,YAAD;IAAY,IAAG;IAAM,IAAG;IAAM,aAAY;IAAY;cAAtD;KACE,iBAAA,GAAA,kBAAA,KAAC,SAAA,WAAD,EAAa,CAAA;KACb,iBAAA,GAAA,kBAAA,KAAC,SAAA,gBAAD;MACE,SAAQ;MACR,MAAM,EAAE,UAAU,IAAI;MACtB,CAAA;KACF,iBAAA,GAAA,kBAAA,KAAC,SAAA,iBAAD;MACE,OAAO;MACP,QAAQ,CAAC,GAAG,IAAI;MAChB,MAAM,EAAE,UAAU,IAAI;MACtB,CAAA;KAEF,iBAAA,GAAA,kBAAA,KAAC,SAAA,OAAD;MACE,MAAM;MACN,SAAQ;MACR,QAAQ;MACR,MAAM;MACO;MACb,CAAA;KAED,uBACC,iBAAA,GAAA,kBAAA,KAAC,SAAA,OAAD;MACE,MAAM;MACN,SAAQ;MACR,QAAQ;MACR,MAAM;MACO;MACb,CAAA;KAGH,eAAe,iBAAA,GAAA,kBAAA,KAAC,SAAA,SAAD,EAAW,CAAA;KAC1B,cAAc,iBAAA,GAAA,kBAAA,KAAC,SAAA,QAAD,EAAU,CAAA;KACd;;GACO,CAAA;EAClB,CAAA"}
@@ -0,0 +1,43 @@
1
+ import { CSSProperties } from 'react';
2
+ import { Gender } from './normalization';
3
+ export interface BodyMeasurements {
4
+ /** Measurement values in inches, keyed by muscle/body-part name. */
5
+ [muscle: string]: number;
6
+ }
7
+ export interface MuscleRadarProps {
8
+ /** Gender – determines which min/max standards are used. */
9
+ gender: Gender;
10
+ /** Raw body measurements in inches. Keys should match `MUSCLE_STANDARDS` keys. */
11
+ measurements: BodyMeasurements;
12
+ /** Optional second data set to overlay (e.g. goals or previous measurements). */
13
+ compareMeasurements?: BodyMeasurements;
14
+ /** Fill colour for the primary radar area (default: `'#8884d8'`). */
15
+ color?: string;
16
+ /** Fill colour for the comparison radar area (default: `'#82ca9d'`). */
17
+ compareColor?: string;
18
+ /** Label for the primary data set shown in the legend (default: `'Current'`). */
19
+ label?: string;
20
+ /** Label for the comparison data set (default: `'Comparison'`). */
21
+ compareLabel?: string;
22
+ /** Width of the chart container (default: `'100%'`). */
23
+ width?: string | number;
24
+ /** Height of the chart container (default: `400`). */
25
+ height?: number;
26
+ /** Optional inline styles applied to the outer wrapper. */
27
+ style?: CSSProperties;
28
+ /** Optional CSS class name for the outer wrapper. */
29
+ className?: string;
30
+ /** Whether to show the tooltip on hover (default: `true`). */
31
+ showTooltip?: boolean;
32
+ /** Whether to show the legend (default: `true`). */
33
+ showLegend?: boolean;
34
+ /** Fill opacity for radar areas (default: `0.35`). */
35
+ fillOpacity?: number;
36
+ }
37
+ /**
38
+ * A Recharts-powered radar / spider chart that normalizes raw body
39
+ * measurements into development scores (0–100) and plots them on a
40
+ * radial grid. Optionally overlays a second data set for comparison.
41
+ */
42
+ export declare function MuscleRadar({ gender, measurements, compareMeasurements, color, compareColor, label, compareLabel, width, height, style, className, showTooltip, showLegend, fillOpacity, }: MuscleRadarProps): import("react/jsx-runtime").JSX.Element | null;
43
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/MuscleRadar/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAWpD,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,YAAY,EAAE,gBAAgB,CAAC;IAC/B,iFAAiF;IACjF,mBAAmB,CAAC,EAAE,gBAAgB,CAAC;IACvC,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oDAAoD;IACpD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAUD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,YAAY,EACZ,mBAAmB,EACnB,KAAiB,EACjB,YAAwB,EACxB,KAAiB,EACjB,YAA2B,EAC3B,KAAc,EACd,MAAY,EACZ,KAAK,EACL,SAAS,EACT,WAAkB,EAClB,UAAiB,EACjB,WAAkB,GACnB,EAAE,gBAAgB,kDA+DlB"}
@@ -0,0 +1,83 @@
1
+ import { MUSCLE_STANDARDS, calculateDevelopmentScore } from "./normalization.es.js";
2
+ import { useMemo } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { Legend, PolarAngleAxis, PolarGrid, PolarRadiusAxis, Radar, RadarChart, ResponsiveContainer, Tooltip } from "recharts";
5
+ /**
6
+ * A Recharts-powered radar / spider chart that normalizes raw body
7
+ * measurements into development scores (0–100) and plots them on a
8
+ * radial grid. Optionally overlays a second data set for comparison.
9
+ */
10
+ function MuscleRadar({ gender, measurements, compareMeasurements, color = "#8884d8", compareColor = "#82ca9d", label = "Current", compareLabel = "Comparison", width = "100%", height = 400, style, className, showTooltip = true, showLegend = true, fillOpacity = .35 }) {
11
+ const data = useMemo(() => {
12
+ const standards = MUSCLE_STANDARDS[gender];
13
+ if (!standards) return [];
14
+ return Object.keys(standards).map((muscle) => {
15
+ const { min, max } = standards[muscle];
16
+ const rawValue = measurements[muscle] ?? min;
17
+ const point = {
18
+ muscle,
19
+ score: calculateDevelopmentScore(rawValue, min, max),
20
+ rawValue
21
+ };
22
+ if (compareMeasurements) {
23
+ const compareRaw = compareMeasurements[muscle] ?? min;
24
+ point.compareScore = calculateDevelopmentScore(compareRaw, min, max);
25
+ point.compareRawValue = compareRaw;
26
+ }
27
+ return point;
28
+ });
29
+ }, [
30
+ gender,
31
+ measurements,
32
+ compareMeasurements
33
+ ]);
34
+ if (data.length === 0) return null;
35
+ return /* @__PURE__ */ jsx("div", {
36
+ className,
37
+ style: {
38
+ width,
39
+ ...style
40
+ },
41
+ children: /* @__PURE__ */ jsx(ResponsiveContainer, {
42
+ width: "100%",
43
+ height,
44
+ children: /* @__PURE__ */ jsxs(RadarChart, {
45
+ cx: "50%",
46
+ cy: "50%",
47
+ outerRadius: "80%",
48
+ data,
49
+ children: [
50
+ /* @__PURE__ */ jsx(PolarGrid, {}),
51
+ /* @__PURE__ */ jsx(PolarAngleAxis, {
52
+ dataKey: "muscle",
53
+ tick: { fontSize: 12 }
54
+ }),
55
+ /* @__PURE__ */ jsx(PolarRadiusAxis, {
56
+ angle: 90,
57
+ domain: [0, 100],
58
+ tick: { fontSize: 10 }
59
+ }),
60
+ /* @__PURE__ */ jsx(Radar, {
61
+ name: label,
62
+ dataKey: "score",
63
+ stroke: color,
64
+ fill: color,
65
+ fillOpacity
66
+ }),
67
+ compareMeasurements && /* @__PURE__ */ jsx(Radar, {
68
+ name: compareLabel,
69
+ dataKey: "compareScore",
70
+ stroke: compareColor,
71
+ fill: compareColor,
72
+ fillOpacity
73
+ }),
74
+ showTooltip && /* @__PURE__ */ jsx(Tooltip, {}),
75
+ showLegend && /* @__PURE__ */ jsx(Legend, {})
76
+ ]
77
+ })
78
+ })
79
+ });
80
+ }
81
+ export { MuscleRadar };
82
+
83
+ //# sourceMappingURL=index.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.es.js","names":[],"sources":["../../../src/components/MuscleRadar/index.tsx"],"sourcesContent":["import { useMemo, type CSSProperties } from 'react';\r\nimport {\r\n Radar,\r\n RadarChart,\r\n PolarGrid,\r\n PolarAngleAxis,\r\n PolarRadiusAxis,\r\n ResponsiveContainer,\r\n Tooltip,\r\n Legend,\r\n} from 'recharts';\r\nimport {\r\n MUSCLE_STANDARDS,\r\n calculateDevelopmentScore,\r\n type Gender,\r\n} from './normalization';\r\n\r\nexport interface BodyMeasurements {\r\n /** Measurement values in inches, keyed by muscle/body-part name. */\r\n [muscle: string]: number;\r\n}\r\n\r\nexport interface MuscleRadarProps {\r\n /** Gender – determines which min/max standards are used. */\r\n gender: Gender;\r\n /** Raw body measurements in inches. Keys should match `MUSCLE_STANDARDS` keys. */\r\n measurements: BodyMeasurements;\r\n /** Optional second data set to overlay (e.g. goals or previous measurements). */\r\n compareMeasurements?: BodyMeasurements;\r\n /** Fill colour for the primary radar area (default: `'#8884d8'`). */\r\n color?: string;\r\n /** Fill colour for the comparison radar area (default: `'#82ca9d'`). */\r\n compareColor?: string;\r\n /** Label for the primary data set shown in the legend (default: `'Current'`). */\r\n label?: string;\r\n /** Label for the comparison data set (default: `'Comparison'`). */\r\n compareLabel?: string;\r\n /** Width of the chart container (default: `'100%'`). */\r\n width?: string | number;\r\n /** Height of the chart container (default: `400`). */\r\n height?: number;\r\n /** Optional inline styles applied to the outer wrapper. */\r\n style?: CSSProperties;\r\n /** Optional CSS class name for the outer wrapper. */\r\n className?: string;\r\n /** Whether to show the tooltip on hover (default: `true`). */\r\n showTooltip?: boolean;\r\n /** Whether to show the legend (default: `true`). */\r\n showLegend?: boolean;\r\n /** Fill opacity for radar areas (default: `0.35`). */\r\n fillOpacity?: number;\r\n}\r\n\r\ninterface RadarDataPoint {\r\n muscle: string;\r\n score: number;\r\n rawValue: number;\r\n compareScore?: number;\r\n compareRawValue?: number;\r\n}\r\n\r\n/**\r\n * A Recharts-powered radar / spider chart that normalizes raw body\r\n * measurements into development scores (0–100) and plots them on a\r\n * radial grid. Optionally overlays a second data set for comparison.\r\n */\r\nexport function MuscleRadar({\r\n gender,\r\n measurements,\r\n compareMeasurements,\r\n color = '#8884d8',\r\n compareColor = '#82ca9d',\r\n label = 'Current',\r\n compareLabel = 'Comparison',\r\n width = '100%',\r\n height = 400,\r\n style,\r\n className,\r\n showTooltip = true,\r\n showLegend = true,\r\n fillOpacity = 0.35,\r\n}: MuscleRadarProps) {\r\n const data = useMemo<RadarDataPoint[]>(() => {\r\n const standards = MUSCLE_STANDARDS[gender];\r\n if (!standards) return [];\r\n\r\n return Object.keys(standards).map((muscle) => {\r\n const { min, max } = standards[muscle];\r\n const rawValue = measurements[muscle] ?? min;\r\n const score = calculateDevelopmentScore(rawValue, min, max);\r\n\r\n const point: RadarDataPoint = { muscle, score, rawValue };\r\n\r\n if (compareMeasurements) {\r\n const compareRaw = compareMeasurements[muscle] ?? min;\r\n point.compareScore = calculateDevelopmentScore(compareRaw, min, max);\r\n point.compareRawValue = compareRaw;\r\n }\r\n\r\n return point;\r\n });\r\n }, [gender, measurements, compareMeasurements]);\r\n\r\n if (data.length === 0) return null;\r\n\r\n return (\r\n <div className={className} style={{ width, ...style }}>\r\n <ResponsiveContainer width=\"100%\" height={height}>\r\n <RadarChart cx=\"50%\" cy=\"50%\" outerRadius=\"80%\" data={data}>\r\n <PolarGrid />\r\n <PolarAngleAxis\r\n dataKey=\"muscle\"\r\n tick={{ fontSize: 12 }}\r\n />\r\n <PolarRadiusAxis\r\n angle={90}\r\n domain={[0, 100]}\r\n tick={{ fontSize: 10 }}\r\n />\r\n\r\n <Radar\r\n name={label}\r\n dataKey=\"score\"\r\n stroke={color}\r\n fill={color}\r\n fillOpacity={fillOpacity}\r\n />\r\n\r\n {compareMeasurements && (\r\n <Radar\r\n name={compareLabel}\r\n dataKey=\"compareScore\"\r\n stroke={compareColor}\r\n fill={compareColor}\r\n fillOpacity={fillOpacity}\r\n />\r\n )}\r\n\r\n {showTooltip && <Tooltip />}\r\n {showLegend && <Legend />}\r\n </RadarChart>\r\n </ResponsiveContainer>\r\n </div>\r\n );\r\n}\r\n"],"mappings":";;;;;;;;;AAkEA,SAAgB,YAAY,EAC1B,QACA,cACA,qBACA,QAAQ,WACR,eAAe,WACf,QAAQ,WACR,eAAe,cACf,QAAQ,QACR,SAAS,KACT,OACA,WACA,cAAc,MACd,aAAa,MACb,cAAc,OACK;CACnB,MAAM,OAAO,cAAgC;EAC3C,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UAAW,QAAO,EAAE;AAEzB,SAAO,OAAO,KAAK,UAAU,CAAC,KAAK,WAAW;GAC5C,MAAM,EAAE,KAAK,QAAQ,UAAU;GAC/B,MAAM,WAAW,aAAa,WAAW;GAGzC,MAAM,QAAwB;IAAE;IAAQ,OAF1B,0BAA0B,UAAU,KAAK,IAAI;IAEZ;IAAU;AAEzD,OAAI,qBAAqB;IACvB,MAAM,aAAa,oBAAoB,WAAW;AAClD,UAAM,eAAe,0BAA0B,YAAY,KAAK,IAAI;AACpE,UAAM,kBAAkB;;AAG1B,UAAO;IACP;IACD;EAAC;EAAQ;EAAc;EAAoB,CAAC;AAE/C,KAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,QACE,oBAAC,OAAD;EAAgB;EAAW,OAAO;GAAE;GAAO,GAAG;GAAO;YACnD,oBAAC,qBAAD;GAAqB,OAAM;GAAe;aACxC,qBAAC,YAAD;IAAY,IAAG;IAAM,IAAG;IAAM,aAAY;IAAY;cAAtD;KACE,oBAAC,WAAD,EAAa,CAAA;KACb,oBAAC,gBAAD;MACE,SAAQ;MACR,MAAM,EAAE,UAAU,IAAI;MACtB,CAAA;KACF,oBAAC,iBAAD;MACE,OAAO;MACP,QAAQ,CAAC,GAAG,IAAI;MAChB,MAAM,EAAE,UAAU,IAAI;MACtB,CAAA;KAEF,oBAAC,OAAD;MACE,MAAM;MACN,SAAQ;MACR,QAAQ;MACR,MAAM;MACO;MACb,CAAA;KAED,uBACC,oBAAC,OAAD;MACE,MAAM;MACN,SAAQ;MACR,QAAQ;MACR,MAAM;MACO;MACb,CAAA;KAGH,eAAe,oBAAC,SAAD,EAAW,CAAA;KAC1B,cAAc,oBAAC,QAAD,EAAU,CAAA;KACd;;GACO,CAAA;EAClB,CAAA"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Physiological min/max limits in inches for body measurements.
3
+ * Used to normalize raw measurements into 0–100 development scores.
4
+ */
5
+ const MUSCLE_STANDARDS = {
6
+ male: {
7
+ chest: {
8
+ min: 32,
9
+ max: 50
10
+ },
11
+ shoulders: {
12
+ min: 38,
13
+ max: 56
14
+ },
15
+ biceps: {
16
+ min: 10,
17
+ max: 20
18
+ },
19
+ forearm: {
20
+ min: 8,
21
+ max: 15
22
+ },
23
+ waist: {
24
+ min: 26,
25
+ max: 40
26
+ },
27
+ hips: {
28
+ min: 32,
29
+ max: 44
30
+ },
31
+ thigh: {
32
+ min: 18,
33
+ max: 30
34
+ },
35
+ calf: {
36
+ min: 12,
37
+ max: 20
38
+ },
39
+ neck: {
40
+ min: 13,
41
+ max: 20
42
+ }
43
+ },
44
+ female: {
45
+ chest: {
46
+ min: 28,
47
+ max: 44
48
+ },
49
+ shoulders: {
50
+ min: 34,
51
+ max: 48
52
+ },
53
+ biceps: {
54
+ min: 8,
55
+ max: 16
56
+ },
57
+ forearm: {
58
+ min: 6,
59
+ max: 12
60
+ },
61
+ waist: {
62
+ min: 22,
63
+ max: 36
64
+ },
65
+ hips: {
66
+ min: 30,
67
+ max: 46
68
+ },
69
+ thigh: {
70
+ min: 16,
71
+ max: 28
72
+ },
73
+ calf: {
74
+ min: 10,
75
+ max: 18
76
+ },
77
+ neck: {
78
+ min: 11,
79
+ max: 16
80
+ }
81
+ }
82
+ };
83
+ /**
84
+ * Converts a raw inch measurement into a 0–100 development score.
85
+ * The score is clamped between 5 and 100.
86
+ *
87
+ * @param val - The measured value in inches
88
+ * @param min - The physiological minimum for the measurement
89
+ * @param max - The physiological maximum for the measurement
90
+ * @returns A score between 5 and 100
91
+ */
92
+ function calculateDevelopmentScore(val, min, max) {
93
+ if (max === min) return 5;
94
+ const raw = (val - min) / (max - min) * 100;
95
+ return Math.min(100, Math.max(5, Math.round(raw)));
96
+ }
97
+ exports.MUSCLE_STANDARDS = MUSCLE_STANDARDS;
98
+ exports.calculateDevelopmentScore = calculateDevelopmentScore;
99
+
100
+ //# sourceMappingURL=normalization.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalization.cjs.js","names":[],"sources":["../../../src/components/MuscleRadar/normalization.ts"],"sourcesContent":["export type Gender = 'male' | 'female';\r\n\r\nexport interface MuscleStandards {\r\n min: number;\r\n max: number;\r\n}\r\n\r\n/**\r\n * Physiological min/max limits in inches for body measurements.\r\n * Used to normalize raw measurements into 0–100 development scores.\r\n */\r\nexport const MUSCLE_STANDARDS: Record<Gender, Record<string, MuscleStandards>> = {\r\n male: {\r\n chest: { min: 32, max: 50 },\r\n shoulders: { min: 38, max: 56 },\r\n biceps: { min: 10, max: 20 },\r\n forearm: { min: 8, max: 15 },\r\n waist: { min: 26, max: 40 },\r\n hips: { min: 32, max: 44 },\r\n thigh: { min: 18, max: 30 },\r\n calf: { min: 12, max: 20 },\r\n neck: { min: 13, max: 20 },\r\n },\r\n female: {\r\n chest: { min: 28, max: 44 },\r\n shoulders: { min: 34, max: 48 },\r\n biceps: { min: 8, max: 16 },\r\n forearm: { min: 6, max: 12 },\r\n waist: { min: 22, max: 36 },\r\n hips: { min: 30, max: 46 },\r\n thigh: { min: 16, max: 28 },\r\n calf: { min: 10, max: 18 },\r\n neck: { min: 11, max: 16 },\r\n },\r\n};\r\n\r\n/**\r\n * Converts a raw inch measurement into a 0–100 development score.\r\n * The score is clamped between 5 and 100.\r\n *\r\n * @param val - The measured value in inches\r\n * @param min - The physiological minimum for the measurement\r\n * @param max - The physiological maximum for the measurement\r\n * @returns A score between 5 and 100\r\n */\r\nexport function calculateDevelopmentScore(val: number, min: number, max: number): number {\r\n if (max === min) return 5;\r\n const raw = ((val - min) / (max - min)) * 100;\r\n return Math.min(100, Math.max(5, Math.round(raw)));\r\n}\r\n"],"mappings":";;;;AAWA,MAAa,mBAAoE;CAC/E,MAAM;EACJ,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,WAAW;GAAE,KAAK;GAAI,KAAK;GAAI;EAC/B,QAAQ;GAAE,KAAK;GAAI,KAAK;GAAI;EAC5B,SAAS;GAAE,KAAK;GAAG,KAAK;GAAI;EAC5B,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC1B,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC1B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B;CACD,QAAQ;EACN,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,WAAW;GAAE,KAAK;GAAI,KAAK;GAAI;EAC/B,QAAQ;GAAE,KAAK;GAAG,KAAK;GAAI;EAC3B,SAAS;GAAE,KAAK;GAAG,KAAK;GAAI;EAC5B,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC1B,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC1B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B;CACF;;;;;;;;;;AAWD,SAAgB,0BAA0B,KAAa,KAAa,KAAqB;AACvF,KAAI,QAAQ,IAAK,QAAO;CACxB,MAAM,OAAQ,MAAM,QAAQ,MAAM,OAAQ;AAC1C,QAAO,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,CAAC,CAAC"}
@@ -0,0 +1,21 @@
1
+ export type Gender = 'male' | 'female';
2
+ export interface MuscleStandards {
3
+ min: number;
4
+ max: number;
5
+ }
6
+ /**
7
+ * Physiological min/max limits in inches for body measurements.
8
+ * Used to normalize raw measurements into 0–100 development scores.
9
+ */
10
+ export declare const MUSCLE_STANDARDS: Record<Gender, Record<string, MuscleStandards>>;
11
+ /**
12
+ * Converts a raw inch measurement into a 0–100 development score.
13
+ * The score is clamped between 5 and 100.
14
+ *
15
+ * @param val - The measured value in inches
16
+ * @param min - The physiological minimum for the measurement
17
+ * @param max - The physiological maximum for the measurement
18
+ * @returns A score between 5 and 100
19
+ */
20
+ export declare function calculateDevelopmentScore(val: number, min: number, max: number): number;
21
+ //# sourceMappingURL=normalization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalization.d.ts","sourceRoot":"","sources":["../../../src/components/MuscleRadar/normalization.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEvC,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAuB5E,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAIvF"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Physiological min/max limits in inches for body measurements.
3
+ * Used to normalize raw measurements into 0–100 development scores.
4
+ */
5
+ const MUSCLE_STANDARDS = {
6
+ male: {
7
+ chest: {
8
+ min: 32,
9
+ max: 50
10
+ },
11
+ shoulders: {
12
+ min: 38,
13
+ max: 56
14
+ },
15
+ biceps: {
16
+ min: 10,
17
+ max: 20
18
+ },
19
+ forearm: {
20
+ min: 8,
21
+ max: 15
22
+ },
23
+ waist: {
24
+ min: 26,
25
+ max: 40
26
+ },
27
+ hips: {
28
+ min: 32,
29
+ max: 44
30
+ },
31
+ thigh: {
32
+ min: 18,
33
+ max: 30
34
+ },
35
+ calf: {
36
+ min: 12,
37
+ max: 20
38
+ },
39
+ neck: {
40
+ min: 13,
41
+ max: 20
42
+ }
43
+ },
44
+ female: {
45
+ chest: {
46
+ min: 28,
47
+ max: 44
48
+ },
49
+ shoulders: {
50
+ min: 34,
51
+ max: 48
52
+ },
53
+ biceps: {
54
+ min: 8,
55
+ max: 16
56
+ },
57
+ forearm: {
58
+ min: 6,
59
+ max: 12
60
+ },
61
+ waist: {
62
+ min: 22,
63
+ max: 36
64
+ },
65
+ hips: {
66
+ min: 30,
67
+ max: 46
68
+ },
69
+ thigh: {
70
+ min: 16,
71
+ max: 28
72
+ },
73
+ calf: {
74
+ min: 10,
75
+ max: 18
76
+ },
77
+ neck: {
78
+ min: 11,
79
+ max: 16
80
+ }
81
+ }
82
+ };
83
+ /**
84
+ * Converts a raw inch measurement into a 0–100 development score.
85
+ * The score is clamped between 5 and 100.
86
+ *
87
+ * @param val - The measured value in inches
88
+ * @param min - The physiological minimum for the measurement
89
+ * @param max - The physiological maximum for the measurement
90
+ * @returns A score between 5 and 100
91
+ */
92
+ function calculateDevelopmentScore(val, min, max) {
93
+ if (max === min) return 5;
94
+ const raw = (val - min) / (max - min) * 100;
95
+ return Math.min(100, Math.max(5, Math.round(raw)));
96
+ }
97
+ export { MUSCLE_STANDARDS, calculateDevelopmentScore };
98
+
99
+ //# sourceMappingURL=normalization.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalization.es.js","names":[],"sources":["../../../src/components/MuscleRadar/normalization.ts"],"sourcesContent":["export type Gender = 'male' | 'female';\r\n\r\nexport interface MuscleStandards {\r\n min: number;\r\n max: number;\r\n}\r\n\r\n/**\r\n * Physiological min/max limits in inches for body measurements.\r\n * Used to normalize raw measurements into 0–100 development scores.\r\n */\r\nexport const MUSCLE_STANDARDS: Record<Gender, Record<string, MuscleStandards>> = {\r\n male: {\r\n chest: { min: 32, max: 50 },\r\n shoulders: { min: 38, max: 56 },\r\n biceps: { min: 10, max: 20 },\r\n forearm: { min: 8, max: 15 },\r\n waist: { min: 26, max: 40 },\r\n hips: { min: 32, max: 44 },\r\n thigh: { min: 18, max: 30 },\r\n calf: { min: 12, max: 20 },\r\n neck: { min: 13, max: 20 },\r\n },\r\n female: {\r\n chest: { min: 28, max: 44 },\r\n shoulders: { min: 34, max: 48 },\r\n biceps: { min: 8, max: 16 },\r\n forearm: { min: 6, max: 12 },\r\n waist: { min: 22, max: 36 },\r\n hips: { min: 30, max: 46 },\r\n thigh: { min: 16, max: 28 },\r\n calf: { min: 10, max: 18 },\r\n neck: { min: 11, max: 16 },\r\n },\r\n};\r\n\r\n/**\r\n * Converts a raw inch measurement into a 0–100 development score.\r\n * The score is clamped between 5 and 100.\r\n *\r\n * @param val - The measured value in inches\r\n * @param min - The physiological minimum for the measurement\r\n * @param max - The physiological maximum for the measurement\r\n * @returns A score between 5 and 100\r\n */\r\nexport function calculateDevelopmentScore(val: number, min: number, max: number): number {\r\n if (max === min) return 5;\r\n const raw = ((val - min) / (max - min)) * 100;\r\n return Math.min(100, Math.max(5, Math.round(raw)));\r\n}\r\n"],"mappings":";;;;AAWA,MAAa,mBAAoE;CAC/E,MAAM;EACJ,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,WAAW;GAAE,KAAK;GAAI,KAAK;GAAI;EAC/B,QAAQ;GAAE,KAAK;GAAI,KAAK;GAAI;EAC5B,SAAS;GAAE,KAAK;GAAG,KAAK;GAAI;EAC5B,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC1B,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC1B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B;CACD,QAAQ;EACN,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,WAAW;GAAE,KAAK;GAAI,KAAK;GAAI;EAC/B,QAAQ;GAAE,KAAK;GAAG,KAAK;GAAI;EAC3B,SAAS;GAAE,KAAK;GAAG,KAAK;GAAI;EAC5B,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC1B,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC1B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B;CACF;;;;;;;;;;AAWD,SAAgB,0BAA0B,KAAa,KAAa,KAAqB;AACvF,KAAI,QAAQ,IAAK,QAAO;CACxB,MAAM,OAAQ,MAAM,QAAQ,MAAM,OAAQ;AAC1C,QAAO,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,CAAC,CAAC"}
@@ -0,0 +1,49 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_svgs = require("./svgs.cjs.js");
3
+ let react = require("react");
4
+ let react_jsx_runtime = require("react/jsx-runtime");
5
+ /**
6
+ * Renders an interactive SVG body diagram.
7
+ *
8
+ * Muscle groups inside the SVG carry `class="muscle"` and can be
9
+ * highlighted by passing their `id` values through the `activeMuscles`
10
+ * prop. The component uses `dangerouslySetInnerHTML` to inject the SVG
11
+ * markup and `useEffect` to imperatively toggle the `active` class on
12
+ * the matching `<g>` elements.
13
+ */
14
+ function MuscleVisualizer({ gender, view = "front", activeMuscles = [], onMuscleClick, style, className, width = "100%", height = "auto" }) {
15
+ const containerRef = (0, react.useRef)(null);
16
+ const svgKey = `${gender} ${view}`;
17
+ const svgMarkup = require_svgs.MUSCLE_SVGS[svgKey] ?? "";
18
+ (0, react.useEffect)(() => {
19
+ const container = containerRef.current;
20
+ if (!container) return;
21
+ container.querySelectorAll("g.muscle").forEach((g) => {
22
+ if (activeMuscles.includes(g.id)) g.classList.add("active");
23
+ else g.classList.remove("active");
24
+ });
25
+ }, [activeMuscles, svgKey]);
26
+ (0, react.useEffect)(() => {
27
+ const container = containerRef.current;
28
+ if (!container || !onMuscleClick) return;
29
+ const handler = (e) => {
30
+ const target = e.target.closest("g.muscle");
31
+ if (target?.id) onMuscleClick(target.id);
32
+ };
33
+ container.addEventListener("click", handler);
34
+ return () => container.removeEventListener("click", handler);
35
+ }, [onMuscleClick, svgKey]);
36
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
37
+ ref: containerRef,
38
+ className,
39
+ style: {
40
+ width,
41
+ height,
42
+ ...style
43
+ },
44
+ dangerouslySetInnerHTML: { __html: svgMarkup }
45
+ });
46
+ }
47
+ exports.MuscleVisualizer = MuscleVisualizer;
48
+
49
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","names":[],"sources":["../../../src/components/MuscleVisualizer/index.tsx"],"sourcesContent":["import { useEffect, useRef, type CSSProperties } from 'react';\r\nimport { MUSCLE_SVGS } from './svgs';\r\n\r\nexport type Gender = 'male' | 'female';\r\nexport type View = 'front' | 'back';\r\n\r\nexport interface MuscleVisualizerProps {\r\n /** Gender to display – determines the body silhouette. */\r\n gender: Gender;\r\n /** Which side of the body to show. */\r\n view?: View;\r\n /** Muscle IDs that should be highlighted (given the `.active` CSS class). */\r\n activeMuscles?: string[];\r\n /** Optional callback fired when a muscle group `<g>` element is clicked. */\r\n onMuscleClick?: (muscleId: string) => void;\r\n /** Optional inline styles applied to the wrapper `<div>`. */\r\n style?: CSSProperties;\r\n /** Optional CSS class name for the wrapper `<div>`. */\r\n className?: string;\r\n /** Width applied to the SVG wrapper (default: `'100%'`). */\r\n width?: string | number;\r\n /** Height applied to the SVG wrapper (default: `'auto'`). */\r\n height?: string | number;\r\n}\r\n\r\n/**\r\n * Renders an interactive SVG body diagram.\r\n *\r\n * Muscle groups inside the SVG carry `class=\"muscle\"` and can be\r\n * highlighted by passing their `id` values through the `activeMuscles`\r\n * prop. The component uses `dangerouslySetInnerHTML` to inject the SVG\r\n * markup and `useEffect` to imperatively toggle the `active` class on\r\n * the matching `<g>` elements.\r\n */\r\nexport function MuscleVisualizer({\r\n gender,\r\n view = 'front',\r\n activeMuscles = [],\r\n onMuscleClick,\r\n style,\r\n className,\r\n width = '100%',\r\n height = 'auto',\r\n}: MuscleVisualizerProps) {\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n const svgKey = `${gender} ${view}` as keyof typeof MUSCLE_SVGS;\r\n const svgMarkup = MUSCLE_SVGS[svgKey] ?? '';\r\n\r\n // Toggle the `active` class on `<g class=\"muscle\">` elements whose\r\n // `id` is listed in `activeMuscles`.\r\n useEffect(() => {\r\n const container = containerRef.current;\r\n if (!container) return;\r\n\r\n const muscleGroups = container.querySelectorAll<SVGGElement>('g.muscle');\r\n muscleGroups.forEach((g) => {\r\n if (activeMuscles.includes(g.id)) {\r\n g.classList.add('active');\r\n } else {\r\n g.classList.remove('active');\r\n }\r\n });\r\n }, [activeMuscles, svgKey]);\r\n\r\n // Attach click listeners to every `<g class=\"muscle\">` element.\r\n useEffect(() => {\r\n const container = containerRef.current;\r\n if (!container || !onMuscleClick) return;\r\n\r\n const handler = (e: Event) => {\r\n const target = (e.target as SVGElement).closest<SVGGElement>('g.muscle');\r\n if (target?.id) {\r\n onMuscleClick(target.id);\r\n }\r\n };\r\n\r\n container.addEventListener('click', handler);\r\n return () => container.removeEventListener('click', handler);\r\n }, [onMuscleClick, svgKey]);\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n className={className}\r\n style={{ width, height, ...style }}\r\n dangerouslySetInnerHTML={{ __html: svgMarkup }}\r\n />\r\n );\r\n}\r\n"],"mappings":";;;;;;;;;;;;;AAkCA,SAAgB,iBAAiB,EAC/B,QACA,OAAO,SACP,gBAAgB,EAAE,EAClB,eACA,OACA,WACA,QAAQ,QACR,SAAS,UACe;CACxB,MAAM,gBAAA,GAAA,MAAA,QAAsC,KAAK;CAEjD,MAAM,SAAS,GAAG,OAAO,GAAG;CAC5B,MAAM,YAAY,aAAA,YAAY,WAAW;AAIzC,EAAA,GAAA,MAAA,iBAAgB;EACd,MAAM,YAAY,aAAa;AAC/B,MAAI,CAAC,UAAW;AAEK,YAAU,iBAA8B,WAAW,CAC3D,SAAS,MAAM;AAC1B,OAAI,cAAc,SAAS,EAAE,GAAG,CAC9B,GAAE,UAAU,IAAI,SAAS;OAEzB,GAAE,UAAU,OAAO,SAAS;IAE9B;IACD,CAAC,eAAe,OAAO,CAAC;AAG3B,EAAA,GAAA,MAAA,iBAAgB;EACd,MAAM,YAAY,aAAa;AAC/B,MAAI,CAAC,aAAa,CAAC,cAAe;EAElC,MAAM,WAAW,MAAa;GAC5B,MAAM,SAAU,EAAE,OAAsB,QAAqB,WAAW;AACxE,OAAI,QAAQ,GACV,eAAc,OAAO,GAAG;;AAI5B,YAAU,iBAAiB,SAAS,QAAQ;AAC5C,eAAa,UAAU,oBAAoB,SAAS,QAAQ;IAC3D,CAAC,eAAe,OAAO,CAAC;AAE3B,QACE,iBAAA,GAAA,kBAAA,KAAC,OAAD;EACE,KAAK;EACM;EACX,OAAO;GAAE;GAAO;GAAQ,GAAG;GAAO;EAClC,yBAAyB,EAAE,QAAQ,WAAW;EAC9C,CAAA"}
@@ -0,0 +1,32 @@
1
+ import { CSSProperties } from 'react';
2
+ export type Gender = 'male' | 'female';
3
+ export type View = 'front' | 'back';
4
+ export interface MuscleVisualizerProps {
5
+ /** Gender to display – determines the body silhouette. */
6
+ gender: Gender;
7
+ /** Which side of the body to show. */
8
+ view?: View;
9
+ /** Muscle IDs that should be highlighted (given the `.active` CSS class). */
10
+ activeMuscles?: string[];
11
+ /** Optional callback fired when a muscle group `<g>` element is clicked. */
12
+ onMuscleClick?: (muscleId: string) => void;
13
+ /** Optional inline styles applied to the wrapper `<div>`. */
14
+ style?: CSSProperties;
15
+ /** Optional CSS class name for the wrapper `<div>`. */
16
+ className?: string;
17
+ /** Width applied to the SVG wrapper (default: `'100%'`). */
18
+ width?: string | number;
19
+ /** Height applied to the SVG wrapper (default: `'auto'`). */
20
+ height?: string | number;
21
+ }
22
+ /**
23
+ * Renders an interactive SVG body diagram.
24
+ *
25
+ * Muscle groups inside the SVG carry `class="muscle"` and can be
26
+ * highlighted by passing their `id` values through the `activeMuscles`
27
+ * prop. The component uses `dangerouslySetInnerHTML` to inject the SVG
28
+ * markup and `useEffect` to imperatively toggle the `active` class on
29
+ * the matching `<g>` elements.
30
+ */
31
+ export declare function MuscleVisualizer({ gender, view, activeMuscles, onMuscleClick, style, className, width, height, }: MuscleVisualizerProps): import("react/jsx-runtime").JSX.Element;
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/MuscleVisualizer/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAG9D,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;AACvC,MAAM,MAAM,IAAI,GAAG,OAAO,GAAG,MAAM,CAAC;AAEpC,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,4EAA4E;IAC5E,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,6DAA6D;IAC7D,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,IAAc,EACd,aAAkB,EAClB,aAAa,EACb,KAAK,EACL,SAAS,EACT,KAAc,EACd,MAAe,GAChB,EAAE,qBAAqB,2CA8CvB"}