@indico-data/design-system 2.60.15 → 3.0.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 (61) hide show
  1. package/lib/components/forms/date/inputDateRangePicker/InputDateRangePicker.d.ts +1 -1
  2. package/lib/components/index.d.ts +1 -1
  3. package/lib/components/modal/ConfirmationModal.d.ts +2 -0
  4. package/lib/components/modal/Modal.d.ts +1 -1
  5. package/lib/components/modal/Modal.stories.d.ts +2 -1
  6. package/lib/components/modal/index.d.ts +1 -0
  7. package/lib/components/modal/types.d.ts +15 -0
  8. package/lib/index.css +1827 -677
  9. package/lib/index.d.ts +19 -5
  10. package/lib/index.esm.css +1827 -677
  11. package/lib/index.esm.js +25 -6
  12. package/lib/index.esm.js.map +1 -1
  13. package/lib/index.js +25 -5
  14. package/lib/index.js.map +1 -1
  15. package/lib/types.d.ts +1 -1
  16. package/package.json +1 -1
  17. package/src/components/badge/styles/Badge.scss +6 -6
  18. package/src/components/button/styles/Button.scss +7 -13
  19. package/src/components/button/styles/_variables.scss +47 -48
  20. package/src/components/card/styles/Card.scss +15 -6
  21. package/src/components/floatUI/styles/_variables.scss +3 -3
  22. package/src/components/forms/date/datePicker/styles/DatePicker.scss +15 -16
  23. package/src/components/forms/date/inputDateRangePicker/InputDateRangePicker.tsx +1 -1
  24. package/src/components/forms/form/styles/Form.scss +25 -25
  25. package/src/components/forms/select/__tests__/Select.test.tsx +11 -6
  26. package/src/components/forms/select/styles/Select.scss +9 -10
  27. package/src/components/index.ts +1 -1
  28. package/src/components/menu/styles/_variables.scss +8 -8
  29. package/src/components/modal/ConfirmationModal.mdx +69 -0
  30. package/src/components/modal/ConfirmationModal.tsx +103 -0
  31. package/src/components/modal/Modal.mdx +72 -42
  32. package/src/components/modal/Modal.stories.tsx +131 -40
  33. package/src/components/modal/Modal.tsx +20 -3
  34. package/src/components/modal/__tests__/Modal.test.tsx +12 -0
  35. package/src/components/modal/index.ts +1 -0
  36. package/src/components/modal/styles/Modal.scss +51 -20
  37. package/src/components/modal/types.ts +18 -0
  38. package/src/components/pill/Pill.stories.tsx +28 -14
  39. package/src/components/pill/Pill.tsx +7 -1
  40. package/src/components/pill/styles/Pill.scss +79 -55
  41. package/src/components/tanstackTable/styles/_variables.scss +23 -24
  42. package/src/components/tanstackTable/styles/table.scss +3 -3
  43. package/src/components/toast/Toast.mdx +32 -1
  44. package/src/components/toast/Toast.stories.tsx +32 -9
  45. package/src/components/toast/styles/Toast.scss +62 -12
  46. package/src/components/tooltip/Tooltip.tsx +6 -2
  47. package/src/components/tooltip/styles/Tooltip.scss +6 -2
  48. package/src/components/truncate/Truncate.tsx +1 -1
  49. package/src/index.ts +1 -1
  50. package/src/styles/_borders.scss +35 -4
  51. package/src/styles/_colors.scss +1 -1
  52. package/src/styles/globals.scss +107 -25
  53. package/src/styles/storybook.scss +23 -6
  54. package/src/styles/variables/_borders.scss +23 -7
  55. package/src/styles/variables/_padding.scss +0 -4
  56. package/src/styles/variables/themes/dark.scss +193 -133
  57. package/src/styles/variables/themes/light.scss +193 -133
  58. package/src/stylesAndAnimations/borders/BorderColor.tsx +87 -40
  59. package/src/stylesAndAnimations/colors/Swatch.tsx +0 -1
  60. package/src/stylesAndAnimations/colors/constants.ts +316 -193
  61. package/src/types.ts +5 -3
@@ -0,0 +1,69 @@
1
+ import { Canvas, Meta, Controls, Story } from '@storybook/blocks';
2
+ import * as ModalStories from './Modal.stories';
3
+
4
+ <Meta title="Components/Modal" name="ConfirmationModal" of={ModalStories} />
5
+
6
+ # ConfirmationModal
7
+ The ConfirmationModal is a component that provides us with a way to display a confirmation modal. It extends the Modal component. For more information on props not listed here, please visit the [react-modal documentation](https://github.com/reactjs/react-modal).
8
+
9
+ <Canvas of={ModalStories.ConfirmationModalStory} source={{
10
+ code: `
11
+ const [isOpen, setIsOpen] = useState<boolean>(false);
12
+
13
+ const handleOpen = () => {
14
+ setIsOpen(true);
15
+ };
16
+
17
+ const handleClose = () => {
18
+ setIsOpen(false);
19
+ };
20
+
21
+ return (
22
+ <Container>
23
+ <Row>
24
+ <Col sm={4}>
25
+ <ConfirmationModal
26
+ className: '',
27
+ onRequestClose: () => {
28
+ console.log('closed');
29
+ },
30
+ title: 'Disable User',
31
+ confirmationButtonText: 'Delete',
32
+ confirmationButtonVariant: 'destructive',
33
+ cancelButtonText: 'Cancel',
34
+ shouldCloseOnOverlayClick: true,
35
+ icon: 'trash',
36
+ status: 'error',
37
+ shouldCloseOnEsc: true,
38
+ parentSelector: () => document.body,
39
+ maxWidthInPixels: 600,
40
+ isOpen={isOpen}
41
+ onRequestClose={handleClose}
42
+ >
43
+ <p>
44
+ Deleting user will no longer allow this person to have access to this organization.
45
+ You can always invite them again if you change your mind.
46
+ </p>
47
+ </ConfirmationModal>
48
+ <Button ariaLabel="open modal" onClick={handleOpen}>
49
+ Open modal
50
+ </Button>
51
+ </Col>
52
+ </Row>
53
+ </Container>
54
+ `
55
+ }} />
56
+
57
+ ### The following props are available for the ConfirmationModal component:
58
+
59
+ <Controls of={ModalStories.ConfirmationModalStory} />
60
+
61
+ ## Usage
62
+
63
+ The ConfirmationModal component is designed to be used as a confirmation modal. It is a wrapper around the Modal component and extends it with the following additional props:
64
+
65
+ - **confirmationButtonText** (string): The text displayed on the confirmation button. Default: 'Confirm'
66
+ - **confirmationButtonVariant** (string): The variant of the confirmation button. Options: 'solid', 'outline', 'link', 'action', 'destructive', 'soft'. Default: 'solid'
67
+ - **cancelButtonText** (string): The text displayed on the cancel button. Default: 'Cancel'
68
+ - **status** (string): The status of the modal which determines the icon color. Options: 'info', 'success', 'error'. Default: 'info'
69
+ - **icon** (string): The icon to display in the modal. Uses the available icon names from the design system.
@@ -0,0 +1,103 @@
1
+ import { Button } from '../button/Button';
2
+ import { ButtonVariants } from '../button/types';
3
+ import { Col, Row } from '../grid';
4
+ import { Icon } from '../icons/Icon';
5
+ import { Modal } from './Modal';
6
+ import { ConfirmationModalProps } from './types';
7
+ import classNames from 'classnames';
8
+
9
+ const defaultFooter = ({
10
+ onCancelRequest,
11
+ onConfirmRequest,
12
+ confirmationButtonText,
13
+ confirmationButtonVariant,
14
+ cancelButtonText,
15
+ }: {
16
+ onCancelRequest?: () => void;
17
+ onConfirmRequest?: () => void;
18
+ confirmationButtonText?: string;
19
+ confirmationButtonVariant?: ButtonVariants;
20
+ cancelButtonText?: string;
21
+ }) => (
22
+ <Row gutterWidth={12} justify="end" align="center">
23
+ <Col xs="content">
24
+ <Button onClick={onCancelRequest} ariaLabel={cancelButtonText || 'Cancel'} variant="outline">
25
+ {cancelButtonText}
26
+ </Button>
27
+ </Col>
28
+ <Col xs="content">
29
+ <Button
30
+ onClick={onConfirmRequest}
31
+ ariaLabel={confirmationButtonText || 'Confirm'}
32
+ variant={confirmationButtonVariant}
33
+ >
34
+ {confirmationButtonText}
35
+ </Button>
36
+ </Col>
37
+ </Row>
38
+ );
39
+
40
+ export const ConfirmationModal = ({
41
+ className,
42
+ overlayClassName,
43
+ testId,
44
+ isOpen,
45
+ onRequestClose,
46
+ portalClassName,
47
+ appElement,
48
+ parentSelector,
49
+ shouldCloseOnOverlayClick,
50
+ shouldCloseOnEsc,
51
+ contentElement,
52
+ overlayElement,
53
+ footer,
54
+ children,
55
+ onConfirmRequest,
56
+ onCancelRequest,
57
+ confirmationButtonText = 'Confirm',
58
+ cancelButtonText = 'Cancel',
59
+ confirmationButtonVariant = 'solid',
60
+ icon,
61
+ title,
62
+ status = 'info',
63
+ maxWidthInPixels,
64
+ }: ConfirmationModalProps) => {
65
+ const modalFooter =
66
+ footer ||
67
+ defaultFooter({
68
+ onCancelRequest,
69
+ onConfirmRequest,
70
+ confirmationButtonText,
71
+ cancelButtonText,
72
+ confirmationButtonVariant,
73
+ });
74
+
75
+ return (
76
+ <Modal
77
+ className={classNames('confirmation-modal', className)}
78
+ overlayClassName={overlayClassName}
79
+ testId={testId}
80
+ isOpen={isOpen}
81
+ onRequestClose={onRequestClose}
82
+ portalClassName={portalClassName}
83
+ appElement={appElement}
84
+ parentSelector={parentSelector}
85
+ shouldCloseOnOverlayClick={shouldCloseOnOverlayClick}
86
+ shouldCloseOnEsc={shouldCloseOnEsc}
87
+ contentElement={contentElement}
88
+ overlayElement={overlayElement}
89
+ footer={modalFooter}
90
+ maxWidthInPixels={maxWidthInPixels}
91
+ >
92
+ {icon && (
93
+ <Icon
94
+ name={icon}
95
+ className={classNames('confirmation-modal-icon', `color-${status}`)}
96
+ size="xl"
97
+ />
98
+ )}
99
+ {title && <h2 className="confirmation-modal-title">{title}</h2>}
100
+ {children}
101
+ </Modal>
102
+ );
103
+ };
@@ -5,17 +5,85 @@ import * as Modal from './Modal.stories';
5
5
 
6
6
  # Modal
7
7
  The Modal component provides us with a way to display content in a modal window. It can be used as a confirmation modal or as a content modal. It extends the react-modal library. For more information on props not listed here, please visit the [react-modal documentation](https://github.com/reactjs/react-modal).
8
- <Canvas of={Modal.Default} />
9
-
8
+ <Canvas of={Modal.Default} source={{
9
+ code: `
10
+ const [isOpen, setIsOpen] = useState<boolean>(false);
11
+
12
+ const handleOpen = () => {
13
+ setIsOpen(true);
14
+ };
15
+
16
+ const handleClose = () => {
17
+ setIsOpen(false);
18
+ };
19
+
20
+ const footer = (
21
+ <>
22
+ <Row gutterWidth={12} justify="end" align="center">
23
+ <Col xs="content">
24
+ <Button onClick={handleClose} ariaLabel="Cancel" variant="outline">
25
+ Cancel
26
+ </Button>
27
+ </Col>
28
+ <Col xs="content">
29
+ <Button onClick={handleClose} ariaLabel="Confirm" variant="solid">
30
+ Confirm
31
+ </Button>
32
+ </Col>
33
+ </Row>
34
+ </>
35
+ );
36
+
37
+ return (
38
+ <Container>
39
+ <Row>
40
+ <Col sm={4}>
41
+ <Modal
42
+ title: 'The Legend of Zelda',
43
+ subtitle: 'A timeless tale of courage and adventure',
44
+ className: '',
45
+ maxWidthInPixels: 800,
46
+ onRequestClose: () => {
47
+ console.log('closed');
48
+ },
49
+ shouldCloseOnOverlayClick: true,
50
+ shouldCloseOnEsc: true,
51
+ >
52
+ <p className="mb-4">
53
+ In the mystical realm of Hyrule, where ancient magic flows through verdant fields
54
+ and towering mountains, a timeless tale unfolds across countless generations. The
55
+ legendary hero Link, eternally reborn when darkness threatens the land, stands as
56
+ the chosen wielder of the Master Sword and bearer of the Triforce of Courage.
57
+ Through the ages, he has faced the malevolent forces of Ganon, whose insatiable
58
+ desire for power has brought destruction and chaos to this peaceful kingdom.
59
+ </p>
60
+ <p className="mb-4">
61
+ Princess Zelda, blessed with the wisdom of the goddesses and keeper of the royal
62
+ bloodline, serves as both ruler and guardian of Hyrule's most sacred treasures.
63
+ Together with Link, she maintains the delicate balance between light and shadow,
64
+ protecting the realm from those who would seek to corrupt its divine power. From the
65
+ windswept peaks of Death Mountain to the depths of Lake Hylia, their epic saga
66
+ continues to inspire hope and courage in all who call Hyrule home, as they battle
67
+ against the forces of evil that would plunge their world into darkness.
68
+ </p>
69
+ </Modal>
70
+ <Button ariaLabel="open modal" onClick={handleOpen}>
71
+ Open modal
72
+ </Button>
73
+ </Col>
74
+ </Row>
75
+ </Container>
76
+ )
77
+ ` }} />
10
78
  ### The following props are available for the Modal component:
11
79
 
12
80
  <Controls of={Modal.Default} />
13
81
 
14
82
  ## Usage
15
- We have designed this modal with the intention for it to be used as a wrapper for content as you will see below. The recommended way to best use this component is to create your own wrapper in your application for specific modal needs. For example, you may create a wrapper component that displays a confirmation modal. You may also wish to create one for a stepper modal. The idea is that you wrap this modal in a component and build the body of your modal in that wrapped component so that you can reuse the modal component for different modal needs.
83
+ The modal component is designed to be an opinionated wrapper for your content. The header has conditionals that will reveal a horizontal line if there is atleast a title or a subtitle. The footer is also conditionally rendered based on the presence of a footer prop. If you need a simple confirmation modal, you can use the ConfirmationModal component. If you need something a little more custom, you should use this component.
16
84
 
17
85
  ### Parent Selector
18
- You may have an issue with styling and require the modal to be displayed in a specific part of your application. You can use the `parentSelector` prop to pass in a function that returns the element you want to use as the parent element for the modal. In insights we have the following example.
86
+ You may have an issue with styling and require the modal to be displayed in a specific part of your application. You can use the `parentSelector` prop to pass in a function that returns the element you want to use as the parent element for the modal. In insights we have the following example. This is baked into the modal as of the latst version.
19
87
 
20
88
  ```tsx
21
89
  const parentSelector = () =>
@@ -81,41 +149,3 @@ The content inside of the Modal tag will be displayed when you open the modal. A
81
149
  </div>
82
150
  </Modal>
83
151
  ```
84
-
85
- ### Confirmation Modal
86
- If you are looking for a confirmation modal, you can use the following as an example.
87
-
88
- ```tsx
89
- <Modal isOpen={isOpen} onRequestClose={handleClose}>
90
- <h2>Would you like to continue?</h2>
91
- <p>
92
- If you would like to proceed, please click the confirm button. Otherwise, click the
93
- cancel button.
94
- </p>
95
- <hr />
96
- <Row nogutter justify="end" align="center">
97
- <Col xs="content">
98
- <Button
99
- onClick={() => {
100
- console.log('cancelled');
101
- handleClose();
102
- }}
103
- className="mr-2"
104
- variant="outline"
105
- ariaLabel="Cancel"
106
- >
107
- Cancel
108
- </Button>
109
- <Button
110
- onClick={() => {
111
- console.log('confirmed');
112
- handleClose();
113
- }}
114
- ariaLabel="Confirm"
115
- >
116
- Confirm
117
- </Button>
118
- </Col>
119
- </Row>
120
- </Modal>
121
- ```
@@ -6,6 +6,7 @@ import { Button } from '../button';
6
6
  import { registerFontAwesomeIcons } from '@/setup/setupIcons';
7
7
  import { indiconDefinitions } from '@/components/icons/indicons';
8
8
  import { fas } from '@fortawesome/free-solid-svg-icons';
9
+ import { ConfirmationModal } from './ConfirmationModal';
9
10
 
10
11
  registerFontAwesomeIcons(...Object.values(fas), ...indiconDefinitions);
11
12
 
@@ -150,6 +151,99 @@ const meta: Meta = {
150
151
  disable: true,
151
152
  },
152
153
  },
154
+ title: {
155
+ control: 'text',
156
+ description: 'The title of the modal',
157
+ table: {
158
+ category: 'Props',
159
+ type: {
160
+ summary: 'string',
161
+ },
162
+ },
163
+ },
164
+ footer: {
165
+ control: false,
166
+ description: 'The footer of the modal. It accepts a React Component',
167
+ table: {
168
+ category: 'Props',
169
+ type: {
170
+ summary: 'React.ReactNode',
171
+ },
172
+ },
173
+ },
174
+ subtitle: {
175
+ control: 'text',
176
+ description: 'The subtitle of the modal',
177
+ table: {
178
+ category: 'Props',
179
+ type: {
180
+ summary: 'string',
181
+ },
182
+ },
183
+ },
184
+ maxWidthInPixels: {
185
+ control: 'number',
186
+ description: 'The maximum width of the modal in pixels',
187
+ table: {
188
+ category: 'Props',
189
+ type: {
190
+ summary: 'number',
191
+ },
192
+ },
193
+ },
194
+ confirmationButtonText: {
195
+ control: 'text',
196
+ description: 'The text of the confirmation button',
197
+ table: {
198
+ category: 'Confirmation Modal',
199
+ type: {
200
+ summary: 'string',
201
+ },
202
+ },
203
+ },
204
+ confirmationButtonVariant: {
205
+ control: 'select',
206
+ options: ['solid', 'outline', 'link', 'action', 'destructive', 'soft'],
207
+ description: 'The variant of the confirmation button. ',
208
+ table: {
209
+ category: 'Confirmation Modal',
210
+ type: {
211
+ summary: 'string',
212
+ },
213
+ },
214
+ },
215
+ cancelButtonText: {
216
+ control: 'text',
217
+ description: 'The text of the cancel button',
218
+ table: {
219
+ category: 'Confirmation Modal',
220
+ type: {
221
+ summary: 'string',
222
+ },
223
+ },
224
+ },
225
+ status: {
226
+ control: 'select',
227
+ options: ['info', 'success', 'error'],
228
+ description: 'The status of the modal. This will determine the color of the icon.',
229
+ table: {
230
+ category: 'Confirmation Modal',
231
+ type: {
232
+ summary: 'string',
233
+ },
234
+ },
235
+ },
236
+ icon: {
237
+ control: 'select',
238
+ options: Object.keys(indiconDefinitions),
239
+ description: 'The icon of the modal',
240
+ table: {
241
+ category: 'Confirmation Modal',
242
+ type: {
243
+ summary: 'string',
244
+ },
245
+ },
246
+ },
153
247
  },
154
248
  };
155
249
 
@@ -159,12 +253,17 @@ type Story = StoryObj<typeof Modal>;
159
253
 
160
254
  export const Default: Story = {
161
255
  args: {
256
+ title: 'The Legend of Zelda',
257
+ subtitle: 'A timeless tale of courage and adventure',
162
258
  className: '',
259
+ maxWidthInPixels: 800,
163
260
  onRequestClose: () => {
164
261
  console.log('closed');
165
262
  },
166
263
  shouldCloseOnOverlayClick: true,
167
264
  shouldCloseOnEsc: true,
265
+ parentSelector: () =>
266
+ document.getElementById('theme-root') || document.getElementById('root') || document.body,
168
267
  },
169
268
 
170
269
  render: (args) => {
@@ -178,12 +277,28 @@ export const Default: Story = {
178
277
  setIsOpen(false);
179
278
  };
180
279
 
280
+ const footer = (
281
+ <>
282
+ <Row gutterWidth={12} justify="end" align="center">
283
+ <Col xs="content">
284
+ <Button onClick={handleClose} ariaLabel="Cancel" variant="outline">
285
+ Cancel
286
+ </Button>
287
+ </Col>
288
+ <Col xs="content">
289
+ <Button onClick={handleClose} ariaLabel="Confirm" variant="solid">
290
+ Confirm
291
+ </Button>
292
+ </Col>
293
+ </Row>
294
+ </>
295
+ );
296
+
181
297
  return (
182
298
  <Container>
183
299
  <Row>
184
300
  <Col sm={4}>
185
- <Modal {...args} isOpen={isOpen} onRequestClose={handleClose}>
186
- <h2 className="mb-4">The Legend of Zelda</h2>
301
+ <Modal {...args} isOpen={isOpen} onRequestClose={handleClose} footer={footer}>
187
302
  <p className="mb-4">
188
303
  In the mystical realm of Hyrule, where ancient magic flows through verdant fields
189
304
  and towering mountains, a timeless tale unfolds across countless generations. The
@@ -201,12 +316,6 @@ export const Default: Story = {
201
316
  continues to inspire hope and courage in all who call Hyrule home, as they battle
202
317
  against the forces of evil that would plunge their world into darkness.
203
318
  </p>
204
- <div className="actions text-align--right">
205
- <hr />
206
- <Button onClick={handleClose} ariaLabel="Close" variant="outline">
207
- Close
208
- </Button>
209
- </div>
210
319
  </Modal>
211
320
  <Button ariaLabel="open modal" onClick={handleOpen}>
212
321
  Open modal
@@ -218,14 +327,22 @@ export const Default: Story = {
218
327
  },
219
328
  };
220
329
 
221
- export const ConfirmationModal: Story = {
330
+ export const ConfirmationModalStory: StoryObj<typeof ConfirmationModal> = {
222
331
  args: {
223
332
  className: '',
224
333
  onRequestClose: () => {
225
334
  console.log('closed');
226
335
  },
336
+ title: 'Disable User',
337
+ confirmationButtonText: 'Delete',
338
+ confirmationButtonVariant: 'destructive',
339
+ cancelButtonText: 'Cancel',
227
340
  shouldCloseOnOverlayClick: true,
341
+ icon: 'trash',
342
+ status: 'error',
228
343
  shouldCloseOnEsc: true,
344
+ parentSelector: () => document.body,
345
+ maxWidthInPixels: 600,
229
346
  },
230
347
 
231
348
  render: (args) => {
@@ -243,38 +360,12 @@ export const ConfirmationModal: Story = {
243
360
  <Container>
244
361
  <Row>
245
362
  <Col sm={4}>
246
- <Modal {...args} isOpen={isOpen} onRequestClose={handleClose}>
247
- <h2 className="mb-4">Would you like to continue?</h2>
248
- <p className="mb-4">
249
- If you would like to proceed, please click the confirm button. Otherwise, click the
250
- cancel button.
363
+ <ConfirmationModal {...args} isOpen={isOpen} onRequestClose={handleClose}>
364
+ <p>
365
+ Deleting user will no longer allow this person to have access to this organization.
366
+ You can always invite them again if you change your mind.
251
367
  </p>
252
- <hr />
253
- <Row nogutter justify="end" align="center">
254
- <Col xs="content">
255
- <Button
256
- onClick={() => {
257
- console.log('cancelled');
258
- handleClose();
259
- }}
260
- className="mr-2"
261
- variant="outline"
262
- ariaLabel="Cancel"
263
- >
264
- Cancel
265
- </Button>
266
- <Button
267
- onClick={() => {
268
- console.log('confirmed');
269
- handleClose();
270
- }}
271
- ariaLabel="Confirm"
272
- >
273
- Confirm
274
- </Button>
275
- </Col>
276
- </Row>
277
- </Modal>
368
+ </ConfirmationModal>
278
369
  <Button ariaLabel="open modal" onClick={handleOpen}>
279
370
  Open modal
280
371
  </Button>
@@ -20,15 +20,20 @@ export const Modal = ({
20
20
  contentElement,
21
21
  overlayElement,
22
22
  position = 'center',
23
- parentSelector,
23
+ parentSelector = () => document.getElementById('theme-root') || document.getElementById('root')!, // default for our apps, storybook needs a different one
24
+ title,
25
+ subtitle,
26
+ footer,
27
+ maxWidthInPixels,
24
28
  ...rest
25
29
  }: ModalProps) => {
26
30
  const modalClasses = classNames('modal', `modal--${position}`, className);
27
31
  const overlayClasses = classNames('modal-overlay', overlayClassName);
28
32
 
33
+ const hasHeader = title || subtitle;
34
+
29
35
  return (
30
36
  <ReactModal
31
- style={{}}
32
37
  className={modalClasses}
33
38
  overlayClassName={overlayClasses}
34
39
  testId={testId}
@@ -43,7 +48,7 @@ export const Modal = ({
43
48
  overlayElement={overlayElement}
44
49
  {...rest}
45
50
  >
46
- <div className="modal-content">
51
+ <div className="modal-content" style={{ maxWidth: `${maxWidthInPixels}px` }}>
47
52
  <Button
48
53
  className="modal-close-button"
49
54
  onClick={onRequestClose}
@@ -52,7 +57,19 @@ export const Modal = ({
52
57
  iconLeft="x-close"
53
58
  ariaLabel="Close"
54
59
  />
60
+ {hasHeader && (
61
+ <div className="modal-header">
62
+ <Row justify="between" align="center">
63
+ <Col>
64
+ {title && <h2 className="modal-title">{title}</h2>}
65
+ {subtitle && <p className="modal-subtitle">{subtitle}</p>}
66
+ </Col>
67
+ </Row>
68
+ </div>
69
+ )}
70
+
55
71
  <div className="modal-body">{children}</div>
72
+ {footer && <div className="modal-footer">{footer}</div>}
56
73
  </div>
57
74
  </ReactModal>
58
75
  );
@@ -57,4 +57,16 @@ describe('Modal', () => {
57
57
  const modal = screen.getByRole('dialog');
58
58
  expect(modal).toHaveClass('custom-modal');
59
59
  });
60
+
61
+ it('shows header when title is provided', () => {
62
+ render(<Modal {...defaultProps} title="Test Modal" />);
63
+ const header = screen.getByRole('heading');
64
+ expect(header).toBeInTheDocument();
65
+ });
66
+
67
+ it('shows header when subtitle is provided', () => {
68
+ render(<Modal {...defaultProps} subtitle="Test Modal" />);
69
+ const header = screen.getByRole('heading');
70
+ expect(header).toBeInTheDocument();
71
+ });
60
72
  });
@@ -1 +1,2 @@
1
1
  export { Modal } from './Modal';
2
+ export { ConfirmationModal } from './ConfirmationModal';