@multiplatform.one/forms 8.3.0 → 8.4.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 (265) hide show
  1. package/dist/cjs/ControlGroup/index.cjs +2 -1
  2. package/dist/cjs/ControlGroup/index.native.js +2 -1
  3. package/dist/cjs/ControlGroup/index.native.js.map +1 -1
  4. package/dist/cjs/FloatingPanel/index.native.js +20 -2
  5. package/dist/cjs/FloatingPanel/index.native.js.map +1 -1
  6. package/dist/cjs/InputParts/index.cjs +1 -0
  7. package/dist/cjs/InputParts/index.native.js +1 -0
  8. package/dist/cjs/InputParts/index.native.js.map +1 -1
  9. package/dist/cjs/Wheel/index.cjs +22 -6
  10. package/dist/cjs/Wheel/index.native.js +26 -8
  11. package/dist/cjs/Wheel/index.native.js.map +1 -1
  12. package/dist/cjs/fields/DatePicker/parts.cjs +33 -30
  13. package/dist/cjs/fields/DatePicker/parts.native.js +34 -30
  14. package/dist/cjs/fields/DatePicker/parts.native.js.map +1 -1
  15. package/dist/cjs/fields/FileUpload/index.cjs +32 -15
  16. package/dist/cjs/fields/FileUpload/index.native.js +40 -19
  17. package/dist/cjs/fields/FileUpload/index.native.js.map +1 -1
  18. package/dist/cjs/fields/ImageField/index.cjs +35 -20
  19. package/dist/cjs/fields/ImageField/index.native.js +37 -20
  20. package/dist/cjs/fields/ImageField/index.native.js.map +1 -1
  21. package/dist/cjs/fields/Progress/index.cjs +5 -0
  22. package/dist/cjs/fields/Progress/index.native.js +5 -0
  23. package/dist/cjs/fields/Progress/index.native.js.map +1 -1
  24. package/dist/cjs/fields/Slider/index.cjs +38 -12
  25. package/dist/cjs/fields/Slider/index.native.js +39 -11
  26. package/dist/cjs/fields/Slider/index.native.js.map +1 -1
  27. package/dist/cjs/fields/Switch/index.cjs +10 -6
  28. package/dist/cjs/fields/Switch/index.native.js +10 -6
  29. package/dist/cjs/fields/Switch/index.native.js.map +1 -1
  30. package/dist/cjs/fields/TimePicker/parts.cjs +9 -10
  31. package/dist/cjs/fields/TimePicker/parts.native.js +9 -10
  32. package/dist/cjs/fields/TimePicker/parts.native.js.map +1 -1
  33. package/dist/cjs/fields/ToggleGroup/index.cjs +1 -0
  34. package/dist/cjs/fields/ToggleGroup/index.native.js +1 -0
  35. package/dist/cjs/fields/ToggleGroup/index.native.js.map +1 -1
  36. package/dist/cjs/fields/pickerHandle.cjs +18 -0
  37. package/dist/cjs/fields/pickerHandle.native.js +21 -0
  38. package/dist/cjs/fields/pickerHandle.native.js.map +1 -0
  39. package/dist/cjs/hooks/useHoldCue.cjs +54 -0
  40. package/dist/cjs/hooks/useHoldCue.native.js +62 -0
  41. package/dist/cjs/hooks/useHoldCue.native.js.map +1 -0
  42. package/dist/cjs/index.cjs +4 -0
  43. package/dist/cjs/index.gnome.cjs +4 -0
  44. package/dist/cjs/index.gnome.native.js +4 -0
  45. package/dist/cjs/index.gnome.native.js.map +1 -1
  46. package/dist/cjs/index.native.js +4 -0
  47. package/dist/cjs/index.native.js.map +1 -1
  48. package/dist/cjs/shared/colorRamps.cjs +9 -7
  49. package/dist/cjs/shared/colorRamps.native.js +9 -7
  50. package/dist/cjs/shared/colorRamps.native.js.map +1 -1
  51. package/dist/cjs/shared/controlTrack.cjs +32 -0
  52. package/dist/cjs/shared/controlTrack.native.js +35 -0
  53. package/dist/cjs/shared/controlTrack.native.js.map +1 -0
  54. package/dist/esm/ControlGroup/index.mjs +2 -1
  55. package/dist/esm/ControlGroup/index.mjs.map +1 -1
  56. package/dist/esm/ControlGroup/index.native.js +2 -1
  57. package/dist/esm/ControlGroup/index.native.js.map +1 -1
  58. package/dist/esm/FloatingPanel/index.native.js +20 -2
  59. package/dist/esm/FloatingPanel/index.native.js.map +1 -1
  60. package/dist/esm/InputParts/index.mjs +1 -0
  61. package/dist/esm/InputParts/index.mjs.map +1 -1
  62. package/dist/esm/InputParts/index.native.js +1 -0
  63. package/dist/esm/InputParts/index.native.js.map +1 -1
  64. package/dist/esm/Wheel/index.mjs +23 -7
  65. package/dist/esm/Wheel/index.mjs.map +1 -1
  66. package/dist/esm/Wheel/index.native.js +27 -9
  67. package/dist/esm/Wheel/index.native.js.map +1 -1
  68. package/dist/esm/fields/DatePicker/parts.mjs +35 -32
  69. package/dist/esm/fields/DatePicker/parts.mjs.map +1 -1
  70. package/dist/esm/fields/DatePicker/parts.native.js +36 -32
  71. package/dist/esm/fields/DatePicker/parts.native.js.map +1 -1
  72. package/dist/esm/fields/FileUpload/index.mjs +33 -16
  73. package/dist/esm/fields/FileUpload/index.mjs.map +1 -1
  74. package/dist/esm/fields/FileUpload/index.native.js +41 -20
  75. package/dist/esm/fields/FileUpload/index.native.js.map +1 -1
  76. package/dist/esm/fields/ImageField/index.mjs +36 -21
  77. package/dist/esm/fields/ImageField/index.mjs.map +1 -1
  78. package/dist/esm/fields/ImageField/index.native.js +38 -21
  79. package/dist/esm/fields/ImageField/index.native.js.map +1 -1
  80. package/dist/esm/fields/Progress/index.mjs +5 -0
  81. package/dist/esm/fields/Progress/index.mjs.map +1 -1
  82. package/dist/esm/fields/Progress/index.native.js +5 -0
  83. package/dist/esm/fields/Progress/index.native.js.map +1 -1
  84. package/dist/esm/fields/Slider/index.mjs +38 -12
  85. package/dist/esm/fields/Slider/index.mjs.map +1 -1
  86. package/dist/esm/fields/Slider/index.native.js +39 -11
  87. package/dist/esm/fields/Slider/index.native.js.map +1 -1
  88. package/dist/esm/fields/Switch/index.mjs +10 -6
  89. package/dist/esm/fields/Switch/index.mjs.map +1 -1
  90. package/dist/esm/fields/Switch/index.native.js +10 -6
  91. package/dist/esm/fields/Switch/index.native.js.map +1 -1
  92. package/dist/esm/fields/TimePicker/parts.mjs +9 -10
  93. package/dist/esm/fields/TimePicker/parts.mjs.map +1 -1
  94. package/dist/esm/fields/TimePicker/parts.native.js +9 -10
  95. package/dist/esm/fields/TimePicker/parts.native.js.map +1 -1
  96. package/dist/esm/fields/ToggleGroup/index.mjs +2 -1
  97. package/dist/esm/fields/ToggleGroup/index.mjs.map +1 -1
  98. package/dist/esm/fields/ToggleGroup/index.native.js +2 -1
  99. package/dist/esm/fields/ToggleGroup/index.native.js.map +1 -1
  100. package/dist/esm/fields/pickerHandle.mjs +2 -0
  101. package/dist/esm/fields/pickerHandle.mjs.map +1 -0
  102. package/dist/esm/fields/pickerHandle.native.js +2 -0
  103. package/dist/esm/fields/pickerHandle.native.js.map +1 -0
  104. package/dist/esm/hooks/useHoldCue.mjs +27 -0
  105. package/dist/esm/hooks/useHoldCue.mjs.map +1 -0
  106. package/dist/esm/hooks/useHoldCue.native.js +32 -0
  107. package/dist/esm/hooks/useHoldCue.native.js.map +1 -0
  108. package/dist/esm/index.gnome.mjs +2 -1
  109. package/dist/esm/index.gnome.mjs.map +1 -1
  110. package/dist/esm/index.gnome.native.js +2 -1
  111. package/dist/esm/index.gnome.native.js.map +1 -1
  112. package/dist/esm/index.mjs +2 -1
  113. package/dist/esm/index.mjs.map +1 -1
  114. package/dist/esm/index.native.js +2 -1
  115. package/dist/esm/index.native.js.map +1 -1
  116. package/dist/esm/shared/colorRamps.mjs +9 -7
  117. package/dist/esm/shared/colorRamps.mjs.map +1 -1
  118. package/dist/esm/shared/colorRamps.native.js +9 -7
  119. package/dist/esm/shared/colorRamps.native.js.map +1 -1
  120. package/dist/esm/shared/controlTrack.mjs +7 -0
  121. package/dist/esm/shared/controlTrack.mjs.map +1 -0
  122. package/dist/esm/shared/controlTrack.native.js +7 -0
  123. package/dist/esm/shared/controlTrack.native.js.map +1 -0
  124. package/dist/esm/shared/nativeOverlayWindow.mjs.map +1 -1
  125. package/dist/esm/shared/systemControls.mjs.map +1 -1
  126. package/dist/esm/shared/systemControls.native.js.map +1 -1
  127. package/dist/jsx/ControlGroup/index.mjs +2 -1
  128. package/dist/jsx/ControlGroup/index.mjs.map +1 -1
  129. package/dist/jsx/ControlGroup/index.native.js +2 -1
  130. package/dist/jsx/ControlGroup/index.native.js.map +1 -1
  131. package/dist/jsx/FloatingPanel/index.native.js +20 -2
  132. package/dist/jsx/FloatingPanel/index.native.js.map +1 -1
  133. package/dist/jsx/InputParts/index.mjs +1 -0
  134. package/dist/jsx/InputParts/index.mjs.map +1 -1
  135. package/dist/jsx/InputParts/index.native.js +1 -0
  136. package/dist/jsx/InputParts/index.native.js.map +1 -1
  137. package/dist/jsx/Wheel/index.mjs +23 -7
  138. package/dist/jsx/Wheel/index.mjs.map +1 -1
  139. package/dist/jsx/Wheel/index.native.js +26 -8
  140. package/dist/jsx/Wheel/index.native.js.map +1 -1
  141. package/dist/jsx/fields/DatePicker/parts.mjs +35 -32
  142. package/dist/jsx/fields/DatePicker/parts.mjs.map +1 -1
  143. package/dist/jsx/fields/DatePicker/parts.native.js +34 -30
  144. package/dist/jsx/fields/DatePicker/parts.native.js.map +1 -1
  145. package/dist/jsx/fields/FileUpload/index.mjs +33 -16
  146. package/dist/jsx/fields/FileUpload/index.mjs.map +1 -1
  147. package/dist/jsx/fields/FileUpload/index.native.js +40 -19
  148. package/dist/jsx/fields/FileUpload/index.native.js.map +1 -1
  149. package/dist/jsx/fields/ImageField/index.mjs +36 -21
  150. package/dist/jsx/fields/ImageField/index.mjs.map +1 -1
  151. package/dist/jsx/fields/ImageField/index.native.js +37 -20
  152. package/dist/jsx/fields/ImageField/index.native.js.map +1 -1
  153. package/dist/jsx/fields/Progress/index.mjs +5 -0
  154. package/dist/jsx/fields/Progress/index.mjs.map +1 -1
  155. package/dist/jsx/fields/Progress/index.native.js +5 -0
  156. package/dist/jsx/fields/Progress/index.native.js.map +1 -1
  157. package/dist/jsx/fields/Slider/index.mjs +38 -12
  158. package/dist/jsx/fields/Slider/index.mjs.map +1 -1
  159. package/dist/jsx/fields/Slider/index.native.js +39 -11
  160. package/dist/jsx/fields/Slider/index.native.js.map +1 -1
  161. package/dist/jsx/fields/Switch/index.mjs +10 -6
  162. package/dist/jsx/fields/Switch/index.mjs.map +1 -1
  163. package/dist/jsx/fields/Switch/index.native.js +10 -6
  164. package/dist/jsx/fields/Switch/index.native.js.map +1 -1
  165. package/dist/jsx/fields/TimePicker/parts.mjs +9 -10
  166. package/dist/jsx/fields/TimePicker/parts.mjs.map +1 -1
  167. package/dist/jsx/fields/TimePicker/parts.native.js +9 -10
  168. package/dist/jsx/fields/TimePicker/parts.native.js.map +1 -1
  169. package/dist/jsx/fields/ToggleGroup/index.mjs +2 -1
  170. package/dist/jsx/fields/ToggleGroup/index.mjs.map +1 -1
  171. package/dist/jsx/fields/ToggleGroup/index.native.js +1 -0
  172. package/dist/jsx/fields/ToggleGroup/index.native.js.map +1 -1
  173. package/dist/jsx/fields/pickerHandle.mjs +2 -0
  174. package/dist/jsx/fields/pickerHandle.mjs.map +1 -0
  175. package/dist/jsx/fields/pickerHandle.native.js +21 -0
  176. package/dist/jsx/fields/pickerHandle.native.js.map +1 -0
  177. package/dist/jsx/hooks/useHoldCue.mjs +27 -0
  178. package/dist/jsx/hooks/useHoldCue.mjs.map +1 -0
  179. package/dist/jsx/hooks/useHoldCue.native.js +62 -0
  180. package/dist/jsx/hooks/useHoldCue.native.js.map +1 -0
  181. package/dist/jsx/index.gnome.mjs +2 -1
  182. package/dist/jsx/index.gnome.mjs.map +1 -1
  183. package/dist/jsx/index.gnome.native.js +4 -0
  184. package/dist/jsx/index.gnome.native.js.map +1 -1
  185. package/dist/jsx/index.js +2 -1
  186. package/dist/jsx/index.js.map +1 -1
  187. package/dist/jsx/index.mjs +2 -1
  188. package/dist/jsx/index.mjs.map +1 -1
  189. package/dist/jsx/index.native.js +4 -0
  190. package/dist/jsx/index.native.js.map +1 -1
  191. package/dist/jsx/shared/colorRamps.mjs +9 -7
  192. package/dist/jsx/shared/colorRamps.mjs.map +1 -1
  193. package/dist/jsx/shared/colorRamps.native.js +9 -7
  194. package/dist/jsx/shared/colorRamps.native.js.map +1 -1
  195. package/dist/jsx/shared/controlTrack.mjs +7 -0
  196. package/dist/jsx/shared/controlTrack.mjs.map +1 -0
  197. package/dist/jsx/shared/controlTrack.native.js +35 -0
  198. package/dist/jsx/shared/controlTrack.native.js.map +1 -0
  199. package/dist/jsx/shared/nativeOverlayWindow.mjs.map +1 -1
  200. package/dist/jsx/shared/nativeOverlayWindow.native.js.map +1 -1
  201. package/dist/jsx/shared/systemControls.mjs.map +1 -1
  202. package/package.json +11 -10
  203. package/src/Button/Button.controlTiers.spec.tsx +2 -2
  204. package/src/ControlGroup/index.tsx +2 -1
  205. package/src/FloatingPanel/FloatingPanel.holdCue.native.spec.tsx +72 -0
  206. package/src/FloatingPanel/index.native.tsx +9 -1
  207. package/src/InputParts/index.tsx +1 -0
  208. package/src/Wheel/Wheel.edgeFade.spec.tsx +74 -0
  209. package/src/Wheel/index.tsx +25 -11
  210. package/src/fields/DatePicker/dateFieldMarks.spec.tsx +154 -0
  211. package/src/fields/DatePicker/parts.tsx +33 -30
  212. package/src/fields/FileUpload/index.tsx +45 -16
  213. package/src/fields/ImageField/index.tsx +42 -19
  214. package/src/fields/Progress/index.tsx +3 -0
  215. package/src/fields/Slider/Slider.system.native.spec.tsx +44 -0
  216. package/src/fields/Slider/index.tsx +40 -12
  217. package/src/fields/Stepper/Stepper.stories.tsx +42 -0
  218. package/src/fields/Switch/Switch.spec.tsx +14 -10
  219. package/src/fields/Switch/index.tsx +11 -7
  220. package/src/fields/TimePicker/parts.tsx +8 -8
  221. package/src/fields/ToggleGroup/index.tsx +7 -1
  222. package/src/fields/fieldTriggerSlot.spec.tsx +89 -0
  223. package/src/fields/pickerHandle.ts +13 -0
  224. package/src/hooks/useHoldCue.ts +26 -0
  225. package/src/index.gnome.ts +1 -0
  226. package/src/index.ts +2 -0
  227. package/src/overlayGround.spec.ts +5 -3
  228. package/src/shared/OverlayWindow.tsx +2 -2
  229. package/src/shared/colorRamps.ts +9 -6
  230. package/src/shared/controlTrack.ts +7 -0
  231. package/src/shared/nativeOverlayWindow.tsx +1 -1
  232. package/src/shared/systemControls.ts +10 -2
  233. package/types/ControlGroup/index.d.ts.map +1 -1
  234. package/types/FloatingPanel/index.native.d.ts.map +1 -1
  235. package/types/InputParts/index.d.ts +4 -4
  236. package/types/InputParts/index.d.ts.map +1 -1
  237. package/types/Wheel/index.d.ts.map +1 -1
  238. package/types/fields/DatePicker/parts.d.ts +4 -3
  239. package/types/fields/DatePicker/parts.d.ts.map +1 -1
  240. package/types/fields/FileUpload/index.d.ts +6 -1
  241. package/types/fields/FileUpload/index.d.ts.map +1 -1
  242. package/types/fields/ImageField/index.d.ts +7 -2
  243. package/types/fields/ImageField/index.d.ts.map +1 -1
  244. package/types/fields/Progress/index.d.ts.map +1 -1
  245. package/types/fields/Slider/index.d.ts.map +1 -1
  246. package/types/fields/Switch/index.d.ts.map +1 -1
  247. package/types/fields/ToggleGroup/index.d.ts.map +1 -1
  248. package/types/fields/pickerHandle.d.ts +12 -0
  249. package/types/fields/pickerHandle.d.ts.map +1 -0
  250. package/types/hooks/useHoldCue.d.ts +10 -0
  251. package/types/hooks/useHoldCue.d.ts.map +1 -0
  252. package/types/index.d.ts +2 -0
  253. package/types/index.d.ts.map +1 -1
  254. package/types/index.gnome.d.ts +1 -0
  255. package/types/index.gnome.d.ts.map +1 -1
  256. package/types/shared/OverlayWindow.d.ts +2 -2
  257. package/types/shared/OverlayWindow.d.ts.map +1 -1
  258. package/types/shared/colorRamps.d.ts +8 -6
  259. package/types/shared/colorRamps.d.ts.map +1 -1
  260. package/types/shared/controlTrack.d.ts +3 -0
  261. package/types/shared/controlTrack.d.ts.map +1 -0
  262. package/types/shared/nativeOverlayWindow.d.ts +1 -1
  263. package/types/shared/nativeOverlayWindow.d.ts.map +1 -1
  264. package/types/shared/systemControls.d.ts +2 -1
  265. package/types/shared/systemControls.d.ts.map +1 -1
@@ -37,9 +37,9 @@ const SCHEMES = ["light", "dark"] as const;
37
37
  type Scheme = (typeof SCHEMES)[number];
38
38
  const WASH: Record<Scheme, { rest: string; hover: string; press: string }> = {
39
39
  light: { rest: "color3", hover: "color4", press: "color5" },
40
- dark: { rest: "color4", hover: "color5", press: "color6" },
40
+ dark: { rest: "color5", hover: "color6", press: "color7" },
41
41
  };
42
- const ELEVATED_DARK_WASH = { rest: "color5", hover: "color6", press: "color7" };
42
+ const ELEVATED_DARK_WASH = { rest: "color6", hover: "color7", press: "color8" };
43
43
 
44
44
  function houseRender(ui: ReactElement, scheme: Scheme = "light", knobs?: object) {
45
45
  return render(
@@ -186,7 +186,7 @@ function ControlGroupImpl({ children, orientation = "horizontal", ...props }: Co
186
186
  <View
187
187
  key={`seam-${index}`}
188
188
  {...(isVertical ? hairline.line : hairline.vline)}
189
- backgroundColor={formInputColors.border.base}
189
+ backgroundColor={formCommonColors.divider}
190
190
  flexGrow={0}
191
191
  flexShrink={0}
192
192
  alignSelf="stretch"
@@ -235,6 +235,7 @@ function ControlGroupImpl({ children, orientation = "horizontal", ...props }: Co
235
235
  backgroundColor={knobProps.outlined ? "transparent" : formInputColors.background.base}
236
236
  {...knobProps.inputSurface}
237
237
  {...knobProps.borderRadius}
238
+ borderColor={formInputColors.border.base}
238
239
  {...elevationWrapperProps}
239
240
  {...transitionProps(knobProps.motion.control, [
240
241
  "borderRadius",
@@ -0,0 +1,72 @@
1
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
2
+ import { act, screen } from "@testing-library/react";
3
+ import { useState } from "react";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { Text } from "tamagui";
6
+ import { holdCueStartMs } from "../hooks/useHoldCue";
7
+ import { FloatingPanel } from "./index.native";
8
+
9
+ vi.mock("react-native-web", async (importOriginal) => {
10
+ const actual = await importOriginal<typeof import("react-native")>();
11
+ return {
12
+ ...actual,
13
+ ActionSheetIOS: undefined,
14
+ Modal: ({ children, visible }: { children: import("react").ReactNode; visible: boolean }) =>
15
+ visible ? children : null,
16
+ };
17
+ });
18
+
19
+ beforeEach(() => {
20
+ vi.useFakeTimers();
21
+ });
22
+ afterEach(() => {
23
+ vi.useRealTimers();
24
+ });
25
+
26
+ function ContextPanel() {
27
+ const [open, setOpen] = useState(false);
28
+ return (
29
+ <FloatingPanel
30
+ open={open}
31
+ onOpenChange={setOpen}
32
+ openOn="contextmenu"
33
+ trigger={<Text>Row</Text>}
34
+ triggerA11y={{ label: "Row" }}
35
+ >
36
+ <Text>Copy</Text>
37
+ </FloatingPanel>
38
+ );
39
+ }
40
+
41
+ function press(target: Element, type: "mousedown" | "mouseup") {
42
+ act(() => {
43
+ target.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, button: 0 }));
44
+ });
45
+ }
46
+
47
+ describe("FloatingPanel native context trigger hold cue (LC-197, FZ-8902)", () => {
48
+ it("shows the cue partway to the 500ms hold and clears it on release", () => {
49
+ renderWithProviders(<ContextPanel />);
50
+ const trigger = screen.getByRole("button", { name: "Row" });
51
+ press(trigger, "mousedown");
52
+ expect(trigger).not.toHaveAttribute("data-hold");
53
+ act(() => {
54
+ vi.advanceTimersByTime(holdCueStartMs);
55
+ });
56
+ expect(trigger).toHaveAttribute("data-hold", "progress");
57
+ expect(screen.queryByText("Copy")).toBeNull();
58
+ press(trigger, "mouseup");
59
+ expect(trigger).not.toHaveAttribute("data-hold");
60
+ });
61
+
62
+ it("drops the cue when the hold opens the menu", () => {
63
+ renderWithProviders(<ContextPanel />);
64
+ const trigger = screen.getByRole("button", { name: "Row" });
65
+ press(trigger, "mousedown");
66
+ act(() => {
67
+ vi.advanceTimersByTime(600);
68
+ });
69
+ expect(screen.getByText("Copy")).toBeTruthy();
70
+ expect(trigger).not.toHaveAttribute("data-hold");
71
+ });
72
+ });
@@ -124,6 +124,7 @@ import {
124
124
  scrollArrowBand,
125
125
  scrollArrowThreshold,
126
126
  } from "../shared/checkedOnField";
127
+ import { holdCueScale, useHoldCue } from "../hooks/useHoldCue";
127
128
  import { useInsideOverlay, useOverlayZIndex } from "../shared/overlayStack";
128
129
  import { OverlayWindow } from "../shared/OverlayWindow";
129
130
  import { t } from "../shared/t";
@@ -244,8 +245,10 @@ export function FloatingPanel({
244
245
  const handleTriggerPress = useCallback(() => {
245
246
  if (!disabled) onOpenChange(!open);
246
247
  }, [disabled, onOpenChange, open]);
248
+ const hold = useHoldCue();
247
249
  const handleTriggerLongPress = useCallback(
248
250
  (event?: GestureResponderEvent) => {
251
+ hold.stop();
249
252
  if (disabled) return;
250
253
  const { pageX, pageY } = event?.nativeEvent ?? {};
251
254
  setPressPoint(
@@ -253,7 +256,7 @@ export function FloatingPanel({
253
256
  );
254
257
  onOpenChange(true);
255
258
  },
256
- [disabled, onOpenChange],
259
+ [disabled, onOpenChange, hold.stop],
257
260
  );
258
261
  const isContextMenu = openOn === "contextmenu";
259
262
  // A long-press menu never eats its trigger's tap: the web lets the primary
@@ -629,6 +632,10 @@ export function FloatingPanel({
629
632
  ? {
630
633
  onLongPress: handleTriggerLongPress,
631
634
  delayLongPress: 500,
635
+ ...(disabled
636
+ ? undefined
637
+ : { delayPressIn: 0, onPressIn: hold.start, onPressOut: hold.stop }),
638
+ ...(hold.holding ? { dataSet: { hold: "progress" } } : undefined),
632
639
  ...(triggerOwn?.onPress && !disabled ? { onPress: triggerOwn.onPress } : undefined),
633
640
  }
634
641
  : { onPress: handleTriggerPress })}
@@ -648,6 +655,7 @@ export function FloatingPanel({
648
655
  alignSelf: "stretch",
649
656
  width: triggerSizing === "content" ? undefined : "100%",
650
657
  opacity: disabled ? 0.5 : 1,
658
+ ...(hold.holding ? { transform: [{ scale: holdCueScale }] } : undefined),
651
659
  }}
652
660
  >
653
661
  {/* Own the hit target — see file header for why pointerEvents="none". */}
@@ -634,6 +634,7 @@ const InputGroupImpl = InputGroupFrame.styleable<{
634
634
  {...(knobProps.elevation == null ? { elevation: undefined } : knobProps.elevationChrome)}
635
635
  {...knobProps.borderRadius}
636
636
  borderWidth={knobProps.fieldEdge.borderWidth}
637
+ borderColor={formInputColors.border.base}
637
638
  {...hiddenEdge}
638
639
  {...sizeTableRadius}
639
640
  {...transitionProps(knobProps.motion.control)}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The web wheel's edge fade (FZ-8080): a LinearGradient veil of the surface the
3
+ * wheel sits on, never a CSS mask on the drum.
4
+ */
5
+ import { SurfaceContext } from "@multiplatform.one/theme";
6
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
7
+ import { cleanup, screen } from "@testing-library/react";
8
+ import { createElement } from "react";
9
+ import { Theme, useTheme } from "tamagui";
10
+ import { afterEach, describe, expect, it, vi } from "vitest";
11
+ import { Wheel } from "./index";
12
+
13
+ vi.mock("tamagui/linear-gradient", () => ({
14
+ LinearGradient: (props: Record<string, unknown>) =>
15
+ createElement("span", {
16
+ "data-testid": props["data-testid"],
17
+ "data-colors": JSON.stringify(props.colors),
18
+ "data-locations": JSON.stringify(props.locations),
19
+ "data-opacity": props.opacity,
20
+ "data-pointer-events": props.pointerEvents,
21
+ }),
22
+ }));
23
+
24
+ afterEach(cleanup);
25
+
26
+ const items = Array.from({ length: 24 }, (_, i) => i);
27
+ const seen = { background: "" };
28
+
29
+ function Probe() {
30
+ seen.background = String(useTheme().background?.val);
31
+ return null;
32
+ }
33
+
34
+ describe("web Wheel edge fade (FZ-8080)", () => {
35
+ it("paints no CSS mask on the drum", () => {
36
+ renderWithProviders(<Wheel items={items} value={6} onChange={() => {}} />);
37
+ const drum = screen.getByTestId("wheel-wrapper");
38
+ expect(drum.style.maskImage).toBe("");
39
+ expect(drum.style.getPropertyValue("-webkit-mask-image")).toBe("");
40
+ });
41
+
42
+ it("veils both ends in the page surface and leaves the selection band clear", () => {
43
+ renderWithProviders(
44
+ <>
45
+ <Probe />
46
+ <Wheel items={items} value={6} onChange={() => {}} />
47
+ </>,
48
+ );
49
+ const fade = screen.getByTestId("wheel-fade");
50
+ expect(JSON.parse(fade.getAttribute("data-colors")!)).toEqual([
51
+ seen.background,
52
+ "transparent",
53
+ "transparent",
54
+ seen.background,
55
+ ]);
56
+ expect(JSON.parse(fade.getAttribute("data-locations")!)).toEqual([0, 0.32, 0.68, 1]);
57
+ expect(fade.getAttribute("data-opacity")).toBe("0.72");
58
+ expect(fade.getAttribute("data-pointer-events")).toBe("none");
59
+ });
60
+
61
+ it("veils in the elevated surface on a dark overlay", () => {
62
+ renderWithProviders(
63
+ <Theme name="dark">
64
+ <SurfaceContext.Provider elevated>
65
+ <Probe />
66
+ <Wheel items={items} value={6} onChange={() => {}} />
67
+ </SurfaceContext.Provider>
68
+ </Theme>,
69
+ );
70
+ const colors = JSON.parse(screen.getByTestId("wheel-fade").getAttribute("data-colors")!);
71
+ expect(colors[0]).not.toBe(seen.background);
72
+ expect(colors[0]).toBe(colors[3]);
73
+ });
74
+ });
@@ -1,7 +1,8 @@
1
- import type { CSSProperties } from "react";
1
+ import { useElevatedSurface } from "@multiplatform.one/theme";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import type { GetProps } from "tamagui";
4
- import { View, isWeb, styled, Text } from "tamagui";
4
+ import { View, isWeb, styled, Text, useTheme } from "tamagui";
5
+ import { LinearGradient } from "tamagui/linear-gradient";
5
6
  import { t } from "../shared/t";
6
7
  import { fireSelectionHaptic } from "./haptic";
7
8
  import { NativeWheel } from "./NativeWheel";
@@ -49,14 +50,8 @@ function ensureWheelFocusStyles() {
49
50
  document.head.appendChild(style);
50
51
  }
51
52
 
52
- const wheelMaskStyle: CSSProperties | undefined = isWeb
53
- ? {
54
- WebkitMaskImage:
55
- "linear-gradient(to bottom, rgba(0,0,0,0.28) 0%, #000 32%, #000 68%, rgba(0,0,0,0.28) 100%)",
56
- maskImage:
57
- "linear-gradient(to bottom, rgba(0,0,0,0.28) 0%, #000 32%, #000 68%, rgba(0,0,0,0.28) 100%)",
58
- }
59
- : undefined;
53
+ const wheelFadeStops = [0, 0.32, 0.68, 1];
54
+ const wheelFadeOpacity = 0.72;
60
55
 
61
56
  // ── Styled components ─────────────────────────────────────────
62
57
 
@@ -164,6 +159,12 @@ function WebWheel<T = string | number>(props: WheelProps<T>) {
164
159
  highlightClassName,
165
160
  } = useWheelLayout(props);
166
161
  const columnWidth = widthProp ?? defaultColumnWidth;
162
+ const theme = useTheme();
163
+ const elevatedGround = useElevatedSurface();
164
+ const fadeKey = (
165
+ elevatedGround ? knobProps.elevatedSurface.backgroundColor : "$background"
166
+ ) as string;
167
+ const fadeSurface = String(theme[fadeKey.replace(/^\$/, "") as "background"]?.val ?? fadeKey);
167
168
  ensureWheelFocusStyles();
168
169
  const containerRef = useRef<HTMLDivElement>(null);
169
170
  const [scrollOffset, setScrollOffset] = useState(0);
@@ -714,7 +715,6 @@ function WebWheel<T = string | number>(props: WheelProps<T>) {
714
715
  userSelect: "none",
715
716
  touchAction: "none",
716
717
  cursor: disabled ? "not-allowed" : "grab",
717
- ...wheelMaskStyle,
718
718
  }}
719
719
  onPointerDown={handlePointerDown as any}
720
720
  onPointerMove={handlePointerMove as any}
@@ -757,6 +757,20 @@ function WebWheel<T = string | number>(props: WheelProps<T>) {
757
757
  );
758
758
  })}
759
759
  </div>
760
+ <LinearGradient
761
+ data-testid="wheel-fade"
762
+ pointerEvents="none"
763
+ position="absolute"
764
+ top={0}
765
+ right={0}
766
+ bottom={0}
767
+ left={0}
768
+ opacity={wheelFadeOpacity}
769
+ colors={[fadeSurface, "transparent", "transparent", fadeSurface]}
770
+ locations={wheelFadeStops}
771
+ start={[0, 0]}
772
+ end={[0, 1]}
773
+ />
760
774
  </WheelContainer>
761
775
  );
762
776
  }
@@ -0,0 +1,154 @@
1
+ import { CalendarBlankIcon, ClockIcon } from "@phosphor-icons/react";
2
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
3
+ import {
4
+ createThemesBuilder,
5
+ defaultAccentTheme,
6
+ defaultBaseTheme,
7
+ defaultBuilderOptions,
8
+ } from "@multiplatform.one/theme";
9
+ import { configWithoutAnimations } from "@tamagui/config";
10
+ import { animationsCSS } from "@tamagui/config/v5-css";
11
+ import { render } from "@testing-library/react";
12
+ import type { ReactElement } from "react";
13
+ import { renderToStaticMarkup } from "react-dom/server";
14
+ import { TamaguiProvider, createTamagui } from "tamagui";
15
+ import { describe, expect, it, vi } from "vitest";
16
+ import { Input } from "../Input";
17
+ import { TimePicker } from "../TimePicker";
18
+ import { DatePicker, DatetimePicker } from "./index";
19
+ import { CalendarDays, MonthGrid, YearGrid, isSameDay, useCalendarData } from "./parts";
20
+
21
+ vi.mock("@multiplatform.one/storybook", () => ({ action: () => () => {} }));
22
+ vi.mock("@multiplatform.one/table", () => ({ DataTable: () => null }));
23
+
24
+ const houseThemes = createThemesBuilder(
25
+ defaultBaseTheme,
26
+ defaultAccentTheme,
27
+ defaultBuilderOptions,
28
+ ).themes() as Record<string, Record<string, string>>;
29
+
30
+ const houseConfig = createTamagui({
31
+ ...configWithoutAnimations,
32
+ animations: animationsCSS,
33
+ themes: houseThemes as never,
34
+ });
35
+
36
+ function placeholderInk(node: ReactElement) {
37
+ const { container, unmount } = renderWithProviders(node);
38
+ const input = container.querySelector("input");
39
+ expect(input).toBeTruthy();
40
+ const ink = input!.style.getPropertyValue("--t_placeholderColor");
41
+ unmount();
42
+ return ink;
43
+ }
44
+
45
+ function pathOf(icon: ReactElement) {
46
+ return renderToStaticMarkup(icon).match(/ d="([^"]+)"/)![1];
47
+ }
48
+
49
+ function June() {
50
+ const calendarData = useCalendarData(new Date(2024, 5, 15));
51
+ return (
52
+ <CalendarDays
53
+ calendarData={calendarData}
54
+ isSelected={(day) => isSameDay(day, new Date(2024, 5, 15))}
55
+ onSelect={() => {}}
56
+ />
57
+ );
58
+ }
59
+
60
+ function selectedCell(node: ReactElement, scheme: "light" | "dark") {
61
+ const { container } = render(
62
+ <TamaguiProvider config={houseConfig} defaultTheme={scheme} disableInjectCSS>
63
+ {node}
64
+ </TamaguiProvider>,
65
+ );
66
+ const cell = container.querySelector<HTMLElement>("[aria-pressed='true']");
67
+ expect(cell).toBeTruthy();
68
+ return cell!;
69
+ }
70
+
71
+ describe("date and time fields read empty, name their kind and mark the pick (FZ-8433)", () => {
72
+ const stock = placeholderInk(<Input label="Name" placeholder="Your name" />);
73
+
74
+ it.each([
75
+ ["DatePicker", <DatePicker key="d" label="Date" placeholder="Pick a date" />],
76
+ ["DatetimePicker", <DatetimePicker key="dt" label="When" placeholder="Pick a time" />],
77
+ ["TimePicker", <TimePicker key="t" label="Time" placeholder="Select time" />],
78
+ ])("an empty %s draws its placeholder in the theme's placeholder ink", (_name, node) => {
79
+ const ink = placeholderInk(node);
80
+ expect(stock).not.toBe("");
81
+ expect(ink).toBe(stock);
82
+ expect(ink).not.toBe("var(--color11)");
83
+ });
84
+
85
+ it.each([
86
+ [
87
+ "DatetimePicker",
88
+ <DatetimePicker key="dt" label="When" />,
89
+ "date-picker-glyph",
90
+ CalendarBlankIcon,
91
+ ],
92
+ ["DatePicker", <DatePicker key="d" label="Date" />, "date-picker-glyph", CalendarBlankIcon],
93
+ ["TimePicker", <TimePicker key="t" label="Time" />, "time-picker-glyph", ClockIcon],
94
+ [
95
+ "a read-only DatetimePicker",
96
+ <DatetimePicker key="ro" label="When" value={new Date(2026, 9, 31, 9, 30)} readOnly />,
97
+ "date-picker-glyph",
98
+ CalendarBlankIcon,
99
+ ],
100
+ ] as const)("%s draws one glyph, on the input's right", (_name, node, testId, Icon) => {
101
+ const { container, unmount } = renderWithProviders(node);
102
+ const glyphs = container.querySelectorAll(`[data-testid='${testId}']`);
103
+ expect(glyphs).toHaveLength(1);
104
+ const paths = Array.from(glyphs[0]!.querySelectorAll("svg path")).map((p) =>
105
+ p.getAttribute("d"),
106
+ );
107
+ expect(paths).toEqual([pathOf(<Icon />)]);
108
+ const input = container.querySelector("input")!;
109
+ expect(
110
+ input.compareDocumentPosition(glyphs[0]!) & Node.DOCUMENT_POSITION_FOLLOWING,
111
+ ).toBeTruthy();
112
+ unmount();
113
+ });
114
+
115
+ it("a filled web field swaps its glyph for the clear action, so the right holds one icon", () => {
116
+ const { container } = renderWithProviders(
117
+ <DatetimePicker label="When" value={new Date(2026, 9, 31, 9, 30)} />,
118
+ );
119
+ expect(container.querySelector("[data-testid='date-picker-glyph']")).toBeNull();
120
+ expect(container.querySelector("[data-testid='date-picker-clear']")).toBeTruthy();
121
+ });
122
+
123
+ it.each(
124
+ (["light", "dark"] as const).flatMap((scheme) => [
125
+ ["day", scheme, <June key="d" />],
126
+ [
127
+ "month",
128
+ scheme,
129
+ <MonthGrid key="m" currentYear={2024} selectedMonth={5} onSelectMonth={() => {}} />,
130
+ ],
131
+ [
132
+ "year",
133
+ scheme,
134
+ <YearGrid
135
+ key="y"
136
+ yearPageStart={2020}
137
+ selectedYear={2024}
138
+ onSelectYear={() => {}}
139
+ onPrevPage={() => {}}
140
+ onNextPage={() => {}}
141
+ />,
142
+ ],
143
+ ]) as [string, "light" | "dark", ReactElement][],
144
+ )(
145
+ "the picked %s fills with the accent mark in %s, inked for that fill",
146
+ (_name, scheme, node) => {
147
+ const cell = selectedCell(node, scheme);
148
+ expect(cell.className).toMatch(/(?:^|\s)_bg-accentBackg/);
149
+ expect(cell.className).not.toMatch(/t_accent/);
150
+ const ink = cell.querySelector<HTMLElement>("span")!.className;
151
+ expect(ink).toMatch(/(?:^|\s)_col-(?:hsla?|rgba?)/);
152
+ },
153
+ );
154
+ });
@@ -12,6 +12,7 @@ import {
12
12
  getMonthNames,
13
13
  useAccentOnSurface,
14
14
  useGlyphColor,
15
+ useReadableTextOn,
15
16
  useResolvedKnobs,
16
17
  } from "@multiplatform.one/theme";
17
18
  import {
@@ -29,7 +30,7 @@ import { FloatingPanel, floatingPanelActionProps, isNestedPanelAction } from "..
29
30
  import type { PanelActionEvent } from "../../FloatingPanel/nestedAction";
30
31
  import type { PanelWidth } from "../../FloatingPanel/panelWidth";
31
32
  import { Input as InputParts } from "../../InputParts";
32
- import { formCommonColors } from "../../shared/colorRamps";
33
+ import { formCommonColors, formSelectedColors } from "../../shared/colorRamps";
33
34
  import { t } from "../../shared/t";
34
35
 
35
36
  // ---------------------------------------------------------------------------
@@ -341,6 +342,7 @@ export function CalendarDayCell({
341
342
  }) {
342
343
  const { knobProps } = useCalendarKnobs();
343
344
  const accentText = useAccentOnSurface();
345
+ const onMark = useReadableTextOn(formSelectedColors.mark);
344
346
  const { px, hitSlop, box } = splitNestedControl(knobProps.nestedControl);
345
347
 
346
348
  if (!visible) {
@@ -373,8 +375,7 @@ export function CalendarDayCell({
373
375
  // identical to a choosable cell; we do not adopt that.
374
376
  opacity={disabled ? 0.5 : dimmed ? 0.45 : 1}
375
377
  pointerEvents={disabled ? "none" : "auto"}
376
- backgroundColor={selected ? (selectedBackground ?? "$background") : "transparent"}
377
- theme={selected ? ("accent" as any) : undefined}
378
+ backgroundColor={selected ? (selectedBackground ?? formSelectedColors.mark) : "transparent"}
378
379
  borderWidth={showTodayMark ? 2 : 0}
379
380
  borderColor={showTodayMark ? accentText : "transparent"}
380
381
  zIndex={1}
@@ -398,7 +399,7 @@ export function CalendarDayCell({
398
399
  {...knobProps.label}
399
400
  {...knobProps.textWeight}
400
401
  fontWeight={selected ? "bold" : knobProps.textWeight.fontWeight}
401
- color={selected ? undefined : today ? accentText : formCommonColors.text}
402
+ color={selected ? onMark : today ? accentText : formCommonColors.text}
402
403
  >
403
404
  {day.getDate()}
404
405
  </Text>
@@ -675,6 +676,7 @@ interface MonthGridProps {
675
676
 
676
677
  export function MonthGrid({ currentYear, selectedMonth, onSelectMonth }: MonthGridProps) {
677
678
  const { knobProps } = useCalendarKnobs();
679
+ const onMark = useReadableTextOn(formSelectedColors.mark);
678
680
  const { animation, animationKey } = useDateAnimation(currentYear);
679
681
  const { px, hitSlop, box } = splitNestedControl(knobProps.nestedControl);
680
682
  const cellW = (px * 7) / 3;
@@ -706,8 +708,7 @@ export function MonthGrid({ currentYear, selectedMonth, onSelectMonth }: MonthGr
706
708
  cursor="pointer"
707
709
  alignItems="center"
708
710
  justifyContent="center"
709
- theme={isSelected ? ("accent" as any) : undefined}
710
- backgroundColor={isSelected ? "$background" : "transparent"}
711
+ backgroundColor={isSelected ? formSelectedColors.mark : "transparent"}
711
712
  onPress={() => onSelectMonth(index)}
712
713
  outlineWidth={0}
713
714
  focusStyle={{ outlineWidth: 0 }}
@@ -725,7 +726,7 @@ export function MonthGrid({ currentYear, selectedMonth, onSelectMonth }: MonthGr
725
726
  >
726
727
  <Text
727
728
  {...knobProps.label}
728
- color={isSelected ? "$color" : formCommonColors.text}
729
+ color={isSelected ? onMark : formCommonColors.text}
729
730
  fontWeight={isSelected ? "bold" : "400"}
730
731
  >
731
732
  {t(name.slice(0, 3))}
@@ -758,6 +759,7 @@ export function YearGrid({
758
759
  onNextPage,
759
760
  }: YearGridProps) {
760
761
  const { knobProps } = useCalendarKnobs();
762
+ const onMark = useReadableTextOn(formSelectedColors.mark);
761
763
  const glyphColor = useGlyphColor();
762
764
  const { px, hitSlop, box } = splitNestedControl(knobProps.nestedControl);
763
765
  const cellW = (px * 7) / 3;
@@ -810,8 +812,7 @@ export function YearGrid({
810
812
  cursor="pointer"
811
813
  alignItems="center"
812
814
  justifyContent="center"
813
- theme={isSelected ? ("accent" as any) : undefined}
814
- backgroundColor={isSelected ? "$background" : "transparent"}
815
+ backgroundColor={isSelected ? formSelectedColors.mark : "transparent"}
815
816
  onPress={() => onSelectYear(year)}
816
817
  outlineWidth={0}
817
818
  focusStyle={{ outlineWidth: 0 }}
@@ -829,7 +830,7 @@ export function YearGrid({
829
830
  >
830
831
  <Text
831
832
  {...knobProps.label}
832
- color={isSelected ? "$color" : formCommonColors.text}
833
+ color={isSelected ? onMark : formCommonColors.text}
833
834
  fontWeight={isSelected ? "bold" : "400"}
834
835
  >
835
836
  {year}
@@ -855,8 +856,8 @@ interface DatePickerInputTriggerProps {
855
856
  onReset: () => void;
856
857
  onButtonPress: () => void;
857
858
  id?: string;
858
- /** Empty-state glyph. Datetime uses calendar; TimePicker uses clock. */
859
- glyph?: "calendar" | "clock";
859
+ /** Trailing glyph: a clock for a time, else a calendar. */
860
+ glyph?: TriggerGlyphKind;
860
861
  "aria-label"?: string;
861
862
  "aria-labelledby"?: string;
862
863
  "aria-describedby"?: string;
@@ -864,6 +865,16 @@ interface DatePickerInputTriggerProps {
864
865
  "aria-invalid"?: boolean;
865
866
  }
866
867
 
868
+ type TriggerGlyphKind = "calendar" | "clock";
869
+
870
+ function TriggerGlyph({ glyph, ...groupItem }: { glyph: TriggerGlyphKind }) {
871
+ return (
872
+ <InputParts.Icon adornment="trailing" color={formCommonColors.muted} {...groupItem}>
873
+ {glyph === "clock" ? <ClockIcon /> : <CalendarBlankIcon />}
874
+ </InputParts.Icon>
875
+ );
876
+ }
877
+
867
878
  export function DatePickerClearButton({
868
879
  onReset,
869
880
  disabled,
@@ -918,8 +929,6 @@ export function DatePickerInputTrigger({
918
929
  }
919
930
  };
920
931
 
921
- const Glyph = glyph === "clock" ? ClockIcon : CalendarBlankIcon;
922
-
923
932
  return (
924
933
  // LC-40: the disabled Box below is pointerEvents-none, so the cursor
925
934
  // affordance must ride this wrapper — the fragment's not-allowed never
@@ -961,11 +970,6 @@ export function DatePickerInputTrigger({
961
970
  }
962
971
  : undefined)}
963
972
  >
964
- <InputParts.Section data-testid="date-picker-glyph">
965
- <InputParts.Icon adornment="leading" color={formCommonColors.muted}>
966
- <Glyph />
967
- </InputParts.Icon>
968
- </InputParts.Section>
969
973
  <InputParts.Section>
970
974
  <InputParts.Area
971
975
  readOnly
@@ -975,14 +979,17 @@ export function DatePickerInputTrigger({
975
979
  placeholder={placeholder}
976
980
  disabled={disabled}
977
981
  color={formCommonColors.text}
978
- placeholderTextColor={formCommonColors.muted}
979
982
  />
980
983
  </InputParts.Section>
981
- {value && isWeb ? (
984
+ {value && isWeb && !disabled ? (
982
985
  <InputParts.Section>
983
986
  <DatePickerClearButton onReset={onReset} disabled={disabled} />
984
987
  </InputParts.Section>
985
- ) : null}
988
+ ) : (
989
+ <InputParts.Section data-testid="date-picker-glyph">
990
+ <TriggerGlyph glyph={glyph} />
991
+ </InputParts.Section>
992
+ )}
986
993
  </InputParts.Box>
987
994
  </View>
988
995
  );
@@ -992,7 +999,7 @@ interface DatePickerReadOnlyFieldProps {
992
999
  value: string;
993
1000
  placeholder?: string;
994
1001
  id?: string;
995
- glyph?: "calendar" | "clock";
1002
+ glyph?: TriggerGlyphKind;
996
1003
  "aria-label"?: string;
997
1004
  "aria-required"?: boolean;
998
1005
  "aria-invalid"?: boolean;
@@ -1011,7 +1018,6 @@ export function DatePickerReadOnlyField({
1011
1018
  "aria-required": ariaRequired,
1012
1019
  "aria-invalid": ariaInvalid,
1013
1020
  }: DatePickerReadOnlyFieldProps) {
1014
- const Glyph = glyph === "clock" ? ClockIcon : CalendarBlankIcon;
1015
1021
  return (
1016
1022
  <View flexDirection="row" alignItems="center" width="100%">
1017
1023
  <InputParts.Box
@@ -1020,11 +1026,6 @@ export function DatePickerReadOnlyField({
1020
1026
  elevation={undefined}
1021
1027
  data-testid="date-picker-readonly"
1022
1028
  >
1023
- <InputParts.Section data-testid="date-picker-glyph">
1024
- <InputParts.Icon adornment="leading" color={formCommonColors.muted}>
1025
- <Glyph />
1026
- </InputParts.Icon>
1027
- </InputParts.Section>
1028
1029
  <InputParts.Section>
1029
1030
  <InputParts.Area
1030
1031
  readOnly
@@ -1032,13 +1033,15 @@ export function DatePickerReadOnlyField({
1032
1033
  value={value}
1033
1034
  placeholder={placeholder}
1034
1035
  color={formCommonColors.text}
1035
- placeholderTextColor={formCommonColors.muted}
1036
1036
  aria-label={ariaLabel}
1037
1037
  aria-readonly="true"
1038
1038
  aria-required={ariaRequired}
1039
1039
  aria-invalid={ariaInvalid}
1040
1040
  />
1041
1041
  </InputParts.Section>
1042
+ <InputParts.Section data-testid="date-picker-glyph">
1043
+ <TriggerGlyph glyph={glyph} />
1044
+ </InputParts.Section>
1042
1045
  </InputParts.Box>
1043
1046
  </View>
1044
1047
  );