@arbor-education/design-system.components 0.1.5 → 0.2.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 (174) hide show
  1. package/.github/workflows/release.yml +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/dist/components/formField/fieldset/Fieldset.d.ts +6 -0
  4. package/dist/components/formField/fieldset/Fieldset.d.ts.map +1 -0
  5. package/dist/components/formField/fieldset/Fieldset.js +7 -0
  6. package/dist/components/formField/fieldset/Fieldset.js.map +1 -0
  7. package/dist/components/formField/fieldset/Fieldset.stories.d.ts +28 -0
  8. package/dist/components/formField/fieldset/Fieldset.stories.d.ts.map +1 -0
  9. package/dist/components/formField/fieldset/Fieldset.stories.js +51 -0
  10. package/dist/components/formField/fieldset/Fieldset.stories.js.map +1 -0
  11. package/dist/components/formField/fieldset/Fieldset.test.d.ts +2 -0
  12. package/dist/components/formField/fieldset/Fieldset.test.d.ts.map +1 -0
  13. package/dist/components/formField/fieldset/Fieldset.test.js +62 -0
  14. package/dist/components/formField/fieldset/Fieldset.test.js.map +1 -0
  15. package/dist/components/formField/inputs/checkbox/CheckboxGroup.d.ts +8 -0
  16. package/dist/components/formField/inputs/checkbox/CheckboxGroup.d.ts.map +1 -0
  17. package/dist/components/formField/inputs/checkbox/CheckboxGroup.js +8 -0
  18. package/dist/components/formField/inputs/checkbox/CheckboxGroup.js.map +1 -0
  19. package/dist/components/formField/inputs/checkbox/CheckboxGroup.test.d.ts +2 -0
  20. package/dist/components/formField/inputs/checkbox/CheckboxGroup.test.d.ts.map +1 -0
  21. package/dist/components/formField/inputs/checkbox/CheckboxGroup.test.js +86 -0
  22. package/dist/components/formField/inputs/checkbox/CheckboxGroup.test.js.map +1 -0
  23. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts +3 -1
  24. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts.map +1 -1
  25. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js +7 -0
  26. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js.map +1 -1
  27. package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts +11 -0
  28. package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts.map +1 -0
  29. package/dist/components/formField/inputs/radio/RadioButtonGroup.js +8 -0
  30. package/dist/components/formField/inputs/radio/RadioButtonGroup.js.map +1 -0
  31. package/dist/components/formField/inputs/radio/RadioButtonGroup.test.d.ts +2 -0
  32. package/dist/components/formField/inputs/radio/RadioButtonGroup.test.d.ts.map +1 -0
  33. package/dist/components/formField/inputs/radio/RadioButtonGroup.test.js +86 -0
  34. package/dist/components/formField/inputs/radio/RadioButtonGroup.test.js.map +1 -0
  35. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts.map +1 -1
  36. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js +8 -2
  37. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js.map +1 -1
  38. package/dist/components/modal/Modal.d.ts +25 -0
  39. package/dist/components/modal/Modal.d.ts.map +1 -0
  40. package/dist/components/modal/Modal.js +22 -0
  41. package/dist/components/modal/Modal.js.map +1 -0
  42. package/dist/components/modal/Modal.stories.d.ts +13 -0
  43. package/dist/components/modal/Modal.stories.d.ts.map +1 -0
  44. package/dist/components/modal/Modal.stories.js +23 -0
  45. package/dist/components/modal/Modal.stories.js.map +1 -0
  46. package/dist/components/modal/Modal.test.d.ts +2 -0
  47. package/dist/components/modal/Modal.test.d.ts.map +1 -0
  48. package/dist/components/modal/Modal.test.js +131 -0
  49. package/dist/components/modal/Modal.test.js.map +1 -0
  50. package/dist/components/modal/ModalBody.d.ts +7 -0
  51. package/dist/components/modal/ModalBody.d.ts.map +1 -0
  52. package/dist/components/modal/ModalBody.js +7 -0
  53. package/dist/components/modal/ModalBody.js.map +1 -0
  54. package/dist/components/modal/ModalCloseButton.d.ts +3 -0
  55. package/dist/components/modal/ModalCloseButton.d.ts.map +1 -0
  56. package/dist/components/modal/ModalCloseButton.js +25 -0
  57. package/dist/components/modal/ModalCloseButton.js.map +1 -0
  58. package/dist/components/modal/ModalFooter.d.ts +7 -0
  59. package/dist/components/modal/ModalFooter.d.ts.map +1 -0
  60. package/dist/components/modal/ModalFooter.js +7 -0
  61. package/dist/components/modal/ModalFooter.js.map +1 -0
  62. package/dist/components/modal/ModalHeader.d.ts +7 -0
  63. package/dist/components/modal/ModalHeader.d.ts.map +1 -0
  64. package/dist/components/modal/ModalHeader.js +8 -0
  65. package/dist/components/modal/ModalHeader.js.map +1 -0
  66. package/dist/components/modal/ModalTitle.d.ts +3 -0
  67. package/dist/components/modal/ModalTitle.d.ts.map +1 -0
  68. package/dist/components/modal/ModalTitle.js +8 -0
  69. package/dist/components/modal/ModalTitle.js.map +1 -0
  70. package/dist/components/modal/modalManager/ModalManager.d.ts +7 -0
  71. package/dist/components/modal/modalManager/ModalManager.d.ts.map +1 -0
  72. package/dist/components/modal/modalManager/ModalManager.js +22 -0
  73. package/dist/components/modal/modalManager/ModalManager.js.map +1 -0
  74. package/dist/components/modal/modalManager/ModalManager.stories.d.ts +13 -0
  75. package/dist/components/modal/modalManager/ModalManager.stories.d.ts.map +1 -0
  76. package/dist/components/modal/modalManager/ModalManager.stories.js +110 -0
  77. package/dist/components/modal/modalManager/ModalManager.stories.js.map +1 -0
  78. package/dist/components/modal/modalManager/ModalManager.test.d.ts +2 -0
  79. package/dist/components/modal/modalManager/ModalManager.test.d.ts.map +1 -0
  80. package/dist/components/modal/modalManager/ModalManager.test.js +191 -0
  81. package/dist/components/modal/modalManager/ModalManager.test.js.map +1 -0
  82. package/dist/components/separator/Separator.d.ts +7 -0
  83. package/dist/components/separator/Separator.d.ts.map +1 -0
  84. package/dist/components/separator/Separator.js +8 -0
  85. package/dist/components/separator/Separator.js.map +1 -0
  86. package/dist/components/separator/Separator.stories.d.ts +10 -0
  87. package/dist/components/separator/Separator.stories.d.ts.map +1 -0
  88. package/dist/components/separator/Separator.stories.js +12 -0
  89. package/dist/components/separator/Separator.stories.js.map +1 -0
  90. package/dist/components/separator/Separator.test.d.ts +2 -0
  91. package/dist/components/separator/Separator.test.d.ts.map +1 -0
  92. package/dist/components/separator/Separator.test.js +10 -0
  93. package/dist/components/separator/Separator.test.js.map +1 -0
  94. package/dist/components/table/Table.d.ts +20 -0
  95. package/dist/components/table/Table.d.ts.map +1 -1
  96. package/dist/components/table/Table.js +45 -7
  97. package/dist/components/table/Table.js.map +1 -1
  98. package/dist/components/table/Table.stories.d.ts.map +1 -1
  99. package/dist/components/table/Table.stories.js +14 -1
  100. package/dist/components/table/Table.stories.js.map +1 -1
  101. package/dist/components/table/Table.test.js +315 -2
  102. package/dist/components/table/Table.test.js.map +1 -1
  103. package/dist/components/table/pagination/TableSettingsDropdown.d.ts +2 -0
  104. package/dist/components/table/pagination/TableSettingsDropdown.d.ts.map +1 -0
  105. package/dist/components/table/pagination/TableSettingsDropdown.js +43 -0
  106. package/dist/components/table/pagination/TableSettingsDropdown.js.map +1 -0
  107. package/dist/components/table/useTableSettings.d.ts +22 -0
  108. package/dist/components/table/useTableSettings.d.ts.map +1 -0
  109. package/dist/components/table/useTableSettings.js +36 -0
  110. package/dist/components/table/useTableSettings.js.map +1 -0
  111. package/dist/index.css +102 -1
  112. package/dist/index.css.map +1 -1
  113. package/dist/index.d.ts +4 -0
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.js +4 -0
  116. package/dist/index.js.map +1 -1
  117. package/dist/utils/Constants.d.ts +5 -0
  118. package/dist/utils/Constants.d.ts.map +1 -1
  119. package/dist/utils/Constants.js +5 -0
  120. package/dist/utils/Constants.js.map +1 -1
  121. package/dist/utils/ModalUtils.d.ts +7 -0
  122. package/dist/utils/ModalUtils.d.ts.map +1 -0
  123. package/dist/utils/ModalUtils.js +14 -0
  124. package/dist/utils/ModalUtils.js.map +1 -0
  125. package/dist/utils/hooks/useComponentDidUpdate.d.ts +7 -0
  126. package/dist/utils/hooks/useComponentDidUpdate.d.ts.map +1 -0
  127. package/dist/utils/hooks/useComponentDidUpdate.js +18 -0
  128. package/dist/utils/hooks/useComponentDidUpdate.js.map +1 -0
  129. package/dist/utils/hooks/useComponentDidUpdate.test.d.ts +2 -0
  130. package/dist/utils/hooks/useComponentDidUpdate.test.d.ts.map +1 -0
  131. package/dist/utils/hooks/useComponentDidUpdate.test.js +69 -0
  132. package/dist/utils/hooks/useComponentDidUpdate.test.js.map +1 -0
  133. package/package.json +1 -1
  134. package/src/components/formField/fieldset/Fieldset.stories.tsx +89 -0
  135. package/src/components/formField/fieldset/Fieldset.test.tsx +85 -0
  136. package/src/components/formField/fieldset/Fieldset.tsx +17 -0
  137. package/src/components/formField/fieldset/fieldset.scss +19 -0
  138. package/src/components/formField/inputs/checkbox/CheckboxGroup.test.tsx +127 -0
  139. package/src/components/formField/inputs/checkbox/CheckboxGroup.tsx +17 -0
  140. package/src/components/formField/inputs/checkbox/CheckboxInput.stories.tsx +12 -1
  141. package/src/components/formField/inputs/radio/RadioButtonGroup.test.tsx +190 -0
  142. package/src/components/formField/inputs/radio/RadioButtonGroup.tsx +22 -0
  143. package/src/components/formField/inputs/radio/RadioButtonInput.stories.tsx +16 -7
  144. package/src/components/formField/label/label.scss +1 -1
  145. package/src/components/modal/Modal.stories.tsx +37 -0
  146. package/src/components/modal/Modal.test.tsx +244 -0
  147. package/src/components/modal/Modal.tsx +65 -0
  148. package/src/components/modal/ModalBody.tsx +16 -0
  149. package/src/components/modal/ModalCloseButton.tsx +39 -0
  150. package/src/components/modal/ModalFooter.tsx +14 -0
  151. package/src/components/modal/ModalHeader.tsx +20 -0
  152. package/src/components/modal/ModalTitle.tsx +12 -0
  153. package/src/components/modal/modal.scss +74 -0
  154. package/src/components/modal/modalManager/ModalManager.stories.tsx +497 -0
  155. package/src/components/modal/modalManager/ModalManager.test.tsx +255 -0
  156. package/src/components/modal/modalManager/ModalManager.tsx +40 -0
  157. package/src/components/modal/modalManager/modalManager.scss +4 -0
  158. package/src/components/separator/Separator.stories.tsx +15 -0
  159. package/src/components/separator/Separator.test.tsx +10 -0
  160. package/src/components/separator/Separator.tsx +15 -0
  161. package/src/components/separator/separator.scss +6 -0
  162. package/src/components/table/Table.stories.tsx +14 -1
  163. package/src/components/table/Table.test.tsx +553 -1
  164. package/src/components/table/Table.tsx +80 -24
  165. package/src/components/table/pagination/TableSettingsDropdown.tsx +90 -0
  166. package/src/components/table/table.scss +8 -0
  167. package/src/components/table/useTableSettings.ts +59 -0
  168. package/src/index.scss +4 -0
  169. package/src/index.ts +4 -0
  170. package/src/tokens.scss +1 -0
  171. package/src/utils/Constants.ts +6 -0
  172. package/src/utils/ModalUtils.ts +17 -0
  173. package/src/utils/hooks/useComponentDidUpdate.test.ts +107 -0
  174. package/src/utils/hooks/useComponentDidUpdate.ts +19 -0
@@ -0,0 +1,244 @@
1
+ import { describe, expect, test, vi } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { Modal } from './Modal';
5
+ import '@testing-library/jest-dom/vitest';
6
+
7
+ describe('Modal', () => {
8
+ test('renders when open prop is true', () => {
9
+ render(
10
+ <Modal open={true}>
11
+ <Modal.Body>Modal content</Modal.Body>
12
+ </Modal>,
13
+ );
14
+ expect(screen.getByText('Modal content')).toBeInTheDocument();
15
+ });
16
+
17
+ test('does not render when open prop is false', () => {
18
+ render(
19
+ <Modal open={false}>
20
+ <Modal.Body>Modal content</Modal.Body>
21
+ </Modal>,
22
+ );
23
+ expect(screen.queryByText('Modal content')).not.toBeInTheDocument();
24
+ });
25
+
26
+ test('renders with custom className', () => {
27
+ render(
28
+ <Modal open={true} className="custom-modal">
29
+ <Modal.Body>Content</Modal.Body>
30
+ </Modal>,
31
+ );
32
+ const modalContainer = document.querySelector('.custom-modal');
33
+ expect(modalContainer).toBeInTheDocument();
34
+ });
35
+
36
+ test('renders with custom overlayClassName', () => {
37
+ render(
38
+ <Modal open={true} overlayClassName="custom-overlay">
39
+ <Modal.Body>Content</Modal.Body>
40
+ </Modal>,
41
+ );
42
+ const overlay = document.querySelector('.custom-overlay');
43
+ expect(overlay).toBeInTheDocument();
44
+ });
45
+
46
+ test('renders with title prop', () => {
47
+ render(
48
+ <Modal open={true} title="Test Modal Title">
49
+ <Modal.Body>Content</Modal.Body>
50
+ </Modal>,
51
+ );
52
+ expect(screen.getByText('Test Modal Title')).toBeInTheDocument();
53
+ });
54
+
55
+ test('renders close button by default', () => {
56
+ const closeHandler = vi.fn();
57
+ render(
58
+ <Modal open={true} closeHandler={closeHandler}>
59
+ <Modal.Body>Content</Modal.Body>
60
+ </Modal>,
61
+ );
62
+ const closeButton = screen.getByRole('button');
63
+ expect(closeButton).toBeInTheDocument();
64
+ });
65
+
66
+ test('hides close button when hideCloseButton is true', () => {
67
+ const closeHandler = vi.fn();
68
+ render(
69
+ <Modal open={true} hideCloseButton={true} closeHandler={closeHandler}>
70
+ <Modal.Body>Content</Modal.Body>
71
+ </Modal>,
72
+ );
73
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
74
+ });
75
+
76
+ test('calls closeHandler when close button is clicked', async () => {
77
+ const user = userEvent.setup();
78
+ const closeHandler = vi.fn();
79
+ render(
80
+ <Modal open={true} closeHandler={closeHandler}>
81
+ <Modal.Body>Content</Modal.Body>
82
+ </Modal>,
83
+ );
84
+ const closeButton = screen.getByRole('button');
85
+ await user.click(closeButton);
86
+ expect(closeHandler).toHaveBeenCalledTimes(1);
87
+ });
88
+
89
+ test('calls closeHandler when Escape key is pressed', async () => {
90
+ const user = userEvent.setup();
91
+ const closeHandler = vi.fn();
92
+ render(
93
+ <Modal open={true} closeHandler={closeHandler}>
94
+ <Modal.Body>Content</Modal.Body>
95
+ </Modal>,
96
+ );
97
+ await user.keyboard('{Escape}');
98
+ expect(closeHandler).toHaveBeenCalledTimes(1);
99
+ });
100
+
101
+ test('renders Modal.Header component', () => {
102
+ render(
103
+ <Modal open={true}>
104
+ <Modal.Header>Header content</Modal.Header>
105
+ </Modal>,
106
+ );
107
+ expect(screen.getByText('Header content')).toBeInTheDocument();
108
+ const header = document.querySelector('.ds-modal__header');
109
+ expect(header).toBeInTheDocument();
110
+ });
111
+
112
+ test('renders Modal.Body component', () => {
113
+ render(
114
+ <Modal open={true}>
115
+ <Modal.Body>Body content</Modal.Body>
116
+ </Modal>,
117
+ );
118
+ expect(screen.getByText('Body content')).toBeInTheDocument();
119
+ const body = document.querySelector('.ds-modal__body');
120
+ expect(body).toBeInTheDocument();
121
+ });
122
+
123
+ test('renders Modal.Footer component', () => {
124
+ render(
125
+ <Modal open={true}>
126
+ <Modal.Footer>Footer content</Modal.Footer>
127
+ </Modal>,
128
+ );
129
+ expect(screen.getByText('Footer content')).toBeInTheDocument();
130
+ const footer = document.querySelector('.ds-modal__footer');
131
+ expect(footer).toBeInTheDocument();
132
+ });
133
+
134
+ test('renders Modal.Title component', () => {
135
+ render(
136
+ <Modal open={true}>
137
+ <Modal.Header>
138
+ <Modal.Title>Title content</Modal.Title>
139
+ </Modal.Header>
140
+ </Modal>,
141
+ );
142
+ expect(screen.getByText('Title content')).toBeInTheDocument();
143
+ const title = document.querySelector('.ds-modal__title');
144
+ expect(title).toBeInTheDocument();
145
+ });
146
+
147
+ test('renders complete modal structure with all components', () => {
148
+ const closeHandler = vi.fn();
149
+ render(
150
+ <Modal open={true} closeHandler={closeHandler}>
151
+ <Modal.Header>
152
+ <Modal.Title>Complete Modal</Modal.Title>
153
+ </Modal.Header>
154
+ <Modal.Body>This is the modal body</Modal.Body>
155
+ <Modal.Footer>
156
+ <button>Action</button>
157
+ </Modal.Footer>
158
+ </Modal>,
159
+ );
160
+
161
+ expect(screen.getByText('Complete Modal')).toBeInTheDocument();
162
+ expect(screen.getByText('This is the modal body')).toBeInTheDocument();
163
+ expect(screen.getByText('Action')).toBeInTheDocument();
164
+ });
165
+
166
+ test('applies custom className to Modal.Header', () => {
167
+ render(
168
+ <Modal open={true}>
169
+ <Modal.Header className="custom-header">Header</Modal.Header>
170
+ </Modal>,
171
+ );
172
+ const header = document.querySelector('.custom-header');
173
+ expect(header).toBeInTheDocument();
174
+ expect(header).toHaveClass('ds-modal__header');
175
+ });
176
+
177
+ test('applies custom className to Modal.Body', () => {
178
+ render(
179
+ <Modal open={true}>
180
+ <Modal.Body className="custom-body">Body</Modal.Body>
181
+ </Modal>,
182
+ );
183
+ const body = document.querySelector('.custom-body');
184
+ expect(body).toBeInTheDocument();
185
+ expect(body).toHaveClass('ds-modal__body');
186
+ });
187
+
188
+ test('applies custom className to Modal.Footer', () => {
189
+ render(
190
+ <Modal open={true}>
191
+ <Modal.Footer className="custom-footer">Footer</Modal.Footer>
192
+ </Modal>,
193
+ );
194
+ const footer = document.querySelector('.custom-footer');
195
+ expect(footer).toBeInTheDocument();
196
+ expect(footer).toHaveClass('ds-modal__footer');
197
+ });
198
+
199
+ test('renders to custom portal target', () => {
200
+ const container = document.createElement('div');
201
+ container.id = 'custom-portal';
202
+ document.body.appendChild(container);
203
+
204
+ render(
205
+ <Modal open={true} portalTarget={container}>
206
+ <Modal.Body>Portal content</Modal.Body>
207
+ </Modal>,
208
+ );
209
+
210
+ expect(container.querySelector('.ds-modal__overlay')).toBeInTheDocument();
211
+ document.body.removeChild(container);
212
+ });
213
+
214
+ test('Modal.CloseButton does not render without closeHandler', () => {
215
+ render(
216
+ <Modal open={true}>
217
+ <Modal.Body>Content</Modal.Body>
218
+ <Modal.CloseButton />
219
+ </Modal>,
220
+ );
221
+ // Only the default close button should be present
222
+ const buttons = screen.queryAllByRole('button');
223
+ expect(buttons).toHaveLength(0);
224
+ });
225
+
226
+ test('Modal.CloseButton renders with closeHandler in context', async () => {
227
+ const user = userEvent.setup();
228
+ const closeHandler = vi.fn();
229
+ render(
230
+ <Modal open={true} closeHandler={closeHandler}>
231
+ <Modal.Body>Content</Modal.Body>
232
+ <Modal.CloseButton className="custom-close" />
233
+ </Modal>,
234
+ );
235
+
236
+ const closeButtons = screen.getAllByRole('button');
237
+ expect(closeButtons.length).toBeGreaterThan(0);
238
+
239
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
240
+ // @ts-ignore
241
+ await user.click(closeButtons[0]);
242
+ expect(closeHandler).toHaveBeenCalled();
243
+ });
244
+ });
@@ -0,0 +1,65 @@
1
+ import classNames from 'classnames';
2
+ import { Dialog } from 'radix-ui';
3
+ import { ModalHeader } from './ModalHeader';
4
+ import { ModalFooter } from './ModalFooter';
5
+ import { ModalBody } from './ModalBody';
6
+ import { PopupParentContext } from 'Utils/PopupParentContext';
7
+ import { createContext, useRef } from 'react';
8
+ import { ModalCloseButon } from './ModalCloseButton';
9
+ import { ModalTitle } from './ModalTitle';
10
+
11
+ type ModalContextValue = {
12
+ closeHandler?: () => void;
13
+ };
14
+
15
+ export const ModalContext = createContext<ModalContextValue>({});
16
+
17
+ export type ModalProps = {
18
+ className?: string;
19
+ overlayClassName?: string;
20
+ open?: boolean;
21
+ portalTarget?: HTMLElement | null;
22
+ children?: React.ReactNode;
23
+ hideCloseButton?: boolean;
24
+ closeHandler?: ModalContextValue['closeHandler'];
25
+ title?: string;
26
+ };
27
+
28
+ export const Modal = (props: ModalProps) => {
29
+ const {
30
+ className,
31
+ overlayClassName,
32
+ open,
33
+ portalTarget,
34
+ children,
35
+ closeHandler,
36
+ hideCloseButton = false,
37
+ title,
38
+ } = props;
39
+
40
+ const overlayRef = useRef<HTMLDivElement>(null);
41
+
42
+ return (
43
+ <PopupParentContext.Provider value={overlayRef}>
44
+ <ModalContext.Provider value={{ closeHandler }}>
45
+ <Dialog.Root open={open}>
46
+ <Dialog.Portal container={portalTarget}>
47
+ <Dialog.Overlay ref={overlayRef} className={classNames('ds-modal__overlay', overlayClassName)}>
48
+ <Dialog.Content className={classNames('ds-modal__container', className)}>
49
+ {title && <ModalHeader><ModalTitle>{title}</ModalTitle></ModalHeader>}
50
+ {children}
51
+ {!hideCloseButton && <ModalCloseButon className="ds-modal__close-button--top-right" />}
52
+ </Dialog.Content>
53
+ </Dialog.Overlay>
54
+ </Dialog.Portal>
55
+ </Dialog.Root>
56
+ </ModalContext.Provider>
57
+ </PopupParentContext.Provider>
58
+ );
59
+ };
60
+
61
+ Modal.Header = ModalHeader;
62
+ Modal.Body = ModalBody;
63
+ Modal.Footer = ModalFooter;
64
+ Modal.CloseButton = ModalCloseButon;
65
+ Modal.Title = ModalTitle;
@@ -0,0 +1,16 @@
1
+ import type { ReactNode } from 'react';
2
+ import classNames from 'classnames';
3
+
4
+ export type ModalBodyProps = {
5
+ className?: string;
6
+ children?: ReactNode;
7
+ };
8
+
9
+ export const ModalBody = (props: ModalBodyProps) => {
10
+ const { className, children } = props;
11
+ return (
12
+ <div className={classNames('ds-modal__body', className)}>
13
+ {children}
14
+ </div>
15
+ );
16
+ };
@@ -0,0 +1,39 @@
1
+ import { useContext, useEffect } from 'react';
2
+ import { ModalContext } from './Modal';
3
+ import { Button, type ButtonProps } from 'Components/button/Button';
4
+ import classNames from 'classnames';
5
+
6
+ export const ModalCloseButon = (props: ButtonProps) => {
7
+ const { className, ...rest } = props;
8
+ const { closeHandler } = useContext(ModalContext);
9
+
10
+ const handleKeyDown = (event: KeyboardEvent) => {
11
+ if (event.key === 'Escape' && closeHandler) {
12
+ closeHandler();
13
+ }
14
+ };
15
+
16
+ useEffect(() => {
17
+ document.addEventListener('keydown', handleKeyDown);
18
+ return () => {
19
+ document.removeEventListener('keydown', handleKeyDown);
20
+ };
21
+ }, [closeHandler]);
22
+
23
+ if (!closeHandler) {
24
+ return null;
25
+ }
26
+
27
+ return (
28
+ <Button
29
+ size="S"
30
+ onClick={closeHandler}
31
+ variant="secondary"
32
+ iconRightName="x"
33
+ iconRightScreenReaderText="Close"
34
+ borderless
35
+ className={classNames('ds-modal__close-button', className)}
36
+ {...rest}
37
+ />
38
+ );
39
+ };
@@ -0,0 +1,14 @@
1
+ import type { ReactNode } from 'react';
2
+ import classNames from 'classnames';
3
+
4
+ export type ModalFooterProps = {
5
+ className?: string;
6
+ children?: ReactNode;
7
+ };
8
+
9
+ export const ModalFooter = (props: ModalFooterProps) => {
10
+ const { className, children } = props;
11
+ return (
12
+ <footer className={classNames('ds-modal__footer', className)}>{children}</footer>
13
+ );
14
+ };
@@ -0,0 +1,20 @@
1
+ import { type ReactNode } from 'react';
2
+ import classNames from 'classnames';
3
+
4
+ export type ModalHeaderProps = {
5
+ className?: string;
6
+ children?: ReactNode;
7
+ };
8
+
9
+ export const ModalHeader = (props: ModalHeaderProps) => {
10
+ const {
11
+ className,
12
+ children,
13
+ } = props;
14
+
15
+ return (
16
+ <header className={classNames('ds-modal__header', className)}>
17
+ {children}
18
+ </header>
19
+ );
20
+ };
@@ -0,0 +1,12 @@
1
+ import classNames from 'classnames';
2
+ import { Dialog } from 'radix-ui';
3
+
4
+ export const ModalTitle = (props: Dialog.DialogTitleProps) => {
5
+ const { children, className, ...rest } = props;
6
+
7
+ return (
8
+ <Dialog.Title className={classNames('ds-modal__title', className)} {...rest}>
9
+ {children}
10
+ </Dialog.Title>
11
+ );
12
+ };
@@ -0,0 +1,74 @@
1
+ .ds-modal {
2
+ &__overlay {
3
+ position: fixed;
4
+ height: 100vh;
5
+ width: 100vw;
6
+ top: 0;
7
+ left: 0;
8
+ pointer-events: none;
9
+ background-color: var(--modal-overlay-color-background);
10
+ }
11
+
12
+ &__container {
13
+ position: fixed;
14
+ top: 50%;
15
+ left: 50%;
16
+ transform: translate(-50%, -50%);
17
+ border-radius: var(--modal-radius);
18
+ background-color: var(--modal-color-background);
19
+ display: flex;
20
+ flex-direction: column;
21
+ justify-content: space-between;
22
+ overflow: hidden;
23
+ max-width: calc(100vw - var(--spacing-medium));
24
+ max-height: calc(100vh - var(--spacing-medium));
25
+ min-width: var(--modal-min-width);
26
+ font-family: var(--type-body-p-family);
27
+ font-size: var(--type-body-p-size);
28
+ font-weight: var(--type-body-p-weight);
29
+ line-height: var(--type-body-line-height);
30
+ color: var(--type-body-p-color);
31
+
32
+ .ds-modal__close-button--top-right {
33
+ position: absolute;
34
+ top: var(--spacing-small);
35
+ right: var(--spacing-small);
36
+ }
37
+ }
38
+
39
+ &__header {
40
+ padding: var(--modal-header-spacing-padding);
41
+ background-color: var(--modal-header-color-background);
42
+
43
+ .ds-modal__close-button {
44
+ margin-left: auto;
45
+ }
46
+ }
47
+
48
+ &__title {
49
+ font-family: var(--type-headings-h2-family);
50
+ font-size: var(--type-headings-h2-size);
51
+ font-style: normal;
52
+ font-weight: var(--type-headings-h2-weight);
53
+ margin: 0;
54
+ }
55
+
56
+ &__footer {
57
+ padding: var(--modal-footer-spacing-padding);
58
+ background-color: var(--modal-footer-color-background);
59
+ }
60
+
61
+ &__footer, &__header {
62
+ flex-direction: row;
63
+ display: flex;
64
+ align-items: center;
65
+ gap: var(--spacing-large);
66
+ }
67
+
68
+ &__body {
69
+ background-color: var(--color-grey-050);
70
+ flex: 1;
71
+ padding: var(--spacing-xxlarge) var(--spacing-large);
72
+ overflow-y: auto;
73
+ }
74
+ }