@atlaskit/avatar-group 9.3.3 → 9.3.5

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 (41) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cjs/components/avatar-group-item.js +18 -10
  3. package/dist/cjs/components/avatar-group.js +3 -2
  4. package/dist/cjs/components/grid.js +2 -2
  5. package/dist/cjs/components/internal/components/focus-manager.js +64 -0
  6. package/dist/cjs/components/internal/hooks/use-register-item-with-focus-manager.js +23 -0
  7. package/dist/cjs/components/internal/utiles/handle-focus.js +82 -0
  8. package/dist/cjs/components/more-indicator.js +1 -1
  9. package/dist/cjs/components/stack.js +2 -2
  10. package/dist/cjs/version.json +1 -1
  11. package/dist/es2019/components/avatar-group-item.js +14 -8
  12. package/dist/es2019/components/avatar-group.js +3 -2
  13. package/dist/es2019/components/grid.js +2 -2
  14. package/dist/es2019/components/internal/components/focus-manager.js +54 -0
  15. package/dist/es2019/components/internal/hooks/use-register-item-with-focus-manager.js +18 -0
  16. package/dist/es2019/components/internal/utiles/handle-focus.js +78 -0
  17. package/dist/es2019/components/more-indicator.js +1 -1
  18. package/dist/es2019/components/stack.js +2 -2
  19. package/dist/es2019/version.json +1 -1
  20. package/dist/esm/components/avatar-group-item.js +15 -10
  21. package/dist/esm/components/avatar-group.js +3 -2
  22. package/dist/esm/components/grid.js +2 -2
  23. package/dist/esm/components/internal/components/focus-manager.js +53 -0
  24. package/dist/esm/components/internal/hooks/use-register-item-with-focus-manager.js +17 -0
  25. package/dist/esm/components/internal/utiles/handle-focus.js +75 -0
  26. package/dist/esm/components/more-indicator.js +1 -1
  27. package/dist/esm/components/stack.js +2 -2
  28. package/dist/esm/version.json +1 -1
  29. package/dist/types/components/avatar-group-item.d.ts +2 -2
  30. package/dist/types/components/avatar-group.d.ts +8 -8
  31. package/dist/types/components/internal/components/focus-manager.d.ts +21 -0
  32. package/dist/types/components/internal/hooks/use-register-item-with-focus-manager.d.ts +4 -0
  33. package/dist/types/components/internal/utiles/handle-focus.d.ts +2 -0
  34. package/dist/types/components/types.d.ts +2 -0
  35. package/dist/types-ts4.5/components/avatar-group-item.d.ts +2 -2
  36. package/dist/types-ts4.5/components/avatar-group.d.ts +8 -8
  37. package/dist/types-ts4.5/components/internal/components/focus-manager.d.ts +21 -0
  38. package/dist/types-ts4.5/components/internal/hooks/use-register-item-with-focus-manager.d.ts +4 -0
  39. package/dist/types-ts4.5/components/internal/utiles/handle-focus.d.ts +2 -0
  40. package/dist/types-ts4.5/components/types.d.ts +2 -0
  41. package/package.json +9 -15
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @atlaskit/avatar-group
2
2
 
3
+ ## 9.3.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [`4ae083a7e66`](https://bitbucket.org/atlassian/atlassian-frontend/commits/4ae083a7e66) - Use `@af/accessibility-testing` for default jest-axe config and jest-axe import in accessibility testing.
8
+
9
+ ## 9.3.4
10
+
11
+ ### Patch Changes
12
+
13
+ - [`cb7033c5b72`](https://bitbucket.org/atlassian/atlassian-frontend/commits/cb7033c5b72) - keyboard arrow (UP and DOWN) support in avatar-group popup component
14
+
3
15
  ## 9.3.3
4
16
 
5
17
  ### Patch Changes
@@ -1,28 +1,34 @@
1
1
  "use strict";
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _typeof = require("@babel/runtime/helpers/typeof");
4
5
  Object.defineProperty(exports, "__esModule", {
5
6
  value: true
6
7
  });
7
8
  exports.default = void 0;
8
9
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
9
10
  var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
10
- var _react = _interopRequireDefault(require("react"));
11
+ var _react = _interopRequireWildcard(require("react"));
11
12
  var _avatar = _interopRequireDefault(require("@atlaskit/avatar"));
13
+ var _mergeRefs = _interopRequireDefault(require("@atlaskit/ds-lib/merge-refs"));
12
14
  var _menu = require("@atlaskit/menu");
15
+ var _useRegisterItemWithFocusManager = _interopRequireDefault(require("./internal/hooks/use-register-item-with-focus-manager"));
13
16
  var _excluded = ["href", "onClick"],
14
17
  _excluded2 = ["children"];
15
- var AvatarGroupItem = function AvatarGroupItem(_ref) {
16
- var avatar = _ref.avatar,
17
- onAvatarClick = _ref.onAvatarClick,
18
- testId = _ref.testId,
19
- index = _ref.index;
18
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
19
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
20
+ var AvatarGroupItem = /*#__PURE__*/(0, _react.forwardRef)(function (props, ref) {
21
+ var avatar = props.avatar,
22
+ onAvatarClick = props.onAvatarClick,
23
+ testId = props.testId,
24
+ index = props.index;
20
25
  var href = avatar.href,
21
26
  onClick = avatar.onClick,
22
27
  rest = (0, _objectWithoutProperties2.default)(avatar, _excluded);
23
- var CustomComponent = function CustomComponent(_ref2) {
24
- var children = _ref2.children,
25
- props = (0, _objectWithoutProperties2.default)(_ref2, _excluded2);
28
+ var itemRef = (0, _useRegisterItemWithFocusManager.default)();
29
+ var CustomComponent = function CustomComponent(_ref) {
30
+ var children = _ref.children,
31
+ props = (0, _objectWithoutProperties2.default)(_ref, _excluded2);
26
32
  // eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props
27
33
  return /*#__PURE__*/_react.default.createElement("span", props, children);
28
34
  };
@@ -37,6 +43,7 @@ var AvatarGroupItem = function AvatarGroupItem(_ref) {
37
43
  var callback = onClick || onAvatarClick;
38
44
  if (href) {
39
45
  return /*#__PURE__*/_react.default.createElement(_menu.LinkItem, {
46
+ ref: (0, _mergeRefs.default)([ref, itemRef]),
40
47
  href: href,
41
48
  target: avatar.target,
42
49
  rel: avatar.target === '_blank' ? 'noopener noreferrer' : undefined,
@@ -49,6 +56,7 @@ var AvatarGroupItem = function AvatarGroupItem(_ref) {
49
56
  }
50
57
  if (typeof callback === 'function') {
51
58
  return /*#__PURE__*/_react.default.createElement(_menu.ButtonItem, {
59
+ ref: (0, _mergeRefs.default)([ref, itemRef]),
52
60
  onClick: function onClick(event) {
53
61
  return callback && callback(event, undefined, index);
54
62
  },
@@ -61,7 +69,7 @@ var AvatarGroupItem = function AvatarGroupItem(_ref) {
61
69
  component: CustomComponent,
62
70
  testId: testId
63
71
  }, avatar.name);
64
- };
72
+ });
65
73
 
66
74
  // eslint-disable-next-line @repo/internal/react/require-jsdoc
67
75
  var _default = AvatarGroupItem;
@@ -17,6 +17,7 @@ var _constants = require("@atlaskit/theme/constants");
17
17
  var _tooltip = _interopRequireDefault(require("@atlaskit/tooltip"));
18
18
  var _avatarGroupItem = _interopRequireDefault(require("./avatar-group-item"));
19
19
  var _grid = _interopRequireDefault(require("./grid"));
20
+ var _focusManager = _interopRequireDefault(require("./internal/components/focus-manager"));
20
21
  var _moreIndicator = _interopRequireDefault(require("./more-indicator"));
21
22
  var _stack = _interopRequireDefault(require("./stack"));
22
23
  var _utils = require("./utils");
@@ -122,7 +123,7 @@ var AvatarGroup = function AvatarGroup(_ref) {
122
123
  shouldFlip: true,
123
124
  zIndex: _constants.layers.modal(),
124
125
  content: function content() {
125
- return (0, _react2.jsx)(_menu.PopupMenuGroup, {
126
+ return (0, _react2.jsx)(_focusManager.default, null, (0, _react2.jsx)(_menu.PopupMenuGroup, {
126
127
  onClick: function onClick(e) {
127
128
  return e.stopPropagation();
128
129
  },
@@ -138,7 +139,7 @@ var AvatarGroup = function AvatarGroup(_ref) {
138
139
  // This index holds the true index,
139
140
  // adding up the index of non-overflowed avatars and overflowed avatars.
140
141
  index + max);
141
- })));
142
+ }))));
142
143
  },
143
144
  trigger: function trigger(triggerProps) {
144
145
  return renderMoreButton(_objectSpread(_objectSpread({}, triggerProps), {}, {
@@ -17,9 +17,9 @@ var listStyles = (0, _react2.css)({
17
17
  // removes default ul styles. Needs !important to override contextual styles in product.
18
18
  display: 'flex',
19
19
  margin: "var(--ds-space-0, 0px)",
20
- // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage-spacing
20
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
21
21
  marginRight: -gutter,
22
- // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage-spacing
22
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
23
23
  marginLeft: -gutter,
24
24
  padding: "var(--ds-space-0, 0px)",
25
25
  justifyContent: 'flex-start',
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _typeof = require("@babel/runtime/helpers/typeof");
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.default = exports.FocusManagerContext = void 0;
9
+ var _react = _interopRequireWildcard(require("react"));
10
+ var _bindEventListener = require("bind-event-listener");
11
+ var _noop = _interopRequireDefault(require("@atlaskit/ds-lib/noop"));
12
+ var _handleFocus = _interopRequireDefault(require("../utiles/handle-focus"));
13
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
14
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
15
+ /**
16
+ *
17
+ *
18
+ * Context provider which maintains the list of focusable elements and a method to
19
+ * register new menu items.
20
+ * This list drives the keyboard navgation of the menu.
21
+ *
22
+ */
23
+ var FocusManagerContext = /*#__PURE__*/(0, _react.createContext)({
24
+ menuItemRefs: [],
25
+ registerRef: _noop.default
26
+ });
27
+
28
+ /**
29
+ * Focus manager logic
30
+ */
31
+ exports.FocusManagerContext = FocusManagerContext;
32
+ var FocusManager = function FocusManager(_ref) {
33
+ var children = _ref.children;
34
+ var menuItemRefs = (0, _react.useRef)([]);
35
+ var registerRef = (0, _react.useCallback)(function (ref) {
36
+ if (ref && !menuItemRefs.current.includes(ref)) {
37
+ menuItemRefs.current.push(ref);
38
+ }
39
+ }, []);
40
+
41
+ // set focus and intentionally rebinding listener and clean up listener on each render
42
+ (0, _react.useEffect)(function () {
43
+ (0, _bindEventListener.bind)(window, {
44
+ type: 'keydown',
45
+ listener: (0, _handleFocus.default)(menuItemRefs.current)
46
+ });
47
+ var unbind = function unbind() {
48
+ (0, _bindEventListener.bind)(window, {
49
+ type: 'keydown',
50
+ listener: (0, _handleFocus.default)(menuItemRefs.current)
51
+ });
52
+ };
53
+ return unbind;
54
+ }, []);
55
+ var contextValue = {
56
+ menuItemRefs: menuItemRefs.current,
57
+ registerRef: registerRef
58
+ };
59
+ return /*#__PURE__*/_react.default.createElement(FocusManagerContext.Provider, {
60
+ value: contextValue
61
+ }, children);
62
+ };
63
+ var _default = FocusManager;
64
+ exports.default = _default;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _react = require("react");
8
+ var _focusManager = require("../components/focus-manager");
9
+ // The refs stored in the context are used to programatically
10
+ // control focus on a user navigates using the keyboard.
11
+ function useRegisterItemWithFocusManager() {
12
+ var _useContext = (0, _react.useContext)(_focusManager.FocusManagerContext),
13
+ registerRef = _useContext.registerRef;
14
+ var itemRef = (0, _react.useRef)(null);
15
+ (0, _react.useEffect)(function () {
16
+ if (itemRef.current !== null) {
17
+ registerRef(itemRef.current);
18
+ }
19
+ }, [registerRef]);
20
+ return itemRef;
21
+ }
22
+ var _default = useRegisterItemWithFocusManager;
23
+ exports.default = _default;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = handleFocus;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _keycodes = require("@atlaskit/ds-lib/keycodes");
10
+ var _actionMap;
11
+ var actionMap = (_actionMap = {}, (0, _defineProperty2.default)(_actionMap, _keycodes.KEY_DOWN, 'next'), (0, _defineProperty2.default)(_actionMap, _keycodes.KEY_UP, 'prev'), (0, _defineProperty2.default)(_actionMap, _keycodes.KEY_HOME, 'first'), (0, _defineProperty2.default)(_actionMap, _keycodes.KEY_END, 'last'), _actionMap);
12
+
13
+ /**
14
+ * currentFocusedIdx + 1 will not work if the next focusable element
15
+ * is disabled. So, we need to iterate through the following menu items
16
+ * to find one that isn't disabled. If all following elements are disabled,
17
+ * return undefined.
18
+ */
19
+ var getNextFocusableElement = function getNextFocusableElement(refs, currentFocusedIdx) {
20
+ while (currentFocusedIdx + 1 < refs.length) {
21
+ var isDisabled = refs[currentFocusedIdx + 1].getAttribute('disabled') !== null;
22
+ if (!isDisabled) {
23
+ return refs[currentFocusedIdx + 1];
24
+ }
25
+ currentFocusedIdx++;
26
+ }
27
+ };
28
+
29
+ /**
30
+ * currentFocusedIdx - 1 will not work if the prev focusable element
31
+ * is disabled. So, we need to iterate through the previous menu items
32
+ * to find one that isn't disabled. If all previous elements are disabled,
33
+ * return undefined.
34
+ */
35
+ var getPrevFocusableElement = function getPrevFocusableElement(refs, currentFocusedIdx) {
36
+ while (currentFocusedIdx > 0) {
37
+ var isDisabled = refs[currentFocusedIdx - 1].getAttribute('disabled') !== null;
38
+ if (!isDisabled) {
39
+ return refs[currentFocusedIdx - 1];
40
+ }
41
+ currentFocusedIdx--;
42
+ }
43
+ };
44
+ function handleFocus(refs) {
45
+ return function (e) {
46
+ var currentFocusedIdx = refs.findIndex(function (el) {
47
+ var _document$activeEleme;
48
+ return (_document$activeEleme = document.activeElement) === null || _document$activeEleme === void 0 ? void 0 : _document$activeEleme.isSameNode(el);
49
+ });
50
+ var action = actionMap[e.key];
51
+ switch (action) {
52
+ case 'next':
53
+ if (currentFocusedIdx < refs.length - 1) {
54
+ e.preventDefault();
55
+ var _nextFocusableElement = getNextFocusableElement(refs, currentFocusedIdx);
56
+ _nextFocusableElement && _nextFocusableElement.focus();
57
+ }
58
+ break;
59
+ case 'prev':
60
+ if (currentFocusedIdx > 0) {
61
+ e.preventDefault();
62
+ var _prevFocusableElement = getPrevFocusableElement(refs, currentFocusedIdx);
63
+ _prevFocusableElement && _prevFocusableElement.focus();
64
+ }
65
+ break;
66
+ case 'first':
67
+ e.preventDefault();
68
+ // Search for first non-disabled element if first element is disabled
69
+ var nextFocusableElement = getNextFocusableElement(refs, -1);
70
+ nextFocusableElement && nextFocusableElement.focus();
71
+ break;
72
+ case 'last':
73
+ e.preventDefault();
74
+ // Search for last non-disabled element if last element is disabled
75
+ var prevFocusableElement = getPrevFocusableElement(refs, refs.length);
76
+ prevFocusableElement && prevFocusableElement.focus();
77
+ break;
78
+ default:
79
+ return;
80
+ }
81
+ };
82
+ }
@@ -46,7 +46,7 @@ var buttonStyles = (0, _react2.css)({
46
46
  '&&': {
47
47
  backgroundColor: "var(--ds-background-neutral, ".concat(_colors.N20, ")"),
48
48
  color: "var(--ds-text, ".concat(_colors.N500, ")"),
49
- // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage-spacing
49
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
50
50
  fontFamily: 'inherit',
51
51
  fontWeight: "var(--ds-font-weight-medium, 500)",
52
52
  '&:hover': {
@@ -16,7 +16,7 @@ var gutter = _avatar.BORDER_WIDTH * 2 + (0, _constants.gridSize)() / 2;
16
16
  var listStyles = (0, _react2.css)({
17
17
  display: 'flex',
18
18
  margin: "var(--ds-space-0, 0px)",
19
- // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage-spacing
19
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
20
20
  marginRight: gutter,
21
21
  padding: "var(--ds-space-0, 0px)",
22
22
  lineHeight: 1,
@@ -24,7 +24,7 @@ var listStyles = (0, _react2.css)({
24
24
  });
25
25
  var listItemStyles = (0, _react2.css)({
26
26
  margin: "var(--ds-space-0, 0px)",
27
- // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage-spacing
27
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
28
28
  marginRight: -gutter
29
29
  });
30
30
  var Stack = function Stack(_ref) {
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/avatar-group",
3
- "version": "9.3.3",
3
+ "version": "9.3.5",
4
4
  "sideEffects": false
5
5
  }
@@ -1,18 +1,22 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
- import React from 'react';
2
+ import React, { forwardRef } from 'react';
3
3
  import Avatar from '@atlaskit/avatar';
4
+ import mergeRefs from '@atlaskit/ds-lib/merge-refs';
4
5
  import { ButtonItem, CustomItem, LinkItem } from '@atlaskit/menu';
5
- const AvatarGroupItem = ({
6
- avatar,
7
- onAvatarClick,
8
- testId,
9
- index
10
- }) => {
6
+ import useRegisterItemWithFocusManager from './internal/hooks/use-register-item-with-focus-manager';
7
+ const AvatarGroupItem = /*#__PURE__*/forwardRef((props, ref) => {
8
+ const {
9
+ avatar,
10
+ onAvatarClick,
11
+ testId,
12
+ index
13
+ } = props;
11
14
  const {
12
15
  href,
13
16
  onClick,
14
17
  ...rest
15
18
  } = avatar;
19
+ const itemRef = useRegisterItemWithFocusManager();
16
20
  const CustomComponent = ({
17
21
  children,
18
22
  ...props
@@ -31,6 +35,7 @@ const AvatarGroupItem = ({
31
35
  const callback = onClick || onAvatarClick;
32
36
  if (href) {
33
37
  return /*#__PURE__*/React.createElement(LinkItem, {
38
+ ref: mergeRefs([ref, itemRef]),
34
39
  href: href,
35
40
  target: avatar.target,
36
41
  rel: avatar.target === '_blank' ? 'noopener noreferrer' : undefined,
@@ -41,6 +46,7 @@ const AvatarGroupItem = ({
41
46
  }
42
47
  if (typeof callback === 'function') {
43
48
  return /*#__PURE__*/React.createElement(ButtonItem, {
49
+ ref: mergeRefs([ref, itemRef]),
44
50
  onClick: event => callback && callback(event, undefined, index),
45
51
  iconBefore: AvatarIcon,
46
52
  testId: testId
@@ -51,7 +57,7 @@ const AvatarGroupItem = ({
51
57
  component: CustomComponent,
52
58
  testId: testId
53
59
  }, avatar.name);
54
- };
60
+ });
55
61
 
56
62
  // eslint-disable-next-line @repo/internal/react/require-jsdoc
57
63
  export default AvatarGroupItem;
@@ -9,6 +9,7 @@ import { layers } from '@atlaskit/theme/constants';
9
9
  import Tooltip from '@atlaskit/tooltip';
10
10
  import AvatarGroupItem from './avatar-group-item';
11
11
  import Grid from './grid';
12
+ import FocusManager from './internal/components/focus-manager';
12
13
  import MoreIndicator from './more-indicator';
13
14
  import Stack from './stack';
14
15
  import { composeUniqueKey } from './utils';
@@ -97,7 +98,7 @@ const AvatarGroup = ({
97
98
  rootBoundary: rootBoundary,
98
99
  shouldFlip: true,
99
100
  zIndex: layers.modal(),
100
- content: () => jsx(PopupMenuGroup, {
101
+ content: () => jsx(FocusManager, null, jsx(PopupMenuGroup, {
101
102
  onClick: e => e.stopPropagation(),
102
103
  minWidth: 250,
103
104
  maxHeight: 300
@@ -109,7 +110,7 @@ const AvatarGroup = ({
109
110
  },
110
111
  // This index holds the true index,
111
112
  // adding up the index of non-overflowed avatars and overflowed avatars.
112
- index + max)))),
113
+ index + max))))),
113
114
  trigger: triggerProps => renderMoreButton({
114
115
  ...triggerProps,
115
116
  onClick: () => setIsOpen(!isOpen)
@@ -10,9 +10,9 @@ const listStyles = css({
10
10
  // removes default ul styles. Needs !important to override contextual styles in product.
11
11
  display: 'flex',
12
12
  margin: "var(--ds-space-0, 0px)",
13
- // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage-spacing
13
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
14
14
  marginRight: -gutter,
15
- // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage-spacing
15
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
16
16
  marginLeft: -gutter,
17
17
  padding: "var(--ds-space-0, 0px)",
18
18
  justifyContent: 'flex-start',
@@ -0,0 +1,54 @@
1
+ import React, { createContext, useCallback, useEffect, useRef } from 'react';
2
+ import { bind } from 'bind-event-listener';
3
+ import __noop from '@atlaskit/ds-lib/noop';
4
+ import handleFocus from '../utiles/handle-focus';
5
+
6
+ /**
7
+ *
8
+ *
9
+ * Context provider which maintains the list of focusable elements and a method to
10
+ * register new menu items.
11
+ * This list drives the keyboard navgation of the menu.
12
+ *
13
+ */
14
+ export const FocusManagerContext = /*#__PURE__*/createContext({
15
+ menuItemRefs: [],
16
+ registerRef: __noop
17
+ });
18
+
19
+ /**
20
+ * Focus manager logic
21
+ */
22
+ const FocusManager = ({
23
+ children
24
+ }) => {
25
+ const menuItemRefs = useRef([]);
26
+ const registerRef = useCallback(ref => {
27
+ if (ref && !menuItemRefs.current.includes(ref)) {
28
+ menuItemRefs.current.push(ref);
29
+ }
30
+ }, []);
31
+
32
+ // set focus and intentionally rebinding listener and clean up listener on each render
33
+ useEffect(() => {
34
+ bind(window, {
35
+ type: 'keydown',
36
+ listener: handleFocus(menuItemRefs.current)
37
+ });
38
+ const unbind = () => {
39
+ bind(window, {
40
+ type: 'keydown',
41
+ listener: handleFocus(menuItemRefs.current)
42
+ });
43
+ };
44
+ return unbind;
45
+ }, []);
46
+ const contextValue = {
47
+ menuItemRefs: menuItemRefs.current,
48
+ registerRef
49
+ };
50
+ return /*#__PURE__*/React.createElement(FocusManagerContext.Provider, {
51
+ value: contextValue
52
+ }, children);
53
+ };
54
+ export default FocusManager;
@@ -0,0 +1,18 @@
1
+ import { useContext, useEffect, useRef } from 'react';
2
+ import { FocusManagerContext } from '../components/focus-manager';
3
+
4
+ // The refs stored in the context are used to programatically
5
+ // control focus on a user navigates using the keyboard.
6
+ function useRegisterItemWithFocusManager() {
7
+ const {
8
+ registerRef
9
+ } = useContext(FocusManagerContext);
10
+ const itemRef = useRef(null);
11
+ useEffect(() => {
12
+ if (itemRef.current !== null) {
13
+ registerRef(itemRef.current);
14
+ }
15
+ }, [registerRef]);
16
+ return itemRef;
17
+ }
18
+ export default useRegisterItemWithFocusManager;
@@ -0,0 +1,78 @@
1
+ import { KEY_DOWN, KEY_END, KEY_HOME, KEY_UP } from '@atlaskit/ds-lib/keycodes';
2
+ const actionMap = {
3
+ [KEY_DOWN]: 'next',
4
+ [KEY_UP]: 'prev',
5
+ [KEY_HOME]: 'first',
6
+ [KEY_END]: 'last'
7
+ };
8
+
9
+ /**
10
+ * currentFocusedIdx + 1 will not work if the next focusable element
11
+ * is disabled. So, we need to iterate through the following menu items
12
+ * to find one that isn't disabled. If all following elements are disabled,
13
+ * return undefined.
14
+ */
15
+ const getNextFocusableElement = (refs, currentFocusedIdx) => {
16
+ while (currentFocusedIdx + 1 < refs.length) {
17
+ const isDisabled = refs[currentFocusedIdx + 1].getAttribute('disabled') !== null;
18
+ if (!isDisabled) {
19
+ return refs[currentFocusedIdx + 1];
20
+ }
21
+ currentFocusedIdx++;
22
+ }
23
+ };
24
+
25
+ /**
26
+ * currentFocusedIdx - 1 will not work if the prev focusable element
27
+ * is disabled. So, we need to iterate through the previous menu items
28
+ * to find one that isn't disabled. If all previous elements are disabled,
29
+ * return undefined.
30
+ */
31
+ const getPrevFocusableElement = (refs, currentFocusedIdx) => {
32
+ while (currentFocusedIdx > 0) {
33
+ const isDisabled = refs[currentFocusedIdx - 1].getAttribute('disabled') !== null;
34
+ if (!isDisabled) {
35
+ return refs[currentFocusedIdx - 1];
36
+ }
37
+ currentFocusedIdx--;
38
+ }
39
+ };
40
+ export default function handleFocus(refs) {
41
+ return e => {
42
+ const currentFocusedIdx = refs.findIndex(el => {
43
+ var _document$activeEleme;
44
+ return (_document$activeEleme = document.activeElement) === null || _document$activeEleme === void 0 ? void 0 : _document$activeEleme.isSameNode(el);
45
+ });
46
+ const action = actionMap[e.key];
47
+ switch (action) {
48
+ case 'next':
49
+ if (currentFocusedIdx < refs.length - 1) {
50
+ e.preventDefault();
51
+ const nextFocusableElement = getNextFocusableElement(refs, currentFocusedIdx);
52
+ nextFocusableElement && nextFocusableElement.focus();
53
+ }
54
+ break;
55
+ case 'prev':
56
+ if (currentFocusedIdx > 0) {
57
+ e.preventDefault();
58
+ const prevFocusableElement = getPrevFocusableElement(refs, currentFocusedIdx);
59
+ prevFocusableElement && prevFocusableElement.focus();
60
+ }
61
+ break;
62
+ case 'first':
63
+ e.preventDefault();
64
+ // Search for first non-disabled element if first element is disabled
65
+ const nextFocusableElement = getNextFocusableElement(refs, -1);
66
+ nextFocusableElement && nextFocusableElement.focus();
67
+ break;
68
+ case 'last':
69
+ e.preventDefault();
70
+ // Search for last non-disabled element if last element is disabled
71
+ const prevFocusableElement = getPrevFocusableElement(refs, refs.length);
72
+ prevFocusableElement && prevFocusableElement.focus();
73
+ break;
74
+ default:
75
+ return;
76
+ }
77
+ };
78
+ }
@@ -34,7 +34,7 @@ const buttonStyles = css({
34
34
  '&&': {
35
35
  backgroundColor: `var(--ds-background-neutral, ${N20})`,
36
36
  color: `var(--ds-text, ${N500})`,
37
- // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage-spacing
37
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
38
38
  fontFamily: 'inherit',
39
39
  fontWeight: "var(--ds-font-weight-medium, 500)",
40
40
  '&:hover': {
@@ -8,7 +8,7 @@ const gutter = BORDER_WIDTH * 2 + gridSize() / 2;
8
8
  const listStyles = css({
9
9
  display: 'flex',
10
10
  margin: "var(--ds-space-0, 0px)",
11
- // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage-spacing
11
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
12
12
  marginRight: gutter,
13
13
  padding: "var(--ds-space-0, 0px)",
14
14
  lineHeight: 1,
@@ -16,7 +16,7 @@ const listStyles = css({
16
16
  });
17
17
  const listItemStyles = css({
18
18
  margin: "var(--ds-space-0, 0px)",
19
- // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage-spacing
19
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
20
20
  marginRight: -gutter
21
21
  });
22
22
  const Stack = ({
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/avatar-group",
3
- "version": "9.3.3",
3
+ "version": "9.3.5",
4
4
  "sideEffects": false
5
5
  }
@@ -2,20 +2,23 @@ import _extends from "@babel/runtime/helpers/extends";
2
2
  import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
3
3
  var _excluded = ["href", "onClick"],
4
4
  _excluded2 = ["children"];
5
- import React from 'react';
5
+ import React, { forwardRef } from 'react';
6
6
  import Avatar from '@atlaskit/avatar';
7
+ import mergeRefs from '@atlaskit/ds-lib/merge-refs';
7
8
  import { ButtonItem, CustomItem, LinkItem } from '@atlaskit/menu';
8
- var AvatarGroupItem = function AvatarGroupItem(_ref) {
9
- var avatar = _ref.avatar,
10
- onAvatarClick = _ref.onAvatarClick,
11
- testId = _ref.testId,
12
- index = _ref.index;
9
+ import useRegisterItemWithFocusManager from './internal/hooks/use-register-item-with-focus-manager';
10
+ var AvatarGroupItem = /*#__PURE__*/forwardRef(function (props, ref) {
11
+ var avatar = props.avatar,
12
+ onAvatarClick = props.onAvatarClick,
13
+ testId = props.testId,
14
+ index = props.index;
13
15
  var href = avatar.href,
14
16
  onClick = avatar.onClick,
15
17
  rest = _objectWithoutProperties(avatar, _excluded);
16
- var CustomComponent = function CustomComponent(_ref2) {
17
- var children = _ref2.children,
18
- props = _objectWithoutProperties(_ref2, _excluded2);
18
+ var itemRef = useRegisterItemWithFocusManager();
19
+ var CustomComponent = function CustomComponent(_ref) {
20
+ var children = _ref.children,
21
+ props = _objectWithoutProperties(_ref, _excluded2);
19
22
  // eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props
20
23
  return /*#__PURE__*/React.createElement("span", props, children);
21
24
  };
@@ -30,6 +33,7 @@ var AvatarGroupItem = function AvatarGroupItem(_ref) {
30
33
  var callback = onClick || onAvatarClick;
31
34
  if (href) {
32
35
  return /*#__PURE__*/React.createElement(LinkItem, {
36
+ ref: mergeRefs([ref, itemRef]),
33
37
  href: href,
34
38
  target: avatar.target,
35
39
  rel: avatar.target === '_blank' ? 'noopener noreferrer' : undefined,
@@ -42,6 +46,7 @@ var AvatarGroupItem = function AvatarGroupItem(_ref) {
42
46
  }
43
47
  if (typeof callback === 'function') {
44
48
  return /*#__PURE__*/React.createElement(ButtonItem, {
49
+ ref: mergeRefs([ref, itemRef]),
45
50
  onClick: function onClick(event) {
46
51
  return callback && callback(event, undefined, index);
47
52
  },
@@ -54,7 +59,7 @@ var AvatarGroupItem = function AvatarGroupItem(_ref) {
54
59
  component: CustomComponent,
55
60
  testId: testId
56
61
  }, avatar.name);
57
- };
62
+ });
58
63
 
59
64
  // eslint-disable-next-line @repo/internal/react/require-jsdoc
60
65
  export default AvatarGroupItem;
@@ -13,6 +13,7 @@ import { layers } from '@atlaskit/theme/constants';
13
13
  import Tooltip from '@atlaskit/tooltip';
14
14
  import AvatarGroupItem from './avatar-group-item';
15
15
  import Grid from './grid';
16
+ import FocusManager from './internal/components/focus-manager';
16
17
  import MoreIndicator from './more-indicator';
17
18
  import Stack from './stack';
18
19
  import { composeUniqueKey } from './utils';
@@ -116,7 +117,7 @@ var AvatarGroup = function AvatarGroup(_ref) {
116
117
  shouldFlip: true,
117
118
  zIndex: layers.modal(),
118
119
  content: function content() {
119
- return jsx(PopupMenuGroup, {
120
+ return jsx(FocusManager, null, jsx(PopupMenuGroup, {
120
121
  onClick: function onClick(e) {
121
122
  return e.stopPropagation();
122
123
  },
@@ -132,7 +133,7 @@ var AvatarGroup = function AvatarGroup(_ref) {
132
133
  // This index holds the true index,
133
134
  // adding up the index of non-overflowed avatars and overflowed avatars.
134
135
  index + max);
135
- })));
136
+ }))));
136
137
  },
137
138
  trigger: function trigger(triggerProps) {
138
139
  return renderMoreButton(_objectSpread(_objectSpread({}, triggerProps), {}, {
@@ -10,9 +10,9 @@ var listStyles = css({
10
10
  // removes default ul styles. Needs !important to override contextual styles in product.
11
11
  display: 'flex',
12
12
  margin: "var(--ds-space-0, 0px)",
13
- // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage-spacing
13
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
14
14
  marginRight: -gutter,
15
- // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage-spacing
15
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
16
16
  marginLeft: -gutter,
17
17
  padding: "var(--ds-space-0, 0px)",
18
18
  justifyContent: 'flex-start',
@@ -0,0 +1,53 @@
1
+ import React, { createContext, useCallback, useEffect, useRef } from 'react';
2
+ import { bind } from 'bind-event-listener';
3
+ import __noop from '@atlaskit/ds-lib/noop';
4
+ import handleFocus from '../utiles/handle-focus';
5
+
6
+ /**
7
+ *
8
+ *
9
+ * Context provider which maintains the list of focusable elements and a method to
10
+ * register new menu items.
11
+ * This list drives the keyboard navgation of the menu.
12
+ *
13
+ */
14
+ export var FocusManagerContext = /*#__PURE__*/createContext({
15
+ menuItemRefs: [],
16
+ registerRef: __noop
17
+ });
18
+
19
+ /**
20
+ * Focus manager logic
21
+ */
22
+ var FocusManager = function FocusManager(_ref) {
23
+ var children = _ref.children;
24
+ var menuItemRefs = useRef([]);
25
+ var registerRef = useCallback(function (ref) {
26
+ if (ref && !menuItemRefs.current.includes(ref)) {
27
+ menuItemRefs.current.push(ref);
28
+ }
29
+ }, []);
30
+
31
+ // set focus and intentionally rebinding listener and clean up listener on each render
32
+ useEffect(function () {
33
+ bind(window, {
34
+ type: 'keydown',
35
+ listener: handleFocus(menuItemRefs.current)
36
+ });
37
+ var unbind = function unbind() {
38
+ bind(window, {
39
+ type: 'keydown',
40
+ listener: handleFocus(menuItemRefs.current)
41
+ });
42
+ };
43
+ return unbind;
44
+ }, []);
45
+ var contextValue = {
46
+ menuItemRefs: menuItemRefs.current,
47
+ registerRef: registerRef
48
+ };
49
+ return /*#__PURE__*/React.createElement(FocusManagerContext.Provider, {
50
+ value: contextValue
51
+ }, children);
52
+ };
53
+ export default FocusManager;
@@ -0,0 +1,17 @@
1
+ import { useContext, useEffect, useRef } from 'react';
2
+ import { FocusManagerContext } from '../components/focus-manager';
3
+
4
+ // The refs stored in the context are used to programatically
5
+ // control focus on a user navigates using the keyboard.
6
+ function useRegisterItemWithFocusManager() {
7
+ var _useContext = useContext(FocusManagerContext),
8
+ registerRef = _useContext.registerRef;
9
+ var itemRef = useRef(null);
10
+ useEffect(function () {
11
+ if (itemRef.current !== null) {
12
+ registerRef(itemRef.current);
13
+ }
14
+ }, [registerRef]);
15
+ return itemRef;
16
+ }
17
+ export default useRegisterItemWithFocusManager;
@@ -0,0 +1,75 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ var _actionMap;
3
+ import { KEY_DOWN, KEY_END, KEY_HOME, KEY_UP } from '@atlaskit/ds-lib/keycodes';
4
+ var actionMap = (_actionMap = {}, _defineProperty(_actionMap, KEY_DOWN, 'next'), _defineProperty(_actionMap, KEY_UP, 'prev'), _defineProperty(_actionMap, KEY_HOME, 'first'), _defineProperty(_actionMap, KEY_END, 'last'), _actionMap);
5
+
6
+ /**
7
+ * currentFocusedIdx + 1 will not work if the next focusable element
8
+ * is disabled. So, we need to iterate through the following menu items
9
+ * to find one that isn't disabled. If all following elements are disabled,
10
+ * return undefined.
11
+ */
12
+ var getNextFocusableElement = function getNextFocusableElement(refs, currentFocusedIdx) {
13
+ while (currentFocusedIdx + 1 < refs.length) {
14
+ var isDisabled = refs[currentFocusedIdx + 1].getAttribute('disabled') !== null;
15
+ if (!isDisabled) {
16
+ return refs[currentFocusedIdx + 1];
17
+ }
18
+ currentFocusedIdx++;
19
+ }
20
+ };
21
+
22
+ /**
23
+ * currentFocusedIdx - 1 will not work if the prev focusable element
24
+ * is disabled. So, we need to iterate through the previous menu items
25
+ * to find one that isn't disabled. If all previous elements are disabled,
26
+ * return undefined.
27
+ */
28
+ var getPrevFocusableElement = function getPrevFocusableElement(refs, currentFocusedIdx) {
29
+ while (currentFocusedIdx > 0) {
30
+ var isDisabled = refs[currentFocusedIdx - 1].getAttribute('disabled') !== null;
31
+ if (!isDisabled) {
32
+ return refs[currentFocusedIdx - 1];
33
+ }
34
+ currentFocusedIdx--;
35
+ }
36
+ };
37
+ export default function handleFocus(refs) {
38
+ return function (e) {
39
+ var currentFocusedIdx = refs.findIndex(function (el) {
40
+ var _document$activeEleme;
41
+ return (_document$activeEleme = document.activeElement) === null || _document$activeEleme === void 0 ? void 0 : _document$activeEleme.isSameNode(el);
42
+ });
43
+ var action = actionMap[e.key];
44
+ switch (action) {
45
+ case 'next':
46
+ if (currentFocusedIdx < refs.length - 1) {
47
+ e.preventDefault();
48
+ var _nextFocusableElement = getNextFocusableElement(refs, currentFocusedIdx);
49
+ _nextFocusableElement && _nextFocusableElement.focus();
50
+ }
51
+ break;
52
+ case 'prev':
53
+ if (currentFocusedIdx > 0) {
54
+ e.preventDefault();
55
+ var _prevFocusableElement = getPrevFocusableElement(refs, currentFocusedIdx);
56
+ _prevFocusableElement && _prevFocusableElement.focus();
57
+ }
58
+ break;
59
+ case 'first':
60
+ e.preventDefault();
61
+ // Search for first non-disabled element if first element is disabled
62
+ var nextFocusableElement = getNextFocusableElement(refs, -1);
63
+ nextFocusableElement && nextFocusableElement.focus();
64
+ break;
65
+ case 'last':
66
+ e.preventDefault();
67
+ // Search for last non-disabled element if last element is disabled
68
+ var prevFocusableElement = getPrevFocusableElement(refs, refs.length);
69
+ prevFocusableElement && prevFocusableElement.focus();
70
+ break;
71
+ default:
72
+ return;
73
+ }
74
+ };
75
+ }
@@ -36,7 +36,7 @@ var buttonStyles = css({
36
36
  '&&': {
37
37
  backgroundColor: "var(--ds-background-neutral, ".concat(N20, ")"),
38
38
  color: "var(--ds-text, ".concat(N500, ")"),
39
- // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage-spacing
39
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
40
40
  fontFamily: 'inherit',
41
41
  fontWeight: "var(--ds-font-weight-medium, 500)",
42
42
  '&:hover': {
@@ -8,7 +8,7 @@ var gutter = BORDER_WIDTH * 2 + gridSize() / 2;
8
8
  var listStyles = css({
9
9
  display: 'flex',
10
10
  margin: "var(--ds-space-0, 0px)",
11
- // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage-spacing
11
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
12
12
  marginRight: gutter,
13
13
  padding: "var(--ds-space-0, 0px)",
14
14
  lineHeight: 1,
@@ -16,7 +16,7 @@ var listStyles = css({
16
16
  });
17
17
  var listItemStyles = css({
18
18
  margin: "var(--ds-space-0, 0px)",
19
- // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage-spacing
19
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
20
20
  marginRight: -gutter
21
21
  });
22
22
  var Stack = function Stack(_ref) {
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/avatar-group",
3
- "version": "9.3.3",
3
+ "version": "9.3.5",
4
4
  "sideEffects": false
5
5
  }
@@ -1,4 +1,4 @@
1
- import { FC } from 'react';
1
+ import React from 'react';
2
2
  import { AvatarProps, onAvatarClickHandler } from './types';
3
3
  export interface AvatarGroupItemProps {
4
4
  avatar: AvatarProps;
@@ -8,5 +8,5 @@ export interface AvatarGroupItemProps {
8
8
  onAvatarClick?: onAvatarClickHandler;
9
9
  testId?: string;
10
10
  }
11
- declare const AvatarGroupItem: FC<AvatarGroupItemProps>;
11
+ declare const AvatarGroupItem: React.ForwardRefExoticComponent<AvatarGroupItemProps & React.RefAttributes<HTMLElement>>;
12
12
  export default AvatarGroupItem;
@@ -86,14 +86,14 @@ export interface AvatarGroupProps {
86
86
  */
87
87
  isTooltipDisabled?: boolean;
88
88
  /**
89
- Text to be used as aria-label for the list of avatars.
90
- Screen reader announcement with default label, which is `avatar group`, is `list, avatar group, X items`.
91
-
92
- The label should describe the `AvatarGroup`'s entities, for instance:
93
- - `label="team members"`, screen reader announcement would be `list team members, X items`
94
- - `label="reviewers"` screen reader announcement would be `list reviewers, X items`
95
-
96
- When there are several AvatarGroups on the page you should use a unique label to let users distinguish different lists.
89
+ * Text to be used as aria-label for the list of avatars.
90
+ * Screen reader announcement with default label, which is `avatar group`, is `list, avatar group, X items`.
91
+ *
92
+ * The label should describe the `AvatarGroup`'s entities, for instance:
93
+ * - `label="team members"`, screen reader announcement would be `list team members, X items`
94
+ * - `label="reviewers"` screen reader announcement would be `list reviewers, X items`
95
+ *
96
+ * When there are several AvatarGroups on the page you should use a unique label to let users distinguish different lists.
97
97
  */
98
98
  label?: string;
99
99
  }
@@ -0,0 +1,21 @@
1
+ import React, { FC, ReactNode } from 'react';
2
+ import { FocusableElement } from '../../types';
3
+ /**
4
+ *
5
+ *
6
+ * Context provider which maintains the list of focusable elements and a method to
7
+ * register new menu items.
8
+ * This list drives the keyboard navgation of the menu.
9
+ *
10
+ */
11
+ export declare const FocusManagerContext: React.Context<{
12
+ menuItemRefs: FocusableElement[];
13
+ registerRef: (ref: FocusableElement) => void;
14
+ }>;
15
+ /**
16
+ * Focus manager logic
17
+ */
18
+ declare const FocusManager: FC<{
19
+ children: ReactNode;
20
+ }>;
21
+ export default FocusManager;
@@ -0,0 +1,4 @@
1
+ /// <reference types="react" />
2
+ import { FocusableElement } from '../../types';
3
+ declare function useRegisterItemWithFocusManager(): import("react").RefObject<FocusableElement>;
4
+ export default useRegisterItemWithFocusManager;
@@ -0,0 +1,2 @@
1
+ import { FocusableElement } from '../../types';
2
+ export default function handleFocus(refs: Array<FocusableElement>): (e: KeyboardEvent) => void;
@@ -18,3 +18,5 @@ export interface AvatarGroupOverrides {
18
18
  };
19
19
  }
20
20
  export type onAvatarClickHandler = (event: React.MouseEvent, analyticsEvent: AnalyticsEvent | undefined, index: number) => void;
21
+ export type FocusableElement = HTMLAnchorElement | HTMLButtonElement;
22
+ export type Action = 'next' | 'prev' | 'first' | 'last';
@@ -1,4 +1,4 @@
1
- import { FC } from 'react';
1
+ import React from 'react';
2
2
  import { AvatarProps, onAvatarClickHandler } from './types';
3
3
  export interface AvatarGroupItemProps {
4
4
  avatar: AvatarProps;
@@ -8,5 +8,5 @@ export interface AvatarGroupItemProps {
8
8
  onAvatarClick?: onAvatarClickHandler;
9
9
  testId?: string;
10
10
  }
11
- declare const AvatarGroupItem: FC<AvatarGroupItemProps>;
11
+ declare const AvatarGroupItem: React.ForwardRefExoticComponent<AvatarGroupItemProps & React.RefAttributes<HTMLElement>>;
12
12
  export default AvatarGroupItem;
@@ -86,14 +86,14 @@ export interface AvatarGroupProps {
86
86
  */
87
87
  isTooltipDisabled?: boolean;
88
88
  /**
89
- Text to be used as aria-label for the list of avatars.
90
- Screen reader announcement with default label, which is `avatar group`, is `list, avatar group, X items`.
91
-
92
- The label should describe the `AvatarGroup`'s entities, for instance:
93
- - `label="team members"`, screen reader announcement would be `list team members, X items`
94
- - `label="reviewers"` screen reader announcement would be `list reviewers, X items`
95
-
96
- When there are several AvatarGroups on the page you should use a unique label to let users distinguish different lists.
89
+ * Text to be used as aria-label for the list of avatars.
90
+ * Screen reader announcement with default label, which is `avatar group`, is `list, avatar group, X items`.
91
+ *
92
+ * The label should describe the `AvatarGroup`'s entities, for instance:
93
+ * - `label="team members"`, screen reader announcement would be `list team members, X items`
94
+ * - `label="reviewers"` screen reader announcement would be `list reviewers, X items`
95
+ *
96
+ * When there are several AvatarGroups on the page you should use a unique label to let users distinguish different lists.
97
97
  */
98
98
  label?: string;
99
99
  }
@@ -0,0 +1,21 @@
1
+ import React, { FC, ReactNode } from 'react';
2
+ import { FocusableElement } from '../../types';
3
+ /**
4
+ *
5
+ *
6
+ * Context provider which maintains the list of focusable elements and a method to
7
+ * register new menu items.
8
+ * This list drives the keyboard navgation of the menu.
9
+ *
10
+ */
11
+ export declare const FocusManagerContext: React.Context<{
12
+ menuItemRefs: FocusableElement[];
13
+ registerRef: (ref: FocusableElement) => void;
14
+ }>;
15
+ /**
16
+ * Focus manager logic
17
+ */
18
+ declare const FocusManager: FC<{
19
+ children: ReactNode;
20
+ }>;
21
+ export default FocusManager;
@@ -0,0 +1,4 @@
1
+ /// <reference types="react" />
2
+ import { FocusableElement } from '../../types';
3
+ declare function useRegisterItemWithFocusManager(): import("react").RefObject<FocusableElement>;
4
+ export default useRegisterItemWithFocusManager;
@@ -0,0 +1,2 @@
1
+ import { FocusableElement } from '../../types';
2
+ export default function handleFocus(refs: Array<FocusableElement>): (e: KeyboardEvent) => void;
@@ -18,3 +18,5 @@ export interface AvatarGroupOverrides {
18
18
  };
19
19
  }
20
20
  export type onAvatarClickHandler = (event: React.MouseEvent, analyticsEvent: AnalyticsEvent | undefined, index: number) => void;
21
+ export type FocusableElement = HTMLAnchorElement | HTMLButtonElement;
22
+ export type Action = 'next' | 'prev' | 'first' | 'last';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/avatar-group",
3
- "version": "9.3.3",
3
+ "version": "9.3.5",
4
4
  "description": "An avatar group displays a number of avatars grouped together in a stack or grid.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -24,7 +24,7 @@
24
24
  "atlaskit:src": "src/index.tsx",
25
25
  "atlassian": {
26
26
  "team": "Design System Team",
27
- "releaseModel": "scheduled",
27
+ "releaseModel": "continuous",
28
28
  "website": {
29
29
  "name": "Avatar group",
30
30
  "category": "Components"
@@ -35,35 +35,29 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@atlaskit/avatar": "^21.3.0",
38
- "@atlaskit/menu": "^1.7.0",
39
- "@atlaskit/popup": "^1.6.0",
38
+ "@atlaskit/ds-lib": "^2.1.0",
39
+ "@atlaskit/menu": "^1.9.0",
40
+ "@atlaskit/popup": "^1.8.0",
40
41
  "@atlaskit/theme": "^12.5.0",
41
- "@atlaskit/tokens": "^1.5.0",
42
+ "@atlaskit/tokens": "^1.11.0",
42
43
  "@atlaskit/tooltip": "^17.8.0",
43
44
  "@babel/runtime": "^7.0.0",
44
- "@emotion/react": "^11.7.1"
45
+ "@emotion/react": "^11.7.1",
46
+ "bind-event-listener": "^2.1.1"
45
47
  },
46
48
  "peerDependencies": {
47
49
  "react": "^16.8.0"
48
50
  },
49
51
  "devDependencies": {
52
+ "@af/accessibility-testing": "*",
50
53
  "@atlaskit/analytics-next": "^9.1.0",
51
- "@atlaskit/button": "^16.7.0",
52
- "@atlaskit/code": "^14.6.0",
53
- "@atlaskit/docs": "*",
54
54
  "@atlaskit/ds-lib": "^2.2.0",
55
- "@atlaskit/form": "^8.11.0",
56
- "@atlaskit/icon": "^21.12.0",
57
- "@atlaskit/modal-dialog": "^12.5.0",
58
- "@atlaskit/section-message": "^6.4.0",
59
55
  "@atlaskit/ssr": "*",
60
- "@atlaskit/toggle": "^12.6.0",
61
56
  "@atlaskit/visual-regression": "*",
62
57
  "@atlaskit/webdriver-runner": "*",
63
58
  "@atlassian/atlassian-frontend-prettier-config-1.0.1": "npm:@atlassian/atlassian-frontend-prettier-config@1.0.1",
64
59
  "@emotion/styled": "^11.0.0",
65
60
  "@testing-library/react": "^12.1.5",
66
- "jest-axe": "^4.0.0",
67
61
  "lodash": "^4.17.21",
68
62
  "react-dom": "^16.8.0",
69
63
  "typescript": "~4.9.5",