@dhis2-ui/alert 10.19.4 → 10.20.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 (35) hide show
  1. package/build/cjs/alert-bar/action.js +6 -5
  2. package/build/cjs/alert-bar/alert-bar.e2e.stories.js +5 -1
  3. package/build/cjs/alert-bar/alert-bar.js +4 -1
  4. package/build/cjs/alert-bar/dismiss.js +8 -5
  5. package/build/cjs/alert-bar/features/accessibility/index.js +15 -0
  6. package/build/cjs/alert-bar/features/accessibility.feature +17 -0
  7. package/build/cjs/alert-bar/features/common/index.js +4 -0
  8. package/build/cjs/alert-bar/icon.js +1 -0
  9. package/build/cjs/alert-bar/message.js +35 -8
  10. package/build/cjs/locales/en/translations.json +3 -0
  11. package/build/cjs/locales/index.js +26 -0
  12. package/build/es/alert-bar/action.js +7 -6
  13. package/build/es/alert-bar/alert-bar.e2e.stories.js +3 -0
  14. package/build/es/alert-bar/alert-bar.js +4 -1
  15. package/build/es/alert-bar/dismiss.js +9 -6
  16. package/build/es/alert-bar/features/accessibility/index.js +13 -0
  17. package/build/es/alert-bar/features/accessibility.feature +17 -0
  18. package/build/es/alert-bar/features/common/index.js +4 -0
  19. package/build/es/alert-bar/icon.js +1 -0
  20. package/build/es/alert-bar/message.js +35 -8
  21. package/build/es/locales/en/translations.json +3 -0
  22. package/build/es/locales/index.js +18 -0
  23. package/package.json +6 -4
  24. package/src/alert-bar/action.js +20 -5
  25. package/src/alert-bar/alert-bar.e2e.stories.js +1 -0
  26. package/src/alert-bar/alert-bar.js +3 -1
  27. package/src/alert-bar/dismiss.js +23 -7
  28. package/src/alert-bar/features/accessibility/index.js +27 -0
  29. package/src/alert-bar/features/accessibility.feature +17 -0
  30. package/src/alert-bar/features/common/index.js +5 -0
  31. package/src/alert-bar/icon.js +1 -1
  32. package/src/alert-bar/message.js +49 -12
  33. package/src/locales/en/translations.json +3 -0
  34. package/src/locales/index.js +21 -0
  35. package/types/index.d.ts +1 -1
@@ -22,14 +22,15 @@ class Action extends _react.Component {
22
22
  });
23
23
  }
24
24
  render() {
25
- return /*#__PURE__*/_react.default.createElement("span", {
25
+ return /*#__PURE__*/_react.default.createElement("button", {
26
+ type: "button",
26
27
  onClick: this.onClick,
27
28
  "data-test": this.props.dataTest,
28
- className: _style.default.dynamic([["918162538", [_uiConstants.spacers.dp12]]])
29
+ className: _style.default.dynamic([["2865572695", [_uiConstants.spacers.dp12, _uiConstants.theme.focus]]])
29
30
  }, this.props.label, /*#__PURE__*/_react.default.createElement(_style.default, {
30
- id: "918162538",
31
- dynamic: [_uiConstants.spacers.dp12]
32
- }, [`span.__jsx-style-dynamic-selector{margin-inline-end:${_uiConstants.spacers.dp12};-webkit-text-decoration:underline;text-decoration:underline;white-space:nowrap;}`, "span.__jsx-style-dynamic-selector:hover{cursor:pointer;}"]));
31
+ id: "2865572695",
32
+ dynamic: [_uiConstants.spacers.dp12, _uiConstants.theme.focus]
33
+ }, [`button.__jsx-style-dynamic-selector{margin-inline-end:${_uiConstants.spacers.dp12};padding:0;border:none;background:transparent;color:inherit;font:inherit;-webkit-text-decoration:underline;text-decoration:underline;white-space:nowrap;}`, "button.__jsx-style-dynamic-selector:hover{cursor:pointer;}", `button.__jsx-style-dynamic-selector:focus{outline:3px solid ${_uiConstants.theme.focus};}`, "button.__jsx-style-dynamic-selector:focus.__jsx-style-dynamic-selector:not(:focus-visible){outline:none;}"]));
33
34
  }
34
35
  }
35
36
  exports.Action = Action;
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.default = exports.WithOnHidden = exports.WithMessage = exports.PermanentWithActions = exports.Permanent = exports.HiddenProp = exports.DisabledIcon = exports.Default = exports.CustomIcon = exports.CustomDuration = void 0;
6
+ exports.default = exports.WithOnHidden = exports.WithMessage = exports.PermanentWithActions = exports.Permanent = exports.HiddenProp = exports.DisabledIcon = exports.Default = exports.CustomIcon = exports.CustomDuration = exports.Critical = void 0;
7
7
  var _react = _interopRequireWildcard(require("react"));
8
8
  var _index = require("./index.js");
9
9
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
@@ -13,6 +13,10 @@ var _default = exports.default = {
13
13
  };
14
14
  const Default = () => /*#__PURE__*/_react.default.createElement(_index.AlertBar, null, "Default");
15
15
  exports.Default = Default;
16
+ const Critical = () => /*#__PURE__*/_react.default.createElement(_index.AlertBar, {
17
+ critical: true
18
+ }, "Critical");
19
+ exports.Critical = Critical;
16
20
  const CustomDuration = () => /*#__PURE__*/_react.default.createElement(_index.AlertBar, {
17
21
  duration: 2000
18
22
  }, "Custom duration");
@@ -122,7 +122,10 @@ const AlertBar = ({
122
122
  success: success,
123
123
  warning: warning,
124
124
  info: info
125
- }), /*#__PURE__*/_react.default.createElement(_message.Message, null, children), /*#__PURE__*/_react.default.createElement(_actions.Actions, {
125
+ }), /*#__PURE__*/_react.default.createElement(_message.Message, {
126
+ critical: critical,
127
+ dataTest: `${dataTest}-message`
128
+ }, children), /*#__PURE__*/_react.default.createElement(_actions.Actions, {
126
129
  actions: actions,
127
130
  hide: runHideAnimation,
128
131
  dataTest: dataTest
@@ -9,18 +9,21 @@ var _uiConstants = require("@dhis2/ui-constants");
9
9
  var _uiIcons = require("@dhis2/ui-icons");
10
10
  var _propTypes = _interopRequireDefault(require("prop-types"));
11
11
  var _react = _interopRequireDefault(require("react"));
12
+ var _index = _interopRequireDefault(require("../locales/index.js"));
12
13
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
14
  const Dismiss = ({
14
15
  onClick,
15
16
  dataTest
16
- }) => /*#__PURE__*/_react.default.createElement("div", {
17
+ }) => /*#__PURE__*/_react.default.createElement("button", {
18
+ type: "button",
17
19
  onClick: onClick,
18
20
  "data-test": dataTest,
19
- className: _style.default.dynamic([["1892313381", [_uiConstants.spacers.dp16]]])
21
+ "aria-label": _index.default.t('Dismiss'),
22
+ className: _style.default.dynamic([["503636260", [_uiConstants.spacers.dp16, _uiConstants.theme.focus]]])
20
23
  }, /*#__PURE__*/_react.default.createElement(_uiIcons.IconCross24, null), /*#__PURE__*/_react.default.createElement(_style.default, {
21
- id: "1892313381",
22
- dynamic: [_uiConstants.spacers.dp16]
23
- }, [`div.__jsx-style-dynamic-selector{margin-inline-start:${_uiConstants.spacers.dp16};min-height:32px;min-width:32px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;border-radius:5px;}`, "div.__jsx-style-dynamic-selector:hover{cursor:pointer;background:rgba(0,0,0,0.15);}", "div.__jsx-style-dynamic-selector:active{background:rgba(0,0,0,0.25);}", "div.__jsx-style-dynamic-selector svg{width:18px;height:18px;}"]));
24
+ id: "503636260",
25
+ dynamic: [_uiConstants.spacers.dp16, _uiConstants.theme.focus]
26
+ }, [`button.__jsx-style-dynamic-selector{margin-inline-start:${_uiConstants.spacers.dp16};min-height:32px;min-width:32px;padding:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;border:none;border-radius:5px;background:transparent;color:inherit;}`, "button.__jsx-style-dynamic-selector:hover{cursor:pointer;background:rgba(0,0,0,0.15);}", "button.__jsx-style-dynamic-selector:active{background:rgba(0,0,0,0.25);}", `button.__jsx-style-dynamic-selector:focus{outline:3px solid ${_uiConstants.theme.focus};}`, "button.__jsx-style-dynamic-selector:focus.__jsx-style-dynamic-selector:not(:focus-visible){outline:none;}", "button.__jsx-style-dynamic-selector svg{width:18px;height:18px;}"]));
24
27
  exports.Dismiss = Dismiss;
25
28
  Dismiss.propTypes = {
26
29
  dataTest: _propTypes.default.string.isRequired,
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+
3
+ var _cypressCucumberPreprocessor = require("@badeball/cypress-cucumber-preprocessor");
4
+ (0, _cypressCucumberPreprocessor.Then)('the AlertBar message has the status role', () => {
5
+ cy.get('[data-test="dhis2-uicore-alertbar-message"]').find('[role="status"]').should('exist');
6
+ });
7
+ (0, _cypressCucumberPreprocessor.Then)('the AlertBar message has the alert role', () => {
8
+ cy.get('[data-test="dhis2-uicore-alertbar-message"]').find('[role="alert"]').should('exist');
9
+ });
10
+ (0, _cypressCucumberPreprocessor.Then)('the icon is hidden from assistive technologies', () => {
11
+ cy.get('[data-test="dhis2-uicore-alertbar-icon"]').should('have.attr', 'aria-hidden', 'true');
12
+ });
13
+ (0, _cypressCucumberPreprocessor.Then)('the dismiss control is a button labelled "Dismiss"', () => {
14
+ cy.get('[data-test="dhis2-uicore-alertbar-dismiss"]').should('match', 'button').and('have.attr', 'aria-label', 'Dismiss');
15
+ });
@@ -0,0 +1,17 @@
1
+ Feature: AlertBar accessibility
2
+
3
+ Scenario: A default AlertBar message is a polite live region
4
+ Given a default AlertBar is rendered
5
+ Then the AlertBar message has the status role
6
+
7
+ Scenario: A critical AlertBar message is an assertive live region
8
+ Given a critical AlertBar is rendered
9
+ Then the AlertBar message has the alert role
10
+
11
+ Scenario: The icon is hidden from assistive technologies
12
+ Given a default AlertBar is rendered
13
+ Then the icon is hidden from assistive technologies
14
+
15
+ Scenario: The dismiss control is a labelled button
16
+ Given a default AlertBar is rendered
17
+ Then the dismiss control is a button labelled "Dismiss"
@@ -5,6 +5,10 @@ var _cypressCucumberPreprocessor = require("@badeball/cypress-cucumber-preproces
5
5
  cy.visitStory('AlertBar', 'Default');
6
6
  cy.get('[data-test="dhis2-uicore-alertbar"]').should('be.visible');
7
7
  });
8
+ (0, _cypressCucumberPreprocessor.Given)('a critical AlertBar is rendered', () => {
9
+ cy.visitStory('AlertBar', 'Critical');
10
+ cy.get('[data-test="dhis2-uicore-alertbar"]').should('be.visible');
11
+ });
8
12
  (0, _cypressCucumberPreprocessor.Given)('the AlertBar is rendered', () => {
9
13
  cy.get('[data-test="dhis2-uicore-alertbar"]').should('exist');
10
14
  cy.get('[data-test="dhis2-uicore-alertbar"]').should('be.visible');
@@ -60,6 +60,7 @@ const Icon = ({
60
60
  }
61
61
  return /*#__PURE__*/_react.default.createElement("div", {
62
62
  "data-test": dataTest,
63
+ "aria-hidden": "true",
63
64
  className: _style.default.dynamic([["4176481777", [_uiConstants.spacers.dp12]]])
64
65
  }, /*#__PURE__*/_react.default.isValidElement(icon) ? icon : /*#__PURE__*/_react.default.createElement(StatusIcon, {
65
66
  valid: success,
@@ -6,16 +6,43 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.Message = void 0;
7
7
  var _style = _interopRequireDefault(require("styled-jsx/style"));
8
8
  var _propTypes = _interopRequireDefault(require("prop-types"));
9
- var _react = _interopRequireDefault(require("react"));
9
+ var _react = _interopRequireWildcard(require("react"));
10
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
10
11
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
+ // Screen readers don't announce a role="status" region that has text on
13
+ // mount, so the text is added after a delay. role="alert" is announced on
14
+ // insertion and needs no delay. The visible copy is rendered separately so
15
+ // the delay doesn't shift the layout.
16
+ const ANNOUNCE_DELAY = 100;
11
17
  const Message = ({
12
- children
13
- }) => /*#__PURE__*/_react.default.createElement("div", {
14
- className: "jsx-2181391309"
15
- }, children, /*#__PURE__*/_react.default.createElement(_style.default, {
16
- id: "2181391309"
17
- }, ["div.jsx-2181391309{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;}"]));
18
+ children,
19
+ critical,
20
+ dataTest
21
+ }) => {
22
+ const [announceable, setAnnounceable] = (0, _react.useState)(false);
23
+ (0, _react.useEffect)(() => {
24
+ if (critical) {
25
+ return;
26
+ }
27
+ const timeout = setTimeout(() => setAnnounceable(true), ANNOUNCE_DELAY);
28
+ return () => clearTimeout(timeout);
29
+ }, [critical]);
30
+ return /*#__PURE__*/_react.default.createElement("div", {
31
+ "data-test": dataTest,
32
+ className: "jsx-2192918634"
33
+ }, /*#__PURE__*/_react.default.createElement("span", {
34
+ "aria-hidden": "true",
35
+ className: "jsx-2192918634"
36
+ }, children), /*#__PURE__*/_react.default.createElement("span", {
37
+ role: critical ? 'alert' : 'status',
38
+ className: "jsx-2192918634" + " " + "live-region"
39
+ }, critical || announceable ? children : null), /*#__PURE__*/_react.default.createElement(_style.default, {
40
+ id: "2192918634"
41
+ }, ["div.jsx-2192918634{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;}", ".live-region.jsx-2192918634{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;-webkit-clip:rect(0,0,0,0);clip:rect(0,0,0,0);white-space:nowrap;border:0;}"]));
42
+ };
18
43
  exports.Message = Message;
19
44
  Message.propTypes = {
20
- children: _propTypes.default.string.isRequired
45
+ children: _propTypes.default.string.isRequired,
46
+ dataTest: _propTypes.default.string.isRequired,
47
+ critical: _propTypes.default.bool
21
48
  };
@@ -0,0 +1,3 @@
1
+ {
2
+ "Dismiss": "Dismiss"
3
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _d2I18n = _interopRequireDefault(require("@dhis2/d2-i18n"));
8
+ var _translations = _interopRequireDefault(require("./en/translations.json"));
9
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
+ //------------------------------------------------------------------------------
11
+ // <auto-generated>
12
+ // This code was generated by d2-i18n-generate.
13
+ //
14
+ // Changes to this file may cause incorrect behavior and will be lost if
15
+ // the code is regenerated.
16
+ // </auto-generated>
17
+ //------------------------------------------------------------------------------
18
+
19
+ const namespace = 'default';
20
+
21
+ // Use 'deep' = true and 'overwrite' = false to add to, but not overwrite,
22
+ // custom translations from the datastore (added by the app adapter)
23
+
24
+ _d2I18n.default.addResourceBundle('en', namespace, _translations.default, true, false);
25
+ _d2I18n.default.addResourceBundle('en', namespace, _translations.default, true, false);
26
+ var _default = exports.default = _d2I18n.default;
@@ -2,7 +2,7 @@ import _JSXStyle from "styled-jsx/style";
2
2
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
3
3
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
4
4
  function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
5
- import { spacers } from '@dhis2/ui-constants';
5
+ import { spacers, theme } from '@dhis2/ui-constants';
6
6
  import PropTypes from 'prop-types';
7
7
  import React, { Component } from 'react';
8
8
  class Action extends Component {
@@ -14,14 +14,15 @@ class Action extends Component {
14
14
  });
15
15
  }
16
16
  render() {
17
- return /*#__PURE__*/React.createElement("span", {
17
+ return /*#__PURE__*/React.createElement("button", {
18
+ type: "button",
18
19
  onClick: this.onClick,
19
20
  "data-test": this.props.dataTest,
20
- className: _JSXStyle.dynamic([["918162538", [spacers.dp12]]])
21
+ className: _JSXStyle.dynamic([["2865572695", [spacers.dp12, theme.focus]]])
21
22
  }, this.props.label, /*#__PURE__*/React.createElement(_JSXStyle, {
22
- id: "918162538",
23
- dynamic: [spacers.dp12]
24
- }, [`span.__jsx-style-dynamic-selector{margin-inline-end:${spacers.dp12};-webkit-text-decoration:underline;text-decoration:underline;white-space:nowrap;}`, "span.__jsx-style-dynamic-selector:hover{cursor:pointer;}"]));
23
+ id: "2865572695",
24
+ dynamic: [spacers.dp12, theme.focus]
25
+ }, [`button.__jsx-style-dynamic-selector{margin-inline-end:${spacers.dp12};padding:0;border:none;background:transparent;color:inherit;font:inherit;-webkit-text-decoration:underline;text-decoration:underline;white-space:nowrap;}`, "button.__jsx-style-dynamic-selector:hover{cursor:pointer;}", `button.__jsx-style-dynamic-selector:focus{outline:3px solid ${theme.focus};}`, "button.__jsx-style-dynamic-selector:focus.__jsx-style-dynamic-selector:not(:focus-visible){outline:none;}"]));
25
26
  }
26
27
  }
27
28
  Action.propTypes = {
@@ -5,6 +5,9 @@ export default {
5
5
  title: 'AlertBar'
6
6
  };
7
7
  export const Default = () => /*#__PURE__*/React.createElement(AlertBar, null, "Default");
8
+ export const Critical = () => /*#__PURE__*/React.createElement(AlertBar, {
9
+ critical: true
10
+ }, "Critical");
8
11
  export const CustomDuration = () => /*#__PURE__*/React.createElement(AlertBar, {
9
12
  duration: 2000
10
13
  }, "Custom duration");
@@ -114,7 +114,10 @@ const AlertBar = ({
114
114
  success: success,
115
115
  warning: warning,
116
116
  info: info
117
- }), /*#__PURE__*/React.createElement(Message, null, children), /*#__PURE__*/React.createElement(Actions, {
117
+ }), /*#__PURE__*/React.createElement(Message, {
118
+ critical: critical,
119
+ dataTest: `${dataTest}-message`
120
+ }, children), /*#__PURE__*/React.createElement(Actions, {
118
121
  actions: actions,
119
122
  hide: runHideAnimation,
120
123
  dataTest: dataTest
@@ -1,19 +1,22 @@
1
1
  import _JSXStyle from "styled-jsx/style";
2
- import { spacers } from '@dhis2/ui-constants';
2
+ import { spacers, theme } from '@dhis2/ui-constants';
3
3
  import { IconCross24 } from '@dhis2/ui-icons';
4
4
  import PropTypes from 'prop-types';
5
5
  import React from 'react';
6
+ import i18n from '../locales/index.js';
6
7
  const Dismiss = ({
7
8
  onClick,
8
9
  dataTest
9
- }) => /*#__PURE__*/React.createElement("div", {
10
+ }) => /*#__PURE__*/React.createElement("button", {
11
+ type: "button",
10
12
  onClick: onClick,
11
13
  "data-test": dataTest,
12
- className: _JSXStyle.dynamic([["1892313381", [spacers.dp16]]])
14
+ "aria-label": i18n.t('Dismiss'),
15
+ className: _JSXStyle.dynamic([["503636260", [spacers.dp16, theme.focus]]])
13
16
  }, /*#__PURE__*/React.createElement(IconCross24, null), /*#__PURE__*/React.createElement(_JSXStyle, {
14
- id: "1892313381",
15
- dynamic: [spacers.dp16]
16
- }, [`div.__jsx-style-dynamic-selector{margin-inline-start:${spacers.dp16};min-height:32px;min-width:32px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;border-radius:5px;}`, "div.__jsx-style-dynamic-selector:hover{cursor:pointer;background:rgba(0,0,0,0.15);}", "div.__jsx-style-dynamic-selector:active{background:rgba(0,0,0,0.25);}", "div.__jsx-style-dynamic-selector svg{width:18px;height:18px;}"]));
17
+ id: "503636260",
18
+ dynamic: [spacers.dp16, theme.focus]
19
+ }, [`button.__jsx-style-dynamic-selector{margin-inline-start:${spacers.dp16};min-height:32px;min-width:32px;padding:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;border:none;border-radius:5px;background:transparent;color:inherit;}`, "button.__jsx-style-dynamic-selector:hover{cursor:pointer;background:rgba(0,0,0,0.15);}", "button.__jsx-style-dynamic-selector:active{background:rgba(0,0,0,0.25);}", `button.__jsx-style-dynamic-selector:focus{outline:3px solid ${theme.focus};}`, "button.__jsx-style-dynamic-selector:focus.__jsx-style-dynamic-selector:not(:focus-visible){outline:none;}", "button.__jsx-style-dynamic-selector svg{width:18px;height:18px;}"]));
17
20
  Dismiss.propTypes = {
18
21
  dataTest: PropTypes.string.isRequired,
19
22
  onClick: PropTypes.func.isRequired
@@ -0,0 +1,13 @@
1
+ import { Then } from '@badeball/cypress-cucumber-preprocessor';
2
+ Then('the AlertBar message has the status role', () => {
3
+ cy.get('[data-test="dhis2-uicore-alertbar-message"]').find('[role="status"]').should('exist');
4
+ });
5
+ Then('the AlertBar message has the alert role', () => {
6
+ cy.get('[data-test="dhis2-uicore-alertbar-message"]').find('[role="alert"]').should('exist');
7
+ });
8
+ Then('the icon is hidden from assistive technologies', () => {
9
+ cy.get('[data-test="dhis2-uicore-alertbar-icon"]').should('have.attr', 'aria-hidden', 'true');
10
+ });
11
+ Then('the dismiss control is a button labelled "Dismiss"', () => {
12
+ cy.get('[data-test="dhis2-uicore-alertbar-dismiss"]').should('match', 'button').and('have.attr', 'aria-label', 'Dismiss');
13
+ });
@@ -0,0 +1,17 @@
1
+ Feature: AlertBar accessibility
2
+
3
+ Scenario: A default AlertBar message is a polite live region
4
+ Given a default AlertBar is rendered
5
+ Then the AlertBar message has the status role
6
+
7
+ Scenario: A critical AlertBar message is an assertive live region
8
+ Given a critical AlertBar is rendered
9
+ Then the AlertBar message has the alert role
10
+
11
+ Scenario: The icon is hidden from assistive technologies
12
+ Given a default AlertBar is rendered
13
+ Then the icon is hidden from assistive technologies
14
+
15
+ Scenario: The dismiss control is a labelled button
16
+ Given a default AlertBar is rendered
17
+ Then the dismiss control is a button labelled "Dismiss"
@@ -3,6 +3,10 @@ Given('a default AlertBar is rendered', () => {
3
3
  cy.visitStory('AlertBar', 'Default');
4
4
  cy.get('[data-test="dhis2-uicore-alertbar"]').should('be.visible');
5
5
  });
6
+ Given('a critical AlertBar is rendered', () => {
7
+ cy.visitStory('AlertBar', 'Critical');
8
+ cy.get('[data-test="dhis2-uicore-alertbar"]').should('be.visible');
9
+ });
6
10
  Given('the AlertBar is rendered', () => {
7
11
  cy.get('[data-test="dhis2-uicore-alertbar"]').should('exist');
8
12
  cy.get('[data-test="dhis2-uicore-alertbar"]').should('be.visible');
@@ -53,6 +53,7 @@ const Icon = ({
53
53
  }
54
54
  return /*#__PURE__*/React.createElement("div", {
55
55
  "data-test": dataTest,
56
+ "aria-hidden": "true",
56
57
  className: _JSXStyle.dynamic([["4176481777", [spacers.dp12]]])
57
58
  }, /*#__PURE__*/React.isValidElement(icon) ? icon : /*#__PURE__*/React.createElement(StatusIcon, {
58
59
  valid: success,
@@ -1,14 +1,41 @@
1
1
  import _JSXStyle from "styled-jsx/style";
2
2
  import PropTypes from 'prop-types';
3
- import React from 'react';
3
+ import React, { useEffect, useState } from 'react';
4
+
5
+ // Screen readers don't announce a role="status" region that has text on
6
+ // mount, so the text is added after a delay. role="alert" is announced on
7
+ // insertion and needs no delay. The visible copy is rendered separately so
8
+ // the delay doesn't shift the layout.
9
+ const ANNOUNCE_DELAY = 100;
4
10
  const Message = ({
5
- children
6
- }) => /*#__PURE__*/React.createElement("div", {
7
- className: "jsx-2181391309"
8
- }, children, /*#__PURE__*/React.createElement(_JSXStyle, {
9
- id: "2181391309"
10
- }, ["div.jsx-2181391309{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;}"]));
11
+ children,
12
+ critical,
13
+ dataTest
14
+ }) => {
15
+ const [announceable, setAnnounceable] = useState(false);
16
+ useEffect(() => {
17
+ if (critical) {
18
+ return;
19
+ }
20
+ const timeout = setTimeout(() => setAnnounceable(true), ANNOUNCE_DELAY);
21
+ return () => clearTimeout(timeout);
22
+ }, [critical]);
23
+ return /*#__PURE__*/React.createElement("div", {
24
+ "data-test": dataTest,
25
+ className: "jsx-2192918634"
26
+ }, /*#__PURE__*/React.createElement("span", {
27
+ "aria-hidden": "true",
28
+ className: "jsx-2192918634"
29
+ }, children), /*#__PURE__*/React.createElement("span", {
30
+ role: critical ? 'alert' : 'status',
31
+ className: "jsx-2192918634" + " " + "live-region"
32
+ }, critical || announceable ? children : null), /*#__PURE__*/React.createElement(_JSXStyle, {
33
+ id: "2192918634"
34
+ }, ["div.jsx-2192918634{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;}", ".live-region.jsx-2192918634{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;-webkit-clip:rect(0,0,0,0);clip:rect(0,0,0,0);white-space:nowrap;border:0;}"]));
35
+ };
11
36
  Message.propTypes = {
12
- children: PropTypes.string.isRequired
37
+ children: PropTypes.string.isRequired,
38
+ dataTest: PropTypes.string.isRequired,
39
+ critical: PropTypes.bool
13
40
  };
14
41
  export { Message };
@@ -0,0 +1,3 @@
1
+ {
2
+ "Dismiss": "Dismiss"
3
+ }
@@ -0,0 +1,18 @@
1
+ //------------------------------------------------------------------------------
2
+ // <auto-generated>
3
+ // This code was generated by d2-i18n-generate.
4
+ //
5
+ // Changes to this file may cause incorrect behavior and will be lost if
6
+ // the code is regenerated.
7
+ // </auto-generated>
8
+ //------------------------------------------------------------------------------
9
+ import i18n from '@dhis2/d2-i18n';
10
+ import enTranslations from './en/translations.json';
11
+ const namespace = 'default';
12
+
13
+ // Use 'deep' = true and 'overwrite' = false to add to, but not overwrite,
14
+ // custom translations from the datastore (added by the app adapter)
15
+
16
+ i18n.addResourceBundle('en', namespace, enTranslations, true, false);
17
+ i18n.addResourceBundle('en', namespace, enTranslations, true, false);
18
+ export default i18n;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dhis2-ui/alert",
3
- "version": "10.19.4",
3
+ "version": "10.20.0",
4
4
  "description": "UI Alert",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,15 +27,16 @@
27
27
  "test": "d2-app-scripts test --jestConfig ../../jest.config.shared.js"
28
28
  },
29
29
  "peerDependencies": {
30
+ "@dhis2/d2-i18n": "^1",
30
31
  "react": "^16.13 || ^18",
31
32
  "react-dom": "^16.13 || ^18",
32
33
  "styled-jsx": "^4"
33
34
  },
34
35
  "dependencies": {
35
36
  "@dhis2/prop-types": "^3.1.2",
36
- "@dhis2-ui/portal": "10.19.4",
37
- "@dhis2/ui-constants": "10.19.4",
38
- "@dhis2/ui-icons": "10.19.4",
37
+ "@dhis2-ui/portal": "10.20.0",
38
+ "@dhis2/ui-constants": "10.20.0",
39
+ "@dhis2/ui-icons": "10.20.0",
39
40
  "classnames": "^2.3.1",
40
41
  "prop-types": "^15.7.2"
41
42
  },
@@ -45,6 +46,7 @@
45
46
  "src"
46
47
  ],
47
48
  "devDependencies": {
49
+ "@dhis2/d2-i18n": "^1.1.0",
48
50
  "react": "^18.3.1",
49
51
  "react-dom": "^18.3.1",
50
52
  "styled-jsx": "^4.0.1"
@@ -1,4 +1,4 @@
1
- import { spacers } from '@dhis2/ui-constants'
1
+ import { spacers, theme } from '@dhis2/ui-constants'
2
2
  import PropTypes from 'prop-types'
3
3
  import React, { Component } from 'react'
4
4
 
@@ -10,19 +10,34 @@ class Action extends Component {
10
10
 
11
11
  render() {
12
12
  return (
13
- <span onClick={this.onClick} data-test={this.props.dataTest}>
13
+ <button
14
+ type="button"
15
+ onClick={this.onClick}
16
+ data-test={this.props.dataTest}
17
+ >
14
18
  {this.props.label}
15
19
  <style jsx>{`
16
- span {
20
+ button {
17
21
  margin-inline-end: ${spacers.dp12};
22
+ padding: 0;
23
+ border: none;
24
+ background: transparent;
25
+ color: inherit;
26
+ font: inherit;
18
27
  text-decoration: underline;
19
28
  white-space: nowrap;
20
29
  }
21
- span:hover {
30
+ button:hover {
22
31
  cursor: pointer;
23
32
  }
33
+ button:focus {
34
+ outline: 3px solid ${theme.focus};
35
+ }
36
+ button:focus:not(:focus-visible) {
37
+ outline: none;
38
+ }
24
39
  `}</style>
25
- </span>
40
+ </button>
26
41
  )
27
42
  }
28
43
  }
@@ -6,6 +6,7 @@ window.onHidden = window.Cypress && window.Cypress.cy.stub()
6
6
  export default { title: 'AlertBar' }
7
7
 
8
8
  export const Default = () => <AlertBar>Default</AlertBar>
9
+ export const Critical = () => <AlertBar critical>Critical</AlertBar>
9
10
  export const CustomDuration = () => (
10
11
  <AlertBar duration={2000}>Custom duration</AlertBar>
11
12
  )
@@ -129,7 +129,9 @@ const AlertBar = ({
129
129
  warning={warning}
130
130
  info={info}
131
131
  />
132
- <Message>{children}</Message>
132
+ <Message critical={critical} dataTest={`${dataTest}-message`}>
133
+ {children}
134
+ </Message>
133
135
  <Actions
134
136
  actions={actions}
135
137
  hide={runHideAnimation}
@@ -1,34 +1,50 @@
1
- import { spacers } from '@dhis2/ui-constants'
1
+ import { spacers, theme } from '@dhis2/ui-constants'
2
2
  import { IconCross24 } from '@dhis2/ui-icons'
3
3
  import PropTypes from 'prop-types'
4
4
  import React from 'react'
5
+ import i18n from '../locales/index.js'
5
6
 
6
7
  const Dismiss = ({ onClick, dataTest }) => (
7
- <div onClick={onClick} data-test={dataTest}>
8
+ <button
9
+ type="button"
10
+ onClick={onClick}
11
+ data-test={dataTest}
12
+ aria-label={i18n.t('Dismiss')}
13
+ >
8
14
  <IconCross24 />
9
15
  <style jsx>{`
10
- div {
16
+ button {
11
17
  margin-inline-start: ${spacers.dp16};
12
18
  min-height: 32px;
13
19
  min-width: 32px;
20
+ padding: 0;
14
21
  display: flex;
15
22
  align-items: center;
16
23
  justify-content: center;
24
+ border: none;
17
25
  border-radius: 5px;
26
+ background: transparent;
27
+ color: inherit;
18
28
  }
19
- div:hover {
29
+ button:hover {
20
30
  cursor: pointer;
21
31
  background: rgba(0, 0, 0, 0.15);
22
32
  }
23
- div:active {
33
+ button:active {
24
34
  background: rgba(0, 0, 0, 0.25);
25
35
  }
26
- div :global(svg) {
36
+ button:focus {
37
+ outline: 3px solid ${theme.focus};
38
+ }
39
+ button:focus:not(:focus-visible) {
40
+ outline: none;
41
+ }
42
+ button :global(svg) {
27
43
  width: 18px;
28
44
  height: 18px;
29
45
  }
30
46
  `}</style>
31
- </div>
47
+ </button>
32
48
  )
33
49
 
34
50
  Dismiss.propTypes = {
@@ -0,0 +1,27 @@
1
+ import { Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Then('the AlertBar message has the status role', () => {
4
+ cy.get('[data-test="dhis2-uicore-alertbar-message"]')
5
+ .find('[role="status"]')
6
+ .should('exist')
7
+ })
8
+
9
+ Then('the AlertBar message has the alert role', () => {
10
+ cy.get('[data-test="dhis2-uicore-alertbar-message"]')
11
+ .find('[role="alert"]')
12
+ .should('exist')
13
+ })
14
+
15
+ Then('the icon is hidden from assistive technologies', () => {
16
+ cy.get('[data-test="dhis2-uicore-alertbar-icon"]').should(
17
+ 'have.attr',
18
+ 'aria-hidden',
19
+ 'true'
20
+ )
21
+ })
22
+
23
+ Then('the dismiss control is a button labelled "Dismiss"', () => {
24
+ cy.get('[data-test="dhis2-uicore-alertbar-dismiss"]')
25
+ .should('match', 'button')
26
+ .and('have.attr', 'aria-label', 'Dismiss')
27
+ })
@@ -0,0 +1,17 @@
1
+ Feature: AlertBar accessibility
2
+
3
+ Scenario: A default AlertBar message is a polite live region
4
+ Given a default AlertBar is rendered
5
+ Then the AlertBar message has the status role
6
+
7
+ Scenario: A critical AlertBar message is an assertive live region
8
+ Given a critical AlertBar is rendered
9
+ Then the AlertBar message has the alert role
10
+
11
+ Scenario: The icon is hidden from assistive technologies
12
+ Given a default AlertBar is rendered
13
+ Then the icon is hidden from assistive technologies
14
+
15
+ Scenario: The dismiss control is a labelled button
16
+ Given a default AlertBar is rendered
17
+ Then the dismiss control is a button labelled "Dismiss"
@@ -5,6 +5,11 @@ Given('a default AlertBar is rendered', () => {
5
5
  cy.get('[data-test="dhis2-uicore-alertbar"]').should('be.visible')
6
6
  })
7
7
 
8
+ Given('a critical AlertBar is rendered', () => {
9
+ cy.visitStory('AlertBar', 'Critical')
10
+ cy.get('[data-test="dhis2-uicore-alertbar"]').should('be.visible')
11
+ })
12
+
8
13
  Given('the AlertBar is rendered', () => {
9
14
  cy.get('[data-test="dhis2-uicore-alertbar"]').should('exist')
10
15
  cy.get('[data-test="dhis2-uicore-alertbar"]').should('be.visible')
@@ -40,7 +40,7 @@ const Icon = ({ icon, success, warning, critical, info, dataTest }) => {
40
40
  }
41
41
 
42
42
  return (
43
- <div data-test={dataTest}>
43
+ <div data-test={dataTest} aria-hidden="true">
44
44
  {React.isValidElement(icon) ? (
45
45
  icon
46
46
  ) : (
@@ -1,19 +1,56 @@
1
1
  import PropTypes from 'prop-types'
2
- import React from 'react'
3
-
4
- const Message = ({ children }) => (
5
- <div>
6
- {children}
7
- <style jsx>{`
8
- div {
9
- flex-grow: 1;
10
- }
11
- `}</style>
12
- </div>
13
- )
2
+ import React, { useEffect, useState } from 'react'
3
+
4
+ // Screen readers don't announce a role="status" region that has text on
5
+ // mount, so the text is added after a delay. role="alert" is announced on
6
+ // insertion and needs no delay. The visible copy is rendered separately so
7
+ // the delay doesn't shift the layout.
8
+ const ANNOUNCE_DELAY = 100
9
+
10
+ const Message = ({ children, critical, dataTest }) => {
11
+ const [announceable, setAnnounceable] = useState(false)
12
+
13
+ useEffect(() => {
14
+ if (critical) {
15
+ return
16
+ }
17
+
18
+ const timeout = setTimeout(() => setAnnounceable(true), ANNOUNCE_DELAY)
19
+
20
+ return () => clearTimeout(timeout)
21
+ }, [critical])
22
+
23
+ return (
24
+ <div data-test={dataTest}>
25
+ <span aria-hidden="true">{children}</span>
26
+ <span role={critical ? 'alert' : 'status'} className="live-region">
27
+ {critical || announceable ? children : null}
28
+ </span>
29
+ <style jsx>{`
30
+ div {
31
+ flex-grow: 1;
32
+ }
33
+
34
+ .live-region {
35
+ position: absolute;
36
+ width: 1px;
37
+ height: 1px;
38
+ margin: -1px;
39
+ padding: 0;
40
+ overflow: hidden;
41
+ clip: rect(0, 0, 0, 0);
42
+ white-space: nowrap;
43
+ border: 0;
44
+ }
45
+ `}</style>
46
+ </div>
47
+ )
48
+ }
14
49
 
15
50
  Message.propTypes = {
16
51
  children: PropTypes.string.isRequired,
52
+ dataTest: PropTypes.string.isRequired,
53
+ critical: PropTypes.bool,
17
54
  }
18
55
 
19
56
  export { Message }
@@ -0,0 +1,3 @@
1
+ {
2
+ "Dismiss": "Dismiss"
3
+ }
@@ -0,0 +1,21 @@
1
+ //------------------------------------------------------------------------------
2
+ // <auto-generated>
3
+ // This code was generated by d2-i18n-generate.
4
+ //
5
+ // Changes to this file may cause incorrect behavior and will be lost if
6
+ // the code is regenerated.
7
+ // </auto-generated>
8
+ //------------------------------------------------------------------------------
9
+ import i18n from '@dhis2/d2-i18n'
10
+
11
+ import enTranslations from './en/translations.json'
12
+
13
+ const namespace = 'default'
14
+
15
+ // Use 'deep' = true and 'overwrite' = false to add to, but not overwrite,
16
+ // custom translations from the datastore (added by the app adapter)
17
+
18
+ i18n.addResourceBundle('en', namespace, enTranslations, true, false)
19
+ i18n.addResourceBundle('en', namespace, enTranslations, true, false)
20
+
21
+ export default i18n
package/types/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as React from 'react'
2
2
 
3
3
  export interface ActionsAction {
4
4
  label: string
5
- onClick: React.MouseEventHandler<HTMLSpanElement>
5
+ onClick: React.MouseEventHandler<HTMLButtonElement>
6
6
  }
7
7
 
8
8
  type ActionActionTuple = [ActionsAction?, ActionsAction?]