react-native-metrify 0.1.0-alpha.1 → 0.1.0-alpha.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.
Files changed (100) hide show
  1. package/package.json +1 -2
  2. package/src/core/animation/index.ts +0 -113
  3. package/src/core/animation/index.web.ts +0 -112
  4. package/src/core/hooks/index.ts +0 -66
  5. package/src/core/index.ts +0 -26
  6. package/src/core/layout/index.ts +0 -101
  7. package/src/core/math/index.ts +0 -72
  8. package/src/core/package.json +0 -13
  9. package/src/core/theme/ThemeProvider.tsx +0 -36
  10. package/src/core/theme/index.ts +0 -5
  11. package/src/core/theme/themes.ts +0 -132
  12. package/src/core/types/index.ts +0 -164
  13. package/src/core/utils/responsive.ts +0 -203
  14. package/src/core/utils/time.ts +0 -100
  15. package/src/index.ts +0 -13
  16. package/src/renderer-svg/adapters/index.ts +0 -84
  17. package/src/renderer-svg/index.ts +0 -8
  18. package/src/renderer-svg/package.json +0 -17
  19. package/src/renderer-svg/paths/arc.ts +0 -93
  20. package/src/renderer-svg/paths/index.ts +0 -6
  21. package/src/renderer-svg/paths/line.ts +0 -83
  22. package/src/renderer-svg/paths/rect.ts +0 -80
  23. package/src/renderer-svg/primitives/AnimatedCircle.tsx +0 -48
  24. package/src/renderer-svg/primitives/AnimatedPath.tsx +0 -48
  25. package/src/renderer-svg/primitives/Text.tsx +0 -73
  26. package/src/renderer-svg/primitives/index.ts +0 -6
  27. package/src/widgets/AreaChart/AreaChart.tsx +0 -213
  28. package/src/widgets/AreaChart/index.ts +0 -2
  29. package/src/widgets/AreaChart/types.ts +0 -34
  30. package/src/widgets/BarChart/BarChart.tsx +0 -249
  31. package/src/widgets/BarChart/index.ts +0 -10
  32. package/src/widgets/BarChart/types.ts +0 -27
  33. package/src/widgets/BoxPlot/BoxPlot.tsx +0 -252
  34. package/src/widgets/BoxPlot/index.ts +0 -2
  35. package/src/widgets/BoxPlot/types.ts +0 -27
  36. package/src/widgets/BubbleChart/BubbleChart.tsx +0 -175
  37. package/src/widgets/BubbleChart/index.ts +0 -2
  38. package/src/widgets/BubbleChart/types.ts +0 -33
  39. package/src/widgets/CandlestickChart/CandlestickChart.tsx +0 -204
  40. package/src/widgets/CandlestickChart/index.ts +0 -2
  41. package/src/widgets/CandlestickChart/types.ts +0 -29
  42. package/src/widgets/FunnelChart/FunnelChart.tsx +0 -172
  43. package/src/widgets/FunnelChart/index.ts +0 -2
  44. package/src/widgets/FunnelChart/types.ts +0 -22
  45. package/src/widgets/Gauge/Gauge.tsx +0 -235
  46. package/src/widgets/Gauge/index.ts +0 -5
  47. package/src/widgets/Gauge/types.ts +0 -19
  48. package/src/widgets/GroupedBarChart/GroupedBarChart.tsx +0 -190
  49. package/src/widgets/GroupedBarChart/index.ts +0 -2
  50. package/src/widgets/GroupedBarChart/types.ts +0 -30
  51. package/src/widgets/Heatmap/Heatmap.tsx +0 -216
  52. package/src/widgets/Heatmap/index.ts +0 -2
  53. package/src/widgets/Heatmap/types.ts +0 -27
  54. package/src/widgets/Histogram/Histogram.tsx +0 -173
  55. package/src/widgets/Histogram/index.ts +0 -2
  56. package/src/widgets/Histogram/types.ts +0 -18
  57. package/src/widgets/HorizontalBarChart/HorizontalBarChart.tsx +0 -125
  58. package/src/widgets/HorizontalBarChart/index.ts +0 -2
  59. package/src/widgets/HorizontalBarChart/types.ts +0 -23
  60. package/src/widgets/KPI/KPI.tsx +0 -222
  61. package/src/widgets/KPI/index.ts +0 -5
  62. package/src/widgets/KPI/types.ts +0 -19
  63. package/src/widgets/LineChart/LineChart.tsx +0 -364
  64. package/src/widgets/LineChart/index.ts +0 -10
  65. package/src/widgets/LineChart/types.ts +0 -34
  66. package/src/widgets/MultiLineSparkline/MultiLineSparkline.tsx +0 -234
  67. package/src/widgets/MultiLineSparkline/index.ts +0 -10
  68. package/src/widgets/MultiLineSparkline/types.ts +0 -25
  69. package/src/widgets/PieChart/PieChart.tsx +0 -275
  70. package/src/widgets/PieChart/index.ts +0 -10
  71. package/src/widgets/PieChart/types.ts +0 -26
  72. package/src/widgets/Progress/Progress.tsx +0 -201
  73. package/src/widgets/Progress/index.ts +0 -5
  74. package/src/widgets/Progress/types.ts +0 -19
  75. package/src/widgets/RadarChart/RadarChart.tsx +0 -213
  76. package/src/widgets/RadarChart/index.ts +0 -2
  77. package/src/widgets/RadarChart/types.ts +0 -29
  78. package/src/widgets/SankeyDiagram/SankeyDiagram.tsx +0 -272
  79. package/src/widgets/SankeyDiagram/index.ts +0 -2
  80. package/src/widgets/SankeyDiagram/types.ts +0 -29
  81. package/src/widgets/ScatterPlot/ScatterPlot.tsx +0 -167
  82. package/src/widgets/ScatterPlot/index.ts +0 -2
  83. package/src/widgets/ScatterPlot/types.ts +0 -32
  84. package/src/widgets/Sparkline/Sparkline.tsx +0 -203
  85. package/src/widgets/Sparkline/index.ts +0 -5
  86. package/src/widgets/Sparkline/types.ts +0 -18
  87. package/src/widgets/StackedBarChart/StackedBarChart.tsx +0 -181
  88. package/src/widgets/StackedBarChart/index.ts +0 -2
  89. package/src/widgets/StackedBarChart/types.ts +0 -29
  90. package/src/widgets/SunburstChart/SunburstChart.tsx +0 -176
  91. package/src/widgets/SunburstChart/index.ts +0 -2
  92. package/src/widgets/SunburstChart/types.ts +0 -22
  93. package/src/widgets/Treemap/Treemap.tsx +0 -191
  94. package/src/widgets/Treemap/index.ts +0 -2
  95. package/src/widgets/Treemap/types.ts +0 -23
  96. package/src/widgets/WaterfallChart/WaterfallChart.tsx +0 -226
  97. package/src/widgets/WaterfallChart/index.ts +0 -2
  98. package/src/widgets/WaterfallChart/types.ts +0 -26
  99. package/src/widgets/index.ts +0 -40
  100. package/src/widgets/package.json +0 -18
@@ -1,27 +0,0 @@
1
- /**
2
- * Heatmap Widget types
3
- */
4
- import { BaseWidgetProps } from '../../core';
5
-
6
- export interface HeatmapDataPoint {
7
- x: number | string;
8
- y: number | string;
9
- value: number;
10
- }
11
-
12
- export interface HeatmapData {
13
- data: HeatmapDataPoint[];
14
- xLabels: string[];
15
- yLabels: string[];
16
- title?: string;
17
- }
18
-
19
- export interface HeatmapWidgetProps extends BaseWidgetProps<HeatmapData> {
20
- cellSize?: number;
21
- cellSpacing?: number;
22
- showValues?: boolean;
23
- showLabels?: boolean;
24
- colorScheme?: 'blue' | 'green' | 'red' | 'purple' | 'gradient';
25
- minColor?: string;
26
- maxColor?: string;
27
- }
@@ -1,173 +0,0 @@
1
- /**
2
- * Histogram Widget - Frequency distribution
3
- */
4
- import React, { memo, useMemo } from 'react';
5
- import { View, Text as RNText, StyleSheet } from 'react-native';
6
- import Svg, { Rect, Line as SvgLine } from 'react-native-svg';
7
- import { useWidgetDimensions, useWidgetTheme, normalize } from '../../core';
8
- import { HistogramWidgetProps } from './types';
9
-
10
- export const Histogram = memo<HistogramWidgetProps>(({
11
- data: widgetData,
12
- width,
13
- height,
14
- loading = false,
15
- theme: themeOverride,
16
- showXAxis = true,
17
- showYAxis = true,
18
- showGrid = false,
19
- color,
20
- barSpacing = 2,
21
- testID,
22
- }) => {
23
- const theme = useWidgetTheme(themeOverride);
24
- const dimensions = useWidgetDimensions(width, height, 350, 250);
25
-
26
- if (loading) {
27
- return (
28
- <View style={[styles.container, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md }]} testID={`${testID}-loading`}>
29
- <RNText style={[styles.loadingText, { color: theme.colors.textSecondary }]}>Loading...</RNText>
30
- </View>
31
- );
32
- }
33
-
34
- if (!widgetData || !widgetData.data || widgetData.data.length === 0) {
35
- return (
36
- <View style={[styles.container, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md }]} testID={`${testID}-empty`}>
37
- <RNText style={[styles.emptyText, { color: theme.colors.textSecondary }]}>No data</RNText>
38
- </View>
39
- );
40
- }
41
-
42
- const { data, title, binCount = 10 } = widgetData;
43
- const padding = theme.spacing.md;
44
- const titleHeight = title ? theme.fontScale.md + theme.spacing.sm : 0;
45
- const xAxisHeight = showXAxis ? 30 : 0;
46
- const yAxisWidth = showYAxis ? 40 : 0;
47
-
48
- const chartWidth = dimensions.width - padding * 2 - yAxisWidth;
49
- const chartHeight = dimensions.height - padding * 2 - titleHeight - xAxisHeight;
50
-
51
- const barColor = color || theme.colors.chartPrimary;
52
-
53
- // Calculate bins
54
- const { bins, maxFrequency, minValue, maxValue } = useMemo(() => {
55
- const min = Math.min(...data);
56
- const max = Math.max(...data);
57
- const binWidth = (max - min) / binCount;
58
-
59
- const binArray: { start: number; end: number; count: number }[] = [];
60
-
61
- for (let i = 0; i < binCount; i++) {
62
- const start = min + i * binWidth;
63
- const end = start + binWidth;
64
- const count = data.filter(val => val >= start && (i === binCount - 1 ? val <= end : val < end)).length;
65
- binArray.push({ start, end, count });
66
- }
67
-
68
- const maxFreq = Math.max(...binArray.map(b => b.count));
69
-
70
- return {
71
- bins: binArray,
72
- maxFrequency: maxFreq,
73
- minValue: min,
74
- maxValue: max,
75
- };
76
- }, [data, binCount]);
77
-
78
- const yAxisLabels = useMemo(() => {
79
- const step = Math.ceil(maxFrequency / 4);
80
- return Array.from({ length: 5 }, (_, i) => ({
81
- value: i * step,
82
- y: chartHeight - (i * chartHeight) / 4,
83
- }));
84
- }, [maxFrequency, chartHeight]);
85
-
86
- const barWidth = (chartWidth - (bins.length - 1) * barSpacing) / bins.length;
87
-
88
- const bars = useMemo(() => {
89
- return bins.map((bin, index) => {
90
- const barHeight = (bin.count / maxFrequency) * chartHeight;
91
- const barY = chartHeight - barHeight;
92
- const barX = index * (barWidth + barSpacing);
93
-
94
- return {
95
- x: barX,
96
- y: barY,
97
- width: barWidth,
98
- height: barHeight,
99
- count: bin.count,
100
- rangeLabel: `${bin.start.toFixed(0)}-${bin.end.toFixed(0)}`,
101
- };
102
- });
103
- }, [bins, maxFrequency, chartHeight, barWidth, barSpacing]);
104
-
105
- return (
106
- <View style={[styles.wrapper, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md, padding }]} testID={testID}>
107
- {title && (
108
- <RNText style={[styles.title, { color: theme.colors.text, fontSize: theme.fontScale.md, fontWeight: 'bold', marginBottom: theme.spacing.sm }]}>
109
- {title}
110
- </RNText>
111
- )}
112
-
113
- <View style={styles.chartRow}>
114
- {showYAxis && (
115
- <View style={[styles.yAxis, { width: yAxisWidth }]}>
116
- {yAxisLabels.map((label, index) => (
117
- <RNText key={`y-${index}`} style={[styles.yAxisLabel, { color: theme.colors.textSecondary, fontSize: theme.fontScale.xs, top: label.y - 6 }]}>
118
- {label.value}
119
- </RNText>
120
- ))}
121
- </View>
122
- )}
123
-
124
- <View>
125
- <Svg width={chartWidth} height={chartHeight}>
126
- {showGrid && yAxisLabels.map((label, index) => (
127
- <SvgLine key={`grid-${index}`} x1={0} y1={label.y} x2={chartWidth} y2={label.y} stroke={theme.colors.borderLight} strokeWidth={1} />
128
- ))}
129
-
130
- {bars.map((bar, index) => (
131
- <Rect
132
- key={`bar-${index}`}
133
- x={bar.x}
134
- y={bar.y}
135
- width={bar.width}
136
- height={bar.height}
137
- fill={barColor}
138
- rx={theme.radius.sm}
139
- ry={theme.radius.sm}
140
- />
141
- ))}
142
- </Svg>
143
-
144
- {showXAxis && (
145
- <View style={[styles.xAxis, { width: chartWidth }]}>
146
- <RNText style={[styles.xAxisLabel, { color: theme.colors.textSecondary, fontSize: theme.fontScale.xs, left: 0 }]}>
147
- {minValue.toFixed(0)}
148
- </RNText>
149
- <RNText style={[styles.xAxisLabel, { color: theme.colors.textSecondary, fontSize: theme.fontScale.xs, right: 0 }]}>
150
- {maxValue.toFixed(0)}
151
- </RNText>
152
- </View>
153
- )}
154
- </View>
155
- </View>
156
- </View>
157
- );
158
- });
159
-
160
- Histogram.displayName = 'Histogram';
161
-
162
- const styles = StyleSheet.create({
163
- wrapper: { justifyContent: 'flex-start', alignItems: 'flex-start' },
164
- container: { justifyContent: 'center', alignItems: 'center' },
165
- loadingText: { fontSize: 16 },
166
- emptyText: { fontSize: 16 },
167
- title: { textAlign: 'center', width: '100%' },
168
- chartRow: { flexDirection: 'row', alignItems: 'flex-start' },
169
- yAxis: { position: 'relative', marginRight: 8 },
170
- yAxisLabel: { position: 'absolute', right: 0, textAlign: 'right' },
171
- xAxis: { position: 'relative', height: 30, marginTop: 4 },
172
- xAxisLabel: { position: 'absolute' },
173
- });
@@ -1,2 +0,0 @@
1
- export { Histogram } from './Histogram';
2
- export type { HistogramWidgetProps, HistogramData } from './types';
@@ -1,18 +0,0 @@
1
- /**
2
- * Histogram Widget types
3
- */
4
- import { BaseWidgetProps } from '../../core';
5
-
6
- export interface HistogramData {
7
- data: number[];
8
- title?: string;
9
- binCount?: number;
10
- }
11
-
12
- export interface HistogramWidgetProps extends BaseWidgetProps<HistogramData> {
13
- showXAxis?: boolean;
14
- showYAxis?: boolean;
15
- showGrid?: boolean;
16
- color?: string;
17
- barSpacing?: number;
18
- }
@@ -1,125 +0,0 @@
1
- /**
2
- * HorizontalBarChart Widget - Bars go left to right
3
- */
4
- import React, { memo, useMemo } from 'react';
5
- import { View, Text as RNText, StyleSheet } from 'react-native';
6
- import Svg, { Rect } from 'react-native-svg';
7
- import { useWidgetDimensions, useWidgetTheme, normalize } from '../../core';
8
- import { HorizontalBarChartWidgetProps } from './types';
9
-
10
- export const HorizontalBarChart = memo<HorizontalBarChartWidgetProps>(({
11
- data: widgetData,
12
- width,
13
- height,
14
- loading = false,
15
- theme: themeOverride,
16
- barHeight = 24,
17
- barSpacing = 12,
18
- showValues = true,
19
- showLabels = true,
20
- maxBars = 15,
21
- testID,
22
- }) => {
23
- const theme = useWidgetTheme(themeOverride);
24
- const dimensions = useWidgetDimensions(width, height, 350, 300);
25
-
26
- if (loading) {
27
- return (
28
- <View style={[styles.container, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md }]} testID={`${testID}-loading`}>
29
- <RNText style={[styles.loadingText, { color: theme.colors.textSecondary }]}>Loading...</RNText>
30
- </View>
31
- );
32
- }
33
-
34
- if (!widgetData || !widgetData.data || widgetData.data.length === 0) {
35
- return (
36
- <View style={[styles.container, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md }]} testID={`${testID}-empty`}>
37
- <RNText style={[styles.emptyText, { color: theme.colors.textSecondary }]}>No data</RNText>
38
- </View>
39
- );
40
- }
41
-
42
- const { data, title } = widgetData;
43
- const displayData = data.slice(0, maxBars);
44
- const padding = theme.spacing.md;
45
- const titleHeight = title ? theme.fontScale.md + theme.spacing.sm : 0;
46
- const labelWidth = showLabels ? 80 : 0;
47
- const valueWidth = showValues ? 50 : 0;
48
-
49
- const chartWidth = dimensions.width - padding * 2 - labelWidth - valueWidth;
50
- const maxValue = useMemo(() => Math.max(...displayData.map(d => d.value)), [displayData]);
51
-
52
- const bars = useMemo(() => {
53
- return displayData.map((item, index) => {
54
- const normalizedValue = normalize(item.value, 0, maxValue);
55
- const barWidth = normalizedValue * chartWidth;
56
- const barY = index * (barHeight + barSpacing);
57
-
58
- return {
59
- x: 0,
60
- y: barY,
61
- width: barWidth,
62
- height: barHeight,
63
- color: item.color || theme.colors.chartPrimary,
64
- value: item.value,
65
- label: item.label,
66
- };
67
- });
68
- }, [displayData, maxValue, chartWidth, barHeight, barSpacing, theme.colors.chartPrimary]);
69
-
70
- const totalHeight = bars.length * (barHeight + barSpacing);
71
-
72
- return (
73
- <View style={[styles.wrapper, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md, padding }]} testID={testID}>
74
- {title && (
75
- <RNText style={[styles.title, { color: theme.colors.text, fontSize: theme.fontScale.md, fontWeight: 'bold', marginBottom: theme.spacing.sm }]}>
76
- {title}
77
- </RNText>
78
- )}
79
-
80
- <View style={styles.chartContainer}>
81
- {bars.map((bar, index) => (
82
- <View key={`row-${index}`} style={styles.barRow}>
83
- {showLabels && (
84
- <View style={[styles.labelContainer, { width: labelWidth }]}>
85
- <RNText style={[styles.label, { color: theme.colors.text, fontSize: theme.fontScale.sm }]} numberOfLines={1}>
86
- {bar.label}
87
- </RNText>
88
- </View>
89
- )}
90
-
91
- <View style={{ width: chartWidth, height: barHeight }}>
92
- <Svg width={chartWidth} height={barHeight}>
93
- <Rect x={bar.x} y={0} width={bar.width} height={bar.height} fill={bar.color} rx={theme.radius.sm} ry={theme.radius.sm} />
94
- </Svg>
95
- </View>
96
-
97
- {showValues && (
98
- <View style={[styles.valueContainer, { width: valueWidth }]}>
99
- <RNText style={[styles.value, { color: theme.colors.textSecondary, fontSize: theme.fontScale.sm }]}>
100
- {bar.value}
101
- </RNText>
102
- </View>
103
- )}
104
- </View>
105
- ))}
106
- </View>
107
- </View>
108
- );
109
- });
110
-
111
- HorizontalBarChart.displayName = 'HorizontalBarChart';
112
-
113
- const styles = StyleSheet.create({
114
- wrapper: { justifyContent: 'flex-start', alignItems: 'flex-start' },
115
- container: { justifyContent: 'center', alignItems: 'center' },
116
- loadingText: { fontSize: 16 },
117
- emptyText: { fontSize: 16 },
118
- title: { textAlign: 'center', width: '100%' },
119
- chartContainer: { flex: 1, width: '100%' },
120
- barRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 12 },
121
- labelContainer: { justifyContent: 'center', paddingRight: 8 },
122
- label: { textAlign: 'right' },
123
- valueContainer: { justifyContent: 'center', paddingLeft: 8 },
124
- value: { textAlign: 'left', fontWeight: '600' },
125
- });
@@ -1,2 +0,0 @@
1
- export { HorizontalBarChart } from './HorizontalBarChart';
2
- export type { HorizontalBarChartWidgetProps, HorizontalBarChartData, HorizontalBarDataPoint } from './types';
@@ -1,23 +0,0 @@
1
- /**
2
- * HorizontalBarChart Widget types
3
- */
4
- import { BaseWidgetProps } from '../../core';
5
-
6
- export interface HorizontalBarDataPoint {
7
- value: number;
8
- label: string;
9
- color?: string;
10
- }
11
-
12
- export interface HorizontalBarChartData {
13
- data: HorizontalBarDataPoint[];
14
- title?: string;
15
- }
16
-
17
- export interface HorizontalBarChartWidgetProps extends BaseWidgetProps<HorizontalBarChartData> {
18
- barHeight?: number;
19
- barSpacing?: number;
20
- showValues?: boolean;
21
- showLabels?: boolean;
22
- maxBars?: number;
23
- }
@@ -1,222 +0,0 @@
1
- /**
2
- * KPI Widget - Show one metric + trend
3
- * First widget implementation following the widget contract
4
- */
5
- import React, { memo, useMemo } from 'react';
6
- import { View, Text, StyleSheet } from 'react-native';
7
- import {
8
- useWidgetDimensions,
9
- useWidgetTheme,
10
- createFontSizeCalculator,
11
- } from '../../core';
12
- import { formatNumber, getTrendColor } from '../../renderer-svg/adapters';
13
- import { KPIWidgetProps } from './types';
14
-
15
- /**
16
- * KPI Widget Component
17
- */
18
- export const KPI = memo<KPIWidgetProps>(({
19
- data,
20
- width,
21
- height,
22
- loading = false,
23
- theme: themeOverride,
24
- animated = true,
25
- showTrend = true,
26
- showDelta = true,
27
- fontSize: fontConfig,
28
- testID,
29
- }) => {
30
- const theme = useWidgetTheme(themeOverride);
31
- const dimensions = useWidgetDimensions(width, height, 300, 120);
32
-
33
- // Create responsive font size calculator
34
- const fontSizes = useMemo(
35
- () => createFontSizeCalculator(dimensions.width, dimensions.height, fontConfig),
36
- [dimensions.width, dimensions.height, fontConfig]
37
- );
38
-
39
- // Handle states
40
- if (loading) {
41
- return (
42
- <View
43
- style={[
44
- styles.container,
45
- {
46
- width: dimensions.width,
47
- height: dimensions.height,
48
- backgroundColor: theme.colors.surface,
49
- borderRadius: theme.radius.md,
50
- },
51
- ]}
52
- testID={`${testID}-loading`}
53
- >
54
- <Text style={[styles.loadingText, { color: theme.colors.textSecondary }]}>
55
- Loading...
56
- </Text>
57
- </View>
58
- );
59
- }
60
-
61
- if (!data) {
62
- return (
63
- <View
64
- style={[
65
- styles.container,
66
- {
67
- width: dimensions.width,
68
- height: dimensions.height,
69
- backgroundColor: theme.colors.surface,
70
- borderRadius: theme.radius.md,
71
- },
72
- ]}
73
- testID={`${testID}-empty`}
74
- >
75
- <Text style={[styles.emptyText, { color: theme.colors.textSecondary }]}>
76
- No data
77
- </Text>
78
- </View>
79
- );
80
- }
81
-
82
- const { value, label, delta, trend = 'neutral', format = 'number' } = data;
83
-
84
- // Format the value (no animation for now - Expo Go limitation)
85
- const displayValue = useMemo(
86
- () => formatNumber(value, format),
87
- [value, format]
88
- );
89
-
90
- // Get trend color
91
- const trendColor = useMemo(
92
- () =>
93
- getTrendColor(trend, {
94
- positive: theme.colors.chartPositive,
95
- negative: theme.colors.chartNegative,
96
- neutral: theme.colors.chartNeutral,
97
- }),
98
- [trend, theme.colors]
99
- );
100
-
101
- // Format delta
102
- const formattedDelta = useMemo(() => {
103
- if (delta === undefined) return null;
104
- const sign = delta >= 0 ? '+' : '';
105
- return `${sign}${delta.toFixed(1)}%`;
106
- }, [delta]);
107
-
108
- // Trend icon
109
- const trendIcon = useMemo(() => {
110
- if (trend === 'up') return '↑';
111
- if (trend === 'down') return '↓';
112
- return '→';
113
- }, [trend]);
114
-
115
- return (
116
- <View
117
- style={[
118
- styles.container,
119
- {
120
- width: dimensions.width,
121
- height: dimensions.height,
122
- backgroundColor: theme.colors.surface,
123
- borderRadius: theme.radius.md,
124
- padding: theme.spacing.md,
125
- },
126
- ]}
127
- testID={testID}
128
- >
129
- {/* Label */}
130
- <Text
131
- style={[
132
- styles.label,
133
- {
134
- color: theme.colors.textSecondary,
135
- fontSize: fontSizes.label(theme.fontScale.sm),
136
- marginBottom: theme.spacing.xs,
137
- },
138
- ]}
139
- >
140
- {label}
141
- </Text>
142
-
143
- {/* Value */}
144
- <Text
145
- style={[
146
- styles.value,
147
- {
148
- color: theme.colors.text,
149
- fontSize: fontSizes.value(theme.fontScale.xxl),
150
- fontWeight: 'bold',
151
- },
152
- ]}
153
- >
154
- {displayValue}
155
- </Text>
156
-
157
- {/* Trend and Delta */}
158
- {(showTrend || showDelta) && (delta !== undefined || trend !== 'neutral') && (
159
- <View style={styles.trendContainer}>
160
- {showTrend && (
161
- <Text
162
- style={[
163
- styles.trendIcon,
164
- {
165
- color: trendColor,
166
- fontSize: fontSizes.secondary(theme.fontScale.md),
167
- marginRight: theme.spacing.xs,
168
- },
169
- ]}
170
- >
171
- {trendIcon}
172
- </Text>
173
- )}
174
- {showDelta && formattedDelta && (
175
- <Text
176
- style={[
177
- styles.delta,
178
- {
179
- color: trendColor,
180
- fontSize: fontSizes.secondary(theme.fontScale.md),
181
- fontWeight: '600',
182
- },
183
- ]}
184
- >
185
- {formattedDelta}
186
- </Text>
187
- )}
188
- </View>
189
- )}
190
- </View>
191
- );
192
- });
193
-
194
- KPI.displayName = 'KPI';
195
-
196
- const styles = StyleSheet.create({
197
- container: {
198
- justifyContent: 'center',
199
- alignItems: 'flex-start',
200
- },
201
- loadingText: {
202
- fontSize: 16,
203
- },
204
- emptyText: {
205
- fontSize: 16,
206
- },
207
- label: {
208
- textTransform: 'uppercase',
209
- letterSpacing: 0.5,
210
- },
211
- value: {
212
- marginBottom: 4,
213
- },
214
- trendContainer: {
215
- flexDirection: 'row',
216
- alignItems: 'center',
217
- },
218
- trendIcon: {
219
- fontWeight: 'bold',
220
- },
221
- delta: {},
222
- });
@@ -1,5 +0,0 @@
1
- /**
2
- * KPI Widget exports
3
- */
4
- export { KPI } from './KPI';
5
- export type { KPIWidgetProps, KPIData, KPIFormat } from './types';
@@ -1,19 +0,0 @@
1
- /**
2
- * KPI Widget types
3
- */
4
- import { BaseWidgetProps, TrendDirection } from '../../core/types';
5
-
6
- export type KPIFormat = 'number' | 'currency' | 'percent' | 'compact';
7
-
8
- export interface KPIData {
9
- value: number;
10
- label: string;
11
- delta?: number;
12
- trend?: TrendDirection;
13
- format?: KPIFormat;
14
- }
15
-
16
- export interface KPIWidgetProps extends BaseWidgetProps<KPIData> {
17
- showTrend?: boolean;
18
- showDelta?: boolean;
19
- }