@dnanpm/styleguide 3.9.14 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/build/cjs/components/Box/Box.d.ts +4 -0
  2. package/build/cjs/components/Box/Box.js +1 -1
  3. package/build/cjs/components/Button/Button.d.ts +4 -0
  4. package/build/cjs/components/Button/Button.js +1 -1
  5. package/build/cjs/components/ButtonArrow/ButtonArrow.js +9 -2
  6. package/build/cjs/components/Carousel/Carousel.js +4 -1
  7. package/build/cjs/components/EnergyLabel/EnergyLabel.d.ts +65 -0
  8. package/build/cjs/components/EnergyLabel/EnergyLabel.js +98 -0
  9. package/build/cjs/components/Notification/Notification.js +3 -3
  10. package/build/cjs/components/Overlay/Overlay.d.ts +64 -3
  11. package/build/cjs/components/Overlay/Overlay.js +11 -8
  12. package/build/cjs/components/PriorityNavigation/PriorityNavigation.d.ts +0 -4
  13. package/build/cjs/components/PriorityNavigation/PriorityNavigation.js +20 -26
  14. package/build/cjs/components/index.d.ts +1 -0
  15. package/build/cjs/index.js +2 -0
  16. package/build/es/components/Box/Box.d.ts +4 -0
  17. package/build/es/components/Box/Box.js +1 -1
  18. package/build/es/components/Button/Button.d.ts +4 -0
  19. package/build/es/components/Button/Button.js +1 -1
  20. package/build/es/components/ButtonArrow/ButtonArrow.js +10 -3
  21. package/build/es/components/Carousel/Carousel.js +4 -1
  22. package/build/es/components/EnergyLabel/EnergyLabel.d.ts +65 -0
  23. package/build/es/components/EnergyLabel/EnergyLabel.js +90 -0
  24. package/build/es/components/Notification/Notification.js +3 -3
  25. package/build/es/components/Overlay/Overlay.d.ts +64 -3
  26. package/build/es/components/Overlay/Overlay.js +11 -8
  27. package/build/es/components/PriorityNavigation/PriorityNavigation.d.ts +0 -4
  28. package/build/es/components/PriorityNavigation/PriorityNavigation.js +20 -26
  29. package/build/es/components/index.d.ts +1 -0
  30. package/build/es/index.js +1 -0
  31. package/package.json +2 -2
@@ -65,6 +65,10 @@ interface Props {
65
65
  * e.g., "important information" or "example."
66
66
  */
67
67
  ariaLabel?: string;
68
+ /**
69
+ * Screen reader live region policy for the box content
70
+ */
71
+ ariaLive?: 'off' | 'polite' | 'assertive';
68
72
  /**
69
73
  * Allows to pass a role to the component
70
74
  */
@@ -26,7 +26,7 @@ const BoxWrapper = styled__default.default.div `
26
26
  `;
27
27
  const Box = (_a) => {
28
28
  var { elevation = 'none', 'data-testid': dataTestId } = _a, props = tslib.__rest(_a, ["elevation", 'data-testid']);
29
- return (React__default.default.createElement(BoxWrapper, { id: props.id, "$elevation": elevation, shadow: props.shadow, width: props.width, height: props.height, margin: props.margin, padding: props.padding, className: props.className, "data-testid": dataTestId, role: props.role, "aria-label": props.ariaLabel }, props.children));
29
+ return (React__default.default.createElement(BoxWrapper, { id: props.id, "$elevation": elevation, shadow: props.shadow, width: props.width, height: props.height, margin: props.margin, padding: props.padding, className: props.className, "data-testid": dataTestId, role: props.role, "aria-label": props.ariaLabel, "aria-live": props.ariaLive }, props.children));
30
30
  };
31
31
 
32
32
  exports.default = Box;
@@ -24,6 +24,10 @@ export interface Props {
24
24
  * Allows to change the type of resulting HTML element from button (`<button></button>`) to anchor (`<a href="..."></a>`)
25
25
  */
26
26
  href?: string;
27
+ /**
28
+ * Allows to set the target attribute for the link
29
+ */
30
+ target?: '_self' | '_blank' | '_parent' | '_top';
27
31
  /**
28
32
  * Content of Button component
29
33
  */
@@ -123,7 +123,7 @@ const Element = styled__default.default.button `
123
123
  /** @visibleName Button */
124
124
  const Button = (_a) => {
125
125
  var { type = 'submit', 'data-testid': dataTestId, 'data-no-close': dataNoClose, 'data-track-value': dataTrackValue, 'aria-label': ariaLabel } = _a, props = tslib.__rest(_a, ["type", 'data-testid', 'data-no-close', 'data-track-value', 'aria-label']);
126
- return (React__default.default.createElement(Element, Object.assign({ id: props.id, as: props.href ? 'a' : undefined, type: props.href ? undefined : type, href: props.href, onClick: props.onClick, onMouseDown: props.onMouseDown, small: props.small, darkBg: props.darkBg, fullWidth: props.fullWidth, "$loading": props.loading, tabIndex: props.loading ? -1 : 0, "data-loading": props.loading, className: props.className, "data-testid": dataTestId, "data-no-close": dataNoClose, "data-track-value": dataTrackValue, "aria-label": ariaLabel }, props.dataAttributes, (!props.href && {
126
+ return (React__default.default.createElement(Element, Object.assign({ id: props.id, as: props.href ? 'a' : undefined, type: props.href ? undefined : type, href: props.href, target: props.href ? props.target : undefined, rel: props.target === '_blank' ? 'noopener noreferrer' : undefined, onClick: props.onClick, onMouseDown: props.onMouseDown, small: props.small, darkBg: props.darkBg, fullWidth: props.fullWidth, "$loading": props.loading, tabIndex: props.loading ? -1 : 0, "data-loading": props.loading, className: props.className, "data-testid": dataTestId, "data-no-close": dataNoClose, "data-track-value": dataTrackValue, "aria-label": ariaLabel }, props.dataAttributes, (!props.href && {
127
127
  name: props.name,
128
128
  disabled: props.disabled,
129
129
  })), props.loading ? (React__default.default.createElement(PixelLoader.default, { color: props.darkBg ? theme.default.color.default.white : theme.default.color.default.plum, label: props.loadingLabel })) : (React__default.default.createElement("span", { "data-testid": dataTestId && `${dataTestId}-text`, "data-no-close": dataNoClose }, props.children))));
@@ -10,7 +10,6 @@ var theme = require('../../themes/theme.js');
10
10
  var styledUtils = require('../../utils/styledUtils.js');
11
11
  var ButtonPrimary = require('../ButtonPrimary/ButtonPrimary.js');
12
12
  var ButtonSecondary = require('../ButtonSecondary/ButtonSecondary.js');
13
- var Icon = require('../Icon/Icon.js');
14
13
 
15
14
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
16
15
 
@@ -73,7 +72,15 @@ const buttonsMap = {
73
72
  const ButtonArrow = (_a) => {
74
73
  var { variant = 'primary', 'data-testid': dataTestId, 'data-track-value': dataTrackValue, 'aria-label': ariaLabel } = _a, props = tslib.__rest(_a, ["variant", 'data-testid', 'data-track-value', 'aria-label']);
75
74
  const Element = buttonsMap[variant];
76
- return (React__default.default.createElement(Element, { id: props.id, href: props.href, name: props.name, type: props.type, onClick: props.onClick, onMouseDown: props.onMouseDown, darkBg: props.darkBg, disabled: props.disabled, className: props.className, "data-testid": dataTestId, "data-track-value": dataTrackValue, dataAttributes: props.dataAttributes, "aria-label": ariaLabel }, props.direction ? (React__default.default.createElement(Icon.default, { icon: iconsMap[props.direction], size: "1rem", "aria-hidden": true })) : (React.isValidElement(props.icon) && props.icon.type === Icon.default && props.icon)));
75
+ let iconElement = null;
76
+ if (props.direction) {
77
+ const IconComponent = iconsMap[props.direction];
78
+ iconElement = React__default.default.createElement(IconComponent, { size: "1rem" });
79
+ }
80
+ else if (React.isValidElement(props.icon)) {
81
+ iconElement = React.cloneElement(props.icon, { size: '1rem' });
82
+ }
83
+ return (React__default.default.createElement(Element, { id: props.id, href: props.href, name: props.name, type: props.type, onClick: props.onClick, onMouseDown: props.onMouseDown, darkBg: props.darkBg, disabled: props.disabled, className: props.className, "data-testid": dataTestId, "data-track-value": dataTrackValue, dataAttributes: props.dataAttributes, "aria-label": ariaLabel }, iconElement));
77
84
  };
78
85
 
79
86
  exports.default = ButtonArrow;
@@ -184,7 +184,8 @@ const Carousel = (_a) => {
184
184
  };
185
185
  const visibleItems = props.visibleItems || calculatedItems;
186
186
  const slidesWrapperGapSizePx = 20;
187
- const slideScreensCount = React.Children.count(props.children) - Math.floor(visibleItems) + 1;
187
+ const slidesCount = React.Children.count(props.children);
188
+ const slideScreensCount = Math.max(1, slidesCount - Math.floor(visibleItems) + 1);
188
189
  const step = getStep((_b = props.swipeStep) !== null && _b !== void 0 ? _b : 1, visibleItems);
189
190
  const currentStepIndex = Math.ceil(currentIndex / step);
190
191
  const totalSwipeSteps = Math.ceil(slideScreensCount / step + ((slideScreensCount - 1) % step !== 0 ? 1 : 0));
@@ -306,6 +307,8 @@ const Carousel = (_a) => {
306
307
  setIsSwiping(false);
307
308
  };
308
309
  const handleSlidesPointerDown = (e) => {
310
+ if (e.button !== 0)
311
+ return;
309
312
  if (slidesWrapperRef.current && scrollbarRef.current && knobRef.current) {
310
313
  data.startX = e.pageX;
311
314
  data.startTime = Date.now();
@@ -0,0 +1,65 @@
1
+ import React from 'react';
2
+ interface Props {
3
+ /**
4
+ * Unique ID for the component
5
+ */
6
+ id?: string;
7
+ /**
8
+ * Allows to pass a custom className
9
+ */
10
+ className?: string;
11
+ /**
12
+ * Allows to pass testid string for testing purposes
13
+ */
14
+ 'data-testid'?: string;
15
+ /**
16
+ * Allows to pass a screen reader label to the component
17
+ */
18
+ ariaLabel?: string;
19
+ /**
20
+ * Allows to set property `aria-label` for `ButtonClose` element
21
+ */
22
+ closeButtonLabel?: string;
23
+ /**
24
+ * Allows to set the source of the badge image
25
+ */
26
+ badgeSrc: string;
27
+ /**
28
+ * Allows to set the title of the modal
29
+ */
30
+ modalTitle?: string;
31
+ /**
32
+ * Allows to set the source of the image in the modal
33
+ */
34
+ modalImageSrc?: string;
35
+ /**
36
+ * Allows to set the label of the modal
37
+ */
38
+ modalLabel?: string;
39
+ /**
40
+ * Allows to set the label of the button that opens the PDF
41
+ */
42
+ pdfButtonLabel?: string;
43
+ /**
44
+ * Allows to set the href of the PDF file
45
+ */
46
+ pdfHref?: string;
47
+ /**
48
+ * Allows to set the label for the error state
49
+ */
50
+ errorLabel?: string;
51
+ /**
52
+ * Allows to set the title for the error state
53
+ */
54
+ errorTitle?: string;
55
+ /**
56
+ * Allows to hide application from assistive screenreaders and other assistive technologies while the modal is open
57
+ *
58
+ * @default '#__next'
59
+ */
60
+ modalAppElement?: string;
61
+ }
62
+ /** @visibleName Energy Label */
63
+ declare const EnergyLabel: ({ "data-testid": dataTestId, modalAppElement, ...props }: Props) => React.JSX.Element | null;
64
+ /** @component */
65
+ export default EnergyLabel;
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var tslib = require('tslib');
6
+ var React = require('react');
7
+ var icons = require('@dnanpm/icons');
8
+ var Modal = require('../Modal/Modal.js');
9
+ var styled = require('../../themes/styled.js');
10
+ var styledUtils = require('../../utils/styledUtils.js');
11
+ var Button = require('../Button/Button.js');
12
+ var theme = require('../../themes/theme.js');
13
+
14
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
15
+
16
+ var React__default = /*#__PURE__*/_interopDefaultCompat(React);
17
+
18
+ const ERROR_IMAGE = 'https://res.cloudinary.com/dnaoyj/image/upload/v1731073400/Assets/KLT/Enriched%20icons/enriched-error-state.png';
19
+ const ButtonElement = styled.default.button `
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ background-color: transparent;
24
+ border: 0;
25
+ padding: 0;
26
+ cursor: pointer;
27
+ `;
28
+ const StyledModal = styled.default(Modal.default) `
29
+ > div > div {
30
+ max-height: 100%;
31
+ height: 100%;
32
+ width: 100%;
33
+ margin: 0;
34
+
35
+ ${styledUtils.media.xs `
36
+ width: 23.438rem;
37
+ height: 50rem;
38
+ border-radius: ${theme.default.radius.default};
39
+ `};
40
+
41
+ > div {
42
+ border-radius: 0;
43
+ }
44
+ }
45
+ `;
46
+ const ModalLabel = styled.default.p `
47
+ font-size: ${theme.default.fontSize.s};
48
+ margin: 0;
49
+ `;
50
+ const ButtonContent = styled.default.span `
51
+ display: inline-flex;
52
+ align-items: center;
53
+ gap: ${styledUtils.getMultipliedSize(theme.default.base.baseWidth, 0.5)};
54
+ `;
55
+ const ErrorContainer = styled.default.div `
56
+ display: flex;
57
+ padding-top: ${styledUtils.getMultipliedSize(theme.default.base.baseHeight, 2)};
58
+ flex-direction: column;
59
+ text-align: center;
60
+ align-items: center;
61
+ flex-direction: column;
62
+ gap: ${styledUtils.getMultipliedSize(theme.default.base.baseHeight, 3)};
63
+ `;
64
+ const Image = styled.default.img `
65
+ width: ${p => p.imgWidth};
66
+ `;
67
+ /** @visibleName Energy Label */
68
+ const EnergyLabel = (_a) => {
69
+ var { 'data-testid': dataTestId, modalAppElement = '#__next' } = _a, props = tslib.__rest(_a, ['data-testid', "modalAppElement"]);
70
+ const [isOpen, setIsOpen] = React.useState(false);
71
+ const [hasError, setHasError] = React.useState(false);
72
+ const [imgError, setImgError] = React.useState(false);
73
+ const handleOpen = () => {
74
+ setIsOpen(true);
75
+ };
76
+ const handleClose = () => {
77
+ setIsOpen(false);
78
+ };
79
+ const renderFooter = () => (React__default.default.createElement(Button.default, { fullWidth: true, href: props.pdfHref, target: "_blank" },
80
+ React__default.default.createElement(ButtonContent, null,
81
+ props.pdfButtonLabel,
82
+ " ",
83
+ React__default.default.createElement(icons.Open, null))));
84
+ if (imgError) {
85
+ return null;
86
+ }
87
+ return (React__default.default.createElement(React__default.default.Fragment, null,
88
+ React__default.default.createElement(ButtonElement, { onClick: handleOpen, "aria-label": props.ariaLabel, "data-testid": dataTestId, id: props.id, className: props.className },
89
+ React__default.default.createElement(Image, { src: props.badgeSrc, alt: "", "aria-hidden": true, imgWidth: "55px", onError: () => setImgError(true) })),
90
+ React__default.default.createElement(StyledModal, { appElement: modalAppElement, isOpen: isOpen, onRequestClose: handleClose, title: props.modalTitle, closeLabel: props.closeButtonLabel, footer: renderFooter() }, hasError || !props.modalImageSrc ? (React__default.default.createElement(ErrorContainer, null,
91
+ React__default.default.createElement("h3", null, props.errorTitle),
92
+ React__default.default.createElement(Image, { src: ERROR_IMAGE, alt: "", imgWidth: "160px" }),
93
+ React__default.default.createElement("p", null, props.errorLabel))) : (React__default.default.createElement(React__default.default.Fragment, null,
94
+ React__default.default.createElement(Image, { src: props.modalImageSrc, alt: "", imgWidth: "100%", onError: () => setHasError(true) }),
95
+ React__default.default.createElement(ModalLabel, null, props.modalLabel))))));
96
+ };
97
+
98
+ exports.default = EnergyLabel;
@@ -33,7 +33,7 @@ const NotificationWrapper = styled__default.default.div `
33
33
  ${sharedStyles}
34
34
  border-color: ${({ $type }) => theme.default.color.notification[$type]};
35
35
  `;
36
- const StaticWrapper = styled__default.default.section `
36
+ const StaticWrapper = styled__default.default.div `
37
37
  ${sharedStyles}
38
38
  border-color: ${({ $type }) => theme.default.color.notification[$type]};
39
39
  `;
@@ -43,7 +43,7 @@ const IconWrapper = styled__default.default.div `
43
43
  padding: 0.5rem;
44
44
  background-color: ${({ $type }) => theme.default.color.notification[$type]};
45
45
  `;
46
- const ContentWrapper = styled__default.default.span `
46
+ const ContentWrapper = styled__default.default.div `
47
47
  margin: auto 0;
48
48
  padding: 0.5rem 0;
49
49
  width: 100%;
@@ -68,7 +68,7 @@ const Notification = ({ type = 'info', 'data-testid': dataTestId, isStatic = fal
68
68
  React__default.default.createElement(ContentWrapper, null, children),
69
69
  closeButton && (React__default.default.createElement(ButtonCloseStyled, { onClick: onClickCloseButton, "aria-label": closeButtonLabel },
70
70
  React__default.default.createElement(Icon.default, { icon: icons.Close, color: "currentColor", "aria-hidden": true })))));
71
- return isStatic ? (React__default.default.createElement(StaticWrapper, { "$type": type, className: className, "data-testid": dataTestId, "aria-label": ariaLabel }, renderContent())) : (React__default.default.createElement(NotificationWrapper, { "$type": type, className: className, "data-testid": dataTestId, role: "alert" }, renderContent()));
71
+ return isStatic ? (React__default.default.createElement(StaticWrapper, { "$type": type, className: className, "data-testid": dataTestId, "aria-label": ariaLabel, role: "region" }, renderContent())) : (React__default.default.createElement(NotificationWrapper, { "$type": type, className: className, "data-testid": dataTestId, role: "alert" }, renderContent()));
72
72
  };
73
73
 
74
74
  exports.default = Notification;
@@ -1,6 +1,8 @@
1
- import type { MouseEvent, ReactNode } from 'react';
1
+ import type { MouseEvent, ReactNode, KeyboardEvent } from 'react';
2
2
  import React from 'react';
3
- interface Props {
3
+ type AriaLandmarkRoles = 'none' | 'main' | 'navigation' | 'banner' | 'complementary' | 'contentinfo' | 'form' | 'region' | 'search';
4
+ type Html5Tag = 'main' | 'div' | 'section' | 'article' | 'aside' | 'nav' | 'header' | 'footer';
5
+ interface BaseProps {
4
6
  /**
5
7
  * Unique ID for the component
6
8
  */
@@ -13,6 +15,11 @@ interface Props {
13
15
  * On overlay element mouse click
14
16
  */
15
17
  onClick?: (e: MouseEvent<HTMLElement>) => void;
18
+ /**
19
+ * Callback function for keydown events on the overlay element to be used with prop `onClick`.
20
+ * This can be used to handle keyboard interactions, such as closing the overlay with the Escape key.
21
+ */
22
+ onKeyDown?: (e: KeyboardEvent<HTMLElement>) => void;
16
23
  /**
17
24
  * Allows to set DOM node to which Overlay component will be appended. The node must already exist.
18
25
  * Use `false` to render in place without use of `createPortal`
@@ -42,8 +49,62 @@ interface Props {
42
49
  * Allows to pass a custom className
43
50
  */
44
51
  className?: string;
52
+ /**
53
+ * Specifies the HTML5 tag to be used for the content wrapper.
54
+ * It is recommended to use native semantic HTML elements whenever possible.
55
+ * If a suitable semantic element is not available, a `<div>` with an appropriate ARIA role should be used (role from `role`).
56
+ * ARIA roles from https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles
57
+ *
58
+ * @param {Html5Tag} main Uses `<main>` tag for the content wrapper.
59
+ * @param {Html5Tag} div Uses `<div>` tag for the content wrapper.
60
+ * @param {Html5Tag} section Uses `<section>` tag for the content wrapper.
61
+ * @param {Html5Tag} article Uses `<article>` tag for the content wrapper.
62
+ * @param {Html5Tag} aside Uses `<aside>` tag for the content wrapper.
63
+ * @param {Html5Tag} nav Uses `<nav>` tag for the content wrapper.
64
+ * @param {Html5Tag} header Uses `<header>` tag for the content wrapper.
65
+ * @param {Html5Tag} footer Uses `<footer>` tag for the content wrapper.
66
+ *
67
+ * @default 'section'
68
+ */
69
+ as?: Html5Tag;
70
+ /**
71
+ * Specifies the ARIA landmark role for the content wrapper and is used when `as` is a div.
72
+ * It is always recommended to use native semantic HTML elements from `as` whenever possible.
73
+ * ARIA roles from https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles
74
+ *
75
+ * @param {AriaLandmarkRoles} main Represents the main content with `role="main"` (acting as <main>).
76
+ * @param {AriaLandmarkRoles} navigation Represents navigation links with `role="navigation"` (acting as <nav>).
77
+ * @param {AriaLandmarkRoles} banner Represents introductory content with `role="banner"` (acting as <header>).
78
+ * @param {AriaLandmarkRoles} complementary Represents related content with `role="complementary"` (acting as <aside>).
79
+ * @param {AriaLandmarkRoles} contentinfo Represents footer information with `role="contentinfo"` (acting as <footer>).
80
+ * @param {AriaLandmarkRoles} form Indicates an interactive form with `role="form"` (acting as <form>).
81
+ * @param {AriaLandmarkRoles} region Represents an important section with `role="region"` (acting as <section>).
82
+ * @param {AriaLandmarkRoles} search Represents a search area with `role="search"` (acting as <search>).
83
+ */
84
+ role?: AriaLandmarkRoles;
85
+ /**
86
+ * If a role from `role` is used more than once on a page, the aria-label attribute should also be used in order to distinguish between the two regions.
87
+ */
88
+ ariaLabel?: string;
89
+ /**
90
+ * Ensures that overlays maintain proper focus management and accessibility.
91
+ * If `isFocusTrapped` is true, an appropriate ARIA role should be used (role from `focusableTrappedWidgetRole`).
92
+ * If `isFocusTrapped` is false, the tabbable element will not have an ARIA role.
93
+ *
94
+ * @default true
95
+ */
96
+ isFocusTrapped?: boolean;
45
97
  }
98
+ type DivProps = BaseProps & {
99
+ as?: 'div';
100
+ role: AriaLandmarkRoles;
101
+ };
102
+ type NonDivProps = BaseProps & {
103
+ as?: Exclude<Html5Tag, 'div'>;
104
+ role?: undefined;
105
+ };
106
+ type Props = DivProps | NonDivProps;
46
107
  /** @visibleName Overlay */
47
- declare const Overlay: ({ portalContainer: portalContainerSelector, appElement: appElementSelector, preventBodyScroll, "data-testid": dataTestId, ...props }: Props) => React.JSX.Element;
108
+ declare const Overlay: ({ portalContainer: portalContainerSelector, appElement: appElementSelector, preventBodyScroll, "data-testid": dataTestId, as, role, isFocusTrapped, ...props }: Props) => React.JSX.Element;
48
109
  /** @component */
49
110
  export default Overlay;
@@ -33,28 +33,31 @@ const Element = styled.default.div `
33
33
  `;
34
34
  /** @visibleName Overlay */
35
35
  const Overlay = (_a) => {
36
- var { portalContainer: portalContainerSelector = 'body', appElement: appElementSelector = '#__next', preventBodyScroll = true, 'data-testid': dataTestId } = _a, props = tslib.__rest(_a, ["portalContainer", "appElement", "preventBodyScroll", 'data-testid']);
36
+ var { portalContainer: portalContainerSelector = 'body', appElement: appElementSelector = '#__next', preventBodyScroll = true, 'data-testid': dataTestId, as = 'section', role, isFocusTrapped = true } = _a, props = tslib.__rest(_a, ["portalContainer", "appElement", "preventBodyScroll", 'data-testid', "as", "role", "isFocusTrapped"]);
37
37
  const contentRef = React.useRef(null);
38
38
  const portalContainer = portalContainerSelector
39
39
  ? document.querySelector(portalContainerSelector)
40
40
  : null;
41
41
  const appElement = document.querySelector(appElementSelector);
42
- const overlayElement = (React__default.default.createElement(Element, { id: props.id, onClick: props.onClick, className: props.className, "data-testid": dataTestId },
43
- React__default.default.createElement("div", { ref: contentRef, tabIndex: -1 }, props.children)));
42
+ const overlayElement = (React__default.default.createElement(Element, { id: props.id, onClick: props.onClick, className: props.className, "data-testid": dataTestId, onKeyDown: props.onKeyDown, "aria-label": props.ariaLabel, as: as, role: as === 'div' ? role : undefined },
43
+ React__default.default.createElement("div", { ref: contentRef, tabIndex: -1, "data-testid": dataTestId ? `${dataTestId}-content` : '' }, props.children)));
44
44
  React.useEffect(() => {
45
45
  if (preventBodyScroll) {
46
46
  document.body.style.setProperty('overflow', 'hidden');
47
+ document.body.style.setProperty('position', 'relative');
48
+ document.body.style.setProperty('top', '0');
49
+ document.body.style.setProperty('left', '0');
47
50
  return () => {
48
51
  document.body.style.removeProperty('overflow');
49
52
  };
50
53
  }
51
54
  return undefined;
52
- });
55
+ }, [preventBodyScroll]);
53
56
  React.useEffect(() => {
54
57
  var _a;
55
58
  if (appElement) {
56
59
  const focusTrapStart = document.createElement('div');
57
- focusTrapStart.setAttribute('tabindex', '0');
60
+ focusTrapStart.setAttribute('tabindex', isFocusTrapped ? '0' : '-1');
58
61
  focusTrapStart.addEventListener('focus', () => {
59
62
  var _a;
60
63
  (_a = contentRef === null || contentRef === void 0 ? void 0 : contentRef.current) === null || _a === void 0 ? void 0 : _a.focus();
@@ -67,15 +70,15 @@ const Overlay = (_a) => {
67
70
  (_a = contentRef === null || contentRef === void 0 ? void 0 : contentRef.current) === null || _a === void 0 ? void 0 : _a.focus();
68
71
  document.body.prepend(focusTrapStart);
69
72
  document.body.append(focusTrapEnd);
70
- appElement.setAttribute('aria-hidden', 'true');
73
+ appElement.setAttribute('inert', 'true');
71
74
  return () => {
72
75
  document.body.removeChild(focusTrapStart);
73
76
  document.body.removeChild(focusTrapEnd);
74
- appElement.removeAttribute('aria-hidden');
77
+ appElement.removeAttribute('inert');
75
78
  };
76
79
  }
77
80
  return undefined;
78
- });
81
+ }, [appElement, contentRef, isFocusTrapped]);
79
82
  return portalContainerSelector === false
80
83
  ? overlayElement
81
84
  : reactDom.createPortal(overlayElement, portalContainer !== null && portalContainer !== void 0 ? portalContainer : document.body);
@@ -41,10 +41,6 @@ interface Props {
41
41
  * Allows to pass a custom className
42
42
  */
43
43
  className?: string;
44
- /**
45
- * Allows to pass a custom aria-label for mobile dropdown button
46
- */
47
- dropdownButtonAriaLabel?: string;
48
44
  /**
49
45
  * Allows to pass testid string for testing purposes
50
46
  */
@@ -276,14 +276,20 @@ const PriorityNavigation = (_a) => {
276
276
  });
277
277
  React.useEffect(() => {
278
278
  if (!isMobile) {
279
- dispatch({
280
- type: 'resetNavigationState',
281
- payload: {
282
- navigationItems: props.children,
283
- },
279
+ requestAnimationFrame(() => {
280
+ dispatch({
281
+ type: 'resetNavigationState',
282
+ payload: {
283
+ navigationItems: props.children,
284
+ },
285
+ });
286
+ setTimeout(() => {
287
+ checkHorizontalOverflow();
288
+ }, 0);
284
289
  });
285
290
  }
286
291
  setIsMobileNavigationOpen(false);
292
+ // eslint-disable-next-line react-hooks/exhaustive-deps
287
293
  }, [isMobile, props.children]);
288
294
  React.useLayoutEffect(() => {
289
295
  if (!isMobile) {
@@ -299,31 +305,18 @@ const PriorityNavigation = (_a) => {
299
305
  checkHorizontalOverflow,
300
306
  ]);
301
307
  const handleNavigationListKeyDown = React.useCallback((e) => {
302
- var _a;
303
- if (isMobile && isMobileNavigationOpen) {
308
+ var _a, _b;
309
+ if (isMobile && isMobileNavigationOpen && e.key === 'Tab') {
304
310
  const focusableElements = (_a = navigationListRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll('a, button, input, [tabindex]:not([tabindex="-1"])');
305
311
  const lastElement = focusableElements === null || focusableElements === void 0 ? void 0 : focusableElements[focusableElements.length - 1];
306
- if (document.activeElement === lastElement) {
312
+ const goingForward = !e.shiftKey;
313
+ if (goingForward && document.activeElement === lastElement) {
307
314
  e.preventDefault();
308
315
  setIsMobileNavigationOpen(false);
316
+ (_b = dropdownButtonRef.current) === null || _b === void 0 ? void 0 : _b.focus();
309
317
  }
310
318
  }
311
319
  }, [isMobile, isMobileNavigationOpen]);
312
- const handleNavigationListFocus = React.useCallback(() => {
313
- if (isMobile && isMobileNavigationOpen) {
314
- setTimeout(() => {
315
- var _a, _b;
316
- const focusableElements = (_a = navigationListRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll('a, button, input, [tabindex]:not([tabindex="-1"])');
317
- if (!focusableElements || focusableElements.length === 0)
318
- return;
319
- const lastElement = focusableElements[focusableElements.length - 1];
320
- if (document.activeElement === lastElement) {
321
- setIsMobileNavigationOpen(false);
322
- (_b = dropdownButtonRef.current) === null || _b === void 0 ? void 0 : _b.focus();
323
- }
324
- }, 0);
325
- }
326
- }, [isMobile, isMobileNavigationOpen]);
327
320
  const handleItemClick = (e) => {
328
321
  setIsMobileNavigationOpen(false);
329
322
  if (props.onClick) {
@@ -339,7 +332,7 @@ const PriorityNavigation = (_a) => {
339
332
  props.categoryLabel && React__default.default.createElement(Category, null, props.categoryLabel),
340
333
  selectedItem),
341
334
  React__default.default.createElement(Icon.default, { icon: isMobileNavigationOpen ? icons.OvalChevronUp : icons.OvalChevronDown, size: "2.5rem" }))),
342
- React__default.default.createElement(NavigationList, { ref: navigationListRef, isMobileNavigationOpen: isMobileNavigationOpen, onKeyDown: handleNavigationListKeyDown, onFocus: handleNavigationListFocus }, React.Children.map([...state.navigationItems, ...(isMobile ? state.dropdownItems : [])], (navigationItem, index) => {
335
+ React__default.default.createElement(NavigationList, { ref: navigationListRef, isMobileNavigationOpen: isMobileNavigationOpen, onKeyDown: handleNavigationListKeyDown }, React.Children.map([...state.navigationItems, ...(isMobile ? state.dropdownItems : [])], (navigationItem, index) => {
343
336
  if (React.isValidElement(navigationItem) &&
344
337
  navigationItem.type === PriorityNavigationItem.default) {
345
338
  return (React__default.default.createElement(PriorityNavigationItem.default, { id: navigationItem.props.id, key: navigationItem.key, onClick: handleItemClick, onKeyDown: navigationItem.props.onKeyDown || props.onKeyDown, isActive: navigationItem.props.isActive, className: navigationItem.props.className, "data-testid": navigationItem.props['data-testid'], ref: instance => {
@@ -361,8 +354,9 @@ const PriorityNavigation = (_a) => {
361
354
  !isMobile && Boolean(state.dropdownItems.length) && (React__default.default.createElement(React__default.default.Fragment, null,
362
355
  React__default.default.createElement("div", null,
363
356
  React__default.default.createElement(ButtonIcon.default, { ref: dropdownButtonRef, ariaLabel: openMoreSubpagesAriaLabel, ariaExpanded: isDropdownListOpen, onClick: toggleDropdown, icon: isDropdownListOpen ? icons.ChevronUp : icons.ChevronDown, isReversed: true, "data-testid": "dropdown-button" }, dropdownButtonLabel)),
364
- React__default.default.createElement(DropdownList, { isDropdownListOpen: isDropdownListOpen }, state.dropdownItems.map(dropdownItem => React.isValidElement(dropdownItem) &&
365
- dropdownItem.type === PriorityNavigationItem.default && (React__default.default.createElement(PriorityNavigationItem.default, { id: dropdownItem.props.id, key: dropdownItem.key, onClick: dropdownItem.props.onClick || props.onClick, onKeyDown: dropdownItem.props.onKeyDown || props.onKeyDown, isActive: dropdownItem.props.isActive, className: dropdownItem.props.className, "data-testid": dropdownItem.props['data-testid'] }, dropdownItem.props.children))))))))));
357
+ isDropdownListOpen && (React__default.default.createElement(DropdownList, { isDropdownListOpen: isDropdownListOpen }, state.dropdownItems.map(dropdownItem => React.isValidElement(dropdownItem) &&
358
+ dropdownItem.type === PriorityNavigationItem.default && (React__default.default.createElement(PriorityNavigationItem.default, { id: dropdownItem.props.id, key: dropdownItem.key, onClick: dropdownItem.props.onClick || props.onClick, onKeyDown: dropdownItem.props.onKeyDown ||
359
+ props.onKeyDown, isActive: dropdownItem.props.isActive, className: dropdownItem.props.className, "data-testid": dropdownItem.props['data-testid'] }, dropdownItem.props.children)))))))))));
366
360
  };
367
361
 
368
362
  exports.default = PriorityNavigation;
@@ -18,6 +18,7 @@ export { default as Divider } from './Divider/Divider';
18
18
  export { default as DnaLogo } from './DnaLogo/DnaLogo';
19
19
  export { default as Drawer } from './Drawer/Drawer';
20
20
  export { default as EmptyState } from './EmptyState/EmptyState';
21
+ export { default as EnergyLabel } from './EnergyLabel/EnergyLabel';
21
22
  export { default as Expander } from './Expander/Expander';
22
23
  export { default as Floater } from './Floater/Floater';
23
24
  export { default as Footer } from './Footer/Footer';
@@ -20,6 +20,7 @@ var Divider = require('./components/Divider/Divider.js');
20
20
  var DnaLogo = require('./components/DnaLogo/DnaLogo.js');
21
21
  var Drawer = require('./components/Drawer/Drawer.js');
22
22
  var EmptyState = require('./components/EmptyState/EmptyState.js');
23
+ var EnergyLabel = require('./components/EnergyLabel/EnergyLabel.js');
23
24
  var Expander = require('./components/Expander/Expander.js');
24
25
  var Floater = require('./components/Floater/Floater.js');
25
26
  var Footer = require('./components/Footer/Footer.js');
@@ -189,6 +190,7 @@ exports.Divider = Divider.default;
189
190
  exports.DnaLogo = DnaLogo.default;
190
191
  exports.Drawer = Drawer.default;
191
192
  exports.EmptyState = EmptyState.default;
193
+ exports.EnergyLabel = EnergyLabel.default;
192
194
  exports.Expander = Expander.default;
193
195
  exports.Floater = Floater.default;
194
196
  exports.Footer = Footer.default;
@@ -65,6 +65,10 @@ interface Props {
65
65
  * e.g., "important information" or "example."
66
66
  */
67
67
  ariaLabel?: string;
68
+ /**
69
+ * Screen reader live region policy for the box content
70
+ */
71
+ ariaLive?: 'off' | 'polite' | 'assertive';
68
72
  /**
69
73
  * Allows to pass a role to the component
70
74
  */
@@ -17,7 +17,7 @@ const BoxWrapper = styled__default.div `
17
17
  `;
18
18
  const Box = (_a) => {
19
19
  var { elevation = 'none', 'data-testid': dataTestId } = _a, props = __rest(_a, ["elevation", 'data-testid']);
20
- return (React__default.createElement(BoxWrapper, { id: props.id, "$elevation": elevation, shadow: props.shadow, width: props.width, height: props.height, margin: props.margin, padding: props.padding, className: props.className, "data-testid": dataTestId, role: props.role, "aria-label": props.ariaLabel }, props.children));
20
+ return (React__default.createElement(BoxWrapper, { id: props.id, "$elevation": elevation, shadow: props.shadow, width: props.width, height: props.height, margin: props.margin, padding: props.padding, className: props.className, "data-testid": dataTestId, role: props.role, "aria-label": props.ariaLabel, "aria-live": props.ariaLive }, props.children));
21
21
  };
22
22
 
23
23
  export { Box as default };
@@ -24,6 +24,10 @@ export interface Props {
24
24
  * Allows to change the type of resulting HTML element from button (`<button></button>`) to anchor (`<a href="..."></a>`)
25
25
  */
26
26
  href?: string;
27
+ /**
28
+ * Allows to set the target attribute for the link
29
+ */
30
+ target?: '_self' | '_blank' | '_parent' | '_top';
27
31
  /**
28
32
  * Content of Button component
29
33
  */
@@ -114,7 +114,7 @@ const Element = styled__default.button `
114
114
  /** @visibleName Button */
115
115
  const Button = (_a) => {
116
116
  var { type = 'submit', 'data-testid': dataTestId, 'data-no-close': dataNoClose, 'data-track-value': dataTrackValue, 'aria-label': ariaLabel } = _a, props = __rest(_a, ["type", 'data-testid', 'data-no-close', 'data-track-value', 'aria-label']);
117
- return (React__default.createElement(Element, Object.assign({ id: props.id, as: props.href ? 'a' : undefined, type: props.href ? undefined : type, href: props.href, onClick: props.onClick, onMouseDown: props.onMouseDown, small: props.small, darkBg: props.darkBg, fullWidth: props.fullWidth, "$loading": props.loading, tabIndex: props.loading ? -1 : 0, "data-loading": props.loading, className: props.className, "data-testid": dataTestId, "data-no-close": dataNoClose, "data-track-value": dataTrackValue, "aria-label": ariaLabel }, props.dataAttributes, (!props.href && {
117
+ return (React__default.createElement(Element, Object.assign({ id: props.id, as: props.href ? 'a' : undefined, type: props.href ? undefined : type, href: props.href, target: props.href ? props.target : undefined, rel: props.target === '_blank' ? 'noopener noreferrer' : undefined, onClick: props.onClick, onMouseDown: props.onMouseDown, small: props.small, darkBg: props.darkBg, fullWidth: props.fullWidth, "$loading": props.loading, tabIndex: props.loading ? -1 : 0, "data-loading": props.loading, className: props.className, "data-testid": dataTestId, "data-no-close": dataNoClose, "data-track-value": dataTrackValue, "aria-label": ariaLabel }, props.dataAttributes, (!props.href && {
118
118
  name: props.name,
119
119
  disabled: props.disabled,
120
120
  })), props.loading ? (React__default.createElement(PixelLoader, { color: props.darkBg ? theme.color.default.white : theme.color.default.plum, label: props.loadingLabel })) : (React__default.createElement("span", { "data-testid": dataTestId && `${dataTestId}-text`, "data-no-close": dataNoClose }, props.children))));
@@ -1,12 +1,11 @@
1
1
  import { __rest } from 'tslib';
2
2
  import { ChevronUp, ChevronRight, ChevronDown, ChevronLeft } from '@dnanpm/icons';
3
- import React__default, { isValidElement } from 'react';
3
+ import React__default, { isValidElement, cloneElement } from 'react';
4
4
  import styled__default from 'styled-components';
5
5
  import theme from '../../themes/theme.js';
6
6
  import { getMultipliedSize } from '../../utils/styledUtils.js';
7
7
  import ButtonPrimary from '../ButtonPrimary/ButtonPrimary.js';
8
8
  import ButtonSecondary from '../ButtonSecondary/ButtonSecondary.js';
9
- import Icon from '../Icon/Icon.js';
10
9
 
11
10
  const iconsMap = {
12
11
  up: ChevronUp,
@@ -64,7 +63,15 @@ const buttonsMap = {
64
63
  const ButtonArrow = (_a) => {
65
64
  var { variant = 'primary', 'data-testid': dataTestId, 'data-track-value': dataTrackValue, 'aria-label': ariaLabel } = _a, props = __rest(_a, ["variant", 'data-testid', 'data-track-value', 'aria-label']);
66
65
  const Element = buttonsMap[variant];
67
- return (React__default.createElement(Element, { id: props.id, href: props.href, name: props.name, type: props.type, onClick: props.onClick, onMouseDown: props.onMouseDown, darkBg: props.darkBg, disabled: props.disabled, className: props.className, "data-testid": dataTestId, "data-track-value": dataTrackValue, dataAttributes: props.dataAttributes, "aria-label": ariaLabel }, props.direction ? (React__default.createElement(Icon, { icon: iconsMap[props.direction], size: "1rem", "aria-hidden": true })) : (isValidElement(props.icon) && props.icon.type === Icon && props.icon)));
66
+ let iconElement = null;
67
+ if (props.direction) {
68
+ const IconComponent = iconsMap[props.direction];
69
+ iconElement = React__default.createElement(IconComponent, { size: "1rem" });
70
+ }
71
+ else if (isValidElement(props.icon)) {
72
+ iconElement = cloneElement(props.icon, { size: '1rem' });
73
+ }
74
+ return (React__default.createElement(Element, { id: props.id, href: props.href, name: props.name, type: props.type, onClick: props.onClick, onMouseDown: props.onMouseDown, darkBg: props.darkBg, disabled: props.disabled, className: props.className, "data-testid": dataTestId, "data-track-value": dataTrackValue, dataAttributes: props.dataAttributes, "aria-label": ariaLabel }, iconElement));
68
75
  };
69
76
 
70
77
  export { ButtonArrow as default };
@@ -176,7 +176,8 @@ const Carousel = (_a) => {
176
176
  };
177
177
  const visibleItems = props.visibleItems || calculatedItems;
178
178
  const slidesWrapperGapSizePx = 20;
179
- const slideScreensCount = Children.count(props.children) - Math.floor(visibleItems) + 1;
179
+ const slidesCount = Children.count(props.children);
180
+ const slideScreensCount = Math.max(1, slidesCount - Math.floor(visibleItems) + 1);
180
181
  const step = getStep((_b = props.swipeStep) !== null && _b !== void 0 ? _b : 1, visibleItems);
181
182
  const currentStepIndex = Math.ceil(currentIndex / step);
182
183
  const totalSwipeSteps = Math.ceil(slideScreensCount / step + ((slideScreensCount - 1) % step !== 0 ? 1 : 0));
@@ -298,6 +299,8 @@ const Carousel = (_a) => {
298
299
  setIsSwiping(false);
299
300
  };
300
301
  const handleSlidesPointerDown = (e) => {
302
+ if (e.button !== 0)
303
+ return;
301
304
  if (slidesWrapperRef.current && scrollbarRef.current && knobRef.current) {
302
305
  data.startX = e.pageX;
303
306
  data.startTime = Date.now();
@@ -0,0 +1,65 @@
1
+ import React from 'react';
2
+ interface Props {
3
+ /**
4
+ * Unique ID for the component
5
+ */
6
+ id?: string;
7
+ /**
8
+ * Allows to pass a custom className
9
+ */
10
+ className?: string;
11
+ /**
12
+ * Allows to pass testid string for testing purposes
13
+ */
14
+ 'data-testid'?: string;
15
+ /**
16
+ * Allows to pass a screen reader label to the component
17
+ */
18
+ ariaLabel?: string;
19
+ /**
20
+ * Allows to set property `aria-label` for `ButtonClose` element
21
+ */
22
+ closeButtonLabel?: string;
23
+ /**
24
+ * Allows to set the source of the badge image
25
+ */
26
+ badgeSrc: string;
27
+ /**
28
+ * Allows to set the title of the modal
29
+ */
30
+ modalTitle?: string;
31
+ /**
32
+ * Allows to set the source of the image in the modal
33
+ */
34
+ modalImageSrc?: string;
35
+ /**
36
+ * Allows to set the label of the modal
37
+ */
38
+ modalLabel?: string;
39
+ /**
40
+ * Allows to set the label of the button that opens the PDF
41
+ */
42
+ pdfButtonLabel?: string;
43
+ /**
44
+ * Allows to set the href of the PDF file
45
+ */
46
+ pdfHref?: string;
47
+ /**
48
+ * Allows to set the label for the error state
49
+ */
50
+ errorLabel?: string;
51
+ /**
52
+ * Allows to set the title for the error state
53
+ */
54
+ errorTitle?: string;
55
+ /**
56
+ * Allows to hide application from assistive screenreaders and other assistive technologies while the modal is open
57
+ *
58
+ * @default '#__next'
59
+ */
60
+ modalAppElement?: string;
61
+ }
62
+ /** @visibleName Energy Label */
63
+ declare const EnergyLabel: ({ "data-testid": dataTestId, modalAppElement, ...props }: Props) => React.JSX.Element | null;
64
+ /** @component */
65
+ export default EnergyLabel;
@@ -0,0 +1,90 @@
1
+ import { __rest } from 'tslib';
2
+ import React__default, { useState } from 'react';
3
+ import { Open } from '@dnanpm/icons';
4
+ import Modal from '../Modal/Modal.js';
5
+ import styled from '../../themes/styled.js';
6
+ import { media, getMultipliedSize } from '../../utils/styledUtils.js';
7
+ import Button from '../Button/Button.js';
8
+ import theme from '../../themes/theme.js';
9
+
10
+ const ERROR_IMAGE = 'https://res.cloudinary.com/dnaoyj/image/upload/v1731073400/Assets/KLT/Enriched%20icons/enriched-error-state.png';
11
+ const ButtonElement = styled.button `
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ background-color: transparent;
16
+ border: 0;
17
+ padding: 0;
18
+ cursor: pointer;
19
+ `;
20
+ const StyledModal = styled(Modal) `
21
+ > div > div {
22
+ max-height: 100%;
23
+ height: 100%;
24
+ width: 100%;
25
+ margin: 0;
26
+
27
+ ${media.xs `
28
+ width: 23.438rem;
29
+ height: 50rem;
30
+ border-radius: ${theme.radius.default};
31
+ `};
32
+
33
+ > div {
34
+ border-radius: 0;
35
+ }
36
+ }
37
+ `;
38
+ const ModalLabel = styled.p `
39
+ font-size: ${theme.fontSize.s};
40
+ margin: 0;
41
+ `;
42
+ const ButtonContent = styled.span `
43
+ display: inline-flex;
44
+ align-items: center;
45
+ gap: ${getMultipliedSize(theme.base.baseWidth, 0.5)};
46
+ `;
47
+ const ErrorContainer = styled.div `
48
+ display: flex;
49
+ padding-top: ${getMultipliedSize(theme.base.baseHeight, 2)};
50
+ flex-direction: column;
51
+ text-align: center;
52
+ align-items: center;
53
+ flex-direction: column;
54
+ gap: ${getMultipliedSize(theme.base.baseHeight, 3)};
55
+ `;
56
+ const Image = styled.img `
57
+ width: ${p => p.imgWidth};
58
+ `;
59
+ /** @visibleName Energy Label */
60
+ const EnergyLabel = (_a) => {
61
+ var { 'data-testid': dataTestId, modalAppElement = '#__next' } = _a, props = __rest(_a, ['data-testid', "modalAppElement"]);
62
+ const [isOpen, setIsOpen] = useState(false);
63
+ const [hasError, setHasError] = useState(false);
64
+ const [imgError, setImgError] = useState(false);
65
+ const handleOpen = () => {
66
+ setIsOpen(true);
67
+ };
68
+ const handleClose = () => {
69
+ setIsOpen(false);
70
+ };
71
+ const renderFooter = () => (React__default.createElement(Button, { fullWidth: true, href: props.pdfHref, target: "_blank" },
72
+ React__default.createElement(ButtonContent, null,
73
+ props.pdfButtonLabel,
74
+ " ",
75
+ React__default.createElement(Open, null))));
76
+ if (imgError) {
77
+ return null;
78
+ }
79
+ return (React__default.createElement(React__default.Fragment, null,
80
+ React__default.createElement(ButtonElement, { onClick: handleOpen, "aria-label": props.ariaLabel, "data-testid": dataTestId, id: props.id, className: props.className },
81
+ React__default.createElement(Image, { src: props.badgeSrc, alt: "", "aria-hidden": true, imgWidth: "55px", onError: () => setImgError(true) })),
82
+ React__default.createElement(StyledModal, { appElement: modalAppElement, isOpen: isOpen, onRequestClose: handleClose, title: props.modalTitle, closeLabel: props.closeButtonLabel, footer: renderFooter() }, hasError || !props.modalImageSrc ? (React__default.createElement(ErrorContainer, null,
83
+ React__default.createElement("h3", null, props.errorTitle),
84
+ React__default.createElement(Image, { src: ERROR_IMAGE, alt: "", imgWidth: "160px" }),
85
+ React__default.createElement("p", null, props.errorLabel))) : (React__default.createElement(React__default.Fragment, null,
86
+ React__default.createElement(Image, { src: props.modalImageSrc, alt: "", imgWidth: "100%", onError: () => setHasError(true) }),
87
+ React__default.createElement(ModalLabel, null, props.modalLabel))))));
88
+ };
89
+
90
+ export { EnergyLabel as default };
@@ -24,7 +24,7 @@ const NotificationWrapper = styled__default.div `
24
24
  ${sharedStyles}
25
25
  border-color: ${({ $type }) => theme.color.notification[$type]};
26
26
  `;
27
- const StaticWrapper = styled__default.section `
27
+ const StaticWrapper = styled__default.div `
28
28
  ${sharedStyles}
29
29
  border-color: ${({ $type }) => theme.color.notification[$type]};
30
30
  `;
@@ -34,7 +34,7 @@ const IconWrapper = styled__default.div `
34
34
  padding: 0.5rem;
35
35
  background-color: ${({ $type }) => theme.color.notification[$type]};
36
36
  `;
37
- const ContentWrapper = styled__default.span `
37
+ const ContentWrapper = styled__default.div `
38
38
  margin: auto 0;
39
39
  padding: 0.5rem 0;
40
40
  width: 100%;
@@ -59,7 +59,7 @@ const Notification = ({ type = 'info', 'data-testid': dataTestId, isStatic = fal
59
59
  React__default.createElement(ContentWrapper, null, children),
60
60
  closeButton && (React__default.createElement(ButtonCloseStyled, { onClick: onClickCloseButton, "aria-label": closeButtonLabel },
61
61
  React__default.createElement(Icon, { icon: Close, color: "currentColor", "aria-hidden": true })))));
62
- return isStatic ? (React__default.createElement(StaticWrapper, { "$type": type, className: className, "data-testid": dataTestId, "aria-label": ariaLabel }, renderContent())) : (React__default.createElement(NotificationWrapper, { "$type": type, className: className, "data-testid": dataTestId, role: "alert" }, renderContent()));
62
+ return isStatic ? (React__default.createElement(StaticWrapper, { "$type": type, className: className, "data-testid": dataTestId, "aria-label": ariaLabel, role: "region" }, renderContent())) : (React__default.createElement(NotificationWrapper, { "$type": type, className: className, "data-testid": dataTestId, role: "alert" }, renderContent()));
63
63
  };
64
64
 
65
65
  export { Notification as default };
@@ -1,6 +1,8 @@
1
- import type { MouseEvent, ReactNode } from 'react';
1
+ import type { MouseEvent, ReactNode, KeyboardEvent } from 'react';
2
2
  import React from 'react';
3
- interface Props {
3
+ type AriaLandmarkRoles = 'none' | 'main' | 'navigation' | 'banner' | 'complementary' | 'contentinfo' | 'form' | 'region' | 'search';
4
+ type Html5Tag = 'main' | 'div' | 'section' | 'article' | 'aside' | 'nav' | 'header' | 'footer';
5
+ interface BaseProps {
4
6
  /**
5
7
  * Unique ID for the component
6
8
  */
@@ -13,6 +15,11 @@ interface Props {
13
15
  * On overlay element mouse click
14
16
  */
15
17
  onClick?: (e: MouseEvent<HTMLElement>) => void;
18
+ /**
19
+ * Callback function for keydown events on the overlay element to be used with prop `onClick`.
20
+ * This can be used to handle keyboard interactions, such as closing the overlay with the Escape key.
21
+ */
22
+ onKeyDown?: (e: KeyboardEvent<HTMLElement>) => void;
16
23
  /**
17
24
  * Allows to set DOM node to which Overlay component will be appended. The node must already exist.
18
25
  * Use `false` to render in place without use of `createPortal`
@@ -42,8 +49,62 @@ interface Props {
42
49
  * Allows to pass a custom className
43
50
  */
44
51
  className?: string;
52
+ /**
53
+ * Specifies the HTML5 tag to be used for the content wrapper.
54
+ * It is recommended to use native semantic HTML elements whenever possible.
55
+ * If a suitable semantic element is not available, a `<div>` with an appropriate ARIA role should be used (role from `role`).
56
+ * ARIA roles from https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles
57
+ *
58
+ * @param {Html5Tag} main Uses `<main>` tag for the content wrapper.
59
+ * @param {Html5Tag} div Uses `<div>` tag for the content wrapper.
60
+ * @param {Html5Tag} section Uses `<section>` tag for the content wrapper.
61
+ * @param {Html5Tag} article Uses `<article>` tag for the content wrapper.
62
+ * @param {Html5Tag} aside Uses `<aside>` tag for the content wrapper.
63
+ * @param {Html5Tag} nav Uses `<nav>` tag for the content wrapper.
64
+ * @param {Html5Tag} header Uses `<header>` tag for the content wrapper.
65
+ * @param {Html5Tag} footer Uses `<footer>` tag for the content wrapper.
66
+ *
67
+ * @default 'section'
68
+ */
69
+ as?: Html5Tag;
70
+ /**
71
+ * Specifies the ARIA landmark role for the content wrapper and is used when `as` is a div.
72
+ * It is always recommended to use native semantic HTML elements from `as` whenever possible.
73
+ * ARIA roles from https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles
74
+ *
75
+ * @param {AriaLandmarkRoles} main Represents the main content with `role="main"` (acting as <main>).
76
+ * @param {AriaLandmarkRoles} navigation Represents navigation links with `role="navigation"` (acting as <nav>).
77
+ * @param {AriaLandmarkRoles} banner Represents introductory content with `role="banner"` (acting as <header>).
78
+ * @param {AriaLandmarkRoles} complementary Represents related content with `role="complementary"` (acting as <aside>).
79
+ * @param {AriaLandmarkRoles} contentinfo Represents footer information with `role="contentinfo"` (acting as <footer>).
80
+ * @param {AriaLandmarkRoles} form Indicates an interactive form with `role="form"` (acting as <form>).
81
+ * @param {AriaLandmarkRoles} region Represents an important section with `role="region"` (acting as <section>).
82
+ * @param {AriaLandmarkRoles} search Represents a search area with `role="search"` (acting as <search>).
83
+ */
84
+ role?: AriaLandmarkRoles;
85
+ /**
86
+ * If a role from `role` is used more than once on a page, the aria-label attribute should also be used in order to distinguish between the two regions.
87
+ */
88
+ ariaLabel?: string;
89
+ /**
90
+ * Ensures that overlays maintain proper focus management and accessibility.
91
+ * If `isFocusTrapped` is true, an appropriate ARIA role should be used (role from `focusableTrappedWidgetRole`).
92
+ * If `isFocusTrapped` is false, the tabbable element will not have an ARIA role.
93
+ *
94
+ * @default true
95
+ */
96
+ isFocusTrapped?: boolean;
45
97
  }
98
+ type DivProps = BaseProps & {
99
+ as?: 'div';
100
+ role: AriaLandmarkRoles;
101
+ };
102
+ type NonDivProps = BaseProps & {
103
+ as?: Exclude<Html5Tag, 'div'>;
104
+ role?: undefined;
105
+ };
106
+ type Props = DivProps | NonDivProps;
46
107
  /** @visibleName Overlay */
47
- declare const Overlay: ({ portalContainer: portalContainerSelector, appElement: appElementSelector, preventBodyScroll, "data-testid": dataTestId, ...props }: Props) => React.JSX.Element;
108
+ declare const Overlay: ({ portalContainer: portalContainerSelector, appElement: appElementSelector, preventBodyScroll, "data-testid": dataTestId, as, role, isFocusTrapped, ...props }: Props) => React.JSX.Element;
48
109
  /** @component */
49
110
  export default Overlay;
@@ -25,28 +25,31 @@ const Element = styled.div `
25
25
  `;
26
26
  /** @visibleName Overlay */
27
27
  const Overlay = (_a) => {
28
- var { portalContainer: portalContainerSelector = 'body', appElement: appElementSelector = '#__next', preventBodyScroll = true, 'data-testid': dataTestId } = _a, props = __rest(_a, ["portalContainer", "appElement", "preventBodyScroll", 'data-testid']);
28
+ var { portalContainer: portalContainerSelector = 'body', appElement: appElementSelector = '#__next', preventBodyScroll = true, 'data-testid': dataTestId, as = 'section', role, isFocusTrapped = true } = _a, props = __rest(_a, ["portalContainer", "appElement", "preventBodyScroll", 'data-testid', "as", "role", "isFocusTrapped"]);
29
29
  const contentRef = useRef(null);
30
30
  const portalContainer = portalContainerSelector
31
31
  ? document.querySelector(portalContainerSelector)
32
32
  : null;
33
33
  const appElement = document.querySelector(appElementSelector);
34
- const overlayElement = (React__default.createElement(Element, { id: props.id, onClick: props.onClick, className: props.className, "data-testid": dataTestId },
35
- React__default.createElement("div", { ref: contentRef, tabIndex: -1 }, props.children)));
34
+ const overlayElement = (React__default.createElement(Element, { id: props.id, onClick: props.onClick, className: props.className, "data-testid": dataTestId, onKeyDown: props.onKeyDown, "aria-label": props.ariaLabel, as: as, role: as === 'div' ? role : undefined },
35
+ React__default.createElement("div", { ref: contentRef, tabIndex: -1, "data-testid": dataTestId ? `${dataTestId}-content` : '' }, props.children)));
36
36
  useEffect(() => {
37
37
  if (preventBodyScroll) {
38
38
  document.body.style.setProperty('overflow', 'hidden');
39
+ document.body.style.setProperty('position', 'relative');
40
+ document.body.style.setProperty('top', '0');
41
+ document.body.style.setProperty('left', '0');
39
42
  return () => {
40
43
  document.body.style.removeProperty('overflow');
41
44
  };
42
45
  }
43
46
  return undefined;
44
- });
47
+ }, [preventBodyScroll]);
45
48
  useEffect(() => {
46
49
  var _a;
47
50
  if (appElement) {
48
51
  const focusTrapStart = document.createElement('div');
49
- focusTrapStart.setAttribute('tabindex', '0');
52
+ focusTrapStart.setAttribute('tabindex', isFocusTrapped ? '0' : '-1');
50
53
  focusTrapStart.addEventListener('focus', () => {
51
54
  var _a;
52
55
  (_a = contentRef === null || contentRef === void 0 ? void 0 : contentRef.current) === null || _a === void 0 ? void 0 : _a.focus();
@@ -59,15 +62,15 @@ const Overlay = (_a) => {
59
62
  (_a = contentRef === null || contentRef === void 0 ? void 0 : contentRef.current) === null || _a === void 0 ? void 0 : _a.focus();
60
63
  document.body.prepend(focusTrapStart);
61
64
  document.body.append(focusTrapEnd);
62
- appElement.setAttribute('aria-hidden', 'true');
65
+ appElement.setAttribute('inert', 'true');
63
66
  return () => {
64
67
  document.body.removeChild(focusTrapStart);
65
68
  document.body.removeChild(focusTrapEnd);
66
- appElement.removeAttribute('aria-hidden');
69
+ appElement.removeAttribute('inert');
67
70
  };
68
71
  }
69
72
  return undefined;
70
- });
73
+ }, [appElement, contentRef, isFocusTrapped]);
71
74
  return portalContainerSelector === false
72
75
  ? overlayElement
73
76
  : createPortal(overlayElement, portalContainer !== null && portalContainer !== void 0 ? portalContainer : document.body);
@@ -41,10 +41,6 @@ interface Props {
41
41
  * Allows to pass a custom className
42
42
  */
43
43
  className?: string;
44
- /**
45
- * Allows to pass a custom aria-label for mobile dropdown button
46
- */
47
- dropdownButtonAriaLabel?: string;
48
44
  /**
49
45
  * Allows to pass testid string for testing purposes
50
46
  */
@@ -268,14 +268,20 @@ const PriorityNavigation = (_a) => {
268
268
  });
269
269
  useEffect(() => {
270
270
  if (!isMobile) {
271
- dispatch({
272
- type: 'resetNavigationState',
273
- payload: {
274
- navigationItems: props.children,
275
- },
271
+ requestAnimationFrame(() => {
272
+ dispatch({
273
+ type: 'resetNavigationState',
274
+ payload: {
275
+ navigationItems: props.children,
276
+ },
277
+ });
278
+ setTimeout(() => {
279
+ checkHorizontalOverflow();
280
+ }, 0);
276
281
  });
277
282
  }
278
283
  setIsMobileNavigationOpen(false);
284
+ // eslint-disable-next-line react-hooks/exhaustive-deps
279
285
  }, [isMobile, props.children]);
280
286
  useLayoutEffect(() => {
281
287
  if (!isMobile) {
@@ -291,31 +297,18 @@ const PriorityNavigation = (_a) => {
291
297
  checkHorizontalOverflow,
292
298
  ]);
293
299
  const handleNavigationListKeyDown = useCallback((e) => {
294
- var _a;
295
- if (isMobile && isMobileNavigationOpen) {
300
+ var _a, _b;
301
+ if (isMobile && isMobileNavigationOpen && e.key === 'Tab') {
296
302
  const focusableElements = (_a = navigationListRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll('a, button, input, [tabindex]:not([tabindex="-1"])');
297
303
  const lastElement = focusableElements === null || focusableElements === void 0 ? void 0 : focusableElements[focusableElements.length - 1];
298
- if (document.activeElement === lastElement) {
304
+ const goingForward = !e.shiftKey;
305
+ if (goingForward && document.activeElement === lastElement) {
299
306
  e.preventDefault();
300
307
  setIsMobileNavigationOpen(false);
308
+ (_b = dropdownButtonRef.current) === null || _b === void 0 ? void 0 : _b.focus();
301
309
  }
302
310
  }
303
311
  }, [isMobile, isMobileNavigationOpen]);
304
- const handleNavigationListFocus = useCallback(() => {
305
- if (isMobile && isMobileNavigationOpen) {
306
- setTimeout(() => {
307
- var _a, _b;
308
- const focusableElements = (_a = navigationListRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll('a, button, input, [tabindex]:not([tabindex="-1"])');
309
- if (!focusableElements || focusableElements.length === 0)
310
- return;
311
- const lastElement = focusableElements[focusableElements.length - 1];
312
- if (document.activeElement === lastElement) {
313
- setIsMobileNavigationOpen(false);
314
- (_b = dropdownButtonRef.current) === null || _b === void 0 ? void 0 : _b.focus();
315
- }
316
- }, 0);
317
- }
318
- }, [isMobile, isMobileNavigationOpen]);
319
312
  const handleItemClick = (e) => {
320
313
  setIsMobileNavigationOpen(false);
321
314
  if (props.onClick) {
@@ -331,7 +324,7 @@ const PriorityNavigation = (_a) => {
331
324
  props.categoryLabel && React__default.createElement(Category, null, props.categoryLabel),
332
325
  selectedItem),
333
326
  React__default.createElement(Icon, { icon: isMobileNavigationOpen ? OvalChevronUp : OvalChevronDown, size: "2.5rem" }))),
334
- React__default.createElement(NavigationList, { ref: navigationListRef, isMobileNavigationOpen: isMobileNavigationOpen, onKeyDown: handleNavigationListKeyDown, onFocus: handleNavigationListFocus }, Children.map([...state.navigationItems, ...(isMobile ? state.dropdownItems : [])], (navigationItem, index) => {
327
+ React__default.createElement(NavigationList, { ref: navigationListRef, isMobileNavigationOpen: isMobileNavigationOpen, onKeyDown: handleNavigationListKeyDown }, Children.map([...state.navigationItems, ...(isMobile ? state.dropdownItems : [])], (navigationItem, index) => {
335
328
  if (isValidElement(navigationItem) &&
336
329
  navigationItem.type === PriorityNavigationItem) {
337
330
  return (React__default.createElement(PriorityNavigationItem, { id: navigationItem.props.id, key: navigationItem.key, onClick: handleItemClick, onKeyDown: navigationItem.props.onKeyDown || props.onKeyDown, isActive: navigationItem.props.isActive, className: navigationItem.props.className, "data-testid": navigationItem.props['data-testid'], ref: instance => {
@@ -353,8 +346,9 @@ const PriorityNavigation = (_a) => {
353
346
  !isMobile && Boolean(state.dropdownItems.length) && (React__default.createElement(React__default.Fragment, null,
354
347
  React__default.createElement("div", null,
355
348
  React__default.createElement(ButtonIcon, { ref: dropdownButtonRef, ariaLabel: openMoreSubpagesAriaLabel, ariaExpanded: isDropdownListOpen, onClick: toggleDropdown, icon: isDropdownListOpen ? ChevronUp : ChevronDown, isReversed: true, "data-testid": "dropdown-button" }, dropdownButtonLabel)),
356
- React__default.createElement(DropdownList, { isDropdownListOpen: isDropdownListOpen }, state.dropdownItems.map(dropdownItem => isValidElement(dropdownItem) &&
357
- dropdownItem.type === PriorityNavigationItem && (React__default.createElement(PriorityNavigationItem, { id: dropdownItem.props.id, key: dropdownItem.key, onClick: dropdownItem.props.onClick || props.onClick, onKeyDown: dropdownItem.props.onKeyDown || props.onKeyDown, isActive: dropdownItem.props.isActive, className: dropdownItem.props.className, "data-testid": dropdownItem.props['data-testid'] }, dropdownItem.props.children))))))))));
349
+ isDropdownListOpen && (React__default.createElement(DropdownList, { isDropdownListOpen: isDropdownListOpen }, state.dropdownItems.map(dropdownItem => isValidElement(dropdownItem) &&
350
+ dropdownItem.type === PriorityNavigationItem && (React__default.createElement(PriorityNavigationItem, { id: dropdownItem.props.id, key: dropdownItem.key, onClick: dropdownItem.props.onClick || props.onClick, onKeyDown: dropdownItem.props.onKeyDown ||
351
+ props.onKeyDown, isActive: dropdownItem.props.isActive, className: dropdownItem.props.className, "data-testid": dropdownItem.props['data-testid'] }, dropdownItem.props.children)))))))))));
358
352
  };
359
353
 
360
354
  export { PriorityNavigation as default };
@@ -18,6 +18,7 @@ export { default as Divider } from './Divider/Divider';
18
18
  export { default as DnaLogo } from './DnaLogo/DnaLogo';
19
19
  export { default as Drawer } from './Drawer/Drawer';
20
20
  export { default as EmptyState } from './EmptyState/EmptyState';
21
+ export { default as EnergyLabel } from './EnergyLabel/EnergyLabel';
21
22
  export { default as Expander } from './Expander/Expander';
22
23
  export { default as Floater } from './Floater/Floater';
23
24
  export { default as Footer } from './Footer/Footer';
package/build/es/index.js CHANGED
@@ -18,6 +18,7 @@ export { default as Divider } from './components/Divider/Divider.js';
18
18
  export { default as DnaLogo } from './components/DnaLogo/DnaLogo.js';
19
19
  export { default as Drawer } from './components/Drawer/Drawer.js';
20
20
  export { default as EmptyState } from './components/EmptyState/EmptyState.js';
21
+ export { default as EnergyLabel } from './components/EnergyLabel/EnergyLabel.js';
21
22
  export { default as Expander } from './components/Expander/Expander.js';
22
23
  export { default as Floater } from './components/Floater/Floater.js';
23
24
  export { default as Footer } from './components/Footer/Footer.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dnanpm/styleguide",
3
3
  "sideEffects": false,
4
- "version": "v3.9.14",
4
+ "version": "v3.10.0",
5
5
  "main": "build/cjs/index.js",
6
6
  "module": "build/es/index.js",
7
7
  "jsnext:main": "build/es/index.js",
@@ -70,7 +70,7 @@
70
70
  "eslint-config-airbnb": "^19.0.4",
71
71
  "eslint-config-airbnb-typescript": "^17.1.0",
72
72
  "eslint-config-prettier": "^8.10.0",
73
- "eslint-plugin-import": "2.31.0",
73
+ "eslint-plugin-import": "2.32.0",
74
74
  "eslint-plugin-jsdoc": "^39.9.1",
75
75
  "eslint-plugin-jsx-a11y": "^6.10.2",
76
76
  "eslint-plugin-prefer-arrow": "^1.2.3",