panelui-native 0.25.0 → 0.28.1

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 (193) hide show
  1. package/lib/module/components/accordion/index.js +8 -8
  2. package/lib/module/components/accordion/index.js.map +1 -1
  3. package/lib/module/components/alert/index.js +2 -2
  4. package/lib/module/components/alert/index.js.map +1 -1
  5. package/lib/module/components/area-chart/index.js +182 -50
  6. package/lib/module/components/area-chart/index.js.map +1 -1
  7. package/lib/module/components/attachment/index.js +2 -1
  8. package/lib/module/components/attachment/index.js.map +1 -1
  9. package/lib/module/components/avatar/index.js +3 -3
  10. package/lib/module/components/avatar/index.js.map +1 -1
  11. package/lib/module/components/badge/index.js +4 -4
  12. package/lib/module/components/badge/index.js.map +1 -1
  13. package/lib/module/components/bar-chart/index.js +197 -56
  14. package/lib/module/components/bar-chart/index.js.map +1 -1
  15. package/lib/module/components/bottom-sheet/index.js +15 -10
  16. package/lib/module/components/bottom-sheet/index.js.map +1 -1
  17. package/lib/module/components/breadcrumb/index.js +6 -6
  18. package/lib/module/components/breadcrumb/index.js.map +1 -1
  19. package/lib/module/components/button/index.js +11 -6
  20. package/lib/module/components/button/index.js.map +1 -1
  21. package/lib/module/components/calendar/index.js +557 -0
  22. package/lib/module/components/calendar/index.js.map +1 -0
  23. package/lib/module/components/carousel/index.js +680 -0
  24. package/lib/module/components/carousel/index.js.map +1 -0
  25. package/lib/module/components/chip/index.js +4 -4
  26. package/lib/module/components/chip/index.js.map +1 -1
  27. package/lib/module/components/date-picker/index.js +158 -0
  28. package/lib/module/components/date-picker/index.js.map +1 -0
  29. package/lib/module/components/dialog/index.js +7 -2
  30. package/lib/module/components/dialog/index.js.map +1 -1
  31. package/lib/module/components/direction/index.js +2 -1
  32. package/lib/module/components/direction/index.js.map +1 -1
  33. package/lib/module/components/empty-state/index.js +2 -2
  34. package/lib/module/components/empty-state/index.js.map +1 -1
  35. package/lib/module/components/field/index.js +5 -5
  36. package/lib/module/components/field/index.js.map +1 -1
  37. package/lib/module/components/flow/index.js +3 -3
  38. package/lib/module/components/flow/index.js.map +1 -1
  39. package/lib/module/components/frame/index.js +5 -5
  40. package/lib/module/components/frame/index.js.map +1 -1
  41. package/lib/module/components/input-group/index.js +3 -2
  42. package/lib/module/components/input-group/index.js.map +1 -1
  43. package/lib/module/components/item/index.js +9 -9
  44. package/lib/module/components/item/index.js.map +1 -1
  45. package/lib/module/components/label/index.js +4 -4
  46. package/lib/module/components/label/index.js.map +1 -1
  47. package/lib/module/components/line-chart/index.js +167 -44
  48. package/lib/module/components/line-chart/index.js.map +1 -1
  49. package/lib/module/components/map/index.js +7 -7
  50. package/lib/module/components/map/index.js.map +1 -1
  51. package/lib/module/components/marker/index.js +3 -3
  52. package/lib/module/components/marker/index.js.map +1 -1
  53. package/lib/module/components/message/index.js +7 -7
  54. package/lib/module/components/message/index.js.map +1 -1
  55. package/lib/module/components/message-scroller/index.js +5 -4
  56. package/lib/module/components/message-scroller/index.js.map +1 -1
  57. package/lib/module/components/popover/index.js +39 -7
  58. package/lib/module/components/popover/index.js.map +1 -1
  59. package/lib/module/components/radio-group/index.js +2 -2
  60. package/lib/module/components/radio-group/index.js.map +1 -1
  61. package/lib/module/components/section-rail/index.js +4 -4
  62. package/lib/module/components/section-rail/index.js.map +1 -1
  63. package/lib/module/components/select/index.js +10 -4
  64. package/lib/module/components/select/index.js.map +1 -1
  65. package/lib/module/components/steps/index.js +4 -4
  66. package/lib/module/components/steps/index.js.map +1 -1
  67. package/lib/module/components/tabs/index.js +7 -7
  68. package/lib/module/components/tabs/index.js.map +1 -1
  69. package/lib/module/components/timeline/index.js +4 -4
  70. package/lib/module/components/timeline/index.js.map +1 -1
  71. package/lib/module/components/toast/index.js +2 -2
  72. package/lib/module/components/toast/index.js.map +1 -1
  73. package/lib/module/components/toggle-button/index.js +5 -5
  74. package/lib/module/components/toggle-button/index.js.map +1 -1
  75. package/lib/module/components/tooltip/index.js +492 -0
  76. package/lib/module/components/tooltip/index.js.map +1 -0
  77. package/lib/module/components/typography/index.js +2 -2
  78. package/lib/module/components/typography/index.js.map +1 -1
  79. package/lib/module/hooks/index.js +1 -0
  80. package/lib/module/hooks/index.js.map +1 -1
  81. package/lib/module/hooks/use-back-handler.js +37 -0
  82. package/lib/module/hooks/use-back-handler.js.map +1 -0
  83. package/lib/module/icons/index.js +63 -0
  84. package/lib/module/icons/index.js.map +1 -1
  85. package/lib/module/index.js +5 -1
  86. package/lib/module/index.js.map +1 -1
  87. package/lib/module/primitives/keyboard-avoider.js +2 -1
  88. package/lib/module/primitives/keyboard-avoider.js.map +1 -1
  89. package/lib/module/primitives/text.js +25 -1
  90. package/lib/module/primitives/text.js.map +1 -1
  91. package/lib/module/utils/date.js +386 -0
  92. package/lib/module/utils/date.js.map +1 -0
  93. package/lib/typescript/src/components/accordion/index.d.ts.map +1 -1
  94. package/lib/typescript/src/components/alert/index.d.ts.map +1 -1
  95. package/lib/typescript/src/components/area-chart/index.d.ts +41 -1
  96. package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -1
  97. package/lib/typescript/src/components/attachment/index.d.ts.map +1 -1
  98. package/lib/typescript/src/components/bar-chart/index.d.ts +44 -2
  99. package/lib/typescript/src/components/bar-chart/index.d.ts.map +1 -1
  100. package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
  101. package/lib/typescript/src/components/breadcrumb/index.d.ts.map +1 -1
  102. package/lib/typescript/src/components/button/index.d.ts.map +1 -1
  103. package/lib/typescript/src/components/calendar/index.d.ts +108 -0
  104. package/lib/typescript/src/components/calendar/index.d.ts.map +1 -0
  105. package/lib/typescript/src/components/carousel/index.d.ts +179 -0
  106. package/lib/typescript/src/components/carousel/index.d.ts.map +1 -0
  107. package/lib/typescript/src/components/chip/index.d.ts.map +1 -1
  108. package/lib/typescript/src/components/date-picker/index.d.ts +91 -0
  109. package/lib/typescript/src/components/date-picker/index.d.ts.map +1 -0
  110. package/lib/typescript/src/components/dialog/index.d.ts.map +1 -1
  111. package/lib/typescript/src/components/direction/index.d.ts.map +1 -1
  112. package/lib/typescript/src/components/empty-state/index.d.ts.map +1 -1
  113. package/lib/typescript/src/components/field/index.d.ts.map +1 -1
  114. package/lib/typescript/src/components/frame/index.d.ts.map +1 -1
  115. package/lib/typescript/src/components/input-group/index.d.ts.map +1 -1
  116. package/lib/typescript/src/components/item/index.d.ts.map +1 -1
  117. package/lib/typescript/src/components/label/index.d.ts.map +1 -1
  118. package/lib/typescript/src/components/line-chart/index.d.ts +41 -2
  119. package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
  120. package/lib/typescript/src/components/map/index.d.ts.map +1 -1
  121. package/lib/typescript/src/components/marker/index.d.ts.map +1 -1
  122. package/lib/typescript/src/components/message/index.d.ts.map +1 -1
  123. package/lib/typescript/src/components/message-scroller/index.d.ts.map +1 -1
  124. package/lib/typescript/src/components/popover/index.d.ts.map +1 -1
  125. package/lib/typescript/src/components/select/index.d.ts.map +1 -1
  126. package/lib/typescript/src/components/steps/index.d.ts.map +1 -1
  127. package/lib/typescript/src/components/tabs/index.d.ts.map +1 -1
  128. package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
  129. package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
  130. package/lib/typescript/src/components/toggle-button/index.d.ts.map +1 -1
  131. package/lib/typescript/src/components/tooltip/index.d.ts +125 -0
  132. package/lib/typescript/src/components/tooltip/index.d.ts.map +1 -0
  133. package/lib/typescript/src/components/typography/index.d.ts.map +1 -1
  134. package/lib/typescript/src/hooks/index.d.ts +1 -0
  135. package/lib/typescript/src/hooks/index.d.ts.map +1 -1
  136. package/lib/typescript/src/hooks/use-back-handler.d.ts +19 -0
  137. package/lib/typescript/src/hooks/use-back-handler.d.ts.map +1 -0
  138. package/lib/typescript/src/icons/index.d.ts +3 -0
  139. package/lib/typescript/src/icons/index.d.ts.map +1 -1
  140. package/lib/typescript/src/index.d.ts +8 -4
  141. package/lib/typescript/src/index.d.ts.map +1 -1
  142. package/lib/typescript/src/primitives/keyboard-avoider.d.ts.map +1 -1
  143. package/lib/typescript/src/primitives/text.d.ts +20 -0
  144. package/lib/typescript/src/primitives/text.d.ts.map +1 -1
  145. package/lib/typescript/src/utils/date.d.ts +116 -0
  146. package/lib/typescript/src/utils/date.d.ts.map +1 -0
  147. package/package.json +1 -1
  148. package/src/components/accordion/index.tsx +8 -12
  149. package/src/components/alert/index.tsx +2 -2
  150. package/src/components/area-chart/index.tsx +208 -55
  151. package/src/components/attachment/index.tsx +2 -2
  152. package/src/components/avatar/index.tsx +3 -3
  153. package/src/components/badge/index.tsx +6 -6
  154. package/src/components/bar-chart/index.tsx +214 -54
  155. package/src/components/bottom-sheet/index.tsx +17 -16
  156. package/src/components/breadcrumb/index.tsx +6 -8
  157. package/src/components/button/index.tsx +12 -8
  158. package/src/components/calendar/index.tsx +703 -0
  159. package/src/components/carousel/index.tsx +788 -0
  160. package/src/components/chip/index.tsx +4 -6
  161. package/src/components/date-picker/index.tsx +237 -0
  162. package/src/components/dialog/index.tsx +7 -2
  163. package/src/components/direction/index.tsx +2 -1
  164. package/src/components/empty-state/index.tsx +2 -2
  165. package/src/components/field/index.tsx +3 -5
  166. package/src/components/flow/index.tsx +3 -3
  167. package/src/components/frame/index.tsx +5 -7
  168. package/src/components/input-group/index.tsx +3 -2
  169. package/src/components/item/index.tsx +9 -9
  170. package/src/components/label/index.tsx +2 -2
  171. package/src/components/line-chart/index.tsx +189 -50
  172. package/src/components/map/index.tsx +7 -9
  173. package/src/components/marker/index.tsx +3 -3
  174. package/src/components/message/index.tsx +7 -7
  175. package/src/components/message-scroller/index.tsx +5 -4
  176. package/src/components/popover/index.tsx +43 -7
  177. package/src/components/radio-group/index.tsx +2 -2
  178. package/src/components/section-rail/index.tsx +4 -4
  179. package/src/components/select/index.tsx +10 -4
  180. package/src/components/steps/index.tsx +4 -4
  181. package/src/components/tabs/index.tsx +7 -9
  182. package/src/components/timeline/index.tsx +4 -4
  183. package/src/components/toast/index.tsx +2 -2
  184. package/src/components/toggle-button/index.tsx +5 -7
  185. package/src/components/tooltip/index.tsx +610 -0
  186. package/src/components/typography/index.tsx +2 -2
  187. package/src/hooks/index.ts +1 -0
  188. package/src/hooks/use-back-handler.ts +37 -0
  189. package/src/icons/index.tsx +45 -0
  190. package/src/index.ts +45 -0
  191. package/src/primitives/keyboard-avoider.tsx +2 -1
  192. package/src/primitives/text.tsx +28 -1
  193. package/src/utils/date.ts +446 -0
@@ -38,7 +38,7 @@ import {
38
38
  AnimatedPressable,
39
39
  type AnimatedPressableProps,
40
40
  } from '../../primitives/animated-pressable';
41
- import { Text } from '../../primitives/text';
41
+ import { Text, textChildren } from '../../primitives/text';
42
42
  import { cn } from '../../utils/cn';
43
43
  import { selectionTick } from '../../utils/haptics';
44
44
 
@@ -246,13 +246,11 @@ const ChipRoot = forwardRef<View, ChipProps>(
246
246
  const body = (
247
247
  <>
248
248
  {start}
249
- {typeof children === 'string' ? (
249
+ {textChildren(children, (text) => (
250
250
  <Text className={slots.label({ className: labelClassName })}>
251
- {children}
251
+ {text}
252
252
  </Text>
253
- ) : (
254
- children
255
- )}
253
+ ))}
256
254
  {closable ? (
257
255
  <Pressable
258
256
  accessibilityRole="button"
@@ -0,0 +1,237 @@
1
+ /**
2
+ * DatePicker — a calendar behind a button.
3
+ *
4
+ * ```tsx
5
+ * const [day, setDay] = useState<Date>();
6
+ *
7
+ * <DatePicker selected={day} onSelect={setDay} placeholder="Pick a date" />
8
+ * ```
9
+ *
10
+ * It is the `Calendar` in a `Popover` with a trigger that reads back what was
11
+ * chosen, and it is a separate component from the calendar for the same reason
12
+ * `Select` is separate from a list: the hard parts are the ones around the
13
+ * grid — what a closed field says, when the panel closes, and what a range
14
+ * shows while only half of it has been picked.
15
+ *
16
+ * ## It closes when the selection is finished, not when it changes
17
+ *
18
+ * A single date is done in one tap, so the panel closes on it. A range needs
19
+ * two, so it stays open until the second — closing on the first would put
20
+ * someone back where they started with half a range on screen. Multiple never
21
+ * closes on its own; only the caller knows when a set is complete.
22
+ *
23
+ * ## An anchored panel, not a sheet
24
+ *
25
+ * A month grid is a fixed size that fits beside its trigger on any phone, and
26
+ * a sheet is the right container for a list of unknown length rather than for
27
+ * a shape that is always the same. Pass `presentation="bottom-sheet"` where the
28
+ * screen is busy — a form with the keyboard up is the usual case.
29
+ */
30
+ import { useCallback, useMemo, useState, type ReactElement, type ReactNode } from 'react';
31
+ import { View } from 'react-native';
32
+ import { CalendarIcon } from '../../icons';
33
+ import { Text } from '../../primitives/text';
34
+ import { cn } from '../../utils/cn';
35
+ import { calendarShortDate, resolveCalendar, type CalendarSystem } from '../../utils/date';
36
+ import { Button } from '../button';
37
+ import {
38
+ Calendar,
39
+ type CalendarCaptionLayout,
40
+ type CalendarDisabled,
41
+ type CalendarMode,
42
+ type CalendarSelection,
43
+ type DateRange,
44
+ } from '../calendar';
45
+ import { Popover, type PopoverPresentation } from '../popover';
46
+
47
+ export type DatePickerMode = CalendarMode;
48
+
49
+ /** What a closed picker shows when nothing has been chosen yet. */
50
+ const DEFAULT_PLACEHOLDER = 'Pick a date';
51
+
52
+ /**
53
+ * The chosen value as one line of text.
54
+ *
55
+ * A half-picked range reads as its start followed by a dash rather than as
56
+ * just the start: the dash is what says the picker is waiting for the other
57
+ * end, and without it a half range looks like a finished single date.
58
+ */
59
+ function describe<Mode extends DatePickerMode>(
60
+ mode: Mode,
61
+ value: CalendarSelection[Mode] | undefined,
62
+ locale: string | undefined,
63
+ system: 'gregory' | 'islamic'
64
+ ): string | null {
65
+ if (!value) return null;
66
+ const write = (date: Date) => calendarShortDate(date, system, locale);
67
+
68
+ if (mode === 'range') {
69
+ const range = value as DateRange;
70
+ if (!range.from) return null;
71
+ return range.to ? `${write(range.from)} – ${write(range.to)}` : `${write(range.from)} –`;
72
+ }
73
+
74
+ if (mode === 'multiple') {
75
+ const days = value as Date[];
76
+ if (!days.length) return null;
77
+ if (days.length === 1) return write(days[0]!);
78
+ return `${days.length} dates`;
79
+ }
80
+
81
+ return write(value as Date);
82
+ }
83
+
84
+ export interface DatePickerProps<Mode extends DatePickerMode = 'single'> {
85
+ /** One day, several, or a span with two ends. */
86
+ mode?: Mode;
87
+ /** Controlled selection. Its shape follows `mode`. */
88
+ selected?: CalendarSelection[Mode];
89
+ /** Starting selection when uncontrolled. */
90
+ defaultSelected?: CalendarSelection[Mode];
91
+ onSelect?: (selected: CalendarSelection[Mode]) => void;
92
+ /** Controlled open state of the panel. */
93
+ open?: boolean;
94
+ onOpenChange?: (open: boolean) => void;
95
+ /** What the trigger reads when nothing has been chosen. */
96
+ placeholder?: string;
97
+ /** Override how the chosen value is written on the trigger. */
98
+ format?: (selected: CalendarSelection[Mode]) => string;
99
+ /** Anchored panel, or a sheet from the bottom of the screen. */
100
+ presentation?: PopoverPresentation;
101
+ /** Stop the trigger opening it. */
102
+ disabled?: boolean;
103
+ /** Days that cannot be picked: a list, a span, or a rule. */
104
+ disabledDates?: CalendarDisabled;
105
+ /** Earliest selectable day. */
106
+ minDate?: Date;
107
+ /** Latest selectable day. */
108
+ maxDate?: Date;
109
+ /** Months side by side inside the panel. */
110
+ numberOfMonths?: number;
111
+ /** `dropdown` swaps the month caption for month and year pickers. */
112
+ captionLayout?: CalendarCaptionLayout;
113
+ /** `0` is Sunday. */
114
+ weekStartsOn?: number;
115
+ /** BCP 47 tag for the month and weekday names, and for the trigger's text. */
116
+ locale?: string;
117
+ /**
118
+ * Which calendar the months and day numbers are counted in. Forwarded to the
119
+ * grid, and used for the trigger's own text so the two always agree.
120
+ */
121
+ calendar?: CalendarSystem;
122
+ className?: string;
123
+ /**
124
+ * A trigger of your own. Given one, it is cloned with an `onPress` that opens
125
+ * the panel — so a field row or an icon button can stand in for the default
126
+ * button without this component knowing what either looks like.
127
+ */
128
+ children?: ReactElement<{ onPress?: () => void }> | ReactNode;
129
+ }
130
+
131
+ function DatePickerRoot<Mode extends DatePickerMode = 'single'>({
132
+ mode = 'single' as Mode,
133
+ selected,
134
+ defaultSelected,
135
+ onSelect,
136
+ open: openProp,
137
+ onOpenChange,
138
+ placeholder = DEFAULT_PLACEHOLDER,
139
+ format,
140
+ presentation = 'popover',
141
+ disabled = false,
142
+ disabledDates,
143
+ minDate,
144
+ maxDate,
145
+ numberOfMonths = 1,
146
+ captionLayout = 'label',
147
+ weekStartsOn = 0,
148
+ locale,
149
+ calendar = 'gregory',
150
+ className,
151
+ children,
152
+ }: DatePickerProps<Mode>) {
153
+ // Resolved here as well as in the grid, because the trigger's text has to be
154
+ // written in the same calendar the cells were tapped in.
155
+ const system = useMemo(() => resolveCalendar(calendar, locale), [calendar, locale]);
156
+ const [internalOpen, setInternalOpen] = useState(false);
157
+ const [internalSelected, setInternalSelected] = useState<CalendarSelection[Mode] | undefined>(
158
+ defaultSelected
159
+ );
160
+
161
+ const isOpenControlled = openProp !== undefined;
162
+ const open = isOpenControlled ? openProp : internalOpen;
163
+
164
+ const isControlled = selected !== undefined;
165
+ const value = (isControlled ? selected : internalSelected) as CalendarSelection[Mode];
166
+
167
+ const setOpen = useCallback(
168
+ (next: boolean) => {
169
+ if (!isOpenControlled) setInternalOpen(next);
170
+ onOpenChange?.(next);
171
+ },
172
+ [isOpenControlled, onOpenChange]
173
+ );
174
+
175
+ const handleSelect = useCallback(
176
+ (next: CalendarSelection[Mode]) => {
177
+ if (!isControlled) setInternalSelected(next);
178
+ onSelect?.(next);
179
+
180
+ // Done in one tap, so it closes on one. A range waits for its second end,
181
+ // and a set of dates is only the caller's to call finished.
182
+ if (mode === 'single' && next) setOpen(false);
183
+ if (mode === 'range' && (next as DateRange | undefined)?.to) setOpen(false);
184
+ },
185
+ [isControlled, onSelect, mode, setOpen]
186
+ );
187
+
188
+ const label = useMemo(() => {
189
+ if (format && value) return format(value);
190
+ return describe(mode, value, locale, system);
191
+ }, [format, value, mode, locale, system]);
192
+
193
+ const trigger = children ?? (
194
+ <Button
195
+ variant="outline"
196
+ disabled={disabled}
197
+ className={cn('justify-start gap-2', className)}
198
+ >
199
+ <CalendarIcon size={16} />
200
+ <Text className={label ? undefined : 'text-muted-foreground'}>
201
+ {label ?? placeholder}
202
+ </Text>
203
+ </Button>
204
+ );
205
+
206
+ return (
207
+ <Popover open={open} onOpenChange={setOpen} presentation={presentation}>
208
+ <Popover.Trigger>{trigger as ReactElement<{ onPress?: () => void }>}</Popover.Trigger>
209
+ {/* The padding is on the inner view, not the panel: in sheet mode a
210
+ className on the panel is merged into the sheet's own padding and
211
+ would replace it. `self-center` for the same reason — the panel does
212
+ not have to know which presentation it ended up in. */}
213
+ <Popover.Content width="content-fit">
214
+ <View className="w-[300px] max-w-full self-center p-3">
215
+ <Calendar
216
+ mode={mode}
217
+ selected={value}
218
+ onSelect={handleSelect}
219
+ disabled={disabledDates}
220
+ minDate={minDate}
221
+ maxDate={maxDate}
222
+ numberOfMonths={numberOfMonths}
223
+ captionLayout={captionLayout}
224
+ weekStartsOn={weekStartsOn}
225
+ locale={locale}
226
+ calendar={calendar}
227
+ />
228
+ </View>
229
+ </Popover.Content>
230
+ </Popover>
231
+ );
232
+ }
233
+ DatePickerRoot.displayName = 'DatePicker';
234
+
235
+ export const DatePicker = Object.assign(DatePickerRoot, {
236
+ Trigger: Popover.Trigger,
237
+ });
@@ -14,7 +14,8 @@ import { Pressable, View, type ViewProps } from 'react-native';
14
14
  import Animated, { FadeIn, FadeOut, ZoomIn } from 'react-native-reanimated';
15
15
  import { Portal } from '../../primitives/portal';
16
16
  import { Scrim } from '../../primitives/scrim';
17
- import { Text, type TextProps } from '../../primitives/text';
17
+ import { Text, type TextProps, textChildren } from '../../primitives/text';
18
+ import { useBackHandler } from '../../hooks/use-back-handler';
18
19
  import { cn } from '../../utils/cn';
19
20
 
20
21
  interface DialogContextValue {
@@ -102,6 +103,10 @@ function DialogContent({
102
103
  const context = useDialog('Dialog.Content');
103
104
  const { open, setOpen } = context;
104
105
 
106
+ // While the dialog is up, the Android back button closes it rather than
107
+ // popping the screen behind — but only when tapping outside would too.
108
+ useBackHandler(open && dismissible, () => setOpen(false));
109
+
105
110
  if (!open) return null;
106
111
 
107
112
  return (
@@ -130,7 +135,7 @@ function DialogContent({
130
135
  )}
131
136
  {...props}
132
137
  >
133
- {children}
138
+ {textChildren(children)}
134
139
  </Animated.View>
135
140
  </Animated.View>
136
141
  </DialogContext.Provider>
@@ -42,6 +42,7 @@ import {
42
42
  useDirectionSign,
43
43
  type DirectionValue,
44
44
  } from '../../hooks/use-direction';
45
+ import { textChildren } from '../../primitives/text';
45
46
 
46
47
  export { useDirection, useDirectionSign };
47
48
  export type { DirectionValue };
@@ -79,7 +80,7 @@ export function Direction({ className, children, dir, style, ...props }: Directi
79
80
  return (
80
81
  <DirectionContext.Provider value={value}>
81
82
  <View className={className} style={[directionStyle, style]} {...props}>
82
- {children}
83
+ {textChildren(children)}
83
84
  </View>
84
85
  </DirectionContext.Provider>
85
86
  );
@@ -9,7 +9,7 @@
9
9
  import { createContext, forwardRef, useContext, type ReactNode } from 'react';
10
10
  import { View, type Text as RNText, type ViewProps } from 'react-native';
11
11
  import { tv, type VariantProps } from 'tailwind-variants';
12
- import { Text, type TextProps } from '../../primitives/text';
12
+ import { Text, type TextProps, textChildren } from '../../primitives/text';
13
13
 
14
14
  type EmptyStateSize = 'sm' | 'default' | 'lg';
15
15
 
@@ -140,7 +140,7 @@ const EmptyStateMedia = forwardRef<View, EmptyStateMediaProps>(
140
140
  />
141
141
  </>
142
142
  ) : null}
143
- <View className={card()}>{children}</View>
143
+ <View className={card()}>{textChildren(children)}</View>
144
144
  </View>
145
145
  );
146
146
  }
@@ -31,7 +31,7 @@ import {
31
31
  } from 'react';
32
32
  import { View, type Text as RNText, type ViewProps } from 'react-native';
33
33
  import { tv } from 'tailwind-variants';
34
- import { Text, type TextProps } from '../../primitives/text';
34
+ import { Text, type TextProps, textChildren } from '../../primitives/text';
35
35
  import { Label, type LabelProps } from '../label';
36
36
  import { Separator } from '../separator';
37
37
 
@@ -121,7 +121,7 @@ const FieldRoot = forwardRef<View, FieldProps>(
121
121
  className={root({ className })}
122
122
  {...props}
123
123
  >
124
- {children}
124
+ {textChildren(children)}
125
125
  </View>
126
126
  </FieldContext.Provider>
127
127
  );
@@ -227,10 +227,8 @@ const FieldError = forwardRef<View, FieldErrorProps>(
227
227
  </View>
228
228
  ))
229
229
  )
230
- ) : typeof children === 'string' ? (
231
- <Text className={errorText()}>{children}</Text>
232
230
  ) : (
233
- children
231
+ textChildren(children, (text) => <Text className={errorText()}>{text}</Text>)
234
232
  )}
235
233
  </View>
236
234
  );
@@ -84,7 +84,7 @@ import Svg, {
84
84
  import { tv } from 'tailwind-variants';
85
85
  import { LockIcon, MaximizeIcon, MinusIcon, PlusIcon, UnlockIcon } from '../../icons';
86
86
  import { AnimatedPressable } from '../../primitives/animated-pressable';
87
- import { Text } from '../../primitives/text';
87
+ import { Text, textChildren } from '../../primitives/text';
88
88
  import { cn } from '../../utils/cn';
89
89
  import {
90
90
  anchorOf,
@@ -1160,7 +1160,7 @@ function FlowNode({
1160
1160
  accessibilityState={{ selected }}
1161
1161
  {...props}
1162
1162
  >
1163
- {children}
1163
+ {textChildren(children)}
1164
1164
  </Animated.View>
1165
1165
  </GestureDetector>
1166
1166
  </FlowNodeContext.Provider>
@@ -1772,7 +1772,7 @@ function FlowGroup({
1772
1772
  inside an absolutely-positioned box would be positioned against that
1773
1773
  box, and every graph coordinate would mean something different
1774
1774
  depending on which group it happened to be in. */}
1775
- {children}
1775
+ {textChildren(children)}
1776
1776
  </FlowGroupContext.Provider>
1777
1777
  );
1778
1778
  }
@@ -80,7 +80,7 @@ import {
80
80
  } from 'react-native';
81
81
  import { tv } from 'tailwind-variants';
82
82
  import { ChevronRightIcon } from '../../icons';
83
- import { Text, type TextProps } from '../../primitives/text';
83
+ import { Text, type TextProps, textChildren } from '../../primitives/text';
84
84
  import { cn } from '../../utils/cn';
85
85
 
86
86
  const frameVariants = tv({
@@ -208,13 +208,11 @@ const FrameAction = forwardRef<View, FrameActionProps>(
208
208
  className={cn('shrink-0 flex-row items-center gap-2', className)}
209
209
  {...props}
210
210
  >
211
- {typeof children === 'string' ? (
211
+ {textChildren(children, (text) => (
212
212
  <Text size="sm" muted>
213
- {children}
213
+ {text}
214
214
  </Text>
215
- ) : (
216
- children
217
- )}
215
+ ))}
218
216
  </View>
219
217
  )
220
218
  );
@@ -393,7 +391,7 @@ const FrameRow = forwardRef<View, FrameRowProps>(
393
391
 
394
392
  const body = (
395
393
  <>
396
- {children}
394
+ {textChildren(children)}
397
395
  {chevron ? <ChevronRightIcon size={16} /> : null}
398
396
  </>
399
397
  );
@@ -22,6 +22,7 @@ import {
22
22
  } from 'react-native';
23
23
  import { tv } from 'tailwind-variants';
24
24
  import { Input, type InputProps } from '../input';
25
+ import { textChildren } from '../../primitives/text';
25
26
 
26
27
  const inputGroupVariants = tv({
27
28
  slots: {
@@ -71,7 +72,7 @@ const InputGroupRoot = forwardRef<View, InputGroupProps>(
71
72
  return (
72
73
  <InputGroupContext.Provider value={value}>
73
74
  <View ref={ref} className={root({ className })} {...props}>
74
- {children}
75
+ {textChildren(children)}
75
76
  </View>
76
77
  </InputGroupContext.Provider>
77
78
  );
@@ -116,7 +117,7 @@ function createDecorator(side: 'prefix' | 'suffix') {
116
117
  className={slots[side]({ className })}
117
118
  {...props}
118
119
  >
119
- {children}
120
+ {textChildren(children)}
120
121
  </View>
121
122
  );
122
123
  }
@@ -28,7 +28,7 @@ import { createContext, forwardRef, useContext, type ReactNode } from 'react';
28
28
  import { View, type ViewProps } from 'react-native';
29
29
  import { tv, type VariantProps } from 'tailwind-variants';
30
30
  import { AnimatedPressable, type AnimatedPressableProps } from '../../primitives/animated-pressable';
31
- import { Text, type TextProps } from '../../primitives/text';
31
+ import { Text, type TextProps, textChildren } from '../../primitives/text';
32
32
  import { cn } from '../../utils/cn';
33
33
 
34
34
  type ItemSize = 'default' | 'sm' | 'xs';
@@ -152,7 +152,7 @@ const ItemRoot = forwardRef<View, ItemProps>(
152
152
  className={root({ className })}
153
153
  {...(props as ViewProps)}
154
154
  >
155
- {children}
155
+ {textChildren(children)}
156
156
  </View>
157
157
  ) : (
158
158
  <AnimatedPressable
@@ -164,7 +164,7 @@ const ItemRoot = forwardRef<View, ItemProps>(
164
164
  className={root({ className })}
165
165
  {...props}
166
166
  >
167
- {children}
167
+ {textChildren(children)}
168
168
  </AnimatedPressable>
169
169
  );
170
170
 
@@ -200,7 +200,7 @@ const ItemGroup = forwardRef<View, ItemGroupProps>(
200
200
  )}
201
201
  {...props}
202
202
  >
203
- {children}
203
+ {textChildren(children)}
204
204
  </View>
205
205
  )
206
206
  );
@@ -246,7 +246,7 @@ const ItemMedia = forwardRef<View, ItemMediaProps>(
246
246
  className={mediaVariants({ variant, size: size ?? item.size, className })}
247
247
  {...props}
248
248
  >
249
- {children}
249
+ {textChildren(children)}
250
250
  </View>
251
251
  );
252
252
  }
@@ -277,7 +277,7 @@ const ItemContent = forwardRef<View, ItemContentProps>(
277
277
  )}
278
278
  {...props}
279
279
  >
280
- {children}
280
+ {textChildren(children)}
281
281
  </View>
282
282
  );
283
283
  }
@@ -326,7 +326,7 @@ const ItemActions = forwardRef<View, ItemActionsProps>(
326
326
  className={cn('shrink-0 flex-row items-center gap-1.5', className)}
327
327
  {...props}
328
328
  >
329
- {children}
329
+ {textChildren(children)}
330
330
  </View>
331
331
  )
332
332
  );
@@ -349,7 +349,7 @@ const ItemHeader = forwardRef<View, ItemHeaderProps>(
349
349
  className={cn('w-full flex-row items-center justify-between gap-2', className)}
350
350
  {...props}
351
351
  >
352
- {children}
352
+ {textChildren(children)}
353
353
  </View>
354
354
  )
355
355
  );
@@ -368,7 +368,7 @@ const ItemFooter = forwardRef<View, ItemFooterProps>(
368
368
  className={cn('w-full flex-row items-center gap-2', className)}
369
369
  {...props}
370
370
  >
371
- {children}
371
+ {textChildren(children)}
372
372
  </View>
373
373
  )
374
374
  );
@@ -16,7 +16,7 @@ import {
16
16
  } from 'react';
17
17
  import { View, type Text as RNText, type ViewProps } from 'react-native';
18
18
  import { tv } from 'tailwind-variants';
19
- import { Text, type TextProps } from '../../primitives/text';
19
+ import { Text, type TextProps, textChildren } from '../../primitives/text';
20
20
 
21
21
  const labelVariants = tv({
22
22
  slots: {
@@ -86,7 +86,7 @@ const LabelRoot = forwardRef<View, LabelProps>(
86
86
  {...props}
87
87
  >
88
88
  {/* Bare strings are the common case — wrap them so callers don't have to. */}
89
- {typeof children === 'string' ? <LabelText>{children}</LabelText> : children}
89
+ {textChildren(children, (text) => <LabelText>{text}</LabelText>)}
90
90
  </View>
91
91
  </LabelContext.Provider>
92
92
  );