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.
- package/README.md +73 -0
- package/dist/components/MuscleRadar/index.cjs.js +84 -0
- package/dist/components/MuscleRadar/index.cjs.js.map +1 -0
- package/dist/components/MuscleRadar/index.d.ts +43 -0
- package/dist/components/MuscleRadar/index.d.ts.map +1 -0
- package/dist/components/MuscleRadar/index.es.js +83 -0
- package/dist/components/MuscleRadar/index.es.js.map +1 -0
- package/dist/components/MuscleRadar/normalization.cjs.js +100 -0
- package/dist/components/MuscleRadar/normalization.cjs.js.map +1 -0
- package/dist/components/MuscleRadar/normalization.d.ts +21 -0
- package/dist/components/MuscleRadar/normalization.d.ts.map +1 -0
- package/dist/components/MuscleRadar/normalization.es.js +99 -0
- package/dist/components/MuscleRadar/normalization.es.js.map +1 -0
- package/dist/components/MuscleVisualizer/index.cjs.js +49 -0
- package/dist/components/MuscleVisualizer/index.cjs.js.map +1 -0
- package/dist/components/MuscleVisualizer/index.d.ts +32 -0
- package/dist/components/MuscleVisualizer/index.d.ts.map +1 -0
- package/dist/components/MuscleVisualizer/index.es.js +48 -0
- package/dist/components/MuscleVisualizer/index.es.js.map +1 -0
- package/dist/components/MuscleVisualizer/svgs.cjs.js +2274 -0
- package/dist/components/MuscleVisualizer/svgs.cjs.js.map +1 -0
- package/dist/components/MuscleVisualizer/svgs.d.ts +2 -0
- package/dist/components/MuscleVisualizer/svgs.d.ts.map +1 -0
- package/dist/components/MuscleVisualizer/svgs.es.js +2274 -0
- package/dist/components/MuscleVisualizer/svgs.es.js.map +1 -0
- package/dist/components/OneRepMaxChart/index.cjs.js +132 -0
- package/dist/components/OneRepMaxChart/index.cjs.js.map +1 -0
- package/dist/components/OneRepMaxChart/index.d.ts +30 -0
- package/dist/components/OneRepMaxChart/index.d.ts.map +1 -0
- package/dist/components/OneRepMaxChart/index.es.js +127 -0
- package/dist/components/OneRepMaxChart/index.es.js.map +1 -0
- package/dist/components/VolumeChart/index.cjs.js +155 -0
- package/dist/components/VolumeChart/index.cjs.js.map +1 -0
- package/dist/components/VolumeChart/index.d.ts +30 -0
- package/dist/components/VolumeChart/index.d.ts.map +1 -0
- package/dist/components/VolumeChart/index.es.js +150 -0
- package/dist/components/VolumeChart/index.es.js.map +1 -0
- package/dist/index.cjs.js +16 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.es.js +8 -0
- package/dist/utils/math.cjs.js +16 -0
- package/dist/utils/math.cjs.js.map +1 -0
- package/dist/utils/math.d.ts +11 -0
- package/dist/utils/math.d.ts.map +1 -0
- package/dist/utils/math.es.js +16 -0
- package/dist/utils/math.es.js.map +1 -0
- package/package.json +87 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
|
+
let recharts = require("recharts");
|
|
7
|
+
var tooltipWrapperStyle = {
|
|
8
|
+
backgroundColor: "#1e1e2f",
|
|
9
|
+
border: "1px solid #3a3a5c",
|
|
10
|
+
borderRadius: 8,
|
|
11
|
+
padding: "8px 12px",
|
|
12
|
+
color: "#fff",
|
|
13
|
+
fontSize: 13,
|
|
14
|
+
lineHeight: 1.4
|
|
15
|
+
};
|
|
16
|
+
function OneRepMaxTooltip({ active, payload, label, unit }) {
|
|
17
|
+
if (!active || !payload?.length) return null;
|
|
18
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19
|
+
style: tooltipWrapperStyle,
|
|
20
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
21
|
+
style: {
|
|
22
|
+
margin: 0,
|
|
23
|
+
fontWeight: 600
|
|
24
|
+
},
|
|
25
|
+
children: label
|
|
26
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
27
|
+
style: {
|
|
28
|
+
margin: 0,
|
|
29
|
+
opacity: .85
|
|
30
|
+
},
|
|
31
|
+
children: [
|
|
32
|
+
payload[0].value,
|
|
33
|
+
" ",
|
|
34
|
+
unit
|
|
35
|
+
]
|
|
36
|
+
})]
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
var emptyStyle = {
|
|
40
|
+
display: "flex",
|
|
41
|
+
alignItems: "center",
|
|
42
|
+
justifyContent: "center",
|
|
43
|
+
color: "#888",
|
|
44
|
+
fontSize: 14,
|
|
45
|
+
height: 200
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* A sleek `<LineChart>` for tracking estimated one-rep-max progress
|
|
49
|
+
* over time. Inspired by the Hevy app's performance charts.
|
|
50
|
+
*/
|
|
51
|
+
function OneRepMaxChart({ data, lineColor = "#6C63FF", unit = "lbs", width = "100%", height = 300, style, className }) {
|
|
52
|
+
if (data.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
53
|
+
className,
|
|
54
|
+
style: {
|
|
55
|
+
...emptyStyle,
|
|
56
|
+
...style
|
|
57
|
+
},
|
|
58
|
+
children: "No 1RM data available"
|
|
59
|
+
});
|
|
60
|
+
const glowFilter = `drop-shadow(0 0 6px ${lineColor})`;
|
|
61
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
62
|
+
className,
|
|
63
|
+
style: {
|
|
64
|
+
width,
|
|
65
|
+
...style
|
|
66
|
+
},
|
|
67
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.ResponsiveContainer, {
|
|
68
|
+
width: "100%",
|
|
69
|
+
height,
|
|
70
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(recharts.LineChart, {
|
|
71
|
+
data,
|
|
72
|
+
margin: {
|
|
73
|
+
top: 10,
|
|
74
|
+
right: 20,
|
|
75
|
+
bottom: 0,
|
|
76
|
+
left: 0
|
|
77
|
+
},
|
|
78
|
+
children: [
|
|
79
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.XAxis, {
|
|
80
|
+
dataKey: "date",
|
|
81
|
+
axisLine: false,
|
|
82
|
+
tickLine: false,
|
|
83
|
+
tick: {
|
|
84
|
+
fontSize: 11,
|
|
85
|
+
fill: "#aaa"
|
|
86
|
+
}
|
|
87
|
+
}),
|
|
88
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.YAxis, {
|
|
89
|
+
axisLine: false,
|
|
90
|
+
tickLine: false,
|
|
91
|
+
tick: {
|
|
92
|
+
fontSize: 11,
|
|
93
|
+
fill: "#aaa"
|
|
94
|
+
},
|
|
95
|
+
domain: ["dataMin - 10", "dataMax + 10"]
|
|
96
|
+
}),
|
|
97
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.Tooltip, {
|
|
98
|
+
content: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OneRepMaxTooltip, { unit }),
|
|
99
|
+
cursor: {
|
|
100
|
+
stroke: "#555",
|
|
101
|
+
strokeDasharray: "4 4"
|
|
102
|
+
}
|
|
103
|
+
}),
|
|
104
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.Line, {
|
|
105
|
+
type: "monotone",
|
|
106
|
+
dataKey: "weight",
|
|
107
|
+
stroke: lineColor,
|
|
108
|
+
strokeWidth: 2.5,
|
|
109
|
+
dot: {
|
|
110
|
+
r: 4,
|
|
111
|
+
fill: lineColor,
|
|
112
|
+
stroke: "#fff",
|
|
113
|
+
strokeWidth: 2,
|
|
114
|
+
filter: glowFilter
|
|
115
|
+
},
|
|
116
|
+
activeDot: {
|
|
117
|
+
r: 7,
|
|
118
|
+
fill: lineColor,
|
|
119
|
+
stroke: "#fff",
|
|
120
|
+
strokeWidth: 2,
|
|
121
|
+
filter: glowFilter
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
]
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
exports.OneRepMaxChart = OneRepMaxChart;
|
|
130
|
+
exports.default = OneRepMaxChart;
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","names":[],"sources":["../../../src/components/OneRepMaxChart/index.tsx"],"sourcesContent":["import type { CSSProperties } from 'react';\r\nimport {\r\n LineChart,\r\n Line,\r\n XAxis,\r\n YAxis,\r\n Tooltip,\r\n ResponsiveContainer,\r\n type TooltipProps,\r\n} from 'recharts';\r\n\r\nexport interface OneRepMaxDataPoint {\r\n /** Date string displayed on the X axis (e.g. \"Jan 1\" or \"2026-01-15\"). */\r\n date: string;\r\n /** Estimated or actual 1RM weight value. */\r\n weight: number;\r\n}\r\n\r\nexport interface OneRepMaxChartProps {\r\n /** Array of data points to plot. */\r\n data: OneRepMaxDataPoint[];\r\n /** Stroke / dot colour for the line (default: `'#6C63FF'`). */\r\n lineColor?: string;\r\n /** Weight unit shown in the tooltip (default: `'lbs'`). */\r\n unit?: 'lbs' | 'kg';\r\n /** Width of the responsive container (default: `'100%'`). */\r\n width?: string | number;\r\n /** Height of the responsive container (default: `300`). */\r\n height?: number;\r\n /** Optional inline styles for the outer wrapper. */\r\n style?: CSSProperties;\r\n /** Optional CSS class name for the outer wrapper. */\r\n className?: string;\r\n}\r\n\r\n/* ------------------------------------------------------------------ */\r\n/* Custom dark-mode tooltip */\r\n/* ------------------------------------------------------------------ */\r\n\r\nconst tooltipWrapperStyle: CSSProperties = {\r\n backgroundColor: '#1e1e2f',\r\n border: '1px solid #3a3a5c',\r\n borderRadius: 8,\r\n padding: '8px 12px',\r\n color: '#fff',\r\n fontSize: 13,\r\n lineHeight: 1.4,\r\n};\r\n\r\nfunction OneRepMaxTooltip({\r\n active,\r\n payload,\r\n label,\r\n unit,\r\n}: TooltipProps<number, string> & { unit: string }) {\r\n if (!active || !payload?.length) return null;\r\n return (\r\n <div style={tooltipWrapperStyle}>\r\n <p style={{ margin: 0, fontWeight: 600 }}>{label}</p>\r\n <p style={{ margin: 0, opacity: 0.85 }}>\r\n {payload[0].value} {unit}\r\n </p>\r\n </div>\r\n );\r\n}\r\n\r\n/* ------------------------------------------------------------------ */\r\n/* Empty-state fallback */\r\n/* ------------------------------------------------------------------ */\r\n\r\nconst emptyStyle: CSSProperties = {\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n color: '#888',\r\n fontSize: 14,\r\n height: 200,\r\n};\r\n\r\n/* ------------------------------------------------------------------ */\r\n/* Component */\r\n/* ------------------------------------------------------------------ */\r\n\r\n/**\r\n * A sleek `<LineChart>` for tracking estimated one-rep-max progress\r\n * over time. Inspired by the Hevy app's performance charts.\r\n */\r\nexport function OneRepMaxChart({\r\n data,\r\n lineColor = '#6C63FF',\r\n unit = 'lbs',\r\n width = '100%',\r\n height = 300,\r\n style,\r\n className,\r\n}: OneRepMaxChartProps) {\r\n if (data.length === 0) {\r\n return (\r\n <div className={className} style={{ ...emptyStyle, ...style }}>\r\n No 1RM data available\r\n </div>\r\n );\r\n }\r\n\r\n const glowFilter = `drop-shadow(0 0 6px ${lineColor})`;\r\n\r\n return (\r\n <div className={className} style={{ width, ...style }}>\r\n <ResponsiveContainer width=\"100%\" height={height}>\r\n <LineChart data={data} margin={{ top: 10, right: 20, bottom: 0, left: 0 }}>\r\n <XAxis\r\n dataKey=\"date\"\r\n axisLine={false}\r\n tickLine={false}\r\n tick={{ fontSize: 11, fill: '#aaa' }}\r\n />\r\n <YAxis\r\n axisLine={false}\r\n tickLine={false}\r\n tick={{ fontSize: 11, fill: '#aaa' }}\r\n domain={['dataMin - 10', 'dataMax + 10']}\r\n />\r\n <Tooltip\r\n content={<OneRepMaxTooltip unit={unit} />}\r\n cursor={{ stroke: '#555', strokeDasharray: '4 4' }}\r\n />\r\n <Line\r\n type=\"monotone\"\r\n dataKey=\"weight\"\r\n stroke={lineColor}\r\n strokeWidth={2.5}\r\n dot={{\r\n r: 4,\r\n fill: lineColor,\r\n stroke: '#fff',\r\n strokeWidth: 2,\r\n filter: glowFilter,\r\n }}\r\n activeDot={{\r\n r: 7,\r\n fill: lineColor,\r\n stroke: '#fff',\r\n strokeWidth: 2,\r\n filter: glowFilter,\r\n }}\r\n />\r\n </LineChart>\r\n </ResponsiveContainer>\r\n </div>\r\n );\r\n}\r\n\r\nexport default OneRepMaxChart;\r\n"],"mappings":";;;;;;AAuCA,IAAM,sBAAqC;CACzC,iBAAiB;CACjB,QAAQ;CACR,cAAc;CACd,SAAS;CACT,OAAO;CACP,UAAU;CACV,YAAY;CACb;AAED,SAAS,iBAAiB,EACxB,QACA,SACA,OACA,QACkD;AAClD,KAAI,CAAC,UAAU,CAAC,SAAS,OAAQ,QAAO;AACxC,QACE,iBAAA,GAAA,kBAAA,MAAC,OAAD;EAAK,OAAO;YAAZ,CACE,iBAAA,GAAA,kBAAA,KAAC,KAAD;GAAG,OAAO;IAAE,QAAQ;IAAG,YAAY;IAAK;aAAG;GAAU,CAAA,EACrD,iBAAA,GAAA,kBAAA,MAAC,KAAD;GAAG,OAAO;IAAE,QAAQ;IAAG,SAAS;IAAM;aAAtC;IACG,QAAQ,GAAG;IAAM;IAAE;IAClB;KACA;;;AAQV,IAAM,aAA4B;CAChC,SAAS;CACT,YAAY;CACZ,gBAAgB;CAChB,OAAO;CACP,UAAU;CACV,QAAQ;CACT;;;;;AAUD,SAAgB,eAAe,EAC7B,MACA,YAAY,WACZ,OAAO,OACP,QAAQ,QACR,SAAS,KACT,OACA,aACsB;AACtB,KAAI,KAAK,WAAW,EAClB,QACE,iBAAA,GAAA,kBAAA,KAAC,OAAD;EAAgB;EAAW,OAAO;GAAE,GAAG;GAAY,GAAG;GAAO;YAAE;EAEzD,CAAA;CAIV,MAAM,aAAa,uBAAuB,UAAU;AAEpD,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,WAAD;IAAiB;IAAM,QAAQ;KAAE,KAAK;KAAI,OAAO;KAAI,QAAQ;KAAG,MAAM;KAAG;cAAzE;KACE,iBAAA,GAAA,kBAAA,KAAC,SAAA,OAAD;MACE,SAAQ;MACR,UAAU;MACV,UAAU;MACV,MAAM;OAAE,UAAU;OAAI,MAAM;OAAQ;MACpC,CAAA;KACF,iBAAA,GAAA,kBAAA,KAAC,SAAA,OAAD;MACE,UAAU;MACV,UAAU;MACV,MAAM;OAAE,UAAU;OAAI,MAAM;OAAQ;MACpC,QAAQ,CAAC,gBAAgB,eAAe;MACxC,CAAA;KACF,iBAAA,GAAA,kBAAA,KAAC,SAAA,SAAD;MACE,SAAS,iBAAA,GAAA,kBAAA,KAAC,kBAAD,EAAwB,MAAQ,CAAA;MACzC,QAAQ;OAAE,QAAQ;OAAQ,iBAAiB;OAAO;MAClD,CAAA;KACF,iBAAA,GAAA,kBAAA,KAAC,SAAA,MAAD;MACE,MAAK;MACL,SAAQ;MACR,QAAQ;MACR,aAAa;MACb,KAAK;OACH,GAAG;OACH,MAAM;OACN,QAAQ;OACR,aAAa;OACb,QAAQ;OACT;MACD,WAAW;OACT,GAAG;OACH,MAAM;OACN,QAAQ;OACR,aAAa;OACb,QAAQ;OACT;MACD,CAAA;KACQ;;GACQ,CAAA;EAClB,CAAA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { CSSProperties } from 'react';
|
|
2
|
+
export interface OneRepMaxDataPoint {
|
|
3
|
+
/** Date string displayed on the X axis (e.g. "Jan 1" or "2026-01-15"). */
|
|
4
|
+
date: string;
|
|
5
|
+
/** Estimated or actual 1RM weight value. */
|
|
6
|
+
weight: number;
|
|
7
|
+
}
|
|
8
|
+
export interface OneRepMaxChartProps {
|
|
9
|
+
/** Array of data points to plot. */
|
|
10
|
+
data: OneRepMaxDataPoint[];
|
|
11
|
+
/** Stroke / dot colour for the line (default: `'#6C63FF'`). */
|
|
12
|
+
lineColor?: string;
|
|
13
|
+
/** Weight unit shown in the tooltip (default: `'lbs'`). */
|
|
14
|
+
unit?: 'lbs' | 'kg';
|
|
15
|
+
/** Width of the responsive container (default: `'100%'`). */
|
|
16
|
+
width?: string | number;
|
|
17
|
+
/** Height of the responsive container (default: `300`). */
|
|
18
|
+
height?: number;
|
|
19
|
+
/** Optional inline styles for the outer wrapper. */
|
|
20
|
+
style?: CSSProperties;
|
|
21
|
+
/** Optional CSS class name for the outer wrapper. */
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A sleek `<LineChart>` for tracking estimated one-rep-max progress
|
|
26
|
+
* over time. Inspired by the Hevy app's performance charts.
|
|
27
|
+
*/
|
|
28
|
+
export declare function OneRepMaxChart({ data, lineColor, unit, width, height, style, className, }: OneRepMaxChartProps): import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
export default OneRepMaxChart;
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/OneRepMaxChart/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAW3C,MAAM,WAAW,kBAAkB;IACjC,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC3B,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkDD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,SAAqB,EACrB,IAAY,EACZ,KAAc,EACd,MAAY,EACZ,KAAK,EACL,SAAS,GACV,EAAE,mBAAmB,2CAuDrB;AAED,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
|
|
3
|
+
var tooltipWrapperStyle = {
|
|
4
|
+
backgroundColor: "#1e1e2f",
|
|
5
|
+
border: "1px solid #3a3a5c",
|
|
6
|
+
borderRadius: 8,
|
|
7
|
+
padding: "8px 12px",
|
|
8
|
+
color: "#fff",
|
|
9
|
+
fontSize: 13,
|
|
10
|
+
lineHeight: 1.4
|
|
11
|
+
};
|
|
12
|
+
function OneRepMaxTooltip({ active, payload, label, unit }) {
|
|
13
|
+
if (!active || !payload?.length) return null;
|
|
14
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
15
|
+
style: tooltipWrapperStyle,
|
|
16
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
17
|
+
style: {
|
|
18
|
+
margin: 0,
|
|
19
|
+
fontWeight: 600
|
|
20
|
+
},
|
|
21
|
+
children: label
|
|
22
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
23
|
+
style: {
|
|
24
|
+
margin: 0,
|
|
25
|
+
opacity: .85
|
|
26
|
+
},
|
|
27
|
+
children: [
|
|
28
|
+
payload[0].value,
|
|
29
|
+
" ",
|
|
30
|
+
unit
|
|
31
|
+
]
|
|
32
|
+
})]
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
var emptyStyle = {
|
|
36
|
+
display: "flex",
|
|
37
|
+
alignItems: "center",
|
|
38
|
+
justifyContent: "center",
|
|
39
|
+
color: "#888",
|
|
40
|
+
fontSize: 14,
|
|
41
|
+
height: 200
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* A sleek `<LineChart>` for tracking estimated one-rep-max progress
|
|
45
|
+
* over time. Inspired by the Hevy app's performance charts.
|
|
46
|
+
*/
|
|
47
|
+
function OneRepMaxChart({ data, lineColor = "#6C63FF", unit = "lbs", width = "100%", height = 300, style, className }) {
|
|
48
|
+
if (data.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
49
|
+
className,
|
|
50
|
+
style: {
|
|
51
|
+
...emptyStyle,
|
|
52
|
+
...style
|
|
53
|
+
},
|
|
54
|
+
children: "No 1RM data available"
|
|
55
|
+
});
|
|
56
|
+
const glowFilter = `drop-shadow(0 0 6px ${lineColor})`;
|
|
57
|
+
return /* @__PURE__ */ jsx("div", {
|
|
58
|
+
className,
|
|
59
|
+
style: {
|
|
60
|
+
width,
|
|
61
|
+
...style
|
|
62
|
+
},
|
|
63
|
+
children: /* @__PURE__ */ jsx(ResponsiveContainer, {
|
|
64
|
+
width: "100%",
|
|
65
|
+
height,
|
|
66
|
+
children: /* @__PURE__ */ jsxs(LineChart, {
|
|
67
|
+
data,
|
|
68
|
+
margin: {
|
|
69
|
+
top: 10,
|
|
70
|
+
right: 20,
|
|
71
|
+
bottom: 0,
|
|
72
|
+
left: 0
|
|
73
|
+
},
|
|
74
|
+
children: [
|
|
75
|
+
/* @__PURE__ */ jsx(XAxis, {
|
|
76
|
+
dataKey: "date",
|
|
77
|
+
axisLine: false,
|
|
78
|
+
tickLine: false,
|
|
79
|
+
tick: {
|
|
80
|
+
fontSize: 11,
|
|
81
|
+
fill: "#aaa"
|
|
82
|
+
}
|
|
83
|
+
}),
|
|
84
|
+
/* @__PURE__ */ jsx(YAxis, {
|
|
85
|
+
axisLine: false,
|
|
86
|
+
tickLine: false,
|
|
87
|
+
tick: {
|
|
88
|
+
fontSize: 11,
|
|
89
|
+
fill: "#aaa"
|
|
90
|
+
},
|
|
91
|
+
domain: ["dataMin - 10", "dataMax + 10"]
|
|
92
|
+
}),
|
|
93
|
+
/* @__PURE__ */ jsx(Tooltip, {
|
|
94
|
+
content: /* @__PURE__ */ jsx(OneRepMaxTooltip, { unit }),
|
|
95
|
+
cursor: {
|
|
96
|
+
stroke: "#555",
|
|
97
|
+
strokeDasharray: "4 4"
|
|
98
|
+
}
|
|
99
|
+
}),
|
|
100
|
+
/* @__PURE__ */ jsx(Line, {
|
|
101
|
+
type: "monotone",
|
|
102
|
+
dataKey: "weight",
|
|
103
|
+
stroke: lineColor,
|
|
104
|
+
strokeWidth: 2.5,
|
|
105
|
+
dot: {
|
|
106
|
+
r: 4,
|
|
107
|
+
fill: lineColor,
|
|
108
|
+
stroke: "#fff",
|
|
109
|
+
strokeWidth: 2,
|
|
110
|
+
filter: glowFilter
|
|
111
|
+
},
|
|
112
|
+
activeDot: {
|
|
113
|
+
r: 7,
|
|
114
|
+
fill: lineColor,
|
|
115
|
+
stroke: "#fff",
|
|
116
|
+
strokeWidth: 2,
|
|
117
|
+
filter: glowFilter
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
]
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
export { OneRepMaxChart, OneRepMaxChart as default };
|
|
126
|
+
|
|
127
|
+
//# sourceMappingURL=index.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.es.js","names":[],"sources":["../../../src/components/OneRepMaxChart/index.tsx"],"sourcesContent":["import type { CSSProperties } from 'react';\r\nimport {\r\n LineChart,\r\n Line,\r\n XAxis,\r\n YAxis,\r\n Tooltip,\r\n ResponsiveContainer,\r\n type TooltipProps,\r\n} from 'recharts';\r\n\r\nexport interface OneRepMaxDataPoint {\r\n /** Date string displayed on the X axis (e.g. \"Jan 1\" or \"2026-01-15\"). */\r\n date: string;\r\n /** Estimated or actual 1RM weight value. */\r\n weight: number;\r\n}\r\n\r\nexport interface OneRepMaxChartProps {\r\n /** Array of data points to plot. */\r\n data: OneRepMaxDataPoint[];\r\n /** Stroke / dot colour for the line (default: `'#6C63FF'`). */\r\n lineColor?: string;\r\n /** Weight unit shown in the tooltip (default: `'lbs'`). */\r\n unit?: 'lbs' | 'kg';\r\n /** Width of the responsive container (default: `'100%'`). */\r\n width?: string | number;\r\n /** Height of the responsive container (default: `300`). */\r\n height?: number;\r\n /** Optional inline styles for the outer wrapper. */\r\n style?: CSSProperties;\r\n /** Optional CSS class name for the outer wrapper. */\r\n className?: string;\r\n}\r\n\r\n/* ------------------------------------------------------------------ */\r\n/* Custom dark-mode tooltip */\r\n/* ------------------------------------------------------------------ */\r\n\r\nconst tooltipWrapperStyle: CSSProperties = {\r\n backgroundColor: '#1e1e2f',\r\n border: '1px solid #3a3a5c',\r\n borderRadius: 8,\r\n padding: '8px 12px',\r\n color: '#fff',\r\n fontSize: 13,\r\n lineHeight: 1.4,\r\n};\r\n\r\nfunction OneRepMaxTooltip({\r\n active,\r\n payload,\r\n label,\r\n unit,\r\n}: TooltipProps<number, string> & { unit: string }) {\r\n if (!active || !payload?.length) return null;\r\n return (\r\n <div style={tooltipWrapperStyle}>\r\n <p style={{ margin: 0, fontWeight: 600 }}>{label}</p>\r\n <p style={{ margin: 0, opacity: 0.85 }}>\r\n {payload[0].value} {unit}\r\n </p>\r\n </div>\r\n );\r\n}\r\n\r\n/* ------------------------------------------------------------------ */\r\n/* Empty-state fallback */\r\n/* ------------------------------------------------------------------ */\r\n\r\nconst emptyStyle: CSSProperties = {\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n color: '#888',\r\n fontSize: 14,\r\n height: 200,\r\n};\r\n\r\n/* ------------------------------------------------------------------ */\r\n/* Component */\r\n/* ------------------------------------------------------------------ */\r\n\r\n/**\r\n * A sleek `<LineChart>` for tracking estimated one-rep-max progress\r\n * over time. Inspired by the Hevy app's performance charts.\r\n */\r\nexport function OneRepMaxChart({\r\n data,\r\n lineColor = '#6C63FF',\r\n unit = 'lbs',\r\n width = '100%',\r\n height = 300,\r\n style,\r\n className,\r\n}: OneRepMaxChartProps) {\r\n if (data.length === 0) {\r\n return (\r\n <div className={className} style={{ ...emptyStyle, ...style }}>\r\n No 1RM data available\r\n </div>\r\n );\r\n }\r\n\r\n const glowFilter = `drop-shadow(0 0 6px ${lineColor})`;\r\n\r\n return (\r\n <div className={className} style={{ width, ...style }}>\r\n <ResponsiveContainer width=\"100%\" height={height}>\r\n <LineChart data={data} margin={{ top: 10, right: 20, bottom: 0, left: 0 }}>\r\n <XAxis\r\n dataKey=\"date\"\r\n axisLine={false}\r\n tickLine={false}\r\n tick={{ fontSize: 11, fill: '#aaa' }}\r\n />\r\n <YAxis\r\n axisLine={false}\r\n tickLine={false}\r\n tick={{ fontSize: 11, fill: '#aaa' }}\r\n domain={['dataMin - 10', 'dataMax + 10']}\r\n />\r\n <Tooltip\r\n content={<OneRepMaxTooltip unit={unit} />}\r\n cursor={{ stroke: '#555', strokeDasharray: '4 4' }}\r\n />\r\n <Line\r\n type=\"monotone\"\r\n dataKey=\"weight\"\r\n stroke={lineColor}\r\n strokeWidth={2.5}\r\n dot={{\r\n r: 4,\r\n fill: lineColor,\r\n stroke: '#fff',\r\n strokeWidth: 2,\r\n filter: glowFilter,\r\n }}\r\n activeDot={{\r\n r: 7,\r\n fill: lineColor,\r\n stroke: '#fff',\r\n strokeWidth: 2,\r\n filter: glowFilter,\r\n }}\r\n />\r\n </LineChart>\r\n </ResponsiveContainer>\r\n </div>\r\n );\r\n}\r\n\r\nexport default OneRepMaxChart;\r\n"],"mappings":";;AAuCA,IAAM,sBAAqC;CACzC,iBAAiB;CACjB,QAAQ;CACR,cAAc;CACd,SAAS;CACT,OAAO;CACP,UAAU;CACV,YAAY;CACb;AAED,SAAS,iBAAiB,EACxB,QACA,SACA,OACA,QACkD;AAClD,KAAI,CAAC,UAAU,CAAC,SAAS,OAAQ,QAAO;AACxC,QACE,qBAAC,OAAD;EAAK,OAAO;YAAZ,CACE,oBAAC,KAAD;GAAG,OAAO;IAAE,QAAQ;IAAG,YAAY;IAAK;aAAG;GAAU,CAAA,EACrD,qBAAC,KAAD;GAAG,OAAO;IAAE,QAAQ;IAAG,SAAS;IAAM;aAAtC;IACG,QAAQ,GAAG;IAAM;IAAE;IAClB;KACA;;;AAQV,IAAM,aAA4B;CAChC,SAAS;CACT,YAAY;CACZ,gBAAgB;CAChB,OAAO;CACP,UAAU;CACV,QAAQ;CACT;;;;;AAUD,SAAgB,eAAe,EAC7B,MACA,YAAY,WACZ,OAAO,OACP,QAAQ,QACR,SAAS,KACT,OACA,aACsB;AACtB,KAAI,KAAK,WAAW,EAClB,QACE,oBAAC,OAAD;EAAgB;EAAW,OAAO;GAAE,GAAG;GAAY,GAAG;GAAO;YAAE;EAEzD,CAAA;CAIV,MAAM,aAAa,uBAAuB,UAAU;AAEpD,QACE,oBAAC,OAAD;EAAgB;EAAW,OAAO;GAAE;GAAO,GAAG;GAAO;YACnD,oBAAC,qBAAD;GAAqB,OAAM;GAAe;aACxC,qBAAC,WAAD;IAAiB;IAAM,QAAQ;KAAE,KAAK;KAAI,OAAO;KAAI,QAAQ;KAAG,MAAM;KAAG;cAAzE;KACE,oBAAC,OAAD;MACE,SAAQ;MACR,UAAU;MACV,UAAU;MACV,MAAM;OAAE,UAAU;OAAI,MAAM;OAAQ;MACpC,CAAA;KACF,oBAAC,OAAD;MACE,UAAU;MACV,UAAU;MACV,MAAM;OAAE,UAAU;OAAI,MAAM;OAAQ;MACpC,QAAQ,CAAC,gBAAgB,eAAe;MACxC,CAAA;KACF,oBAAC,SAAD;MACE,SAAS,oBAAC,kBAAD,EAAwB,MAAQ,CAAA;MACzC,QAAQ;OAAE,QAAQ;OAAQ,iBAAiB;OAAO;MAClD,CAAA;KACF,oBAAC,MAAD;MACE,MAAK;MACL,SAAQ;MACR,QAAQ;MACR,aAAa;MACb,KAAK;OACH,GAAG;OACH,MAAM;OACN,QAAQ;OACR,aAAa;OACb,QAAQ;OACT;MACD,WAAW;OACT,GAAG;OACH,MAAM;OACN,QAAQ;OACR,aAAa;OACb,QAAQ;OACT;MACD,CAAA;KACQ;;GACQ,CAAA;EAClB,CAAA"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
|
+
let recharts = require("recharts");
|
|
7
|
+
/** Formats large numbers with a "k" suffix (e.g. 12 500 → "12.5k"). */
|
|
8
|
+
function formatVolumeTick(value) {
|
|
9
|
+
if (value >= 1e3) {
|
|
10
|
+
const k = value / 1e3;
|
|
11
|
+
return `${Number.isInteger(k) ? k : k.toFixed(1)}k`;
|
|
12
|
+
}
|
|
13
|
+
return String(value);
|
|
14
|
+
}
|
|
15
|
+
var tooltipWrapperStyle = {
|
|
16
|
+
backgroundColor: "#1e1e2f",
|
|
17
|
+
border: "1px solid #3a3a5c",
|
|
18
|
+
borderRadius: 8,
|
|
19
|
+
padding: "8px 12px",
|
|
20
|
+
color: "#fff",
|
|
21
|
+
fontSize: 13,
|
|
22
|
+
lineHeight: 1.4
|
|
23
|
+
};
|
|
24
|
+
function VolumeTooltip({ active, payload, label, unit }) {
|
|
25
|
+
if (!active || !payload?.length) return null;
|
|
26
|
+
const vol = payload[0].value;
|
|
27
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
28
|
+
style: tooltipWrapperStyle,
|
|
29
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
30
|
+
style: {
|
|
31
|
+
margin: 0,
|
|
32
|
+
fontWeight: 600
|
|
33
|
+
},
|
|
34
|
+
children: label
|
|
35
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
36
|
+
style: {
|
|
37
|
+
margin: 0,
|
|
38
|
+
opacity: .85
|
|
39
|
+
},
|
|
40
|
+
children: [
|
|
41
|
+
vol.toLocaleString(),
|
|
42
|
+
" ",
|
|
43
|
+
unit
|
|
44
|
+
]
|
|
45
|
+
})]
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
var emptyStyle = {
|
|
49
|
+
display: "flex",
|
|
50
|
+
alignItems: "center",
|
|
51
|
+
justifyContent: "center",
|
|
52
|
+
color: "#888",
|
|
53
|
+
fontSize: 14,
|
|
54
|
+
height: 200
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* A Recharts `<AreaChart>` with a fading gradient fill for tracking
|
|
58
|
+
* training volume over time. Inspired by the Hevy app.
|
|
59
|
+
*/
|
|
60
|
+
function VolumeChart({ data, fillColor = "#00C49F", unit = "lbs", width = "100%", height = 300, style, className }) {
|
|
61
|
+
if (data.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
62
|
+
className,
|
|
63
|
+
style: {
|
|
64
|
+
...emptyStyle,
|
|
65
|
+
...style
|
|
66
|
+
},
|
|
67
|
+
children: "No volume data available"
|
|
68
|
+
});
|
|
69
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
70
|
+
className,
|
|
71
|
+
style: {
|
|
72
|
+
width,
|
|
73
|
+
...style
|
|
74
|
+
},
|
|
75
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.ResponsiveContainer, {
|
|
76
|
+
width: "100%",
|
|
77
|
+
height,
|
|
78
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(recharts.AreaChart, {
|
|
79
|
+
data,
|
|
80
|
+
margin: {
|
|
81
|
+
top: 10,
|
|
82
|
+
right: 20,
|
|
83
|
+
bottom: 0,
|
|
84
|
+
left: 0
|
|
85
|
+
},
|
|
86
|
+
children: [
|
|
87
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("linearGradient", {
|
|
88
|
+
id: "colorVolume",
|
|
89
|
+
x1: "0",
|
|
90
|
+
y1: "0",
|
|
91
|
+
x2: "0",
|
|
92
|
+
y2: "1",
|
|
93
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
|
|
94
|
+
offset: "5%",
|
|
95
|
+
stopColor: fillColor,
|
|
96
|
+
stopOpacity: .4
|
|
97
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
|
|
98
|
+
offset: "95%",
|
|
99
|
+
stopColor: fillColor,
|
|
100
|
+
stopOpacity: .02
|
|
101
|
+
})]
|
|
102
|
+
}) }),
|
|
103
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.XAxis, {
|
|
104
|
+
dataKey: "date",
|
|
105
|
+
axisLine: false,
|
|
106
|
+
tickLine: false,
|
|
107
|
+
tick: {
|
|
108
|
+
fontSize: 11,
|
|
109
|
+
fill: "#aaa"
|
|
110
|
+
}
|
|
111
|
+
}),
|
|
112
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.YAxis, {
|
|
113
|
+
axisLine: false,
|
|
114
|
+
tickLine: false,
|
|
115
|
+
tick: {
|
|
116
|
+
fontSize: 11,
|
|
117
|
+
fill: "#aaa"
|
|
118
|
+
},
|
|
119
|
+
tickFormatter: formatVolumeTick
|
|
120
|
+
}),
|
|
121
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.Tooltip, {
|
|
122
|
+
content: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VolumeTooltip, { unit }),
|
|
123
|
+
cursor: {
|
|
124
|
+
stroke: "#555",
|
|
125
|
+
strokeDasharray: "4 4"
|
|
126
|
+
}
|
|
127
|
+
}),
|
|
128
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.Area, {
|
|
129
|
+
type: "monotone",
|
|
130
|
+
dataKey: "volume",
|
|
131
|
+
stroke: fillColor,
|
|
132
|
+
strokeWidth: 2,
|
|
133
|
+
fill: "url(#colorVolume)",
|
|
134
|
+
dot: {
|
|
135
|
+
r: 3,
|
|
136
|
+
fill: fillColor,
|
|
137
|
+
stroke: "#fff",
|
|
138
|
+
strokeWidth: 1.5
|
|
139
|
+
},
|
|
140
|
+
activeDot: {
|
|
141
|
+
r: 6,
|
|
142
|
+
fill: fillColor,
|
|
143
|
+
stroke: "#fff",
|
|
144
|
+
strokeWidth: 2
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
]
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
exports.VolumeChart = VolumeChart;
|
|
153
|
+
exports.default = VolumeChart;
|
|
154
|
+
|
|
155
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","names":[],"sources":["../../../src/components/VolumeChart/index.tsx"],"sourcesContent":["import type { CSSProperties } from 'react';\r\nimport {\r\n AreaChart,\r\n Area,\r\n XAxis,\r\n YAxis,\r\n Tooltip,\r\n ResponsiveContainer,\r\n type TooltipProps,\r\n} from 'recharts';\r\n\r\nexport interface VolumeDataPoint {\r\n /** Date string displayed on the X axis. */\r\n date: string;\r\n /** Total volume (sets × reps × weight). */\r\n volume: number;\r\n}\r\n\r\nexport interface VolumeChartProps {\r\n /** Array of data points to plot. */\r\n data: VolumeDataPoint[];\r\n /** Primary fill / stroke colour for the area (default: `'#00C49F'`). */\r\n fillColor?: string;\r\n /** Weight unit shown in the tooltip (default: `'lbs'`). */\r\n unit?: 'lbs' | 'kg';\r\n /** Width of the responsive container (default: `'100%'`). */\r\n width?: string | number;\r\n /** Height of the responsive container (default: `300`). */\r\n height?: number;\r\n /** Optional inline styles for the outer wrapper. */\r\n style?: CSSProperties;\r\n /** Optional CSS class name for the outer wrapper. */\r\n className?: string;\r\n}\r\n\r\n/* ------------------------------------------------------------------ */\r\n/* Helpers */\r\n/* ------------------------------------------------------------------ */\r\n\r\n/** Formats large numbers with a \"k\" suffix (e.g. 12 500 → \"12.5k\"). */\r\nfunction formatVolumeTick(value: number): string {\r\n if (value >= 1000) {\r\n const k = value / 1000;\r\n // Drop the decimal when it's a whole number (e.g. 10k not 10.0k)\r\n return `${Number.isInteger(k) ? k : k.toFixed(1)}k`;\r\n }\r\n return String(value);\r\n}\r\n\r\n/* ------------------------------------------------------------------ */\r\n/* Custom dark-mode tooltip */\r\n/* ------------------------------------------------------------------ */\r\n\r\nconst tooltipWrapperStyle: CSSProperties = {\r\n backgroundColor: '#1e1e2f',\r\n border: '1px solid #3a3a5c',\r\n borderRadius: 8,\r\n padding: '8px 12px',\r\n color: '#fff',\r\n fontSize: 13,\r\n lineHeight: 1.4,\r\n};\r\n\r\nfunction VolumeTooltip({\r\n active,\r\n payload,\r\n label,\r\n unit,\r\n}: TooltipProps<number, string> & { unit: string }) {\r\n if (!active || !payload?.length) return null;\r\n const vol = payload[0].value as number;\r\n return (\r\n <div style={tooltipWrapperStyle}>\r\n <p style={{ margin: 0, fontWeight: 600 }}>{label}</p>\r\n <p style={{ margin: 0, opacity: 0.85 }}>\r\n {vol.toLocaleString()} {unit}\r\n </p>\r\n </div>\r\n );\r\n}\r\n\r\n/* ------------------------------------------------------------------ */\r\n/* Empty-state fallback */\r\n/* ------------------------------------------------------------------ */\r\n\r\nconst emptyStyle: CSSProperties = {\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n color: '#888',\r\n fontSize: 14,\r\n height: 200,\r\n};\r\n\r\n/* ------------------------------------------------------------------ */\r\n/* Component */\r\n/* ------------------------------------------------------------------ */\r\n\r\n/**\r\n * A Recharts `<AreaChart>` with a fading gradient fill for tracking\r\n * training volume over time. Inspired by the Hevy app.\r\n */\r\nexport function VolumeChart({\r\n data,\r\n fillColor = '#00C49F',\r\n unit = 'lbs',\r\n width = '100%',\r\n height = 300,\r\n style,\r\n className,\r\n}: VolumeChartProps) {\r\n if (data.length === 0) {\r\n return (\r\n <div className={className} style={{ ...emptyStyle, ...style }}>\r\n No volume data available\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div className={className} style={{ width, ...style }}>\r\n <ResponsiveContainer width=\"100%\" height={height}>\r\n <AreaChart data={data} margin={{ top: 10, right: 20, bottom: 0, left: 0 }}>\r\n <defs>\r\n <linearGradient id=\"colorVolume\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\r\n <stop offset=\"5%\" stopColor={fillColor} stopOpacity={0.4} />\r\n <stop offset=\"95%\" stopColor={fillColor} stopOpacity={0.02} />\r\n </linearGradient>\r\n </defs>\r\n\r\n <XAxis\r\n dataKey=\"date\"\r\n axisLine={false}\r\n tickLine={false}\r\n tick={{ fontSize: 11, fill: '#aaa' }}\r\n />\r\n <YAxis\r\n axisLine={false}\r\n tickLine={false}\r\n tick={{ fontSize: 11, fill: '#aaa' }}\r\n tickFormatter={formatVolumeTick}\r\n />\r\n <Tooltip\r\n content={<VolumeTooltip unit={unit} />}\r\n cursor={{ stroke: '#555', strokeDasharray: '4 4' }}\r\n />\r\n <Area\r\n type=\"monotone\"\r\n dataKey=\"volume\"\r\n stroke={fillColor}\r\n strokeWidth={2}\r\n fill=\"url(#colorVolume)\"\r\n dot={{ r: 3, fill: fillColor, stroke: '#fff', strokeWidth: 1.5 }}\r\n activeDot={{ r: 6, fill: fillColor, stroke: '#fff', strokeWidth: 2 }}\r\n />\r\n </AreaChart>\r\n </ResponsiveContainer>\r\n </div>\r\n );\r\n}\r\n\r\nexport default VolumeChart;\r\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { CSSProperties } from 'react';
|
|
2
|
+
export interface VolumeDataPoint {
|
|
3
|
+
/** Date string displayed on the X axis. */
|
|
4
|
+
date: string;
|
|
5
|
+
/** Total volume (sets × reps × weight). */
|
|
6
|
+
volume: number;
|
|
7
|
+
}
|
|
8
|
+
export interface VolumeChartProps {
|
|
9
|
+
/** Array of data points to plot. */
|
|
10
|
+
data: VolumeDataPoint[];
|
|
11
|
+
/** Primary fill / stroke colour for the area (default: `'#00C49F'`). */
|
|
12
|
+
fillColor?: string;
|
|
13
|
+
/** Weight unit shown in the tooltip (default: `'lbs'`). */
|
|
14
|
+
unit?: 'lbs' | 'kg';
|
|
15
|
+
/** Width of the responsive container (default: `'100%'`). */
|
|
16
|
+
width?: string | number;
|
|
17
|
+
/** Height of the responsive container (default: `300`). */
|
|
18
|
+
height?: number;
|
|
19
|
+
/** Optional inline styles for the outer wrapper. */
|
|
20
|
+
style?: CSSProperties;
|
|
21
|
+
/** Optional CSS class name for the outer wrapper. */
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A Recharts `<AreaChart>` with a fading gradient fill for tracking
|
|
26
|
+
* training volume over time. Inspired by the Hevy app.
|
|
27
|
+
*/
|
|
28
|
+
export declare function VolumeChart({ data, fillColor, unit, width, height, style, className, }: VolumeChartProps): import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
export default VolumeChart;
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/VolumeChart/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAW3C,MAAM,WAAW,eAAe;IAC9B,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,IAAI,EAAE,eAAe,EAAE,CAAC;IACxB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAiED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,SAAqB,EACrB,IAAY,EACZ,KAAc,EACd,MAAY,EACZ,KAAK,EACL,SAAS,GACV,EAAE,gBAAgB,2CAiDlB;AAED,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Area, AreaChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
|
|
3
|
+
/** Formats large numbers with a "k" suffix (e.g. 12 500 → "12.5k"). */
|
|
4
|
+
function formatVolumeTick(value) {
|
|
5
|
+
if (value >= 1e3) {
|
|
6
|
+
const k = value / 1e3;
|
|
7
|
+
return `${Number.isInteger(k) ? k : k.toFixed(1)}k`;
|
|
8
|
+
}
|
|
9
|
+
return String(value);
|
|
10
|
+
}
|
|
11
|
+
var tooltipWrapperStyle = {
|
|
12
|
+
backgroundColor: "#1e1e2f",
|
|
13
|
+
border: "1px solid #3a3a5c",
|
|
14
|
+
borderRadius: 8,
|
|
15
|
+
padding: "8px 12px",
|
|
16
|
+
color: "#fff",
|
|
17
|
+
fontSize: 13,
|
|
18
|
+
lineHeight: 1.4
|
|
19
|
+
};
|
|
20
|
+
function VolumeTooltip({ active, payload, label, unit }) {
|
|
21
|
+
if (!active || !payload?.length) return null;
|
|
22
|
+
const vol = payload[0].value;
|
|
23
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
24
|
+
style: tooltipWrapperStyle,
|
|
25
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
26
|
+
style: {
|
|
27
|
+
margin: 0,
|
|
28
|
+
fontWeight: 600
|
|
29
|
+
},
|
|
30
|
+
children: label
|
|
31
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
32
|
+
style: {
|
|
33
|
+
margin: 0,
|
|
34
|
+
opacity: .85
|
|
35
|
+
},
|
|
36
|
+
children: [
|
|
37
|
+
vol.toLocaleString(),
|
|
38
|
+
" ",
|
|
39
|
+
unit
|
|
40
|
+
]
|
|
41
|
+
})]
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
var emptyStyle = {
|
|
45
|
+
display: "flex",
|
|
46
|
+
alignItems: "center",
|
|
47
|
+
justifyContent: "center",
|
|
48
|
+
color: "#888",
|
|
49
|
+
fontSize: 14,
|
|
50
|
+
height: 200
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* A Recharts `<AreaChart>` with a fading gradient fill for tracking
|
|
54
|
+
* training volume over time. Inspired by the Hevy app.
|
|
55
|
+
*/
|
|
56
|
+
function VolumeChart({ data, fillColor = "#00C49F", unit = "lbs", width = "100%", height = 300, style, className }) {
|
|
57
|
+
if (data.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
58
|
+
className,
|
|
59
|
+
style: {
|
|
60
|
+
...emptyStyle,
|
|
61
|
+
...style
|
|
62
|
+
},
|
|
63
|
+
children: "No volume data available"
|
|
64
|
+
});
|
|
65
|
+
return /* @__PURE__ */ jsx("div", {
|
|
66
|
+
className,
|
|
67
|
+
style: {
|
|
68
|
+
width,
|
|
69
|
+
...style
|
|
70
|
+
},
|
|
71
|
+
children: /* @__PURE__ */ jsx(ResponsiveContainer, {
|
|
72
|
+
width: "100%",
|
|
73
|
+
height,
|
|
74
|
+
children: /* @__PURE__ */ jsxs(AreaChart, {
|
|
75
|
+
data,
|
|
76
|
+
margin: {
|
|
77
|
+
top: 10,
|
|
78
|
+
right: 20,
|
|
79
|
+
bottom: 0,
|
|
80
|
+
left: 0
|
|
81
|
+
},
|
|
82
|
+
children: [
|
|
83
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", {
|
|
84
|
+
id: "colorVolume",
|
|
85
|
+
x1: "0",
|
|
86
|
+
y1: "0",
|
|
87
|
+
x2: "0",
|
|
88
|
+
y2: "1",
|
|
89
|
+
children: [/* @__PURE__ */ jsx("stop", {
|
|
90
|
+
offset: "5%",
|
|
91
|
+
stopColor: fillColor,
|
|
92
|
+
stopOpacity: .4
|
|
93
|
+
}), /* @__PURE__ */ jsx("stop", {
|
|
94
|
+
offset: "95%",
|
|
95
|
+
stopColor: fillColor,
|
|
96
|
+
stopOpacity: .02
|
|
97
|
+
})]
|
|
98
|
+
}) }),
|
|
99
|
+
/* @__PURE__ */ jsx(XAxis, {
|
|
100
|
+
dataKey: "date",
|
|
101
|
+
axisLine: false,
|
|
102
|
+
tickLine: false,
|
|
103
|
+
tick: {
|
|
104
|
+
fontSize: 11,
|
|
105
|
+
fill: "#aaa"
|
|
106
|
+
}
|
|
107
|
+
}),
|
|
108
|
+
/* @__PURE__ */ jsx(YAxis, {
|
|
109
|
+
axisLine: false,
|
|
110
|
+
tickLine: false,
|
|
111
|
+
tick: {
|
|
112
|
+
fontSize: 11,
|
|
113
|
+
fill: "#aaa"
|
|
114
|
+
},
|
|
115
|
+
tickFormatter: formatVolumeTick
|
|
116
|
+
}),
|
|
117
|
+
/* @__PURE__ */ jsx(Tooltip, {
|
|
118
|
+
content: /* @__PURE__ */ jsx(VolumeTooltip, { unit }),
|
|
119
|
+
cursor: {
|
|
120
|
+
stroke: "#555",
|
|
121
|
+
strokeDasharray: "4 4"
|
|
122
|
+
}
|
|
123
|
+
}),
|
|
124
|
+
/* @__PURE__ */ jsx(Area, {
|
|
125
|
+
type: "monotone",
|
|
126
|
+
dataKey: "volume",
|
|
127
|
+
stroke: fillColor,
|
|
128
|
+
strokeWidth: 2,
|
|
129
|
+
fill: "url(#colorVolume)",
|
|
130
|
+
dot: {
|
|
131
|
+
r: 3,
|
|
132
|
+
fill: fillColor,
|
|
133
|
+
stroke: "#fff",
|
|
134
|
+
strokeWidth: 1.5
|
|
135
|
+
},
|
|
136
|
+
activeDot: {
|
|
137
|
+
r: 6,
|
|
138
|
+
fill: fillColor,
|
|
139
|
+
stroke: "#fff",
|
|
140
|
+
strokeWidth: 2
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
]
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
export { VolumeChart, VolumeChart as default };
|
|
149
|
+
|
|
150
|
+
//# sourceMappingURL=index.es.js.map
|