@multiplatform.one/forms 7.67.0 → 7.69.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 (271) hide show
  1. package/dist/cjs/AdaptivePopup/index.cjs +45 -5
  2. package/dist/cjs/AdaptivePopup/index.native.js +47 -5
  3. package/dist/cjs/AdaptivePopup/index.native.js.map +1 -1
  4. package/dist/cjs/Button/IconButtonGroup.cjs +95 -0
  5. package/dist/cjs/Button/IconButtonGroup.native.js +101 -0
  6. package/dist/cjs/Button/IconButtonGroup.native.js.map +1 -0
  7. package/dist/cjs/Button/glass.cjs +34 -0
  8. package/dist/cjs/Button/glass.native.js +55 -0
  9. package/dist/cjs/Button/glass.native.js.map +1 -0
  10. package/dist/cjs/Button/iconButtonGroupContext.cjs +33 -0
  11. package/dist/cjs/Button/iconButtonGroupContext.native.js +36 -0
  12. package/dist/cjs/Button/iconButtonGroupContext.native.js.map +1 -0
  13. package/dist/cjs/Button/index.cjs +174 -30
  14. package/dist/cjs/Button/index.native.js +174 -29
  15. package/dist/cjs/Button/index.native.js.map +1 -1
  16. package/dist/cjs/Button/mediaPlate.cjs +31 -0
  17. package/dist/cjs/Button/mediaPlate.native.js +34 -0
  18. package/dist/cjs/Button/mediaPlate.native.js.map +1 -0
  19. package/dist/cjs/ContextualSaveBar/index.cjs +6 -8
  20. package/dist/cjs/ContextualSaveBar/index.native.js +6 -8
  21. package/dist/cjs/ContextualSaveBar/index.native.js.map +1 -1
  22. package/dist/cjs/DescriptionList/index.cjs +0 -1
  23. package/dist/cjs/DescriptionList/index.native.js +0 -1
  24. package/dist/cjs/DescriptionList/index.native.js.map +1 -1
  25. package/dist/cjs/FloatingPanel/index.native.js +21 -16
  26. package/dist/cjs/FloatingPanel/index.native.js.map +1 -1
  27. package/dist/cjs/fields/Barcode/BarcodeScanner.cjs +7 -8
  28. package/dist/cjs/fields/Barcode/BarcodeScanner.native.js +7 -8
  29. package/dist/cjs/fields/Barcode/BarcodeScanner.native.js.map +1 -1
  30. package/dist/cjs/fields/FileUpload/index.cjs +86 -60
  31. package/dist/cjs/fields/FileUpload/index.native.js +99 -73
  32. package/dist/cjs/fields/FileUpload/index.native.js.map +1 -1
  33. package/dist/cjs/fields/Geolocation/index.cjs +7 -8
  34. package/dist/cjs/fields/Geolocation/index.native.js +7 -8
  35. package/dist/cjs/fields/Geolocation/index.native.js.map +1 -1
  36. package/dist/cjs/fields/ImageField/index.cjs +4 -6
  37. package/dist/cjs/fields/ImageField/index.native.js +4 -6
  38. package/dist/cjs/fields/ImageField/index.native.js.map +1 -1
  39. package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.cjs +20 -28
  40. package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js +20 -28
  41. package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
  42. package/dist/cjs/index.cjs +11 -1
  43. package/dist/cjs/index.native.js +10 -0
  44. package/dist/cjs/index.native.js.map +1 -1
  45. package/dist/cjs/shared/colorRamps.cjs +0 -8
  46. package/dist/cjs/shared/colorRamps.native.js +0 -8
  47. package/dist/cjs/shared/colorRamps.native.js.map +1 -1
  48. package/dist/cjs/standardActions.cjs +117 -0
  49. package/dist/cjs/standardActions.native.js +120 -0
  50. package/dist/cjs/standardActions.native.js.map +1 -0
  51. package/dist/esm/AdaptivePopup/index.mjs +45 -5
  52. package/dist/esm/AdaptivePopup/index.mjs.map +1 -1
  53. package/dist/esm/AdaptivePopup/index.native.js +47 -5
  54. package/dist/esm/AdaptivePopup/index.native.js.map +1 -1
  55. package/dist/esm/Button/IconButtonGroup.mjs +69 -0
  56. package/dist/esm/Button/IconButtonGroup.mjs.map +1 -0
  57. package/dist/esm/Button/IconButtonGroup.native.js +72 -0
  58. package/dist/esm/Button/IconButtonGroup.native.js.map +1 -0
  59. package/dist/esm/Button/glass.mjs +8 -0
  60. package/dist/esm/Button/glass.mjs.map +1 -0
  61. package/dist/esm/Button/glass.native.js +26 -0
  62. package/dist/esm/Button/glass.native.js.map +1 -0
  63. package/dist/esm/Button/iconButtonGroupContext.mjs +7 -0
  64. package/dist/esm/Button/iconButtonGroupContext.mjs.map +1 -0
  65. package/dist/esm/Button/iconButtonGroupContext.native.js +7 -0
  66. package/dist/esm/Button/iconButtonGroupContext.native.js.map +1 -0
  67. package/dist/esm/Button/index.mjs +166 -30
  68. package/dist/esm/Button/index.mjs.map +1 -1
  69. package/dist/esm/Button/index.native.js +167 -30
  70. package/dist/esm/Button/index.native.js.map +1 -1
  71. package/dist/esm/Button/mediaPlate.mjs +6 -0
  72. package/dist/esm/Button/mediaPlate.mjs.map +1 -0
  73. package/dist/esm/Button/mediaPlate.native.js +6 -0
  74. package/dist/esm/Button/mediaPlate.native.js.map +1 -0
  75. package/dist/esm/ContextualSaveBar/index.mjs +7 -9
  76. package/dist/esm/ContextualSaveBar/index.mjs.map +1 -1
  77. package/dist/esm/ContextualSaveBar/index.native.js +7 -9
  78. package/dist/esm/ContextualSaveBar/index.native.js.map +1 -1
  79. package/dist/esm/DescriptionList/index.mjs +0 -1
  80. package/dist/esm/DescriptionList/index.mjs.map +1 -1
  81. package/dist/esm/DescriptionList/index.native.js +0 -1
  82. package/dist/esm/DescriptionList/index.native.js.map +1 -1
  83. package/dist/esm/FloatingPanel/index.native.js +22 -17
  84. package/dist/esm/FloatingPanel/index.native.js.map +1 -1
  85. package/dist/esm/fields/Barcode/BarcodeScanner.mjs +8 -9
  86. package/dist/esm/fields/Barcode/BarcodeScanner.mjs.map +1 -1
  87. package/dist/esm/fields/Barcode/BarcodeScanner.native.js +8 -9
  88. package/dist/esm/fields/Barcode/BarcodeScanner.native.js.map +1 -1
  89. package/dist/esm/fields/FileUpload/index.mjs +87 -61
  90. package/dist/esm/fields/FileUpload/index.mjs.map +1 -1
  91. package/dist/esm/fields/FileUpload/index.native.js +100 -74
  92. package/dist/esm/fields/FileUpload/index.native.js.map +1 -1
  93. package/dist/esm/fields/Geolocation/index.mjs +8 -9
  94. package/dist/esm/fields/Geolocation/index.mjs.map +1 -1
  95. package/dist/esm/fields/Geolocation/index.native.js +8 -9
  96. package/dist/esm/fields/Geolocation/index.native.js.map +1 -1
  97. package/dist/esm/fields/ImageField/index.mjs +6 -8
  98. package/dist/esm/fields/ImageField/index.mjs.map +1 -1
  99. package/dist/esm/fields/ImageField/index.native.js +6 -8
  100. package/dist/esm/fields/ImageField/index.native.js.map +1 -1
  101. package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs +20 -28
  102. package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
  103. package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js +20 -28
  104. package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
  105. package/dist/esm/index.mjs +3 -2
  106. package/dist/esm/index.mjs.map +1 -1
  107. package/dist/esm/index.native.js +3 -2
  108. package/dist/esm/index.native.js.map +1 -1
  109. package/dist/esm/shared/colorRamps.mjs +0 -8
  110. package/dist/esm/shared/colorRamps.mjs.map +1 -1
  111. package/dist/esm/shared/colorRamps.native.js +0 -8
  112. package/dist/esm/shared/colorRamps.native.js.map +1 -1
  113. package/dist/esm/standardActions.mjs +91 -0
  114. package/dist/esm/standardActions.mjs.map +1 -0
  115. package/dist/esm/standardActions.native.js +91 -0
  116. package/dist/esm/standardActions.native.js.map +1 -0
  117. package/dist/jsx/AdaptivePopup/index.mjs +45 -5
  118. package/dist/jsx/AdaptivePopup/index.mjs.map +1 -1
  119. package/dist/jsx/AdaptivePopup/index.native.js +47 -5
  120. package/dist/jsx/AdaptivePopup/index.native.js.map +1 -1
  121. package/dist/jsx/Button/IconButtonGroup.mjs +69 -0
  122. package/dist/jsx/Button/IconButtonGroup.mjs.map +1 -0
  123. package/dist/jsx/Button/IconButtonGroup.native.js +101 -0
  124. package/dist/jsx/Button/IconButtonGroup.native.js.map +1 -0
  125. package/dist/jsx/Button/glass.mjs +8 -0
  126. package/dist/jsx/Button/glass.mjs.map +1 -0
  127. package/dist/jsx/Button/glass.native.js +55 -0
  128. package/dist/jsx/Button/glass.native.js.map +1 -0
  129. package/dist/jsx/Button/iconButtonGroupContext.mjs +7 -0
  130. package/dist/jsx/Button/iconButtonGroupContext.mjs.map +1 -0
  131. package/dist/jsx/Button/iconButtonGroupContext.native.js +36 -0
  132. package/dist/jsx/Button/iconButtonGroupContext.native.js.map +1 -0
  133. package/dist/jsx/Button/index.mjs +166 -30
  134. package/dist/jsx/Button/index.mjs.map +1 -1
  135. package/dist/jsx/Button/index.native.js +174 -29
  136. package/dist/jsx/Button/index.native.js.map +1 -1
  137. package/dist/jsx/Button/mediaPlate.mjs +6 -0
  138. package/dist/jsx/Button/mediaPlate.mjs.map +1 -0
  139. package/dist/jsx/Button/mediaPlate.native.js +34 -0
  140. package/dist/jsx/Button/mediaPlate.native.js.map +1 -0
  141. package/dist/jsx/ContextualSaveBar/index.mjs +7 -9
  142. package/dist/jsx/ContextualSaveBar/index.mjs.map +1 -1
  143. package/dist/jsx/ContextualSaveBar/index.native.js +6 -8
  144. package/dist/jsx/ContextualSaveBar/index.native.js.map +1 -1
  145. package/dist/jsx/DescriptionList/index.mjs +0 -1
  146. package/dist/jsx/DescriptionList/index.mjs.map +1 -1
  147. package/dist/jsx/DescriptionList/index.native.js +0 -1
  148. package/dist/jsx/DescriptionList/index.native.js.map +1 -1
  149. package/dist/jsx/FloatingPanel/index.native.js +21 -16
  150. package/dist/jsx/FloatingPanel/index.native.js.map +1 -1
  151. package/dist/jsx/fields/Barcode/BarcodeScanner.mjs +8 -9
  152. package/dist/jsx/fields/Barcode/BarcodeScanner.mjs.map +1 -1
  153. package/dist/jsx/fields/Barcode/BarcodeScanner.native.js +7 -8
  154. package/dist/jsx/fields/Barcode/BarcodeScanner.native.js.map +1 -1
  155. package/dist/jsx/fields/FileUpload/index.mjs +87 -61
  156. package/dist/jsx/fields/FileUpload/index.mjs.map +1 -1
  157. package/dist/jsx/fields/FileUpload/index.native.js +99 -73
  158. package/dist/jsx/fields/FileUpload/index.native.js.map +1 -1
  159. package/dist/jsx/fields/Geolocation/index.mjs +8 -9
  160. package/dist/jsx/fields/Geolocation/index.mjs.map +1 -1
  161. package/dist/jsx/fields/Geolocation/index.native.js +7 -8
  162. package/dist/jsx/fields/Geolocation/index.native.js.map +1 -1
  163. package/dist/jsx/fields/ImageField/index.mjs +6 -8
  164. package/dist/jsx/fields/ImageField/index.mjs.map +1 -1
  165. package/dist/jsx/fields/ImageField/index.native.js +4 -6
  166. package/dist/jsx/fields/ImageField/index.native.js.map +1 -1
  167. package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs +20 -28
  168. package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
  169. package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js +20 -28
  170. package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
  171. package/dist/jsx/index.js +3 -2
  172. package/dist/jsx/index.js.map +1 -1
  173. package/dist/jsx/index.mjs +3 -2
  174. package/dist/jsx/index.mjs.map +1 -1
  175. package/dist/jsx/index.native.js +10 -0
  176. package/dist/jsx/index.native.js.map +1 -1
  177. package/dist/jsx/shared/colorRamps.mjs +0 -8
  178. package/dist/jsx/shared/colorRamps.mjs.map +1 -1
  179. package/dist/jsx/shared/colorRamps.native.js +0 -8
  180. package/dist/jsx/shared/colorRamps.native.js.map +1 -1
  181. package/dist/jsx/standardActions.mjs +91 -0
  182. package/dist/jsx/standardActions.mjs.map +1 -0
  183. package/dist/jsx/standardActions.native.js +120 -0
  184. package/dist/jsx/standardActions.native.js.map +1 -0
  185. package/package.json +12 -8
  186. package/src/AdaptivePopup/AdaptivePopup.spec.tsx +44 -0
  187. package/src/AdaptivePopup/AdaptivePopup.stories.tsx +12 -32
  188. package/src/AdaptivePopup/index.tsx +65 -13
  189. package/src/Button/Button.disabledGlyph.spec.tsx +111 -0
  190. package/src/Button/Button.iconButton.spec.tsx +341 -0
  191. package/src/Button/Button.stories.tsx +179 -2
  192. package/src/Button/IconButtonGroup.spec.tsx +126 -0
  193. package/src/Button/IconButtonGroup.tsx +87 -0
  194. package/src/Button/glass.native.ts +51 -0
  195. package/src/Button/glass.ts +33 -0
  196. package/src/Button/iconButtonGroupContext.ts +14 -0
  197. package/src/Button/index.tsx +314 -42
  198. package/src/Button/mediaPlate.ts +9 -0
  199. package/src/ContextualSaveBar/ContextualSaveBar.spec.tsx +3 -1
  200. package/src/ContextualSaveBar/ContextualSaveBar.stories.tsx +2 -2
  201. package/src/ContextualSaveBar/index.tsx +7 -10
  202. package/src/ControlGroup/ControlGroup.stories.tsx +3 -2
  203. package/src/DescriptionList/index.tsx +0 -1
  204. package/src/ErrorSummary.stories.tsx +2 -1
  205. package/src/FieldLayout.stories.tsx +3 -2
  206. package/src/FloatingPanel/FloatingPanel.pointMenu.native.spec.tsx +69 -0
  207. package/src/FloatingPanel/FloatingPanel.stories.tsx +6 -10
  208. package/src/FloatingPanel/index.native.tsx +25 -4
  209. package/src/Form/Form.stories.tsx +6 -5
  210. package/src/errorSummaryStories.spec.tsx +4 -4
  211. package/src/fields/Barcode/BarcodeScanner.native.tsx +10 -9
  212. package/src/fields/Barcode/BarcodeScanner.tsx +10 -9
  213. package/src/fields/Checkbox/Checkbox.stories.tsx +3 -3
  214. package/src/fields/Checkbox/CheckboxGroup.stories.tsx +2 -2
  215. package/src/fields/ColorPicker/ColorPicker.stories.tsx +2 -2
  216. package/src/fields/Combobox/Combobox.stories.tsx +2 -2
  217. package/src/fields/DatePicker/DatePicker.stories.tsx +2 -1
  218. package/src/fields/DatePicker/DateRangePicker.stories.tsx +2 -2
  219. package/src/fields/DatePicker/DatetimePicker.stories.tsx +2 -1
  220. package/src/fields/DatePicker/MonthPicker.stories.tsx +2 -2
  221. package/src/fields/DatePicker/MultiDatePicker.stories.tsx +2 -2
  222. package/src/fields/FileUpload/FileUpload.spec.tsx +14 -5
  223. package/src/fields/FileUpload/FileUpload.stories.tsx +2 -2
  224. package/src/fields/FileUpload/index.tsx +103 -77
  225. package/src/fields/Geolocation/index.tsx +12 -7
  226. package/src/fields/ImageField/ImageField.spec.tsx +5 -5
  227. package/src/fields/ImageField/ImageField.stories.tsx +2 -2
  228. package/src/fields/ImageField/index.tsx +6 -9
  229. package/src/fields/Input/Input.stories.tsx +2 -2
  230. package/src/fields/MentionInput/MentionInput.stories.tsx +2 -1
  231. package/src/fields/Progress/Progress.stories.tsx +2 -1
  232. package/src/fields/RadioGroup/RadioGroup.stories.tsx +2 -1
  233. package/src/fields/Signature/Signature.stories.tsx +2 -2
  234. package/src/fields/Stepper/Stepper.stories.tsx +2 -2
  235. package/src/fields/TextArea/TextArea.stories.tsx +2 -2
  236. package/src/fields/ToggleGroup/ToggleGroup.stories.tsx +2 -2
  237. package/src/fields/nativeDateTime/OsDateTimeSheet.presentation.spec.tsx +6 -5
  238. package/src/fields/nativeDateTime/OsDateTimeSheet.tsx +24 -12
  239. package/src/index.ts +18 -2
  240. package/src/shared/colorRamps.ts +1 -11
  241. package/src/standardActions.ts +96 -0
  242. package/types/AdaptivePopup/index.d.ts +13 -1
  243. package/types/AdaptivePopup/index.d.ts.map +1 -1
  244. package/types/Button/IconButtonGroup.d.ts +22 -0
  245. package/types/Button/IconButtonGroup.d.ts.map +1 -0
  246. package/types/Button/glass.d.ts +26 -0
  247. package/types/Button/glass.d.ts.map +1 -0
  248. package/types/Button/glass.native.d.ts +17 -0
  249. package/types/Button/glass.native.d.ts.map +1 -0
  250. package/types/Button/iconButtonGroupContext.d.ts +9 -0
  251. package/types/Button/iconButtonGroupContext.d.ts.map +1 -0
  252. package/types/Button/index.d.ts +55 -2
  253. package/types/Button/index.d.ts.map +1 -1
  254. package/types/Button/mediaPlate.d.ts +10 -0
  255. package/types/Button/mediaPlate.d.ts.map +1 -0
  256. package/types/ContextualSaveBar/index.d.ts.map +1 -1
  257. package/types/DescriptionList/index.d.ts.map +1 -1
  258. package/types/FloatingPanel/index.native.d.ts +2 -1
  259. package/types/FloatingPanel/index.native.d.ts.map +1 -1
  260. package/types/fields/Barcode/BarcodeScanner.d.ts.map +1 -1
  261. package/types/fields/Barcode/BarcodeScanner.native.d.ts.map +1 -1
  262. package/types/fields/FileUpload/index.d.ts.map +1 -1
  263. package/types/fields/Geolocation/index.d.ts.map +1 -1
  264. package/types/fields/ImageField/index.d.ts.map +1 -1
  265. package/types/fields/nativeDateTime/OsDateTimeSheet.d.ts.map +1 -1
  266. package/types/index.d.ts +4 -2
  267. package/types/index.d.ts.map +1 -1
  268. package/types/shared/colorRamps.d.ts +1 -11
  269. package/types/shared/colorRamps.d.ts.map +1 -1
  270. package/types/standardActions.d.ts +24 -0
  271. package/types/standardActions.d.ts.map +1 -0
@@ -10,6 +10,7 @@ import { Field, FieldLayout } from "./fieldLayout";
10
10
  import { Input } from "./fields/Input";
11
11
  import { Input as InputParts } from "./InputParts";
12
12
  import { Checkbox } from "./fields/Checkbox";
13
+ import { ActionButton } from "@multiplatform.one/components";
13
14
 
14
15
  /**
15
16
  * FieldLayout wraps a CONTROL, so these stories cannot mount the house `Input`
@@ -196,7 +197,7 @@ export const FieldWrapper: Story = {
196
197
  )}
197
198
  </Field>
198
199
  <XStack gap="$2">
199
- <Button action="submit">Submit</Button>
200
+ <ActionButton kind="submit" formAction="submit" />
200
201
  <Button onPress={() => form.reset()}>Reset</Button>
201
202
  </XStack>
202
203
  </YStack>
@@ -237,7 +238,7 @@ export const FieldRequiredValidator: Story = {
237
238
  </FieldLayout>
238
239
  )}
239
240
  </Field>
240
- <Button action="submit">Submit</Button>
241
+ <ActionButton kind="submit" formAction="submit" />
241
242
  </YStack>
242
243
  </Form>
243
244
  );
@@ -124,3 +124,72 @@ describe("FloatingPanel native ContextMenu at the long-press point", () => {
124
124
  expect(sheet()).toBeNull();
125
125
  });
126
126
  });
127
+
128
+ function Glyph(_: { onPress: () => void; "aria-label": string }) {
129
+ return <Text>◐</Text>;
130
+ }
131
+
132
+ function GlyphPanel({ onPress, anchored = false }: { onPress: () => void; anchored?: boolean }) {
133
+ const [open, setOpen] = useState(false);
134
+ return (
135
+ <FloatingPanel
136
+ open={open}
137
+ onOpenChange={setOpen}
138
+ openOn="contextmenu"
139
+ triggerSizing="content"
140
+ {...(anchored
141
+ ? { compactFace: "menu" as const, anchorMode: "attach" as const, triggerEcho: false }
142
+ : null)}
143
+ trigger={<Glyph onPress={onPress} aria-label="Appearance" />}
144
+ >
145
+ <Text>Dark</Text>
146
+ </FloatingPanel>
147
+ );
148
+ }
149
+
150
+ function tap(target: Element) {
151
+ act(() => {
152
+ target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, button: 0 }));
153
+ vi.advanceTimersByTime(100);
154
+ target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, button: 0 }));
155
+ target.dispatchEvent(new MouseEvent("click", { bubbles: true, button: 0 }));
156
+ });
157
+ }
158
+
159
+ describe("FloatingPanel native ContextMenu keeps its trigger's tap (FZ-8158)", () => {
160
+ it("runs the trigger's own onPress on a tap and names the button after it", () => {
161
+ screen.width = 390;
162
+ screen.height = 844;
163
+ const onPress = vi.fn();
164
+ const result = renderWithProviders(<GlyphPanel onPress={onPress} />);
165
+ tap(result.getByRole("button", { name: "Appearance" }));
166
+ expect(onPress).toHaveBeenCalledTimes(1);
167
+ expect(sheet()).toBeNull();
168
+ });
169
+
170
+ it("opens the menu on a long-press without running the tap", () => {
171
+ screen.width = 390;
172
+ screen.height = 844;
173
+ const onPress = vi.fn();
174
+ const result = renderWithProviders(<GlyphPanel onPress={onPress} />);
175
+ longPress(result.getByRole("button", { name: "Appearance" }), 200, 300);
176
+ expect(sheet()).not.toBeNull();
177
+ expect(onPress).not.toHaveBeenCalled();
178
+ });
179
+
180
+ it("opens as the pull-down under its trigger on a phone when menu-faced, never a sheet (FZ-8158 r5)", () => {
181
+ screen.width = 390;
182
+ screen.height = 844;
183
+ const onPress = vi.fn();
184
+ const result = renderWithProviders(<GlyphPanel onPress={onPress} anchored />);
185
+ longPress(result.getByRole("button", { name: "Appearance" }), 200, 300);
186
+ act(() => {
187
+ vi.advanceTimersByTime(1);
188
+ });
189
+ expect(document.querySelector('[data-testid="floating-panel-menu"]')).not.toBeNull();
190
+ expect(document.querySelector('[data-testid="floating-panel-menu-echo"]')).toBeNull();
191
+ expect(sheet()).toBeNull();
192
+ expect(pointPanel()).toBeNull();
193
+ expect(onPress).not.toHaveBeenCalled();
194
+ });
195
+ });
@@ -1,7 +1,8 @@
1
1
  import { useState } from "react";
2
2
  import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
3
- import { Preset, Paragraph, H4, Text } from "@multiplatform.one/theme";
4
- import { ScrollView, Separator, View, XStack, YStack } from "tamagui";
3
+ import { Preset, Paragraph, Text } from "@multiplatform.one/theme";
4
+ import { ScrollView, View, XStack, YStack } from "tamagui";
5
+ import { OverlayHeader } from "@multiplatform.one/components";
5
6
  import { FloatingPanel } from "./index";
6
7
  import { Button } from "../Button";
7
8
  import { Select } from "../fields/Select";
@@ -38,13 +39,11 @@ export const Basic: Story = {
38
39
  trigger={<Button>{open ? "Panel is open" : "Click to open panel"}</Button>}
39
40
  >
40
41
  <YStack gap="$3">
41
- <H4>Panel Content</H4>
42
+ <OverlayHeader title="Panel Content" onClose={() => setOpen(false)} />
42
43
  <Paragraph>
43
44
  Opens over its trigger: the top edge sits on the trigger's top edge and the trigger is
44
45
  the first row. Escape or an outside click dismisses and returns focus.
45
46
  </Paragraph>
46
- <Separator />
47
- <Button onPress={() => setOpen(false)}>Close</Button>
48
47
  </YStack>
49
48
  </FloatingPanel>
50
49
  </YStack>
@@ -185,9 +184,8 @@ export const ForceSheet: Story = {
185
184
  trigger={<Button>{open ? "Sheet open" : "Click for Sheet (forced)"}</Button>}
186
185
  >
187
186
  <YStack gap="$3">
188
- <H4>Forced Sheet</H4>
187
+ <OverlayHeader title="Forced Sheet" onClose={() => setOpen(false)} />
189
188
  <Paragraph>This panel always uses Sheet, even on large screens.</Paragraph>
190
- <Button onPress={() => setOpen(false)}>Close</Button>
191
189
  </YStack>
192
190
  </FloatingPanel>
193
191
  </YStack>
@@ -224,13 +222,11 @@ function ScrollClipPair({ where }: { where: string }) {
224
222
  trigger={<Button>{`FloatingPanel (${where})`}</Button>}
225
223
  >
226
224
  <YStack gap="$3" data-testid={`scroll-clip-panel-${where}`}>
227
- <H4>Panel {where}</H4>
225
+ <OverlayHeader title={`Panel ${where}`} onClose={() => setOpen(false)} />
228
226
  <Paragraph>
229
227
  This body is taller than the space left in the scroll view, so a clipped panel loses its
230
228
  lower half.
231
229
  </Paragraph>
232
- <Separator />
233
- <Button onPress={() => setOpen(false)}>Close</Button>
234
230
  </YStack>
235
231
  </FloatingPanel>
236
232
  </YStack>
@@ -54,7 +54,8 @@
54
54
  * ContextMenu (`openOn="contextmenu"`): long-press opens it. On a window wider
55
55
  * than OVERLAY_BREAKPOINT (a tablet) it opens at the long-press point, the
56
56
  * same free overlay the web draws at the pointer; a phone keeps the sheet,
57
- * as web does at ≤640.
57
+ * as web does at ≤640. With `compactFace="menu"` it opens as the pull-down at
58
+ * its trigger instead, at every width (a bar button's long-press, FZ-8158).
58
59
  *
59
60
  * Regular width (FZ-8116, LC-266): on an iPad or any window wider than
60
61
  * OVERLAY_BREAKPOINT the sheet is the wrong form. The panel opens over its
@@ -64,6 +65,7 @@
64
65
 
65
66
  import { nativeAnimationConfig, type MotionStop } from "@multiplatform.one/theme";
66
67
  import {
68
+ isValidElement,
67
69
  type ReactNode,
68
70
  useCallback,
69
71
  useContext,
@@ -208,10 +210,25 @@ export function FloatingPanel({
208
210
  [disabled, onOpenChange],
209
211
  );
210
212
  const isContextMenu = openOn === "contextmenu";
213
+ // A long-press menu never eats its trigger's tap: the web lets the primary
214
+ // click through to the trigger, so here the Pressable that owns the gesture
215
+ // runs the trigger's own onPress and carries its name.
216
+ const triggerOwn =
217
+ isContextMenu && isValidElement(trigger)
218
+ ? (trigger.props as {
219
+ onPress?: (event: GestureResponderEvent) => void;
220
+ "aria-label"?: string;
221
+ accessibilityLabel?: string;
222
+ })
223
+ : undefined;
224
+ const triggerLabel =
225
+ triggerA11y?.label ?? triggerOwn?.["aria-label"] ?? triggerOwn?.accessibilityLabel;
211
226
  const regular = overlaySizeClass(windowWidth) === "regular";
212
227
  const atPoint = isContextMenu && pressPoint !== null && regular;
213
228
  const insideOverlay = useInsideOverlay();
214
- const asMenu = !isContextMenu && resolveCompactFace(compactFace, insideOverlay) === "menu";
229
+ const asMenu = isContextMenu
230
+ ? compactFace === "menu"
231
+ : resolveCompactFace(compactFace, insideOverlay) === "menu";
215
232
  const asRegular = regular && !asMenu && !isContextMenu && sheet !== true;
216
233
  const anchored = asMenu || asRegular;
217
234
  const triggerRef = useRef<RNView | null>(null);
@@ -543,12 +560,16 @@ export function FloatingPanel({
543
560
  >
544
561
  <Pressable
545
562
  {...(isContextMenu
546
- ? { onLongPress: handleTriggerLongPress, delayLongPress: 500 }
563
+ ? {
564
+ onLongPress: handleTriggerLongPress,
565
+ delayLongPress: 500,
566
+ ...(triggerOwn?.onPress && !disabled ? { onPress: triggerOwn.onPress } : undefined),
567
+ }
547
568
  : { onPress: handleTriggerPress })}
548
569
  disabled={disabled}
549
570
  accessibilityRole="button"
550
571
  accessibilityState={{ disabled: !!disabled, expanded: !!open }}
551
- {...(triggerA11y?.label ? { accessibilityLabel: triggerA11y.label } : undefined)}
572
+ {...(triggerLabel ? { accessibilityLabel: triggerLabel } : undefined)}
552
573
  {...(triggerA11y?.value
553
574
  ? { accessibilityValue: { text: triggerA11y.value } }
554
575
  : undefined)}
@@ -53,6 +53,7 @@ import { formatFieldError } from "../ErrorSummary";
53
53
  import type { AnyFormApi } from "../types";
54
54
  import { Form, FormActions } from "./index";
55
55
  import { H3 } from "@multiplatform.one/theme";
56
+ import { ActionButton } from "@multiplatform.one/components";
56
57
 
57
58
  const meta: Meta<typeof Form> = {
58
59
  title: "Forms/Form",
@@ -95,7 +96,7 @@ export const Basic = () => (
95
96
  <Input label="Username" name="username" required />
96
97
  <Input label="Email" name="email" required />
97
98
  <FormActions>
98
- <Button action="submit">Submit</Button>
99
+ <ActionButton kind="submit" formAction="submit" />
99
100
  </FormActions>
100
101
  </Form>
101
102
  );
@@ -292,7 +293,7 @@ export const DynamicFields = () => {
292
293
  })()}
293
294
 
294
295
  <FormActions>
295
- <Button action="submit">Submit</Button>
296
+ <ActionButton kind="submit" formAction="submit" />
296
297
  </FormActions>
297
298
  </Form>
298
299
  );
@@ -313,7 +314,7 @@ export const ReadOnlyAndDisabledSubmit = () => (
313
314
  <Input label="Read Only" name="readOnlyField" readOnly />
314
315
  <Input label="Disabled" name="disabledField" disabled />
315
316
  <FormActions>
316
- <Button action="submit">Submit</Button>
317
+ <ActionButton kind="submit" formAction="submit" />
317
318
  </FormActions>
318
319
  </Form>
319
320
  );
@@ -342,7 +343,7 @@ export const InputWithLinkResolver = () => (
342
343
  linkResolver={(v) => (v ? (`/items/${String(v)}` as any) : null)}
343
344
  />
344
345
  <FormActions>
345
- <Button action="submit">Submit</Button>
346
+ <ActionButton kind="submit" formAction="submit" />
346
347
  </FormActions>
347
348
  </Form>
348
349
  );
@@ -999,7 +1000,7 @@ export const Layout = () => (
999
1000
  </FormSection>
1000
1001
  </FormSectionStack>
1001
1002
  <FormActions>
1002
- <Button action="submit">Save</Button>
1003
+ <ActionButton kind="save" formAction="submit" />
1003
1004
  </FormActions>
1004
1005
  </Form>
1005
1006
  );
@@ -86,17 +86,17 @@ describe("Forms/ErrorSummary stories", () => {
86
86
  });
87
87
 
88
88
  it("FocusesTheFieldOnPress starts clean and raises the summary on submit", () => {
89
- const { container, getByText } = mount(FocusesTheFieldOnPress as StoryLike);
89
+ const { container, getByRole } = mount(FocusesTheFieldOnPress as StoryLike);
90
90
  expect(container.querySelector("[data-testid='error-summary']")).toBeNull();
91
- fireEvent.click(getByText("Submit"));
91
+ fireEvent.click(getByRole("button", { name: "Submit" }));
92
92
  const summary = container.querySelector("[data-testid='error-summary']");
93
93
  expect(summary).toBeTruthy();
94
94
  expect(container.querySelectorAll("a").length).toBe(2);
95
95
  });
96
96
 
97
97
  it("FocusesTheFieldOnPress links target the ids its fields actually carry", () => {
98
- const { container, getByText } = mount(FocusesTheFieldOnPress as StoryLike);
99
- fireEvent.click(getByText("Submit"));
98
+ const { container, getByRole } = mount(FocusesTheFieldOnPress as StoryLike);
99
+ fireEvent.click(getByRole("button", { name: "Submit" }));
100
100
  const hrefs = Array.from(container.querySelectorAll("a")).map((a) => a.getAttribute("href"));
101
101
  expect(hrefs).toEqual(["#email", "#password"]);
102
102
  expect(container.querySelector("#email")).toBeTruthy();
@@ -2,7 +2,8 @@ import { Camera, CameraView, useCameraPermissions } from "expo-camera";
2
2
  import { useEffect, useRef, useState } from "react";
3
3
  import { StyleSheet } from "react-native";
4
4
  import { Text, View, XStack, YStack } from "tamagui";
5
- import { Button } from "../../Button";
5
+ import { Button, IconButton } from "../../Button";
6
+ import { standardActions } from "../../standardActions";
6
7
  import { formCommonColors, formInputColors } from "../../shared/colorRamps";
7
8
  import { t } from "../../shared/t";
8
9
  import type { BarcodeScannerProps, CameraPreflightResult } from "./scannerTypes";
@@ -95,8 +96,8 @@ export function BarcodeScanner({
95
96
  {t("Camera permission denied. Please allow camera access.")}
96
97
  </Text>
97
98
  <XStack gap="$2">
98
- <Button
99
- size={knobProps.sizeToken}
99
+ <IconButton
100
+ icon={standardActions.retry.icon}
100
101
  onPress={() => {
101
102
  setDenied(false);
102
103
  requestPermission().then((res) => {
@@ -104,12 +105,12 @@ export function BarcodeScanner({
104
105
  });
105
106
  }}
106
107
  aria-label={t("Retry camera")}
107
- >
108
- {t("Retry")}
109
- </Button>
110
- <Button size={knobProps.sizeToken} onPress={onClose} aria-label={t("Close scanner")}>
111
- {t("Close")}
112
- </Button>
108
+ />
109
+ <IconButton
110
+ icon={standardActions.close.icon}
111
+ onPress={onClose}
112
+ aria-label={t("Close scanner")}
113
+ />
113
114
  </XStack>
114
115
  </YStack>
115
116
  ) : !permission?.granted ? (
@@ -1,6 +1,7 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
2
  import { Text, View, XStack, YStack, isWeb } from "tamagui";
3
- import { Button } from "../../Button";
3
+ import { Button, IconButton } from "../../Button";
4
+ import { standardActions } from "../../standardActions";
4
5
  import { queryPermissionState } from "../../shared/capabilityPreflight";
5
6
  import { formCommonColors, formInputColors } from "../../shared/colorRamps";
6
7
  import { t } from "../../shared/t";
@@ -173,16 +174,16 @@ export function BarcodeScanner({
173
174
  {errorMessage}
174
175
  </Text>
175
176
  <XStack gap="$2">
176
- <Button
177
- size={knobProps.sizeToken}
177
+ <IconButton
178
+ icon={standardActions.retry.icon}
178
179
  onPress={() => setAttempt((a) => a + 1)}
179
180
  aria-label={t("Retry camera")}
180
- >
181
- {t("Retry")}
182
- </Button>
183
- <Button size={knobProps.sizeToken} onPress={onClose} aria-label={t("Close scanner")}>
184
- {t("Close")}
185
- </Button>
181
+ />
182
+ <IconButton
183
+ icon={standardActions.close.icon}
184
+ onPress={onClose}
185
+ aria-label={t("Close scanner")}
186
+ />
186
187
  </XStack>
187
188
  </YStack>
188
189
  ) : (
@@ -6,10 +6,10 @@ import { useForm } from "@tanstack/react-form";
6
6
  import { useState } from "react";
7
7
  import { Checkbox as TamaguiCheckbox, XStack, YStack } from "tamagui";
8
8
  import { Label } from "../../Label";
9
- import { Button } from "../../Button";
10
9
  import { Form } from "../../Form";
11
10
  import { Checkbox, Checkboxes, CheckboxGroup } from "./index";
12
11
  import { Paragraph } from "@multiplatform.one/theme";
12
+ import { ActionButton } from "@multiplatform.one/components";
13
13
 
14
14
  const meta: Meta<typeof Checkbox> = {
15
15
  title: "Forms/Checkbox",
@@ -54,7 +54,7 @@ export const Main: Story = {
54
54
  validators={{ onChange: ({ value }) => (!value ? "Terms are required" : undefined) }}
55
55
  />
56
56
  <Checkbox label="Subscribe to newsletter" name="newsletter" />
57
- <Button action="submit">Submit</Button>
57
+ <ActionButton kind="submit" formAction="submit" />
58
58
  </Form>
59
59
  );
60
60
  };
@@ -274,7 +274,7 @@ export const CheckboxGroupWithForm: StoryObj = {
274
274
  helperText="Select required permissions"
275
275
  required
276
276
  />
277
- <Button action="submit">Submit</Button>
277
+ <ActionButton kind="submit" formAction="submit" />
278
278
  </YStack>
279
279
  </Form>
280
280
  );
@@ -3,9 +3,9 @@ import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
3
3
  import { Preset, Paragraph } from "@multiplatform.one/theme";
4
4
  import { useForm } from "@tanstack/react-form";
5
5
  import { YStack } from "tamagui";
6
- import { Button } from "../../Button";
7
6
  import { Form } from "../../Form";
8
7
  import { CheckboxGroup } from "./CheckboxGroup";
8
+ import { ActionButton } from "@multiplatform.one/components";
9
9
 
10
10
  const meta: Meta<typeof CheckboxGroup> = {
11
11
  title: "Forms/CheckboxGroup",
@@ -255,7 +255,7 @@ export const RequiredForm: Story = {
255
255
  ]}
256
256
  helperText="Submit empty to trigger required error"
257
257
  />
258
- <Button action="submit">Submit</Button>
258
+ <ActionButton kind="submit" formAction="submit" />
259
259
  </YStack>
260
260
  </Form>
261
261
  );
@@ -4,9 +4,9 @@ import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
4
4
  import { useForm } from "@tanstack/react-form";
5
5
  import { useState } from "react";
6
6
  import { YStack } from "tamagui";
7
- import { Button } from "../../Button";
8
7
  import { Form } from "../../Form";
9
8
  import { ColorPicker } from "./index";
9
+ import { ActionButton } from "@multiplatform.one/components";
10
10
 
11
11
  const meta: Meta<typeof ColorPicker> = {
12
12
  title: "Forms/ColorPicker",
@@ -69,7 +69,7 @@ export const Main: Story = {
69
69
  helperText="Choose background color"
70
70
  />
71
71
  <ColorPicker label="Text Color" name="textColor" helperText="Choose text color" />
72
- <Button action="submit">Submit</Button>
72
+ <ActionButton kind="submit" formAction="submit" />
73
73
  </YStack>
74
74
  </Form>
75
75
  );
@@ -4,11 +4,11 @@ import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
4
4
  import { useForm } from "@tanstack/react-form";
5
5
  import { useState } from "react";
6
6
  import { XStack, YStack } from "tamagui";
7
- import { Button } from "../../Button";
8
7
  import { Form } from "../../Form";
9
8
  import { Combobox, type ComboboxOption } from "./index";
10
9
  import { authorOptions } from "./authorOptions";
11
10
  import { Text } from "@multiplatform.one/theme";
11
+ import { ActionButton } from "@multiplatform.one/components";
12
12
 
13
13
  const meta: Meta<typeof Combobox> = {
14
14
  title: "Forms/Combobox",
@@ -117,7 +117,7 @@ export const Main: StoryObj<typeof Combobox> = {
117
117
  searchPlaceholder="Type to search..."
118
118
  />
119
119
 
120
- <Button action="submit">Submit</Button>
120
+ <ActionButton kind="submit" formAction="submit" />
121
121
  </YStack>
122
122
  </Form>
123
123
  );
@@ -14,6 +14,7 @@ import { MonthPicker } from "./MonthPicker";
14
14
  import { MultiDatePicker } from "./MultiDatePicker";
15
15
  import { DatePicker } from "./index";
16
16
  import { formatCalendarDate, Text } from "@multiplatform.one/theme";
17
+ import { ActionButton } from "@multiplatform.one/components";
17
18
  import { TimePicker } from "../TimePicker";
18
19
 
19
20
  const meta: Meta<typeof DatePicker> = {
@@ -86,7 +87,7 @@ export const Main: Story = {
86
87
  name={"endDate" as any}
87
88
  helperText="Select project end date"
88
89
  />
89
- <Button action="submit">Submit</Button>
90
+ <ActionButton kind="submit" formAction="submit" />
90
91
  </YStack>
91
92
  </Form>
92
93
  );
@@ -4,10 +4,10 @@ import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
4
4
  import { useForm } from "@tanstack/react-form";
5
5
  import { useState } from "react";
6
6
  import { YStack } from "tamagui";
7
- import { Button } from "../../Button";
8
7
  import { Form } from "../../Form";
9
8
  import { DateRangePicker } from "./DateRangePicker";
10
9
  import { Text } from "@multiplatform.one/theme";
10
+ import { ActionButton } from "@multiplatform.one/components";
11
11
 
12
12
  const meta: Meta<typeof DateRangePicker> = {
13
13
  title: "Forms/DateRangePicker",
@@ -103,7 +103,7 @@ export const FormBound: Story = {
103
103
  <FormSubscribe selector={(s: any) => [s.values.range]}>
104
104
  {([range]: any[]) => <Text>{`form-value:${JSON.stringify(range)}`}</Text>}
105
105
  </FormSubscribe>
106
- <Button action="submit">Submit</Button>
106
+ <ActionButton kind="submit" formAction="submit" />
107
107
  </YStack>
108
108
  </Form>
109
109
  );
@@ -8,6 +8,7 @@ import { Button } from "../../Button";
8
8
  import { Form } from "../../Form";
9
9
  import { DatetimePicker } from "./DatetimePicker";
10
10
  import { Text } from "@multiplatform.one/theme";
11
+ import { ActionButton } from "@multiplatform.one/components";
11
12
 
12
13
  const meta: Meta<typeof DatetimePicker> = {
13
14
  title: "Forms/DatetimePicker",
@@ -170,7 +171,7 @@ export const FormBound: Story = {
170
171
  </Text>
171
172
  )}
172
173
  </FormSubscribe>
173
- <Button action="submit">Submit</Button>
174
+ <ActionButton kind="submit" formAction="submit" />
174
175
  </YStack>
175
176
  </Form>
176
177
  );
@@ -2,10 +2,10 @@ import { action } from "@multiplatform.one/storybook";
2
2
  import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
3
3
  import { useForm } from "@tanstack/react-form";
4
4
  import { YStack } from "tamagui";
5
- import { Button } from "../../Button";
6
5
  import { Form } from "../../Form";
7
6
  import { MonthPicker } from "./MonthPicker";
8
7
  import { Text } from "@multiplatform.one/theme";
8
+ import { ActionButton } from "@multiplatform.one/components";
9
9
 
10
10
  const meta: Meta<typeof MonthPicker> = {
11
11
  title: "Forms/MonthPicker",
@@ -104,7 +104,7 @@ export const FormBound: Story = {
104
104
  <FormSubscribe selector={(s: any) => [s.values.period]}>
105
105
  {([period]: any[]) => <Text>{`form-value:${JSON.stringify(period)}`}</Text>}
106
106
  </FormSubscribe>
107
- <Button action="submit">Submit</Button>
107
+ <ActionButton kind="submit" formAction="submit" />
108
108
  </YStack>
109
109
  </Form>
110
110
  );
@@ -4,10 +4,10 @@ import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
4
4
  import { useForm } from "@tanstack/react-form";
5
5
  import { useState } from "react";
6
6
  import { YStack } from "tamagui";
7
- import { Button } from "../../Button";
8
7
  import { Form } from "../../Form";
9
8
  import { MultiDatePicker } from "./MultiDatePicker";
10
9
  import { Text } from "@multiplatform.one/theme";
10
+ import { ActionButton } from "@multiplatform.one/components";
11
11
 
12
12
  const meta: Meta<typeof MultiDatePicker> = {
13
13
  title: "Forms/MultiDatePicker",
@@ -115,7 +115,7 @@ export const FormBound: Story = {
115
115
  </Text>
116
116
  )}
117
117
  </FormSubscribe>
118
- <Button action="submit">Submit</Button>
118
+ <ActionButton kind="submit" formAction="submit" />
119
119
  </YStack>
120
120
  </Form>
121
121
  );
@@ -67,7 +67,16 @@ describe("FileUpload", () => {
67
67
  const result = renderWithProviders(
68
68
  <FileUpload existing={{ src: "/files/sprite.png" }} onExistingRemove={remove} />,
69
69
  );
70
- expect(result.container.textContent).toContain("Replace");
70
+ // LC-260: Replace and Remove are glyphs over the image; LC-176 puts the
71
+ // pair in one shared media capsule, so the buttons draw glyphs alone.
72
+ const actions = result.container.querySelector('[data-file-upload="preview-actions"]');
73
+ const capsule = actions?.querySelector('[data-mp-icon-button-group="media"]');
74
+ expect(capsule?.querySelector('[aria-label="Replace"]')).toBeTruthy();
75
+ expect(capsule?.querySelector('[aria-label^="Remove"]')).toBeTruthy();
76
+ expect(
77
+ capsule?.querySelector('[aria-label="Replace"]')?.getAttribute("data-mp-icon-button"),
78
+ ).not.toBe("media");
79
+ expect(result.container.textContent).not.toContain("Replace");
71
80
  expect(result.container.textContent).not.toContain("Choose File");
72
81
  expect(result.container.textContent).not.toContain("Drag a file");
73
82
  expect(result.container.querySelector('[data-file-upload="preview"] img')).toBeTruthy();
@@ -344,7 +353,7 @@ describe("FileUpload bento-derived dropzone (MPO-57)", () => {
344
353
  expect(zone.textContent).toContain("Drag a file into this area");
345
354
  });
346
355
 
347
- it("renders picked files as in-zone rows: name, bold size line, filled $color12 remove disc (29 painted, hitSlop 44)", async () => {
356
+ it("renders picked files as in-zone rows: name, bold size line, bare remove glyph (29 painted, hitSlop 44)", async () => {
348
357
  const { container } = renderWithHouseThemes(
349
358
  <FileUpload label="Attachment" name="attachment" multiple />,
350
359
  );
@@ -367,14 +376,14 @@ describe("FileUpload bento-derived dropzone (MPO-57)", () => {
367
376
  expect(sizeLine).toBeTruthy();
368
377
  expect(sizeLine.className).toMatch(/_fow-700/);
369
378
 
370
- // Remove: a circular filled $color12 disc painted at nestedControl (29)
379
+ // Remove: LC-176 bare ✕, no disc or fill, painted at nestedControl (29)
371
380
  // with hitSlop restoring the 44 floor (data-mp-press-slop 8 → 29+16=45).
372
381
  const remove = flist.querySelector('[aria-label="Remove hello.txt"]') as HTMLElement;
373
382
  expect(remove).toBeTruthy();
374
383
  expect(remove.getAttribute("data-nested-px")).toBe("29");
375
384
  expect(remove.getAttribute("data-mp-press-slop")).toBe("8");
376
- expect(remove.className).toMatch(/_bg-color12/);
377
- // The inverse ✕ rides inside the disc.
385
+ expect(remove.className).toMatch(/_bg-transparent/);
386
+ expect(remove.className).not.toMatch(/_bg-color12/);
378
387
  expect(remove.querySelector("svg")).toBeTruthy();
379
388
  // The caption is dropped once files exist; the CTA stays.
380
389
  expect(zone.textContent).not.toContain("Drag a file into this area");
@@ -4,9 +4,9 @@ import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
4
4
  import { useForm } from "@tanstack/react-form";
5
5
  import { useEffect, useRef, useState } from "react";
6
6
  import { YStack } from "tamagui";
7
- import { Button } from "../../Button";
8
7
  import { Form } from "../../Form";
9
8
  import { FileUpload } from "./index";
9
+ import { ActionButton } from "@multiplatform.one/components";
10
10
 
11
11
  const meta: Meta<typeof FileUpload> = {
12
12
  title: "Forms/FileUpload",
@@ -80,7 +80,7 @@ export const Main: Story = {
80
80
  helperText: "Upload multiple files",
81
81
  } as any)}
82
82
  />
83
- <Button action="submit">Submit</Button>
83
+ <ActionButton kind="submit" formAction="submit" />
84
84
  </YStack>
85
85
  </Form>
86
86
  );