@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,148 @@
1
+ import { forwardRef, type ComponentRef, type ReactNode, type Ref } from 'react';
2
+ import { Platform, View, type ViewProps } from 'react-native';
3
+ import { cn } from '@cdx-ui/utils';
4
+ import { ArrowBack, Close, type ForgeIcon } from '@cdx-ui/icons';
5
+ import { Heading } from '../Heading';
6
+ import { IconButton } from '../IconButton';
7
+ import { Text } from '../Text';
8
+ import {
9
+ SHEET_HEADER_BUTTON_SPACER,
10
+ SHEET_HEADER_DEFAULT_BACKGROUND,
11
+ SHEET_HEADER_FOCUSABLE_TITLE,
12
+ sheetHeaderVariants,
13
+ } from './styles';
14
+
15
+ // Shared back / title / close header row for sheet-style surfaces. Not exported from the package.
16
+
17
+ const WEB_PROGRAMMATIC_FOCUS = Platform.OS === 'web' ? { tabIndex: -1 } : undefined;
18
+
19
+ export type SheetHeaderTitleRef = ComponentRef<typeof Heading>;
20
+
21
+ export interface SheetHeaderProps extends Omit<ViewProps, 'children'> {
22
+ title?: string;
23
+ subtitle?: string;
24
+ children?: ReactNode;
25
+ className?: string;
26
+ onClose?: () => void;
27
+ closeAccessibilityLabel?: string;
28
+ onBack?: () => void;
29
+ backAccessibilityLabel?: string;
30
+ /** `nativeID` for the default title, so a host surface can point `aria-labelledby` at it. */
31
+ titleNativeID?: string;
32
+ /** Ref to the default title. Not attached when `children` replaces the title. */
33
+ titleRef?: Ref<SheetHeaderTitleRef>;
34
+ /** Lets the host move focus to the default title. On web it gets `tabIndex={-1}`, so it stays out of the tab order. */
35
+ titleFocusable?: boolean;
36
+ /** Replaces the default header surface class. */
37
+ backgroundClassName?: string;
38
+ }
39
+
40
+ export const SheetHeader = forwardRef<View, SheetHeaderProps>(
41
+ (
42
+ {
43
+ title,
44
+ subtitle,
45
+ children,
46
+ className,
47
+ onClose,
48
+ closeAccessibilityLabel = 'Close',
49
+ onBack,
50
+ backAccessibilityLabel = 'Go back',
51
+ titleNativeID,
52
+ titleRef,
53
+ titleFocusable = false,
54
+ backgroundClassName = SHEET_HEADER_DEFAULT_BACKGROUND,
55
+ ...props
56
+ },
57
+ ref,
58
+ ) => {
59
+ const hasTitle = title !== undefined && title !== '';
60
+ const hasContent = hasTitle || !!children || !!onBack;
61
+ const computedClassName = cn(
62
+ sheetHeaderVariants({ hasTitle: hasContent }),
63
+ backgroundClassName,
64
+ className,
65
+ );
66
+
67
+ return (
68
+ <View ref={ref} className={computedClassName} {...props}>
69
+ {onBack ? (
70
+ <SheetHeaderButton
71
+ as={ArrowBack}
72
+ onPress={onBack}
73
+ accessibilityLabel={backAccessibilityLabel}
74
+ />
75
+ ) : (
76
+ <SheetHeaderButtonSpacer />
77
+ )}
78
+ <View className="flex-1">
79
+ {children ??
80
+ (hasTitle ? (
81
+ <View>
82
+ <Heading
83
+ ref={titleRef}
84
+ size="sm"
85
+ className={cn('text-center', titleFocusable && SHEET_HEADER_FOCUSABLE_TITLE)}
86
+ nativeID={titleNativeID}
87
+ {...(titleFocusable ? WEB_PROGRAMMATIC_FOCUS : undefined)}
88
+ >
89
+ {title}
90
+ </Heading>
91
+ {subtitle ? (
92
+ <Text size="sm" className="text-center text-content-secondary">
93
+ {subtitle}
94
+ </Text>
95
+ ) : null}
96
+ </View>
97
+ ) : null)}
98
+ </View>
99
+ {onClose ? (
100
+ <SheetHeaderButton
101
+ as={Close}
102
+ onPress={onClose}
103
+ accessibilityLabel={closeAccessibilityLabel}
104
+ />
105
+ ) : (
106
+ <SheetHeaderButtonSpacer />
107
+ )}
108
+ </View>
109
+ );
110
+ },
111
+ );
112
+
113
+ SheetHeader.displayName = 'SheetHeader';
114
+
115
+ interface SheetHeaderButtonProps {
116
+ as: ForgeIcon;
117
+ onPress: () => void;
118
+ accessibilityLabel: string;
119
+ }
120
+
121
+ function SheetHeaderButton({ as, onPress, accessibilityLabel }: Readonly<SheetHeaderButtonProps>) {
122
+ return (
123
+ <IconButton
124
+ as={as}
125
+ variant="solid"
126
+ color="neutral"
127
+ size="default"
128
+ onPress={onPress}
129
+ hitSlop={12}
130
+ accessibilityLabel={accessibilityLabel}
131
+ />
132
+ );
133
+ }
134
+
135
+ SheetHeaderButton.displayName = 'SheetHeader.Button';
136
+
137
+ function SheetHeaderButtonSpacer() {
138
+ return (
139
+ <View
140
+ className={SHEET_HEADER_BUTTON_SPACER}
141
+ accessibilityElementsHidden
142
+ importantForAccessibility="no-hide-descendants"
143
+ aria-hidden
144
+ />
145
+ );
146
+ }
147
+
148
+ SheetHeaderButtonSpacer.displayName = 'SheetHeader.ButtonSpacer';
@@ -0,0 +1,28 @@
1
+ import { cva, type VariantProps } from '@cdx-ui/utils';
2
+ import { iconButtonSizeClassNames } from '../IconButton/styles';
3
+
4
+ /** Surface behind the header row unless the host passes `backgroundClassName`. */
5
+ export const SHEET_HEADER_DEFAULT_BACKGROUND = 'bg-surface-primary';
6
+
7
+ /** Holds an absent back or close button's place so the title stays centered. */
8
+ export const SHEET_HEADER_BUTTON_SPACER = iconButtonSizeClassNames.default;
9
+
10
+ /** The title only takes programmatic focus, so it never shows a focus ring. */
11
+ export const SHEET_HEADER_FOCUSABLE_TITLE = 'web:outline-none';
12
+
13
+ export const sheetHeaderVariants = cva(
14
+ ['flex-row items-center justify-between min-h-11 px-4 pt-3 pb-2.5', 'text-content-primary'],
15
+ {
16
+ variants: {
17
+ hasTitle: {
18
+ true: '',
19
+ false: 'justify-end',
20
+ },
21
+ },
22
+ defaultVariants: {
23
+ hasTitle: true,
24
+ },
25
+ },
26
+ );
27
+
28
+ export type SheetHeaderVariants = VariantProps<typeof sheetHeaderVariants>;
@@ -177,6 +177,7 @@ export function ToastProvider({
177
177
  disableFullWindowOverlay = false,
178
178
  unstable_accessibilityContainerViewIsModal,
179
179
  announceLabel,
180
+ container,
180
181
  }: Readonly<ToastProviderProps>) {
181
182
  const [toasts, dispatch] = useReducer(toastReducer, []);
182
183
 
@@ -185,7 +186,19 @@ export function ToastProvider({
185
186
  */
186
187
  const globalConfig = useMemo<ToastGlobalConfig | undefined>(() => defaultProps, [defaultProps]);
187
188
 
188
- const isToastVisible = toasts.length > 0;
189
+ const usesWebPortal =
190
+ Platform.OS === 'web' && createPortalFn !== undefined && typeof document !== 'undefined';
191
+
192
+ /**
193
+ * A `null` container renders no viewport, so no toast can play its exit
194
+ * transition or be seen. Timers hold until it attaches, and hides remove
195
+ * directly instead of waiting on a transition that will never run.
196
+ */
197
+ const isViewportDetached = usesWebPortal && container === null;
198
+ const viewportDetachedRef = useRef(isViewportDetached);
199
+ viewportDetachedRef.current = isViewportDetached;
200
+
201
+ const isToastVisible = toasts.length > 0 && !isViewportDetached;
189
202
 
190
203
  const placementStacks = useToastPlacementStacks();
191
204
 
@@ -287,12 +300,13 @@ export function ToastProvider({
287
300
  * On web this only marks toasts as `pendingRemoval` so their CSS exit
288
301
  * transition can play; the actual removal happens when the toast
289
302
  * component calls `remove(id)` on transition end.
290
- * On native this dispatches removal directly — Reanimated's `exiting`
291
- * layout animation plays before unmount.
303
+ * On native, and on web while the container is `null`, this dispatches
304
+ * removal directly — Reanimated's `exiting` layout animation plays before
305
+ * unmount, and a detached web viewport has no transition to wait on.
292
306
  */
293
307
  const hide = useCallback(
294
308
  (ids?: (string & {}) | string[] | 'all') => {
295
- const isWeb = Platform.OS === 'web';
309
+ const playsWebExit = Platform.OS === 'web' && !viewportDetachedRef.current;
296
310
 
297
311
  if (ids === undefined) {
298
312
  if (toasts.length === 0) return;
@@ -318,7 +332,7 @@ export function ToastProvider({
318
332
  lastToast.onHide();
319
333
  }
320
334
 
321
- if (isWeb) {
335
+ if (playsWebExit) {
322
336
  dispatch({ type: 'MARK_CLOSED', payload: { ids: [lastToast.id] } });
323
337
  } else {
324
338
  remove([lastToast.id]);
@@ -340,7 +354,7 @@ export function ToastProvider({
340
354
 
341
355
  if (targetIds.length === 0) return;
342
356
 
343
- if (isWeb) {
357
+ if (playsWebExit) {
344
358
  dispatch({ type: 'MARK_CLOSED', payload: { ids: targetIds } });
345
359
  } else {
346
360
  remove(targetIds);
@@ -367,7 +381,7 @@ export function ToastProvider({
367
381
 
368
382
  if (targetIds.length === 0) return;
369
383
 
370
- if (isWeb) {
384
+ if (playsWebExit) {
371
385
  dispatch({ type: 'MARK_CLOSED', payload: { ids: targetIds } });
372
386
  } else {
373
387
  remove(targetIds);
@@ -404,6 +418,7 @@ export function ToastProvider({
404
418
  */
405
419
  const resumeAll = useCallback(() => {
406
420
  pausedRef.current = false;
421
+ if (viewportDetachedRef.current) return;
407
422
 
408
423
  const now = Date.now();
409
424
  timersRef.current.forEach((entry, id) => {
@@ -421,6 +436,24 @@ export function ToastProvider({
421
436
  });
422
437
  }, []);
423
438
 
439
+ useEffect(() => {
440
+ if (isViewportDetached) {
441
+ pauseAll();
442
+ } else {
443
+ resumeAll();
444
+ }
445
+ }, [isViewportDetached, pauseAll, resumeAll]);
446
+
447
+ /**
448
+ * Detaching the container unmounts toasts mid-exit, and an unmounted toast
449
+ * never reports its transition end.
450
+ */
451
+ useEffect(() => {
452
+ if (!isViewportDetached) return;
453
+ const pendingIds = toasts.filter((toast) => toast.pendingRemoval).map((toast) => toast.id);
454
+ if (pendingIds.length > 0) remove(pendingIds);
455
+ }, [isViewportDetached, remove, toasts]);
456
+
424
457
  const lifecycleContextValue = useMemo<ToastLifecycleContextValue>(() => {
425
458
  const pending = new Set<string>();
426
459
  const frontmostByPlacement = new Map<ToastPlacement, string>();
@@ -541,7 +574,7 @@ export function ToastProvider({
541
574
  };
542
575
  timersRef.current.set(id, entry);
543
576
 
544
- if (pausedRef.current) {
577
+ if (pausedRef.current || viewportDetachedRef.current) {
545
578
  clearTimeout(timer);
546
579
  entry.timer = null;
547
580
  }
@@ -567,7 +600,7 @@ export function ToastProvider({
567
600
  );
568
601
 
569
602
  useOverlayStackRegistration({
570
- active: toasts.length > 0,
603
+ active: isToastVisible,
571
604
  capabilities: [],
572
605
  id: 'forge-ui-toast-viewport',
573
606
  groupId: 'forge-ui-notifications',
@@ -619,10 +652,11 @@ export function ToastProvider({
619
652
  </InsetsContainer>
620
653
  ) : null;
621
654
 
622
- const renderedToastViewport =
623
- Platform.OS === 'web' && toastViewport && createPortalFn && typeof document !== 'undefined'
624
- ? createPortalFn(toastViewport, document.body)
625
- : toastViewport;
655
+ let renderedToastViewport: React.ReactNode = toastViewport;
656
+ if (usesWebPortal && toastViewport && createPortalFn) {
657
+ renderedToastViewport =
658
+ container === null ? null : createPortalFn(toastViewport, container ?? document.body);
659
+ }
626
660
 
627
661
  return (
628
662
  <ToastConfigContext.Provider value={globalConfig}>
@@ -105,6 +105,16 @@ export interface ToastProviderProps {
105
105
  * @platform web
106
106
  */
107
107
  announceLabel?: string;
108
+ /**
109
+ * Element that receives the toast viewport instead of `document.body`.
110
+ *
111
+ * Use it to keep toasts inside an embedded surface, such as a device preview. The fixed
112
+ * viewport stays inside the container only when an ancestor of it creates a containing
113
+ * block for fixed descendants, for example through a `transform`. While `null`, such as
114
+ * before a callback ref attaches, no viewport renders.
115
+ * @platform web
116
+ */
117
+ container?: Element | null;
108
118
  /**
109
119
  * Children to render
110
120
  */
@@ -170,8 +180,9 @@ export interface ToastShowConfig extends Pick<
170
180
  */
171
181
  duration?: number | 'persistent';
172
182
  /**
173
- * Optional ID for the toast
174
- * If not provided, one will be generated automatically
183
+ * Optional ID for the toast.
184
+ * If omitted, one is generated. If a toast with this id is already in the
185
+ * stack, `show` returns that id and does not add another.
175
186
  */
176
187
  id?: string;
177
188
  /**
@@ -221,8 +232,9 @@ export interface ToastShowConfig extends Pick<
221
232
  */
222
233
  export interface ToastShowOptionsWithComponent {
223
234
  /**
224
- * Optional ID for the toast
225
- * If not provided, one will be generated automatically
235
+ * Optional ID for the toast.
236
+ * If omitted, one is generated. If a toast with this id is already in the
237
+ * stack, `show` returns that id and does not add another.
226
238
  */
227
239
  id?: string;
228
240
  /**