@sproutsocial/seeds-react-modal 1.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.
- package/.eslintignore +6 -0
- package/.eslintrc.js +4 -0
- package/.turbo/turbo-build.log +21 -0
- package/CHANGELOG.md +7 -0
- package/dist/esm/index.js +237 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/index.d.mts +76 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.js +274 -0
- package/dist/index.js.map +1 -0
- package/jest.config.js +9 -0
- package/package.json +52 -0
- package/src/Modal.stories.tsx +420 -0
- package/src/Modal.tsx +160 -0
- package/src/ModalTypes.ts +67 -0
- package/src/__tests__/Modal.test.tsx +134 -0
- package/src/__tests__/Modal.typetest.tsx +209 -0
- package/src/index.ts +5 -0
- package/src/styled.d.ts +7 -0
- package/src/styles.tsx +141 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +12 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import React, { useState, type FC } from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import { Box } from "@sproutsocial/seeds-react-box";
|
|
4
|
+
import { Button } from "@sproutsocial/seeds-react-button";
|
|
5
|
+
import { FormField } from "@sproutsocial/seeds-react-form-field";
|
|
6
|
+
import { Input } from "@sproutsocial/seeds-react-input";
|
|
7
|
+
import { Text } from "@sproutsocial/seeds-react-text";
|
|
8
|
+
import Modal from "./Modal";
|
|
9
|
+
|
|
10
|
+
export interface StatefulStoryProps<T> {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
12
|
+
children: FC<{ state: T; setState: (state: T) => void }>;
|
|
13
|
+
initialState: T;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function StatefulStory<T>({
|
|
17
|
+
children,
|
|
18
|
+
initialState,
|
|
19
|
+
}: StatefulStoryProps<T>) {
|
|
20
|
+
const [state, setState] = useState<T>(initialState);
|
|
21
|
+
return children({ state, setState });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const meta: Meta<typeof Modal> = {
|
|
25
|
+
title: "Components/Modal",
|
|
26
|
+
component: Modal,
|
|
27
|
+
};
|
|
28
|
+
export default meta;
|
|
29
|
+
|
|
30
|
+
type Story = StoryObj<typeof Modal>;
|
|
31
|
+
|
|
32
|
+
export const Default: Story = {
|
|
33
|
+
render: () => (
|
|
34
|
+
<StatefulStory
|
|
35
|
+
initialState={{
|
|
36
|
+
isOpen: false,
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
{({ setState, state }) => (
|
|
40
|
+
<div>
|
|
41
|
+
<Button
|
|
42
|
+
appearance="primary"
|
|
43
|
+
onClick={() =>
|
|
44
|
+
setState({
|
|
45
|
+
isOpen: !state.isOpen,
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
>
|
|
49
|
+
Open Modal
|
|
50
|
+
</Button>
|
|
51
|
+
<Box height="2000px" color="text.body">
|
|
52
|
+
Really tall box.
|
|
53
|
+
</Box>
|
|
54
|
+
<Modal
|
|
55
|
+
appElementSelector="#root"
|
|
56
|
+
isOpen={state.isOpen}
|
|
57
|
+
onClose={() =>
|
|
58
|
+
setState({
|
|
59
|
+
isOpen: !state.isOpen,
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
closeButtonLabel="Close this dialog"
|
|
63
|
+
label="Example Modal"
|
|
64
|
+
>
|
|
65
|
+
<React.Fragment>
|
|
66
|
+
<Modal.Header
|
|
67
|
+
title="Assign Chatbot"
|
|
68
|
+
subtitle="The chatbot will respond to customers from this profile."
|
|
69
|
+
/>
|
|
70
|
+
<Modal.Content>
|
|
71
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
|
72
|
+
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
|
73
|
+
{/* ...existing content... */}
|
|
74
|
+
</Modal.Content>
|
|
75
|
+
<Modal.Footer>
|
|
76
|
+
<Button appearance="primary" width={1}>
|
|
77
|
+
Full-Width Button
|
|
78
|
+
</Button>
|
|
79
|
+
</Modal.Footer>
|
|
80
|
+
</React.Fragment>
|
|
81
|
+
</Modal>
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
84
|
+
</StatefulStory>
|
|
85
|
+
),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const NotCloseable: Story = {
|
|
89
|
+
render: () => (
|
|
90
|
+
<StatefulStory
|
|
91
|
+
initialState={{
|
|
92
|
+
isOpen: false,
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
{({ setState, state }) => (
|
|
96
|
+
<div>
|
|
97
|
+
<Button
|
|
98
|
+
appearance="primary"
|
|
99
|
+
onClick={() =>
|
|
100
|
+
setState({
|
|
101
|
+
isOpen: !state.isOpen,
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
>
|
|
105
|
+
Open Modal
|
|
106
|
+
</Button>
|
|
107
|
+
<Box height="2000px" color="text.body">
|
|
108
|
+
Really tall box.
|
|
109
|
+
</Box>
|
|
110
|
+
<Modal
|
|
111
|
+
appElementSelector="#root"
|
|
112
|
+
isOpen={state.isOpen}
|
|
113
|
+
closeButtonLabel="n/a"
|
|
114
|
+
label="Example Modal"
|
|
115
|
+
>
|
|
116
|
+
<React.Fragment>
|
|
117
|
+
<Modal.Content>
|
|
118
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
|
119
|
+
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
|
120
|
+
{/* ...existing content... */}
|
|
121
|
+
</Modal.Content>
|
|
122
|
+
<Modal.Footer>
|
|
123
|
+
<Button
|
|
124
|
+
appearance="primary"
|
|
125
|
+
width={1}
|
|
126
|
+
onClick={() =>
|
|
127
|
+
setState({
|
|
128
|
+
isOpen: !state.isOpen,
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
>
|
|
132
|
+
Must click to close
|
|
133
|
+
</Button>
|
|
134
|
+
</Modal.Footer>
|
|
135
|
+
</React.Fragment>
|
|
136
|
+
</Modal>
|
|
137
|
+
</div>
|
|
138
|
+
)}
|
|
139
|
+
</StatefulStory>
|
|
140
|
+
),
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export const CustomHeader: Story = {
|
|
144
|
+
render: () => (
|
|
145
|
+
<StatefulStory
|
|
146
|
+
initialState={{
|
|
147
|
+
isOpen: false,
|
|
148
|
+
}}
|
|
149
|
+
>
|
|
150
|
+
{({ setState, state }) => (
|
|
151
|
+
<div>
|
|
152
|
+
<Button
|
|
153
|
+
appearance="primary"
|
|
154
|
+
onClick={() =>
|
|
155
|
+
setState({
|
|
156
|
+
isOpen: !state.isOpen,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
>
|
|
160
|
+
Open Modal
|
|
161
|
+
</Button>
|
|
162
|
+
<Modal
|
|
163
|
+
appElementSelector="#root"
|
|
164
|
+
isOpen={state.isOpen}
|
|
165
|
+
onClose={() =>
|
|
166
|
+
setState({
|
|
167
|
+
isOpen: !state.isOpen,
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
closeButtonLabel="Close this dialog"
|
|
171
|
+
label="Example Modal"
|
|
172
|
+
>
|
|
173
|
+
<React.Fragment>
|
|
174
|
+
<Modal.Header title="" subtitle="" bordered>
|
|
175
|
+
<Box width="100%" bg="purple.400">
|
|
176
|
+
Custom header
|
|
177
|
+
</Box>
|
|
178
|
+
</Modal.Header>
|
|
179
|
+
<Modal.Content>
|
|
180
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
|
181
|
+
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
|
182
|
+
{/* ...existing content... */}
|
|
183
|
+
</Modal.Content>
|
|
184
|
+
<Modal.Footer>
|
|
185
|
+
<Button appearance="primary" width={1}>
|
|
186
|
+
Full-Width Button
|
|
187
|
+
</Button>
|
|
188
|
+
</Modal.Footer>
|
|
189
|
+
</React.Fragment>
|
|
190
|
+
</Modal>
|
|
191
|
+
</div>
|
|
192
|
+
)}
|
|
193
|
+
</StatefulStory>
|
|
194
|
+
),
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export const NoFooter: Story = {
|
|
198
|
+
render: () => (
|
|
199
|
+
<StatefulStory
|
|
200
|
+
initialState={{
|
|
201
|
+
isOpen: false,
|
|
202
|
+
}}
|
|
203
|
+
>
|
|
204
|
+
{({ setState, state }) => (
|
|
205
|
+
<div>
|
|
206
|
+
<Button
|
|
207
|
+
appearance="primary"
|
|
208
|
+
onClick={() =>
|
|
209
|
+
setState({
|
|
210
|
+
isOpen: !state.isOpen,
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
>
|
|
214
|
+
Open Modal
|
|
215
|
+
</Button>
|
|
216
|
+
<Modal
|
|
217
|
+
appElementSelector="#root"
|
|
218
|
+
isOpen={state.isOpen}
|
|
219
|
+
onClose={() =>
|
|
220
|
+
setState({
|
|
221
|
+
isOpen: !state.isOpen,
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
closeButtonLabel="Close this dialog"
|
|
225
|
+
label="Example Modal"
|
|
226
|
+
>
|
|
227
|
+
<React.Fragment>
|
|
228
|
+
<Modal.Header bordered>
|
|
229
|
+
<Box>
|
|
230
|
+
<Text as="h1" fontSize={400} fontWeight="semibold">
|
|
231
|
+
Assign Chatbot
|
|
232
|
+
</Text>
|
|
233
|
+
<Text as="div" fontSize={200}>
|
|
234
|
+
The chatbot will respond to customers from this profile.
|
|
235
|
+
</Text>
|
|
236
|
+
</Box>
|
|
237
|
+
<Box>
|
|
238
|
+
<button>dummy button 1</button>
|
|
239
|
+
<button>dummy button 2</button>
|
|
240
|
+
<Modal.CloseButton />
|
|
241
|
+
</Box>
|
|
242
|
+
</Modal.Header>
|
|
243
|
+
<Modal.Content>
|
|
244
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
|
245
|
+
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
|
246
|
+
{/* ...existing content... */}
|
|
247
|
+
</Modal.Content>
|
|
248
|
+
</React.Fragment>
|
|
249
|
+
</Modal>
|
|
250
|
+
</div>
|
|
251
|
+
)}
|
|
252
|
+
</StatefulStory>
|
|
253
|
+
),
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
export const NoTitle: Story = {
|
|
257
|
+
render: () => (
|
|
258
|
+
<StatefulStory
|
|
259
|
+
initialState={{
|
|
260
|
+
isOpen: false,
|
|
261
|
+
}}
|
|
262
|
+
>
|
|
263
|
+
{({ setState, state }) => (
|
|
264
|
+
<div>
|
|
265
|
+
<Button
|
|
266
|
+
appearance="primary"
|
|
267
|
+
onClick={() =>
|
|
268
|
+
setState({
|
|
269
|
+
isOpen: !state.isOpen,
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
>
|
|
273
|
+
Open Modal
|
|
274
|
+
</Button>
|
|
275
|
+
<Modal
|
|
276
|
+
appElementSelector="#root"
|
|
277
|
+
isOpen={state.isOpen}
|
|
278
|
+
onClose={() =>
|
|
279
|
+
setState({
|
|
280
|
+
isOpen: !state.isOpen,
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
closeButtonLabel="Close this dialog"
|
|
284
|
+
label="Example Modal"
|
|
285
|
+
>
|
|
286
|
+
<React.Fragment>
|
|
287
|
+
<Modal.Header />
|
|
288
|
+
<Modal.Content>
|
|
289
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
|
290
|
+
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
|
291
|
+
{/* ...existing content... */}
|
|
292
|
+
</Modal.Content>
|
|
293
|
+
<Modal.Footer>
|
|
294
|
+
<Button appearance="primary" width={1}>
|
|
295
|
+
Full-Width Button
|
|
296
|
+
</Button>
|
|
297
|
+
</Modal.Footer>
|
|
298
|
+
</React.Fragment>
|
|
299
|
+
</Modal>
|
|
300
|
+
</div>
|
|
301
|
+
)}
|
|
302
|
+
</StatefulStory>
|
|
303
|
+
),
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
export const WithForm: Story = {
|
|
307
|
+
render: () => (
|
|
308
|
+
<StatefulStory
|
|
309
|
+
initialState={{
|
|
310
|
+
isOpen: false,
|
|
311
|
+
}}
|
|
312
|
+
>
|
|
313
|
+
{({ setState, state }) => (
|
|
314
|
+
<div>
|
|
315
|
+
<Button
|
|
316
|
+
appearance="primary"
|
|
317
|
+
onClick={() =>
|
|
318
|
+
setState({
|
|
319
|
+
isOpen: !state.isOpen,
|
|
320
|
+
})
|
|
321
|
+
}
|
|
322
|
+
>
|
|
323
|
+
Open Modal
|
|
324
|
+
</Button>
|
|
325
|
+
<Modal
|
|
326
|
+
width="500px"
|
|
327
|
+
appElementSelector="#root"
|
|
328
|
+
isOpen={state.isOpen}
|
|
329
|
+
onClose={() =>
|
|
330
|
+
setState({
|
|
331
|
+
isOpen: !state.isOpen,
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
closeButtonLabel="Close this dialog"
|
|
335
|
+
label="Example Modal"
|
|
336
|
+
>
|
|
337
|
+
<React.Fragment>
|
|
338
|
+
<Modal.Header
|
|
339
|
+
title="Create Share Link"
|
|
340
|
+
subtitle="Anyone with this link will be able to view its contents."
|
|
341
|
+
/>
|
|
342
|
+
<Modal.Content>
|
|
343
|
+
<FormField
|
|
344
|
+
label="Label"
|
|
345
|
+
helperText="This is some helpful helper text"
|
|
346
|
+
>
|
|
347
|
+
{
|
|
348
|
+
// is there a reason that the props are not passed to the input?
|
|
349
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
350
|
+
(props) => <Input name="title" id="title" />
|
|
351
|
+
}
|
|
352
|
+
</FormField>
|
|
353
|
+
</Modal.Content>
|
|
354
|
+
<Modal.Footer>
|
|
355
|
+
<Box display="flex" justifyContent="flex-end">
|
|
356
|
+
<Button appearance="primary">Create Link</Button>
|
|
357
|
+
</Box>
|
|
358
|
+
</Modal.Footer>
|
|
359
|
+
</React.Fragment>
|
|
360
|
+
</Modal>
|
|
361
|
+
</div>
|
|
362
|
+
)}
|
|
363
|
+
</StatefulStory>
|
|
364
|
+
),
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
export const CustomBackgroundColor: Story = {
|
|
368
|
+
render: () => (
|
|
369
|
+
<StatefulStory
|
|
370
|
+
initialState={{
|
|
371
|
+
isOpen: false,
|
|
372
|
+
}}
|
|
373
|
+
>
|
|
374
|
+
{({ setState, state }) => (
|
|
375
|
+
<div>
|
|
376
|
+
<Button
|
|
377
|
+
appearance="primary"
|
|
378
|
+
onClick={() =>
|
|
379
|
+
setState({
|
|
380
|
+
isOpen: !state.isOpen,
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
>
|
|
384
|
+
Open Modal
|
|
385
|
+
</Button>
|
|
386
|
+
<Modal
|
|
387
|
+
bg="container.background.decorative.purple"
|
|
388
|
+
width="500px"
|
|
389
|
+
appElementSelector="#root"
|
|
390
|
+
isOpen={state.isOpen}
|
|
391
|
+
onClose={() =>
|
|
392
|
+
setState({
|
|
393
|
+
isOpen: !state.isOpen,
|
|
394
|
+
})
|
|
395
|
+
}
|
|
396
|
+
closeButtonLabel="Close this dialog"
|
|
397
|
+
label="Example Modal"
|
|
398
|
+
>
|
|
399
|
+
<React.Fragment>
|
|
400
|
+
<Modal.Header
|
|
401
|
+
title="Create Share Link"
|
|
402
|
+
subtitle="Anyone with this link will be able to view its contents."
|
|
403
|
+
/>
|
|
404
|
+
<Modal.Content>
|
|
405
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
|
406
|
+
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
|
407
|
+
{/* ...existing content... */}
|
|
408
|
+
</Modal.Content>
|
|
409
|
+
<Modal.Footer>
|
|
410
|
+
<Box display="flex" justifyContent="flex-end">
|
|
411
|
+
<Button appearance="primary">Create Link</Button>
|
|
412
|
+
</Box>
|
|
413
|
+
</Modal.Footer>
|
|
414
|
+
</React.Fragment>
|
|
415
|
+
</Modal>
|
|
416
|
+
</div>
|
|
417
|
+
)}
|
|
418
|
+
</StatefulStory>
|
|
419
|
+
),
|
|
420
|
+
};
|
package/src/Modal.tsx
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useContext } from "react";
|
|
3
|
+
import Box from "@sproutsocial/seeds-react-box";
|
|
4
|
+
import Button from "@sproutsocial/seeds-react-button";
|
|
5
|
+
import Icon from "@sproutsocial/seeds-react-icon";
|
|
6
|
+
import Text from "@sproutsocial/seeds-react-text";
|
|
7
|
+
import { Container, Content, Header, Footer, Body } from "./styles";
|
|
8
|
+
import type {
|
|
9
|
+
TypeModalProps,
|
|
10
|
+
TypeModalCloseButtonProps,
|
|
11
|
+
TypeModalContentProps,
|
|
12
|
+
TypeModalFooterProps,
|
|
13
|
+
TypeModalHeaderProps,
|
|
14
|
+
} from "./ModalTypes";
|
|
15
|
+
|
|
16
|
+
type TypeModalContext = Partial<{
|
|
17
|
+
onClose: () => void;
|
|
18
|
+
closeButtonLabel: string;
|
|
19
|
+
label: string;
|
|
20
|
+
}>;
|
|
21
|
+
|
|
22
|
+
const ModalContext = React.createContext<TypeModalContext>({});
|
|
23
|
+
|
|
24
|
+
const ModalHeader = (props: TypeModalHeaderProps) => {
|
|
25
|
+
const { title, subtitle, children, bordered, ...rest } = props;
|
|
26
|
+
return (
|
|
27
|
+
<Header bordered={title || subtitle || bordered} {...rest}>
|
|
28
|
+
{children ? (
|
|
29
|
+
children
|
|
30
|
+
) : (
|
|
31
|
+
<React.Fragment>
|
|
32
|
+
<Box>
|
|
33
|
+
{title && (
|
|
34
|
+
<Text as="h1" fontSize={400} fontWeight="semibold">
|
|
35
|
+
{title}
|
|
36
|
+
</Text>
|
|
37
|
+
)}
|
|
38
|
+
{subtitle && (
|
|
39
|
+
<Text as="div" fontSize={200}>
|
|
40
|
+
{subtitle}
|
|
41
|
+
</Text>
|
|
42
|
+
)}
|
|
43
|
+
</Box>
|
|
44
|
+
<Box display="flex" alignItems="center" justify-content="flex-end">
|
|
45
|
+
<ModalCloseButton ml={400} />
|
|
46
|
+
</Box>
|
|
47
|
+
</React.Fragment>
|
|
48
|
+
)}
|
|
49
|
+
</Header>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const ModalCloseButton = (props: TypeModalCloseButtonProps) => {
|
|
54
|
+
const { onClose, closeButtonLabel } = useContext(ModalContext);
|
|
55
|
+
if (!onClose) return null;
|
|
56
|
+
return (
|
|
57
|
+
<Button onClick={onClose} {...props}>
|
|
58
|
+
<Icon name="x-outline" ariaLabel={closeButtonLabel} />
|
|
59
|
+
</Button>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const ModalFooter = (props: TypeModalFooterProps) => (
|
|
64
|
+
<Footer borderTop={500} borderColor="container.border.base" {...props} />
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
ModalFooter.defaultProps = {
|
|
68
|
+
bg: "container.background.base",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const ModalContent = React.forwardRef(
|
|
72
|
+
({ children, ...rest }: TypeModalContentProps, ref) => {
|
|
73
|
+
const { label } = useContext(ModalContext);
|
|
74
|
+
return (
|
|
75
|
+
<Content data-qa-modal data-qa-label={label} ref={ref} {...rest}>
|
|
76
|
+
{children}
|
|
77
|
+
</Content>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The modal you want
|
|
84
|
+
*/
|
|
85
|
+
const Modal = (props: TypeModalProps) => {
|
|
86
|
+
const {
|
|
87
|
+
appElementSelector,
|
|
88
|
+
children,
|
|
89
|
+
isOpen,
|
|
90
|
+
label,
|
|
91
|
+
onClose,
|
|
92
|
+
closeButtonLabel,
|
|
93
|
+
width,
|
|
94
|
+
zIndex,
|
|
95
|
+
data = {},
|
|
96
|
+
...rest
|
|
97
|
+
} = props;
|
|
98
|
+
|
|
99
|
+
const isCloseable = Boolean(onClose);
|
|
100
|
+
const appElement =
|
|
101
|
+
appElementSelector && document
|
|
102
|
+
? (document.querySelector(appElementSelector) as HTMLElement)
|
|
103
|
+
: undefined;
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<Container
|
|
107
|
+
appElement={appElement}
|
|
108
|
+
ariaHideApp={!!appElement}
|
|
109
|
+
isOpen={isOpen}
|
|
110
|
+
contentLabel={label}
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
112
|
+
onRequestClose={onClose || (() => {})}
|
|
113
|
+
shouldFocusAfterRender={true}
|
|
114
|
+
shouldCloseOnOverlayClick={isCloseable}
|
|
115
|
+
shouldCloseOnEsc={isCloseable}
|
|
116
|
+
shouldReturnFocusAfterClose={true}
|
|
117
|
+
closeTimeoutMS={200}
|
|
118
|
+
role="dialog"
|
|
119
|
+
width={width}
|
|
120
|
+
zIndex={zIndex}
|
|
121
|
+
data={{
|
|
122
|
+
"qa-modal": "",
|
|
123
|
+
"qa-modal-isopen": isOpen,
|
|
124
|
+
...data,
|
|
125
|
+
}}
|
|
126
|
+
{...rest}
|
|
127
|
+
>
|
|
128
|
+
<React.Fragment>
|
|
129
|
+
<Body />
|
|
130
|
+
|
|
131
|
+
<ModalContext.Provider
|
|
132
|
+
value={{
|
|
133
|
+
onClose,
|
|
134
|
+
closeButtonLabel,
|
|
135
|
+
label,
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
{children}
|
|
139
|
+
</ModalContext.Provider>
|
|
140
|
+
</React.Fragment>
|
|
141
|
+
</Container>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
Modal.defaultProps = {
|
|
146
|
+
width: "800px",
|
|
147
|
+
zIndex: 6,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
ModalHeader.displayName = "Modal.Header";
|
|
151
|
+
ModalFooter.displayName = "Modal.Footer";
|
|
152
|
+
ModalContent.displayName = "Modal.Content";
|
|
153
|
+
ModalCloseButton.displayName = "Modal.CloseButton";
|
|
154
|
+
|
|
155
|
+
Modal.Header = ModalHeader;
|
|
156
|
+
Modal.Footer = ModalFooter;
|
|
157
|
+
Modal.Content = ModalContent;
|
|
158
|
+
Modal.CloseButton = ModalCloseButton;
|
|
159
|
+
|
|
160
|
+
export default Modal;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import ReactModal from "react-modal";
|
|
3
|
+
import type {
|
|
4
|
+
TypeBoxProps,
|
|
5
|
+
TypeContainerProps,
|
|
6
|
+
} from "@sproutsocial/seeds-react-box";
|
|
7
|
+
import type { TypeButtonProps } from "@sproutsocial/seeds-react-button";
|
|
8
|
+
|
|
9
|
+
export interface TypeModalCloseButtonProps
|
|
10
|
+
extends Omit<TypeButtonProps, "children"> {
|
|
11
|
+
children?: void | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface TypeModalHeaderProps extends TypeBoxProps {
|
|
15
|
+
title?: string;
|
|
16
|
+
subtitle?: string;
|
|
17
|
+
|
|
18
|
+
/** Passing children will override the default modal header */
|
|
19
|
+
children?: React.ReactNode;
|
|
20
|
+
|
|
21
|
+
/** If you're rendering a custom header, you can use this prop to add a bottom border */
|
|
22
|
+
bordered?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TypeModalFooterProps extends TypeBoxProps {
|
|
26
|
+
bg?: string;
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface TypeModalContentProps extends TypeBoxProps {
|
|
31
|
+
children?: React.ReactNode;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TypeModalProps
|
|
35
|
+
extends TypeContainerProps,
|
|
36
|
+
// @ts-notes - onClose is an alias for onRequestClose so we don't need to include it here
|
|
37
|
+
Omit<ReactModal.Props, keyof TypeContainerProps | "onRequestClose"> {
|
|
38
|
+
/** section of app to aria hide for the modal */
|
|
39
|
+
appElementSelector?: string;
|
|
40
|
+
|
|
41
|
+
/** trigger to open or close the modal */
|
|
42
|
+
isOpen: boolean;
|
|
43
|
+
|
|
44
|
+
/** label for screen readers to announce the modal */
|
|
45
|
+
label: string;
|
|
46
|
+
|
|
47
|
+
/** body content of the modal */
|
|
48
|
+
children: React.ReactNode;
|
|
49
|
+
|
|
50
|
+
/** callback for close */
|
|
51
|
+
onClose?: () => void;
|
|
52
|
+
|
|
53
|
+
/** aria-label for modal X */
|
|
54
|
+
closeButtonLabel: string;
|
|
55
|
+
|
|
56
|
+
/** Controls the z-index CSS property */
|
|
57
|
+
zIndex?: number;
|
|
58
|
+
|
|
59
|
+
/** The max width of the modal container */
|
|
60
|
+
width?: string | number;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Custom attributes to be added to the modals container
|
|
64
|
+
* Each key will be prepended with "data-" when rendered in the DOM
|
|
65
|
+
*/
|
|
66
|
+
data?: Record<string, string | boolean | number>;
|
|
67
|
+
}
|