@orangesk/orange-design-system 2.0.0-alpha.5 → 2.0.0-beta.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.
- package/build/components/Accordion/tsconfig.tsbuildinfo +1 -1
- package/build/components/Alert/tsconfig.tsbuildinfo +1 -1
- package/build/components/AnchorNavigation/tsconfig.tsbuildinfo +1 -1
- package/build/components/Bar/tsconfig.tsbuildinfo +1 -1
- package/build/components/BlockAction/tsconfig.tsbuildinfo +1 -1
- package/build/components/BodyBanner/tsconfig.tsbuildinfo +1 -1
- package/build/components/Breadcrumbs/tsconfig.tsbuildinfo +1 -1
- package/build/components/Button/tsconfig.tsbuildinfo +1 -1
- package/build/components/Buttons/tsconfig.tsbuildinfo +1 -1
- package/build/components/Card/tsconfig.tsbuildinfo +1 -1
- package/build/components/Carousel/tsconfig.tsbuildinfo +1 -1
- package/build/components/CarouselPromotions/tsconfig.tsbuildinfo +1 -1
- package/build/components/CartTable/tsconfig.tsbuildinfo +1 -1
- package/build/components/Code/tsconfig.tsbuildinfo +1 -1
- package/build/components/Container/tsconfig.tsbuildinfo +1 -1
- package/build/components/Controls/tsconfig.tsbuildinfo +1 -1
- package/build/components/Cover/tsconfig.tsbuildinfo +1 -1
- package/build/components/Divider/tsconfig.tsbuildinfo +1 -1
- package/build/components/DocumentationSidebar/index.js +1 -1
- package/build/components/DocumentationSidebar/index.js.map +1 -1
- package/build/components/DocumentationSidebar/tsconfig.tsbuildinfo +1 -1
- package/build/components/Dropdown/tsconfig.tsbuildinfo +1 -1
- package/build/components/Expander/tsconfig.tsbuildinfo +1 -1
- package/build/components/FeatureAccordion/tsconfig.tsbuildinfo +1 -1
- package/build/components/Footer/index.js.map +1 -1
- package/build/components/Footer/tsconfig.tsbuildinfo +1 -1
- package/build/components/Forms/index.js +1 -1
- package/build/components/Forms/index.js.map +1 -1
- package/build/components/Forms/tsconfig.tsbuildinfo +1 -1
- package/build/components/Gauge/tsconfig.tsbuildinfo +1 -1
- package/build/components/Grid/tsconfig.tsbuildinfo +1 -1
- package/build/components/Hero/tsconfig.tsbuildinfo +1 -1
- package/build/components/Icon/tsconfig.tsbuildinfo +1 -1
- package/build/components/IconList/tsconfig.tsbuildinfo +1 -1
- package/build/components/Image/tsconfig.tsbuildinfo +1 -1
- package/build/components/Link/tsconfig.tsbuildinfo +1 -1
- package/build/components/List/tsconfig.tsbuildinfo +1 -1
- package/build/components/Loader/tsconfig.tsbuildinfo +1 -1
- package/build/components/Megamenu/index.js +6 -6
- package/build/components/Megamenu/index.js.map +1 -1
- package/build/components/Megamenu/style.css +1 -1
- package/build/components/Megamenu/style.css.map +1 -1
- package/build/components/Megamenu/tsconfig.tsbuildinfo +1 -1
- package/build/components/Modal/index.js +4 -4
- package/build/components/Modal/index.js.map +1 -1
- package/build/components/Modal/tsconfig.tsbuildinfo +1 -1
- package/build/components/Pagination/tsconfig.tsbuildinfo +1 -1
- package/build/components/Pill/tsconfig.tsbuildinfo +1 -1
- package/build/components/Preview/tsconfig.tsbuildinfo +1 -1
- package/build/components/Progress/tsconfig.tsbuildinfo +1 -1
- package/build/components/PromoBanner/tsconfig.tsbuildinfo +1 -1
- package/build/components/PromotionCard/tsconfig.tsbuildinfo +1 -1
- package/build/components/Section/index.js.map +1 -1
- package/build/components/Section/tsconfig.tsbuildinfo +1 -1
- package/build/components/Skeleton/tsconfig.tsbuildinfo +1 -1
- package/build/components/SkipLink/tsconfig.tsbuildinfo +1 -1
- package/build/components/Stepbar/tsconfig.tsbuildinfo +1 -1
- package/build/components/Sticker/tsconfig.tsbuildinfo +1 -1
- package/build/components/Table/tsconfig.tsbuildinfo +1 -1
- package/build/components/Tabs/tsconfig.tsbuildinfo +1 -1
- package/build/components/Tag/tsconfig.tsbuildinfo +1 -1
- package/build/components/Testimonial/tsconfig.tsbuildinfo +1 -1
- package/build/components/Tile/tsconfig.tsbuildinfo +1 -1
- package/build/components/Tooltip/tsconfig.tsbuildinfo +1 -1
- package/build/components/index.css +1 -1
- package/build/components/index.css.map +1 -1
- package/build/components/index.js +7 -7
- package/build/components/index.js.map +1 -1
- package/build/components/static.js +1 -1
- package/build/components/static.js.map +1 -1
- package/build/components/tsconfig.tsbuildinfo +1 -1
- package/build/components/types/src/components/Forms/Message/Message.d.ts +1 -1
- package/build/components/types/src/components/Megamenu/index.d.ts +2 -0
- package/build/components/types/src/components/Modal/Modal.d.ts +6 -0
- package/build/components/types/src/components/Modal/ModalBody.d.ts +1 -0
- package/build/components/types/src/components/Modal/ModalProductFooter.d.ts +10 -0
- package/build/components/types/src/components/Modal/ModalProductHeader.d.ts +9 -0
- package/build/components/types/src/components/Modal/index.d.ts +2 -0
- package/build/components/types/src/components/Section/Section.d.ts +1 -1
- package/build/components/types/src/components/index.d.ts +3 -2
- package/build/lib/after-components.css +1 -1
- package/build/lib/after-components.css.map +1 -1
- package/build/lib/before-components.css +1 -1
- package/build/lib/before-components.css.map +1 -1
- package/build/lib/components.css +1 -1
- package/build/lib/components.css.map +1 -1
- package/build/lib/scripts.js +1 -1
- package/build/lib/scripts.js.map +1 -1
- package/build/lib/style.css +1 -1
- package/build/lib/style.css.map +1 -1
- package/build/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/components/CarouselPromotions/styles/mixins.scss +6 -8
- package/src/components/Forms/Message/Message.tsx +6 -2
- package/src/components/Forms/Message/styles/style.scss +6 -0
- package/src/components/Megamenu/index.ts +3 -0
- package/src/components/Megamenu/styles/config.scss +2 -0
- package/src/components/Megamenu/styles/mixins.scss +6 -1
- package/src/components/Megamenu/styles/style.scss +2 -1
- package/src/components/Modal/Modal.mdx +119 -3
- package/src/components/Modal/Modal.static.ts +4 -1
- package/src/components/Modal/Modal.tsx +29 -4
- package/src/components/Modal/ModalBody.tsx +3 -0
- package/src/components/Modal/ModalProductFooter.tsx +38 -0
- package/src/components/Modal/ModalProductHeader.tsx +36 -0
- package/src/components/Modal/index.ts +2 -0
- package/src/components/Modal/styles/config.scss +7 -0
- package/src/components/Modal/styles/mixins.scss +97 -13
- package/src/components/Modal/styles/style.scss +36 -0
- package/src/components/Modal/tests/Modal.conformance.test.js +8 -1
- package/src/components/Modal/tests/Modal.unit.test.js +36 -0
- package/src/components/Modal/tests/ModalProductHeader.unit.test.js +73 -0
- package/src/components/Pagination/styles/mixins.scss +1 -0
- package/src/components/Section/Section.tsx +7 -1
- package/src/components/Section/tests/Section.conformance.test.js +7 -1
- package/src/components/Section/tests/Section.unit.test.js +28 -1
- package/src/components/index.ts +11 -1
- package/src/styles/tokens/color.scss +4 -0
- package/src/styles/typography/mixins.scss +23 -0
- package/src/styles/typography/style.scss +4 -0
- package/src/styles/utilities/color.scss +14 -0
|
@@ -15,7 +15,15 @@ import Grid, { GridCol } from "../Grid";
|
|
|
15
15
|
import Icon from "../Icon";
|
|
16
16
|
import IconList from "../IconList";
|
|
17
17
|
import { Image } from "../Image";
|
|
18
|
-
import Modal, {
|
|
18
|
+
import Modal, {
|
|
19
|
+
ModalHeader,
|
|
20
|
+
ModalBody,
|
|
21
|
+
ModalFooter,
|
|
22
|
+
ModalProductHeader, ModalCloseButton
|
|
23
|
+
} from ".";
|
|
24
|
+
import { Divider } from "../Divider";
|
|
25
|
+
import { Bar, BarItem } from "../Bar";
|
|
26
|
+
import { Tag } from "../Tag";
|
|
19
27
|
|
|
20
28
|
export const actionButtons = [
|
|
21
29
|
<Button type="primary" data-a11y-dialog-hide key="1">
|
|
@@ -181,6 +189,112 @@ Customize the footer. Should be primary used for specific action button cases.
|
|
|
181
189
|
</Modal>
|
|
182
190
|
</Preview>
|
|
183
191
|
|
|
192
|
+
## Product header
|
|
193
|
+
|
|
194
|
+
Use the product header to display product information with an optional image, similar to the Card component's product header.
|
|
195
|
+
|
|
196
|
+
<Preview>
|
|
197
|
+
<Button data-a11y-dialog-show="modal-product-header">
|
|
198
|
+
Open Modal with product header
|
|
199
|
+
</Button>
|
|
200
|
+
<Modal
|
|
201
|
+
id="modal-product-header"
|
|
202
|
+
actions={actionButtons}
|
|
203
|
+
renderHeader={() => (
|
|
204
|
+
<>
|
|
205
|
+
<ModalCloseButton />
|
|
206
|
+
<ModalProductHeader
|
|
207
|
+
image={<img src="/images/product-1.svg" alt="Product" />}
|
|
208
|
+
>
|
|
209
|
+
<p className="bold color-orange">Odporúčame</p>
|
|
210
|
+
<Bar space="small" className="mb-none">
|
|
211
|
+
<BarItem>
|
|
212
|
+
<h2 className="h4 mb-none">Prémiový paušál</h2>
|
|
213
|
+
</BarItem>
|
|
214
|
+
<BarItem>
|
|
215
|
+
<Tag color="black">5G</Tag>
|
|
216
|
+
</BarItem>
|
|
217
|
+
</Bar>
|
|
218
|
+
<p className="h2 color-orange mb-none thin">∞ GB</p>
|
|
219
|
+
</ModalProductHeader>
|
|
220
|
+
<Divider thinLine />
|
|
221
|
+
</>
|
|
222
|
+
)}
|
|
223
|
+
>
|
|
224
|
+
<p className="h5 mb">
|
|
225
|
+
V paušále získate 200 GB plnou rýchlosťou, potom 20 Mbit/s
|
|
226
|
+
</p>
|
|
227
|
+
<h3 className="h4">S paušálom</h3>
|
|
228
|
+
<ul className="list--marker-orange">
|
|
229
|
+
<li>
|
|
230
|
+
nekonečné dáta, po prečerpaní 200 GB pokračujete s rýchlosťou 20
|
|
231
|
+
Mbit/s, dáta v EÚ podliehajú regulácii
|
|
232
|
+
</li>
|
|
233
|
+
<li>
|
|
234
|
+
nekonečné volania v SR a v EÚ (Zóna 1), medzinárodné volania do EÚ
|
|
235
|
+
a do Zóny 1 sú spoplatnené sumou 0,03 € za minútu
|
|
236
|
+
</li>
|
|
237
|
+
<li>nekonečné SMS/MMS v SR/EÚ/Zóna 1</li>
|
|
238
|
+
<li>prístup do 5G siete</li>
|
|
239
|
+
<li>možnosť dokúpiť ďalších 5 GB len za 5,13 €</li>
|
|
240
|
+
<li>možnosť bezplatnej aktivácie služby Online ochrana Basic</li>
|
|
241
|
+
<li>bezplatné založenie skupiny Navzájom zadarmo</li>
|
|
242
|
+
</ul>
|
|
243
|
+
{parse(loremIpsum({ count: 2, units: "paragraph", format: "html" }))}
|
|
244
|
+
</Modal>
|
|
245
|
+
</Preview>
|
|
246
|
+
|
|
247
|
+
<Preview>
|
|
248
|
+
<Button data-a11y-dialog-show="modal-product-header-small">
|
|
249
|
+
Open Modal with compact product header
|
|
250
|
+
</Button>
|
|
251
|
+
<Modal
|
|
252
|
+
id="modal-product-header-small"
|
|
253
|
+
actions={actionButtons}
|
|
254
|
+
renderHeader={() => (
|
|
255
|
+
<>
|
|
256
|
+
<ModalCloseButton />
|
|
257
|
+
<ModalProductHeader
|
|
258
|
+
space="small"
|
|
259
|
+
image={<img src="/images/product-2.svg" alt="Product" />}
|
|
260
|
+
>
|
|
261
|
+
<Bar space="small" className="mb-none">
|
|
262
|
+
<BarItem>
|
|
263
|
+
<h2 className="h3 mb-none">Veľký paušál</h2>
|
|
264
|
+
</BarItem>
|
|
265
|
+
<BarItem>
|
|
266
|
+
<Tag color="black">5G</Tag>
|
|
267
|
+
</BarItem>
|
|
268
|
+
</Bar>
|
|
269
|
+
<p className="h2 color-orange mb-none thin">∞ GB</p>
|
|
270
|
+
</ModalProductHeader>
|
|
271
|
+
<Divider thinLine />
|
|
272
|
+
</>
|
|
273
|
+
)}
|
|
274
|
+
>
|
|
275
|
+
<p className="h5 mb">
|
|
276
|
+
V paušále získate 200 GB plnou rýchlosťou, potom 20 Mbit/s
|
|
277
|
+
</p>
|
|
278
|
+
<h3 className="h4">S paušálom</h3>
|
|
279
|
+
<ul className="list--marker-orange">
|
|
280
|
+
<li>
|
|
281
|
+
nekonečné dáta, po prečerpaní 200 GB pokračujete s rýchlosťou 20
|
|
282
|
+
Mbit/s, dáta v EÚ podliehajú regulácii
|
|
283
|
+
</li>
|
|
284
|
+
<li>
|
|
285
|
+
nekonečné volania v SR a v EÚ (Zóna 1), medzinárodné volania do EÚ
|
|
286
|
+
a do Zóny 1 sú spoplatnené sumou 0,03 € za minútu
|
|
287
|
+
</li>
|
|
288
|
+
<li>nekonečné SMS/MMS v SR/EÚ/Zóna 1</li>
|
|
289
|
+
<li>prístup do 5G siete</li>
|
|
290
|
+
<li>možnosť dokúpiť ďalších 5 GB len za 5,13 €</li>
|
|
291
|
+
<li>možnosť bezplatnej aktivácie služby Online ochrana Basic</li>
|
|
292
|
+
<li>bezplatné založenie skupiny Navzájom zadarmo</li>
|
|
293
|
+
</ul>
|
|
294
|
+
{parse(loremIpsum({ count: 1, units: "paragraph", format: "html" }))}
|
|
295
|
+
</Modal>
|
|
296
|
+
</Preview>
|
|
297
|
+
|
|
184
298
|
## Full height on XS
|
|
185
299
|
|
|
186
300
|
On XS screens (mobile devices), the modal will take the full height of the viewport to maintain a consistent height when the modal contains a flow or multiple steps.
|
|
@@ -332,6 +446,8 @@ when the first/last body element is using default color (white).
|
|
|
332
446
|
|
|
333
447
|
<ComponentDocs title="<Modal />" component={Modal} />
|
|
334
448
|
|
|
449
|
+
<ComponentDocs title="<ModalProductHeader />" component={ModalProductHeader} />
|
|
450
|
+
|
|
335
451
|
## JS module reference
|
|
336
452
|
|
|
337
453
|
Modal is backed with [a11y-dialog | see docs](https://github.com/edenspiekermann/a11y-dialog)
|
|
@@ -442,8 +558,8 @@ instance.method();
|
|
|
442
558
|
|
|
443
559
|
- Modals should only be opened by activating a `button` element.
|
|
444
560
|
- Opening a modal sets focus on element with `data-a11y-modal-initial-focus`
|
|
445
|
-
|
|
446
|
-
|
|
561
|
+
attribute (modal title by default). When not present, focus is set on first
|
|
562
|
+
focusable element, usually the close button.
|
|
447
563
|
- An open modal traps focus. It must always contain a close button.
|
|
448
564
|
- Pressing ESC or clicking the overlay closes the modal.
|
|
449
565
|
- Closing a modal sets focus back on the activating `button` element.
|
|
@@ -98,10 +98,13 @@ export default class Modal {
|
|
|
98
98
|
*/
|
|
99
99
|
const modalHeader = this.element.querySelector(".modal__header");
|
|
100
100
|
if (modalHeader) {
|
|
101
|
+
const hasNoSpacingClass = modalHeader.classList.contains(
|
|
102
|
+
"modal__header--no-spacing",
|
|
103
|
+
);
|
|
101
104
|
const firstNonBtnElement = Array.from(modalHeader.children).find(
|
|
102
105
|
(child) => !child.classList.contains("btn"),
|
|
103
106
|
);
|
|
104
|
-
if (firstNonBtnElement) {
|
|
107
|
+
if (firstNonBtnElement && !hasNoSpacingClass) {
|
|
105
108
|
(firstNonBtnElement as HTMLElement).style.marginRight = "40px";
|
|
106
109
|
}
|
|
107
110
|
}
|
|
@@ -33,6 +33,8 @@ interface ModalProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
33
33
|
isActive?: boolean;
|
|
34
34
|
/** Disable plugin initialization. */
|
|
35
35
|
noInit?: boolean;
|
|
36
|
+
/** Custom header renderer. Receives id as function param. Returned element(s) must contain a header with close button. */
|
|
37
|
+
renderHeader?: (id: string) => React.ReactNode;
|
|
36
38
|
/** Custom body renderer. Receives title as function param. Returned element(s) must contain a title. */
|
|
37
39
|
renderBody?: () => React.ReactNode;
|
|
38
40
|
/** Custom footer renderer. Receives actions as function param. Returned element(s) must contain the actions. */
|
|
@@ -49,6 +51,10 @@ interface ModalProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
49
51
|
className?: string;
|
|
50
52
|
/** Color scheme for the modal */
|
|
51
53
|
colorScheme?: "dark" | "light";
|
|
54
|
+
/** Disables header paddings */
|
|
55
|
+
disableHeaderSpacing?: boolean;
|
|
56
|
+
/** Disables footer paddings */
|
|
57
|
+
disableFooterSpacing?: boolean;
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
const defaultProps = {
|
|
@@ -70,11 +76,14 @@ const Modal: React.FC<ModalProps> = ({
|
|
|
70
76
|
isActive,
|
|
71
77
|
size,
|
|
72
78
|
title,
|
|
79
|
+
renderHeader,
|
|
73
80
|
renderBody,
|
|
74
81
|
renderTitle,
|
|
75
82
|
renderFooter,
|
|
76
83
|
fullHeight,
|
|
77
84
|
colorScheme,
|
|
85
|
+
disableHeaderSpacing,
|
|
86
|
+
disableFooterSpacing,
|
|
78
87
|
...other
|
|
79
88
|
}) => {
|
|
80
89
|
const [modalRef, instance] = useStatic(ModalStatic);
|
|
@@ -119,14 +128,30 @@ const Modal: React.FC<ModalProps> = ({
|
|
|
119
128
|
>
|
|
120
129
|
<div className="modal__overlay" tabIndex={-1} data-a11y-dialog-hide />
|
|
121
130
|
<div className={dialogClasses} role="document" {...other}>
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
{
|
|
131
|
+
{}
|
|
132
|
+
<div
|
|
133
|
+
className={cx(`${CLASS_ROOT}__header`, {
|
|
134
|
+
[`${CLASS_ROOT}__header--no-spacing`]: disableHeaderSpacing,
|
|
135
|
+
})}
|
|
136
|
+
>
|
|
137
|
+
{renderHeader ? (
|
|
138
|
+
renderHeader(id)
|
|
139
|
+
) : (
|
|
140
|
+
<>
|
|
141
|
+
<ModalCloseButton />
|
|
142
|
+
{dialogTitle}
|
|
143
|
+
</>
|
|
144
|
+
)}
|
|
125
145
|
</div>
|
|
126
|
-
{renderBody ?
|
|
146
|
+
{renderBody ? (
|
|
147
|
+
renderBody()
|
|
148
|
+
) : (
|
|
149
|
+
<ModalBody withTopPadding={!!renderHeader}>{children}</ModalBody>
|
|
150
|
+
)}
|
|
127
151
|
<div
|
|
128
152
|
className={cx(`${CLASS_ROOT}__footer`, {
|
|
129
153
|
[`${CLASS_ROOT}__footer--sticky`]: hasStickyFooter,
|
|
154
|
+
[`${CLASS_ROOT}__footer--no-spacing`]: disableFooterSpacing,
|
|
130
155
|
})}
|
|
131
156
|
>
|
|
132
157
|
{renderFooter ? (
|
|
@@ -20,6 +20,7 @@ interface ModalBodyProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
20
20
|
color?: BodyColor;
|
|
21
21
|
className?: string;
|
|
22
22
|
children?: React.ReactNode;
|
|
23
|
+
withTopPadding?: boolean;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
const CLASS_ROOT = "modal__body";
|
|
@@ -28,12 +29,14 @@ const ModalBody: React.FC<ModalBodyProps> = ({
|
|
|
28
29
|
color,
|
|
29
30
|
className,
|
|
30
31
|
children,
|
|
32
|
+
withTopPadding,
|
|
31
33
|
...other
|
|
32
34
|
}) => (
|
|
33
35
|
<div
|
|
34
36
|
className={cx(CLASS_ROOT, className, {
|
|
35
37
|
[`bg-${color}`]: color,
|
|
36
38
|
[`${CLASS_ROOT}--colorful`]: color,
|
|
39
|
+
[`${CLASS_ROOT}--with-top-padding`]: withTopPadding,
|
|
37
40
|
})}
|
|
38
41
|
{...other}
|
|
39
42
|
>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import cx from "classnames";
|
|
3
|
+
|
|
4
|
+
interface ModalProductFooterProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
image?: React.ReactNode;
|
|
6
|
+
/** Use small spacing */
|
|
7
|
+
space?: "small";
|
|
8
|
+
button?: React.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const CLASS_ROOT = "modal__product";
|
|
12
|
+
|
|
13
|
+
const ModalProductFooter = React.forwardRef<
|
|
14
|
+
HTMLDivElement,
|
|
15
|
+
ModalProductFooterProps
|
|
16
|
+
>(({ className, children, button, image, space, ...other }, ref) => {
|
|
17
|
+
const classes = cx(
|
|
18
|
+
`${CLASS_ROOT}-footer`,
|
|
19
|
+
// {
|
|
20
|
+
// [`${CLASS_ROOT}-footer--space-small`]: space === "small",
|
|
21
|
+
// },
|
|
22
|
+
className,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const contentClasses = `${CLASS_ROOT}-footer-content`;
|
|
26
|
+
const buttonClasses = `${CLASS_ROOT}-footer-button`;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className={classes} ref={ref} {...other}>
|
|
30
|
+
<div className={contentClasses}>{children}</div>
|
|
31
|
+
<div className={buttonClasses}>{button}</div>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
ModalProductFooter.displayName = "ModalProductFooter";
|
|
37
|
+
|
|
38
|
+
export { ModalProductFooter };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import cx from "classnames";
|
|
3
|
+
|
|
4
|
+
interface ModalProductHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
image?: React.ReactNode;
|
|
6
|
+
/** Use small spacing */
|
|
7
|
+
space?: "small";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const CLASS_ROOT = "modal__product";
|
|
11
|
+
|
|
12
|
+
const ModalProductHeader = React.forwardRef<
|
|
13
|
+
HTMLDivElement,
|
|
14
|
+
ModalProductHeaderProps
|
|
15
|
+
>(({ className, children, image, space, ...other }, ref) => {
|
|
16
|
+
const classes = cx(
|
|
17
|
+
`${CLASS_ROOT}-header`,
|
|
18
|
+
{
|
|
19
|
+
[`${CLASS_ROOT}-header--space-small`]: space === "small",
|
|
20
|
+
},
|
|
21
|
+
className,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const contentClasses = `${CLASS_ROOT}-content`;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className={classes} ref={ref} {...other}>
|
|
28
|
+
<div className={contentClasses}>{children}</div>
|
|
29
|
+
{image}
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
ModalProductHeader.displayName = "ModalProductHeader";
|
|
35
|
+
|
|
36
|
+
export { ModalProductHeader };
|
|
@@ -2,3 +2,5 @@ export { Modal } from "./Modal";
|
|
|
2
2
|
export { ModalBody } from "./ModalBody";
|
|
3
3
|
export { ModalTitle } from "./ModalTitle";
|
|
4
4
|
export { ModalCloseButton } from "./ModalCloseButton";
|
|
5
|
+
export { ModalProductHeader } from "./ModalProductHeader";
|
|
6
|
+
export { ModalProductFooter } from "./ModalProductFooter";
|
|
@@ -39,3 +39,10 @@ $spacing: (
|
|
|
39
39
|
),
|
|
40
40
|
large: (), // same as default,
|
|
41
41
|
);
|
|
42
|
+
|
|
43
|
+
$spacing-product-footer: (
|
|
44
|
+
default: (
|
|
45
|
+
default: space.get("medium") space.get("small") convert.to-rem(40px),
|
|
46
|
+
md: space.get("medium") space.get("large") space.get("large") space.get("large"),
|
|
47
|
+
)
|
|
48
|
+
)
|
|
@@ -33,9 +33,8 @@
|
|
|
33
33
|
z-index: config.$overlay-z-index;
|
|
34
34
|
opacity: 0;
|
|
35
35
|
transform: scale(1.1, 1.1);
|
|
36
|
-
transition:
|
|
37
|
-
|
|
38
|
-
transform config.$overlay-e;
|
|
36
|
+
transition: opacity config.$overlay-e,
|
|
37
|
+
transform config.$overlay-e;
|
|
39
38
|
backface-visibility: hidden;
|
|
40
39
|
}
|
|
41
40
|
|
|
@@ -93,6 +92,10 @@
|
|
|
93
92
|
> *:last-child {
|
|
94
93
|
margin-bottom: 0;
|
|
95
94
|
}
|
|
95
|
+
|
|
96
|
+
.divider {
|
|
97
|
+
padding: 0;
|
|
98
|
+
}
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
@mixin close {
|
|
@@ -110,9 +113,20 @@
|
|
|
110
113
|
@mixin header-spacing($size, $sizes: config.$spacing) {
|
|
111
114
|
$spacing: map.get($sizes, $size);
|
|
112
115
|
|
|
116
|
+
&:not(.modal__header--no-spacing) {
|
|
117
|
+
@each $breakpoint, $space in $spacing {
|
|
118
|
+
@include breakpoint.get($breakpoint) {
|
|
119
|
+
padding: $space;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@mixin body-spacing-top($size, $sizes: config.$spacing) {
|
|
126
|
+
$spacing: map.get($sizes, $size);
|
|
113
127
|
@each $breakpoint, $space in $spacing {
|
|
114
128
|
@include breakpoint.get($breakpoint) {
|
|
115
|
-
padding: $space;
|
|
129
|
+
padding-top: $space;
|
|
116
130
|
}
|
|
117
131
|
}
|
|
118
132
|
}
|
|
@@ -170,21 +184,27 @@
|
|
|
170
184
|
> *:last-child {
|
|
171
185
|
margin-bottom: 0;
|
|
172
186
|
}
|
|
187
|
+
|
|
188
|
+
.divider {
|
|
189
|
+
padding: 0;
|
|
190
|
+
}
|
|
173
191
|
}
|
|
174
192
|
|
|
175
193
|
@mixin footer-spacing($size, $sizes: config.$spacing) {
|
|
176
194
|
$spacing: map.get($sizes, $size);
|
|
177
195
|
|
|
178
|
-
|
|
179
|
-
@
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
196
|
+
&:not(.modal__footer--no-spacing) {
|
|
197
|
+
@each $breakpoint, $space in $spacing {
|
|
198
|
+
@include breakpoint.get($breakpoint) {
|
|
199
|
+
padding-top: 0;
|
|
200
|
+
padding-right: $space;
|
|
201
|
+
padding-bottom: $space;
|
|
202
|
+
padding-left: $space;
|
|
184
203
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
204
|
+
@at-root {
|
|
205
|
+
.modal__body--colorful + .modal__footer {
|
|
206
|
+
padding: $space !important;
|
|
207
|
+
}
|
|
188
208
|
}
|
|
189
209
|
}
|
|
190
210
|
}
|
|
@@ -198,3 +218,67 @@
|
|
|
198
218
|
background-color: var(--color-surface-primary);
|
|
199
219
|
}
|
|
200
220
|
}
|
|
221
|
+
|
|
222
|
+
@mixin modal-product-header($spacing) {
|
|
223
|
+
flex: 0 1 auto;
|
|
224
|
+
|
|
225
|
+
@if ($spacing == null) {
|
|
226
|
+
@each $breakpoint, $value in map.get(config.$spacing, "default") {
|
|
227
|
+
@include breakpoint.get($breakpoint) {
|
|
228
|
+
padding: $value;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} @else {
|
|
232
|
+
padding: $spacing;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
@mixin modal-product-footer() {
|
|
237
|
+
display: flex;
|
|
238
|
+
align-items: center;
|
|
239
|
+
gap: convert.to-rem(20px);
|
|
240
|
+
|
|
241
|
+
@include breakpoint.get("md") {
|
|
242
|
+
gap: convert.to-rem(30px);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
@include breakpoint.get("xs", "down") {
|
|
246
|
+
flex-direction: column;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
@each $breakpoint, $value in map.get(config.$spacing-product-footer, "default") {
|
|
250
|
+
@include breakpoint.get($breakpoint) {
|
|
251
|
+
padding: $value;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
@mixin modal-product-footer-content() {
|
|
257
|
+
flex: 1 0 auto;
|
|
258
|
+
display: flex;
|
|
259
|
+
flex-direction: column;
|
|
260
|
+
gap: convert.to-rem(7px);
|
|
261
|
+
|
|
262
|
+
@include breakpoint.get("xs", "down") {
|
|
263
|
+
flex-direction: row-reverse;
|
|
264
|
+
align-items: center;
|
|
265
|
+
width: 100%;
|
|
266
|
+
justify-content: space-between;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@mixin modal-product-footer-button() {
|
|
271
|
+
@include breakpoint.get("xs", "down") {
|
|
272
|
+
width: 100%;
|
|
273
|
+
|
|
274
|
+
.btn {
|
|
275
|
+
width: 100%;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
@mixin modal-product-image() {
|
|
281
|
+
object-fit: cover;
|
|
282
|
+
object-position: left;
|
|
283
|
+
max-width: convert.to-rem(125px);
|
|
284
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
@use "sass:map";
|
|
2
2
|
@use "../../../styles/tools/generate";
|
|
3
|
+
@use "../../../styles/tokens/space";
|
|
3
4
|
@use "../../../styles/tokens/breakpoint";
|
|
4
5
|
@use "./config";
|
|
5
6
|
@use "./mixins";
|
|
@@ -54,6 +55,10 @@
|
|
|
54
55
|
.modal__dialog#{generate.variant-name($size)} & {
|
|
55
56
|
@include mixins.body-spacing($size);
|
|
56
57
|
}
|
|
58
|
+
|
|
59
|
+
.modal__dialog#{generate.variant-name($size)} > &--with-top-padding {
|
|
60
|
+
@include mixins.body-spacing-top($size);
|
|
61
|
+
}
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
&--colorful {
|
|
@@ -96,4 +101,35 @@
|
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
103
|
}
|
|
104
|
+
|
|
105
|
+
.modal__product {
|
|
106
|
+
&-header {
|
|
107
|
+
display: flex;
|
|
108
|
+
justify-content: space-between;
|
|
109
|
+
|
|
110
|
+
> img {
|
|
111
|
+
@include mixins.modal-product-image;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
&-content {
|
|
116
|
+
@include mixins.modal-product-header(null);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.modal__product-header--space-small .modal__product-content {
|
|
121
|
+
@include mixins.modal-product-header(space.get());
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.modal__product-footer {
|
|
125
|
+
@include mixins.modal-product-footer();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.modal__product-footer-content {
|
|
129
|
+
@include mixins.modal-product-footer-content();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.modal__product-footer-button {
|
|
133
|
+
@include mixins.modal-product-footer-button();
|
|
134
|
+
}
|
|
99
135
|
}
|
|
@@ -3,7 +3,7 @@ import { render, fireEvent } from "@testing-library/react";
|
|
|
3
3
|
import { axe } from "jest-axe";
|
|
4
4
|
|
|
5
5
|
import { Button } from "../../Button";
|
|
6
|
-
import { Modal } from "../";
|
|
6
|
+
import { Modal, ModalProductHeader } from "../";
|
|
7
7
|
|
|
8
8
|
const example = (
|
|
9
9
|
<>
|
|
@@ -20,6 +20,13 @@ const example = (
|
|
|
20
20
|
interdum. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
|
21
21
|
</p>
|
|
22
22
|
</Modal>
|
|
23
|
+
<Modal id="modal-with-product-header" title="Modal with Product Header">
|
|
24
|
+
<ModalProductHeader image={<div>Product Image</div>}>
|
|
25
|
+
<h3>Product Title</h3>
|
|
26
|
+
<p>Product description</p>
|
|
27
|
+
</ModalProductHeader>
|
|
28
|
+
<p>Modal content</p>
|
|
29
|
+
</Modal>
|
|
23
30
|
</div>
|
|
24
31
|
<div id="root-modals" />
|
|
25
32
|
</>
|
|
@@ -75,4 +75,40 @@ describe("rendering Modal", () => {
|
|
|
75
75
|
expect(getByTestId("modal-light")).toHaveClass("is-light");
|
|
76
76
|
});
|
|
77
77
|
});
|
|
78
|
+
describe("disableHeaderSpacing prop", () => {
|
|
79
|
+
it("should have modal__header--no-spacing class when disableHeaderSpacing is true", () => {
|
|
80
|
+
const { getByTestId } = render(
|
|
81
|
+
<div id="root">
|
|
82
|
+
<Modal
|
|
83
|
+
id="modal-no-header-spacing"
|
|
84
|
+
data-testid="modal-no-header-spacing"
|
|
85
|
+
disableHeaderSpacing
|
|
86
|
+
/>
|
|
87
|
+
</div>,
|
|
88
|
+
);
|
|
89
|
+
const child = getByTestId(
|
|
90
|
+
"modal-no-header-spacing",
|
|
91
|
+
).getElementsByClassName("modal__header--no-spacing");
|
|
92
|
+
expect(child.length).toBe(1);
|
|
93
|
+
expect(child[0]).toHaveClass("modal__header--no-spacing");
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
describe("disableFooterSpacing prop", () => {
|
|
97
|
+
it("should have modal__footer--no-spacing class when disableFooterSpacing is true", () => {
|
|
98
|
+
const { getByTestId } = render(
|
|
99
|
+
<div id="root">
|
|
100
|
+
<Modal
|
|
101
|
+
id="modal-no-footer-spacing"
|
|
102
|
+
data-testid="modal-no-footer-spacing"
|
|
103
|
+
disableFooterSpacing
|
|
104
|
+
/>
|
|
105
|
+
</div>,
|
|
106
|
+
);
|
|
107
|
+
const child = getByTestId(
|
|
108
|
+
"modal-no-footer-spacing",
|
|
109
|
+
).getElementsByClassName("modal__footer--no-spacing");
|
|
110
|
+
expect(child.length).toBe(1);
|
|
111
|
+
expect(child[0]).toHaveClass("modal__footer--no-spacing");
|
|
112
|
+
});
|
|
113
|
+
});
|
|
78
114
|
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
import { ModalProductHeader } from "../ModalProductHeader";
|
|
5
|
+
|
|
6
|
+
describe("rendering ModalProductHeader", () => {
|
|
7
|
+
it("has default class modal__product-header", () => {
|
|
8
|
+
const { getByTestId } = render(
|
|
9
|
+
<ModalProductHeader data-testid="test-id">Header</ModalProductHeader>,
|
|
10
|
+
);
|
|
11
|
+
expect(getByTestId("test-id")).toHaveClass("modal__product-header");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("renders children", () => {
|
|
15
|
+
const { getByText } = render(
|
|
16
|
+
<ModalProductHeader>Product Header</ModalProductHeader>,
|
|
17
|
+
);
|
|
18
|
+
expect(getByText("Product Header")).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("renders image if provided", () => {
|
|
22
|
+
const { getByTestId } = render(
|
|
23
|
+
<ModalProductHeader
|
|
24
|
+
image={<div data-testid="test-image">Test Image</div>}
|
|
25
|
+
>
|
|
26
|
+
Header
|
|
27
|
+
</ModalProductHeader>,
|
|
28
|
+
);
|
|
29
|
+
expect(getByTestId("test-image")).toBeInTheDocument();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("has additional class when className is set", () => {
|
|
33
|
+
const { getByTestId } = render(
|
|
34
|
+
<ModalProductHeader data-testid="test-id" className="test-class">
|
|
35
|
+
Header
|
|
36
|
+
</ModalProductHeader>,
|
|
37
|
+
);
|
|
38
|
+
expect(getByTestId("test-id")).toHaveClass("modal__product-header");
|
|
39
|
+
expect(getByTestId("test-id")).toHaveClass("test-class");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('has modal__product-header--space-small class when space="small"', () => {
|
|
43
|
+
const { getByTestId } = render(
|
|
44
|
+
<ModalProductHeader data-testid="test-id" space="small">
|
|
45
|
+
Header
|
|
46
|
+
</ModalProductHeader>,
|
|
47
|
+
);
|
|
48
|
+
expect(getByTestId("test-id")).toHaveClass(
|
|
49
|
+
"modal__product-header--space-small",
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("does not have modal__product-header--space-small class when space is not set", () => {
|
|
54
|
+
const { getByTestId } = render(
|
|
55
|
+
<ModalProductHeader data-testid="test-id">Header</ModalProductHeader>,
|
|
56
|
+
);
|
|
57
|
+
expect(getByTestId("test-id")).not.toHaveClass(
|
|
58
|
+
"modal__product-header--space-small",
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("has content wrapper with modal__product-content class", () => {
|
|
63
|
+
const { container } = render(
|
|
64
|
+
<ModalProductHeader>Product Header</ModalProductHeader>,
|
|
65
|
+
);
|
|
66
|
+
expect(
|
|
67
|
+
container.querySelector(".modal__product-content"),
|
|
68
|
+
).toBeInTheDocument();
|
|
69
|
+
expect(
|
|
70
|
+
container.querySelector(".modal__product-content"),
|
|
71
|
+
).toHaveTextContent("Product Header");
|
|
72
|
+
});
|
|
73
|
+
});
|