panelui-native 0.28.1 → 0.32.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 (121) hide show
  1. package/README.md +22 -3
  2. package/lib/module/components/calendar/index.js +450 -132
  3. package/lib/module/components/calendar/index.js.map +1 -1
  4. package/lib/module/components/code-block/highlight.js +363 -0
  5. package/lib/module/components/code-block/highlight.js.map +1 -0
  6. package/lib/module/components/code-block/index.js +313 -0
  7. package/lib/module/components/code-block/index.js.map +1 -0
  8. package/lib/module/components/date-picker/index.js +8 -1
  9. package/lib/module/components/date-picker/index.js.map +1 -1
  10. package/lib/module/components/heatmap-chart/index.js +9 -8
  11. package/lib/module/components/heatmap-chart/index.js.map +1 -1
  12. package/lib/module/components/map/index.js +50 -4
  13. package/lib/module/components/map/index.js.map +1 -1
  14. package/lib/module/components/menu/index.js +714 -0
  15. package/lib/module/components/menu/index.js.map +1 -0
  16. package/lib/module/components/plan/index.js +582 -0
  17. package/lib/module/components/plan/index.js.map +1 -0
  18. package/lib/module/components/popover/index.js +35 -4
  19. package/lib/module/components/popover/index.js.map +1 -1
  20. package/lib/module/components/post/index.js +992 -0
  21. package/lib/module/components/post/index.js.map +1 -0
  22. package/lib/module/components/radio-group/index.js +13 -8
  23. package/lib/module/components/radio-group/index.js.map +1 -1
  24. package/lib/module/components/reasoning/index.js +244 -0
  25. package/lib/module/components/reasoning/index.js.map +1 -0
  26. package/lib/module/components/response/index.js +296 -0
  27. package/lib/module/components/response/index.js.map +1 -0
  28. package/lib/module/components/response/markdown.js +463 -0
  29. package/lib/module/components/response/markdown.js.map +1 -0
  30. package/lib/module/components/select/index.js +97 -15
  31. package/lib/module/components/select/index.js.map +1 -1
  32. package/lib/module/components/sources/index.js +221 -0
  33. package/lib/module/components/sources/index.js.map +1 -0
  34. package/lib/module/components/table/index.js +632 -0
  35. package/lib/module/components/table/index.js.map +1 -0
  36. package/lib/module/components/task/index.js +300 -0
  37. package/lib/module/components/task/index.js.map +1 -0
  38. package/lib/module/components/textarea/index.js +295 -0
  39. package/lib/module/components/textarea/index.js.map +1 -0
  40. package/lib/module/components/tooltip/index.js +179 -23
  41. package/lib/module/components/tooltip/index.js.map +1 -1
  42. package/lib/module/icons/index.js +339 -0
  43. package/lib/module/icons/index.js.map +1 -1
  44. package/lib/module/index.js +12 -1
  45. package/lib/module/index.js.map +1 -1
  46. package/lib/module/primitives/collapse.js +112 -0
  47. package/lib/module/primitives/collapse.js.map +1 -0
  48. package/lib/module/utils/date.js +129 -22
  49. package/lib/module/utils/date.js.map +1 -1
  50. package/lib/typescript/src/components/calendar/index.d.ts +79 -5
  51. package/lib/typescript/src/components/calendar/index.d.ts.map +1 -1
  52. package/lib/typescript/src/components/code-block/highlight.d.ts +39 -0
  53. package/lib/typescript/src/components/code-block/highlight.d.ts.map +1 -0
  54. package/lib/typescript/src/components/code-block/index.d.ts +113 -0
  55. package/lib/typescript/src/components/code-block/index.d.ts.map +1 -0
  56. package/lib/typescript/src/components/date-picker/index.d.ts +7 -1
  57. package/lib/typescript/src/components/date-picker/index.d.ts.map +1 -1
  58. package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
  59. package/lib/typescript/src/components/map/index.d.ts +8 -1
  60. package/lib/typescript/src/components/map/index.d.ts.map +1 -1
  61. package/lib/typescript/src/components/menu/index.d.ts +261 -0
  62. package/lib/typescript/src/components/menu/index.d.ts.map +1 -0
  63. package/lib/typescript/src/components/plan/index.d.ts +238 -0
  64. package/lib/typescript/src/components/plan/index.d.ts.map +1 -0
  65. package/lib/typescript/src/components/popover/index.d.ts +35 -1
  66. package/lib/typescript/src/components/popover/index.d.ts.map +1 -1
  67. package/lib/typescript/src/components/post/index.d.ts +358 -0
  68. package/lib/typescript/src/components/post/index.d.ts.map +1 -0
  69. package/lib/typescript/src/components/radio-group/index.d.ts.map +1 -1
  70. package/lib/typescript/src/components/reasoning/index.d.ts +97 -0
  71. package/lib/typescript/src/components/reasoning/index.d.ts.map +1 -0
  72. package/lib/typescript/src/components/response/index.d.ts +91 -0
  73. package/lib/typescript/src/components/response/index.d.ts.map +1 -0
  74. package/lib/typescript/src/components/response/markdown.d.ts +88 -0
  75. package/lib/typescript/src/components/response/markdown.d.ts.map +1 -0
  76. package/lib/typescript/src/components/select/index.d.ts +27 -2
  77. package/lib/typescript/src/components/select/index.d.ts.map +1 -1
  78. package/lib/typescript/src/components/sources/index.d.ts +85 -0
  79. package/lib/typescript/src/components/sources/index.d.ts.map +1 -0
  80. package/lib/typescript/src/components/table/index.d.ts +274 -0
  81. package/lib/typescript/src/components/table/index.d.ts.map +1 -0
  82. package/lib/typescript/src/components/task/index.d.ts +103 -0
  83. package/lib/typescript/src/components/task/index.d.ts.map +1 -0
  84. package/lib/typescript/src/components/textarea/index.d.ts +175 -0
  85. package/lib/typescript/src/components/textarea/index.d.ts.map +1 -0
  86. package/lib/typescript/src/components/tooltip/index.d.ts +63 -8
  87. package/lib/typescript/src/components/tooltip/index.d.ts.map +1 -1
  88. package/lib/typescript/src/icons/index.d.ts +54 -1
  89. package/lib/typescript/src/icons/index.d.ts.map +1 -1
  90. package/lib/typescript/src/index.d.ts +14 -3
  91. package/lib/typescript/src/index.d.ts.map +1 -1
  92. package/lib/typescript/src/primitives/collapse.d.ts +42 -0
  93. package/lib/typescript/src/primitives/collapse.d.ts.map +1 -0
  94. package/lib/typescript/src/utils/date.d.ts +49 -5
  95. package/lib/typescript/src/utils/date.d.ts.map +1 -1
  96. package/package.json +1 -1
  97. package/src/components/calendar/index.tsx +519 -139
  98. package/src/components/code-block/highlight.ts +278 -0
  99. package/src/components/code-block/index.tsx +341 -0
  100. package/src/components/date-picker/index.tsx +19 -1
  101. package/src/components/heatmap-chart/index.tsx +9 -9
  102. package/src/components/map/index.tsx +53 -4
  103. package/src/components/menu/index.tsx +812 -0
  104. package/src/components/plan/index.tsx +648 -0
  105. package/src/components/popover/index.tsx +69 -2
  106. package/src/components/post/index.tsx +1116 -0
  107. package/src/components/radio-group/index.tsx +11 -2
  108. package/src/components/reasoning/index.tsx +297 -0
  109. package/src/components/response/index.tsx +390 -0
  110. package/src/components/response/markdown.ts +489 -0
  111. package/src/components/select/index.tsx +132 -12
  112. package/src/components/sources/index.tsx +278 -0
  113. package/src/components/table/index.tsx +747 -0
  114. package/src/components/task/index.tsx +329 -0
  115. package/src/components/textarea/index.tsx +381 -0
  116. package/src/components/tooltip/index.tsx +203 -20
  117. package/src/icons/index.tsx +264 -1
  118. package/src/index.ts +131 -0
  119. package/src/primitives/collapse.tsx +128 -0
  120. package/src/utils/date.ts +146 -22
  121. package/theme.css +112 -0
@@ -211,13 +211,22 @@ const RadioGroupItem = forwardRef<View, RadioGroupItemProps>(
211
211
  </View>
212
212
  );
213
213
 
214
+ // Either way the label is a string in a row that is otherwise all views,
215
+ // so children given instead of the prop are dressed the same way rather
216
+ // than reaching the pressable bare.
217
+ const labelled = label ? (
218
+ <Text className={slots.label()}>{label}</Text>
219
+ ) : (
220
+ textChildren(children, (text) => <Text className={slots.label()}>{text}</Text>)
221
+ );
222
+
214
223
  // The card lays its text out first so the disc sits at the trailing edge;
215
224
  // the dot row keeps the classic disc-then-label order.
216
225
  const body =
217
226
  variant === 'card' ? (
218
227
  <>
219
228
  <View className="flex-1 gap-1">
220
- {label ? <Text className={slots.label()}>{label}</Text> : children}
229
+ {labelled}
221
230
  {description ? (
222
231
  <Text className={slots.description()}>{description}</Text>
223
232
  ) : null}
@@ -227,7 +236,7 @@ const RadioGroupItem = forwardRef<View, RadioGroupItemProps>(
227
236
  ) : (
228
237
  <>
229
238
  {indicator}
230
- {label ? <Text className={slots.label()}>{label}</Text> : children}
239
+ {labelled}
231
240
  </>
232
241
  );
233
242
 
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Reasoning — the model's working, shown while it happens and folded away after.
3
+ *
4
+ * A reasoning trace is interesting for exactly as long as it is the only thing
5
+ * arriving. Once the answer starts, it is a wall of text above the thing the
6
+ * reader actually asked for. So it opens itself when the trace starts streaming
7
+ * and closes itself about a second after it stops — once, and only if it ever
8
+ * streamed, because a trace that arrives complete was never a live thing to
9
+ * watch and should not perform.
10
+ *
11
+ * The trigger changes with it: a shimmering "Thinking…" while the tokens are
12
+ * coming, and "Thought for 8 seconds" afterwards. The duration is measured from
13
+ * the first streaming frame rather than taken on trust, so it is the time the
14
+ * reader actually waited.
15
+ *
16
+ * ```tsx
17
+ * <Reasoning isStreaming={streaming}>
18
+ * <Reasoning.Trigger />
19
+ * <Reasoning.Content>{text}</Reasoning.Content>
20
+ * </Reasoning>
21
+ * ```
22
+ *
23
+ * ## Where the props come from
24
+ *
25
+ * With the AI SDK, `isStreaming` is whether the last part of the last message
26
+ * is a reasoning part while the request is still streaming, and the content is
27
+ * every `reasoning` part joined together. Joined, not one component each:
28
+ * some models emit a run of separate reasoning parts, and one component per
29
+ * part is a column of "Thinking…" rows for a single thought.
30
+ */
31
+ import {
32
+ createContext,
33
+ useCallback,
34
+ useContext,
35
+ useEffect,
36
+ useMemo,
37
+ useRef,
38
+ useState,
39
+ type ReactNode,
40
+ } from 'react';
41
+ import { Pressable, View, type PressableProps, type ViewProps } from 'react-native';
42
+ import Animated, {
43
+ useAnimatedStyle,
44
+ useReducedMotion,
45
+ useSharedValue,
46
+ withTiming,
47
+ } from 'react-native-reanimated';
48
+ import { tv } from 'tailwind-variants';
49
+ import { ChevronDownIcon, SparklesIcon } from '../../icons';
50
+ import { Collapse } from '../../primitives/collapse';
51
+ import { Text, textChildren } from '../../primitives/text';
52
+ import { cn } from '../../utils/cn';
53
+ import { Shimmer } from '../shimmer';
54
+
55
+ /**
56
+ * How long the finished trace stays open before folding away.
57
+ *
58
+ * Long enough that the fold reads as a consequence of the trace ending rather
59
+ * than as part of it, short enough that nobody is waiting on it.
60
+ */
61
+ const AUTO_CLOSE_DELAY = 1000;
62
+
63
+ const reasoningVariants = tv({
64
+ slots: {
65
+ root: 'w-full gap-2',
66
+ trigger: 'flex-row items-center gap-2 py-0.5',
67
+ label: 'text-sm text-muted-foreground',
68
+ content: 'gap-2 ps-6',
69
+ contentText: 'text-sm leading-relaxed text-muted-foreground',
70
+ },
71
+ });
72
+
73
+ interface ReasoningContextValue {
74
+ isStreaming: boolean;
75
+ open: boolean;
76
+ setOpen: (open: boolean) => void;
77
+ /** Seconds the trace took, once it has finished. */
78
+ duration: number | undefined;
79
+ }
80
+
81
+ const ReasoningContext = createContext<ReasoningContextValue | null>(null);
82
+
83
+ function useReasoning(component: string): ReasoningContextValue {
84
+ const context = useContext(ReasoningContext);
85
+ if (!context) {
86
+ throw new Error(`${component} must be used within a <Reasoning>`);
87
+ }
88
+ return context;
89
+ }
90
+
91
+ export interface ReasoningProps extends Omit<ViewProps, 'children'> {
92
+ className?: string;
93
+ /**
94
+ * Whether the trace is still arriving. Drives the shimmer on the trigger,
95
+ * opens the panel on the way in and closes it a beat after it goes false.
96
+ */
97
+ isStreaming?: boolean;
98
+ /**
99
+ * How long the trace took, in seconds. Measured from the first streaming
100
+ * frame when it is not given, which is the number worth showing — it is what
101
+ * the reader actually waited.
102
+ */
103
+ duration?: number;
104
+ /** Controlled open state. */
105
+ open?: boolean;
106
+ /**
107
+ * Initial state when uncontrolled. Defaults to whether it is streaming, so a
108
+ * live trace is open and a finished one arrives folded. Passing `false`
109
+ * explicitly also opts out of the auto-open.
110
+ */
111
+ defaultOpen?: boolean;
112
+ onOpenChange?: (open: boolean) => void;
113
+ children?: ReactNode;
114
+ }
115
+
116
+ function ReasoningRoot({
117
+ className,
118
+ isStreaming = false,
119
+ duration: durationProp,
120
+ open: openProp,
121
+ defaultOpen,
122
+ onOpenChange,
123
+ children,
124
+ ...props
125
+ }: ReasoningProps) {
126
+ const { root } = reasoningVariants();
127
+ const [internalOpen, setInternalOpen] = useState(defaultOpen ?? isStreaming);
128
+ const [measured, setMeasured] = useState<number>();
129
+
130
+ const isControlled = openProp !== undefined;
131
+ const open = isControlled ? openProp : internalOpen;
132
+
133
+ const setOpen = useCallback(
134
+ (next: boolean) => {
135
+ if (!isControlled) setInternalOpen(next);
136
+ onOpenChange?.(next);
137
+ },
138
+ [isControlled, onOpenChange]
139
+ );
140
+
141
+ /*
142
+ * A caller that said `defaultOpen={false}` meant it. Without this the panel
143
+ * would spring open the moment the trace started, which is the one thing that
144
+ * request was asking not to happen.
145
+ */
146
+ const optedOut = defaultOpen === false;
147
+ const everStreamed = useRef(isStreaming);
148
+ const startedAt = useRef<number | null>(null);
149
+ const [autoClosed, setAutoClosed] = useState(false);
150
+
151
+ // Timed here rather than taken on trust: `duration` is optional, and the
152
+ // honest number is the wall clock between the first token and the last.
153
+ useEffect(() => {
154
+ if (isStreaming) {
155
+ everStreamed.current = true;
156
+ startedAt.current ??= Date.now();
157
+ return;
158
+ }
159
+ if (startedAt.current !== null) {
160
+ setMeasured(Math.max(1, Math.round((Date.now() - startedAt.current) / 1000)));
161
+ startedAt.current = null;
162
+ }
163
+ }, [isStreaming]);
164
+
165
+ useEffect(() => {
166
+ if (isStreaming && !open && !optedOut) setOpen(true);
167
+ }, [isStreaming, open, optedOut, setOpen]);
168
+
169
+ /*
170
+ * Once only. Without the latch, reopening a finished trace by hand would be
171
+ * undone a second later by the same effect, and the panel would refuse to
172
+ * stay open for the one reader who wanted to read it.
173
+ */
174
+ useEffect(() => {
175
+ if (!everStreamed.current || isStreaming || !open || autoClosed) return;
176
+ const timer = setTimeout(() => {
177
+ setOpen(false);
178
+ setAutoClosed(true);
179
+ }, AUTO_CLOSE_DELAY);
180
+ return () => clearTimeout(timer);
181
+ }, [isStreaming, open, autoClosed, setOpen]);
182
+
183
+ const context = useMemo(
184
+ () => ({ isStreaming, open, setOpen, duration: durationProp ?? measured }),
185
+ [isStreaming, open, setOpen, durationProp, measured]
186
+ );
187
+
188
+ return (
189
+ <ReasoningContext.Provider value={context}>
190
+ <View {...props} className={cn(root(), className)}>
191
+ {children}
192
+ </View>
193
+ </ReasoningContext.Provider>
194
+ );
195
+ }
196
+ ReasoningRoot.displayName = 'Reasoning';
197
+
198
+ export interface ReasoningTriggerProps
199
+ extends Omit<PressableProps, 'children' | 'style'> {
200
+ className?: string;
201
+ /**
202
+ * What the row says. Given the streaming state and the measured duration, so
203
+ * a caller can put its own words to both without reimplementing the timing.
204
+ */
205
+ label?: (isStreaming: boolean, duration?: number) => ReactNode;
206
+ /** Replaces the whole row, icon and chevron included. */
207
+ children?: ReactNode;
208
+ }
209
+
210
+ /** The row that says how long it thought, and folds the trace away. */
211
+ function ReasoningTrigger({ className, label, children, onPress, ...props }: ReasoningTriggerProps) {
212
+ const { isStreaming, open, setOpen, duration } = useReasoning('Reasoning.Trigger');
213
+ const { trigger, label: labelClass } = reasoningVariants();
214
+ const reducedMotion = useReducedMotion();
215
+ const progress = useSharedValue(open ? 1 : 0);
216
+
217
+ useEffect(() => {
218
+ progress.value = reducedMotion
219
+ ? open
220
+ ? 1
221
+ : 0
222
+ : withTiming(open ? 1 : 0, { duration: 180 });
223
+ }, [open, reducedMotion, progress]);
224
+
225
+ const chevronStyle = useAnimatedStyle(() => ({
226
+ transform: [{ rotate: `${progress.value * 180}deg` }],
227
+ }));
228
+
229
+ const body = label ? (
230
+ label(isStreaming, duration)
231
+ ) : isStreaming ? (
232
+ <Shimmer textClassName={labelClass()}>Thinking…</Shimmer>
233
+ ) : (
234
+ <Text className={labelClass()}>
235
+ {duration === undefined
236
+ ? 'Thought for a few seconds'
237
+ : `Thought for ${duration} ${duration === 1 ? 'second' : 'seconds'}`}
238
+ </Text>
239
+ );
240
+
241
+ return (
242
+ <Pressable
243
+ accessibilityRole="button"
244
+ accessibilityState={{ expanded: open }}
245
+ onPress={(event) => {
246
+ onPress?.(event);
247
+ setOpen(!open);
248
+ }}
249
+ className={cn(trigger(), className)}
250
+ {...props}
251
+ >
252
+ {children ?? (
253
+ <>
254
+ <SparklesIcon size={16} />
255
+ <View className="flex-1">{body}</View>
256
+ <Animated.View style={chevronStyle}>
257
+ <ChevronDownIcon size={16} />
258
+ </Animated.View>
259
+ </>
260
+ )}
261
+ </Pressable>
262
+ );
263
+ }
264
+
265
+ export interface ReasoningContentProps extends Omit<ViewProps, 'children'> {
266
+ className?: string;
267
+ /** The trace. A string is wrapped for you; anything else is left alone. */
268
+ children?: ReactNode;
269
+ }
270
+
271
+ /**
272
+ * The trace itself.
273
+ *
274
+ * Kept mounted and collapsed to nothing rather than unmounted, because it is
275
+ * usually still growing while it is folded — and a body that remounts on every
276
+ * open would drop whatever the reader had scrolled to.
277
+ */
278
+ function ReasoningContent({ className, children, ...props }: ReasoningContentProps) {
279
+ const { open } = useReasoning('Reasoning.Content');
280
+ const { content, contentText } = reasoningVariants();
281
+
282
+ return (
283
+ <Collapse open={open} className={cn(content(), className)} {...props}>
284
+ {textChildren(children, (text) => (
285
+ <Text className={contentText()}>{text}</Text>
286
+ ))}
287
+ </Collapse>
288
+ );
289
+ }
290
+
291
+ ReasoningTrigger.displayName = 'Reasoning.Trigger';
292
+ ReasoningContent.displayName = 'Reasoning.Content';
293
+
294
+ export const Reasoning = Object.assign(ReasoningRoot, {
295
+ Trigger: ReasoningTrigger,
296
+ Content: ReasoningContent,
297
+ });