@vvedantb/native-ui 0.17.2 → 0.18.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 (220) hide show
  1. package/dist/AlertDialog.jsx +123 -0
  2. package/dist/AmbientBackground.jsx +73 -0
  3. package/dist/ArtifactList.jsx +59 -0
  4. package/dist/Avatar.jsx +285 -0
  5. package/dist/Badge.jsx +154 -0
  6. package/dist/BottomSheetContainer.jsx +208 -0
  7. package/dist/BulkActionBar.jsx +717 -0
  8. package/dist/Button.jsx +344 -0
  9. package/dist/CaptureButton.jsx +104 -0
  10. package/dist/Card.jsx +371 -0
  11. package/dist/Checkbox.jsx +177 -0
  12. package/dist/Chip.jsx +79 -0
  13. package/dist/ColorSwatches.jsx +159 -0
  14. package/dist/ConfirmDialog.jsx +157 -0
  15. package/dist/DateTimeField.jsx +204 -0
  16. package/dist/Dialog.jsx +614 -0
  17. package/dist/DividedList.jsx +120 -0
  18. package/dist/DropdownMenu.jsx +849 -0
  19. package/dist/EmptyState.jsx +434 -0
  20. package/dist/ExpoWebRoot.jsx +134 -0
  21. package/dist/FloatingFAB.jsx +95 -0
  22. package/dist/FloatingSurface.jsx +226 -0
  23. package/dist/FloatingTabBar.jsx +403 -0
  24. package/dist/HeaderIconButton.jsx +53 -0
  25. package/dist/IconButton.jsx +157 -0
  26. package/dist/Input.jsx +96 -0
  27. package/dist/Label.jsx +96 -0
  28. package/dist/LabeledSwitchRow.jsx +170 -0
  29. package/dist/LiquidGlass.jsx +2 -0
  30. package/dist/ListRow.jsx +281 -0
  31. package/dist/LoadingState.jsx +205 -0
  32. package/dist/PageIndicator.jsx +128 -0
  33. package/dist/PageTitle.jsx +205 -0
  34. package/dist/Pager.jsx +105 -0
  35. package/dist/PasswordInput.jsx +120 -0
  36. package/dist/PillTabs.jsx +300 -0
  37. package/dist/PinnedBotTile.jsx +212 -0
  38. package/dist/Progress.jsx +147 -0
  39. package/dist/SafeAreaContainer.jsx +38 -0
  40. package/dist/ScreenContainer.jsx +117 -0
  41. package/dist/SearchInput.jsx +150 -0
  42. package/dist/SectionLabel.jsx +128 -0
  43. package/dist/Select.jsx +223 -0
  44. package/dist/Separator.jsx +95 -0
  45. package/dist/Sheet.jsx +343 -0
  46. package/dist/SheetRow.jsx +198 -0
  47. package/dist/Skeleton.jsx +311 -0
  48. package/dist/Spinner.jsx +195 -0
  49. package/dist/StackPageHeader.jsx +160 -0
  50. package/dist/StatGrid.jsx +74 -0
  51. package/dist/StatusDot.jsx +129 -0
  52. package/dist/SurfaceRow.jsx +143 -0
  53. package/dist/SwipeableRow.jsx +50 -0
  54. package/dist/Switch.jsx +130 -0
  55. package/dist/Tabs.jsx +385 -0
  56. package/dist/Textarea.jsx +103 -0
  57. package/dist/ThemeToggle.jsx +78 -0
  58. package/dist/Toast.jsx +396 -0
  59. package/dist/ai/activity-step-detail.jsx +179 -0
  60. package/dist/ai/activity-step-label.js +816 -0
  61. package/dist/ai/activity-step-types.js +14 -0
  62. package/dist/ai/activity-step.jsx +98 -0
  63. package/dist/ai/activity-tasks-utils.js +172 -0
  64. package/dist/ai/activity-timeline.js +47 -0
  65. package/dist/ai/attachments.jsx +258 -0
  66. package/dist/ai/card-button.jsx +188 -0
  67. package/dist/ai/chain-of-thought.jsx +459 -0
  68. package/dist/ai/chat-jump-rail.jsx +57 -0
  69. package/dist/ai/chat-text.js +44 -0
  70. package/dist/ai/chat-thread.jsx +135 -0
  71. package/dist/ai/choice-question.jsx +925 -0
  72. package/dist/ai/code-block.jsx +574 -0
  73. package/dist/ai/collapsible.jsx +275 -0
  74. package/dist/ai/conversation.jsx +723 -0
  75. package/dist/ai/file-chip.jsx +198 -0
  76. package/dist/ai/icon-button.jsx +179 -0
  77. package/dist/ai/index.js +37 -0
  78. package/dist/ai/message-action-sheet.jsx +408 -0
  79. package/dist/ai/message-quote.jsx +241 -0
  80. package/dist/ai/message.jsx +739 -0
  81. package/dist/ai/prompt-input-menu.jsx +365 -0
  82. package/dist/ai/prompt-input.jsx +626 -0
  83. package/dist/ai/reasoning.jsx +446 -0
  84. package/dist/ai/reply-quote-chip.jsx +278 -0
  85. package/dist/ai/response-text.jsx +130 -0
  86. package/dist/ai/shimmer.jsx +149 -0
  87. package/dist/ai/sources.jsx +260 -0
  88. package/dist/ai/subagent-cards.jsx +645 -0
  89. package/dist/ai/suggestion.jsx +186 -0
  90. package/dist/ai/tool-approval-card.jsx +359 -0
  91. package/dist/ai/trace.jsx +728 -0
  92. package/dist/ai/unified-diff.jsx +280 -0
  93. package/dist/ai/use-streaming-disclosure.js +70 -0
  94. package/dist/auth/AuthProviders.jsx +27 -0
  95. package/dist/auth/AuthScreen.jsx +292 -0
  96. package/dist/auth/GoogleSignInButton.jsx +188 -0
  97. package/dist/auth/SecondFactorScreen.jsx +133 -0
  98. package/dist/auth/SignInScreen.jsx +482 -0
  99. package/dist/auth/authErrors.js +131 -0
  100. package/dist/auth/index.js +11 -0
  101. package/dist/auth/totp.js +378 -0
  102. package/dist/blur.js +36 -0
  103. package/dist/brand/AppIcons.jsx +118 -0
  104. package/dist/brand/AppMark.jsx +128 -0
  105. package/dist/brand/AppWordmark.jsx +138 -0
  106. package/dist/brand/GoogleMark.jsx +71 -0
  107. package/dist/brand/ProviderIcon.jsx +192 -0
  108. package/dist/brand/familyApps.js +33 -0
  109. package/dist/brand/index.js +11 -0
  110. package/dist/brand/svg.js +252 -0
  111. package/dist/brand/vProAppBenefits.js +19 -0
  112. package/dist/cn.js +5 -0
  113. package/dist/colors.js +15 -0
  114. package/dist/configureTheme.js +72 -0
  115. package/dist/controlStyles.js +520 -0
  116. package/dist/definiteSize.js +12 -0
  117. package/dist/dialogLayout.js +18 -0
  118. package/dist/entries/ai.js +4 -0
  119. package/dist/entries/ambient.js +3 -0
  120. package/dist/entries/auth.js +5 -0
  121. package/dist/entries/bottom-sheet.js +2 -0
  122. package/dist/entries/brand.js +2 -0
  123. package/dist/entries/button.js +2 -0
  124. package/dist/entries/date-time.js +3 -0
  125. package/dist/entries/expo-router.js +4 -0
  126. package/dist/entries/expo-web-root.js +3 -0
  127. package/dist/entries/floating.js +7 -0
  128. package/dist/entries/gesture-handler.js +4 -0
  129. package/dist/entries/keyboard-controller.js +4 -0
  130. package/dist/entries/layout.js +16 -0
  131. package/dist/entries/markdown.js +4 -0
  132. package/dist/entries/marketing.js +3 -0
  133. package/dist/entries/page-title.js +7 -0
  134. package/dist/entries/password-input.js +3 -0
  135. package/dist/entries/primitives.js +57 -0
  136. package/dist/entries/search-input.js +3 -0
  137. package/dist/entries/tab-bar.js +4 -0
  138. package/dist/entries/theme.js +7 -0
  139. package/dist/entries/toast.js +5 -0
  140. package/dist/entries/video.js +3 -0
  141. package/dist/entries/webview.js +4 -0
  142. package/dist/examples/AiChatGallery.jsx +509 -0
  143. package/dist/examples/ParityGallery.jsx +1307 -0
  144. package/dist/floating.js +62 -0
  145. package/dist/glassBlur.jsx +145 -0
  146. package/dist/gradient.js +17 -0
  147. package/dist/haptics.js +44 -0
  148. package/dist/highlight.js +382 -0
  149. package/dist/index.js +85 -0
  150. package/dist/initials.js +4 -0
  151. package/dist/loadingGrid.js +81 -0
  152. package/dist/markdown/Markdown.jsx +515 -0
  153. package/dist/markdown/MarkdownTable.jsx +443 -0
  154. package/dist/markdown/tableText.js +68 -0
  155. package/dist/marketing/PricingModal.jsx +608 -0
  156. package/dist/marketing/UpgradeToProCard.jsx +164 -0
  157. package/dist/marketing/pricing.js +34 -0
  158. package/dist/media/ImageGallery.jsx +580 -0
  159. package/dist/media/MediaCard.jsx +162 -0
  160. package/dist/media/imageGrid.js +54 -0
  161. package/dist/motion.js +33 -0
  162. package/dist/pagerSwipe.js +104 -0
  163. package/dist/palette.js +104 -0
  164. package/dist/primitives/button.jsx +127 -0
  165. package/dist/primitives/card.jsx +249 -0
  166. package/dist/primitives/icon.jsx +37 -0
  167. package/dist/primitives/input.jsx +56 -0
  168. package/dist/primitives/keyboard-controller.jsx +207 -0
  169. package/dist/primitives/keyboard-parts.jsx +75 -0
  170. package/dist/primitives/native-only-animated-view.jsx +68 -0
  171. package/dist/primitives/overlay.jsx +391 -0
  172. package/dist/primitives/pager-gesture.jsx +131 -0
  173. package/dist/primitives/select-host.jsx +103 -0
  174. package/dist/primitives/select.jsx +555 -0
  175. package/dist/primitives/sheet-drag.jsx +420 -0
  176. package/dist/primitives/sheet-gesture.jsx +219 -0
  177. package/dist/primitives/swipe-row-gesture.jsx +341 -0
  178. package/dist/primitives/switch.jsx +105 -0
  179. package/dist/primitives/text.jsx +145 -0
  180. package/dist/primitives/toast-drag-gesture.jsx +94 -0
  181. package/dist/primitives/use-controllable-state.js +40 -0
  182. package/dist/primitives/web-click-guard.js +69 -0
  183. package/dist/push.js +166 -0
  184. package/dist/relativeTime.js +70 -0
  185. package/dist/router/RouterStackPageHeader.jsx +53 -0
  186. package/dist/selectA11y.js +32 -0
  187. package/dist/settings/MoreFromProjectV.jsx +168 -0
  188. package/dist/settings/SettingsEmptyState.jsx +102 -0
  189. package/dist/settings/SettingsField.jsx +49 -0
  190. package/dist/settings/SettingsSection.jsx +101 -0
  191. package/dist/settings/SettingsStack.jsx +17 -0
  192. package/dist/settings/SettingsToggleRow.jsx +163 -0
  193. package/dist/settings/projectVMobileApps.js +126 -0
  194. package/dist/settings/relaxedSizeStyle.js +11 -0
  195. package/dist/sheetDrag.js +76 -0
  196. package/dist/svgA11y.js +46 -0
  197. package/dist/swipeRow.js +161 -0
  198. package/dist/tabBarRoutes.js +41 -0
  199. package/dist/tabIndicator.js +139 -0
  200. package/dist/tailwind-preset.cjs +255 -0
  201. package/dist/tailwind-preset.d.cts +55 -0
  202. package/dist/theme.jsx +62 -0
  203. package/dist/toastChrome.js +110 -0
  204. package/dist/toastDrag.js +51 -0
  205. package/dist/toastStore.js +54 -0
  206. package/dist/tokens.js +70 -0
  207. package/dist/typography.js +40 -0
  208. package/dist/useReduceMotion.js +32 -0
  209. package/dist/useReduceTransparency.js +27 -0
  210. package/dist/useScheme.js +15 -0
  211. package/dist/video/InlineVideo.jsx +115 -0
  212. package/dist/webview/HtmlView.jsx +169 -0
  213. package/dist/webview/HtmlView.web.jsx +63 -0
  214. package/dist/webview/htmlView.js +43 -0
  215. package/package.json +70 -33
  216. package/src/DropdownMenu.tsx +28 -16
  217. package/src/FloatingTabBar.tsx +78 -41
  218. package/src/ai/conversation.tsx +12 -8
  219. package/src/glassBlur.tsx +47 -22
  220. package/src/primitives/swipe-row-gesture.tsx +11 -2
@@ -1,5 +1,5 @@
1
- import { useEffect } from "react";
2
- import { Pressable, Text, View } from "react-native";
1
+ import { memo, useEffect } from "react";
2
+ import { Pressable, Text, View, type ColorValue } from "react-native";
3
3
  import Animated, {
4
4
  Easing,
5
5
  useAnimatedStyle,
@@ -108,49 +108,19 @@ export function FloatingTabBar({
108
108
  ? options.tabBarActiveTintColor
109
109
  : options.tabBarInactiveTintColor) ??
110
110
  tabColors("segmented", focused, scheme).color;
111
- const onPress = () => {
112
- const event = navigation.emit({
113
- type: "tabPress",
114
- target: route.key,
115
- canPreventDefault: true,
116
- });
117
- if (!focused && !event.defaultPrevented) {
118
- selectionHaptic();
119
- navigation.navigate(route.name, route.params);
120
- }
121
- };
122
-
123
111
  return (
124
- <Pressable
112
+ <TabBarItem
125
113
  key={route.key}
114
+ route={route}
115
+ navigation={navigation}
116
+ focused={focused}
117
+ label={label}
118
+ color={color}
119
+ largerText={largerText}
120
+ renderIcon={options.tabBarIcon}
126
121
  testID={options.tabBarButtonTestID}
127
- accessibilityRole="tab"
128
- accessibilityState={{ selected: focused }}
129
122
  accessibilityLabel={options.tabBarAccessibilityLabel ?? label}
130
- onPress={onPress}
131
- className="min-h-12 min-w-12 flex-1 items-center justify-center gap-1"
132
- style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })}
133
- >
134
- {options.tabBarIcon
135
- ? options.tabBarIcon({
136
- focused,
137
- color,
138
- size: largerText ? 28 : 24,
139
- })
140
- : null}
141
- <Text
142
- numberOfLines={1}
143
- // The bar has a fixed height (`tabBarReserve`), so labels
144
- // scale with Dynamic Type only up to what fits in it.
145
- maxFontSizeMultiplier={1.5}
146
- className={`font-instrument-sans-semibold ${
147
- largerText ? "text-base" : "text-sm"
148
- }`}
149
- style={[{ color }, typeStyle("semibold")]}
150
- >
151
- {label}
152
- </Text>
153
- </Pressable>
123
+ />
154
124
  );
155
125
  })}
156
126
  </View>
@@ -159,6 +129,73 @@ export function FloatingTabBar({
159
129
  );
160
130
  }
161
131
 
132
+ type TabBarItemProps = {
133
+ route: FloatingTabBarProps["state"]["routes"][number];
134
+ navigation: FloatingTabBarProps["navigation"];
135
+ focused: boolean;
136
+ label: string;
137
+ color: ColorValue;
138
+ largerText: boolean;
139
+ renderIcon: FloatingTabOptions["tabBarIcon"];
140
+ testID: string | undefined;
141
+ accessibilityLabel: string;
142
+ };
143
+
144
+ /**
145
+ * One tab. Memoised: a switch re-renders the bar, and without it every tab
146
+ * and its SVG icon re-rendered too (the kit is not built with React
147
+ * Compiler). Now only the two tabs whose focus changed render. The props are
148
+ * stable between switches: `route` and `navigation` keep their identity, and
149
+ * `tabBarIcon` is the caller's function.
150
+ */
151
+ const TabBarItem = memo(function TabBarItem({
152
+ route,
153
+ navigation,
154
+ focused,
155
+ label,
156
+ color,
157
+ largerText,
158
+ renderIcon,
159
+ testID,
160
+ accessibilityLabel,
161
+ }: TabBarItemProps) {
162
+ const onPress = () => {
163
+ const event = navigation.emit({
164
+ type: "tabPress",
165
+ target: route.key,
166
+ canPreventDefault: true,
167
+ });
168
+ if (!focused && !event.defaultPrevented) {
169
+ selectionHaptic();
170
+ navigation.navigate(route.name, route.params);
171
+ }
172
+ };
173
+
174
+ return (
175
+ <Pressable
176
+ testID={testID}
177
+ accessibilityRole="tab"
178
+ accessibilityState={{ selected: focused }}
179
+ accessibilityLabel={accessibilityLabel}
180
+ onPress={onPress}
181
+ className="min-h-12 min-w-12 flex-1 items-center justify-center gap-1"
182
+ style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })}
183
+ >
184
+ {renderIcon ? renderIcon({ focused, color, size: largerText ? 28 : 24 }) : null}
185
+ <Text
186
+ numberOfLines={1}
187
+ // The bar has a fixed height (`tabBarReserve`), so labels
188
+ // scale with Dynamic Type only up to what fits in it.
189
+ maxFontSizeMultiplier={1.5}
190
+ className={`font-instrument-sans-semibold ${largerText ? "text-base" : "text-sm"}`}
191
+ style={[{ color }, typeStyle("semibold")]}
192
+ >
193
+ {label}
194
+ </Text>
195
+ </Pressable>
196
+ );
197
+ });
198
+
162
199
  /**
163
200
  * The pill behind tab `index` of `count` drawn tabs: one tab wide (they share
164
201
  * the row equally), `TAB_BAR_PADDING` inside the bar, in
@@ -531,23 +531,27 @@ export function ConversationScrollButton({
531
531
  testID={testID}
532
532
  // Wider than 44 with its label; only the height needs slop.
533
533
  hitSlop={touchSlop(MIN_TOUCH_TARGET, SCROLL_PILL_HEIGHT)}
534
- style={({ pressed }) => ({
534
+ // A plain style object: NativeWind drops a function `style` on a
535
+ // native `Pressable`, which lost the pill's row, border and fill.
536
+ style={{
535
537
  minHeight: SCROLL_PILL_HEIGHT,
536
538
  flexDirection: "row",
537
539
  alignItems: "center",
538
- gap: 6,
539
540
  paddingHorizontal: 12,
540
541
  borderRadius: tokens.radius.pill,
541
542
  borderWidth: 1,
542
543
  borderColor: withAlpha(tokens.color.border[scheme], 0.6),
543
544
  backgroundColor: tokens.color.surface[scheme],
544
- opacity: pressed ? 0.7 : 1,
545
- })}
545
+ }}
546
546
  >
547
- <Icon size={largerText ? 20 : 16} color={tokens.color.muted[scheme]} />
548
- <Text className={cn(labelSize, "font-instrument-sans")} style={[sizeStyle(labelSize), textStyle("muted", scheme)]}>
549
- {label}
550
- </Text>
547
+ {({ pressed }) => (
548
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 6, opacity: pressed ? 0.7 : 1 }}>
549
+ <Icon size={largerText ? 20 : 16} color={tokens.color.muted[scheme]} />
550
+ <Text className={cn(labelSize, "font-instrument-sans")} style={[sizeStyle(labelSize), textStyle("muted", scheme)]}>
551
+ {label}
552
+ </Text>
553
+ </View>
554
+ )}
551
555
  </Pressable>
552
556
  ) : (
553
557
  <Button
package/src/glassBlur.tsx CHANGED
@@ -2,44 +2,68 @@ import {
2
2
  createContext,
3
3
  useContext,
4
4
  useLayoutEffect,
5
- useMemo,
6
5
  useRef,
7
6
  useState,
7
+ useSyncExternalStore,
8
8
  type ComponentRef,
9
- type Dispatch,
10
9
  type ReactNode,
11
10
  type RefObject,
12
- type SetStateAction,
13
11
  } from "react";
14
12
  import { StyleSheet, View, type ViewProps } from "react-native";
15
13
  import { loadBlur } from "./blur";
16
14
 
17
15
  type BlurTargetRef = RefObject<ComponentRef<typeof View> | null>;
18
16
 
19
- type GlassBlurContextValue = {
20
- target: BlurTargetRef | null;
21
- setTarget: Dispatch<SetStateAction<BlurTargetRef | null>> | null;
17
+ /**
18
+ * The current blur target, outside React state. A target change (each tab
19
+ * switch hands it from one tab to the next) then re-renders only the glass
20
+ * that subscribes, not the whole tab tree under the provider: as a context
21
+ * value it walked every mounted tab twice per switch.
22
+ */
23
+ type BlurTargetStore = {
24
+ get: () => BlurTargetRef | null;
25
+ set: (next: BlurTargetRef | null) => void;
26
+ subscribe: (listener: () => void) => () => void;
22
27
  };
23
28
 
24
- const NO_TARGET: GlassBlurContextValue = { target: null, setTarget: null };
29
+ function createBlurTargetStore(): BlurTargetStore {
30
+ let current: BlurTargetRef | null = null;
31
+ const listeners = new Set<() => void>();
32
+ return {
33
+ get: () => current,
34
+ set: (next) => {
35
+ if (next === current) return;
36
+ current = next;
37
+ for (const listener of listeners) listener();
38
+ },
39
+ subscribe: (listener) => {
40
+ listeners.add(listener);
41
+ return () => listeners.delete(listener);
42
+ },
43
+ };
44
+ }
45
+
46
+ // `null` outside a provider and inside a target (a blur view must not sit
47
+ // inside its own target). The value is the provider's one store, so it never
48
+ // changes.
49
+ const BlurTargetStoreContext = createContext<BlurTargetStore | null>(null);
25
50
 
26
- const GlassBlurContext = createContext<GlassBlurContextValue>(NO_TARGET);
51
+ const noSubscribe = () => () => {};
52
+ const noTarget = () => null;
27
53
 
28
54
  /**
29
55
  * The mounted `GlassBlurTarget` that floating glass blurs on Android, or
30
56
  * `null` outside a `GlassBlurProvider` and inside the target itself.
31
57
  */
32
58
  export function useGlassBlurTarget() {
33
- return useContext(GlassBlurContext).target;
59
+ const store = useContext(BlurTargetStoreContext);
60
+ return useSyncExternalStore(store?.subscribe ?? noSubscribe, store?.get ?? noTarget, noTarget);
34
61
  }
35
62
 
36
63
  export function GlassBlurProvider({ children }: { children: ReactNode }) {
37
- const [target, setTarget] = useState<BlurTargetRef | null>(null);
38
- const value = useMemo(() => ({ target, setTarget }), [target]);
64
+ const [store] = useState(createBlurTargetStore);
39
65
 
40
- return (
41
- <GlassBlurContext.Provider value={value}>{children}</GlassBlurContext.Provider>
42
- );
66
+ return <BlurTargetStoreContext.Provider value={store}>{children}</BlurTargetStoreContext.Provider>;
43
67
  }
44
68
 
45
69
  export type GlassBlurTargetProps = ViewProps & {
@@ -59,23 +83,24 @@ export function GlassBlurTarget({
59
83
  children,
60
84
  ...props
61
85
  }: GlassBlurTargetProps) {
62
- const { setTarget } = useContext(GlassBlurContext);
86
+ const store = useContext(BlurTargetStoreContext);
63
87
  const ref = useRef<ComponentRef<typeof View>>(null);
64
88
  const BlurTargetView = loadBlur()?.BlurTargetView;
65
89
 
66
90
  // Publish the ref only once it is attached: expo-blur compares
67
91
  // `blurTarget.current` between renders to pick up a new target.
68
92
  useLayoutEffect(() => {
69
- if (!setTarget || !active) {
93
+ if (!store || !active) {
70
94
  return;
71
95
  }
72
- setTarget(ref);
73
- return () => setTarget((current) => (current === ref ? null : current));
74
- }, [setTarget, active]);
96
+ store.set(ref);
97
+ return () => {
98
+ if (store.get() === ref) store.set(null);
99
+ };
100
+ }, [store, active]);
75
101
 
76
- const content = (
77
- <GlassBlurContext.Provider value={NO_TARGET}>{children}</GlassBlurContext.Provider>
78
- );
102
+ // Surfaces in here get no target: a blur view must not sit inside its own.
103
+ const content = <BlurTargetStoreContext.Provider value={null}>{children}</BlurTargetStoreContext.Provider>;
79
104
 
80
105
  return BlurTargetView ? (
81
106
  <BlurTargetView ref={ref} style={[styles.fill, style]} {...props}>
@@ -80,6 +80,12 @@ export function GestureSwipeableRow({
80
80
 
81
81
  // Whether this touch began while a row was open, so its release only closes that row. Nothing renders from it.
82
82
  const closesOnRelease = useRef(false);
83
+ // The action strips mount on the row's first touch, not with the row: a
84
+ // list of a hundred rows then mounts no hidden buttons and icons up front.
85
+ // A touch lands a few frames before a swipe can move the row, so the strip
86
+ // is there before any of it shows.
87
+ const [revealed, setRevealed] = useState(false);
88
+ const reveal = () => setRevealed(true);
83
89
  const webClicks = useWebClickGuard(isSwipeRowOpen);
84
90
  const insidePager = useContext(InsidePagerContext);
85
91
  const opened = () => {
@@ -137,6 +143,9 @@ export function GestureSwipeableRow({
137
143
  .failOffsetY([-SWIPE_FAIL_OFFSET_Y, SWIPE_FAIL_OFFSET_Y]);
138
144
  if (activation.failOffsetX !== undefined) pan.failOffsetX(activation.failOffsetX);
139
145
  pan
146
+ .onBegin(() => {
147
+ runOnJS(reveal)();
148
+ })
140
149
  .onStart(() => {
141
150
  cancelAnimation(x);
142
151
  start.set(x.get());
@@ -184,10 +193,10 @@ export function GestureSwipeableRow({
184
193
  const rowStyle = useAnimatedStyle(() => ({ transform: [{ translateX: x.get() }] }));
185
194
  return (
186
195
  <View onLayout={(event) => width.set(event.nativeEvent.layout.width)} style={styles.clip}>
187
- {leadingActions.length > 0 ? (
196
+ {revealed && leadingActions.length > 0 ? (
188
197
  <ActionStrip side="leading" actions={leadingActions} x={x} armedScale={armedScale} onPress={press} />
189
198
  ) : null}
190
- {trailingActions.length > 0 ? (
199
+ {revealed && trailingActions.length > 0 ? (
191
200
  <ActionStrip side="trailing" actions={trailingActions} x={x} armedScale={armedScale} onPress={press} />
192
201
  ) : null}
193
202
  <GestureDetector gesture={pan}>