@bacon-pos/ui 0.10.0 → 0.15.1

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 (213) hide show
  1. package/lib/commonjs/components/Alert/Alert.js +137 -0
  2. package/lib/commonjs/components/Alert/Alert.js.map +1 -0
  3. package/lib/commonjs/components/Alert/Alert.stories.js +104 -0
  4. package/lib/commonjs/components/Alert/Alert.stories.js.map +1 -0
  5. package/lib/commonjs/components/Alert/Alert.test.js +62 -0
  6. package/lib/commonjs/components/Alert/Alert.test.js.map +1 -0
  7. package/lib/commonjs/components/Alert/index.js +13 -0
  8. package/lib/commonjs/components/Alert/index.js.map +1 -0
  9. package/lib/commonjs/components/CodeInput/CodeInput.js +28 -9
  10. package/lib/commonjs/components/CodeInput/CodeInput.js.map +1 -1
  11. package/lib/commonjs/components/CodeInput/CodeInput.stories.js +28 -1
  12. package/lib/commonjs/components/CodeInput/CodeInput.stories.js.map +1 -1
  13. package/lib/commonjs/components/CodeInput/CodeInput.test.js +16 -0
  14. package/lib/commonjs/components/CodeInput/CodeInput.test.js.map +1 -1
  15. package/lib/commonjs/components/Dialog/Dialog.js +116 -71
  16. package/lib/commonjs/components/Dialog/Dialog.js.map +1 -1
  17. package/lib/commonjs/components/Dialog/StepDialog.js +111 -0
  18. package/lib/commonjs/components/Dialog/StepDialog.js.map +1 -0
  19. package/lib/commonjs/components/Dialog/StepDialog.stories.js +157 -0
  20. package/lib/commonjs/components/Dialog/StepDialog.stories.js.map +1 -0
  21. package/lib/commonjs/components/Dialog/StepDialog.test.js +118 -0
  22. package/lib/commonjs/components/Dialog/StepDialog.test.js.map +1 -0
  23. package/lib/commonjs/components/Dialog/index.js +7 -0
  24. package/lib/commonjs/components/Dialog/index.js.map +1 -1
  25. package/lib/commonjs/components/Icon/icons.generated.js +9 -1
  26. package/lib/commonjs/components/Icon/icons.generated.js.map +1 -1
  27. package/lib/commonjs/components/List/List.js +45 -0
  28. package/lib/commonjs/components/List/List.js.map +1 -0
  29. package/lib/commonjs/components/List/List.stories.js +215 -0
  30. package/lib/commonjs/components/List/List.stories.js.map +1 -0
  31. package/lib/commonjs/components/List/List.test.js +94 -0
  32. package/lib/commonjs/components/List/List.test.js.map +1 -0
  33. package/lib/commonjs/components/List/ListItem.js +210 -0
  34. package/lib/commonjs/components/List/ListItem.js.map +1 -0
  35. package/lib/commonjs/components/List/index.js +20 -0
  36. package/lib/commonjs/components/List/index.js.map +1 -0
  37. package/lib/commonjs/components/Menu/Menu.js +11 -3
  38. package/lib/commonjs/components/Menu/Menu.js.map +1 -1
  39. package/lib/commonjs/components/ProgressBar/ProgressBar.js +4 -3
  40. package/lib/commonjs/components/ProgressBar/ProgressBar.js.map +1 -1
  41. package/lib/commonjs/components/Sidebar/Sidebar.js +20 -3
  42. package/lib/commonjs/components/Sidebar/Sidebar.js.map +1 -1
  43. package/lib/commonjs/components/Sidebar/Sidebar.stories.js +17 -1
  44. package/lib/commonjs/components/Sidebar/Sidebar.stories.js.map +1 -1
  45. package/lib/commonjs/components/StatTile/StatTile.js +94 -0
  46. package/lib/commonjs/components/StatTile/StatTile.js.map +1 -0
  47. package/lib/commonjs/components/StatTile/StatTile.stories.js +100 -0
  48. package/lib/commonjs/components/StatTile/StatTile.stories.js.map +1 -0
  49. package/lib/commonjs/components/StatTile/StatTile.test.js +49 -0
  50. package/lib/commonjs/components/StatTile/StatTile.test.js.map +1 -0
  51. package/lib/commonjs/components/StatTile/index.js +13 -0
  52. package/lib/commonjs/components/StatTile/index.js.map +1 -0
  53. package/lib/commonjs/components/Table/Table.js +69 -6
  54. package/lib/commonjs/components/Table/Table.js.map +1 -1
  55. package/lib/commonjs/components/Table/Table.stories.js +118 -1
  56. package/lib/commonjs/components/Table/Table.stories.js.map +1 -1
  57. package/lib/commonjs/components/Table/Table.test.js +58 -0
  58. package/lib/commonjs/components/Table/Table.test.js.map +1 -1
  59. package/lib/commonjs/components/_internal/ModalHostContext.js +21 -0
  60. package/lib/commonjs/components/_internal/ModalHostContext.js.map +1 -0
  61. package/lib/commonjs/components/index.js +64 -31
  62. package/lib/commonjs/components/index.js.map +1 -1
  63. package/lib/module/components/Alert/Alert.js +133 -0
  64. package/lib/module/components/Alert/Alert.js.map +1 -0
  65. package/lib/module/components/Alert/Alert.stories.js +100 -0
  66. package/lib/module/components/Alert/Alert.stories.js.map +1 -0
  67. package/lib/module/components/Alert/Alert.test.js +62 -0
  68. package/lib/module/components/Alert/Alert.test.js.map +1 -0
  69. package/lib/module/components/Alert/index.js +4 -0
  70. package/lib/module/components/Alert/index.js.map +1 -0
  71. package/lib/module/components/CodeInput/CodeInput.js +28 -9
  72. package/lib/module/components/CodeInput/CodeInput.js.map +1 -1
  73. package/lib/module/components/CodeInput/CodeInput.stories.js +27 -0
  74. package/lib/module/components/CodeInput/CodeInput.stories.js.map +1 -1
  75. package/lib/module/components/CodeInput/CodeInput.test.js +16 -0
  76. package/lib/module/components/CodeInput/CodeInput.test.js.map +1 -1
  77. package/lib/module/components/Dialog/Dialog.js +116 -71
  78. package/lib/module/components/Dialog/Dialog.js.map +1 -1
  79. package/lib/module/components/Dialog/StepDialog.js +107 -0
  80. package/lib/module/components/Dialog/StepDialog.js.map +1 -0
  81. package/lib/module/components/Dialog/StepDialog.stories.js +153 -0
  82. package/lib/module/components/Dialog/StepDialog.stories.js.map +1 -0
  83. package/lib/module/components/Dialog/StepDialog.test.js +117 -0
  84. package/lib/module/components/Dialog/StepDialog.test.js.map +1 -0
  85. package/lib/module/components/Dialog/index.js +1 -0
  86. package/lib/module/components/Dialog/index.js.map +1 -1
  87. package/lib/module/components/Icon/icons.generated.js +9 -1
  88. package/lib/module/components/Icon/icons.generated.js.map +1 -1
  89. package/lib/module/components/List/List.js +41 -0
  90. package/lib/module/components/List/List.js.map +1 -0
  91. package/lib/module/components/List/List.stories.js +211 -0
  92. package/lib/module/components/List/List.stories.js.map +1 -0
  93. package/lib/module/components/List/List.test.js +94 -0
  94. package/lib/module/components/List/List.test.js.map +1 -0
  95. package/lib/module/components/List/ListItem.js +206 -0
  96. package/lib/module/components/List/ListItem.js.map +1 -0
  97. package/lib/module/components/List/index.js +5 -0
  98. package/lib/module/components/List/index.js.map +1 -0
  99. package/lib/module/components/Menu/Menu.js +12 -4
  100. package/lib/module/components/Menu/Menu.js.map +1 -1
  101. package/lib/module/components/ProgressBar/ProgressBar.js +4 -3
  102. package/lib/module/components/ProgressBar/ProgressBar.js.map +1 -1
  103. package/lib/module/components/Sidebar/Sidebar.js +20 -3
  104. package/lib/module/components/Sidebar/Sidebar.js.map +1 -1
  105. package/lib/module/components/Sidebar/Sidebar.stories.js +16 -0
  106. package/lib/module/components/Sidebar/Sidebar.stories.js.map +1 -1
  107. package/lib/module/components/StatTile/StatTile.js +90 -0
  108. package/lib/module/components/StatTile/StatTile.js.map +1 -0
  109. package/lib/module/components/StatTile/StatTile.stories.js +96 -0
  110. package/lib/module/components/StatTile/StatTile.stories.js.map +1 -0
  111. package/lib/module/components/StatTile/StatTile.test.js +49 -0
  112. package/lib/module/components/StatTile/StatTile.test.js.map +1 -0
  113. package/lib/module/components/StatTile/index.js +4 -0
  114. package/lib/module/components/StatTile/index.js.map +1 -0
  115. package/lib/module/components/Table/Table.js +70 -7
  116. package/lib/module/components/Table/Table.js.map +1 -1
  117. package/lib/module/components/Table/Table.stories.js +117 -0
  118. package/lib/module/components/Table/Table.stories.js.map +1 -1
  119. package/lib/module/components/Table/Table.test.js +58 -0
  120. package/lib/module/components/Table/Table.test.js.map +1 -1
  121. package/lib/module/components/_internal/ModalHostContext.js +17 -0
  122. package/lib/module/components/_internal/ModalHostContext.js.map +1 -0
  123. package/lib/module/components/index.js +3 -0
  124. package/lib/module/components/index.js.map +1 -1
  125. package/lib/typescript/commonjs/components/Alert/Alert.d.ts +41 -0
  126. package/lib/typescript/commonjs/components/Alert/Alert.d.ts.map +1 -0
  127. package/lib/typescript/commonjs/components/Alert/index.d.ts +3 -0
  128. package/lib/typescript/commonjs/components/Alert/index.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/components/CodeInput/CodeInput.d.ts +5 -1
  130. package/lib/typescript/commonjs/components/CodeInput/CodeInput.d.ts.map +1 -1
  131. package/lib/typescript/commonjs/components/Dialog/Dialog.d.ts +13 -1
  132. package/lib/typescript/commonjs/components/Dialog/Dialog.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/components/Dialog/StepDialog.d.ts +63 -0
  134. package/lib/typescript/commonjs/components/Dialog/StepDialog.d.ts.map +1 -0
  135. package/lib/typescript/commonjs/components/Dialog/index.d.ts +1 -0
  136. package/lib/typescript/commonjs/components/Dialog/index.d.ts.map +1 -1
  137. package/lib/typescript/commonjs/components/Icon/icons.generated.d.ts +8 -0
  138. package/lib/typescript/commonjs/components/Icon/icons.generated.d.ts.map +1 -1
  139. package/lib/typescript/commonjs/components/List/List.d.ts +22 -0
  140. package/lib/typescript/commonjs/components/List/List.d.ts.map +1 -0
  141. package/lib/typescript/commonjs/components/List/ListItem.d.ts +61 -0
  142. package/lib/typescript/commonjs/components/List/ListItem.d.ts.map +1 -0
  143. package/lib/typescript/commonjs/components/List/index.d.ts +5 -0
  144. package/lib/typescript/commonjs/components/List/index.d.ts.map +1 -0
  145. package/lib/typescript/commonjs/components/Menu/Menu.d.ts +4 -2
  146. package/lib/typescript/commonjs/components/Menu/Menu.d.ts.map +1 -1
  147. package/lib/typescript/commonjs/components/ProgressBar/ProgressBar.d.ts +3 -1
  148. package/lib/typescript/commonjs/components/ProgressBar/ProgressBar.d.ts.map +1 -1
  149. package/lib/typescript/commonjs/components/Sidebar/Sidebar.d.ts.map +1 -1
  150. package/lib/typescript/commonjs/components/StatTile/StatTile.d.ts +31 -0
  151. package/lib/typescript/commonjs/components/StatTile/StatTile.d.ts.map +1 -0
  152. package/lib/typescript/commonjs/components/StatTile/index.d.ts +3 -0
  153. package/lib/typescript/commonjs/components/StatTile/index.d.ts.map +1 -0
  154. package/lib/typescript/commonjs/components/Table/Table.d.ts +25 -3
  155. package/lib/typescript/commonjs/components/Table/Table.d.ts.map +1 -1
  156. package/lib/typescript/commonjs/components/_internal/ModalHostContext.d.ts +11 -0
  157. package/lib/typescript/commonjs/components/_internal/ModalHostContext.d.ts.map +1 -0
  158. package/lib/typescript/commonjs/components/index.d.ts +3 -0
  159. package/lib/typescript/commonjs/components/index.d.ts.map +1 -1
  160. package/lib/typescript/module/components/Alert/Alert.d.ts +41 -0
  161. package/lib/typescript/module/components/Alert/Alert.d.ts.map +1 -0
  162. package/lib/typescript/module/components/Alert/index.d.ts +3 -0
  163. package/lib/typescript/module/components/Alert/index.d.ts.map +1 -0
  164. package/lib/typescript/module/components/CodeInput/CodeInput.d.ts +5 -1
  165. package/lib/typescript/module/components/CodeInput/CodeInput.d.ts.map +1 -1
  166. package/lib/typescript/module/components/Dialog/Dialog.d.ts +13 -1
  167. package/lib/typescript/module/components/Dialog/Dialog.d.ts.map +1 -1
  168. package/lib/typescript/module/components/Dialog/StepDialog.d.ts +63 -0
  169. package/lib/typescript/module/components/Dialog/StepDialog.d.ts.map +1 -0
  170. package/lib/typescript/module/components/Dialog/index.d.ts +1 -0
  171. package/lib/typescript/module/components/Dialog/index.d.ts.map +1 -1
  172. package/lib/typescript/module/components/Icon/icons.generated.d.ts +8 -0
  173. package/lib/typescript/module/components/Icon/icons.generated.d.ts.map +1 -1
  174. package/lib/typescript/module/components/List/List.d.ts +22 -0
  175. package/lib/typescript/module/components/List/List.d.ts.map +1 -0
  176. package/lib/typescript/module/components/List/ListItem.d.ts +61 -0
  177. package/lib/typescript/module/components/List/ListItem.d.ts.map +1 -0
  178. package/lib/typescript/module/components/List/index.d.ts +5 -0
  179. package/lib/typescript/module/components/List/index.d.ts.map +1 -0
  180. package/lib/typescript/module/components/Menu/Menu.d.ts +4 -2
  181. package/lib/typescript/module/components/Menu/Menu.d.ts.map +1 -1
  182. package/lib/typescript/module/components/ProgressBar/ProgressBar.d.ts +3 -1
  183. package/lib/typescript/module/components/ProgressBar/ProgressBar.d.ts.map +1 -1
  184. package/lib/typescript/module/components/Sidebar/Sidebar.d.ts.map +1 -1
  185. package/lib/typescript/module/components/StatTile/StatTile.d.ts +31 -0
  186. package/lib/typescript/module/components/StatTile/StatTile.d.ts.map +1 -0
  187. package/lib/typescript/module/components/StatTile/index.d.ts +3 -0
  188. package/lib/typescript/module/components/StatTile/index.d.ts.map +1 -0
  189. package/lib/typescript/module/components/Table/Table.d.ts +25 -3
  190. package/lib/typescript/module/components/Table/Table.d.ts.map +1 -1
  191. package/lib/typescript/module/components/_internal/ModalHostContext.d.ts +11 -0
  192. package/lib/typescript/module/components/_internal/ModalHostContext.d.ts.map +1 -0
  193. package/lib/typescript/module/components/index.d.ts +3 -0
  194. package/lib/typescript/module/components/index.d.ts.map +1 -1
  195. package/package.json +16 -14
  196. package/src/components/Alert/Alert.tsx +132 -0
  197. package/src/components/Alert/index.ts +2 -0
  198. package/src/components/CodeInput/CodeInput.tsx +46 -14
  199. package/src/components/Dialog/Dialog.tsx +92 -31
  200. package/src/components/Dialog/StepDialog.tsx +173 -0
  201. package/src/components/Dialog/index.ts +1 -0
  202. package/src/components/Icon/icons.generated.ts +9 -1
  203. package/src/components/List/List.tsx +45 -0
  204. package/src/components/List/ListItem.tsx +214 -0
  205. package/src/components/List/index.ts +4 -0
  206. package/src/components/Menu/Menu.tsx +15 -6
  207. package/src/components/ProgressBar/ProgressBar.tsx +6 -3
  208. package/src/components/Sidebar/Sidebar.tsx +18 -10
  209. package/src/components/StatTile/StatTile.tsx +98 -0
  210. package/src/components/StatTile/index.ts +2 -0
  211. package/src/components/Table/Table.tsx +93 -8
  212. package/src/components/_internal/ModalHostContext.tsx +15 -0
  213. package/src/components/index.ts +3 -0
@@ -14,7 +14,9 @@ import {
14
14
  import { useTheme } from '../../theme';
15
15
  import { Button, type ButtonVariant } from '../Button/Button';
16
16
  import { Icon } from '../Icon/Icon';
17
+ import { IconButton } from '../IconButton/IconButton';
17
18
  import { Text } from '../Text/Text';
19
+ import { ModalHostContext } from '../_internal/ModalHostContext';
18
20
  import { ScrollArea } from '../_internal/ScrollArea';
19
21
 
20
22
  export interface DialogAction {
@@ -44,6 +46,18 @@ export interface DialogProps {
44
46
  title: string;
45
47
  /** Optional subtitle. */
46
48
  subtitle?: string;
49
+ /**
50
+ * Show a round back button to the left of the title and wire its press here.
51
+ * Used for multi-step flows (see `StepDialog`). Omit for a plain dialog.
52
+ */
53
+ onBack?: () => void;
54
+ /** Accessibility label for the back button. Defaults to "Back". */
55
+ backAccessibilityLabel?: string;
56
+ /**
57
+ * Content pinned in the footer, above the action buttons (e.g. a step
58
+ * progress bar). Centered. Shows the footer region even without actions.
59
+ */
60
+ footerAccessory?: ReactNode;
47
61
  /** Any content — from text to a full form. */
48
62
  children?: ReactNode;
49
63
  /** Primary/secondary action buttons (right side). Keep total buttons ≤ 3. */
@@ -99,6 +113,9 @@ export function Dialog({
99
113
  onClose,
100
114
  title,
101
115
  subtitle,
116
+ onBack,
117
+ backAccessibilityLabel = 'Back',
118
+ footerAccessory,
102
119
  children,
103
120
  actions = [],
104
121
  cancel,
@@ -140,54 +157,94 @@ export function Dialog({
140
157
 
141
158
  const header = (
142
159
  <View style={{ paddingHorizontal: padding, paddingTop: padding, paddingBottom: contentSpacing }}>
143
- <View style={{ paddingRight: showClose ? 40 : 0, gap: 4 }}>
144
- <Text variant="h3" weight="semibold" color="primary">
145
- {title}
146
- </Text>
147
- {subtitle ? (
148
- <Text variant="bodyLarge" color="secondary">
149
- {subtitle}
150
- </Text>
160
+ <View
161
+ style={{
162
+ flexDirection: 'row',
163
+ alignItems: 'center',
164
+ gap: 20,
165
+ paddingRight: showClose ? 40 : 0,
166
+ }}
167
+ >
168
+ {onBack ? (
169
+ <IconButton
170
+ icon="arrow-left"
171
+ accessibilityLabel={backAccessibilityLabel}
172
+ variant="secondary"
173
+ size="lg"
174
+ onPress={onBack}
175
+ />
151
176
  ) : null}
177
+ <View style={{ flex: 1, gap: 4 }}>
178
+ <Text variant="h3" weight="semibold" color="primary">
179
+ {title}
180
+ </Text>
181
+ {subtitle ? (
182
+ <Text variant="bodyLarge" color="secondary">
183
+ {subtitle}
184
+ </Text>
185
+ ) : null}
186
+ </View>
152
187
  </View>
153
188
  </View>
154
189
  );
155
190
 
156
- const contentBody = <View style={{ paddingHorizontal: padding }}>{children}</View>;
191
+ const hasButtons = cancel != null || actions.length > 0;
192
+ const hasFooter = hasButtons || footerAccessory != null;
193
+
194
+ // The footer owns the bottom padding; without a footer the content would
195
+ // otherwise hug the bottom edge, so it takes over the bottom padding itself.
196
+ const contentBody = (
197
+ <View style={{ paddingHorizontal: padding, paddingBottom: hasFooter ? 0 : padding }}>
198
+ {children}
199
+ </View>
200
+ );
157
201
 
158
- const hasFooter = cancel != null || actions.length > 0;
159
202
  const footer = hasFooter ? (
160
203
  <View
161
204
  style={{
162
- flexDirection: 'row',
163
- gap: 12,
164
205
  paddingHorizontal: padding,
165
206
  paddingBottom: padding,
166
207
  paddingTop: contentSpacing,
167
208
  }}
168
209
  >
169
- {cancel ? (
170
- <View style={{ flex: 1 }}>
171
- <Button
172
- fullWidth
173
- variant={cancel.destructive ? 'destructive' : 'secondary'}
174
- label={cancel.label ?? 'Cancel'}
175
- onPress={cancel.onPress ?? onClose}
176
- />
210
+ {footerAccessory ? (
211
+ <View
212
+ style={{
213
+ alignItems: 'center',
214
+ // Figma keeps the accessory (e.g. step progress) closer to the
215
+ // content above it than to the buttons below (~40 vs ~56).
216
+ marginBottom: hasButtons ? Math.round(contentSpacing * 1.4) : 0,
217
+ }}
218
+ >
219
+ {footerAccessory}
177
220
  </View>
178
221
  ) : null}
179
- {actions.map((action, i) => (
180
- <View key={i} style={{ flex: 1 }}>
181
- <Button
182
- fullWidth
183
- variant={action.variant ?? 'default'}
184
- label={action.label}
185
- onPress={action.onPress}
186
- loading={action.loading}
187
- disabled={action.disabled}
188
- />
222
+ {hasButtons ? (
223
+ <View style={{ flexDirection: 'row', gap: 12 }}>
224
+ {cancel ? (
225
+ <View style={{ flex: 1 }}>
226
+ <Button
227
+ fullWidth
228
+ variant={cancel.destructive ? 'destructive' : 'secondary'}
229
+ label={cancel.label ?? 'Cancel'}
230
+ onPress={cancel.onPress ?? onClose}
231
+ />
232
+ </View>
233
+ ) : null}
234
+ {actions.map((action, i) => (
235
+ <View key={i} style={{ flex: 1 }}>
236
+ <Button
237
+ fullWidth
238
+ variant={action.variant ?? 'default'}
239
+ label={action.label}
240
+ onPress={action.onPress}
241
+ loading={action.loading}
242
+ disabled={action.disabled}
243
+ />
244
+ </View>
245
+ ))}
189
246
  </View>
190
- ))}
247
+ ) : null}
191
248
  </View>
192
249
  ) : null;
193
250
 
@@ -213,6 +270,7 @@ export function Dialog({
213
270
  <ScrollArea
214
271
  indicatorColor={indicatorColor}
215
272
  style={{ flex: 1 }}
273
+ contentContainerStyle={{ paddingVertical: 8 }}
216
274
  keyboardShouldPersistTaps="handled"
217
275
  keyboardDismissMode="interactive"
218
276
  >
@@ -239,6 +297,7 @@ export function Dialog({
239
297
  <ScrollArea
240
298
  indicatorColor={indicatorColor}
241
299
  style={{ flexShrink: 1 }}
300
+ contentContainerStyle={{ paddingVertical: 8 }}
242
301
  keyboardShouldPersistTaps="handled"
243
302
  keyboardDismissMode="interactive"
244
303
  >
@@ -258,6 +317,7 @@ export function Dialog({
258
317
  statusBarTranslucent
259
318
  onRequestClose={onClose}
260
319
  >
320
+ <ModalHostContext.Provider value={true}>
261
321
  <KeyboardAvoidingView
262
322
  behavior={Platform.OS === 'ios' ? 'padding' : undefined}
263
323
  style={{ flex: 1 }}
@@ -294,6 +354,7 @@ export function Dialog({
294
354
  )}
295
355
  </View>
296
356
  </KeyboardAvoidingView>
357
+ </ModalHostContext.Provider>
297
358
  </Modal>
298
359
  );
299
360
  }
@@ -0,0 +1,173 @@
1
+ import { useEffect, useState, type ReactNode } from 'react';
2
+ import { ProgressBar } from '../ProgressBar/ProgressBar';
3
+ import { Dialog, type DialogProps } from './Dialog';
4
+
5
+ export interface StepDialogStep {
6
+ /** Header title for this step. */
7
+ title: string;
8
+ /** Optional subtitle under the title. */
9
+ subtitle?: string;
10
+ /** The step body. */
11
+ content: ReactNode;
12
+ /** Primary button label. Defaults to "Continue" (or `finishLabel` on the last step). */
13
+ nextLabel?: string;
14
+ /** Disable the primary button — e.g. until this step is valid. */
15
+ nextDisabled?: boolean;
16
+ /** Show a spinner on the primary button (e.g. while submitting). */
17
+ nextLoading?: boolean;
18
+ /**
19
+ * Run when the primary button is pressed on this step. Return (or resolve to)
20
+ * `false` to stay on the step — e.g. when validation fails. Anything else
21
+ * advances to the next step (or finishes on the last one).
22
+ */
23
+ onNext?: () => boolean | void | Promise<boolean | void>;
24
+ }
25
+
26
+ export interface StepDialogProps
27
+ extends Omit<
28
+ DialogProps,
29
+ 'title' | 'subtitle' | 'children' | 'actions' | 'cancel' | 'onBack' | 'footerAccessory'
30
+ > {
31
+ /** The ordered steps of the flow. */
32
+ steps: StepDialogStep[];
33
+ /** Controlled step index. Omit for uncontrolled. */
34
+ step?: number;
35
+ /** Initial step index (uncontrolled). Defaults to 0. Resets to this on close. */
36
+ defaultStep?: number;
37
+ /** Notified whenever the step changes. */
38
+ onStepChange?: (index: number) => void;
39
+ /** Called after the last step's `onNext` resolves (unless it returned `false`). */
40
+ onFinish?: () => void;
41
+ /** Primary label on the last step. Defaults to "Finish". */
42
+ finishLabel?: string;
43
+ /** Secondary label used to go back on steps after the first. Defaults to "Back". */
44
+ backLabel?: string;
45
+ /** Secondary label on the first step (closes the dialog). Defaults to "Cancel". */
46
+ cancelLabel?: string;
47
+ /** Show the round back button in the header from step 2 onward. Defaults to true. */
48
+ showHeaderBack?: boolean;
49
+ /** Show the footer secondary button (Back / Cancel). Defaults to true. */
50
+ showFooterBack?: boolean;
51
+ /** Show the progress bar in the footer. Defaults to true. */
52
+ showProgress?: boolean;
53
+ /**
54
+ * Width of the progress bar within the footer (centered). A number is px; a
55
+ * string is any RN dimension (e.g. `'62%'`). Defaults to `'62%'` — the narrower,
56
+ * centered bar from Figma (≈ 500 / 820 of the content width).
57
+ */
58
+ progressWidth?: number | `${number}%`;
59
+ }
60
+
61
+ const clamp = (n: number, min: number, max: number) => (n < min ? min : n > max ? max : n);
62
+
63
+ /**
64
+ * StepDialog — a multi-step (wizard) flow built on `Dialog`. Give it an array of
65
+ * `steps`; it tracks the current one, renders a round back button in the header
66
+ * from step 2 onward, a thin progress bar in the footer, and a
67
+ * secondary (Back / Cancel) + primary (Continue / Finish) button pair. A step's
68
+ * `onNext` can return `false` (sync or async) to block advancing after failed
69
+ * validation. Controlled via `step`/`onStepChange` or fully uncontrolled.
70
+ * Forwards all other `Dialog` props (width, fullscreen, scrollMode, …).
71
+ */
72
+ export function StepDialog({
73
+ steps,
74
+ step,
75
+ defaultStep = 0,
76
+ onStepChange,
77
+ onFinish,
78
+ finishLabel = 'Finish',
79
+ backLabel = 'Back',
80
+ cancelLabel = 'Cancel',
81
+ showHeaderBack = true,
82
+ showFooterBack = true,
83
+ showProgress = true,
84
+ progressWidth = '62%',
85
+ visible,
86
+ onClose,
87
+ ...dialogProps
88
+ }: StepDialogProps) {
89
+ const total = steps.length;
90
+ const [uncontrolled, setUncontrolled] = useState(defaultStep);
91
+ const isControlled = step !== undefined;
92
+ const index = clamp(isControlled ? step : uncontrolled, 0, Math.max(0, total - 1));
93
+
94
+ // Reset an uncontrolled flow to its first step whenever it is closed.
95
+ useEffect(() => {
96
+ if (!visible && !isControlled) setUncontrolled(defaultStep);
97
+ }, [visible, isControlled, defaultStep]);
98
+
99
+ const setIndex = (next: number) => {
100
+ const clamped = clamp(next, 0, total - 1);
101
+ if (!isControlled) setUncontrolled(clamped);
102
+ onStepChange?.(clamped);
103
+ };
104
+
105
+ const current = steps[index];
106
+ if (!current) return null;
107
+
108
+ const isFirst = index === 0;
109
+ const isLast = index === total - 1;
110
+
111
+ const goBack = () => {
112
+ if (!isFirst) setIndex(index - 1);
113
+ };
114
+
115
+ const advance = () => {
116
+ if (isLast) onFinish?.();
117
+ else setIndex(index + 1);
118
+ };
119
+
120
+ const goNext = () => {
121
+ const result = current.onNext?.();
122
+ // Await only a real promise, so sync steps advance synchronously.
123
+ if (result && typeof (result as Promise<unknown>).then === 'function') {
124
+ void (result as Promise<boolean | void>).then((r) => {
125
+ if (r !== false) advance();
126
+ });
127
+ return;
128
+ }
129
+ if (result === false) return;
130
+ advance();
131
+ };
132
+
133
+ const footerAccessory = showProgress ? (
134
+ <ProgressBar
135
+ progress={(index + 1) / total}
136
+ showSupporting={false}
137
+ bordered={false}
138
+ height={6}
139
+ style={{ width: progressWidth, alignSelf: 'center' }}
140
+ accessibilityLabel={`Step ${index + 1} of ${total}`}
141
+ />
142
+ ) : undefined;
143
+
144
+ return (
145
+ <Dialog
146
+ {...dialogProps}
147
+ visible={visible}
148
+ onClose={onClose}
149
+ title={current.title}
150
+ subtitle={current.subtitle}
151
+ onBack={!isFirst && showHeaderBack ? goBack : undefined}
152
+ backAccessibilityLabel={backLabel}
153
+ footerAccessory={footerAccessory}
154
+ cancel={
155
+ showFooterBack
156
+ ? isFirst
157
+ ? { label: cancelLabel, onPress: onClose }
158
+ : { label: backLabel, onPress: goBack }
159
+ : undefined
160
+ }
161
+ actions={[
162
+ {
163
+ label: current.nextLabel ?? (isLast ? finishLabel : 'Continue'),
164
+ onPress: goNext,
165
+ disabled: current.nextDisabled,
166
+ loading: current.nextLoading,
167
+ },
168
+ ]}
169
+ >
170
+ {current.content}
171
+ </Dialog>
172
+ );
173
+ }
@@ -4,3 +4,4 @@ export {
4
4
  type DialogAction,
5
5
  type DialogCancelConfig,
6
6
  } from './Dialog';
7
+ export { StepDialog, type StepDialogProps, type StepDialogStep } from './StepDialog';