panelui-native 0.60.0 → 0.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +3 -1
  2. package/lib/module/components/area-chart/index.js +86 -1
  3. package/lib/module/components/area-chart/index.js.map +1 -1
  4. package/lib/module/components/bar-chart/index.js +112 -2
  5. package/lib/module/components/bar-chart/index.js.map +1 -1
  6. package/lib/module/components/live-line-chart/index.js +1063 -0
  7. package/lib/module/components/live-line-chart/index.js.map +1 -0
  8. package/lib/module/components/polar-area-chart/index.js +719 -0
  9. package/lib/module/components/polar-area-chart/index.js.map +1 -0
  10. package/lib/module/components/treemap-chart/index.js +957 -0
  11. package/lib/module/components/treemap-chart/index.js.map +1 -0
  12. package/lib/module/index.js +3 -0
  13. package/lib/module/index.js.map +1 -1
  14. package/lib/module/utils/chart.js +171 -4
  15. package/lib/module/utils/chart.js.map +1 -1
  16. package/lib/typescript/src/components/area-chart/index.d.ts +28 -1
  17. package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -1
  18. package/lib/typescript/src/components/bar-chart/index.d.ts +33 -3
  19. package/lib/typescript/src/components/bar-chart/index.d.ts.map +1 -1
  20. package/lib/typescript/src/components/live-line-chart/index.d.ts +293 -0
  21. package/lib/typescript/src/components/live-line-chart/index.d.ts.map +1 -0
  22. package/lib/typescript/src/components/polar-area-chart/index.d.ts +259 -0
  23. package/lib/typescript/src/components/polar-area-chart/index.d.ts.map +1 -0
  24. package/lib/typescript/src/components/treemap-chart/index.d.ts +317 -0
  25. package/lib/typescript/src/components/treemap-chart/index.d.ts.map +1 -0
  26. package/lib/typescript/src/index.d.ts +3 -0
  27. package/lib/typescript/src/index.d.ts.map +1 -1
  28. package/lib/typescript/src/utils/chart.d.ts +27 -1
  29. package/lib/typescript/src/utils/chart.d.ts.map +1 -1
  30. package/package.json +1 -1
  31. package/src/components/area-chart/index.tsx +86 -1
  32. package/src/components/bar-chart/index.tsx +115 -4
  33. package/src/components/live-line-chart/index.tsx +1294 -0
  34. package/src/components/polar-area-chart/index.tsx +928 -0
  35. package/src/components/treemap-chart/index.tsx +1175 -0
  36. package/src/index.ts +52 -0
  37. package/src/utils/chart.ts +202 -4
@@ -52,15 +52,17 @@ import { StyleSheet, View, type LayoutChangeEvent, type ViewProps } from 'react-
52
52
  import { Gesture, GestureDetector } from 'react-native-gesture-handler';
53
53
  import Animated, {
54
54
  Easing,
55
+ cancelAnimation,
55
56
  runOnJS,
56
57
  useAnimatedProps,
57
58
  useAnimatedStyle,
58
59
  useReducedMotion,
59
60
  useSharedValue,
61
+ withRepeat,
60
62
  withTiming,
61
63
  type SharedValue,
62
64
  } from 'react-native-reanimated';
63
- import Svg, { G, Line as SvgLine, Path } from 'react-native-svg';
65
+ import Svg, { Defs, G, Line as SvgLine, LinearGradient, Path, Stop } from 'react-native-svg';
64
66
  import { useCSSVariable } from 'uniwind';
65
67
  import { Text } from '../../primitives/text';
66
68
  import {
@@ -75,6 +77,7 @@ import {
75
77
  import { cn } from '../../utils/cn';
76
78
 
77
79
  const AnimatedPath = Animated.createAnimatedComponent(Path);
80
+ const AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient);
78
81
 
79
82
  /** Room left around the plot for the axis labels. */
80
83
  const PADDING = { top: 12, right: 10, bottom: 22, left: 10 };
@@ -163,9 +166,10 @@ export interface BarChartProps extends ViewProps {
163
166
  /** Key holding the category label. Used by the axis and the readout. */
164
167
  xDataKey?: string;
165
168
  /**
166
- * `loading` draws a row of flat placeholder bars and grows them into the real
167
- * ones when it turns `ready`. One component throughout, rather than a spinner
168
- * swapped for a chart — swapping loses the transition.
169
+ * `loading` holds the bars at the baseline and grows them into the real ones
170
+ * when it turns `ready`. One component throughout, rather than a spinner
171
+ * swapped for a chart — swapping loses the transition. Add a
172
+ * `BarChart.Skeleton` for something to stand in the plot meanwhile.
169
173
  */
170
174
  status?: BarChartStatus;
171
175
  /** Width ÷ height. `2` is the wide card shape. */
@@ -738,6 +742,112 @@ function BarChartBar({ dataKey, color, colorIndex = 1, cornerRadius }: BarChartB
738
742
  BarChartBar.displayName = 'BarChart.Bar';
739
743
  BarChartBar.layer = 'svg' as Layer;
740
744
 
745
+ /** How much of the value axis a placeholder bar takes. */
746
+ const SKELETON_LENGTH = 0.22;
747
+
748
+ /** Bands to draw when there is no data yet to count them from. */
749
+ const SKELETON_BARS = 7;
750
+
751
+ export interface BarChartSkeletonProps {
752
+ /**
753
+ * How many placeholder bars to draw. Defaults to one per row, and to seven
754
+ * when the data has not arrived — the count is the one thing a loading
755
+ * chart can be honest about only if it already has the rows.
756
+ */
757
+ bars?: number;
758
+ /** Milliseconds for one pass of the sweep. */
759
+ duration?: number;
760
+ color?: string;
761
+ }
762
+
763
+ /**
764
+ * The loading state: a row of short, equal stubs on the baseline, with a
765
+ * highlight travelling across them.
766
+ *
767
+ * Equal on purpose. Placeholder bars of differing heights are a distribution
768
+ * the reader has no way to tell from the real one until it changes under them,
769
+ * so these say only how many bars there will be and where the baseline is.
770
+ *
771
+ * The sweep is the part that carries the meaning. Without it a chart waiting
772
+ * for data and a chart whose values are all zero draw the same picture, and
773
+ * the reader is left to guess which one they are looking at.
774
+ */
775
+ function BarChartSkeleton({ bars, duration = 1400, color }: BarChartSkeletonProps) {
776
+ const { plot, status, orientation, data, barGap, barWidth, cornerRadius } =
777
+ useChart('BarChart.Skeleton');
778
+ const token = useCSSVariable('--color-skeleton');
779
+ const base = color ?? (typeof token === 'string' ? token : 'rgba(128,128,128,0.2)');
780
+ const highlight = useSeriesColor(undefined, 1);
781
+
782
+ const sweep = useSharedValue(0);
783
+ const reducedMotion = useReducedMotion();
784
+ const loading = status === 'loading';
785
+
786
+ useEffect(() => {
787
+ if (!loading || reducedMotion) {
788
+ cancelAnimation(sweep);
789
+ sweep.value = 0;
790
+ return;
791
+ }
792
+ sweep.value = 0;
793
+ sweep.value = withRepeat(withTiming(1, { duration, easing: Easing.linear }), -1, false);
794
+ return () => cancelAnimation(sweep);
795
+ }, [loading, reducedMotion, duration, sweep]);
796
+
797
+ // The band travels by moving the gradient's own endpoints, so the whole
798
+ // effect is two numbers changing on the UI thread.
799
+ const animatedProps = useAnimatedProps(() => ({
800
+ x1: `${(sweep.value * 1.4 - 0.4) * 100}%`,
801
+ x2: `${(sweep.value * 1.4 - 0.4 + 0.4) * 100}%`,
802
+ }));
803
+
804
+ const horizontal = orientation === 'horizontal';
805
+ const total = Math.max(1, bars ?? (data.length || SKELETON_BARS));
806
+
807
+ const d = useMemo(() => {
808
+ if (plot.width <= 0 || plot.height <= 0) return '';
809
+
810
+ // The same two axes the bars use: `along` is the category one, `across`
811
+ // the value one, so a sideways chart is this code with the pair swapped.
812
+ const along = horizontal ? plot.height : plot.width;
813
+ const alongStart = horizontal ? plot.top : plot.left;
814
+ const across = horizontal ? plot.width : plot.height;
815
+
816
+ const band = along / total;
817
+ const usable = band * (1 - barGap);
818
+ const thickness = Math.min(barWidth ?? usable, usable);
819
+ const length = across * SKELETON_LENGTH;
820
+
821
+ let path = '';
822
+ for (let i = 0; i < total; i += 1) {
823
+ const lead = alongStart + i * band + (band - thickness) / 2;
824
+ path += horizontal
825
+ ? barPath(plot.left, lead, length, thickness, cornerRadius, 'right')
826
+ : barPath(lead, plot.top + plot.height - length, thickness, length, cornerRadius, 'up');
827
+ }
828
+ return path;
829
+ }, [plot, horizontal, total, barGap, barWidth, cornerRadius]);
830
+
831
+ if (!loading || !d) return null;
832
+
833
+ const gradientId = 'panelui-bar-skeleton';
834
+
835
+ return (
836
+ <G>
837
+ <Defs>
838
+ <AnimatedLinearGradient id={gradientId} animatedProps={animatedProps} y1="0" y2="0">
839
+ <Stop offset="0" stopColor={base} />
840
+ <Stop offset="0.5" stopColor={highlight} stopOpacity={0.55} />
841
+ <Stop offset="1" stopColor={base} />
842
+ </AnimatedLinearGradient>
843
+ </Defs>
844
+ <Path d={d} fill={`url(#${gradientId})`} />
845
+ </G>
846
+ );
847
+ }
848
+ BarChartSkeleton.displayName = 'BarChart.Skeleton';
849
+ BarChartSkeleton.layer = 'svg' as Layer;
850
+
741
851
  /* -------------------------------------------------------------------------- */
742
852
  /* Overlay layer */
743
853
  /* -------------------------------------------------------------------------- */
@@ -1201,6 +1311,7 @@ export const BarChart = Object.assign(BarChartRoot, {
1201
1311
  Header: BarChartHeader,
1202
1312
  Grid: BarChartGrid,
1203
1313
  Bar: BarChartBar,
1314
+ Skeleton: BarChartSkeleton,
1204
1315
  XAxis: BarChartXAxis,
1205
1316
  YAxis: BarChartYAxis,
1206
1317
  Tooltip: BarChartTooltip,