agroptima-design-system 0.31.7 → 0.31.9
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/package.json +1 -1
- package/src/atoms/DatePicker/DateRangePicker.tsx +1 -1
- package/src/atoms/DatePicker/DateSinglePicker.tsx +1 -1
- package/src/atoms/DatePicker/translations.ts +2 -6
- package/src/atoms/Modal/Modal.scss +15 -12
- package/src/atoms/Modal/Modal.tsx +3 -0
- package/src/atoms/Modal/ModalDialog.tsx +22 -26
- package/src/stories/Changelog.mdx +8 -0
- package/src/stories/Modal.stories.js +327 -212
- package/tests/Modal.spec.tsx +24 -8
- package/tests/a11y.spec.tsx +5 -0
package/package.json
CHANGED
|
@@ -88,7 +88,7 @@ function Footer({
|
|
|
88
88
|
)
|
|
89
89
|
.replace('${to}', formatDatePickerFooterDate(selected?.to, lng as string))
|
|
90
90
|
}
|
|
91
|
-
return translations[lng].
|
|
91
|
+
return translations[lng].selectedDate.replace(
|
|
92
92
|
'${from}',
|
|
93
93
|
formatDatePickerFooterDate(selected?.from, lng as string),
|
|
94
94
|
)
|
|
@@ -54,7 +54,7 @@ export function DateSinglePicker({
|
|
|
54
54
|
)
|
|
55
55
|
}
|
|
56
56
|
function Footer({ lng, selected }: { selected?: Date; lng: Locale }): string {
|
|
57
|
-
if (!selected) return translations[lng].
|
|
57
|
+
if (!selected) return translations[lng].pickDate
|
|
58
58
|
|
|
59
59
|
return translations[lng].selectedDate.replace(
|
|
60
60
|
'${date}',
|
|
@@ -18,17 +18,13 @@ export type Locale = keyof typeof availableLocales
|
|
|
18
18
|
|
|
19
19
|
export const translations: Translation = {
|
|
20
20
|
en: {
|
|
21
|
-
pickDate: 'Pick a
|
|
22
|
-
pickSingleDate: 'Pick a date',
|
|
21
|
+
pickDate: 'Pick a date or a range of dates',
|
|
23
22
|
selectedDate: 'Selected date: ${date}',
|
|
24
23
|
selectedRangeOfDates: 'Selected dates range: from ${from} to ${to}',
|
|
25
|
-
selectedOnlyFrom: 'Selected dates range: from ${from}',
|
|
26
24
|
},
|
|
27
25
|
es: {
|
|
28
|
-
pickDate: 'Selecciona
|
|
29
|
-
pickSingleDate: 'Selecciona una fecha',
|
|
26
|
+
pickDate: 'Selecciona una fecha o un rango de fechas',
|
|
30
27
|
selectedDate: 'Fecha seleccionada: ${date}',
|
|
31
28
|
selectedRangeOfDates: 'Rango de fechas seleccionado: desde ${from} a ${to}',
|
|
32
|
-
selectedOnlyFrom: 'Rango de fechas seleccionado: desde ${from}',
|
|
33
29
|
},
|
|
34
30
|
}
|
|
@@ -27,6 +27,21 @@ $backdrop-background-color: color_alias.$neutral-color-900;
|
|
|
27
27
|
overflow-x: hidden;
|
|
28
28
|
overflow-y: auto;
|
|
29
29
|
outline: 0;
|
|
30
|
+
border: 0px;
|
|
31
|
+
background-color: transparent;
|
|
32
|
+
margin: 0 auto;
|
|
33
|
+
|
|
34
|
+
&::backdrop {
|
|
35
|
+
opacity: $backdrop-opacity;
|
|
36
|
+
position: fixed;
|
|
37
|
+
top: 0;
|
|
38
|
+
left: 0;
|
|
39
|
+
z-index: depth.$z-modal;
|
|
40
|
+
width: 100vw;
|
|
41
|
+
height: 100vh;
|
|
42
|
+
background-color: $backdrop-background-color;
|
|
43
|
+
transition: opacity 0.15s linear;
|
|
44
|
+
}
|
|
30
45
|
}
|
|
31
46
|
|
|
32
47
|
.modal-dialog {
|
|
@@ -88,18 +103,6 @@ $backdrop-background-color: color_alias.$neutral-color-900;
|
|
|
88
103
|
gap: config.$space-2x;
|
|
89
104
|
}
|
|
90
105
|
|
|
91
|
-
.modal-backdrop {
|
|
92
|
-
opacity: $backdrop-opacity;
|
|
93
|
-
position: fixed;
|
|
94
|
-
top: 0;
|
|
95
|
-
left: 0;
|
|
96
|
-
z-index: depth.$z-modal;
|
|
97
|
-
width: 100vw;
|
|
98
|
-
height: 100vh;
|
|
99
|
-
background-color: $backdrop-background-color;
|
|
100
|
-
transition: opacity 0.15s linear;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
106
|
.modal-details {
|
|
104
107
|
.modal-dialog {
|
|
105
108
|
max-width: 50rem;
|
|
@@ -26,6 +26,7 @@ export interface ModalProps {
|
|
|
26
26
|
variant?: Variant
|
|
27
27
|
scrollable?: boolean
|
|
28
28
|
className?: string
|
|
29
|
+
isOpen?: boolean
|
|
29
30
|
onClose?: () => void
|
|
30
31
|
buttons: ButtonProps[]
|
|
31
32
|
children: ReactNode
|
|
@@ -43,6 +44,7 @@ export function Modal({
|
|
|
43
44
|
id,
|
|
44
45
|
title,
|
|
45
46
|
buttons,
|
|
47
|
+
isOpen = true,
|
|
46
48
|
onClose,
|
|
47
49
|
children,
|
|
48
50
|
variant = 'details',
|
|
@@ -54,6 +56,7 @@ export function Modal({
|
|
|
54
56
|
<ModalDialog
|
|
55
57
|
aria-labelledby={`${id}-title`}
|
|
56
58
|
aria-describedby={`${id}-body`}
|
|
59
|
+
isOpen={isOpen}
|
|
57
60
|
onClose={onClose}
|
|
58
61
|
details={isDetails}
|
|
59
62
|
{...props}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
import './Modal.scss'
|
|
3
|
-
import React, {
|
|
3
|
+
import React, { useEffect, useRef } from 'react'
|
|
4
4
|
import { classNames } from '../../utils/classNames'
|
|
5
5
|
|
|
6
|
-
export interface ModalDialogProps
|
|
6
|
+
export interface ModalDialogProps
|
|
7
|
+
extends React.HTMLAttributes<HTMLDialogElement> {
|
|
8
|
+
isOpen?: boolean
|
|
7
9
|
onClose?: () => void
|
|
8
10
|
details?: boolean
|
|
9
11
|
scrollable?: boolean
|
|
@@ -11,12 +13,14 @@ export interface ModalDialogProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
11
13
|
|
|
12
14
|
export function ModalDialog({
|
|
13
15
|
className,
|
|
16
|
+
isOpen = true,
|
|
14
17
|
onClose,
|
|
15
18
|
children,
|
|
16
19
|
details = false,
|
|
17
20
|
scrollable = false,
|
|
18
21
|
...props
|
|
19
22
|
}: ModalDialogProps) {
|
|
23
|
+
const dialogRef = useRef<HTMLDialogElement>(null)
|
|
20
24
|
const handleClick = (event: React.MouseEvent) => {
|
|
21
25
|
if (event.target !== event.currentTarget) return
|
|
22
26
|
onClose?.()
|
|
@@ -33,36 +37,28 @@ export function ModalDialog({
|
|
|
33
37
|
}, [])
|
|
34
38
|
|
|
35
39
|
useEffect(() => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
if (isOpen) {
|
|
41
|
+
dialogRef.current?.showModal()
|
|
42
|
+
} else {
|
|
43
|
+
dialogRef.current?.close()
|
|
40
44
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return () => {
|
|
44
|
-
document.removeEventListener('keydown', handleKeyDown)
|
|
45
|
-
}
|
|
46
|
-
}, [onClose])
|
|
45
|
+
}, [isOpen])
|
|
47
46
|
|
|
48
47
|
return (
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
<dialog
|
|
49
|
+
ref={dialogRef}
|
|
50
|
+
className={classNames('modal', className, { 'modal-details': details })}
|
|
51
|
+
onClick={handleClick}
|
|
52
|
+
{...props}
|
|
53
|
+
>
|
|
51
54
|
<div
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
{...props}
|
|
55
|
+
className={classNames('modal-dialog', {
|
|
56
|
+
'modal-dialog-scrollable': scrollable,
|
|
57
|
+
})}
|
|
56
58
|
>
|
|
57
|
-
<div
|
|
58
|
-
className={classNames('modal-dialog', {
|
|
59
|
-
'modal-dialog-scrollable': scrollable,
|
|
60
|
-
})}
|
|
61
|
-
>
|
|
62
|
-
<div className="modal-content">{children}</div>
|
|
63
|
-
</div>
|
|
59
|
+
<div className="modal-content">{children}</div>
|
|
64
60
|
</div>
|
|
65
|
-
|
|
61
|
+
</dialog>
|
|
66
62
|
)
|
|
67
63
|
}
|
|
68
64
|
|
|
@@ -4,6 +4,14 @@ import { Meta } from "@storybook/blocks";
|
|
|
4
4
|
|
|
5
5
|
# Changelog
|
|
6
6
|
|
|
7
|
+
## 0.31.9
|
|
8
|
+
|
|
9
|
+
* Change footer text for DateSinglePicker and DateRangePicker
|
|
10
|
+
|
|
11
|
+
## 0.31.8
|
|
12
|
+
|
|
13
|
+
* Refactor Modal component to use HTML dialog
|
|
14
|
+
|
|
7
15
|
## 0.31.7
|
|
8
16
|
|
|
9
17
|
* Fixed date clearing event in range and added a new translation to the footer for the first day of the selected range.
|
|
@@ -30,12 +30,16 @@ const meta = {
|
|
|
30
30
|
docs: {
|
|
31
31
|
description: {
|
|
32
32
|
component:
|
|
33
|
-
"<h2>Usage guidelines</h2><p>Modal component is used when requiring users to interact with the application, but without jumping to a new page and interrupting the user's workflow. It creates a new floating layer over the current page to get user feedback or display information.</p><ul><li>Require a response from the user</li><li>Notify the user of any related information</li><li>Confirm a user decision</li></ul>",
|
|
33
|
+
"<h2>Usage guidelines</h2><p>Modal component is used when requiring users to interact with the application, but without jumping to a new page and interrupting the user's workflow. It creates a new floating layer over the current page to get user feedback or display information.</p><ul><li>Require a response from the user</li><li>Notify the user of any related information</li><li>Confirm a user decision</li><li>It's opened/closed through `isOpen` prop. If we don't want it to be part of the DOM, we can also add a conditional render on the frontend project.</li><li>Natively, focus is set on the first nested focusable element and explicitly indicated by default by the browser</li><li>When nesting a Form inside Modal component, remember to add `type='button'` to all Cancel buttons to not to be considered as submitable</li></ul>",
|
|
34
34
|
},
|
|
35
35
|
},
|
|
36
36
|
figmaPrimaryDesign,
|
|
37
37
|
},
|
|
38
38
|
tags: ['autodocs'],
|
|
39
|
+
args: {
|
|
40
|
+
children: 'Modal',
|
|
41
|
+
isOpen: false,
|
|
42
|
+
},
|
|
39
43
|
argTypes: {
|
|
40
44
|
id: {
|
|
41
45
|
description: 'Id for aria purposes',
|
|
@@ -60,242 +64,243 @@ const meta = {
|
|
|
60
64
|
|
|
61
65
|
export default meta
|
|
62
66
|
|
|
67
|
+
const OpenAndCloseInfo = () => {
|
|
68
|
+
const [isOpen, setIsOpen] = React.useState(false)
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<>
|
|
72
|
+
<Modal
|
|
73
|
+
id="info-dangerous-alone"
|
|
74
|
+
isOpen={isOpen}
|
|
75
|
+
variant="info"
|
|
76
|
+
title="It's dangerous to go alone!"
|
|
77
|
+
buttons={[
|
|
78
|
+
{
|
|
79
|
+
label: 'Done',
|
|
80
|
+
onClick: () => alert('click'),
|
|
81
|
+
},
|
|
82
|
+
]}
|
|
83
|
+
>
|
|
84
|
+
Take this 🗡️
|
|
85
|
+
</Modal>
|
|
86
|
+
<Button label="Open Modal" onClick={() => setIsOpen(true)} />
|
|
87
|
+
</>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
63
91
|
export const Info = {
|
|
64
|
-
render: () =>
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
92
|
+
render: () => {
|
|
93
|
+
return <OpenAndCloseInfo />
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const OpenAndCloseSuccess = () => {
|
|
98
|
+
const [isOpen, setIsOpen] = React.useState(false)
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<>
|
|
102
|
+
<Modal
|
|
103
|
+
id="success-dangerous-alone"
|
|
104
|
+
isOpen={isOpen}
|
|
105
|
+
variant="success"
|
|
106
|
+
title="It's dangerous to go alone!"
|
|
107
|
+
buttons={[
|
|
108
|
+
{
|
|
109
|
+
label: 'Done',
|
|
110
|
+
onClick: () => alert('click'),
|
|
111
|
+
},
|
|
112
|
+
]}
|
|
113
|
+
>
|
|
114
|
+
Take this 🗡️
|
|
115
|
+
</Modal>
|
|
116
|
+
<Button label="Open Modal" onClick={() => setIsOpen(true)} />
|
|
117
|
+
</>
|
|
118
|
+
)
|
|
79
119
|
}
|
|
80
120
|
|
|
81
121
|
export const Success = {
|
|
82
|
-
render: () =>
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
122
|
+
render: () => {
|
|
123
|
+
return <OpenAndCloseSuccess />
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const OpenAndCloseWarning = () => {
|
|
128
|
+
const [isOpen, setIsOpen] = React.useState(false)
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<>
|
|
132
|
+
<Modal
|
|
133
|
+
id="warning-dangerous-alone"
|
|
134
|
+
isOpen={isOpen}
|
|
135
|
+
variant="warning"
|
|
136
|
+
title="It's dangerous to go alone!"
|
|
137
|
+
buttons={[
|
|
138
|
+
{
|
|
139
|
+
label: 'Done',
|
|
140
|
+
onClick: () => alert('click'),
|
|
141
|
+
},
|
|
142
|
+
]}
|
|
143
|
+
>
|
|
144
|
+
Take this 🗡️
|
|
145
|
+
</Modal>
|
|
146
|
+
<Button label="Open Modal" onClick={() => setIsOpen(true)} />
|
|
147
|
+
</>
|
|
148
|
+
)
|
|
97
149
|
}
|
|
98
150
|
|
|
99
151
|
export const Warning = {
|
|
100
|
-
render: () =>
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
variant="warning"
|
|
104
|
-
title="It's dangerous to go alone!"
|
|
105
|
-
buttons={[
|
|
106
|
-
{
|
|
107
|
-
label: 'Done',
|
|
108
|
-
onClick: () => alert('click'),
|
|
109
|
-
},
|
|
110
|
-
]}
|
|
111
|
-
>
|
|
112
|
-
Take this 🗡️
|
|
113
|
-
</Modal>
|
|
114
|
-
),
|
|
152
|
+
render: () => {
|
|
153
|
+
return <OpenAndCloseWarning />
|
|
154
|
+
},
|
|
115
155
|
}
|
|
116
156
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
{
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
157
|
+
const OpenAndCloseError = () => {
|
|
158
|
+
const [isOpen, setIsOpen] = React.useState(false)
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<>
|
|
162
|
+
<Modal
|
|
163
|
+
id="error-dangerous-alone"
|
|
164
|
+
isOpen={isOpen}
|
|
165
|
+
variant="error"
|
|
166
|
+
title="It's dangerous to go alone!"
|
|
167
|
+
buttons={[
|
|
168
|
+
{
|
|
169
|
+
label: 'Done',
|
|
170
|
+
onClick: () => alert('click'),
|
|
171
|
+
},
|
|
172
|
+
]}
|
|
173
|
+
>
|
|
174
|
+
Take this 🗡️
|
|
175
|
+
</Modal>
|
|
176
|
+
<Button label="Open Modal" onClick={() => setIsOpen(true)} />
|
|
177
|
+
</>
|
|
178
|
+
)
|
|
133
179
|
}
|
|
134
180
|
|
|
135
|
-
export const
|
|
136
|
-
render: () =>
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
variant="discard"
|
|
140
|
-
title="The far horizon was always steamy and indistinct"
|
|
141
|
-
buttons={[
|
|
142
|
-
{
|
|
143
|
-
label: 'Cancel',
|
|
144
|
-
variant: 'neutral',
|
|
145
|
-
onClick: () => alert('click'),
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
label: 'Delete',
|
|
149
|
-
variant: 'error',
|
|
150
|
-
onClick: () => alert('click'),
|
|
151
|
-
},
|
|
152
|
-
]}
|
|
153
|
-
>
|
|
154
|
-
But I could see that great jungles of unknown tree-ferns, calamites,
|
|
155
|
-
lepidodendra, and sigillaria lay outside the city, their fantastic
|
|
156
|
-
frondage waving mockingly in the shifting vapours. Now and then there
|
|
157
|
-
would be suggestions of motion in the sky, but these my early visions
|
|
158
|
-
never resolved.
|
|
159
|
-
</Modal>
|
|
160
|
-
),
|
|
181
|
+
export const Error = {
|
|
182
|
+
render: () => {
|
|
183
|
+
return <OpenAndCloseError />
|
|
184
|
+
},
|
|
161
185
|
}
|
|
162
186
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
title="Large Modal"
|
|
166
|
-
onClose={() => alert('Close')}
|
|
167
|
-
buttons={[
|
|
168
|
-
{ label: 'Close', variant: 'neutral', onClick: () => alert('Close') },
|
|
169
|
-
{ label: 'Save' },
|
|
170
|
-
]}
|
|
171
|
-
>
|
|
172
|
-
<LoremIpsum lorems={10} />
|
|
173
|
-
<Select
|
|
174
|
-
id="select"
|
|
175
|
-
label="Select"
|
|
176
|
-
helpText="This is a help text"
|
|
177
|
-
placeholder="Select an option"
|
|
178
|
-
options={[
|
|
179
|
-
{ id: '1', label: 'Option 1' },
|
|
180
|
-
{ id: '2', label: 'Option 2' },
|
|
181
|
-
{ id: '3', label: 'Option 3' },
|
|
182
|
-
{ id: '4', label: 'Option 4' },
|
|
183
|
-
{ id: '5', label: 'Option 5' },
|
|
184
|
-
{ id: '6', label: 'Option 6' },
|
|
185
|
-
{ id: '7', label: 'Option 7' },
|
|
186
|
-
{ id: '8', label: 'Option 8' },
|
|
187
|
-
{ id: '9', label: 'Option 9' },
|
|
188
|
-
{ id: '10', label: 'Option 10' },
|
|
189
|
-
{ id: '11', label: 'Option 11' },
|
|
190
|
-
{ id: '12', label: 'Option 12' },
|
|
191
|
-
{ id: '13', label: 'Option 13' },
|
|
192
|
-
{ id: '14', label: 'Option 14' },
|
|
193
|
-
{ id: '15', label: 'Option 15' },
|
|
194
|
-
]}
|
|
195
|
-
/>
|
|
196
|
-
</Modal>
|
|
197
|
-
)
|
|
187
|
+
const OpenAndCloseDeleteOrDiscard = () => {
|
|
188
|
+
const [isOpen, setIsOpen] = React.useState(false)
|
|
198
189
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
{ id: '11', label: 'Option 11' },
|
|
230
|
-
{ id: '12', label: 'Option 12' },
|
|
231
|
-
{ id: '13', label: 'Option 13' },
|
|
232
|
-
{ id: '14', label: 'Option 14' },
|
|
233
|
-
{ id: '15', label: 'Option 15' },
|
|
234
|
-
]}
|
|
235
|
-
/>
|
|
236
|
-
</Modal>
|
|
237
|
-
)
|
|
190
|
+
return (
|
|
191
|
+
<>
|
|
192
|
+
<Modal
|
|
193
|
+
id="discard-dangerous-alone"
|
|
194
|
+
isOpen={isOpen}
|
|
195
|
+
variant="discard"
|
|
196
|
+
title="The far horizon was always steamy and indistinct"
|
|
197
|
+
buttons={[
|
|
198
|
+
{
|
|
199
|
+
label: 'Cancel',
|
|
200
|
+
variant: 'neutral',
|
|
201
|
+
onClick: () => alert('click'),
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
label: 'Delete',
|
|
205
|
+
variant: 'error',
|
|
206
|
+
onClick: () => alert('click'),
|
|
207
|
+
},
|
|
208
|
+
]}
|
|
209
|
+
>
|
|
210
|
+
But I could see that great jungles of unknown tree-ferns, calamites,
|
|
211
|
+
lepidodendra, and sigillaria lay outside the city, their fantastic
|
|
212
|
+
frondage waving mockingly in the shifting vapours. Now and then there
|
|
213
|
+
would be suggestions of motion in the sky, but these my early visions
|
|
214
|
+
never resolved.
|
|
215
|
+
</Modal>
|
|
216
|
+
<Button label="Open Modal" onClick={() => setIsOpen(true)} />
|
|
217
|
+
</>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
238
220
|
|
|
239
|
-
const
|
|
221
|
+
export const DeleteOrDiscard = {
|
|
222
|
+
render: () => {
|
|
223
|
+
return <OpenAndCloseDeleteOrDiscard />
|
|
224
|
+
},
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const OpenAndCloseLargeModal = () => {
|
|
240
228
|
const [isOpen, setIsOpen] = React.useState(false)
|
|
241
|
-
|
|
229
|
+
|
|
242
230
|
return (
|
|
243
231
|
<>
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
232
|
+
<Modal
|
|
233
|
+
title="Large Modal"
|
|
234
|
+
isOpen={isOpen}
|
|
235
|
+
onClose={() => alert('Close')}
|
|
236
|
+
buttons={[
|
|
237
|
+
{
|
|
238
|
+
label: 'Close',
|
|
239
|
+
type: 'button',
|
|
240
|
+
variant: 'neutral',
|
|
241
|
+
onClick: () => alert('Close'),
|
|
242
|
+
},
|
|
243
|
+
{ label: 'Save' },
|
|
244
|
+
]}
|
|
245
|
+
>
|
|
246
|
+
<LoremIpsum lorems={10} />
|
|
247
|
+
<Select
|
|
248
|
+
id="select"
|
|
249
|
+
label="Select"
|
|
250
|
+
helpText="This is a help text"
|
|
251
|
+
placeholder="Select an option"
|
|
252
|
+
options={[
|
|
253
|
+
{ id: '1', label: 'Option 1' },
|
|
254
|
+
{ id: '2', label: 'Option 2' },
|
|
255
|
+
{ id: '3', label: 'Option 3' },
|
|
256
|
+
{ id: '4', label: 'Option 4' },
|
|
257
|
+
{ id: '5', label: 'Option 5' },
|
|
258
|
+
{ id: '6', label: 'Option 6' },
|
|
259
|
+
{ id: '7', label: 'Option 7' },
|
|
260
|
+
{ id: '8', label: 'Option 8' },
|
|
261
|
+
{ id: '9', label: 'Option 9' },
|
|
262
|
+
{ id: '10', label: 'Option 10' },
|
|
263
|
+
{ id: '11', label: 'Option 11' },
|
|
264
|
+
{ id: '12', label: 'Option 12' },
|
|
265
|
+
{ id: '13', label: 'Option 13' },
|
|
266
|
+
{ id: '14', label: 'Option 14' },
|
|
267
|
+
{ id: '15', label: 'Option 15' },
|
|
268
|
+
]}
|
|
269
|
+
/>
|
|
270
|
+
</Modal>
|
|
262
271
|
<Button label="Open Modal" onClick={() => setIsOpen(true)} />
|
|
263
272
|
</>
|
|
264
273
|
)
|
|
265
274
|
}
|
|
266
275
|
|
|
267
|
-
export const
|
|
276
|
+
export const LargeModal = {
|
|
268
277
|
render: () => {
|
|
269
|
-
return <
|
|
278
|
+
return <OpenAndCloseLargeModal />
|
|
270
279
|
},
|
|
271
280
|
}
|
|
272
281
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
{
|
|
292
|
-
]}
|
|
293
|
-
>
|
|
294
|
-
<FormContainer fluid>
|
|
295
|
-
<Input name="input" label="Input" placeholder="Type something" />
|
|
282
|
+
const OpenAndCloseScrollableModal = () => {
|
|
283
|
+
const [isOpen, setIsOpen] = React.useState(false)
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<>
|
|
287
|
+
<Modal
|
|
288
|
+
title="Scrollable Modal Title"
|
|
289
|
+
isOpen={isOpen}
|
|
290
|
+
onClose={() => console.log('close')}
|
|
291
|
+
scrollable
|
|
292
|
+
buttons={[
|
|
293
|
+
{
|
|
294
|
+
label: 'Close',
|
|
295
|
+
variant: 'neutral',
|
|
296
|
+
onClick: () => console.log('close'),
|
|
297
|
+
},
|
|
298
|
+
]}
|
|
299
|
+
>
|
|
300
|
+
<LoremIpsum lorems={10} />
|
|
296
301
|
<Select
|
|
302
|
+
id="select"
|
|
297
303
|
label="Select"
|
|
298
|
-
name="select"
|
|
299
304
|
helpText="This is a help text"
|
|
300
305
|
placeholder="Select an option"
|
|
301
306
|
options={[
|
|
@@ -316,7 +321,117 @@ export const FormModal = () => (
|
|
|
316
321
|
{ id: '15', label: 'Option 15' },
|
|
317
322
|
]}
|
|
318
323
|
/>
|
|
319
|
-
</
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
)
|
|
324
|
+
</Modal>
|
|
325
|
+
<Button label="Open Modal" onClick={() => setIsOpen(true)} />
|
|
326
|
+
</>
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export const ScrollableModal = {
|
|
331
|
+
render: () => {
|
|
332
|
+
return <OpenAndCloseScrollableModal />
|
|
333
|
+
},
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const OpenAndCloseModalComponent = () => {
|
|
337
|
+
const [isOpen, setIsOpen] = React.useState(false)
|
|
338
|
+
const closeModal = () => setIsOpen(false)
|
|
339
|
+
return (
|
|
340
|
+
<>
|
|
341
|
+
<ModalDialog isOpen={isOpen} details onClose={closeModal}>
|
|
342
|
+
<ModalHeader>
|
|
343
|
+
<ModalTitle>Modal title</ModalTitle>
|
|
344
|
+
<ModalCloseButton onClick={closeModal} />
|
|
345
|
+
</ModalHeader>
|
|
346
|
+
<ModalBody>
|
|
347
|
+
<LoremIpsum lorems={1} />
|
|
348
|
+
</ModalBody>
|
|
349
|
+
<ModalFooter>
|
|
350
|
+
<Button label="Close" onClick={closeModal} />
|
|
351
|
+
</ModalFooter>
|
|
352
|
+
</ModalDialog>
|
|
353
|
+
|
|
354
|
+
<LoremIpsum lorems={10} />
|
|
355
|
+
<br />
|
|
356
|
+
<hr />
|
|
357
|
+
<br />
|
|
358
|
+
<Button label="Open Modal" onClick={() => setIsOpen(true)} />
|
|
359
|
+
</>
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export const OpenAndCloseModal = {
|
|
364
|
+
render: () => {
|
|
365
|
+
return <OpenAndCloseModalComponent />
|
|
366
|
+
},
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const OpenAndCloseFormModal = () => {
|
|
370
|
+
const [isOpen, setIsOpen] = React.useState(false)
|
|
371
|
+
|
|
372
|
+
return (
|
|
373
|
+
<form
|
|
374
|
+
onSubmit={(e) => {
|
|
375
|
+
e.preventDefault()
|
|
376
|
+
const formData = new FormData(e.currentTarget)
|
|
377
|
+
alert(
|
|
378
|
+
`Form submitted: ${JSON.stringify(Object.fromEntries(formData))}`,
|
|
379
|
+
)
|
|
380
|
+
}}
|
|
381
|
+
>
|
|
382
|
+
<Modal
|
|
383
|
+
isOpen={isOpen}
|
|
384
|
+
title="Form Modal"
|
|
385
|
+
onClose={() => alert('Close')}
|
|
386
|
+
buttons={[
|
|
387
|
+
{
|
|
388
|
+
label: 'Close',
|
|
389
|
+
type: 'button',
|
|
390
|
+
variant: 'neutral',
|
|
391
|
+
onClick: () => alert('Close'),
|
|
392
|
+
},
|
|
393
|
+
{ label: 'Save', type: 'submit' },
|
|
394
|
+
]}
|
|
395
|
+
>
|
|
396
|
+
<FormContainer fluid>
|
|
397
|
+
<Input name="input" label="Input" placeholder="Type something" />
|
|
398
|
+
<Select
|
|
399
|
+
label="Select"
|
|
400
|
+
name="select"
|
|
401
|
+
helpText="This is a help text"
|
|
402
|
+
placeholder="Select an option"
|
|
403
|
+
options={[
|
|
404
|
+
{ id: '1', label: 'Option 1' },
|
|
405
|
+
{ id: '2', label: 'Option 2' },
|
|
406
|
+
{ id: '3', label: 'Option 3' },
|
|
407
|
+
{ id: '4', label: 'Option 4' },
|
|
408
|
+
{ id: '5', label: 'Option 5' },
|
|
409
|
+
{ id: '6', label: 'Option 6' },
|
|
410
|
+
{ id: '7', label: 'Option 7' },
|
|
411
|
+
{ id: '8', label: 'Option 8' },
|
|
412
|
+
{ id: '9', label: 'Option 9' },
|
|
413
|
+
{ id: '10', label: 'Option 10' },
|
|
414
|
+
{ id: '11', label: 'Option 11' },
|
|
415
|
+
{ id: '12', label: 'Option 12' },
|
|
416
|
+
{ id: '13', label: 'Option 13' },
|
|
417
|
+
{ id: '14', label: 'Option 14' },
|
|
418
|
+
{ id: '15', label: 'Option 15' },
|
|
419
|
+
]}
|
|
420
|
+
/>
|
|
421
|
+
</FormContainer>
|
|
422
|
+
</Modal>
|
|
423
|
+
|
|
424
|
+
<Button
|
|
425
|
+
label="Open Modal"
|
|
426
|
+
type="button"
|
|
427
|
+
onClick={() => setIsOpen(true)}
|
|
428
|
+
/>
|
|
429
|
+
</form>
|
|
430
|
+
)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export const FormModal = {
|
|
434
|
+
render: () => {
|
|
435
|
+
return <OpenAndCloseFormModal />
|
|
436
|
+
},
|
|
437
|
+
}
|
package/tests/Modal.spec.tsx
CHANGED
|
@@ -3,6 +3,12 @@ import React from 'react'
|
|
|
3
3
|
import { Modal, type Variant } from '../src/atoms/Modal'
|
|
4
4
|
|
|
5
5
|
describe('Modal', () => {
|
|
6
|
+
beforeAll(() => {
|
|
7
|
+
HTMLDialogElement.prototype.show = jest.fn()
|
|
8
|
+
HTMLDialogElement.prototype.showModal = jest.fn()
|
|
9
|
+
HTMLDialogElement.prototype.close = jest.fn()
|
|
10
|
+
})
|
|
11
|
+
|
|
6
12
|
const variants = ['info', 'success', 'warning', 'error']
|
|
7
13
|
it.each(variants)(
|
|
8
14
|
'renders the %s variant with title and the expected icon and button',
|
|
@@ -14,6 +20,7 @@ describe('Modal', () => {
|
|
|
14
20
|
variant={variant as Variant}
|
|
15
21
|
id={`${variant}-modal`}
|
|
16
22
|
title={title}
|
|
23
|
+
isOpen={true}
|
|
17
24
|
buttons={[
|
|
18
25
|
{
|
|
19
26
|
label: 'Done',
|
|
@@ -23,11 +30,11 @@ describe('Modal', () => {
|
|
|
23
30
|
{content}
|
|
24
31
|
</Modal>,
|
|
25
32
|
)
|
|
26
|
-
expect(getByRole('img')).toHaveClass(variant)
|
|
33
|
+
expect(getByRole('img', { hidden: true })).toHaveClass(variant)
|
|
27
34
|
expect(getByText(title)).toBeInTheDocument()
|
|
28
35
|
expect(getByText(content)).toBeInTheDocument()
|
|
29
|
-
expect(getByRole('button')).toHaveTextContent('Done')
|
|
30
|
-
expect(getByRole('button')).toHaveClass('primary')
|
|
36
|
+
expect(getByRole('button', { hidden: true })).toHaveTextContent('Done')
|
|
37
|
+
expect(getByRole('button', { hidden: true })).toHaveClass('primary')
|
|
31
38
|
},
|
|
32
39
|
)
|
|
33
40
|
|
|
@@ -39,6 +46,7 @@ describe('Modal', () => {
|
|
|
39
46
|
id="discard-modal"
|
|
40
47
|
title={title}
|
|
41
48
|
variant="discard"
|
|
49
|
+
isOpen={true}
|
|
42
50
|
buttons={[
|
|
43
51
|
{
|
|
44
52
|
label: 'Cancel',
|
|
@@ -53,12 +61,20 @@ describe('Modal', () => {
|
|
|
53
61
|
{content}
|
|
54
62
|
</Modal>,
|
|
55
63
|
)
|
|
56
|
-
expect(getByRole('img')).toHaveClass('warning')
|
|
64
|
+
expect(getByRole('img', { hidden: true })).toHaveClass('warning')
|
|
57
65
|
expect(getByText(title)).toBeInTheDocument()
|
|
58
66
|
expect(getByText(content)).toBeInTheDocument()
|
|
59
|
-
expect(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
expect(screen.getAllByRole('button')[
|
|
67
|
+
expect(
|
|
68
|
+
screen.getAllByRole('button', { hidden: true })[0],
|
|
69
|
+
).toHaveTextContent('Cancel')
|
|
70
|
+
expect(screen.getAllByRole('button', { hidden: true })[0]).toHaveClass(
|
|
71
|
+
'neutral',
|
|
72
|
+
)
|
|
73
|
+
expect(
|
|
74
|
+
screen.getAllByRole('button', { hidden: true })[1],
|
|
75
|
+
).toHaveTextContent('Delete')
|
|
76
|
+
expect(screen.getAllByRole('button', { hidden: true })[1]).toHaveClass(
|
|
77
|
+
'error',
|
|
78
|
+
)
|
|
63
79
|
})
|
|
64
80
|
})
|
package/tests/a11y.spec.tsx
CHANGED
|
@@ -13,6 +13,11 @@ const stories = Object.values(components).map((component) => {
|
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
stories.forEach(({ title, stories }) => {
|
|
16
|
+
beforeAll(() => {
|
|
17
|
+
HTMLDialogElement.prototype.show = jest.fn()
|
|
18
|
+
HTMLDialogElement.prototype.showModal = jest.fn()
|
|
19
|
+
HTMLDialogElement.prototype.close = jest.fn()
|
|
20
|
+
})
|
|
16
21
|
const variations = Object.entries(stories)
|
|
17
22
|
|
|
18
23
|
variations.forEach(([variationName, story]: [string, any]) => {
|