panelui-native 0.43.0 → 0.46.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 (58) hide show
  1. package/README.md +2 -1
  2. package/lib/module/components/card/index.js +8 -154
  3. package/lib/module/components/card/index.js.map +1 -1
  4. package/lib/module/components/combobox/index.js +7 -1
  5. package/lib/module/components/combobox/index.js.map +1 -1
  6. package/lib/module/components/date-time-picker/index.js +254 -0
  7. package/lib/module/components/date-time-picker/index.js.map +1 -0
  8. package/lib/module/components/grid-item/index.js +486 -0
  9. package/lib/module/components/grid-item/index.js.map +1 -0
  10. package/lib/module/components/{kpi-chart → kpi}/index.js +62 -62
  11. package/lib/module/components/kpi/index.js.map +1 -0
  12. package/lib/module/components/line-chart/index.js +92 -10
  13. package/lib/module/components/line-chart/index.js.map +1 -1
  14. package/lib/module/components/pie-chart/index.js +625 -0
  15. package/lib/module/components/pie-chart/index.js.map +1 -0
  16. package/lib/module/components/scatter-chart/index.js +1173 -0
  17. package/lib/module/components/scatter-chart/index.js.map +1 -0
  18. package/lib/module/components/tabs/index.js +275 -33
  19. package/lib/module/components/tabs/index.js.map +1 -1
  20. package/lib/module/index.js +5 -1
  21. package/lib/module/index.js.map +1 -1
  22. package/lib/module/utils/chart.js +81 -0
  23. package/lib/module/utils/chart.js.map +1 -1
  24. package/lib/typescript/src/components/card/index.d.ts +0 -20
  25. package/lib/typescript/src/components/card/index.d.ts.map +1 -1
  26. package/lib/typescript/src/components/combobox/index.d.ts.map +1 -1
  27. package/lib/typescript/src/components/date-time-picker/index.d.ts +121 -0
  28. package/lib/typescript/src/components/date-time-picker/index.d.ts.map +1 -0
  29. package/lib/typescript/src/components/grid-item/index.d.ts +292 -0
  30. package/lib/typescript/src/components/grid-item/index.d.ts.map +1 -0
  31. package/lib/typescript/src/components/{kpi-chart → kpi}/index.d.ts +65 -65
  32. package/lib/typescript/src/components/kpi/index.d.ts.map +1 -0
  33. package/lib/typescript/src/components/line-chart/index.d.ts +25 -1
  34. package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
  35. package/lib/typescript/src/components/pie-chart/index.d.ts +245 -0
  36. package/lib/typescript/src/components/pie-chart/index.d.ts.map +1 -0
  37. package/lib/typescript/src/components/scatter-chart/index.d.ts +309 -0
  38. package/lib/typescript/src/components/scatter-chart/index.d.ts.map +1 -0
  39. package/lib/typescript/src/components/tabs/index.d.ts +20 -2
  40. package/lib/typescript/src/components/tabs/index.d.ts.map +1 -1
  41. package/lib/typescript/src/index.d.ts +7 -3
  42. package/lib/typescript/src/index.d.ts.map +1 -1
  43. package/lib/typescript/src/utils/chart.d.ts +34 -0
  44. package/lib/typescript/src/utils/chart.d.ts.map +1 -1
  45. package/package.json +1 -1
  46. package/src/components/card/index.tsx +7 -209
  47. package/src/components/combobox/index.tsx +7 -1
  48. package/src/components/date-time-picker/index.tsx +375 -0
  49. package/src/components/grid-item/index.tsx +515 -0
  50. package/src/components/{kpi-chart → kpi}/index.tsx +85 -85
  51. package/src/components/line-chart/index.tsx +98 -8
  52. package/src/components/pie-chart/index.tsx +863 -0
  53. package/src/components/scatter-chart/index.tsx +1401 -0
  54. package/src/components/tabs/index.tsx +305 -35
  55. package/src/index.ts +66 -18
  56. package/src/utils/chart.ts +110 -0
  57. package/lib/module/components/kpi-chart/index.js.map +0 -1
  58. package/lib/typescript/src/components/kpi-chart/index.d.ts.map +0 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * KpiChart — one number, and what it is doing.
2
+ * Kpi — one number, and what it is doing.
3
3
  *
4
4
  * A metric card is not a chart with a caption. The number is the message and
5
5
  * the chart is the footnote, so the parts here are sized and ordered around
@@ -8,16 +8,16 @@
8
8
  * because nobody is reading values off it.
9
9
  *
10
10
  * ```tsx
11
- * <KpiChart>
12
- * <KpiChart.Header>
13
- * <KpiChart.Title>Active users</KpiChart.Title>
14
- * </KpiChart.Header>
15
- * <KpiChart.Content>
16
- * <KpiChart.Value>12,480</KpiChart.Value>
17
- * <KpiChart.Trend value={12.4} />
18
- * </KpiChart.Content>
19
- * <KpiChart.Chart data={week} dataKey="v" />
20
- * </KpiChart>
11
+ * <Kpi>
12
+ * <Kpi.Header>
13
+ * <Kpi.Title>Active users</Kpi.Title>
14
+ * </Kpi.Header>
15
+ * <Kpi.Content>
16
+ * <Kpi.Value>12,480</Kpi.Value>
17
+ * <Kpi.Trend value={12.4} />
18
+ * </Kpi.Content>
19
+ * <Kpi.Chart data={week} dataKey="v" />
20
+ * </Kpi>
21
21
  * ```
22
22
  *
23
23
  * The trend is given a number rather than a written string, so the component
@@ -131,15 +131,15 @@ export type KpiTone = NonNullable<KpiVariantProps['tone']>;
131
131
  /** Which direction of movement is the good news for this metric. */
132
132
  export type KpiGoodDirection = 'up' | 'down' | 'none';
133
133
 
134
- interface KpiChartContextValue {
134
+ interface KpiContextValue {
135
135
  colorIndex: SeriesColorIndex;
136
136
  goodDirection: KpiGoodDirection;
137
137
  }
138
138
 
139
- const KpiChartContext = createContext<KpiChartContextValue | null>(null);
139
+ const KpiContext = createContext<KpiContextValue | null>(null);
140
140
 
141
141
  /**
142
- * Whether the part rendering is inside `KpiChart.Header`.
142
+ * Whether the part rendering is inside `Kpi.Header`.
143
143
  *
144
144
  * `Title` is the only part that cares, and it cares a great deal. In a header
145
145
  * it is one of several things on a row and has to take the space between the
@@ -152,7 +152,7 @@ const KpiChartContext = createContext<KpiChartContextValue | null>(null);
152
152
  const KpiHeaderContext = createContext(false);
153
153
 
154
154
  /**
155
- * Whether the part rendering is inside `KpiChart.Stat`.
155
+ * Whether the part rendering is inside `Kpi.Stat`.
156
156
  *
157
157
  * `Trend` is the one that cares. `self-center` on a flex child means "centre
158
158
  * on the cross axis", and the cross axis is not the same axis in the two
@@ -163,9 +163,9 @@ const KpiHeaderContext = createContext(false);
163
163
  */
164
164
  const KpiStatContext = createContext(false);
165
165
 
166
- function useKpiChart(part: string): KpiChartContextValue {
167
- const context = useContext(KpiChartContext);
168
- if (!context) throw new Error(`${part} must be used inside <KpiChart>.`);
166
+ function useKpi(part: string): KpiContextValue {
167
+ const context = useContext(KpiContext);
168
+ if (!context) throw new Error(`${part} must be used inside <Kpi>.`);
169
169
  return context;
170
170
  }
171
171
 
@@ -173,7 +173,7 @@ function useKpiChart(part: string): KpiChartContextValue {
173
173
  * Root.
174
174
  * ------------------------------------------------------------------ */
175
175
 
176
- export interface KpiChartProps extends Omit<ViewProps, 'children'> {
176
+ export interface KpiProps extends Omit<ViewProps, 'children'> {
177
177
  className?: string;
178
178
  /**
179
179
  * Which `--color-chart-*` token the sparkline and the icon take. Set on the
@@ -192,7 +192,7 @@ export interface KpiChartProps extends Omit<ViewProps, 'children'> {
192
192
  children: ReactNode;
193
193
  }
194
194
 
195
- const KpiChartRoot = forwardRef<View, KpiChartProps>(function KpiChartRoot(
195
+ const KpiRoot = forwardRef<View, KpiProps>(function KpiRoot(
196
196
  { className, colorIndex = 1, goodDirection = 'up', surface = true, children, ...props },
197
197
  ref
198
198
  ) {
@@ -206,7 +206,7 @@ const KpiChartRoot = forwardRef<View, KpiChartProps>(function KpiChartRoot(
206
206
  );
207
207
 
208
208
  return (
209
- <KpiChartContext.Provider value={context}>
209
+ <KpiContext.Provider value={context}>
210
210
  {surface ? (
211
211
  <Surface ref={ref} variant="secondary" padding="lg" className="w-full">
212
212
  {body}
@@ -214,7 +214,7 @@ const KpiChartRoot = forwardRef<View, KpiChartProps>(function KpiChartRoot(
214
214
  ) : (
215
215
  body
216
216
  )}
217
- </KpiChartContext.Provider>
217
+ </KpiContext.Provider>
218
218
  );
219
219
  });
220
220
 
@@ -222,13 +222,13 @@ const KpiChartRoot = forwardRef<View, KpiChartProps>(function KpiChartRoot(
222
222
  * Header, and the things that live in it.
223
223
  * ------------------------------------------------------------------ */
224
224
 
225
- export interface KpiChartHeaderProps extends ViewProps {
225
+ export interface KpiHeaderProps extends ViewProps {
226
226
  className?: string;
227
227
  children: ReactNode;
228
228
  }
229
229
 
230
230
  /** The top row: an icon, the metric's name, and anything acting on it. */
231
- function KpiChartHeader({ className, children, ...props }: KpiChartHeaderProps) {
231
+ function KpiHeader({ className, children, ...props }: KpiHeaderProps) {
232
232
  const { header } = kpiVariants();
233
233
  return (
234
234
  <KpiHeaderContext.Provider value={true}>
@@ -239,7 +239,7 @@ function KpiChartHeader({ className, children, ...props }: KpiChartHeaderProps)
239
239
  );
240
240
  }
241
241
 
242
- export interface KpiChartIconProps extends ViewProps {
242
+ export interface KpiIconProps extends ViewProps {
243
243
  className?: string;
244
244
  /** Overrides the tint the card's `colorIndex` would give it. */
245
245
  tone?: KpiTone;
@@ -253,7 +253,7 @@ export interface KpiChartIconProps extends ViewProps {
253
253
  * from whatever set the app already uses — and an icon from outside this
254
254
  * library will not read an ambient colour, so pass it one.
255
255
  */
256
- function KpiChartIcon({ className, tone = 'neutral', children, ...props }: KpiChartIconProps) {
256
+ function KpiIcon({ className, tone = 'neutral', children, ...props }: KpiIconProps) {
257
257
  const { icon } = kpiVariants({ tone });
258
258
  return (
259
259
  <View className={icon({ className })} {...props}>
@@ -262,13 +262,13 @@ function KpiChartIcon({ className, tone = 'neutral', children, ...props }: KpiCh
262
262
  );
263
263
  }
264
264
 
265
- export interface KpiChartTitleProps {
265
+ export interface KpiTitleProps {
266
266
  className?: string;
267
267
  children: ReactNode;
268
268
  }
269
269
 
270
270
  /** The metric's name. Quiet on purpose — the value is the thing being read. */
271
- function KpiChartTitle({ className, children }: KpiChartTitleProps) {
271
+ function KpiTitle({ className, children }: KpiTitleProps) {
272
272
  const { title } = kpiVariants();
273
273
  // Grows across a header row; never down a column — see `KpiHeaderContext`.
274
274
  const inHeader = useContext(KpiHeaderContext);
@@ -279,7 +279,7 @@ function KpiChartTitle({ className, children }: KpiChartTitleProps) {
279
279
  );
280
280
  }
281
281
 
282
- export interface KpiChartStatProps extends ViewProps {
282
+ export interface KpiStatProps extends ViewProps {
283
283
  className?: string;
284
284
  children: ReactNode;
285
285
  }
@@ -293,7 +293,7 @@ export interface KpiChartStatProps extends ViewProps {
293
293
  * between facts. It also takes the width in an `inline` row, leaving the chart
294
294
  * its column on the end.
295
295
  */
296
- function KpiChartStat({ className, children, ...props }: KpiChartStatProps) {
296
+ function KpiStat({ className, children, ...props }: KpiStatProps) {
297
297
  const { stat } = kpiVariants();
298
298
  return (
299
299
  <KpiStatContext.Provider value={true}>
@@ -304,13 +304,13 @@ function KpiChartStat({ className, children, ...props }: KpiChartStatProps) {
304
304
  );
305
305
  }
306
306
 
307
- export interface KpiChartActionsProps extends ViewProps {
307
+ export interface KpiActionsProps extends ViewProps {
308
308
  className?: string;
309
309
  children: ReactNode;
310
310
  }
311
311
 
312
312
  /** The trailing end of the header — a menu trigger, a filter, a link. */
313
- function KpiChartActions({ className, children, ...props }: KpiChartActionsProps) {
313
+ function KpiActions({ className, children, ...props }: KpiActionsProps) {
314
314
  return (
315
315
  <View className={cn('flex-row items-center gap-1', className)} {...props}>
316
316
  {children}
@@ -322,7 +322,7 @@ function KpiChartActions({ className, children, ...props }: KpiChartActionsProps
322
322
  * The number, and what it is doing.
323
323
  * ------------------------------------------------------------------ */
324
324
 
325
- export interface KpiChartContentProps extends ViewProps {
325
+ export interface KpiContentProps extends ViewProps {
326
326
  className?: string;
327
327
  /** `inline` puts the chart beside the value instead of under everything. */
328
328
  layout?: NonNullable<KpiVariantProps['layout']>;
@@ -330,12 +330,12 @@ export interface KpiChartContentProps extends ViewProps {
330
330
  }
331
331
 
332
332
  /** The row the value and the trend share. */
333
- function KpiChartContent({
333
+ function KpiContent({
334
334
  className,
335
335
  layout = 'below',
336
336
  children,
337
337
  ...props
338
- }: KpiChartContentProps) {
338
+ }: KpiContentProps) {
339
339
  const { content } = kpiVariants({ layout });
340
340
  return (
341
341
  <View className={content({ className })} {...props}>
@@ -344,7 +344,7 @@ function KpiChartContent({
344
344
  );
345
345
  }
346
346
 
347
- export interface KpiChartValueProps {
347
+ export interface KpiValueProps {
348
348
  className?: string;
349
349
  children: ReactNode;
350
350
  }
@@ -356,7 +356,7 @@ export interface KpiChartValueProps {
356
356
  * and units are locale decisions, and a component that guessed them would be
357
357
  * wrong in a way that is hard to notice and impossible to override.
358
358
  */
359
- function KpiChartValue({ className, children }: KpiChartValueProps) {
359
+ function KpiValue({ className, children }: KpiValueProps) {
360
360
  const { value } = kpiVariants();
361
361
  return (
362
362
  <Text className={value({ className })} numberOfLines={1}>
@@ -365,7 +365,7 @@ function KpiChartValue({ className, children }: KpiChartValueProps) {
365
365
  );
366
366
  }
367
367
 
368
- export interface KpiChartTrendProps extends Omit<ViewProps, 'children'> {
368
+ export interface KpiTrendProps extends Omit<ViewProps, 'children'> {
369
369
  className?: string;
370
370
  /**
371
371
  * How much it moved, as a percentage. The sign carries the direction, so
@@ -403,7 +403,7 @@ export interface KpiChartTrendProps extends Omit<ViewProps, 'children'> {
403
403
  * label has to remember to change the colour when the metric changes, and
404
404
  * nobody does.
405
405
  */
406
- function KpiChartTrend({
406
+ function KpiTrend({
407
407
  className,
408
408
  value,
409
409
  format,
@@ -413,8 +413,8 @@ function KpiChartTrend({
413
413
  threshold = 0,
414
414
  children,
415
415
  ...props
416
- }: KpiChartTrendProps) {
417
- const context = useKpiChart('KpiChart.Trend');
416
+ }: KpiTrendProps) {
417
+ const context = useKpi('Kpi.Trend');
418
418
  const polarity = goodDirection ?? context.goodDirection;
419
419
 
420
420
  const flat = Math.abs(value) <= threshold;
@@ -478,7 +478,7 @@ function KpiChartTrend({
478
478
  * The chart, and the bar.
479
479
  * ------------------------------------------------------------------ */
480
480
 
481
- export interface KpiChartSparklineProps {
481
+ export interface KpiSparklineProps {
482
482
  className?: string;
483
483
  /** The rows. One point each, in order. */
484
484
  data: LineChartDatum[];
@@ -510,7 +510,7 @@ export interface KpiChartSparklineProps {
510
510
  * every point of padding is a point the shape is not using. Nobody reads a
511
511
  * value off one of these; they read whether it is going up.
512
512
  */
513
- function KpiChartSparkline({
513
+ function KpiSparkline({
514
514
  className,
515
515
  data,
516
516
  dataKey,
@@ -519,8 +519,8 @@ function KpiChartSparkline({
519
519
  height,
520
520
  inline = false,
521
521
  strokeWidth = 2,
522
- }: KpiChartSparklineProps) {
523
- const context = useKpiChart('KpiChart.Chart');
522
+ }: KpiSparklineProps) {
523
+ const context = useKpi('Kpi.Chart');
524
524
  const index = colorIndex ?? context.colorIndex;
525
525
  const fill = filled ?? !inline;
526
526
 
@@ -541,7 +541,7 @@ function KpiChartSparkline({
541
541
  );
542
542
  }
543
543
 
544
- export interface KpiChartProgressProps {
544
+ export interface KpiProgressProps {
545
545
  className?: string;
546
546
  /** Where it has got to. */
547
547
  value: number;
@@ -554,13 +554,13 @@ export interface KpiChartProgressProps {
554
554
  }
555
555
 
556
556
  /** Progress towards a target, for a metric that has one. */
557
- function KpiChartProgressBar({
557
+ function KpiProgressBar({
558
558
  className,
559
559
  value,
560
560
  maxValue = 100,
561
561
  label,
562
562
  showValueLabel,
563
- }: KpiChartProgressProps) {
563
+ }: KpiProgressProps) {
564
564
  return (
565
565
  <Progress
566
566
  className={className}
@@ -577,13 +577,13 @@ function KpiChartProgressBar({
577
577
  * Footer and separator.
578
578
  * ------------------------------------------------------------------ */
579
579
 
580
- export interface KpiChartFooterProps extends ViewProps {
580
+ export interface KpiFooterProps extends ViewProps {
581
581
  className?: string;
582
582
  children: ReactNode;
583
583
  }
584
584
 
585
585
  /** The bottom strip — a comparison period, a caveat, a link. */
586
- function KpiChartFooter({ className, children, ...props }: KpiChartFooterProps) {
586
+ function KpiFooter({ className, children, ...props }: KpiFooterProps) {
587
587
  const { footer } = kpiVariants();
588
588
  return (
589
589
  <View className={footer({ className })} {...props}>
@@ -592,12 +592,12 @@ function KpiChartFooter({ className, children, ...props }: KpiChartFooterProps)
592
592
  );
593
593
  }
594
594
 
595
- export interface KpiChartSeparatorProps extends ViewProps {
595
+ export interface KpiSeparatorProps extends ViewProps {
596
596
  className?: string;
597
597
  }
598
598
 
599
599
  /** A hairline across the card. */
600
- function KpiChartSeparator({ className, ...props }: KpiChartSeparatorProps) {
600
+ function KpiSeparator({ className, ...props }: KpiSeparatorProps) {
601
601
  const { separator } = kpiVariants();
602
602
  return <View className={separator({ className })} {...props} />;
603
603
  }
@@ -606,12 +606,12 @@ function KpiChartSeparator({ className, ...props }: KpiChartSeparatorProps) {
606
606
  * Group.
607
607
  * ------------------------------------------------------------------ */
608
608
 
609
- export type KpiChartGroupOrientation = 'horizontal' | 'vertical';
609
+ export type KpiGroupOrientation = 'horizontal' | 'vertical';
610
610
 
611
- export interface KpiChartGroupProps extends ViewProps {
611
+ export interface KpiGroupProps extends ViewProps {
612
612
  className?: string;
613
613
  /** `horizontal` splits the row between the cards; `vertical` stacks them. */
614
- orientation?: KpiChartGroupOrientation;
614
+ orientation?: KpiGroupOrientation;
615
615
  /**
616
616
  * Draw a hairline between the cards rather than spacing them apart. Several
617
617
  * metrics separated by a rule read as one panel; several spaced apart read
@@ -628,7 +628,7 @@ export interface KpiChartGroupProps extends ViewProps {
628
628
  * caller, because "between" is the one thing a list of siblings cannot express
629
629
  * — a trailing rule after the last card is the mistake this exists to prevent.
630
630
  */
631
- const KpiChartGroup = forwardRef<View, KpiChartGroupProps>(function KpiChartGroup(
631
+ const KpiGroup = forwardRef<View, KpiGroupProps>(function KpiGroup(
632
632
  { className, orientation = 'horizontal', separated = true, children, ...props },
633
633
  ref
634
634
  ) {
@@ -687,33 +687,33 @@ const KpiChartGroup = forwardRef<View, KpiChartGroupProps>(function KpiChartGrou
687
687
  );
688
688
  });
689
689
 
690
- KpiChartRoot.displayName = 'KpiChart';
691
- KpiChartHeader.displayName = 'KpiChart.Header';
692
- KpiChartIcon.displayName = 'KpiChart.Icon';
693
- KpiChartTitle.displayName = 'KpiChart.Title';
694
- KpiChartStat.displayName = 'KpiChart.Stat';
695
- KpiChartActions.displayName = 'KpiChart.Actions';
696
- KpiChartContent.displayName = 'KpiChart.Content';
697
- KpiChartValue.displayName = 'KpiChart.Value';
698
- KpiChartTrend.displayName = 'KpiChart.Trend';
699
- KpiChartSparkline.displayName = 'KpiChart.Chart';
700
- KpiChartProgressBar.displayName = 'KpiChart.Progress';
701
- KpiChartFooter.displayName = 'KpiChart.Footer';
702
- KpiChartSeparator.displayName = 'KpiChart.Separator';
703
- KpiChartGroup.displayName = 'KpiChart.Group';
704
-
705
- export const KpiChart = Object.assign(KpiChartRoot, {
706
- Header: KpiChartHeader,
707
- Icon: KpiChartIcon,
708
- Title: KpiChartTitle,
709
- Stat: KpiChartStat,
710
- Actions: KpiChartActions,
711
- Content: KpiChartContent,
712
- Value: KpiChartValue,
713
- Trend: KpiChartTrend,
714
- Chart: KpiChartSparkline,
715
- Progress: KpiChartProgressBar,
716
- Footer: KpiChartFooter,
717
- Separator: KpiChartSeparator,
718
- Group: KpiChartGroup,
690
+ KpiRoot.displayName = 'Kpi';
691
+ KpiHeader.displayName = 'Kpi.Header';
692
+ KpiIcon.displayName = 'Kpi.Icon';
693
+ KpiTitle.displayName = 'Kpi.Title';
694
+ KpiStat.displayName = 'Kpi.Stat';
695
+ KpiActions.displayName = 'Kpi.Actions';
696
+ KpiContent.displayName = 'Kpi.Content';
697
+ KpiValue.displayName = 'Kpi.Value';
698
+ KpiTrend.displayName = 'Kpi.Trend';
699
+ KpiSparkline.displayName = 'Kpi.Chart';
700
+ KpiProgressBar.displayName = 'Kpi.Progress';
701
+ KpiFooter.displayName = 'Kpi.Footer';
702
+ KpiSeparator.displayName = 'Kpi.Separator';
703
+ KpiGroup.displayName = 'Kpi.Group';
704
+
705
+ export const Kpi = Object.assign(KpiRoot, {
706
+ Header: KpiHeader,
707
+ Icon: KpiIcon,
708
+ Title: KpiTitle,
709
+ Stat: KpiStat,
710
+ Actions: KpiActions,
711
+ Content: KpiContent,
712
+ Value: KpiValue,
713
+ Trend: KpiTrend,
714
+ Chart: KpiSparkline,
715
+ Progress: KpiProgressBar,
716
+ Footer: KpiFooter,
717
+ Separator: KpiSeparator,
718
+ Group: KpiGroup,
719
719
  });
@@ -101,6 +101,12 @@ const AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient);
101
101
  /** Room left around the plot for the axis labels and the marker rings. */
102
102
  const PADDING = { top: 12, right: 10, bottom: 22, left: 10 };
103
103
 
104
+ /** Left gutter reserved when a `YAxis` is present, for its labels to sit in. */
105
+ const Y_AXIS_WIDTH = 44;
106
+
107
+ /** Gap between the value labels and the plot they sit beside. */
108
+ const Y_AXIS_GUTTER = 6;
109
+
104
110
  /**
105
111
  * Which layer a part belongs to. Read off the component itself, so composition
106
112
  * stays a flat list of children instead of two nested slots the caller has to
@@ -125,6 +131,12 @@ interface LineChartContextValue {
125
131
  /** Tweened y-domain. Read inside worklets to build the paths. */
126
132
  domainMin: SharedValue<number>;
127
133
  domainMax: SharedValue<number>;
134
+ /**
135
+ * The domain the tween is heading for. The axis labels read this rather than
136
+ * the shared values: a number re-rendered on every frame of a 500ms tween is
137
+ * 30 renders of a label that lands on the same string it started on.
138
+ */
139
+ extent: [number, number];
128
140
  /** Index under the finger, or -1 when nothing is being touched. */
129
141
  activeIndex: SharedValue<number>;
130
142
  activeIndexJS: number;
@@ -246,10 +258,28 @@ const LineChartRoot = forwardRef<LineChartHandle, LineChartProps>(function LineC
246
258
  []
247
259
  );
248
260
 
261
+ /*
262
+ * Whether a `YAxis` was declared, read off the children before anything is
263
+ * laid out. The gutter its labels sit in has to come off the plot's width
264
+ * *before* the plot exists — an axis given no room is drawn over the series,
265
+ * which loses both the numbers and the shape they were meant to explain.
266
+ */
267
+ const hasYAxis = useMemo(() => {
268
+ let found = false;
269
+ Children.forEach(children, (child) => {
270
+ if (isValidElement(child) && (child.type as { axis?: string }).axis === 'y') {
271
+ found = true;
272
+ }
273
+ });
274
+ return found;
275
+ }, [children]);
276
+
249
277
  // A sparkline has no axis or grid to leave room for, so the line reaches the
250
278
  // edges; a stroke of a couple of pixels still needs a hair of inset not to be
251
279
  // clipped at the very top and bottom.
252
- const pad = compact ? { top: 2, right: 1, bottom: 2, left: 1 } : PADDING;
280
+ const pad = compact
281
+ ? { top: 2, right: 1, bottom: 2, left: 1 }
282
+ : { ...PADDING, left: hasYAxis ? Y_AXIS_WIDTH : PADDING.left };
253
283
  const plot: Plot = {
254
284
  left: pad.left,
255
285
  top: pad.top,
@@ -360,6 +390,7 @@ const LineChartRoot = forwardRef<LineChartHandle, LineChartProps>(function LineC
360
390
  unregisterSeries,
361
391
  domainMin,
362
392
  domainMax,
393
+ extent,
363
394
  activeIndex,
364
395
  activeIndexJS,
365
396
  setActiveIndexJS: handleActiveIndex,
@@ -381,6 +412,7 @@ const LineChartRoot = forwardRef<LineChartHandle, LineChartProps>(function LineC
381
412
  unregisterSeries,
382
413
  domainMin,
383
414
  domainMax,
415
+ extent,
384
416
  activeIndex,
385
417
  activeIndexJS,
386
418
  handleActiveIndex,
@@ -789,6 +821,69 @@ function LineChartXAxis({ ticks = 4, format, className }: LineChartXAxisProps) {
789
821
  LineChartXAxis.displayName = 'LineChart.XAxis';
790
822
  LineChartXAxis.layer = 'overlay' as Layer;
791
823
 
824
+ export interface LineChartYAxisProps {
825
+ /** How many intervals to divide the axis into. Yields `ticks + 1` labels. */
826
+ ticks?: number;
827
+ /** Turn a value into its label. Defaults to a compact number. */
828
+ format?: (value: number) => string;
829
+ className?: string;
830
+ }
831
+
832
+ /**
833
+ * Value labels down the side, one per grid line.
834
+ *
835
+ * Give it the same `ticks` as the grid, or the numbers name lines that are not
836
+ * there. Four is the default on both for that reason.
837
+ *
838
+ * The labels are the domain the data settles at, not the tweening one — a
839
+ * number that counts up through every intermediate value while the axis
840
+ * animates is noise, and the axis is the part of the chart that is supposed to
841
+ * hold still enough to read.
842
+ */
843
+ function LineChartYAxis({ ticks = 4, format, className }: LineChartYAxisProps) {
844
+ const { plot, extent } = useChart('LineChart.YAxis');
845
+
846
+ const labels = useMemo(() => {
847
+ const [min, max] = extent;
848
+ if (min === 0 && max === 0) return [];
849
+ return Array.from({ length: ticks + 1 }, (_unused, index) => {
850
+ const value = max - ((max - min) * index) / ticks;
851
+ return { key: index, text: format ? format(value) : compactNumber(value) };
852
+ });
853
+ }, [extent, ticks, format]);
854
+
855
+ return (
856
+ <View
857
+ pointerEvents="none"
858
+ style={{
859
+ position: 'absolute',
860
+ left: 0,
861
+ // Centred on the grid line each label names: the strip is lifted half
862
+ // a label and grown by a whole one, so `justify-between` lands the
863
+ // text's middle on the line rather than its top edge on the first line
864
+ // and its bottom edge on the last.
865
+ top: plot.top - AXIS_LABEL_HEIGHT / 2,
866
+ height: plot.height + AXIS_LABEL_HEIGHT,
867
+ // The gutter the root reserved, less a little breathing room, so the
868
+ // numbers sit clear of the plot instead of against it.
869
+ width: Math.max(plot.left - Y_AXIS_GUTTER, 0),
870
+ }}
871
+ className={cn('items-end justify-between', className)}
872
+ >
873
+ {labels.map((label) => (
874
+ <Text key={label.key} size="xs" muted numberOfLines={1}>
875
+ {label.text}
876
+ </Text>
877
+ ))}
878
+ </View>
879
+ );
880
+ }
881
+ LineChartYAxis.displayName = 'LineChart.YAxis';
882
+ LineChartYAxis.layer = 'overlay' as Layer;
883
+ // Read by the root, which has to leave room for the labels before it lays the
884
+ // plot out.
885
+ LineChartYAxis.axis = 'y' as const;
886
+
792
887
  export interface LineChartTooltipProps {
793
888
  color?: string;
794
889
  /**
@@ -994,7 +1089,7 @@ const POINT_LABEL_WIDTH = 56;
994
1089
  /** Line height of an `xs` label, for centring one on the grid line it names. */
995
1090
  const AXIS_LABEL_HEIGHT = 16;
996
1091
 
997
- /** A compact number for the crosshair label: 1204 → "1.2k", 24801 → "24.8k". */
1092
+ /** Diameter of the dot that rides a series under the crosshair. */
998
1093
  const DOT = 9;
999
1094
 
1000
1095
  /** The dot riding one series under the crosshair. */
@@ -1177,12 +1272,6 @@ function LineChartHeader({
1177
1272
  LineChartHeader.displayName = 'LineChart.Header';
1178
1273
  LineChartHeader.layer = 'header' as Layer;
1179
1274
 
1180
- /* -------------------------------------------------------------------------- */
1181
- /* Scales and paths */
1182
- /* -------------------------------------------------------------------------- */
1183
-
1184
- /** Only reached if the theme CSS was never imported. */
1185
-
1186
1275
  export const LineChart = Object.assign(LineChartRoot, {
1187
1276
  Header: LineChartHeader,
1188
1277
  Grid: LineChartGrid,
@@ -1190,6 +1279,7 @@ export const LineChart = Object.assign(LineChartRoot, {
1190
1279
  Line: LineChartLine,
1191
1280
  Skeleton: LineChartSkeleton,
1192
1281
  XAxis: LineChartXAxis,
1282
+ YAxis: LineChartYAxis,
1193
1283
  Tooltip: LineChartTooltip,
1194
1284
  Legend: LineChartLegend,
1195
1285
  });