@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.
Files changed (121) hide show
  1. package/build/components/Accordion/tsconfig.tsbuildinfo +1 -1
  2. package/build/components/Alert/tsconfig.tsbuildinfo +1 -1
  3. package/build/components/AnchorNavigation/tsconfig.tsbuildinfo +1 -1
  4. package/build/components/Bar/tsconfig.tsbuildinfo +1 -1
  5. package/build/components/BlockAction/tsconfig.tsbuildinfo +1 -1
  6. package/build/components/BodyBanner/tsconfig.tsbuildinfo +1 -1
  7. package/build/components/Breadcrumbs/tsconfig.tsbuildinfo +1 -1
  8. package/build/components/Button/tsconfig.tsbuildinfo +1 -1
  9. package/build/components/Buttons/tsconfig.tsbuildinfo +1 -1
  10. package/build/components/Card/tsconfig.tsbuildinfo +1 -1
  11. package/build/components/Carousel/tsconfig.tsbuildinfo +1 -1
  12. package/build/components/CarouselPromotions/tsconfig.tsbuildinfo +1 -1
  13. package/build/components/CartTable/tsconfig.tsbuildinfo +1 -1
  14. package/build/components/Code/tsconfig.tsbuildinfo +1 -1
  15. package/build/components/Container/tsconfig.tsbuildinfo +1 -1
  16. package/build/components/Controls/tsconfig.tsbuildinfo +1 -1
  17. package/build/components/Cover/tsconfig.tsbuildinfo +1 -1
  18. package/build/components/Divider/tsconfig.tsbuildinfo +1 -1
  19. package/build/components/DocumentationSidebar/index.js +1 -1
  20. package/build/components/DocumentationSidebar/index.js.map +1 -1
  21. package/build/components/DocumentationSidebar/tsconfig.tsbuildinfo +1 -1
  22. package/build/components/Dropdown/tsconfig.tsbuildinfo +1 -1
  23. package/build/components/Expander/tsconfig.tsbuildinfo +1 -1
  24. package/build/components/FeatureAccordion/tsconfig.tsbuildinfo +1 -1
  25. package/build/components/Footer/index.js.map +1 -1
  26. package/build/components/Footer/tsconfig.tsbuildinfo +1 -1
  27. package/build/components/Forms/index.js +1 -1
  28. package/build/components/Forms/index.js.map +1 -1
  29. package/build/components/Forms/tsconfig.tsbuildinfo +1 -1
  30. package/build/components/Gauge/tsconfig.tsbuildinfo +1 -1
  31. package/build/components/Grid/tsconfig.tsbuildinfo +1 -1
  32. package/build/components/Hero/tsconfig.tsbuildinfo +1 -1
  33. package/build/components/Icon/tsconfig.tsbuildinfo +1 -1
  34. package/build/components/IconList/tsconfig.tsbuildinfo +1 -1
  35. package/build/components/Image/tsconfig.tsbuildinfo +1 -1
  36. package/build/components/Link/tsconfig.tsbuildinfo +1 -1
  37. package/build/components/List/tsconfig.tsbuildinfo +1 -1
  38. package/build/components/Loader/tsconfig.tsbuildinfo +1 -1
  39. package/build/components/Megamenu/index.js +6 -6
  40. package/build/components/Megamenu/index.js.map +1 -1
  41. package/build/components/Megamenu/style.css +1 -1
  42. package/build/components/Megamenu/style.css.map +1 -1
  43. package/build/components/Megamenu/tsconfig.tsbuildinfo +1 -1
  44. package/build/components/Modal/index.js +4 -4
  45. package/build/components/Modal/index.js.map +1 -1
  46. package/build/components/Modal/tsconfig.tsbuildinfo +1 -1
  47. package/build/components/Pagination/tsconfig.tsbuildinfo +1 -1
  48. package/build/components/Pill/tsconfig.tsbuildinfo +1 -1
  49. package/build/components/Preview/tsconfig.tsbuildinfo +1 -1
  50. package/build/components/Progress/tsconfig.tsbuildinfo +1 -1
  51. package/build/components/PromoBanner/tsconfig.tsbuildinfo +1 -1
  52. package/build/components/PromotionCard/tsconfig.tsbuildinfo +1 -1
  53. package/build/components/Section/index.js.map +1 -1
  54. package/build/components/Section/tsconfig.tsbuildinfo +1 -1
  55. package/build/components/Skeleton/tsconfig.tsbuildinfo +1 -1
  56. package/build/components/SkipLink/tsconfig.tsbuildinfo +1 -1
  57. package/build/components/Stepbar/tsconfig.tsbuildinfo +1 -1
  58. package/build/components/Sticker/tsconfig.tsbuildinfo +1 -1
  59. package/build/components/Table/tsconfig.tsbuildinfo +1 -1
  60. package/build/components/Tabs/tsconfig.tsbuildinfo +1 -1
  61. package/build/components/Tag/tsconfig.tsbuildinfo +1 -1
  62. package/build/components/Testimonial/tsconfig.tsbuildinfo +1 -1
  63. package/build/components/Tile/tsconfig.tsbuildinfo +1 -1
  64. package/build/components/Tooltip/tsconfig.tsbuildinfo +1 -1
  65. package/build/components/index.css +1 -1
  66. package/build/components/index.css.map +1 -1
  67. package/build/components/index.js +7 -7
  68. package/build/components/index.js.map +1 -1
  69. package/build/components/static.js +1 -1
  70. package/build/components/static.js.map +1 -1
  71. package/build/components/tsconfig.tsbuildinfo +1 -1
  72. package/build/components/types/src/components/Forms/Message/Message.d.ts +1 -1
  73. package/build/components/types/src/components/Megamenu/index.d.ts +2 -0
  74. package/build/components/types/src/components/Modal/Modal.d.ts +6 -0
  75. package/build/components/types/src/components/Modal/ModalBody.d.ts +1 -0
  76. package/build/components/types/src/components/Modal/ModalProductFooter.d.ts +10 -0
  77. package/build/components/types/src/components/Modal/ModalProductHeader.d.ts +9 -0
  78. package/build/components/types/src/components/Modal/index.d.ts +2 -0
  79. package/build/components/types/src/components/Section/Section.d.ts +1 -1
  80. package/build/components/types/src/components/index.d.ts +3 -2
  81. package/build/lib/after-components.css +1 -1
  82. package/build/lib/after-components.css.map +1 -1
  83. package/build/lib/before-components.css +1 -1
  84. package/build/lib/before-components.css.map +1 -1
  85. package/build/lib/components.css +1 -1
  86. package/build/lib/components.css.map +1 -1
  87. package/build/lib/scripts.js +1 -1
  88. package/build/lib/scripts.js.map +1 -1
  89. package/build/lib/style.css +1 -1
  90. package/build/lib/style.css.map +1 -1
  91. package/build/lib/tsconfig.tsbuildinfo +1 -1
  92. package/package.json +1 -1
  93. package/src/components/CarouselPromotions/styles/mixins.scss +6 -8
  94. package/src/components/Forms/Message/Message.tsx +6 -2
  95. package/src/components/Forms/Message/styles/style.scss +6 -0
  96. package/src/components/Megamenu/index.ts +3 -0
  97. package/src/components/Megamenu/styles/config.scss +2 -0
  98. package/src/components/Megamenu/styles/mixins.scss +6 -1
  99. package/src/components/Megamenu/styles/style.scss +2 -1
  100. package/src/components/Modal/Modal.mdx +119 -3
  101. package/src/components/Modal/Modal.static.ts +4 -1
  102. package/src/components/Modal/Modal.tsx +29 -4
  103. package/src/components/Modal/ModalBody.tsx +3 -0
  104. package/src/components/Modal/ModalProductFooter.tsx +38 -0
  105. package/src/components/Modal/ModalProductHeader.tsx +36 -0
  106. package/src/components/Modal/index.ts +2 -0
  107. package/src/components/Modal/styles/config.scss +7 -0
  108. package/src/components/Modal/styles/mixins.scss +97 -13
  109. package/src/components/Modal/styles/style.scss +36 -0
  110. package/src/components/Modal/tests/Modal.conformance.test.js +8 -1
  111. package/src/components/Modal/tests/Modal.unit.test.js +36 -0
  112. package/src/components/Modal/tests/ModalProductHeader.unit.test.js +73 -0
  113. package/src/components/Pagination/styles/mixins.scss +1 -0
  114. package/src/components/Section/Section.tsx +7 -1
  115. package/src/components/Section/tests/Section.conformance.test.js +7 -1
  116. package/src/components/Section/tests/Section.unit.test.js +28 -1
  117. package/src/components/index.ts +11 -1
  118. package/src/styles/tokens/color.scss +4 -0
  119. package/src/styles/typography/mixins.scss +23 -0
  120. package/src/styles/typography/style.scss +4 -0
  121. 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, { ModalHeader, ModalBody, ModalFooter } from ".";
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
- attribute (modal title by default). When not present, focus is set on first
446
- focusable element, usually the close button.
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
- <div className={`${CLASS_ROOT}__header`}>
123
- <ModalCloseButton />
124
- {dialogTitle}
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 ? renderBody() : <ModalBody>{children}</ModalBody>}
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
- opacity config.$overlay-e,
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
- @each $breakpoint, $space in $spacing {
179
- @include breakpoint.get($breakpoint) {
180
- padding-top: 0;
181
- padding-right: $space;
182
- padding-bottom: $space;
183
- padding-left: $space;
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
- @at-root {
186
- .modal__body--colorful + .modal__footer {
187
- padding: $space !important;
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
+ });