panelui-native 0.75.1 → 0.76.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 (151) hide show
  1. package/lib/module/components/ai-input/ai-input-growth.js +62 -0
  2. package/lib/module/components/ai-input/ai-input-growth.js.map +1 -0
  3. package/lib/module/components/ai-input/index.js +1568 -0
  4. package/lib/module/components/ai-input/index.js.map +1 -0
  5. package/lib/module/components/alert/index.js +1 -1
  6. package/lib/module/components/alert/index.js.map +1 -1
  7. package/lib/module/components/avatar/avatar-source.js +15 -0
  8. package/lib/module/components/avatar/avatar-source.js.map +1 -0
  9. package/lib/module/components/avatar/index.js +32 -31
  10. package/lib/module/components/avatar/index.js.map +1 -1
  11. package/lib/module/components/bottom-sheet/index.js +3 -2
  12. package/lib/module/components/bottom-sheet/index.js.map +1 -1
  13. package/lib/module/components/button-group/index.js +2 -3
  14. package/lib/module/components/button-group/index.js.map +1 -1
  15. package/lib/module/components/carousel/index.js +10 -7
  16. package/lib/module/components/carousel/index.js.map +1 -1
  17. package/lib/module/components/collapsible/index.js +1 -1
  18. package/lib/module/components/collapsible/index.js.map +1 -1
  19. package/lib/module/components/field/index.js +7 -8
  20. package/lib/module/components/field/index.js.map +1 -1
  21. package/lib/module/components/label/index.js +1 -1
  22. package/lib/module/components/label/index.js.map +1 -1
  23. package/lib/module/components/marquee/index.js +36 -11
  24. package/lib/module/components/marquee/index.js.map +1 -1
  25. package/lib/module/components/marquee/marquee-math.js +22 -6
  26. package/lib/module/components/marquee/marquee-math.js.map +1 -1
  27. package/lib/module/components/plan/index.js +2 -1
  28. package/lib/module/components/plan/index.js.map +1 -1
  29. package/lib/module/components/plot/index.js +27 -9
  30. package/lib/module/components/plot/index.js.map +1 -1
  31. package/lib/module/components/search-bar/index.js +11 -3
  32. package/lib/module/components/search-bar/index.js.map +1 -1
  33. package/lib/module/components/split-view/index.js +26 -27
  34. package/lib/module/components/split-view/index.js.map +1 -1
  35. package/lib/module/components/split-view/split-view-lifecycle.js +32 -0
  36. package/lib/module/components/split-view/split-view-lifecycle.js.map +1 -0
  37. package/lib/module/components/splitter/index.js +7 -13
  38. package/lib/module/components/splitter/index.js.map +1 -1
  39. package/lib/module/components/splitter/splitter-math.js +14 -0
  40. package/lib/module/components/splitter/splitter-math.js.map +1 -1
  41. package/lib/module/components/tabs/index.js +1 -1
  42. package/lib/module/components/tabs/index.js.map +1 -1
  43. package/lib/module/components/timeline/index.js +156 -42
  44. package/lib/module/components/timeline/index.js.map +1 -1
  45. package/lib/module/components/timeline/timeline-geometry.js +11 -0
  46. package/lib/module/components/timeline/timeline-geometry.js.map +1 -1
  47. package/lib/module/components/toast/index.js +1 -1
  48. package/lib/module/components/toast/index.js.map +1 -1
  49. package/lib/module/components/tooltip/index.js +2 -2
  50. package/lib/module/components/tooltip/index.js.map +1 -1
  51. package/lib/module/components/tree/index.js +2 -2
  52. package/lib/module/components/tree/index.js.map +1 -1
  53. package/lib/module/components/typography/index.js +2 -1
  54. package/lib/module/components/typography/index.js.map +1 -1
  55. package/lib/module/components/waterfall-chart/index.js +22 -3
  56. package/lib/module/components/waterfall-chart/index.js.map +1 -1
  57. package/lib/module/hooks/use-disclosure.js +10 -8
  58. package/lib/module/hooks/use-disclosure.js.map +1 -1
  59. package/lib/module/icons/index.js +135 -0
  60. package/lib/module/icons/index.js.map +1 -1
  61. package/lib/module/index.js +5 -1
  62. package/lib/module/index.js.map +1 -1
  63. package/lib/module/primitives/controllable-state.js +61 -0
  64. package/lib/module/primitives/controllable-state.js.map +1 -0
  65. package/lib/module/primitives/finite-chart.js +15 -0
  66. package/lib/module/primitives/finite-chart.js.map +1 -0
  67. package/lib/module/primitives/glass.js +136 -0
  68. package/lib/module/primitives/glass.js.map +1 -0
  69. package/lib/module/primitives/scrim.js +8 -1
  70. package/lib/module/primitives/scrim.js.map +1 -1
  71. package/lib/typescript/src/components/ai-input/ai-input-growth.d.ts +59 -0
  72. package/lib/typescript/src/components/ai-input/ai-input-growth.d.ts.map +1 -0
  73. package/lib/typescript/src/components/ai-input/index.d.ts +423 -0
  74. package/lib/typescript/src/components/ai-input/index.d.ts.map +1 -0
  75. package/lib/typescript/src/components/avatar/avatar-source.d.ts +4 -0
  76. package/lib/typescript/src/components/avatar/avatar-source.d.ts.map +1 -0
  77. package/lib/typescript/src/components/avatar/index.d.ts.map +1 -1
  78. package/lib/typescript/src/components/bottom-sheet/index.d.ts +10 -1
  79. package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
  80. package/lib/typescript/src/components/carousel/index.d.ts.map +1 -1
  81. package/lib/typescript/src/components/marquee/index.d.ts +15 -6
  82. package/lib/typescript/src/components/marquee/index.d.ts.map +1 -1
  83. package/lib/typescript/src/components/marquee/marquee-math.d.ts +6 -0
  84. package/lib/typescript/src/components/marquee/marquee-math.d.ts.map +1 -1
  85. package/lib/typescript/src/components/plan/index.d.ts.map +1 -1
  86. package/lib/typescript/src/components/plot/index.d.ts +2 -1
  87. package/lib/typescript/src/components/plot/index.d.ts.map +1 -1
  88. package/lib/typescript/src/components/search-bar/index.d.ts.map +1 -1
  89. package/lib/typescript/src/components/split-view/index.d.ts.map +1 -1
  90. package/lib/typescript/src/components/split-view/split-view-lifecycle.d.ts +14 -0
  91. package/lib/typescript/src/components/split-view/split-view-lifecycle.d.ts.map +1 -0
  92. package/lib/typescript/src/components/splitter/index.d.ts +2 -2
  93. package/lib/typescript/src/components/splitter/index.d.ts.map +1 -1
  94. package/lib/typescript/src/components/splitter/splitter-math.d.ts +2 -0
  95. package/lib/typescript/src/components/splitter/splitter-math.d.ts.map +1 -1
  96. package/lib/typescript/src/components/timeline/index.d.ts +24 -2
  97. package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
  98. package/lib/typescript/src/components/timeline/timeline-geometry.d.ts +2 -0
  99. package/lib/typescript/src/components/timeline/timeline-geometry.d.ts.map +1 -1
  100. package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
  101. package/lib/typescript/src/components/waterfall-chart/index.d.ts +2 -1
  102. package/lib/typescript/src/components/waterfall-chart/index.d.ts.map +1 -1
  103. package/lib/typescript/src/hooks/use-disclosure.d.ts.map +1 -1
  104. package/lib/typescript/src/icons/index.d.ts +18 -0
  105. package/lib/typescript/src/icons/index.d.ts.map +1 -1
  106. package/lib/typescript/src/index.d.ts +3 -1
  107. package/lib/typescript/src/index.d.ts.map +1 -1
  108. package/lib/typescript/src/primitives/controllable-state.d.ts +33 -0
  109. package/lib/typescript/src/primitives/controllable-state.d.ts.map +1 -0
  110. package/lib/typescript/src/primitives/finite-chart.d.ts +5 -0
  111. package/lib/typescript/src/primitives/finite-chart.d.ts.map +1 -0
  112. package/lib/typescript/src/primitives/glass.d.ts +43 -0
  113. package/lib/typescript/src/primitives/glass.d.ts.map +1 -0
  114. package/lib/typescript/src/primitives/scrim.d.ts +7 -0
  115. package/lib/typescript/src/primitives/scrim.d.ts.map +1 -1
  116. package/package.json +7 -2
  117. package/src/components/ai-input/ai-input-growth.ts +65 -0
  118. package/src/components/ai-input/index.tsx +1937 -0
  119. package/src/components/alert/index.tsx +1 -1
  120. package/src/components/avatar/avatar-source.ts +18 -0
  121. package/src/components/avatar/index.tsx +35 -27
  122. package/src/components/bottom-sheet/index.tsx +15 -3
  123. package/src/components/button-group/index.tsx +1 -1
  124. package/src/components/carousel/index.tsx +10 -11
  125. package/src/components/collapsible/index.tsx +1 -1
  126. package/src/components/field/index.tsx +4 -4
  127. package/src/components/label/index.tsx +1 -1
  128. package/src/components/marquee/index.tsx +56 -12
  129. package/src/components/marquee/marquee-math.ts +26 -3
  130. package/src/components/plan/index.tsx +6 -2
  131. package/src/components/plot/index.tsx +37 -11
  132. package/src/components/search-bar/index.tsx +9 -3
  133. package/src/components/split-view/index.tsx +24 -33
  134. package/src/components/split-view/split-view-lifecycle.ts +43 -0
  135. package/src/components/splitter/index.tsx +7 -11
  136. package/src/components/splitter/splitter-math.ts +28 -0
  137. package/src/components/tabs/index.tsx +1 -1
  138. package/src/components/timeline/index.tsx +189 -34
  139. package/src/components/timeline/timeline-geometry.ts +11 -0
  140. package/src/components/toast/index.tsx +6 -1
  141. package/src/components/tooltip/index.tsx +1 -1
  142. package/src/components/tree/index.tsx +2 -2
  143. package/src/components/typography/index.tsx +2 -2
  144. package/src/components/waterfall-chart/index.tsx +37 -4
  145. package/src/hooks/use-disclosure.ts +7 -12
  146. package/src/icons/index.tsx +106 -0
  147. package/src/index.ts +27 -0
  148. package/src/primitives/controllable-state.ts +112 -0
  149. package/src/primitives/finite-chart.ts +14 -0
  150. package/src/primitives/glass.tsx +171 -0
  151. package/src/primitives/scrim.tsx +7 -1
@@ -286,10 +286,10 @@ const TreeRoot = forwardRef<View, TreeProps>(
286
286
  <TreeContext.Provider value={context}>
287
287
  <Animated.View
288
288
  ref={ref}
289
+ {...props}
289
290
  layout={LinearTransition.duration(TRANSITION_DURATION)}
290
291
  accessibilityRole="list"
291
292
  className={root({ className })}
292
- {...props}
293
293
  >
294
294
  {children}
295
295
  </Animated.View>
@@ -394,6 +394,7 @@ const TreeTrigger = forwardRef<View, TreeTriggerProps>(
394
394
  return (
395
395
  <Pressable
396
396
  ref={ref}
397
+ {...props}
397
398
  accessibilityRole="button"
398
399
  accessibilityState={{
399
400
  expanded: isBranch ? isExpanded : undefined,
@@ -403,7 +404,6 @@ const TreeTrigger = forwardRef<View, TreeTriggerProps>(
403
404
  aria-level={level + 1}
404
405
  disabled={triggerDisabled}
405
406
  className={trigger({ className })}
406
- {...props}
407
407
  onPress={handlePress}
408
408
  >
409
409
  <IconColorProvider color={isSelected ? selectedColor : undefined}>
@@ -290,9 +290,9 @@ export interface TypographyListProps extends ViewProps {
290
290
  */
291
291
  const TypographyList = forwardRef<View, TypographyListProps>(
292
292
  ({ className, ordered = false, children, ...props }, ref) => (
293
- <View ref={ref} accessibilityRole="list" className={cn('gap-2', className)} {...props}>
293
+ <View ref={ref} {...props} accessibilityRole="list" className={cn('gap-2', className)}>
294
294
  {Children.map(children, (child, index) => (
295
- <View className="w-full flex-row gap-2">
295
+ <View role="listitem" className="w-full flex-row gap-2">
296
296
  {ordered ? (
297
297
  <Text className="text-base text-muted-foreground">{index + 1}.</Text>
298
298
  ) : (
@@ -65,7 +65,12 @@ import Animated, {
65
65
  } from 'react-native-reanimated';
66
66
  import Svg, { Defs, G, Line as SvgLine, LinearGradient, Path, Stop } from 'react-native-svg';
67
67
  import { useCSSVariable } from 'uniwind';
68
+ import { ChartAccessibilityData, type ChartAccessibilityProps } from '../../primitives/chart-accessibility';
68
69
  import { Text } from '../../primitives/text';
70
+ import {
71
+ finiteChartDomain,
72
+ finiteChartNumber,
73
+ } from '../../primitives/finite-chart';
69
74
  import { barPath, compactNumber, type Plot } from '../../utils/chart';
70
75
  import { cn } from '../../utils/cn';
71
76
 
@@ -196,7 +201,7 @@ export function waterfallSteps(data: WaterfallDatum[]): WaterfallStep[] {
196
201
  let running = 0;
197
202
 
198
203
  for (const datum of data) {
199
- const value = typeof datum.value === 'number' && !Number.isNaN(datum.value) ? datum.value : 0;
204
+ const value = finiteChartNumber(datum.value) ?? 0;
200
205
 
201
206
  if (datum.total) {
202
207
  // A total is a reading, so it is measured from the baseline and the
@@ -228,7 +233,7 @@ export function waterfallSteps(data: WaterfallDatum[]): WaterfallStep[] {
228
233
  return steps;
229
234
  }
230
235
 
231
- export interface WaterfallChartProps extends ViewProps {
236
+ export interface WaterfallChartProps extends ViewProps, ChartAccessibilityProps<WaterfallStep> {
232
237
  className?: string;
233
238
  /** The steps, in the order they happen. */
234
239
  data: WaterfallDatum[];
@@ -327,6 +332,11 @@ const WaterfallChartRoot = forwardRef<WaterfallChartHandle, WaterfallChartProps>
327
332
  fallColor,
328
333
  totalColor,
329
334
  onActiveIndexChange,
335
+ accessible,
336
+ accessibilityLabel,
337
+ accessibilityHint,
338
+ accessibilityLabelForDatum,
339
+ onAccessibilityDatumPress,
330
340
  compact = false,
331
341
  children,
332
342
  ...props
@@ -385,7 +395,8 @@ const WaterfallChartRoot = forwardRef<WaterfallChartHandle, WaterfallChartProps>
385
395
  };
386
396
 
387
397
  const extent = useMemo<[number, number]>(() => {
388
- if (yDomain) return yDomain;
398
+ const explicit = finiteChartDomain(yDomain);
399
+ if (explicit) return explicit;
389
400
 
390
401
  /*
391
402
  * Both ends of every bar, not just the values. A step's bar occupies the
@@ -535,7 +546,29 @@ const WaterfallChartRoot = forwardRef<WaterfallChartHandle, WaterfallChartProps>
535
546
  <WaterfallChartContext.Provider value={context}>
536
547
  <View {...props} style={props.style} className={cn('w-full', className)}>
537
548
  {header}
538
- <View onLayout={onLayout} style={{ aspectRatio }} className="w-full">
549
+ <ChartAccessibilityData
550
+ chart="Waterfall chart"
551
+ data={steps}
552
+ disabled={accessible === false || loading}
553
+ accessibilityLabel={accessibilityLabel}
554
+ accessibilityHint={accessibilityHint}
555
+ accessibilityLabelForDatum={accessibilityLabelForDatum}
556
+ onAccessibilityDatumPress={onAccessibilityDatumPress}
557
+ valueOf={(step) => [
558
+ ['label', step.label],
559
+ ['change', step.value],
560
+ ['kind', step.kind],
561
+ ['starts at', step.start],
562
+ ['ends at', step.end],
563
+ ]}
564
+ />
565
+ <View
566
+ onLayout={onLayout}
567
+ style={{ aspectRatio }}
568
+ className="w-full"
569
+ accessibilityElementsHidden
570
+ importantForAccessibility="no-hide-descendants"
571
+ >
539
572
  {plot.width > 0 ? (
540
573
  <>
541
574
  <Svg width="100%" height="100%" style={StyleSheet.absoluteFill}>
@@ -1,4 +1,5 @@
1
- import { useCallback, useMemo, useState } from 'react';
1
+ import { useCallback, useMemo } from 'react';
2
+ import { useControllableState } from '../primitives/controllable-state';
2
3
 
3
4
  export interface UseDisclosureOptions {
4
5
  defaultOpen?: boolean;
@@ -34,17 +35,11 @@ export function useDisclosure({
34
35
  open: controlledOpen,
35
36
  onOpenChange,
36
37
  }: UseDisclosureOptions = {}): UseDisclosureResult {
37
- const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
38
- const isControlled = controlledOpen !== undefined;
39
- const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
40
-
41
- const setOpen = useCallback(
42
- (next: boolean) => {
43
- if (!isControlled) setUncontrolledOpen(next);
44
- onOpenChange?.(next);
45
- },
46
- [isControlled, onOpenChange]
47
- );
38
+ const { value: isOpen, setValue: setOpen } = useControllableState({
39
+ value: controlledOpen,
40
+ defaultValue: defaultOpen,
41
+ onChange: onOpenChange,
42
+ });
48
43
 
49
44
  const open = useCallback(() => setOpen(true), [setOpen]);
50
45
  const close = useCallback(() => setOpen(false), [setOpen]);
@@ -852,6 +852,22 @@ export function PaperclipIcon({ size = 16, color, ...props }: IconProps) {
852
852
  }
853
853
 
854
854
  /** A capsule on a stand — a microphone, for anything voice. */
855
+ /** A voice waveform — dictation, voice mode, a recording that has a shape. */
856
+ export function AudioLinesIcon({ size = 16, color, ...props }: IconProps) {
857
+ const resolved = useResolvedColor(color, '#737373');
858
+ return (
859
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill="none" {...props}>
860
+ <Path
861
+ d="M2 10v3M6 6v11M10 3v18M14 8v7M18 5v13M22 10v3"
862
+ stroke={resolved}
863
+ strokeWidth={2}
864
+ strokeLinecap="round"
865
+ strokeLinejoin="round"
866
+ />
867
+ </Svg>
868
+ );
869
+ }
870
+
855
871
  export function MicIcon({ size = 16, color, ...props }: IconProps) {
856
872
  const resolved = useResolvedColor(color, '#737373');
857
873
  return (
@@ -1078,6 +1094,73 @@ export function CrosshairIcon({ size = 16, color, ...props }: IconProps) {
1078
1094
  }
1079
1095
 
1080
1096
  /** A compass needle — which way is north. */
1097
+ export function GlobeIcon({ size = 16, color, ...props }: IconProps) {
1098
+ const resolved = useResolvedColor(color, '#737373');
1099
+ return (
1100
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill="none" {...props}>
1101
+ <Circle
1102
+ cx={12}
1103
+ cy={12}
1104
+ r={10}
1105
+ stroke={resolved}
1106
+ strokeWidth={2}
1107
+ strokeLinecap="round"
1108
+ strokeLinejoin="round"
1109
+ />
1110
+ <Path
1111
+ d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20M2 12h20"
1112
+ stroke={resolved}
1113
+ strokeWidth={2}
1114
+ strokeLinecap="round"
1115
+ strokeLinejoin="round"
1116
+ />
1117
+ </Svg>
1118
+ );
1119
+ }
1120
+
1121
+ export function CameraIcon({ size = 16, color, ...props }: IconProps) {
1122
+ const resolved = useResolvedColor(color, '#737373');
1123
+ return (
1124
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill="none" {...props}>
1125
+ <Path
1126
+ d="M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z"
1127
+ stroke={resolved}
1128
+ strokeWidth={2}
1129
+ strokeLinecap="round"
1130
+ strokeLinejoin="round"
1131
+ />
1132
+ <Circle
1133
+ cx={12}
1134
+ cy={13}
1135
+ r={3}
1136
+ stroke={resolved}
1137
+ strokeWidth={2}
1138
+ strokeLinecap="round"
1139
+ strokeLinejoin="round"
1140
+ />
1141
+ </Svg>
1142
+ );
1143
+ }
1144
+
1145
+ /**
1146
+ * The glyph on a control that opens a list of values — a stacked pair, because
1147
+ * one chevron promises a direction the control does not travel in.
1148
+ */
1149
+ export function ChevronsUpDownIcon({ size = 16, color, ...props }: IconProps) {
1150
+ const resolved = useResolvedColor(color, '#737373');
1151
+ return (
1152
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill="none" {...props}>
1153
+ <Path
1154
+ d="m7 15 5 5 5-5M7 9l5-5 5 5"
1155
+ stroke={resolved}
1156
+ strokeWidth={2}
1157
+ strokeLinecap="round"
1158
+ strokeLinejoin="round"
1159
+ />
1160
+ </Svg>
1161
+ );
1162
+ }
1163
+
1081
1164
  export function CompassIcon({ size = 16, color, ...props }: IconProps) {
1082
1165
  const resolved = useResolvedColor(color, '#737373');
1083
1166
  return (
@@ -1256,6 +1339,29 @@ export interface ToggleIconProps extends IconProps {
1256
1339
  }
1257
1340
 
1258
1341
  /** Upvote. Filled once the vote is cast. */
1342
+ /**
1343
+ * A plain up arrow, drawn symmetrically about the centre of its box so it sits
1344
+ * dead centre in a circular button.
1345
+ *
1346
+ * Distinct from `ArrowUpIcon`, which is the heavy solid arrow a vote control
1347
+ * wants, and from `SendIcon`, which is a paper plane. This is the one a
1348
+ * composer's send button uses.
1349
+ */
1350
+ export function SendArrowIcon({ size = 16, color, ...props }: IconProps) {
1351
+ const resolved = useResolvedColor(color, '#737373');
1352
+ return (
1353
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill="none" {...props}>
1354
+ <Path
1355
+ d="m5 12 7-7 7 7M12 19V5"
1356
+ stroke={resolved}
1357
+ strokeWidth={2.5}
1358
+ strokeLinecap="round"
1359
+ strokeLinejoin="round"
1360
+ />
1361
+ </Svg>
1362
+ );
1363
+ }
1364
+
1259
1365
  export function ArrowUpIcon({ size = 16, color, filled, ...props }: ToggleIconProps) {
1260
1366
  const resolved = useResolvedColor(color, '#737373');
1261
1367
  return (
package/src/index.ts CHANGED
@@ -20,6 +20,9 @@ export { Portal, PortalHost, PortalProvider } from './primitives/portal';
20
20
  // recede. Public so a custom overlay gets the same backdrop — and the same
21
21
  // graceful fall back from blur to dim — as the built-in ones.
22
22
  export { Scrim, hasBlur, type ScrimProps } from './primitives/scrim';
23
+ // The material iOS draws its floating controls in, with a solid fallback where
24
+ // there is none. Public so a surface of your own gets the same treatment.
25
+ export { Glass, hasGlass, type GlassProps, type GlassVariant, type GlassRadius } from './primitives/glass';
23
26
  export { Text, type TextProps } from './primitives/text';
24
27
  export { Collapse, type CollapseProps } from './primitives/collapse';
25
28
  export {
@@ -935,6 +938,25 @@ export {
935
938
  type SignatureToolbarProps,
936
939
  type SignatureButtonProps,
937
940
  } from './components/signature';
941
+ export {
942
+ AIInput,
943
+ type AIInputProps,
944
+ type AIInputStatus,
945
+ type AIInputFieldProps,
946
+ type AIInputRowProps,
947
+ type AIInputToolbarProps,
948
+ type AIInputActionProps,
949
+ type AIInputPillProps,
950
+ type AIInputSubmitProps,
951
+ type AIInputRecordingProps,
952
+ type AIInputSheetProps,
953
+ type AIInputSheetScreenProps,
954
+ type AIInputSheetGroupProps,
955
+ type AIInputSheetRowProps,
956
+ type AIInputSheetToggleProps,
957
+ type AIInputSheetChoiceProps,
958
+ type AIInputVoiceModeProps,
959
+ } from './components/ai-input';
938
960
  export { Shimmer, type ShimmerProps } from './components/shimmer';
939
961
  export {
940
962
  Reasoning,
@@ -1190,17 +1212,20 @@ export {
1190
1212
  ArrowDownIcon,
1191
1213
  ArrowUpIcon,
1192
1214
  ArrowUpRightIcon,
1215
+ AudioLinesIcon,
1193
1216
  BadgeCheckIcon,
1194
1217
  BellIcon,
1195
1218
  BookmarkIcon,
1196
1219
  BoldIcon,
1197
1220
  CalendarIcon,
1221
+ CameraIcon,
1198
1222
  CardIcon,
1199
1223
  CheckCircleIcon,
1200
1224
  CheckIcon,
1201
1225
  ChevronDownIcon,
1202
1226
  ChevronLeftIcon,
1203
1227
  ChevronRightIcon,
1228
+ ChevronsUpDownIcon,
1204
1229
  ChevronUpIcon,
1205
1230
  CircleIcon,
1206
1231
  CodeIcon,
@@ -1215,6 +1240,7 @@ export {
1215
1240
  FileIcon,
1216
1241
  FolderIcon,
1217
1242
  FolderOpenIcon,
1243
+ GlobeIcon,
1218
1244
  GoogleIcon,
1219
1245
  HeartIcon,
1220
1246
  HeadingIcon,
@@ -1247,6 +1273,7 @@ export {
1247
1273
  RotateCcwIcon,
1248
1274
  RotateCwIcon,
1249
1275
  SearchIcon,
1276
+ SendArrowIcon,
1250
1277
  SendIcon,
1251
1278
  ShareNodesIcon,
1252
1279
  ShieldAlertIcon,
@@ -0,0 +1,112 @@
1
+ import {
2
+ useCallback,
3
+ useLayoutEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ type Dispatch,
8
+ type SetStateAction,
9
+ } from 'react';
10
+
11
+ const identity = <T,>(value: T): T => value;
12
+ const objectIs = <T,>(left: T, right: T): boolean => Object.is(left, right);
13
+
14
+ export interface ControllableStateOptions<T> {
15
+ /** Accepted owner state. `undefined` selects uncontrolled ownership. */
16
+ value: T | undefined;
17
+ /** Initial state used only while uncontrolled. */
18
+ defaultValue: T;
19
+ /** Reports a distinct requested value exactly once per setter invocation. */
20
+ onChange?: (value: T) => void;
21
+ /** Reports accepted state changes, whether requested or externally reset. */
22
+ onSettled?: (value: T) => void;
23
+ /** Normalize defaults, controlled values, and requests through one boundary. */
24
+ normalize?: (value: T) => T;
25
+ /** Equality used for no-op requests and accepted settlement. */
26
+ isEqual?: (left: T, right: T) => boolean;
27
+ /** Stable semantic identity for controlled objects recreated by their owner. */
28
+ getValueKey?: (value: T) => unknown;
29
+ }
30
+
31
+ export interface ControllableStateResult<T> {
32
+ /** State accepted by the current owner; controlled requests never render optimistically. */
33
+ value: T;
34
+ setValue: Dispatch<SetStateAction<T>>;
35
+ isControlled: boolean;
36
+ }
37
+
38
+ /**
39
+ * Owner-driven controlled/uncontrolled state with an explicit settlement edge.
40
+ *
41
+ * Switching from controlled to uncontrolled retains the last accepted owner
42
+ * value instead of resurrecting a stale default. Switching into controlled
43
+ * mode adopts the prop immediately. `undefined` is the ownership sentinel and
44
+ * therefore cannot itself be a controlled value; use `null` for empty state.
45
+ */
46
+ export function useControllableState<T>({
47
+ value: valueProp,
48
+ defaultValue,
49
+ onChange,
50
+ onSettled,
51
+ normalize = identity,
52
+ isEqual = objectIs,
53
+ getValueKey,
54
+ }: ControllableStateOptions<T>): ControllableStateResult<T> {
55
+ const [internalValue, setInternalValue] = useState(() =>
56
+ normalize(defaultValue)
57
+ );
58
+ const isControlled = valueProp !== undefined;
59
+ const controlledKey = isControlled
60
+ ? getValueKey
61
+ ? getValueKey(valueProp as T)
62
+ : valueProp
63
+ : undefined;
64
+ const controlledValue = useMemo(
65
+ () => (isControlled ? normalize(valueProp as T) : undefined),
66
+ // The semantic key deliberately replaces object identity for equivalent owner values.
67
+ // eslint-disable-next-line react-hooks/exhaustive-deps
68
+ [controlledKey, isControlled, normalize]
69
+ );
70
+ const wasControlled = useRef(isControlled);
71
+ const lastControlledValue = useRef<T | undefined>(controlledValue);
72
+ if (isControlled) lastControlledValue.current = controlledValue;
73
+
74
+ const leavingControlled = wasControlled.current && !isControlled;
75
+ const value = isControlled
76
+ ? (controlledValue as T)
77
+ : leavingControlled
78
+ ? (lastControlledValue.current as T)
79
+ : internalValue;
80
+ const valueRef = useRef(value);
81
+ valueRef.current = value;
82
+
83
+ useLayoutEffect(() => {
84
+ if (wasControlled.current && !isControlled) {
85
+ setInternalValue(lastControlledValue.current as T);
86
+ }
87
+ wasControlled.current = isControlled;
88
+ }, [isControlled]);
89
+
90
+ const previousSettled = useRef(value);
91
+ useLayoutEffect(() => {
92
+ if (isEqual(previousSettled.current, value)) return;
93
+ previousSettled.current = value;
94
+ onSettled?.(value);
95
+ }, [isEqual, onSettled, value]);
96
+
97
+ const setValue = useCallback<Dispatch<SetStateAction<T>>>(
98
+ (next) => {
99
+ const requested = normalize(
100
+ typeof next === 'function'
101
+ ? (next as (current: T) => T)(valueRef.current)
102
+ : next
103
+ );
104
+ if (isEqual(valueRef.current, requested)) return;
105
+ if (!isControlled) setInternalValue(requested);
106
+ onChange?.(requested);
107
+ },
108
+ [isControlled, isEqual, normalize, onChange]
109
+ );
110
+
111
+ return { value, setValue, isControlled };
112
+ }
@@ -0,0 +1,14 @@
1
+ /** Returns a number only when it is safe to use in chart arithmetic. */
2
+ export function finiteChartNumber(value: unknown): number | undefined {
3
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
4
+ }
5
+
6
+ /** Accepts an explicit domain only when both ends are finite. */
7
+ export function finiteChartDomain(
8
+ domain: readonly [number, number] | undefined
9
+ ): [number, number] | undefined {
10
+ if (!domain) return undefined;
11
+ const low = finiteChartNumber(domain[0]);
12
+ const high = finiteChartNumber(domain[1]);
13
+ return low === undefined || high === undefined ? undefined : [low, high];
14
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Glass — the material iOS draws its own floating controls in.
3
+ *
4
+ * A control that floats over content needs to read as *over* it, and a flat
5
+ * fill cannot say that: it either hides what is behind it or disappears into
6
+ * it. The system material does both jobs at once — it refracts what is behind,
7
+ * lifts its own edge, and stays legible over anything.
8
+ *
9
+ * ```tsx
10
+ * <Glass radius={28} className="px-4 py-3">
11
+ * <Text>Over whatever is behind it.</Text>
12
+ * </Glass>
13
+ * ```
14
+ *
15
+ * ## Where it is real, and where it is not
16
+ *
17
+ * The material exists on iOS 26 and above. Below that, on Android, on web, and
18
+ * for anyone who has switched Reduce Transparency on, this draws
19
+ * `fallbackClassName` instead — a solid token surface. That is deliberate
20
+ * rather than a gap: a hand-drawn approximation of a system material is a
21
+ * near-miss on the one platform that has the real thing, and on Android it is
22
+ * an iOS look pasted onto a platform that never asked for it.
23
+ *
24
+ * So `Glass` is not a promise that the screen will be glass. It is a promise
25
+ * that the container will be drawn correctly either way, and callers should
26
+ * pick a `fallbackClassName` that stands on its own.
27
+ *
28
+ * ## The material is a layer, not the box
29
+ *
30
+ * The native view is rendered as a non-interactive layer filling this
31
+ * container, with the content above it, for the same reason `Scrim` layers a
32
+ * blur: it keeps every layout class, every token and every touch target on an
33
+ * ordinary `View` that behaves the way the rest of the library does.
34
+ *
35
+ * `radius` is a number rather than a class because the material has to round
36
+ * its own corners — clipping a square one to a rounded parent throws away the
37
+ * lit edge that makes it read as glass.
38
+ *
39
+ * ## Do not fade it
40
+ *
41
+ * Setting `opacity` to `0` on the material or on anything above it stops it
42
+ * rendering at all, and it does not come back when the opacity does. Move it,
43
+ * or unmount it; never animate it out.
44
+ */
45
+ import type { ComponentType, ReactNode } from 'react';
46
+ import { Platform, StyleSheet, View, type StyleProp, type ViewProps, type ViewStyle } from 'react-native';
47
+ import { cn } from '../utils/cn';
48
+ import { useReduceTransparency } from './scrim';
49
+
50
+ /** How the material treats what is behind it. */
51
+ export type GlassVariant = 'regular' | 'clear';
52
+
53
+ /**
54
+ * Corner radius in points — one number for all four, or a top and a bottom.
55
+ *
56
+ * The two-sided form is for a surface with an edge that is not a real edge: a
57
+ * sheet docked to the bottom of the screen rounds its top and leaves its
58
+ * bottom square, because the screen edge is where it ends.
59
+ */
60
+ export type GlassRadius = number | { top?: number; bottom?: number };
61
+
62
+ function shapeOf(radius: GlassRadius | undefined) {
63
+ if (radius === undefined) return null;
64
+ if (typeof radius === 'number') return { borderRadius: radius };
65
+ return {
66
+ borderTopLeftRadius: radius.top ?? 0,
67
+ borderTopRightRadius: radius.top ?? 0,
68
+ borderBottomLeftRadius: radius.bottom ?? 0,
69
+ borderBottomRightRadius: radius.bottom ?? 0,
70
+ };
71
+ }
72
+
73
+ interface GlassViewProps {
74
+ glassEffectStyle?: GlassVariant | 'none';
75
+ tintColor?: string;
76
+ colorScheme?: 'auto' | 'light' | 'dark';
77
+ style?: StyleProp<ViewStyle>;
78
+ pointerEvents?: ViewProps['pointerEvents'];
79
+ children?: ReactNode;
80
+ }
81
+
82
+ /**
83
+ * `expo-glass-effect`'s GlassView, or null when the material cannot be drawn
84
+ * here.
85
+ *
86
+ * Resolved once at module load, behind three gates that all have to pass. The
87
+ * package is optional, so the require can fail; the API is missing from some
88
+ * iOS 26 builds, and reaching for it there crashes; and the design itself is
89
+ * only present when the app was compiled for it. Asking all three once is
90
+ * cheaper than a try/catch on every render, and there is no answer that can
91
+ * change while the process is alive.
92
+ */
93
+ const GlassView: ComponentType<GlassViewProps> | null = (() => {
94
+ if (Platform.OS !== 'ios') return null;
95
+ try {
96
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
97
+ const mod = require('expo-glass-effect');
98
+ if (typeof mod?.isGlassEffectAPIAvailable === 'function' && !mod.isGlassEffectAPIAvailable()) {
99
+ return null;
100
+ }
101
+ if (typeof mod?.isLiquidGlassAvailable === 'function' && !mod.isLiquidGlassAvailable()) {
102
+ return null;
103
+ }
104
+ return (mod?.GlassView as ComponentType<GlassViewProps>) ?? null;
105
+ } catch {
106
+ return null;
107
+ }
108
+ })();
109
+
110
+ /**
111
+ * True when the real material can be drawn — for a caller that wants to know
112
+ * before it commits to a look. It says nothing about Reduce Transparency, which
113
+ * is a live preference and belongs to the hook.
114
+ */
115
+ export const hasGlass = GlassView !== null;
116
+
117
+ export interface GlassProps extends ViewProps {
118
+ /**
119
+ * How much of what is behind shows through. `regular` is the everyday
120
+ * material; `clear` is thinner, for something over its own artwork.
121
+ */
122
+ variant?: GlassVariant;
123
+ /** Tints the material. Takes a colour, not a token name. */
124
+ tint?: string;
125
+ /**
126
+ * Corner radius in points. The material rounds itself to this, so give it
127
+ * the same shape the container is drawn with — clipping a square material
128
+ * to a rounded parent throws away the lit edge that makes it read as glass.
129
+ */
130
+ radius?: GlassRadius;
131
+ /** Applied only when the material cannot be drawn. Give it a real surface. */
132
+ fallbackClassName?: string;
133
+ className?: string;
134
+ }
135
+
136
+ export function Glass({
137
+ variant = 'regular',
138
+ tint,
139
+ radius,
140
+ fallbackClassName = 'bg-card',
141
+ className,
142
+ children,
143
+ style,
144
+ ...props
145
+ }: GlassProps) {
146
+ const reduceTransparency = useReduceTransparency();
147
+ // Not knowing yet counts as "do not draw it": the material arriving a frame
148
+ // late is invisible, and one flashing at somebody who opted out is not.
149
+ const material = GlassView !== null && reduceTransparency === false;
150
+ const shape = shapeOf(radius);
151
+
152
+ return (
153
+ <View
154
+ className={cn('overflow-hidden', material ? null : fallbackClassName, className)}
155
+ style={[shape, style]}
156
+ {...props}
157
+ >
158
+ {material && GlassView ? (
159
+ <GlassView
160
+ glassEffectStyle={variant}
161
+ tintColor={tint}
162
+ pointerEvents="none"
163
+ style={[StyleSheet.absoluteFill, shape]}
164
+ />
165
+ ) : null}
166
+ {children}
167
+ </View>
168
+ );
169
+ }
170
+
171
+ Glass.displayName = 'Glass';
@@ -113,7 +113,13 @@ function observeReduceTransparency(
113
113
  */
114
114
  let knownReduceTransparency: boolean | null = null;
115
115
 
116
- function useReduceTransparency() {
116
+ /**
117
+ * Whether the device asks for Reduce Transparency: `true`, `false`, or `null`
118
+ * while the platform has not answered yet. Exported because every material in
119
+ * the library has to respect the same preference, and two copies of this would
120
+ * be two answers.
121
+ */
122
+ export function useReduceTransparency() {
117
123
  // Not knowing is deliberately conservative: draw opaque until the platform
118
124
  // says blur is allowed, rather than flashing blur at someone who opted out.
119
125
  const [enabled, setEnabled] = useState<boolean | null>(knownReduceTransparency);