@canonical/react-components 3.5.1 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Modal/Modal.d.ts +6 -2
- package/dist/components/Modal/Modal.js +14 -17
- package/dist/components/Modal/Modal.stories.d.ts +1 -0
- package/dist/components/Modal/Modal.stories.js +33 -1
- package/dist/esm/components/Modal/Modal.d.ts +6 -2
- package/dist/esm/components/Modal/Modal.js +15 -18
- package/dist/esm/components/Modal/Modal.stories.d.ts +1 -0
- package/dist/esm/components/Modal/Modal.stories.js +33 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import type { HTMLProps, ReactNode } from "react";
|
|
2
|
+
import type { HTMLProps, ReactNode, RefObject } from "react";
|
|
3
3
|
import { ClassName, PropsWithSpread } from "../../types";
|
|
4
4
|
export type Props = PropsWithSpread<{
|
|
5
5
|
/**
|
|
@@ -18,6 +18,10 @@ export type Props = PropsWithSpread<{
|
|
|
18
18
|
* Function to handle closing the modal.
|
|
19
19
|
*/
|
|
20
20
|
close?: () => void | null;
|
|
21
|
+
/**
|
|
22
|
+
* The element that will be focused upon opening the modal.
|
|
23
|
+
*/
|
|
24
|
+
focusRef?: RefObject<HTMLElement | null>;
|
|
21
25
|
/**
|
|
22
26
|
* The title of the modal.
|
|
23
27
|
*/
|
|
@@ -36,5 +40,5 @@ export type Props = PropsWithSpread<{
|
|
|
36
40
|
*
|
|
37
41
|
* The modal component can be used to overlay an area of the screen which can contain a prompt, dialog or interaction.
|
|
38
42
|
*/
|
|
39
|
-
export declare const Modal: ({ buttonRow, children, className, close, title, shouldPropagateClickEvent, closeOnOutsideClick, ...wrapperProps }: Props) => React.JSX.Element;
|
|
43
|
+
export declare const Modal: ({ buttonRow, children, className, close, focusRef, title, shouldPropagateClickEvent, closeOnOutsideClick, ...wrapperProps }: Props) => React.JSX.Element;
|
|
40
44
|
export default Modal;
|
|
@@ -21,6 +21,7 @@ const Modal = _ref => {
|
|
|
21
21
|
children,
|
|
22
22
|
className,
|
|
23
23
|
close,
|
|
24
|
+
focusRef,
|
|
24
25
|
title,
|
|
25
26
|
shouldPropagateClickEvent = false,
|
|
26
27
|
closeOnOutsideClick = true,
|
|
@@ -33,6 +34,7 @@ const Modal = _ref => {
|
|
|
33
34
|
const titleId = (0, _react.useId)();
|
|
34
35
|
const shouldClose = (0, _react.useRef)(false);
|
|
35
36
|
const modalRef = (0, _react.useRef)(null);
|
|
37
|
+
const closeButtonRef = (0, _react.useRef)(null);
|
|
36
38
|
const focusableModalElements = (0, _react.useRef)(null);
|
|
37
39
|
const handleTabKey = event => {
|
|
38
40
|
if (focusableModalElements.current.length > 0) {
|
|
@@ -60,24 +62,18 @@ const Modal = _ref => {
|
|
|
60
62
|
close();
|
|
61
63
|
}
|
|
62
64
|
};
|
|
63
|
-
const keyListenersMap = new Map([["Escape", handleEscKey], ["Tab", handleTabKey]]);
|
|
64
65
|
(0, _react.useEffect)(() => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
let focusIndex = 0;
|
|
72
|
-
// when the close button is rendered, focus on the 2nd content element and not the close btn.
|
|
73
|
-
if (hasCloseButton && focusableModalElements.current.length > 1) {
|
|
74
|
-
focusIndex = 1;
|
|
66
|
+
if (focusRef !== null && focusRef !== void 0 && focusRef.current) {
|
|
67
|
+
focusRef.current.focus();
|
|
68
|
+
} else if (closeButtonRef.current) {
|
|
69
|
+
closeButtonRef.current.focus();
|
|
70
|
+
} else {
|
|
71
|
+
modalRef.current.focus();
|
|
75
72
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
});
|
|
79
|
-
}, [hasCloseButton]);
|
|
73
|
+
focusableModalElements.current = modalRef.current.querySelectorAll(focusableElementSelectors);
|
|
74
|
+
}, [focusRef]);
|
|
80
75
|
(0, _react.useEffect)(() => {
|
|
76
|
+
const keyListenersMap = new Map([["Escape", handleEscKey], ["Tab", handleTabKey]]);
|
|
81
77
|
const keyDown = event => {
|
|
82
78
|
const listener = keyListenersMap.get(event.code);
|
|
83
79
|
return listener && listener(event);
|
|
@@ -130,11 +126,12 @@ const Modal = _ref => {
|
|
|
130
126
|
}, /*#__PURE__*/_react.default.createElement("h2", {
|
|
131
127
|
className: "p-modal__title",
|
|
132
128
|
id: titleId
|
|
133
|
-
}, title),
|
|
129
|
+
}, title), close && /*#__PURE__*/_react.default.createElement("button", {
|
|
134
130
|
type: "button",
|
|
135
131
|
className: "p-modal__close",
|
|
136
132
|
"aria-label": "Close active modal",
|
|
137
|
-
onClick: handleClose
|
|
133
|
+
onClick: handleClose,
|
|
134
|
+
ref: closeButtonRef
|
|
138
135
|
}, "Close")), /*#__PURE__*/_react.default.createElement("div", {
|
|
139
136
|
id: descriptionId
|
|
140
137
|
}, children), !!buttonRow && /*#__PURE__*/_react.default.createElement("footer", {
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.default = exports.Default = void 0;
|
|
6
|
+
exports.default = exports.Focus = exports.Default = void 0;
|
|
7
7
|
var _react = _interopRequireWildcard(require("react"));
|
|
8
|
+
var _Button = _interopRequireDefault(require("../Button"));
|
|
8
9
|
var _Modal = _interopRequireDefault(require("./Modal"));
|
|
9
10
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
10
11
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
@@ -61,4 +62,35 @@ const Default = exports.Default = {
|
|
|
61
62
|
}, /*#__PURE__*/_react.default.createElement("p", null, "This will permanently delete the user \"Simon\".", /*#__PURE__*/_react.default.createElement("br", null), "You cannot undo this action.")) : null);
|
|
62
63
|
},
|
|
63
64
|
name: "Default"
|
|
65
|
+
};
|
|
66
|
+
const Focus = exports.Focus = {
|
|
67
|
+
render: _ref2 => {
|
|
68
|
+
let {
|
|
69
|
+
closeOnOutsideClick
|
|
70
|
+
} = _ref2;
|
|
71
|
+
/* eslint-disable react-hooks/rules-of-hooks */
|
|
72
|
+
const [modalOpen, setModalOpen] = (0, _react.useState)(true);
|
|
73
|
+
const buttonRef = (0, _react.useRef)(null);
|
|
74
|
+
/* eslint-enable react-hooks/rules-of-hooks */
|
|
75
|
+
|
|
76
|
+
const closeHandler = () => setModalOpen(false);
|
|
77
|
+
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("button", {
|
|
78
|
+
onClick: () => setModalOpen(true)
|
|
79
|
+
}, "Open modal"), modalOpen ? /*#__PURE__*/_react.default.createElement(_Modal.default, {
|
|
80
|
+
close: closeHandler,
|
|
81
|
+
title: "Confirm delete",
|
|
82
|
+
closeOnOutsideClick: closeOnOutsideClick,
|
|
83
|
+
buttonRow: /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("button", {
|
|
84
|
+
className: "u-no-margin--bottom",
|
|
85
|
+
onClick: closeHandler
|
|
86
|
+
}, "Cancel"), /*#__PURE__*/_react.default.createElement("button", {
|
|
87
|
+
className: "p-button--negative u-no-margin--bottom"
|
|
88
|
+
}, "Delete")),
|
|
89
|
+
focusRef: buttonRef
|
|
90
|
+
}, /*#__PURE__*/_react.default.createElement("p", null, "This will permanently delete the user \"Simon\".", /*#__PURE__*/_react.default.createElement("br", null), "You cannot undo this action."), /*#__PURE__*/_react.default.createElement("p", null, /*#__PURE__*/_react.default.createElement(_Button.default, {
|
|
91
|
+
appearance: "link",
|
|
92
|
+
ref: buttonRef
|
|
93
|
+
}, "More information"))) : null);
|
|
94
|
+
},
|
|
95
|
+
name: "Focus"
|
|
64
96
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import type { HTMLProps, ReactNode } from "react";
|
|
2
|
+
import type { HTMLProps, ReactNode, RefObject } from "react";
|
|
3
3
|
import { ClassName, PropsWithSpread } from "../../types";
|
|
4
4
|
export type Props = PropsWithSpread<{
|
|
5
5
|
/**
|
|
@@ -18,6 +18,10 @@ export type Props = PropsWithSpread<{
|
|
|
18
18
|
* Function to handle closing the modal.
|
|
19
19
|
*/
|
|
20
20
|
close?: () => void | null;
|
|
21
|
+
/**
|
|
22
|
+
* The element that will be focused upon opening the modal.
|
|
23
|
+
*/
|
|
24
|
+
focusRef?: RefObject<HTMLElement | null>;
|
|
21
25
|
/**
|
|
22
26
|
* The title of the modal.
|
|
23
27
|
*/
|
|
@@ -36,5 +40,5 @@ export type Props = PropsWithSpread<{
|
|
|
36
40
|
*
|
|
37
41
|
* The modal component can be used to overlay an area of the screen which can contain a prompt, dialog or interaction.
|
|
38
42
|
*/
|
|
39
|
-
export declare const Modal: ({ buttonRow, children, className, close, title, shouldPropagateClickEvent, closeOnOutsideClick, ...wrapperProps }: Props) => React.JSX.Element;
|
|
43
|
+
export declare const Modal: ({ buttonRow, children, className, close, focusRef, title, shouldPropagateClickEvent, closeOnOutsideClick, ...wrapperProps }: Props) => React.JSX.Element;
|
|
40
44
|
export default Modal;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var _excluded = ["buttonRow", "children", "className", "close", "title", "shouldPropagateClickEvent", "closeOnOutsideClick"];
|
|
1
|
+
var _excluded = ["buttonRow", "children", "className", "close", "focusRef", "title", "shouldPropagateClickEvent", "closeOnOutsideClick"];
|
|
2
2
|
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
3
3
|
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
|
|
4
4
|
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
|
|
@@ -15,6 +15,7 @@ export var Modal = _ref => {
|
|
|
15
15
|
children,
|
|
16
16
|
className,
|
|
17
17
|
close,
|
|
18
|
+
focusRef,
|
|
18
19
|
title,
|
|
19
20
|
shouldPropagateClickEvent = false,
|
|
20
21
|
closeOnOutsideClick = true
|
|
@@ -27,6 +28,7 @@ export var Modal = _ref => {
|
|
|
27
28
|
var titleId = useId();
|
|
28
29
|
var shouldClose = useRef(false);
|
|
29
30
|
var modalRef = useRef(null);
|
|
31
|
+
var closeButtonRef = useRef(null);
|
|
30
32
|
var focusableModalElements = useRef(null);
|
|
31
33
|
var handleTabKey = event => {
|
|
32
34
|
if (focusableModalElements.current.length > 0) {
|
|
@@ -54,24 +56,18 @@ export var Modal = _ref => {
|
|
|
54
56
|
close();
|
|
55
57
|
}
|
|
56
58
|
};
|
|
57
|
-
var keyListenersMap = new Map([["Escape", handleEscKey], ["Tab", handleTabKey]]);
|
|
58
59
|
useEffect(() => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
var focusIndex = 0;
|
|
66
|
-
// when the close button is rendered, focus on the 2nd content element and not the close btn.
|
|
67
|
-
if (hasCloseButton && focusableModalElements.current.length > 1) {
|
|
68
|
-
focusIndex = 1;
|
|
60
|
+
if (focusRef !== null && focusRef !== void 0 && focusRef.current) {
|
|
61
|
+
focusRef.current.focus();
|
|
62
|
+
} else if (closeButtonRef.current) {
|
|
63
|
+
closeButtonRef.current.focus();
|
|
64
|
+
} else {
|
|
65
|
+
modalRef.current.focus();
|
|
69
66
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
});
|
|
73
|
-
}, [hasCloseButton]);
|
|
67
|
+
focusableModalElements.current = modalRef.current.querySelectorAll(focusableElementSelectors);
|
|
68
|
+
}, [focusRef]);
|
|
74
69
|
useEffect(() => {
|
|
70
|
+
var keyListenersMap = new Map([["Escape", handleEscKey], ["Tab", handleTabKey]]);
|
|
75
71
|
var keyDown = event => {
|
|
76
72
|
var listener = keyListenersMap.get(event.code);
|
|
77
73
|
return listener && listener(event);
|
|
@@ -124,11 +120,12 @@ export var Modal = _ref => {
|
|
|
124
120
|
}, /*#__PURE__*/React.createElement("h2", {
|
|
125
121
|
className: "p-modal__title",
|
|
126
122
|
id: titleId
|
|
127
|
-
}, title),
|
|
123
|
+
}, title), close && /*#__PURE__*/React.createElement("button", {
|
|
128
124
|
type: "button",
|
|
129
125
|
className: "p-modal__close",
|
|
130
126
|
"aria-label": "Close active modal",
|
|
131
|
-
onClick: handleClose
|
|
127
|
+
onClick: handleClose,
|
|
128
|
+
ref: closeButtonRef
|
|
132
129
|
}, "Close")), /*#__PURE__*/React.createElement("div", {
|
|
133
130
|
id: descriptionId
|
|
134
131
|
}, children), !!buttonRow && /*#__PURE__*/React.createElement("footer", {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
1
|
+
import { useRef, useState } from "react";
|
|
2
2
|
import React from "react";
|
|
3
|
+
import Button from "../Button";
|
|
3
4
|
import Modal from "./Modal";
|
|
4
5
|
var Template = args => {
|
|
5
6
|
return /*#__PURE__*/React.createElement("div", {
|
|
@@ -53,4 +54,35 @@ export var Default = {
|
|
|
53
54
|
}, /*#__PURE__*/React.createElement("p", null, "This will permanently delete the user \"Simon\".", /*#__PURE__*/React.createElement("br", null), "You cannot undo this action.")) : null);
|
|
54
55
|
},
|
|
55
56
|
name: "Default"
|
|
57
|
+
};
|
|
58
|
+
export var Focus = {
|
|
59
|
+
render: _ref2 => {
|
|
60
|
+
var {
|
|
61
|
+
closeOnOutsideClick
|
|
62
|
+
} = _ref2;
|
|
63
|
+
/* eslint-disable react-hooks/rules-of-hooks */
|
|
64
|
+
var [modalOpen, setModalOpen] = useState(true);
|
|
65
|
+
var buttonRef = useRef(null);
|
|
66
|
+
/* eslint-enable react-hooks/rules-of-hooks */
|
|
67
|
+
|
|
68
|
+
var closeHandler = () => setModalOpen(false);
|
|
69
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
|
|
70
|
+
onClick: () => setModalOpen(true)
|
|
71
|
+
}, "Open modal"), modalOpen ? /*#__PURE__*/React.createElement(Modal, {
|
|
72
|
+
close: closeHandler,
|
|
73
|
+
title: "Confirm delete",
|
|
74
|
+
closeOnOutsideClick: closeOnOutsideClick,
|
|
75
|
+
buttonRow: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
|
|
76
|
+
className: "u-no-margin--bottom",
|
|
77
|
+
onClick: closeHandler
|
|
78
|
+
}, "Cancel"), /*#__PURE__*/React.createElement("button", {
|
|
79
|
+
className: "p-button--negative u-no-margin--bottom"
|
|
80
|
+
}, "Delete")),
|
|
81
|
+
focusRef: buttonRef
|
|
82
|
+
}, /*#__PURE__*/React.createElement("p", null, "This will permanently delete the user \"Simon\".", /*#__PURE__*/React.createElement("br", null), "You cannot undo this action."), /*#__PURE__*/React.createElement("p", null, /*#__PURE__*/React.createElement(Button, {
|
|
83
|
+
appearance: "link",
|
|
84
|
+
ref: buttonRef
|
|
85
|
+
}, "More information"))) : null);
|
|
86
|
+
},
|
|
87
|
+
name: "Focus"
|
|
56
88
|
};
|