@oxyhq/bloom 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/lib/commonjs/alert-dialog/AlertDialog.js +35 -27
  2. package/lib/commonjs/alert-dialog/AlertDialog.js.map +1 -1
  3. package/lib/commonjs/alert-dialog/index.js +1 -1
  4. package/lib/commonjs/alert-dialog/index.js.map +1 -1
  5. package/lib/commonjs/alert-dialog/index.web.js +1 -1
  6. package/lib/commonjs/alert-dialog/index.web.js.map +1 -1
  7. package/lib/commonjs/command/Command.js +27 -16
  8. package/lib/commonjs/command/Command.js.map +1 -1
  9. package/lib/commonjs/command/index.js +1 -1
  10. package/lib/commonjs/command/index.js.map +1 -1
  11. package/lib/commonjs/command/index.web.js +1 -1
  12. package/lib/commonjs/command/index.web.js.map +1 -1
  13. package/lib/commonjs/dialog/Dialog.js +1 -2
  14. package/lib/commonjs/dialog/Dialog.js.map +1 -1
  15. package/lib/commonjs/dialog/Dialog.web.js +9 -6
  16. package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
  17. package/lib/commonjs/dialog/index.js +0 -13
  18. package/lib/commonjs/dialog/index.js.map +1 -1
  19. package/lib/commonjs/dialog/index.web.js +0 -19
  20. package/lib/commonjs/dialog/index.web.js.map +1 -1
  21. package/lib/commonjs/index.js +0 -29
  22. package/lib/commonjs/index.js.map +1 -1
  23. package/lib/commonjs/index.web.js +6 -35
  24. package/lib/commonjs/index.web.js.map +1 -1
  25. package/lib/module/alert-dialog/AlertDialog.js +35 -27
  26. package/lib/module/alert-dialog/AlertDialog.js.map +1 -1
  27. package/lib/module/alert-dialog/index.js +2 -2
  28. package/lib/module/alert-dialog/index.js.map +1 -1
  29. package/lib/module/alert-dialog/index.web.js +2 -2
  30. package/lib/module/alert-dialog/index.web.js.map +1 -1
  31. package/lib/module/command/Command.js +27 -16
  32. package/lib/module/command/Command.js.map +1 -1
  33. package/lib/module/command/index.js +2 -2
  34. package/lib/module/command/index.js.map +1 -1
  35. package/lib/module/command/index.web.js +2 -2
  36. package/lib/module/command/index.web.js.map +1 -1
  37. package/lib/module/dialog/Dialog.js +1 -2
  38. package/lib/module/dialog/Dialog.js.map +1 -1
  39. package/lib/module/dialog/Dialog.web.js +9 -6
  40. package/lib/module/dialog/Dialog.web.js.map +1 -1
  41. package/lib/module/dialog/index.js +0 -1
  42. package/lib/module/dialog/index.js.map +1 -1
  43. package/lib/module/dialog/index.web.js +0 -1
  44. package/lib/module/dialog/index.web.js.map +1 -1
  45. package/lib/module/index.js +1 -3
  46. package/lib/module/index.js.map +1 -1
  47. package/lib/module/index.web.js +1 -3
  48. package/lib/module/index.web.js.map +1 -1
  49. package/lib/typescript/commonjs/alert-dialog/AlertDialog.d.ts +13 -12
  50. package/lib/typescript/commonjs/alert-dialog/AlertDialog.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/alert-dialog/index.d.ts.map +1 -1
  52. package/lib/typescript/commonjs/alert-dialog/index.web.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/command/Command.d.ts +8 -8
  54. package/lib/typescript/commonjs/command/Command.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/command/index.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/command/index.web.d.ts.map +1 -1
  57. package/lib/typescript/commonjs/dialog/Dialog.d.ts +1 -2
  58. package/lib/typescript/commonjs/dialog/Dialog.d.ts.map +1 -1
  59. package/lib/typescript/commonjs/dialog/Dialog.web.d.ts +2 -2
  60. package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
  61. package/lib/typescript/commonjs/dialog/index.d.ts +0 -2
  62. package/lib/typescript/commonjs/dialog/index.d.ts.map +1 -1
  63. package/lib/typescript/commonjs/dialog/index.web.d.ts +0 -2
  64. package/lib/typescript/commonjs/dialog/index.web.d.ts.map +1 -1
  65. package/lib/typescript/commonjs/index.d.ts +2 -4
  66. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  67. package/lib/typescript/commonjs/index.web.d.ts +2 -4
  68. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  69. package/lib/typescript/module/alert-dialog/AlertDialog.d.ts +13 -12
  70. package/lib/typescript/module/alert-dialog/AlertDialog.d.ts.map +1 -1
  71. package/lib/typescript/module/alert-dialog/index.d.ts.map +1 -1
  72. package/lib/typescript/module/alert-dialog/index.web.d.ts.map +1 -1
  73. package/lib/typescript/module/command/Command.d.ts +8 -8
  74. package/lib/typescript/module/command/Command.d.ts.map +1 -1
  75. package/lib/typescript/module/command/index.d.ts.map +1 -1
  76. package/lib/typescript/module/command/index.web.d.ts.map +1 -1
  77. package/lib/typescript/module/dialog/Dialog.d.ts +1 -2
  78. package/lib/typescript/module/dialog/Dialog.d.ts.map +1 -1
  79. package/lib/typescript/module/dialog/Dialog.web.d.ts +2 -2
  80. package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
  81. package/lib/typescript/module/dialog/index.d.ts +0 -2
  82. package/lib/typescript/module/dialog/index.d.ts.map +1 -1
  83. package/lib/typescript/module/dialog/index.web.d.ts +0 -2
  84. package/lib/typescript/module/dialog/index.web.d.ts.map +1 -1
  85. package/lib/typescript/module/index.d.ts +2 -4
  86. package/lib/typescript/module/index.d.ts.map +1 -1
  87. package/lib/typescript/module/index.web.d.ts +2 -4
  88. package/lib/typescript/module/index.web.d.ts.map +1 -1
  89. package/package.json +1 -18
  90. package/src/__tests__/AlertDialog.test.tsx +122 -0
  91. package/src/__tests__/Command.test.tsx +107 -0
  92. package/src/alert-dialog/AlertDialog.tsx +46 -36
  93. package/src/alert-dialog/index.ts +2 -2
  94. package/src/alert-dialog/index.web.ts +2 -2
  95. package/src/command/Command.tsx +30 -19
  96. package/src/command/index.ts +2 -2
  97. package/src/command/index.web.ts +2 -2
  98. package/src/dialog/Dialog.tsx +1 -2
  99. package/src/dialog/Dialog.web.tsx +9 -6
  100. package/src/dialog/index.ts +0 -2
  101. package/src/dialog/index.web.ts +0 -6
  102. package/src/index.ts +0 -11
  103. package/src/index.web.ts +0 -11
  104. package/lib/commonjs/dialog/CenteredDialog.js +0 -252
  105. package/lib/commonjs/dialog/CenteredDialog.js.map +0 -1
  106. package/lib/commonjs/dialog/CenteredDialog.web.js +0 -330
  107. package/lib/commonjs/dialog/CenteredDialog.web.js.map +0 -1
  108. package/lib/commonjs/dialog/centered-dialog-tokens.js +0 -62
  109. package/lib/commonjs/dialog/centered-dialog-tokens.js.map +0 -1
  110. package/lib/commonjs/dialog/centered-dialog-types.js +0 -6
  111. package/lib/commonjs/dialog/centered-dialog-types.js.map +0 -1
  112. package/lib/commonjs/responsive-sheet/ResponsiveSheet.js +0 -97
  113. package/lib/commonjs/responsive-sheet/ResponsiveSheet.js.map +0 -1
  114. package/lib/commonjs/responsive-sheet/ResponsiveSheet.web.js +0 -84
  115. package/lib/commonjs/responsive-sheet/ResponsiveSheet.web.js.map +0 -1
  116. package/lib/commonjs/responsive-sheet/index.js +0 -19
  117. package/lib/commonjs/responsive-sheet/index.js.map +0 -1
  118. package/lib/commonjs/responsive-sheet/index.web.js +0 -19
  119. package/lib/commonjs/responsive-sheet/index.web.js.map +0 -1
  120. package/lib/commonjs/responsive-sheet/types.js +0 -6
  121. package/lib/commonjs/responsive-sheet/types.js.map +0 -1
  122. package/lib/module/dialog/CenteredDialog.js +0 -246
  123. package/lib/module/dialog/CenteredDialog.js.map +0 -1
  124. package/lib/module/dialog/CenteredDialog.web.js +0 -324
  125. package/lib/module/dialog/CenteredDialog.web.js.map +0 -1
  126. package/lib/module/dialog/centered-dialog-tokens.js +0 -58
  127. package/lib/module/dialog/centered-dialog-tokens.js.map +0 -1
  128. package/lib/module/dialog/centered-dialog-types.js +0 -4
  129. package/lib/module/dialog/centered-dialog-types.js.map +0 -1
  130. package/lib/module/responsive-sheet/ResponsiveSheet.js +0 -90
  131. package/lib/module/responsive-sheet/ResponsiveSheet.js.map +0 -1
  132. package/lib/module/responsive-sheet/ResponsiveSheet.web.js +0 -77
  133. package/lib/module/responsive-sheet/ResponsiveSheet.web.js.map +0 -1
  134. package/lib/module/responsive-sheet/index.js +0 -4
  135. package/lib/module/responsive-sheet/index.js.map +0 -1
  136. package/lib/module/responsive-sheet/index.web.js +0 -12
  137. package/lib/module/responsive-sheet/index.web.js.map +0 -1
  138. package/lib/module/responsive-sheet/types.js +0 -4
  139. package/lib/module/responsive-sheet/types.js.map +0 -1
  140. package/lib/typescript/commonjs/dialog/CenteredDialog.d.ts +0 -22
  141. package/lib/typescript/commonjs/dialog/CenteredDialog.d.ts.map +0 -1
  142. package/lib/typescript/commonjs/dialog/CenteredDialog.web.d.ts +0 -31
  143. package/lib/typescript/commonjs/dialog/CenteredDialog.web.d.ts.map +0 -1
  144. package/lib/typescript/commonjs/dialog/centered-dialog-tokens.d.ts +0 -47
  145. package/lib/typescript/commonjs/dialog/centered-dialog-tokens.d.ts.map +0 -1
  146. package/lib/typescript/commonjs/dialog/centered-dialog-types.d.ts +0 -74
  147. package/lib/typescript/commonjs/dialog/centered-dialog-types.d.ts.map +0 -1
  148. package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.d.ts +0 -22
  149. package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.d.ts.map +0 -1
  150. package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.web.d.ts +0 -16
  151. package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.web.d.ts.map +0 -1
  152. package/lib/typescript/commonjs/responsive-sheet/index.d.ts +0 -3
  153. package/lib/typescript/commonjs/responsive-sheet/index.d.ts.map +0 -1
  154. package/lib/typescript/commonjs/responsive-sheet/index.web.d.ts +0 -3
  155. package/lib/typescript/commonjs/responsive-sheet/index.web.d.ts.map +0 -1
  156. package/lib/typescript/commonjs/responsive-sheet/types.d.ts +0 -58
  157. package/lib/typescript/commonjs/responsive-sheet/types.d.ts.map +0 -1
  158. package/lib/typescript/module/dialog/CenteredDialog.d.ts +0 -22
  159. package/lib/typescript/module/dialog/CenteredDialog.d.ts.map +0 -1
  160. package/lib/typescript/module/dialog/CenteredDialog.web.d.ts +0 -31
  161. package/lib/typescript/module/dialog/CenteredDialog.web.d.ts.map +0 -1
  162. package/lib/typescript/module/dialog/centered-dialog-tokens.d.ts +0 -47
  163. package/lib/typescript/module/dialog/centered-dialog-tokens.d.ts.map +0 -1
  164. package/lib/typescript/module/dialog/centered-dialog-types.d.ts +0 -74
  165. package/lib/typescript/module/dialog/centered-dialog-types.d.ts.map +0 -1
  166. package/lib/typescript/module/responsive-sheet/ResponsiveSheet.d.ts +0 -22
  167. package/lib/typescript/module/responsive-sheet/ResponsiveSheet.d.ts.map +0 -1
  168. package/lib/typescript/module/responsive-sheet/ResponsiveSheet.web.d.ts +0 -16
  169. package/lib/typescript/module/responsive-sheet/ResponsiveSheet.web.d.ts.map +0 -1
  170. package/lib/typescript/module/responsive-sheet/index.d.ts +0 -3
  171. package/lib/typescript/module/responsive-sheet/index.d.ts.map +0 -1
  172. package/lib/typescript/module/responsive-sheet/index.web.d.ts +0 -3
  173. package/lib/typescript/module/responsive-sheet/index.web.d.ts.map +0 -1
  174. package/lib/typescript/module/responsive-sheet/types.d.ts +0 -58
  175. package/lib/typescript/module/responsive-sheet/types.d.ts.map +0 -1
  176. package/src/__tests__/CenteredDialog.test.tsx +0 -108
  177. package/src/__tests__/ResponsiveSheet.test.tsx +0 -88
  178. package/src/dialog/CenteredDialog.stories.tsx +0 -125
  179. package/src/dialog/CenteredDialog.tsx +0 -305
  180. package/src/dialog/CenteredDialog.web.tsx +0 -374
  181. package/src/dialog/centered-dialog-tokens.ts +0 -68
  182. package/src/dialog/centered-dialog-types.ts +0 -74
  183. package/src/responsive-sheet/ResponsiveSheet.stories.tsx +0 -54
  184. package/src/responsive-sheet/ResponsiveSheet.tsx +0 -88
  185. package/src/responsive-sheet/ResponsiveSheet.web.tsx +0 -76
  186. package/src/responsive-sheet/index.ts +0 -6
  187. package/src/responsive-sheet/index.web.ts +0 -14
  188. package/src/responsive-sheet/types.ts +0 -60
@@ -1,108 +0,0 @@
1
- import React from 'react';
2
- import { Modal, Text } from 'react-native';
3
- import { fireEvent, render } from '@testing-library/react-native';
4
-
5
- import { CenteredDialog, CENTERED_DIALOG_BACKDROP_TESTID } from '../dialog';
6
- import { BloomThemeProvider } from '../theme/BloomThemeProvider';
7
-
8
- function renderWithTheme(ui: React.ReactElement) {
9
- return render(
10
- <BloomThemeProvider mode="light" colorPreset="teal">
11
- {ui}
12
- </BloomThemeProvider>,
13
- );
14
- }
15
-
16
- const CLOSE_LABEL = 'Close dialog';
17
-
18
- describe('CenteredDialog', () => {
19
- it('renders the title, children and footer when visible', () => {
20
- const { getByText } = renderWithTheme(
21
- <CenteredDialog
22
- visible
23
- onClose={() => {}}
24
- title="Confirm action"
25
- footer={<Text>Footer area</Text>}
26
- >
27
- <Text>Body content</Text>
28
- </CenteredDialog>,
29
- );
30
- expect(getByText('Confirm action')).toBeTruthy();
31
- expect(getByText('Body content')).toBeTruthy();
32
- expect(getByText('Footer area')).toBeTruthy();
33
- });
34
-
35
- it('forwards visible=false to the underlying Modal', () => {
36
- const { UNSAFE_getByType } = renderWithTheme(
37
- <CenteredDialog visible={false} onClose={() => {}} title="Hidden">
38
- <Text>Body</Text>
39
- </CenteredDialog>,
40
- );
41
- // The native fork always mounts the RN Modal; the platform hides it via
42
- // the `visible` prop. Assert the prop is wired through.
43
- expect(UNSAFE_getByType(Modal).props.visible).toBe(false);
44
- });
45
-
46
- it('renders a header close button by default when a title is set and fires onClose', () => {
47
- const onClose = jest.fn();
48
- const { getByLabelText } = renderWithTheme(
49
- <CenteredDialog visible onClose={onClose} title="With close">
50
- <Text>Body</Text>
51
- </CenteredDialog>,
52
- );
53
- fireEvent.press(getByLabelText(CLOSE_LABEL));
54
- expect(onClose).toHaveBeenCalledTimes(1);
55
- });
56
-
57
- it('omits the header close button when showClose is false even with a title', () => {
58
- const { queryByLabelText } = renderWithTheme(
59
- <CenteredDialog visible onClose={() => {}} title="No close" showClose={false}>
60
- <Text>Body</Text>
61
- </CenteredDialog>,
62
- );
63
- expect(queryByLabelText(CLOSE_LABEL)).toBeNull();
64
- });
65
-
66
- it('closes when the backdrop is pressed (dismissible default)', () => {
67
- const onClose = jest.fn();
68
- const { getByTestId } = renderWithTheme(
69
- <CenteredDialog visible onClose={onClose}>
70
- <Text>Body</Text>
71
- </CenteredDialog>,
72
- );
73
- fireEvent.press(getByTestId(CENTERED_DIALOG_BACKDROP_TESTID));
74
- expect(onClose).toHaveBeenCalledTimes(1);
75
- });
76
-
77
- it('does NOT close on backdrop press when dismissible is false', () => {
78
- const onClose = jest.fn();
79
- const { getByTestId } = renderWithTheme(
80
- <CenteredDialog visible onClose={onClose} dismissible={false}>
81
- <Text>Body</Text>
82
- </CenteredDialog>,
83
- );
84
- fireEvent.press(getByTestId(CENTERED_DIALOG_BACKDROP_TESTID));
85
- expect(onClose).not.toHaveBeenCalled();
86
- });
87
-
88
- it('derives the backdrop testID from a provided testID', () => {
89
- const { getByTestId } = renderWithTheme(
90
- <CenteredDialog visible onClose={() => {}} testID="signout">
91
- <Text>Body</Text>
92
- </CenteredDialog>,
93
- );
94
- expect(getByTestId('signout-backdrop')).toBeTruthy();
95
- });
96
-
97
- it('renders a chromeless card (no header) when no title and showClose unset', () => {
98
- const { queryByLabelText, getByText, getByTestId } = renderWithTheme(
99
- <CenteredDialog visible onClose={() => {}}>
100
- <Text>Chromeless body</Text>
101
- </CenteredDialog>,
102
- );
103
- expect(getByText('Chromeless body')).toBeTruthy();
104
- // No header close button; only the backdrop is interactive.
105
- expect(queryByLabelText(CLOSE_LABEL)).toBeNull();
106
- expect(getByTestId(CENTERED_DIALOG_BACKDROP_TESTID)).toBeTruthy();
107
- });
108
- });
@@ -1,88 +0,0 @@
1
- import React from 'react';
2
- import { Text } from 'react-native';
3
- import { act, render } from '@testing-library/react-native';
4
-
5
- import { ResponsiveSheet } from '../responsive-sheet';
6
- import { BloomThemeProvider } from '../theme/BloomThemeProvider';
7
-
8
- function renderWithTheme(ui: React.ReactElement) {
9
- return render(
10
- <BloomThemeProvider mode="light" colorPreset="teal">
11
- {ui}
12
- </BloomThemeProvider>,
13
- );
14
- }
15
-
16
- function rerenderWithTheme(
17
- rerender: ReturnType<typeof render>['rerender'],
18
- ui: React.ReactElement,
19
- ) {
20
- rerender(
21
- <BloomThemeProvider mode="light" colorPreset="teal">
22
- {ui}
23
- </BloomThemeProvider>,
24
- );
25
- }
26
-
27
- const LABEL = 'Filters sheet';
28
-
29
- // `ResponsiveSheet` is now a deprecated thin wrapper over `<Dialog>`. These
30
- // tests verify the controlled wrapper contract it still owns (open/close
31
- // presence + delegation). The jest env reports a 375px-wide viewport (below
32
- // the default `md` breakpoint) so the wrapper resolves to the bottom-sheet
33
- // placement, which delegates to bloom's `BottomSheet`.
34
- describe('ResponsiveSheet (deprecated Dialog wrapper)', () => {
35
- it('renders children when open', () => {
36
- const { getByText } = renderWithTheme(
37
- <ResponsiveSheet open onClose={() => {}} label={LABEL}>
38
- <Text>Sheet body</Text>
39
- </ResponsiveSheet>,
40
- );
41
- expect(getByText('Sheet body')).toBeTruthy();
42
- });
43
-
44
- it('renders nothing when closed (never opened)', () => {
45
- const { queryByText } = renderWithTheme(
46
- <ResponsiveSheet open={false} onClose={() => {}} label={LABEL}>
47
- <Text>Sheet body</Text>
48
- </ResponsiveSheet>,
49
- );
50
- expect(queryByText('Sheet body')).toBeNull();
51
- });
52
-
53
- it('unmounts the body after a controlled close settles', () => {
54
- jest.useFakeTimers();
55
- try {
56
- const { getByText, queryByText, rerender } = renderWithTheme(
57
- <ResponsiveSheet open onClose={() => {}} label={LABEL}>
58
- <Text>Sheet body</Text>
59
- </ResponsiveSheet>,
60
- );
61
- expect(getByText('Sheet body')).toBeTruthy();
62
-
63
- rerenderWithTheme(
64
- rerender,
65
- <ResponsiveSheet open={false} onClose={() => {}} label={LABEL}>
66
- <Text>Sheet body</Text>
67
- </ResponsiveSheet>,
68
- );
69
-
70
- // After the close animation/fallback settles the body is gone.
71
- act(() => {
72
- jest.advanceTimersByTime(500);
73
- });
74
- expect(queryByText('Sheet body')).toBeNull();
75
- } finally {
76
- jest.useRealTimers();
77
- }
78
- });
79
-
80
- it('forwards a custom label to the dialog surface', () => {
81
- const { getByLabelText } = renderWithTheme(
82
- <ResponsiveSheet open onClose={() => {}} label={LABEL}>
83
- <Text>Sheet body</Text>
84
- </ResponsiveSheet>,
85
- );
86
- expect(getByLabelText(LABEL)).toBeTruthy();
87
- });
88
- });
@@ -1,125 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { Text, View } from 'react-native';
3
- import type { Meta, StoryObj } from '@storybook/react-vite';
4
-
5
- import { Button } from '../button';
6
- import { CenteredDialog } from './CenteredDialog';
7
-
8
- const meta: Meta<typeof CenteredDialog> = {
9
- title: 'Components/CenteredDialog',
10
- component: CenteredDialog,
11
- };
12
-
13
- export default meta;
14
-
15
- type Story = StoryObj<typeof CenteredDialog>;
16
-
17
- function CompactDemo() {
18
- const [visible, setVisible] = useState(false);
19
- return (
20
- <>
21
- <Button onPress={() => setVisible(true)}>Open compact dialog</Button>
22
- <CenteredDialog
23
- visible={visible}
24
- onClose={() => setVisible(false)}
25
- title="Delete project?"
26
- footer={
27
- <View style={{ flexDirection: 'row', justifyContent: 'flex-end', gap: 8 }}>
28
- <Button variant="ghost" size="small" onPress={() => setVisible(false)}>
29
- Cancel
30
- </Button>
31
- <Button variant="primary" size="small" onPress={() => setVisible(false)}>
32
- Delete
33
- </Button>
34
- </View>
35
- }
36
- >
37
- <Text>This action cannot be undone. The project and all its data will be permanently removed.</Text>
38
- </CenteredDialog>
39
- </>
40
- );
41
- }
42
-
43
- function CozyDemo() {
44
- const [visible, setVisible] = useState(false);
45
- return (
46
- <>
47
- <Button onPress={() => setVisible(true)}>Open cozy dialog</Button>
48
- <CenteredDialog
49
- visible={visible}
50
- onClose={() => setVisible(false)}
51
- title="Roomier layout"
52
- compact={false}
53
- >
54
- <Text>compact={'{false}'} relaxes the padding and header/footer gaps for content-heavy dialogs.</Text>
55
- </CenteredDialog>
56
- </>
57
- );
58
- }
59
-
60
- function ChromelessDemo() {
61
- const [visible, setVisible] = useState(false);
62
- return (
63
- <>
64
- <Button onPress={() => setVisible(true)}>Open chromeless dialog</Button>
65
- <CenteredDialog visible={visible} onClose={() => setVisible(false)}>
66
- <View style={{ gap: 12 }}>
67
- <Text>No title, no close button — the card owns every pixel.</Text>
68
- <Button variant="secondary" size="small" onPress={() => setVisible(false)}>
69
- Done
70
- </Button>
71
- </View>
72
- </CenteredDialog>
73
- </>
74
- );
75
- }
76
-
77
- function NonDismissibleDemo() {
78
- const [visible, setVisible] = useState(false);
79
- return (
80
- <>
81
- <Button onPress={() => setVisible(true)}>Open blocking dialog</Button>
82
- <CenteredDialog
83
- visible={visible}
84
- onClose={() => setVisible(false)}
85
- title="Action required"
86
- dismissible={false}
87
- showClose={false}
88
- footer={
89
- <Button size="small" onPress={() => setVisible(false)}>
90
- Acknowledge
91
- </Button>
92
- }
93
- >
94
- <Text>Backdrop tap and Escape are disabled — the user must choose an action.</Text>
95
- </CenteredDialog>
96
- </>
97
- );
98
- }
99
-
100
- export const Compact: Story = {
101
- render: () => <CompactDemo />,
102
- };
103
-
104
- export const Cozy: Story = {
105
- render: () => <CozyDemo />,
106
- };
107
-
108
- export const Chromeless: Story = {
109
- render: () => <ChromelessDemo />,
110
- };
111
-
112
- export const NonDismissible: Story = {
113
- render: () => <NonDismissibleDemo />,
114
- };
115
-
116
- export const Gallery: Story = {
117
- render: () => (
118
- <View style={{ gap: 12, alignItems: 'flex-start' }}>
119
- <CompactDemo />
120
- <CozyDemo />
121
- <ChromelessDemo />
122
- <NonDismissibleDemo />
123
- </View>
124
- ),
125
- };
@@ -1,305 +0,0 @@
1
- import React, { useMemo } from 'react';
2
- import {
3
- Modal,
4
- Pressable,
5
- ScrollView,
6
- StyleSheet,
7
- View,
8
- } from 'react-native';
9
-
10
- import { useInteractionState } from '../hooks/useInteractionState';
11
- import { TimesLarge_Stroke2_Corner0_Rounded as CloseIcon } from '../icons/Times';
12
- import { useTheme } from '../theme/use-theme';
13
- import { Text } from '../typography';
14
- import type { CenteredDialogProps } from './centered-dialog-types';
15
- import {
16
- CARD_RADIUS,
17
- DEFAULT_MAX_WIDTH,
18
- MAX_HEIGHT_FRACTION,
19
- resolveSpacing,
20
- VIEWPORT_GUTTER,
21
- } from './centered-dialog-tokens';
22
-
23
- const CLOSE_HIT_SLOP = { top: 8, bottom: 8, left: 8, right: 8 } as const;
24
-
25
- /** Stable testID for the dimmed backdrop. Overridden when a `testID` is set. */
26
- export const CENTERED_DIALOG_BACKDROP_TESTID = 'bloom-centered-dialog-backdrop';
27
-
28
- /**
29
- * @deprecated Use `<Dialog placement="center">` instead.
30
- *
31
- * Native variant of `<CenteredDialog>`.
32
- *
33
- * A controlled, centered modal: a transparent RN `Modal` (fade animation,
34
- * own native window) hosting a dimmed full-screen backdrop and a centered,
35
- * rounded card styled entirely from Bloom theme tokens.
36
- *
37
- * The backdrop is a sibling `Pressable` rendered BEHIND the card (absolute
38
- * filled), never a parent of it — so a tap on the card never bubbles to the
39
- * backdrop's close handler and the card never ends up nested inside the
40
- * backdrop's pressable on web.
41
- *
42
- * Dismissal: backdrop tap (when `dismissible`), the header close button, and
43
- * the Android hardware back button (`onRequestClose`) all call `onClose`.
44
- */
45
- export function CenteredDialog({
46
- visible,
47
- onClose,
48
- title,
49
- dismissible = true,
50
- maxWidth = DEFAULT_MAX_WIDTH,
51
- compact = true,
52
- showClose,
53
- children,
54
- footer,
55
- accessibilityLabel,
56
- closeAccessibilityLabel = 'Close dialog',
57
- backdropAccessibilityLabel = 'Dismiss dialog',
58
- cardStyle,
59
- contentStyle,
60
- testID,
61
- }: CenteredDialogProps) {
62
- const theme = useTheme();
63
- const backdropPress = useInteractionState();
64
-
65
- const spacing = useMemo(() => resolveSpacing(compact), [compact]);
66
- const resolvedShowClose = showClose ?? Boolean(title);
67
- const hasHeader = Boolean(title) || resolvedShowClose;
68
-
69
- const handleBackdropPress = dismissible ? onClose : undefined;
70
- // Android's hardware back must always be answerable; when the dialog is
71
- // non-dismissible we still need a handler (RN requires one for transparent
72
- // modals) — make it a no-op so back does nothing rather than crash.
73
- const handleRequestClose = dismissible ? onClose : noop;
74
-
75
- return (
76
- <Modal
77
- visible={visible}
78
- transparent
79
- animationType="fade"
80
- statusBarTranslucent
81
- onRequestClose={handleRequestClose}
82
- >
83
- <View style={[styles.root, { padding: VIEWPORT_GUTTER }]}>
84
- <Pressable
85
- testID={testID ? `${testID}-backdrop` : CENTERED_DIALOG_BACKDROP_TESTID}
86
- style={[
87
- styles.backdrop,
88
- { backgroundColor: theme.colors.overlay },
89
- backdropPress.state && styles.backdropPressed,
90
- ]}
91
- onPress={handleBackdropPress}
92
- onPressIn={backdropPress.onIn}
93
- onPressOut={backdropPress.onOut}
94
- disabled={!dismissible}
95
- accessibilityRole="button"
96
- accessibilityLabel={backdropAccessibilityLabel}
97
- />
98
-
99
- {/* RN's `Modal` already isolates its contents in a separate
100
- accessibility window, so an explicit `accessibilityViewIsModal`
101
- on the card is redundant here (and would mark the sibling
102
- backdrop inaccessible). The web fork uses `aria-modal` instead. */}
103
- <View
104
- testID={testID}
105
- accessibilityLabel={accessibilityLabel ?? title}
106
- style={[
107
- styles.card,
108
- {
109
- maxWidth,
110
- maxHeight: `${MAX_HEIGHT_FRACTION * 100}%`,
111
- backgroundColor: theme.colors.card,
112
- borderColor: theme.colors.border,
113
- shadowColor: theme.colors.shadow,
114
- },
115
- cardStyle,
116
- ]}
117
- >
118
- {hasHeader ? (
119
- <DialogHeader
120
- title={title}
121
- showClose={resolvedShowClose}
122
- padding={spacing.pad}
123
- gap={spacing.headerGap}
124
- closeAccessibilityLabel={closeAccessibilityLabel}
125
- onClose={onClose}
126
- />
127
- ) : null}
128
-
129
- <ScrollView
130
- style={styles.bodyScroll}
131
- contentContainerStyle={[
132
- {
133
- padding: spacing.pad,
134
- paddingTop: hasHeader ? spacing.headerGap : spacing.pad,
135
- gap: spacing.contentGap,
136
- },
137
- contentStyle,
138
- ]}
139
- showsVerticalScrollIndicator={false}
140
- keyboardShouldPersistTaps="handled"
141
- >
142
- {children}
143
- </ScrollView>
144
-
145
- {footer ? (
146
- <View
147
- style={[
148
- styles.footer,
149
- {
150
- padding: spacing.pad,
151
- paddingTop: spacing.footerGap,
152
- borderTopColor: theme.colors.borderLight,
153
- },
154
- ]}
155
- >
156
- {footer}
157
- </View>
158
- ) : null}
159
- </View>
160
- </View>
161
- </Modal>
162
- );
163
- }
164
-
165
- function DialogHeader({
166
- title,
167
- showClose,
168
- padding,
169
- gap,
170
- closeAccessibilityLabel,
171
- onClose,
172
- }: {
173
- title?: string;
174
- showClose: boolean;
175
- padding: number;
176
- gap: number;
177
- closeAccessibilityLabel: string;
178
- onClose: () => void;
179
- }) {
180
- const theme = useTheme();
181
-
182
- return (
183
- <View
184
- style={[
185
- styles.header,
186
- { paddingHorizontal: padding, paddingTop: padding, gap },
187
- ]}
188
- >
189
- {title ? (
190
- <Text
191
- accessibilityRole="header"
192
- numberOfLines={2}
193
- style={[styles.title, { color: theme.colors.text }]}
194
- >
195
- {title}
196
- </Text>
197
- ) : (
198
- <View style={styles.titleSpacer} />
199
- )}
200
- {showClose ? (
201
- <CloseButton
202
- accessibilityLabel={closeAccessibilityLabel}
203
- onPress={onClose}
204
- />
205
- ) : null}
206
- </View>
207
- );
208
- }
209
-
210
- function CloseButton({
211
- accessibilityLabel,
212
- onPress,
213
- }: {
214
- accessibilityLabel: string;
215
- onPress: () => void;
216
- }) {
217
- const theme = useTheme();
218
- const press = useInteractionState();
219
-
220
- return (
221
- <Pressable
222
- onPress={onPress}
223
- onPressIn={press.onIn}
224
- onPressOut={press.onOut}
225
- hitSlop={CLOSE_HIT_SLOP}
226
- accessibilityRole="button"
227
- accessibilityLabel={accessibilityLabel}
228
- style={[
229
- styles.closeButton,
230
- { backgroundColor: theme.colors.contrast50 },
231
- press.state && styles.closeButtonPressed,
232
- ]}
233
- >
234
- <CloseIcon size="sm" fill={theme.colors.textSecondary} />
235
- </Pressable>
236
- );
237
- }
238
-
239
- function noop(): void {
240
- /* non-dismissible: hardware back is intentionally inert */
241
- }
242
-
243
- const styles = StyleSheet.create({
244
- root: {
245
- flex: 1,
246
- alignItems: 'center',
247
- justifyContent: 'center',
248
- },
249
- backdrop: {
250
- // Spell out the absolute-fill rect (matches `Fill`'s canonical style)
251
- // rather than spreading the deprecated `StyleSheet.absoluteFillObject`,
252
- // which RN 0.83's generated types no longer surface on the `StyleSheet`
253
- // namespace and which downstream type-checks reject.
254
- position: 'absolute',
255
- top: 0,
256
- right: 0,
257
- bottom: 0,
258
- left: 0,
259
- },
260
- // A subtle deepening on press confirms the tap-to-dismiss affordance.
261
- backdropPressed: {
262
- opacity: 0.92,
263
- },
264
- card: {
265
- width: '100%',
266
- borderRadius: CARD_RADIUS,
267
- borderWidth: StyleSheet.hairlineWidth,
268
- overflow: 'hidden',
269
- shadowOpacity: 0.18,
270
- shadowRadius: 24,
271
- shadowOffset: { width: 0, height: 8 },
272
- elevation: 12,
273
- },
274
- header: {
275
- flexDirection: 'row',
276
- alignItems: 'flex-start',
277
- justifyContent: 'space-between',
278
- },
279
- title: {
280
- flex: 1,
281
- fontSize: 17,
282
- fontWeight: '700',
283
- lineHeight: 22,
284
- },
285
- titleSpacer: {
286
- flex: 1,
287
- },
288
- closeButton: {
289
- width: 28,
290
- height: 28,
291
- borderRadius: 14,
292
- alignItems: 'center',
293
- justifyContent: 'center',
294
- },
295
- closeButtonPressed: {
296
- opacity: 0.7,
297
- },
298
- bodyScroll: {
299
- flexGrow: 0,
300
- flexShrink: 1,
301
- },
302
- footer: {
303
- borderTopWidth: StyleSheet.hairlineWidth,
304
- },
305
- });