@cdx-ui/components 0.1.5 → 0.1.7

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 (242) hide show
  1. package/coverage.svg +1 -1
  2. package/lib/commonjs/components/Alert/index.js.map +1 -1
  3. package/lib/commonjs/components/Avatar/index.js +6 -4
  4. package/lib/commonjs/components/Avatar/index.js.map +1 -1
  5. package/lib/commonjs/components/Badge/index.js.map +1 -1
  6. package/lib/commonjs/components/Badge/styles.js +2 -2
  7. package/lib/commonjs/components/BottomSheet/index.js +106 -91
  8. package/lib/commonjs/components/BottomSheet/index.js.map +1 -1
  9. package/lib/commonjs/components/BottomSheet/styles.js +1 -12
  10. package/lib/commonjs/components/BottomSheet/styles.js.map +1 -1
  11. package/lib/commonjs/components/Button/index.js.map +1 -1
  12. package/lib/commonjs/components/Card/index.js.map +1 -1
  13. package/lib/commonjs/components/Dialog/index.js.map +1 -1
  14. package/lib/commonjs/components/Divider/index.js.map +1 -1
  15. package/lib/commonjs/components/Form/styles.js +1 -1
  16. package/lib/commonjs/components/Form/styles.js.map +1 -1
  17. package/lib/commonjs/components/FullScreenDialog/animation.js +46 -0
  18. package/lib/commonjs/components/FullScreenDialog/animation.js.map +1 -0
  19. package/lib/commonjs/components/FullScreenDialog/index.js +343 -0
  20. package/lib/commonjs/components/FullScreenDialog/index.js.map +1 -0
  21. package/lib/commonjs/components/FullScreenDialog/styles.js +14 -0
  22. package/lib/commonjs/components/FullScreenDialog/styles.js.map +1 -0
  23. package/lib/commonjs/components/IconButton/styles.js +7 -5
  24. package/lib/commonjs/components/IconButton/styles.js.map +1 -1
  25. package/lib/commonjs/components/InsuranceDisclosure/index.js.map +1 -1
  26. package/lib/commonjs/components/Link/index.js.map +1 -1
  27. package/lib/commonjs/components/ListItem/index.js +2 -0
  28. package/lib/commonjs/components/ListItem/index.js.map +1 -1
  29. package/lib/commonjs/components/ListItem/styles.js +6 -0
  30. package/lib/commonjs/components/ListItem/styles.js.map +1 -1
  31. package/lib/commonjs/components/ProgressSegmented/index.js.map +1 -1
  32. package/lib/commonjs/components/Radio/index.js.map +1 -1
  33. package/lib/commonjs/components/ScopedTheme/index.js +11 -3
  34. package/lib/commonjs/components/ScopedTheme/index.js.map +1 -1
  35. package/lib/commonjs/components/Skeleton/index.js.map +1 -1
  36. package/lib/commonjs/components/Stack/HStack.js.map +1 -1
  37. package/lib/commonjs/components/Stack/VStack.js.map +1 -1
  38. package/lib/commonjs/components/Textarea/index.js.map +1 -1
  39. package/lib/commonjs/components/Tile/index.js.map +1 -1
  40. package/lib/commonjs/components/Toast/index.js +40 -12
  41. package/lib/commonjs/components/Toast/index.js.map +1 -1
  42. package/lib/commonjs/components/Toast/styles.js +15 -4
  43. package/lib/commonjs/components/Toast/styles.js.map +1 -1
  44. package/lib/commonjs/components/ToggleButton/index.js +249 -0
  45. package/lib/commonjs/components/ToggleButton/index.js.map +1 -0
  46. package/lib/commonjs/components/ToggleButton/styles.js +155 -0
  47. package/lib/commonjs/components/ToggleButton/styles.js.map +1 -0
  48. package/lib/commonjs/components/index.js +24 -0
  49. package/lib/commonjs/components/index.js.map +1 -1
  50. package/lib/commonjs/components/picker/styles.js +6 -2
  51. package/lib/commonjs/components/picker/styles.js.map +1 -1
  52. package/lib/commonjs/components/sheet-header/index.js +97 -0
  53. package/lib/commonjs/components/sheet-header/index.js.map +1 -0
  54. package/lib/commonjs/components/sheet-header/styles.js +28 -0
  55. package/lib/commonjs/components/sheet-header/styles.js.map +1 -0
  56. package/lib/commonjs/figma/BottomSheet.figma.js +1 -1
  57. package/lib/commonjs/providers/toast/provider.js +44 -11
  58. package/lib/commonjs/providers/toast/provider.js.map +1 -1
  59. package/lib/module/components/Alert/index.js.map +1 -1
  60. package/lib/module/components/Avatar/index.js +6 -4
  61. package/lib/module/components/Avatar/index.js.map +1 -1
  62. package/lib/module/components/Badge/index.js.map +1 -1
  63. package/lib/module/components/Badge/styles.js +2 -2
  64. package/lib/module/components/BottomSheet/index.js +110 -95
  65. package/lib/module/components/BottomSheet/index.js.map +1 -1
  66. package/lib/module/components/BottomSheet/styles.js +0 -11
  67. package/lib/module/components/BottomSheet/styles.js.map +1 -1
  68. package/lib/module/components/Button/index.js.map +1 -1
  69. package/lib/module/components/Card/index.js.map +1 -1
  70. package/lib/module/components/Dialog/index.js.map +1 -1
  71. package/lib/module/components/Divider/index.js.map +1 -1
  72. package/lib/module/components/Form/styles.js +1 -1
  73. package/lib/module/components/Form/styles.js.map +1 -1
  74. package/lib/module/components/FullScreenDialog/animation.js +41 -0
  75. package/lib/module/components/FullScreenDialog/animation.js.map +1 -0
  76. package/lib/module/components/FullScreenDialog/index.js +338 -0
  77. package/lib/module/components/FullScreenDialog/index.js.map +1 -0
  78. package/lib/module/components/FullScreenDialog/styles.js +10 -0
  79. package/lib/module/components/FullScreenDialog/styles.js.map +1 -0
  80. package/lib/module/components/IconButton/styles.js +7 -4
  81. package/lib/module/components/IconButton/styles.js.map +1 -1
  82. package/lib/module/components/InsuranceDisclosure/index.js.map +1 -1
  83. package/lib/module/components/Link/index.js.map +1 -1
  84. package/lib/module/components/ListItem/index.js +2 -0
  85. package/lib/module/components/ListItem/index.js.map +1 -1
  86. package/lib/module/components/ListItem/styles.js +6 -0
  87. package/lib/module/components/ListItem/styles.js.map +1 -1
  88. package/lib/module/components/ProgressSegmented/index.js.map +1 -1
  89. package/lib/module/components/Radio/index.js.map +1 -1
  90. package/lib/module/components/ScopedTheme/index.js +11 -3
  91. package/lib/module/components/ScopedTheme/index.js.map +1 -1
  92. package/lib/module/components/Skeleton/index.js.map +1 -1
  93. package/lib/module/components/Stack/HStack.js.map +1 -1
  94. package/lib/module/components/Stack/VStack.js.map +1 -1
  95. package/lib/module/components/Textarea/index.js.map +1 -1
  96. package/lib/module/components/Tile/index.js.map +1 -1
  97. package/lib/module/components/Toast/index.js +43 -15
  98. package/lib/module/components/Toast/index.js.map +1 -1
  99. package/lib/module/components/Toast/styles.js +14 -3
  100. package/lib/module/components/Toast/styles.js.map +1 -1
  101. package/lib/module/components/ToggleButton/index.js +245 -0
  102. package/lib/module/components/ToggleButton/index.js.map +1 -0
  103. package/lib/module/components/ToggleButton/styles.js +152 -0
  104. package/lib/module/components/ToggleButton/styles.js.map +1 -0
  105. package/lib/module/components/index.js +2 -0
  106. package/lib/module/components/index.js.map +1 -1
  107. package/lib/module/components/picker/styles.js +6 -2
  108. package/lib/module/components/picker/styles.js.map +1 -1
  109. package/lib/module/components/sheet-header/index.js +94 -0
  110. package/lib/module/components/sheet-header/index.js.map +1 -0
  111. package/lib/module/components/sheet-header/styles.js +25 -0
  112. package/lib/module/components/sheet-header/styles.js.map +1 -0
  113. package/lib/module/figma/BottomSheet.figma.js +1 -1
  114. package/lib/module/providers/toast/provider.js +44 -11
  115. package/lib/module/providers/toast/provider.js.map +1 -1
  116. package/lib/typescript/components/Alert/index.d.ts +4 -1
  117. package/lib/typescript/components/Alert/index.d.ts.map +1 -1
  118. package/lib/typescript/components/Avatar/index.d.ts +10 -4
  119. package/lib/typescript/components/Avatar/index.d.ts.map +1 -1
  120. package/lib/typescript/components/Badge/index.d.ts +34 -0
  121. package/lib/typescript/components/Badge/index.d.ts.map +1 -1
  122. package/lib/typescript/components/BottomSheet/index.d.ts +12 -1
  123. package/lib/typescript/components/BottomSheet/index.d.ts.map +1 -1
  124. package/lib/typescript/components/BottomSheet/styles.d.ts +0 -5
  125. package/lib/typescript/components/BottomSheet/styles.d.ts.map +1 -1
  126. package/lib/typescript/components/Button/index.d.ts +9 -1
  127. package/lib/typescript/components/Button/index.d.ts.map +1 -1
  128. package/lib/typescript/components/Card/index.d.ts +14 -0
  129. package/lib/typescript/components/Card/index.d.ts.map +1 -1
  130. package/lib/typescript/components/Chip/index.d.ts +1 -1
  131. package/lib/typescript/components/CurrencyInput/index.d.ts +2 -2
  132. package/lib/typescript/components/CurrencyInput/index.d.ts.map +1 -1
  133. package/lib/typescript/components/Dialog/index.d.ts +10 -0
  134. package/lib/typescript/components/Dialog/index.d.ts.map +1 -1
  135. package/lib/typescript/components/Dialog/styles.d.ts +1 -0
  136. package/lib/typescript/components/Dialog/styles.d.ts.map +1 -1
  137. package/lib/typescript/components/Divider/index.d.ts +5 -0
  138. package/lib/typescript/components/Divider/index.d.ts.map +1 -1
  139. package/lib/typescript/components/Drawer/styles.d.ts +1 -1
  140. package/lib/typescript/components/Form/styles.d.ts.map +1 -1
  141. package/lib/typescript/components/FullScreenDialog/animation.d.ts +9 -0
  142. package/lib/typescript/components/FullScreenDialog/animation.d.ts.map +1 -0
  143. package/lib/typescript/components/FullScreenDialog/index.d.ts +85 -0
  144. package/lib/typescript/components/FullScreenDialog/index.d.ts.map +1 -0
  145. package/lib/typescript/components/FullScreenDialog/styles.d.ts +6 -0
  146. package/lib/typescript/components/FullScreenDialog/styles.d.ts.map +1 -0
  147. package/lib/typescript/components/IconButton/styles.d.ts +5 -0
  148. package/lib/typescript/components/IconButton/styles.d.ts.map +1 -1
  149. package/lib/typescript/components/Input/index.d.ts +1 -1
  150. package/lib/typescript/components/InsuranceDisclosure/index.d.ts +1 -0
  151. package/lib/typescript/components/InsuranceDisclosure/index.d.ts.map +1 -1
  152. package/lib/typescript/components/Link/index.d.ts +2 -0
  153. package/lib/typescript/components/Link/index.d.ts.map +1 -1
  154. package/lib/typescript/components/ListItem/index.d.ts +43 -4
  155. package/lib/typescript/components/ListItem/index.d.ts.map +1 -1
  156. package/lib/typescript/components/ListItem/styles.d.ts +2 -0
  157. package/lib/typescript/components/ListItem/styles.d.ts.map +1 -1
  158. package/lib/typescript/components/ProgressSegmented/index.d.ts +10 -3
  159. package/lib/typescript/components/ProgressSegmented/index.d.ts.map +1 -1
  160. package/lib/typescript/components/Radio/index.d.ts +1 -0
  161. package/lib/typescript/components/Radio/index.d.ts.map +1 -1
  162. package/lib/typescript/components/ScopedTheme/index.d.ts +11 -3
  163. package/lib/typescript/components/ScopedTheme/index.d.ts.map +1 -1
  164. package/lib/typescript/components/Skeleton/index.d.ts +5 -1
  165. package/lib/typescript/components/Skeleton/index.d.ts.map +1 -1
  166. package/lib/typescript/components/Stack/HStack.d.ts +6 -0
  167. package/lib/typescript/components/Stack/HStack.d.ts.map +1 -1
  168. package/lib/typescript/components/Stack/VStack.d.ts +6 -0
  169. package/lib/typescript/components/Stack/VStack.d.ts.map +1 -1
  170. package/lib/typescript/components/Switch/index.d.ts +1 -1
  171. package/lib/typescript/components/Tabs/index.d.ts +2 -2
  172. package/lib/typescript/components/Textarea/index.d.ts +1 -0
  173. package/lib/typescript/components/Textarea/index.d.ts.map +1 -1
  174. package/lib/typescript/components/Tile/index.d.ts +49 -0
  175. package/lib/typescript/components/Tile/index.d.ts.map +1 -1
  176. package/lib/typescript/components/Toast/index.d.ts +15 -0
  177. package/lib/typescript/components/Toast/index.d.ts.map +1 -1
  178. package/lib/typescript/components/Toast/styles.d.ts +10 -0
  179. package/lib/typescript/components/Toast/styles.d.ts.map +1 -1
  180. package/lib/typescript/components/Toast/types.d.ts +9 -0
  181. package/lib/typescript/components/Toast/types.d.ts.map +1 -1
  182. package/lib/typescript/components/ToggleButton/index.d.ts +78 -0
  183. package/lib/typescript/components/ToggleButton/index.d.ts.map +1 -0
  184. package/lib/typescript/components/ToggleButton/styles.d.ts +30 -0
  185. package/lib/typescript/components/ToggleButton/styles.d.ts.map +1 -0
  186. package/lib/typescript/components/index.d.ts +2 -0
  187. package/lib/typescript/components/index.d.ts.map +1 -1
  188. package/lib/typescript/components/picker/styles.d.ts +5 -2
  189. package/lib/typescript/components/picker/styles.d.ts.map +1 -1
  190. package/lib/typescript/components/sheet-header/index.d.ts +24 -0
  191. package/lib/typescript/components/sheet-header/index.d.ts.map +1 -0
  192. package/lib/typescript/components/sheet-header/styles.d.ts +12 -0
  193. package/lib/typescript/components/sheet-header/styles.d.ts.map +1 -0
  194. package/lib/typescript/providers/toast/provider.d.ts +1 -1
  195. package/lib/typescript/providers/toast/provider.d.ts.map +1 -1
  196. package/lib/typescript/providers/toast/types.d.ts +16 -4
  197. package/lib/typescript/providers/toast/types.d.ts.map +1 -1
  198. package/package.json +5 -5
  199. package/src/components/Alert/index.tsx +4 -1
  200. package/src/components/Avatar/index.tsx +10 -4
  201. package/src/components/Badge/index.tsx +34 -0
  202. package/src/components/Badge/styles.ts +2 -2
  203. package/src/components/BottomSheet/index.tsx +156 -125
  204. package/src/components/BottomSheet/styles.ts +1 -22
  205. package/src/components/Button/index.tsx +9 -1
  206. package/src/components/Card/index.tsx +14 -0
  207. package/src/components/Chip/index.tsx +1 -1
  208. package/src/components/CurrencyInput/index.tsx +2 -2
  209. package/src/components/Dialog/index.tsx +10 -0
  210. package/src/components/Dialog/styles.ts +1 -0
  211. package/src/components/Divider/index.tsx +5 -0
  212. package/src/components/Form/styles.ts +1 -1
  213. package/src/components/FullScreenDialog/animation.ts +22 -0
  214. package/src/components/FullScreenDialog/index.tsx +484 -0
  215. package/src/components/FullScreenDialog/styles.ts +23 -0
  216. package/src/components/IconButton/styles.ts +7 -4
  217. package/src/components/Input/index.tsx +1 -1
  218. package/src/components/InsuranceDisclosure/index.tsx +1 -0
  219. package/src/components/Link/index.tsx +2 -0
  220. package/src/components/ListItem/index.tsx +48 -4
  221. package/src/components/ListItem/styles.ts +7 -0
  222. package/src/components/ProgressSegmented/index.tsx +10 -3
  223. package/src/components/Radio/index.tsx +1 -0
  224. package/src/components/ScopedTheme/index.tsx +11 -3
  225. package/src/components/Skeleton/index.tsx +5 -1
  226. package/src/components/Stack/HStack.tsx +6 -0
  227. package/src/components/Stack/VStack.tsx +6 -0
  228. package/src/components/Switch/index.tsx +1 -1
  229. package/src/components/Tabs/index.tsx +2 -2
  230. package/src/components/Textarea/index.tsx +1 -0
  231. package/src/components/Tile/index.tsx +49 -0
  232. package/src/components/Toast/index.tsx +55 -9
  233. package/src/components/Toast/styles.ts +14 -3
  234. package/src/components/Toast/types.ts +9 -0
  235. package/src/components/ToggleButton/index.tsx +346 -0
  236. package/src/components/ToggleButton/styles.ts +191 -0
  237. package/src/components/index.ts +2 -0
  238. package/src/components/picker/styles.ts +6 -2
  239. package/src/components/sheet-header/index.tsx +148 -0
  240. package/src/components/sheet-header/styles.ts +28 -0
  241. package/src/providers/toast/provider.tsx +47 -13
  242. package/src/providers/toast/types.ts +16 -4
@@ -0,0 +1,484 @@
1
+ import {
2
+ createContext,
3
+ forwardRef,
4
+ useCallback,
5
+ useContext,
6
+ useEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ type ReactNode,
11
+ type RefObject,
12
+ } from 'react';
13
+ import {
14
+ AccessibilityInfo,
15
+ Platform,
16
+ useWindowDimensions,
17
+ View,
18
+ type ViewProps,
19
+ } from 'react-native';
20
+ import Animated from 'react-native-reanimated';
21
+ import { SafeAreaInsetsContext, type EdgeInsets } from 'react-native-safe-area-context';
22
+ import {
23
+ DialogContent as DialogContentPrimitive,
24
+ DialogPortal as DialogPortalPrimitive,
25
+ DialogRoot as DialogRootPrimitive,
26
+ DialogTrigger as DialogTriggerPrimitive,
27
+ MODAL_OVERLAY_Z_INDEX,
28
+ Slot,
29
+ dataAttributes,
30
+ mergeDataAttributes,
31
+ useDialog,
32
+ type IDialogContentProps,
33
+ type IDialogRootProps,
34
+ type IDialogTriggerProps,
35
+ } from '@cdx-ui/primitives';
36
+ import { cn, mergeRefs } from '@cdx-ui/utils';
37
+ import { useCombinedAnimationDisabledState, type AnimationRootDisableAll } from '../../animation';
38
+ import {
39
+ AnimationSettingsProvider,
40
+ useAnimationSettings,
41
+ } from '../../providers/animation-settings';
42
+ import { BottomSheet } from '../BottomSheet';
43
+ import { SheetHeader, type SheetHeaderTitleRef } from '../sheet-header';
44
+ import { WEB_TRANSITION_SAFETY_MS, getFullScreenDialogSurfaceMotion } from './animation';
45
+ import {
46
+ fullScreenDialogContentVariants,
47
+ fullScreenDialogHeaderVariants,
48
+ fullScreenDialogPopupVariants,
49
+ fullScreenDialogPortalVariants,
50
+ } from './styles';
51
+
52
+ const IS_WEB = Platform.OS === 'web';
53
+
54
+ type FullScreenDialogDataState = 'entering' | 'open' | 'closed';
55
+
56
+ const ZERO_INSETS: EdgeInsets = { top: 0, bottom: 0, left: 0, right: 0 };
57
+
58
+ function useSafeInsets(): EdgeInsets {
59
+ return useContext(SafeAreaInsetsContext) ?? ZERO_INSETS;
60
+ }
61
+
62
+ // The header's last title / back state for the current open session. Lives on the
63
+ // popup so a header that remounts per step still counts as a step change.
64
+ const StepFocusKeyContext = createContext<RefObject<string | undefined> | null>(null);
65
+
66
+ function focusTitle(node: SheetHeaderTitleRef | null): void {
67
+ if (!node) return;
68
+ if (IS_WEB) {
69
+ (node as unknown as HTMLElement).focus?.();
70
+ } else {
71
+ AccessibilityInfo.sendAccessibilityEvent(node, 'focus');
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Moves focus to the header title when `stepKey` changes during an open session.
77
+ * The first key after open is only recorded, so `FocusScope` auto-focus decides
78
+ * initial focus. An undefined key (custom header content) never moves focus.
79
+ */
80
+ function useStepChangeFocus(
81
+ stepKey: string | undefined,
82
+ titleRef: RefObject<SheetHeaderTitleRef | null>,
83
+ ): void {
84
+ const lastKeyRef = useContext(StepFocusKeyContext);
85
+
86
+ useEffect(() => {
87
+ if (!lastKeyRef || stepKey === undefined) return;
88
+ const previousKey = lastKeyRef.current;
89
+ lastKeyRef.current = stepKey;
90
+ if (previousKey === undefined || previousKey === stepKey) return;
91
+
92
+ if (IS_WEB) {
93
+ focusTitle(titleRef.current);
94
+ return;
95
+ }
96
+ // Native: wait a frame so the new title is in the accessibility tree.
97
+ let fired = false;
98
+ const frame = requestAnimationFrame(() => {
99
+ fired = true;
100
+ focusTitle(titleRef.current);
101
+ });
102
+ return () => {
103
+ if (fired) return;
104
+ cancelAnimationFrame(frame);
105
+ // Re-arm the pending move for the next run (StrictMode remounts, rapid changes).
106
+ lastKeyRef.current = previousKey;
107
+ };
108
+ }, [lastKeyRef, stepKey, titleRef]);
109
+ }
110
+
111
+ // =============================================================================
112
+ // STYLED ROOT
113
+ // =============================================================================
114
+
115
+ export interface FullScreenDialogRootProps
116
+ extends
117
+ Omit<ViewProps, 'children' | 'role'>,
118
+ Omit<
119
+ IDialogRootProps,
120
+ | 'closeOnBackdropPress'
121
+ | 'role'
122
+ | 'onOpenChange'
123
+ | 'defaultOpen'
124
+ | 'closeOnEscKey'
125
+ | 'children'
126
+ > {
127
+ /**
128
+ * Called with the next open state. Fires `false` for the header close button, Escape, and
129
+ * Android back while the header has no `onBack`.
130
+ */
131
+ onOpenChange?: (open: boolean) => void;
132
+ /** Initial open state when uncontrolled. @default false */
133
+ defaultOpen?: boolean;
134
+ /**
135
+ * Close when Escape is pressed. Web only. Escape never steps back; Android back is routed
136
+ * by the header's `onBack` independently of this prop.
137
+ * @default true
138
+ */
139
+ closeOnEscKey?: boolean;
140
+ /** `FullScreenDialog.Trigger` and `FullScreenDialog.Popup`. */
141
+ children: ReactNode;
142
+ /** Classes for the root wrapper around the trigger. */
143
+ className?: string;
144
+ /** Pass `'disable-all'` to turn off the popup's enter and exit motion. */
145
+ animation?: AnimationRootDisableAll;
146
+ }
147
+
148
+ const FullScreenDialogRoot = forwardRef<View, FullScreenDialogRootProps>(
149
+ ({ animation, className, children, style, ...props }, ref) => {
150
+ const isAllAnimationsDisabled = useCombinedAnimationDisabledState(animation);
151
+ const animationSettingsContextValue = useMemo(
152
+ () => ({ isAllAnimationsDisabled }),
153
+ [isAllAnimationsDisabled],
154
+ );
155
+
156
+ return (
157
+ <AnimationSettingsProvider value={animationSettingsContextValue}>
158
+ <DialogRootPrimitive
159
+ ref={ref}
160
+ className={className}
161
+ style={style}
162
+ {...props}
163
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'full-screen-dialog' }))}
164
+ >
165
+ {children}
166
+ </DialogRootPrimitive>
167
+ </AnimationSettingsProvider>
168
+ );
169
+ },
170
+ );
171
+
172
+ FullScreenDialogRoot.displayName = 'FullScreenDialog';
173
+
174
+ // =============================================================================
175
+ // STYLED TRIGGER
176
+ // =============================================================================
177
+
178
+ export interface FullScreenDialogTriggerProps extends Omit<IDialogTriggerProps, 'asChild'> {
179
+ /** Render the single child as the trigger instead of a `Pressable`. */
180
+ asChild?: boolean;
181
+ /** Classes for the trigger. */
182
+ className?: string;
183
+ /** Content that opens the dialog when pressed. */
184
+ children?: ReactNode;
185
+ }
186
+
187
+ const FullScreenDialogTrigger = forwardRef<View, FullScreenDialogTriggerProps>(
188
+ ({ className, children, style, ...props }, ref) => (
189
+ <DialogTriggerPrimitive
190
+ ref={ref}
191
+ className={className}
192
+ style={style}
193
+ {...props}
194
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'full-screen-dialog-trigger' }))}
195
+ >
196
+ {children}
197
+ </DialogTriggerPrimitive>
198
+ ),
199
+ );
200
+
201
+ FullScreenDialogTrigger.displayName = 'FullScreenDialog.Trigger';
202
+
203
+ // =============================================================================
204
+ // STYLED POPUP (portal + full-viewport page in the app window)
205
+ // =============================================================================
206
+
207
+ export interface FullScreenDialogPopupProps extends Omit<
208
+ IDialogContentProps,
209
+ 'asChild' | 'forceMount' | 'avoidKeyboard' | 'keyboardVerticalOffset'
210
+ > {
211
+ /** Classes for the page surface, merged after the defaults. */
212
+ className?: string;
213
+ /** `FullScreenDialog.Header` and `FullScreenDialog.Content`. Bottom sheets declared here present above the page. */
214
+ children?: ReactNode;
215
+ }
216
+
217
+ const FullScreenDialogPopup = forwardRef<View, FullScreenDialogPopupProps>(
218
+ ({ className, children, style, ...props }, ref) => {
219
+ const { open } = useDialog();
220
+ const animationSettings = useAnimationSettings();
221
+ const { isAllAnimationsDisabled } = animationSettings;
222
+ const { height: windowHeight } = useWindowDimensions();
223
+ const surfaceMotion = useMemo(
224
+ () => getFullScreenDialogSurfaceMotion(windowHeight),
225
+ [windowHeight],
226
+ );
227
+
228
+ // Web enter/exit lifecycle mirrors `Dialog.Popup`: mount → 'entering' →
229
+ // (rAF) → 'open' → (close) → 'closed' → (transitionend) → unmount. The exit
230
+ // flag is derived during render so the popup stays mounted in the same
231
+ // render that sees `open` go false.
232
+ const popupRef = useRef<View | null>(null);
233
+ const prevOpenRef = useRef(open);
234
+ const [isExiting, setIsExiting] = useState(false);
235
+ const [webState, setWebState] = useState<FullScreenDialogDataState>(
236
+ open ? 'entering' : 'closed',
237
+ );
238
+
239
+ if (IS_WEB && prevOpenRef.current && !open && !isExiting) {
240
+ setIsExiting(true);
241
+ setWebState('closed');
242
+ }
243
+ if (IS_WEB && !prevOpenRef.current && open) {
244
+ setIsExiting(false);
245
+ setWebState('entering');
246
+ }
247
+ prevOpenRef.current = open;
248
+
249
+ const stepFocusKeyRef = useRef<string | undefined>(undefined);
250
+ useEffect(() => {
251
+ if (!open) stepFocusKeyRef.current = undefined;
252
+ }, [open]);
253
+
254
+ useEffect(() => {
255
+ if (!IS_WEB || !open) return;
256
+ const raf = requestAnimationFrame(() => {
257
+ setWebState('open');
258
+ });
259
+ return () => cancelAnimationFrame(raf);
260
+ }, [open]);
261
+
262
+ useEffect(() => {
263
+ if (!IS_WEB || !isExiting) return;
264
+
265
+ const node = popupRef.current as unknown as HTMLElement | null;
266
+ let done = false;
267
+
268
+ const finish = () => {
269
+ if (done) return;
270
+ done = true;
271
+ setIsExiting(false);
272
+ };
273
+
274
+ const safetyTimer = setTimeout(finish, WEB_TRANSITION_SAFETY_MS);
275
+
276
+ if (!node || typeof node.addEventListener !== 'function') {
277
+ return () => {
278
+ clearTimeout(safetyTimer);
279
+ };
280
+ }
281
+
282
+ const handler = (event: Event) => {
283
+ if (event.target !== node) return;
284
+ const propertyName = (event as unknown as { propertyName?: string }).propertyName ?? '';
285
+ if (propertyName === 'opacity' || propertyName === 'transform') {
286
+ finish();
287
+ }
288
+ };
289
+ node.addEventListener('transitionend', handler);
290
+
291
+ return () => {
292
+ clearTimeout(safetyTimer);
293
+ node.removeEventListener('transitionend', handler);
294
+ };
295
+ }, [isExiting]);
296
+
297
+ const shouldMount = IS_WEB ? open || isExiting : open;
298
+
299
+ if (!shouldMount) {
300
+ return null;
301
+ }
302
+
303
+ const isNativeMotion = !IS_WEB && !isAllAnimationsDisabled;
304
+
305
+ return (
306
+ <DialogPortalPrimitive forceMount>
307
+ <AnimationSettingsProvider value={animationSettings}>
308
+ {/*
309
+ No `FullWindowOverlay`: the page lives in the app window so native
310
+ modals (Menu, Select, picker popover) present above it. Without the
311
+ overlay window, VoiceOver containment comes from this wrapper, a
312
+ native sibling of the app tree under the root `PortalHost`.
313
+ */}
314
+ <Animated.View
315
+ className={fullScreenDialogPortalVariants()}
316
+ style={{ zIndex: MODAL_OVERLAY_Z_INDEX }}
317
+ entering={isNativeMotion ? surfaceMotion.entering : undefined}
318
+ exiting={isNativeMotion ? surfaceMotion.exiting : undefined}
319
+ {...(IS_WEB ? undefined : { accessibilityViewIsModal: true })}
320
+ >
321
+ <DialogContentPrimitive
322
+ ref={mergeRefs(ref, popupRef)}
323
+ forceMount
324
+ className={cn(fullScreenDialogPopupVariants(), className)}
325
+ style={style}
326
+ {...props}
327
+ {...mergeDataAttributes(
328
+ props,
329
+ dataAttributes({ slot: 'full-screen-dialog-popup' }),
330
+ IS_WEB ? dataAttributes({ state: webState }) : undefined,
331
+ )}
332
+ >
333
+ <StepFocusKeyContext.Provider value={stepFocusKeyRef}>
334
+ <BottomSheet.Provider>{children}</BottomSheet.Provider>
335
+ </StepFocusKeyContext.Provider>
336
+ </DialogContentPrimitive>
337
+ </Animated.View>
338
+ </AnimationSettingsProvider>
339
+ </DialogPortalPrimitive>
340
+ );
341
+ },
342
+ );
343
+
344
+ FullScreenDialogPopup.displayName = 'FullScreenDialog.Popup';
345
+
346
+ // =============================================================================
347
+ // STYLED HEADER (shared back / title / close row)
348
+ // =============================================================================
349
+
350
+ export interface FullScreenDialogHeaderProps {
351
+ /** Title text. Labels the dialog. Ignored when `children` is set. */
352
+ title?: string;
353
+ /** Secondary line under the title. Has no effect without `title`, or when `children` is set. */
354
+ subtitle?: string;
355
+ /**
356
+ * Custom header content. Replaces `title` and `subtitle`. The consumer then owns the
357
+ * dialog's accessible name and focus on step change: focus doesn't move to the header.
358
+ */
359
+ children?: ReactNode;
360
+ /** Classes for the header region, which includes the top safe-area padding. */
361
+ className?: string;
362
+ /** Called by the close button. @default Closes the dialog (`onOpenChange(false)`). */
363
+ onClose?: () => void;
364
+ /** Accessible name for the close button. @default Close */
365
+ closeAccessibilityLabel?: string;
366
+ /**
367
+ * Shows the back button when set. Android system back calls it too, instead of
368
+ * closing the dialog. Escape on web always closes.
369
+ */
370
+ onBack?: () => void;
371
+ /** Accessible name for the back button. @default Go back */
372
+ backAccessibilityLabel?: string;
373
+ }
374
+
375
+ const FullScreenDialogHeader = forwardRef<View, FullScreenDialogHeaderProps>(
376
+ (
377
+ {
378
+ title,
379
+ subtitle,
380
+ children,
381
+ className,
382
+ onClose,
383
+ closeAccessibilityLabel,
384
+ onBack,
385
+ backAccessibilityLabel,
386
+ },
387
+ ref,
388
+ ) => {
389
+ const { nativeID, onOpenChange, backHandlerRef } = useDialog();
390
+ const insets = useSafeInsets();
391
+ const titleRef = useRef<SheetHeaderTitleRef | null>(null);
392
+
393
+ const hasTitle = children == null && title !== undefined && title !== '';
394
+ useStepChangeFocus(hasTitle ? `${onBack ? 'back' : 'root'}:${title}` : undefined, titleRef);
395
+
396
+ useEffect(() => {
397
+ backHandlerRef.current = onBack;
398
+ return () => {
399
+ backHandlerRef.current = undefined;
400
+ };
401
+ }, [backHandlerRef, onBack]);
402
+
403
+ const handleClose = useCallback(() => {
404
+ onOpenChange(false);
405
+ }, [onOpenChange]);
406
+
407
+ return (
408
+ <View
409
+ ref={ref}
410
+ className={cn(fullScreenDialogHeaderVariants(), className)}
411
+ style={{ paddingTop: insets.top }}
412
+ {...dataAttributes({ slot: 'full-screen-dialog-header' })}
413
+ >
414
+ <SheetHeader
415
+ title={title}
416
+ subtitle={subtitle}
417
+ titleNativeID={`${nativeID}-title`}
418
+ titleRef={titleRef}
419
+ titleFocusable
420
+ onClose={onClose ?? handleClose}
421
+ closeAccessibilityLabel={closeAccessibilityLabel}
422
+ onBack={onBack}
423
+ backAccessibilityLabel={backAccessibilityLabel}
424
+ >
425
+ {children}
426
+ </SheetHeader>
427
+ </View>
428
+ );
429
+ },
430
+ );
431
+
432
+ FullScreenDialogHeader.displayName = 'FullScreenDialog.Header';
433
+
434
+ // =============================================================================
435
+ // STYLED CONTENT
436
+ // =============================================================================
437
+
438
+ export interface FullScreenDialogContentProps extends ViewProps {
439
+ /** Render the single child as the content region instead of a `View`. */
440
+ asChild?: boolean;
441
+ /** Classes for the content region, merged after the defaults. */
442
+ className?: string;
443
+ /** Page body. Usually a `ScrollView`. */
444
+ children?: ReactNode;
445
+ }
446
+
447
+ const FullScreenDialogContent = forwardRef<View, FullScreenDialogContentProps>(
448
+ ({ asChild, className, children, style, ...props }, ref) => {
449
+ const insets = useSafeInsets();
450
+ const Comp = asChild ? Slot : View;
451
+
452
+ return (
453
+ <Comp
454
+ ref={ref}
455
+ className={cn(fullScreenDialogContentVariants(), className)}
456
+ style={[{ paddingBottom: insets.bottom }, style]}
457
+ {...props}
458
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'full-screen-dialog-content' }))}
459
+ >
460
+ {children}
461
+ </Comp>
462
+ );
463
+ },
464
+ );
465
+
466
+ FullScreenDialogContent.displayName = 'FullScreenDialog.Content';
467
+
468
+ // =============================================================================
469
+ // COMPOUND COMPONENT
470
+ // =============================================================================
471
+
472
+ type FullScreenDialogCompoundComponent = typeof FullScreenDialogRoot & {
473
+ Trigger: typeof FullScreenDialogTrigger;
474
+ Popup: typeof FullScreenDialogPopup;
475
+ Header: typeof FullScreenDialogHeader;
476
+ Content: typeof FullScreenDialogContent;
477
+ };
478
+
479
+ export const FullScreenDialog = Object.assign(FullScreenDialogRoot, {
480
+ Trigger: FullScreenDialogTrigger,
481
+ Popup: FullScreenDialogPopup,
482
+ Header: FullScreenDialogHeader,
483
+ Content: FullScreenDialogContent,
484
+ }) as FullScreenDialogCompoundComponent;
@@ -0,0 +1,23 @@
1
+ import { cva } from '@cdx-ui/utils';
2
+
3
+ export const fullScreenDialogPortalVariants = cva([
4
+ 'absolute inset-0',
5
+ 'overflow-hidden',
6
+ 'web:fixed',
7
+ ]);
8
+
9
+ export const fullScreenDialogPopupVariants = cva([
10
+ 'flex-1 flex-col',
11
+ 'bg-surface-primary',
12
+ 'overflow-hidden',
13
+ 'web:transition-transform web:duration-300 web:ease-[cubic-bezier(0.32,0.72,0,1)]',
14
+ 'web:data-[state=entering]:translate-y-full',
15
+ 'web:data-[state=open]:translate-y-0',
16
+ 'web:data-[state=closed]:translate-y-full web:data-[state=closed]:duration-[220ms]',
17
+ 'web:motion-reduce:transition-none web:motion-reduce:duration-0',
18
+ ]);
19
+
20
+ /** Paints the surface behind the status bar inset above the header row. */
21
+ export const fullScreenDialogHeaderVariants = cva(['shrink-0', 'bg-surface-primary']);
22
+
23
+ export const fullScreenDialogContentVariants = cva(['flex-1 min-h-0']);
@@ -2,6 +2,12 @@ import { Platform } from 'react-native';
2
2
  import { cva, type VariantProps } from '@cdx-ui/utils';
3
3
  import { DISABLED_CURSOR, TRANSITION_COLORS } from '../../styles/constants';
4
4
 
5
+ /** Box classes per size. Shared with layout placeholders that must match an `IconButton`'s footprint. */
6
+ export const iconButtonSizeClassNames = {
7
+ default: 'h-11 w-11 min-w-11 shrink-0 p-2.5',
8
+ small: 'h-8 w-8 min-w-8 shrink-0 p-1.5',
9
+ } as const;
10
+
5
11
  export const iconButtonRootVariants = cva(
6
12
  [
7
13
  'flex items-center justify-center',
@@ -24,10 +30,7 @@ export const iconButtonRootVariants = cva(
24
30
  action: [],
25
31
  danger: [],
26
32
  },
27
- size: {
28
- default: 'h-11 w-11 min-w-11 shrink-0 p-2.5',
29
- small: 'h-8 w-8 min-w-8 shrink-0 p-1.5',
30
- },
33
+ size: iconButtonSizeClassNames,
31
34
  },
32
35
  compoundVariants: [
33
36
  // ── solid × color ───────────────────────────────────────────────────
@@ -42,7 +42,7 @@ export interface InputProps extends ViewProps, IInputFieldProps {
42
42
  */
43
43
  variant?: 'outline';
44
44
  /**
45
- * Height and padding scale.
45
+ * Height, padding, and field font size.
46
46
  * @default 'default'
47
47
  */
48
48
  size?: 'default' | 'small';
@@ -21,6 +21,7 @@ type InsuranceDisclosureSharedProps = Omit<HStackProps, 'children'> & {
21
21
  * Pass `framed` to restore chrome; padding is now `px-3 py-2`.
22
22
  */
23
23
  framed?: boolean;
24
+ /** Disclosure copy rendered beside the logo. */
24
25
  label?: string;
25
26
  className?: string;
26
27
  };
@@ -24,6 +24,7 @@ export { LinkProvider, type LinkConfig, type LinkAction } from '@cdx-ui/primitiv
24
24
  // =============================================================================
25
25
 
26
26
  export interface LinkProps extends ILinkProps {
27
+ /** Renders the consumer's child element in place of the default pressable host. */
27
28
  asChild?: boolean;
28
29
  className?: string;
29
30
  children?: ReactNode;
@@ -52,6 +53,7 @@ LinkRoot.displayName = 'Link';
52
53
  // =============================================================================
53
54
 
54
55
  export interface LinkLabelProps extends TextProps {
56
+ /** Renders the consumer's child element in place of the default `Text` host. */
55
57
  asChild?: boolean;
56
58
  className?: string;
57
59
  children?: ReactNode;