tekivex-ui 2.3.0 → 2.5.2
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 +436 -83
- package/dist/TkxForm-BWK4LqY3.js +1461 -0
- package/dist/TkxForm-ljQjX7KD.cjs +15 -0
- package/dist/charts.cjs +1 -0
- package/dist/charts.js +480 -0
- package/dist/headless.cjs +1 -0
- package/dist/headless.js +258 -0
- package/dist/index-BINBzXuY.cjs +2 -0
- package/dist/index-eT_U4qB2.js +512 -0
- package/dist/index.cjs +20 -32
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7942 -8903
- package/dist/src/charts/TkxAreaChart.d.ts +29 -0
- package/dist/src/charts/TkxAreaChart.d.ts.map +1 -0
- package/dist/src/charts/TkxBarChart.d.ts +28 -0
- package/dist/src/charts/TkxBarChart.d.ts.map +1 -0
- package/dist/src/charts/TkxDonutChart.d.ts +23 -0
- package/dist/src/charts/TkxDonutChart.d.ts.map +1 -0
- package/dist/src/charts/TkxLineChart.d.ts +35 -0
- package/dist/src/charts/TkxLineChart.d.ts.map +1 -0
- package/dist/src/charts/TkxPieChart.d.ts +19 -0
- package/dist/src/charts/TkxPieChart.d.ts.map +1 -0
- package/dist/src/charts/TkxRadarChart.d.ts +20 -0
- package/dist/src/charts/TkxRadarChart.d.ts.map +1 -0
- package/dist/src/charts/TkxScatterChart.d.ts +28 -0
- package/dist/src/charts/TkxScatterChart.d.ts.map +1 -0
- package/dist/src/charts/index.d.ts +15 -0
- package/dist/src/charts/index.d.ts.map +1 -0
- package/dist/src/charts/shared.d.ts +33 -0
- package/dist/src/charts/shared.d.ts.map +1 -0
- package/dist/src/components/TkxDataGrid.d.ts +16 -2
- package/dist/src/components/TkxDataGrid.d.ts.map +1 -1
- package/dist/src/components/TkxForm.d.ts +33 -3
- package/dist/src/components/TkxForm.d.ts.map +1 -1
- package/dist/src/components/TkxImage.d.ts.map +1 -1
- package/dist/src/components/TkxTypography.d.ts +1 -1
- package/dist/src/components/TkxTypography.d.ts.map +1 -1
- package/dist/src/engine/tkx.d.ts.map +1 -1
- package/dist/src/headless/index.d.ts +18 -0
- package/dist/src/headless/index.d.ts.map +1 -0
- package/dist/src/headless/useControllable.d.ts +20 -0
- package/dist/src/headless/useControllable.d.ts.map +1 -0
- package/dist/src/headless/useDebounce.d.ts +14 -0
- package/dist/src/headless/useDebounce.d.ts.map +1 -0
- package/dist/src/headless/useDisclosure.d.ts +20 -0
- package/dist/src/headless/useDisclosure.d.ts.map +1 -0
- package/dist/src/headless/useFormState.d.ts +44 -0
- package/dist/src/headless/useFormState.d.ts.map +1 -0
- package/dist/src/headless/useIntersectionObserver.d.ts +23 -0
- package/dist/src/headless/useIntersectionObserver.d.ts.map +1 -0
- package/dist/src/headless/useListSelection.d.ts +29 -0
- package/dist/src/headless/useListSelection.d.ts.map +1 -0
- package/dist/src/headless/useLocalStorage.d.ts +9 -0
- package/dist/src/headless/useLocalStorage.d.ts.map +1 -0
- package/dist/src/headless/useMediaQuery.d.ts +21 -0
- package/dist/src/headless/useMediaQuery.d.ts.map +1 -0
- package/dist/src/headless/useRovingTabIndex.d.ts +33 -0
- package/dist/src/headless/useRovingTabIndex.d.ts.map +1 -0
- package/dist/src/headless/useThrottle.d.ts +9 -0
- package/dist/src/headless/useThrottle.d.ts.map +1 -0
- package/dist/src/i18n/index.d.ts +47 -2
- package/dist/src/i18n/index.d.ts.map +1 -1
- package/dist/src/themes/index.d.ts.map +1 -1
- package/package.json +61 -7
- package/src/charts/TkxAreaChart.tsx +119 -0
- package/src/charts/TkxBarChart.tsx +115 -0
- package/src/charts/TkxDonutChart.tsx +145 -0
- package/src/charts/TkxLineChart.tsx +135 -0
- package/src/charts/TkxPieChart.tsx +82 -0
- package/src/charts/TkxRadarChart.tsx +90 -0
- package/src/charts/TkxScatterChart.tsx +102 -0
- package/src/charts/index.ts +20 -0
- package/src/charts/shared.ts +45 -0
- package/src/components/TkxDataGrid.tsx +532 -268
- package/src/components/TkxForm.tsx +67 -15
- package/src/components/TkxImage.tsx +1 -1
- package/src/components/TkxTypography.tsx +4 -2
- package/src/engine/tkx.ts +60 -3
- package/src/headless/index.ts +44 -0
- package/src/headless/useControllable.ts +40 -0
- package/src/headless/useDebounce.ts +35 -0
- package/src/headless/useDisclosure.ts +28 -0
- package/src/headless/useFormState.ts +111 -0
- package/src/headless/useIntersectionObserver.ts +51 -0
- package/src/headless/useListSelection.ts +101 -0
- package/src/headless/useLocalStorage.ts +46 -0
- package/src/headless/useMediaQuery.ts +37 -0
- package/src/headless/useRovingTabIndex.ts +84 -0
- package/src/headless/useThrottle.ts +46 -0
- package/src/i18n/I18nProvider.tsx +1 -1
- package/src/i18n/index.ts +678 -38
- package/src/themes/index.ts +31 -4
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ResponsiveContainer,
|
|
3
|
+
PieChart,
|
|
4
|
+
Pie,
|
|
5
|
+
Cell,
|
|
6
|
+
Tooltip,
|
|
7
|
+
Legend,
|
|
8
|
+
} from 'recharts';
|
|
9
|
+
import { useTheme } from '../themes';
|
|
10
|
+
import { getDefaultColors, tooltipStyle } from './shared';
|
|
11
|
+
|
|
12
|
+
export interface TkxDonutChartSlice {
|
|
13
|
+
name: string;
|
|
14
|
+
value: number;
|
|
15
|
+
color?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TkxDonutChartProps {
|
|
19
|
+
data: TkxDonutChartSlice[];
|
|
20
|
+
height?: number;
|
|
21
|
+
showLegend?: boolean;
|
|
22
|
+
showTooltip?: boolean;
|
|
23
|
+
/** Center label — large text (e.g. total value) */
|
|
24
|
+
centerLabel?: string;
|
|
25
|
+
/** Center sublabel — small text below centerLabel */
|
|
26
|
+
centerSublabel?: string;
|
|
27
|
+
/** Inner radius as number or percentage string. Default: '55%' */
|
|
28
|
+
innerRadius?: number | string;
|
|
29
|
+
/** Outer radius as number or percentage string. Default: '75%' */
|
|
30
|
+
outerRadius?: number | string;
|
|
31
|
+
startAngle?: number;
|
|
32
|
+
ariaLabel?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function CenterLabel({
|
|
36
|
+
cx, cy, label, sublabel, theme,
|
|
37
|
+
}: {
|
|
38
|
+
cx: number; cy: number;
|
|
39
|
+
label?: string; sublabel?: string;
|
|
40
|
+
theme: any;
|
|
41
|
+
}) {
|
|
42
|
+
if (!label && !sublabel) return null;
|
|
43
|
+
return (
|
|
44
|
+
<>
|
|
45
|
+
{label && (
|
|
46
|
+
<text x={cx} y={cy - (sublabel ? 8 : 0)} textAnchor="middle" dominantBaseline="middle"
|
|
47
|
+
fill={theme.text} fontSize={22} fontWeight={700} fontFamily="inherit">
|
|
48
|
+
{label}
|
|
49
|
+
</text>
|
|
50
|
+
)}
|
|
51
|
+
{sublabel && (
|
|
52
|
+
<text x={cx} y={cy + (label ? 18 : 0)} textAnchor="middle" dominantBaseline="middle"
|
|
53
|
+
fill={theme.textMuted} fontSize={13} fontFamily="inherit">
|
|
54
|
+
{sublabel}
|
|
55
|
+
</text>
|
|
56
|
+
)}
|
|
57
|
+
</>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function TkxDonutChart({
|
|
62
|
+
data,
|
|
63
|
+
height = 300,
|
|
64
|
+
showLegend = true,
|
|
65
|
+
showTooltip = true,
|
|
66
|
+
centerLabel,
|
|
67
|
+
centerSublabel,
|
|
68
|
+
innerRadius = '55%',
|
|
69
|
+
outerRadius = '75%',
|
|
70
|
+
startAngle = 90,
|
|
71
|
+
ariaLabel = 'Donut chart',
|
|
72
|
+
}: TkxDonutChartProps) {
|
|
73
|
+
const theme = useTheme();
|
|
74
|
+
const colors = getDefaultColors(theme);
|
|
75
|
+
const tt = tooltipStyle(theme);
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div role="img" aria-label={ariaLabel} style={{ width: '100%', height }}>
|
|
79
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
80
|
+
<PieChart>
|
|
81
|
+
{showTooltip && (
|
|
82
|
+
<Tooltip
|
|
83
|
+
contentStyle={tt.contentStyle}
|
|
84
|
+
labelStyle={tt.labelStyle}
|
|
85
|
+
itemStyle={tt.itemStyle}
|
|
86
|
+
/>
|
|
87
|
+
)}
|
|
88
|
+
{showLegend && (
|
|
89
|
+
<Legend wrapperStyle={{ fontSize: 13, color: theme.textMuted }} />
|
|
90
|
+
)}
|
|
91
|
+
<Pie
|
|
92
|
+
data={data}
|
|
93
|
+
dataKey="value"
|
|
94
|
+
nameKey="name"
|
|
95
|
+
innerRadius={innerRadius}
|
|
96
|
+
outerRadius={outerRadius}
|
|
97
|
+
startAngle={startAngle}
|
|
98
|
+
endAngle={startAngle - 360}
|
|
99
|
+
strokeWidth={3}
|
|
100
|
+
stroke={theme.bg}
|
|
101
|
+
label={false}
|
|
102
|
+
labelLine={false}
|
|
103
|
+
>
|
|
104
|
+
{data.map((entry, i) => (
|
|
105
|
+
<Cell key={entry.name} fill={entry.color ?? colors[i % colors.length]} />
|
|
106
|
+
))}
|
|
107
|
+
{/* Center text via customized label — recharts passes cx/cy to label */}
|
|
108
|
+
</Pie>
|
|
109
|
+
{/* SVG center label rendered as a separate layer */}
|
|
110
|
+
{(centerLabel || centerSublabel) && (
|
|
111
|
+
<text>
|
|
112
|
+
{/* This renders nothing — actual center text needs PieChart customization */}
|
|
113
|
+
</text>
|
|
114
|
+
)}
|
|
115
|
+
</PieChart>
|
|
116
|
+
</ResponsiveContainer>
|
|
117
|
+
{/* Overlay center label using absolute positioning */}
|
|
118
|
+
{(centerLabel || centerSublabel) && (
|
|
119
|
+
<div
|
|
120
|
+
style={{
|
|
121
|
+
position: 'absolute',
|
|
122
|
+
inset: 0,
|
|
123
|
+
display: 'flex',
|
|
124
|
+
flexDirection: 'column',
|
|
125
|
+
alignItems: 'center',
|
|
126
|
+
justifyContent: 'center',
|
|
127
|
+
pointerEvents: 'none',
|
|
128
|
+
marginTop: showLegend ? -24 : 0,
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
{centerLabel && (
|
|
132
|
+
<span style={{ fontSize: 22, fontWeight: 700, color: theme.text, lineHeight: 1 }}>
|
|
133
|
+
{centerLabel}
|
|
134
|
+
</span>
|
|
135
|
+
)}
|
|
136
|
+
{centerSublabel && (
|
|
137
|
+
<span style={{ fontSize: 13, color: theme.textMuted, marginTop: 4 }}>
|
|
138
|
+
{centerSublabel}
|
|
139
|
+
</span>
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ResponsiveContainer,
|
|
3
|
+
LineChart,
|
|
4
|
+
Line,
|
|
5
|
+
XAxis,
|
|
6
|
+
YAxis,
|
|
7
|
+
CartesianGrid,
|
|
8
|
+
Tooltip,
|
|
9
|
+
Legend,
|
|
10
|
+
ReferenceLine,
|
|
11
|
+
} from 'recharts';
|
|
12
|
+
import { useTheme } from '../themes';
|
|
13
|
+
import { getDefaultColors, tooltipStyle, DEFAULT_MARGIN, type ChartMargin } from './shared';
|
|
14
|
+
|
|
15
|
+
export interface TkxLineChartSeries {
|
|
16
|
+
key: string;
|
|
17
|
+
label?: string;
|
|
18
|
+
color?: string;
|
|
19
|
+
strokeWidth?: number;
|
|
20
|
+
dashed?: boolean;
|
|
21
|
+
dot?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TkxReferenceLineConfig {
|
|
25
|
+
y?: number;
|
|
26
|
+
x?: number | string;
|
|
27
|
+
label?: string;
|
|
28
|
+
color?: string;
|
|
29
|
+
dashed?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface TkxLineChartProps {
|
|
33
|
+
data: Record<string, any>[];
|
|
34
|
+
series: TkxLineChartSeries[];
|
|
35
|
+
xKey: string;
|
|
36
|
+
height?: number;
|
|
37
|
+
margin?: ChartMargin;
|
|
38
|
+
showGrid?: boolean;
|
|
39
|
+
showLegend?: boolean;
|
|
40
|
+
showTooltip?: boolean;
|
|
41
|
+
smooth?: boolean;
|
|
42
|
+
referenceLines?: TkxReferenceLineConfig[];
|
|
43
|
+
xTickFormatter?: (value: any) => string;
|
|
44
|
+
yTickFormatter?: (value: any) => string;
|
|
45
|
+
yDomain?: [number | 'auto' | 'dataMin' | 'dataMax', number | 'auto' | 'dataMin' | 'dataMax'];
|
|
46
|
+
connectNulls?: boolean;
|
|
47
|
+
ariaLabel?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function TkxLineChart({
|
|
51
|
+
data,
|
|
52
|
+
series,
|
|
53
|
+
xKey,
|
|
54
|
+
height = 300,
|
|
55
|
+
margin = DEFAULT_MARGIN,
|
|
56
|
+
showGrid = true,
|
|
57
|
+
showLegend = true,
|
|
58
|
+
showTooltip = true,
|
|
59
|
+
smooth = true,
|
|
60
|
+
referenceLines = [],
|
|
61
|
+
xTickFormatter,
|
|
62
|
+
yTickFormatter,
|
|
63
|
+
yDomain,
|
|
64
|
+
connectNulls = false,
|
|
65
|
+
ariaLabel = 'Line chart',
|
|
66
|
+
}: TkxLineChartProps) {
|
|
67
|
+
const theme = useTheme();
|
|
68
|
+
const colors = getDefaultColors(theme);
|
|
69
|
+
const tt = tooltipStyle(theme);
|
|
70
|
+
const tickStyle = { fill: theme.textMuted, fontSize: 12 };
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div role="img" aria-label={ariaLabel} style={{ width: '100%', height }}>
|
|
74
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
75
|
+
<LineChart data={data} margin={margin}>
|
|
76
|
+
{showGrid && (
|
|
77
|
+
<CartesianGrid strokeDasharray="3 3" stroke={theme.border} vertical={false} />
|
|
78
|
+
)}
|
|
79
|
+
<XAxis
|
|
80
|
+
dataKey={xKey}
|
|
81
|
+
tick={tickStyle}
|
|
82
|
+
axisLine={{ stroke: theme.border }}
|
|
83
|
+
tickLine={false}
|
|
84
|
+
tickFormatter={xTickFormatter}
|
|
85
|
+
/>
|
|
86
|
+
<YAxis
|
|
87
|
+
tick={tickStyle}
|
|
88
|
+
axisLine={false}
|
|
89
|
+
tickLine={false}
|
|
90
|
+
tickFormatter={yTickFormatter}
|
|
91
|
+
domain={yDomain}
|
|
92
|
+
/>
|
|
93
|
+
{showTooltip && (
|
|
94
|
+
<Tooltip
|
|
95
|
+
contentStyle={tt.contentStyle}
|
|
96
|
+
labelStyle={tt.labelStyle}
|
|
97
|
+
itemStyle={tt.itemStyle}
|
|
98
|
+
cursor={{ stroke: theme.border, strokeWidth: 1 }}
|
|
99
|
+
/>
|
|
100
|
+
)}
|
|
101
|
+
{showLegend && (
|
|
102
|
+
<Legend wrapperStyle={{ fontSize: 13, color: theme.textMuted, paddingTop: 8 }} />
|
|
103
|
+
)}
|
|
104
|
+
{referenceLines.map((rl, i) => (
|
|
105
|
+
<ReferenceLine
|
|
106
|
+
key={i}
|
|
107
|
+
y={rl.y}
|
|
108
|
+
x={rl.x}
|
|
109
|
+
label={rl.label ? { value: rl.label, fill: theme.textMuted, fontSize: 11 } : undefined}
|
|
110
|
+
stroke={rl.color ?? theme.border}
|
|
111
|
+
strokeDasharray={rl.dashed ? '4 4' : undefined}
|
|
112
|
+
/>
|
|
113
|
+
))}
|
|
114
|
+
{series.map((s, i) => {
|
|
115
|
+
const color = s.color ?? colors[i % colors.length];
|
|
116
|
+
return (
|
|
117
|
+
<Line
|
|
118
|
+
key={s.key}
|
|
119
|
+
type={smooth ? 'monotone' : 'linear'}
|
|
120
|
+
dataKey={s.key}
|
|
121
|
+
name={s.label ?? s.key}
|
|
122
|
+
stroke={color}
|
|
123
|
+
strokeWidth={s.strokeWidth ?? 2}
|
|
124
|
+
strokeDasharray={s.dashed ? '6 3' : undefined}
|
|
125
|
+
dot={s.dot ?? false}
|
|
126
|
+
activeDot={{ r: 5, strokeWidth: 0, fill: color }}
|
|
127
|
+
connectNulls={connectNulls}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
})}
|
|
131
|
+
</LineChart>
|
|
132
|
+
</ResponsiveContainer>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ResponsiveContainer,
|
|
3
|
+
PieChart,
|
|
4
|
+
Pie,
|
|
5
|
+
Cell,
|
|
6
|
+
Tooltip,
|
|
7
|
+
Legend,
|
|
8
|
+
} from 'recharts';
|
|
9
|
+
import { useTheme } from '../themes';
|
|
10
|
+
import { getDefaultColors, tooltipStyle } from './shared';
|
|
11
|
+
|
|
12
|
+
export interface TkxPieChartSlice {
|
|
13
|
+
name: string;
|
|
14
|
+
value: number;
|
|
15
|
+
color?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TkxPieChartProps {
|
|
19
|
+
data: TkxPieChartSlice[];
|
|
20
|
+
height?: number;
|
|
21
|
+
showLegend?: boolean;
|
|
22
|
+
showTooltip?: boolean;
|
|
23
|
+
showLabels?: boolean;
|
|
24
|
+
/** Outer radius as a number (px) or percentage string. Default: '70%' */
|
|
25
|
+
outerRadius?: number | string;
|
|
26
|
+
/** Start angle in degrees. Default: 90 (12 o'clock) */
|
|
27
|
+
startAngle?: number;
|
|
28
|
+
ariaLabel?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function TkxPieChart({
|
|
32
|
+
data,
|
|
33
|
+
height = 300,
|
|
34
|
+
showLegend = true,
|
|
35
|
+
showTooltip = true,
|
|
36
|
+
showLabels = false,
|
|
37
|
+
outerRadius = '70%',
|
|
38
|
+
startAngle = 90,
|
|
39
|
+
ariaLabel = 'Pie chart',
|
|
40
|
+
}: TkxPieChartProps) {
|
|
41
|
+
const theme = useTheme();
|
|
42
|
+
const colors = getDefaultColors(theme);
|
|
43
|
+
const tt = tooltipStyle(theme);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div role="img" aria-label={ariaLabel} style={{ width: '100%', height }}>
|
|
47
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
48
|
+
<PieChart>
|
|
49
|
+
{showTooltip && (
|
|
50
|
+
<Tooltip
|
|
51
|
+
contentStyle={tt.contentStyle}
|
|
52
|
+
labelStyle={tt.labelStyle}
|
|
53
|
+
itemStyle={tt.itemStyle}
|
|
54
|
+
/>
|
|
55
|
+
)}
|
|
56
|
+
{showLegend && (
|
|
57
|
+
<Legend wrapperStyle={{ fontSize: 13, color: theme.textMuted }} />
|
|
58
|
+
)}
|
|
59
|
+
<Pie
|
|
60
|
+
data={data}
|
|
61
|
+
dataKey="value"
|
|
62
|
+
nameKey="name"
|
|
63
|
+
outerRadius={outerRadius}
|
|
64
|
+
startAngle={startAngle}
|
|
65
|
+
endAngle={startAngle - 360}
|
|
66
|
+
label={showLabels ? ({ name, percent }) => `${name} (${(percent * 100).toFixed(0)}%)` : undefined}
|
|
67
|
+
labelLine={showLabels}
|
|
68
|
+
strokeWidth={2}
|
|
69
|
+
stroke={theme.bg}
|
|
70
|
+
>
|
|
71
|
+
{data.map((entry, i) => (
|
|
72
|
+
<Cell
|
|
73
|
+
key={entry.name}
|
|
74
|
+
fill={entry.color ?? colors[i % colors.length]}
|
|
75
|
+
/>
|
|
76
|
+
))}
|
|
77
|
+
</Pie>
|
|
78
|
+
</PieChart>
|
|
79
|
+
</ResponsiveContainer>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ResponsiveContainer,
|
|
3
|
+
RadarChart,
|
|
4
|
+
PolarGrid,
|
|
5
|
+
PolarAngleAxis,
|
|
6
|
+
PolarRadiusAxis,
|
|
7
|
+
Radar,
|
|
8
|
+
Legend,
|
|
9
|
+
Tooltip,
|
|
10
|
+
} from 'recharts';
|
|
11
|
+
import { useTheme } from '../themes';
|
|
12
|
+
import { getDefaultColors, tooltipStyle } from './shared';
|
|
13
|
+
|
|
14
|
+
export interface TkxRadarSeries {
|
|
15
|
+
key: string;
|
|
16
|
+
label?: string;
|
|
17
|
+
color?: string;
|
|
18
|
+
fillOpacity?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TkxRadarChartProps {
|
|
22
|
+
data: Record<string, any>[];
|
|
23
|
+
series: TkxRadarSeries[];
|
|
24
|
+
/** Key in data for the axis labels (e.g. 'subject', 'metric') */
|
|
25
|
+
angleKey: string;
|
|
26
|
+
height?: number;
|
|
27
|
+
showLegend?: boolean;
|
|
28
|
+
showTooltip?: boolean;
|
|
29
|
+
/** Outer radius as percentage string or number. Default: '70%' */
|
|
30
|
+
outerRadius?: string | number;
|
|
31
|
+
ariaLabel?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function TkxRadarChart({
|
|
35
|
+
data,
|
|
36
|
+
series,
|
|
37
|
+
angleKey,
|
|
38
|
+
height = 300,
|
|
39
|
+
showLegend = true,
|
|
40
|
+
showTooltip = true,
|
|
41
|
+
outerRadius = '70%',
|
|
42
|
+
ariaLabel = 'Radar chart',
|
|
43
|
+
}: TkxRadarChartProps) {
|
|
44
|
+
const theme = useTheme();
|
|
45
|
+
const colors = getDefaultColors(theme);
|
|
46
|
+
const tt = tooltipStyle(theme);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div role="img" aria-label={ariaLabel} style={{ width: '100%', height }}>
|
|
50
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
51
|
+
<RadarChart data={data} outerRadius={outerRadius}>
|
|
52
|
+
<PolarGrid stroke={theme.border} />
|
|
53
|
+
<PolarAngleAxis
|
|
54
|
+
dataKey={angleKey}
|
|
55
|
+
tick={{ fill: theme.textMuted, fontSize: 12 }}
|
|
56
|
+
/>
|
|
57
|
+
<PolarRadiusAxis
|
|
58
|
+
tick={{ fill: theme.textMuted, fontSize: 10 }}
|
|
59
|
+
axisLine={false}
|
|
60
|
+
tickLine={false}
|
|
61
|
+
/>
|
|
62
|
+
{showTooltip && (
|
|
63
|
+
<Tooltip
|
|
64
|
+
contentStyle={tt.contentStyle}
|
|
65
|
+
labelStyle={tt.labelStyle}
|
|
66
|
+
itemStyle={tt.itemStyle}
|
|
67
|
+
/>
|
|
68
|
+
)}
|
|
69
|
+
{showLegend && (
|
|
70
|
+
<Legend wrapperStyle={{ fontSize: 13, color: theme.textMuted }} />
|
|
71
|
+
)}
|
|
72
|
+
{series.map((s, i) => {
|
|
73
|
+
const color = s.color ?? colors[i % colors.length];
|
|
74
|
+
return (
|
|
75
|
+
<Radar
|
|
76
|
+
key={s.key}
|
|
77
|
+
name={s.label ?? s.key}
|
|
78
|
+
dataKey={s.key}
|
|
79
|
+
stroke={color}
|
|
80
|
+
fill={color}
|
|
81
|
+
fillOpacity={s.fillOpacity ?? 0.2}
|
|
82
|
+
strokeWidth={2}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
})}
|
|
86
|
+
</RadarChart>
|
|
87
|
+
</ResponsiveContainer>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ResponsiveContainer,
|
|
3
|
+
ScatterChart,
|
|
4
|
+
Scatter,
|
|
5
|
+
XAxis,
|
|
6
|
+
YAxis,
|
|
7
|
+
ZAxis,
|
|
8
|
+
CartesianGrid,
|
|
9
|
+
Tooltip,
|
|
10
|
+
Legend,
|
|
11
|
+
} from 'recharts';
|
|
12
|
+
import { useTheme } from '../themes';
|
|
13
|
+
import { getDefaultColors, tooltipStyle, DEFAULT_MARGIN, type ChartMargin } from './shared';
|
|
14
|
+
|
|
15
|
+
export interface TkxScatterSeries {
|
|
16
|
+
data: { x: number; y: number; z?: number; label?: string }[];
|
|
17
|
+
name?: string;
|
|
18
|
+
color?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TkxScatterChartProps {
|
|
22
|
+
series: TkxScatterSeries[];
|
|
23
|
+
height?: number;
|
|
24
|
+
margin?: ChartMargin;
|
|
25
|
+
showGrid?: boolean;
|
|
26
|
+
showLegend?: boolean;
|
|
27
|
+
showTooltip?: boolean;
|
|
28
|
+
xLabel?: string;
|
|
29
|
+
yLabel?: string;
|
|
30
|
+
xTickFormatter?: (value: any) => string;
|
|
31
|
+
yTickFormatter?: (value: any) => string;
|
|
32
|
+
/** Z-axis range (bubble size). Default: [20, 400] */
|
|
33
|
+
zRange?: [number, number];
|
|
34
|
+
ariaLabel?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function TkxScatterChart({
|
|
38
|
+
series,
|
|
39
|
+
height = 300,
|
|
40
|
+
margin = DEFAULT_MARGIN,
|
|
41
|
+
showGrid = true,
|
|
42
|
+
showLegend = true,
|
|
43
|
+
showTooltip = true,
|
|
44
|
+
xTickFormatter,
|
|
45
|
+
yTickFormatter,
|
|
46
|
+
zRange = [20, 400],
|
|
47
|
+
ariaLabel = 'Scatter chart',
|
|
48
|
+
}: TkxScatterChartProps) {
|
|
49
|
+
const theme = useTheme();
|
|
50
|
+
const colors = getDefaultColors(theme);
|
|
51
|
+
const tt = tooltipStyle(theme);
|
|
52
|
+
const tickStyle = { fill: theme.textMuted, fontSize: 12 };
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div role="img" aria-label={ariaLabel} style={{ width: '100%', height }}>
|
|
56
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
57
|
+
<ScatterChart margin={margin}>
|
|
58
|
+
{showGrid && (
|
|
59
|
+
<CartesianGrid strokeDasharray="3 3" stroke={theme.border} />
|
|
60
|
+
)}
|
|
61
|
+
<XAxis
|
|
62
|
+
type="number"
|
|
63
|
+
dataKey="x"
|
|
64
|
+
tick={tickStyle}
|
|
65
|
+
axisLine={{ stroke: theme.border }}
|
|
66
|
+
tickLine={false}
|
|
67
|
+
tickFormatter={xTickFormatter}
|
|
68
|
+
/>
|
|
69
|
+
<YAxis
|
|
70
|
+
type="number"
|
|
71
|
+
dataKey="y"
|
|
72
|
+
tick={tickStyle}
|
|
73
|
+
axisLine={false}
|
|
74
|
+
tickLine={false}
|
|
75
|
+
tickFormatter={yTickFormatter}
|
|
76
|
+
/>
|
|
77
|
+
<ZAxis type="number" dataKey="z" range={zRange} />
|
|
78
|
+
{showTooltip && (
|
|
79
|
+
<Tooltip
|
|
80
|
+
contentStyle={tt.contentStyle}
|
|
81
|
+
labelStyle={tt.labelStyle}
|
|
82
|
+
itemStyle={tt.itemStyle}
|
|
83
|
+
cursor={{ strokeDasharray: '3 3' }}
|
|
84
|
+
/>
|
|
85
|
+
)}
|
|
86
|
+
{showLegend && (
|
|
87
|
+
<Legend wrapperStyle={{ fontSize: 13, color: theme.textMuted, paddingTop: 8 }} />
|
|
88
|
+
)}
|
|
89
|
+
{series.map((s, i) => (
|
|
90
|
+
<Scatter
|
|
91
|
+
key={s.name ?? i}
|
|
92
|
+
name={s.name}
|
|
93
|
+
data={s.data}
|
|
94
|
+
fill={s.color ?? colors[i % colors.length]}
|
|
95
|
+
fillOpacity={0.8}
|
|
96
|
+
/>
|
|
97
|
+
))}
|
|
98
|
+
</ScatterChart>
|
|
99
|
+
</ResponsiveContainer>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// ── @tekivex/ui/charts ───────────────────────────────────────────────────────
|
|
2
|
+
// Theme-aware chart components built on Recharts.
|
|
3
|
+
// Import from '@tekivex/ui/charts' — zero impact on the main bundle.
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export { TkxAreaChart } from './TkxAreaChart';
|
|
7
|
+
export { TkxBarChart } from './TkxBarChart';
|
|
8
|
+
export { TkxLineChart } from './TkxLineChart';
|
|
9
|
+
export { TkxPieChart } from './TkxPieChart';
|
|
10
|
+
export { TkxDonutChart } from './TkxDonutChart';
|
|
11
|
+
export { TkxScatterChart } from './TkxScatterChart';
|
|
12
|
+
export { TkxRadarChart } from './TkxRadarChart';
|
|
13
|
+
|
|
14
|
+
export type { TkxAreaChartProps } from './TkxAreaChart';
|
|
15
|
+
export type { TkxBarChartProps } from './TkxBarChart';
|
|
16
|
+
export type { TkxLineChartProps } from './TkxLineChart';
|
|
17
|
+
export type { TkxPieChartProps } from './TkxPieChart';
|
|
18
|
+
export type { TkxDonutChartProps } from './TkxDonutChart';
|
|
19
|
+
export type { TkxScatterChartProps } from './TkxScatterChart';
|
|
20
|
+
export type { TkxRadarChartProps } from './TkxRadarChart';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// ── Shared chart utilities ───────────────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
import type { ThemeTokens } from '../themes';
|
|
4
|
+
|
|
5
|
+
/** Default palette: uses theme tokens then falls back to categorical colors */
|
|
6
|
+
export function getDefaultColors(theme: ThemeTokens): string[] {
|
|
7
|
+
return [
|
|
8
|
+
theme.primary,
|
|
9
|
+
theme.secondary,
|
|
10
|
+
theme.info,
|
|
11
|
+
theme.success,
|
|
12
|
+
theme.warning,
|
|
13
|
+
theme.danger,
|
|
14
|
+
'#a855f7',
|
|
15
|
+
'#f97316',
|
|
16
|
+
'#14b8a6',
|
|
17
|
+
'#e879f9',
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ChartMargin {
|
|
22
|
+
top?: number;
|
|
23
|
+
right?: number;
|
|
24
|
+
bottom?: number;
|
|
25
|
+
left?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const DEFAULT_MARGIN: ChartMargin = { top: 10, right: 30, bottom: 10, left: 0 };
|
|
29
|
+
|
|
30
|
+
/** Common recharts tooltip style that matches TekiVex theme */
|
|
31
|
+
export function tooltipStyle(theme: ThemeTokens) {
|
|
32
|
+
return {
|
|
33
|
+
contentStyle: {
|
|
34
|
+
backgroundColor: theme.surface,
|
|
35
|
+
border: `1px solid ${theme.border}`,
|
|
36
|
+
borderRadius: 8,
|
|
37
|
+
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
|
38
|
+
color: theme.text,
|
|
39
|
+
fontSize: 13,
|
|
40
|
+
},
|
|
41
|
+
labelStyle: { color: theme.textMuted, fontWeight: 600, marginBottom: 4 },
|
|
42
|
+
itemStyle: { color: theme.text },
|
|
43
|
+
cursor: { fill: `${theme.primary}15` },
|
|
44
|
+
};
|
|
45
|
+
}
|