panelui-native 0.40.0 → 0.43.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 (92) hide show
  1. package/README.md +4 -2
  2. package/lib/module/components/card/index.js +172 -3
  3. package/lib/module/components/card/index.js.map +1 -1
  4. package/lib/module/components/color-picker/index.js +1395 -0
  5. package/lib/module/components/color-picker/index.js.map +1 -0
  6. package/lib/module/components/combobox/index.js +87 -19
  7. package/lib/module/components/combobox/index.js.map +1 -1
  8. package/lib/module/components/drawer/index.js +13 -4
  9. package/lib/module/components/drawer/index.js.map +1 -1
  10. package/lib/module/components/kpi-chart/index.js +648 -0
  11. package/lib/module/components/kpi-chart/index.js.map +1 -0
  12. package/lib/module/components/map/index.js +72 -12
  13. package/lib/module/components/map/index.js.map +1 -1
  14. package/lib/module/components/menu/index.js +40 -10
  15. package/lib/module/components/menu/index.js.map +1 -1
  16. package/lib/module/components/pagination/index.js +19 -9
  17. package/lib/module/components/pagination/index.js.map +1 -1
  18. package/lib/module/components/panelside/index.js +68 -18
  19. package/lib/module/components/panelside/index.js.map +1 -1
  20. package/lib/module/components/radar-chart/index.js +704 -0
  21. package/lib/module/components/radar-chart/index.js.map +1 -0
  22. package/lib/module/components/section-rail/index.js +32 -7
  23. package/lib/module/components/section-rail/index.js.map +1 -1
  24. package/lib/module/components/slider/index.js +240 -39
  25. package/lib/module/components/slider/index.js.map +1 -1
  26. package/lib/module/components/tabs/index.js +194 -10
  27. package/lib/module/components/tabs/index.js.map +1 -1
  28. package/lib/module/components/text-animation/index.js +857 -0
  29. package/lib/module/components/text-animation/index.js.map +1 -0
  30. package/lib/module/icons/index.js +25 -7
  31. package/lib/module/icons/index.js.map +1 -1
  32. package/lib/module/index.js +5 -0
  33. package/lib/module/index.js.map +1 -1
  34. package/lib/module/theme/use-theme.js +1 -1
  35. package/lib/module/utils/chart.js +44 -0
  36. package/lib/module/utils/chart.js.map +1 -1
  37. package/lib/module/utils/color.js +268 -0
  38. package/lib/module/utils/color.js.map +1 -0
  39. package/lib/typescript/src/components/card/index.d.ts +20 -0
  40. package/lib/typescript/src/components/card/index.d.ts.map +1 -1
  41. package/lib/typescript/src/components/color-picker/index.d.ts +258 -0
  42. package/lib/typescript/src/components/color-picker/index.d.ts.map +1 -0
  43. package/lib/typescript/src/components/combobox/index.d.ts.map +1 -1
  44. package/lib/typescript/src/components/drawer/index.d.ts +13 -1
  45. package/lib/typescript/src/components/drawer/index.d.ts.map +1 -1
  46. package/lib/typescript/src/components/kpi-chart/index.d.ts +490 -0
  47. package/lib/typescript/src/components/kpi-chart/index.d.ts.map +1 -0
  48. package/lib/typescript/src/components/map/index.d.ts +37 -2
  49. package/lib/typescript/src/components/map/index.d.ts.map +1 -1
  50. package/lib/typescript/src/components/menu/index.d.ts.map +1 -1
  51. package/lib/typescript/src/components/pagination/index.d.ts.map +1 -1
  52. package/lib/typescript/src/components/panelside/index.d.ts +50 -4
  53. package/lib/typescript/src/components/panelside/index.d.ts.map +1 -1
  54. package/lib/typescript/src/components/radar-chart/index.d.ts +214 -0
  55. package/lib/typescript/src/components/radar-chart/index.d.ts.map +1 -0
  56. package/lib/typescript/src/components/section-rail/index.d.ts +8 -1
  57. package/lib/typescript/src/components/section-rail/index.d.ts.map +1 -1
  58. package/lib/typescript/src/components/slider/index.d.ts +19 -0
  59. package/lib/typescript/src/components/slider/index.d.ts.map +1 -1
  60. package/lib/typescript/src/components/tabs/index.d.ts +11 -1
  61. package/lib/typescript/src/components/tabs/index.d.ts.map +1 -1
  62. package/lib/typescript/src/components/text-animation/index.d.ts +268 -0
  63. package/lib/typescript/src/components/text-animation/index.d.ts.map +1 -0
  64. package/lib/typescript/src/icons/index.d.ts +6 -0
  65. package/lib/typescript/src/icons/index.d.ts.map +1 -1
  66. package/lib/typescript/src/index.d.ts +8 -3
  67. package/lib/typescript/src/index.d.ts.map +1 -1
  68. package/lib/typescript/src/utils/chart.d.ts +21 -0
  69. package/lib/typescript/src/utils/chart.d.ts.map +1 -1
  70. package/lib/typescript/src/utils/color.d.ts +74 -0
  71. package/lib/typescript/src/utils/color.d.ts.map +1 -0
  72. package/package.json +2 -1
  73. package/src/components/card/index.tsx +227 -2
  74. package/src/components/color-picker/index.tsx +1618 -0
  75. package/src/components/combobox/index.tsx +89 -19
  76. package/src/components/drawer/index.tsx +31 -9
  77. package/src/components/kpi-chart/index.tsx +719 -0
  78. package/src/components/map/index.tsx +158 -14
  79. package/src/components/menu/index.tsx +51 -6
  80. package/src/components/pagination/index.tsx +18 -6
  81. package/src/components/panelside/index.tsx +134 -19
  82. package/src/components/radar-chart/index.tsx +910 -0
  83. package/src/components/section-rail/index.tsx +42 -8
  84. package/src/components/slider/index.tsx +283 -41
  85. package/src/components/tabs/index.tsx +263 -8
  86. package/src/components/text-animation/index.tsx +1093 -0
  87. package/src/icons/index.tsx +23 -4
  88. package/src/index.ts +73 -1
  89. package/src/theme/use-theme.ts +1 -1
  90. package/src/utils/chart.ts +52 -0
  91. package/src/utils/color.ts +246 -0
  92. package/theme.css +125 -112
@@ -123,6 +123,7 @@ import { Text, textChildren } from '../../primitives/text';
123
123
  import { useBackHandler } from '../../hooks/use-back-handler';
124
124
  import { useDirectionSign } from '../../hooks/use-direction';
125
125
  import { cn } from '../../utils/cn';
126
+ import { selectionTick } from '../../utils/haptics';
126
127
 
127
128
  const SPRING = { damping: 24, stiffness: 300, mass: 0.7 } as const;
128
129
 
@@ -245,6 +246,8 @@ const clamp = (value: number, min: number, max: number) => {
245
246
 
246
247
  export type PanelsideMode = 'push' | 'overlay';
247
248
  export type PanelsideSwipeFrom = 'anywhere' | 'edge';
249
+ export type PanelsideItemSize = 'default' | 'sm';
250
+ export type PanelsideCtaSize = 'default' | 'lg';
248
251
 
249
252
  const itemVariants = tv({
250
253
  // No width: in a group it stretches on its own, and pinning it to full width
@@ -252,26 +255,40 @@ const itemVariants = tv({
252
255
  // React Native defaults `flexShrink` to 0 — in a footer beside a button, on a
253
256
  // panel narrow enough for the two not to fit, nothing would give way and
254
257
  // both would simply hang off the edge.
255
- base: 'shrink flex-row items-center gap-3 rounded-xl px-3 py-2.5',
258
+ base: 'shrink flex-row items-center rounded-xl',
256
259
  variants: {
260
+ size: {
261
+ default: 'gap-3 px-3 py-2.5',
262
+ sm: 'gap-2.5 px-2.5 py-2',
263
+ },
257
264
  active: { true: 'bg-secondary' },
258
265
  disabled: { true: 'opacity-40' },
259
266
  },
267
+ defaultVariants: {
268
+ size: 'default',
269
+ },
260
270
  });
261
271
 
262
272
  const ctaVariants = tv({
263
- // Taller and wider than a list row's control, and set a step up. It is the
264
- // one thing in the panel you are meant to reach for without reading, so it
265
- // should not be the same size as the eight chat titles above it.
266
- base: 'h-12 shrink flex-row items-center justify-center gap-2 rounded-full px-6',
273
+ // Taller and wider than a list row's control. It is the one thing in the
274
+ // panel you are meant to reach for without reading, so it should not be the
275
+ // same size as the eight chat titles above it — but it shares its footer row
276
+ // with an account button, and a pill that stands a whole step above that row
277
+ // makes the footer taller than anything in the panel needs it to be.
278
+ base: 'shrink flex-row items-center justify-center rounded-full',
267
279
  variants: {
268
280
  variant: {
269
281
  primary: 'bg-primary',
270
282
  secondary: 'bg-secondary',
271
283
  },
284
+ size: {
285
+ default: 'h-10 gap-2 px-5',
286
+ lg: 'h-12 gap-2 px-6',
287
+ },
272
288
  },
273
289
  defaultVariants: {
274
290
  variant: 'primary',
291
+ size: 'default',
275
292
  },
276
293
  });
277
294
 
@@ -287,6 +304,10 @@ interface PanelsideContextValue {
287
304
  /** True when the panel is laid out beside the scene rather than behind it. */
288
305
  docked: boolean;
289
306
  dismissible: boolean;
307
+ /** Scene defaults set on the root. A `Panelside.Scene` prop still wins. */
308
+ scale?: number;
309
+ radius?: number;
310
+ dim?: number;
290
311
  }
291
312
 
292
313
  const PanelsideContext = createContext<PanelsideContextValue | null>(null);
@@ -396,6 +417,26 @@ export interface PanelsideProps {
396
417
  * Default true.
397
418
  */
398
419
  dismissible?: boolean;
420
+ /**
421
+ * A tick under the finger when a swipe commits to opening or closing. Off by
422
+ * default — needs the optional `expo-haptics`, and is silent without it.
423
+ *
424
+ * It fires on the commit rather than during the drag: the panel following
425
+ * your thumb is already the feedback for the drag, and a tick per frame is
426
+ * what makes a gesture feel broken rather than responsive.
427
+ */
428
+ haptics?: boolean;
429
+ /**
430
+ * How far the scene shrinks at full open, as a scale factor. Sets the
431
+ * default for every `Panelside.Scene` underneath; the scene's own prop still
432
+ * wins. Here so the three numbers that describe the curve can be set once
433
+ * where the panel is configured, rather than on a part further down.
434
+ */
435
+ scale?: number;
436
+ /** The corner radius the scene reaches at full open, in points. */
437
+ radius?: number;
438
+ /** How far the scene is dimmed at full open, 0 to 1. */
439
+ dim?: number;
399
440
  className?: string;
400
441
  }
401
442
 
@@ -411,6 +452,10 @@ function PanelsideRoot({
411
452
  swipeFrom = 'anywhere',
412
453
  edgeWidth = EDGE_WIDTH,
413
454
  dismissible = true,
455
+ haptics = false,
456
+ scale,
457
+ radius,
458
+ dim,
414
459
  className,
415
460
  }: PanelsideProps) {
416
461
  const { width: windowWidth } = useWindowDimensions();
@@ -499,6 +544,15 @@ function PanelsideRoot({
499
544
  const toggle = useCallback(() => setOpen(!open), [open, setOpen]);
500
545
  const close = useCallback(() => setOpen(false), [setOpen]);
501
546
 
547
+ /*
548
+ * Wrapped so the gesture's worklet has one stable function to hand to
549
+ * `runOnJS` — a fresh closure each render would be re-serialised on every
550
+ * frame the pan is rebuilt.
551
+ */
552
+ const tick = useCallback(() => {
553
+ if (haptics) selectionTick();
554
+ }, [haptics]);
555
+
502
556
  useBackHandler(open && dismissible && !docked, close);
503
557
 
504
558
  /*
@@ -539,6 +593,10 @@ function PanelsideRoot({
539
593
  // `velocity` is already in travel space; the spring runs on the same
540
594
  // axis, so it must not be converted back to screen space here.
541
595
  settle(next, velocity);
596
+ // Only where the drag changed something. A swipe that fell short and
597
+ // sprang back did not open or close anything, and a tick that says it
598
+ // did is worse than no tick at all.
599
+ if (next !== open) runOnJS(tick)();
542
600
  runOnJS(setOpen)(next);
543
601
  });
544
602
 
@@ -565,12 +623,25 @@ function PanelsideRoot({
565
623
  sign,
566
624
  swipeEnabled,
567
625
  swipeFrom,
626
+ tick,
568
627
  translation,
569
628
  ]);
570
629
 
571
630
  const context = useMemo<PanelsideContextValue>(
572
- () => ({ open, setOpen, toggle, progress, width, mode, docked, dismissible }),
573
- [dismissible, docked, mode, open, progress, setOpen, toggle, width]
631
+ () => ({
632
+ open,
633
+ setOpen,
634
+ toggle,
635
+ progress,
636
+ width,
637
+ mode,
638
+ docked,
639
+ dismissible,
640
+ scale,
641
+ radius,
642
+ dim,
643
+ }),
644
+ [dim, dismissible, docked, mode, open, progress, radius, scale, setOpen, toggle, width]
574
645
  );
575
646
 
576
647
  return (
@@ -831,6 +902,12 @@ export interface PanelsideItemProps extends Omit<PressableProps, 'children'> {
831
902
  */
832
903
  badge?: ReactNode;
833
904
  disabled?: boolean;
905
+ /**
906
+ * Row density. `sm` tightens the padding for a panel that has to show more
907
+ * history at once, without touching the type size — a list you can read is
908
+ * worth more than two extra rows.
909
+ */
910
+ size?: PanelsideItemSize;
834
911
  /** Trailing content — usually a `Panelside.Action`. */
835
912
  children?: ReactNode;
836
913
  }
@@ -842,6 +919,7 @@ function PanelsideItem({
842
919
  active = false,
843
920
  badge,
844
921
  disabled = false,
922
+ size = 'default',
845
923
  children,
846
924
  ...props
847
925
  }: PanelsideItemProps) {
@@ -858,7 +936,7 @@ function PanelsideItem({
858
936
 
859
937
  return (
860
938
  <AnimatedPressable
861
- className={itemVariants({ active, disabled, className })}
939
+ className={itemVariants({ active, disabled, size, className })}
862
940
  disabled={disabled}
863
941
  accessibilityRole="button"
864
942
  accessibilityState={{ selected: active, disabled }}
@@ -1033,6 +1111,15 @@ export interface PanelsideCtaProps extends Omit<PressableProps, 'children'> {
1033
1111
  icon?: ReactNode;
1034
1112
  /** `primary` is the filled accent pill; `secondary` is the quiet one. */
1035
1113
  variant?: 'primary' | 'secondary';
1114
+ /**
1115
+ * How tall the pill is. `default` is 40pt, which sits level with the account
1116
+ * button beside it in the footer; `lg` is the 48pt pill, for a panel where
1117
+ * the call to action is the only thing in the row.
1118
+ *
1119
+ * Ignored under `native` — the platform sizes its own button, and asks for a
1120
+ * control size rather than a height.
1121
+ */
1122
+ size?: PanelsideCtaSize;
1036
1123
  /**
1037
1124
  * Render the platform's own button instead of the pill. Requires the
1038
1125
  * optional `@expo/ui` package; without it this prop does nothing.
@@ -1054,6 +1141,7 @@ function PanelsideCta({
1054
1141
  label,
1055
1142
  icon,
1056
1143
  variant = 'primary',
1144
+ size = 'default',
1057
1145
  native = false,
1058
1146
  glass = false,
1059
1147
  disabled,
@@ -1076,9 +1164,9 @@ function PanelsideCta({
1076
1164
  <Button
1077
1165
  native
1078
1166
  glass={glass}
1079
- // The platform sizes a native button from its label, so the step up
1080
- // has to be asked for rather than styled on.
1081
- size="lg"
1167
+ // The platform sizes a native button from its label, so a height means
1168
+ // nothing here — the step is asked for as a control size instead.
1169
+ size={size === 'lg' ? 'lg' : 'md'}
1082
1170
  variant={variant}
1083
1171
  accessibilityLabel={label}
1084
1172
  // Pressable allows `null` for disabled; Button does not.
@@ -1107,7 +1195,7 @@ function PanelsideCta({
1107
1195
 
1108
1196
  return (
1109
1197
  <AnimatedPressable
1110
- className={ctaVariants({ variant, className })}
1198
+ className={ctaVariants({ variant, size, className })}
1111
1199
  accessibilityRole="button"
1112
1200
  accessibilityLabel={label}
1113
1201
  disabled={disabled}
@@ -1117,7 +1205,7 @@ function PanelsideCta({
1117
1205
  {icon}
1118
1206
  {label ? (
1119
1207
  <Text
1120
- size="lg"
1208
+ size={size === 'lg' ? 'lg' : 'base'}
1121
1209
  weight="medium"
1122
1210
  // The pill gives way before the panel does, so the label has to be
1123
1211
  // able to end somewhere rather than pushing the button off the edge.
@@ -1143,26 +1231,53 @@ export interface PanelsideSceneProps extends ViewProps {
1143
1231
  * full height and stays behind the status bar, and the radius and dim do the
1144
1232
  * work. Below one it shrinks about its centre, which insets it top and bottom
1145
1233
  * as well as at the side.
1234
+ *
1235
+ * Falls back to the same prop on the `Panelside` root, so the three numbers
1236
+ * that describe the curve can be set once where the panel is configured.
1146
1237
  */
1147
1238
  scale?: number;
1148
1239
  /** The corner radius the scene reaches at full travel. Default 44. */
1149
1240
  radius?: number;
1150
1241
  /** How far the scene dims at full travel, 0 to 1. Default 0.45. */
1151
1242
  dim?: number;
1243
+ /**
1244
+ * Styles the layer that dims the scene. Its opacity is `dim`'s to set, so
1245
+ * this is for the colour — a scrim that is not black, for a light theme
1246
+ * where black at 45% reads as a hole rather than as shade.
1247
+ */
1248
+ scrimClassName?: string;
1152
1249
  children?: ReactNode;
1153
1250
  }
1154
1251
 
1155
1252
  function PanelsideScene({
1156
1253
  className,
1157
- scale = SCENE_SCALE,
1158
- radius = SCENE_RADIUS,
1159
- dim = SCENE_DIM,
1254
+ scale: scaleProp,
1255
+ radius: radiusProp,
1256
+ dim: dimProp,
1257
+ scrimClassName,
1160
1258
  children,
1161
1259
  style,
1162
1260
  ...props
1163
1261
  }: PanelsideSceneProps) {
1164
- const { progress, width, mode, docked, dismissible, open, setOpen } =
1165
- usePanelsideContext('Panelside.Scene');
1262
+ const {
1263
+ progress,
1264
+ width,
1265
+ mode,
1266
+ docked,
1267
+ dismissible,
1268
+ open,
1269
+ setOpen,
1270
+ scale: rootScale,
1271
+ radius: rootRadius,
1272
+ dim: rootDim,
1273
+ } = usePanelsideContext('Panelside.Scene');
1274
+
1275
+ // The part's own prop, then the root's, then the constant. Three levels
1276
+ // because the root's is a default for every scene under it and the part's is
1277
+ // a statement about this one.
1278
+ const scale = scaleProp ?? rootScale ?? SCENE_SCALE;
1279
+ const radius = radiusProp ?? rootRadius ?? SCENE_RADIUS;
1280
+ const dim = dimProp ?? rootDim ?? SCENE_DIM;
1166
1281
  const [sceneWidth, setSceneWidth] = useState(0);
1167
1282
  const sign = useDirectionSign();
1168
1283
  /*
@@ -1251,7 +1366,7 @@ function PanelsideScene({
1251
1366
  including the one on the button that opens the panel. */}
1252
1367
  <Animated.View
1253
1368
  pointerEvents={open && !docked ? 'auto' : 'none'}
1254
- className="absolute bottom-0 end-0 start-0 top-0 bg-black"
1369
+ className={cn('absolute bottom-0 end-0 start-0 top-0 bg-black', scrimClassName)}
1255
1370
  style={scrimStyle}
1256
1371
  >
1257
1372
  {dismissible ? (