@atlaskit/avatar-group 9.3.5 → 9.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @atlaskit/avatar-group
2
2
 
3
+ ## 9.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`da7b6be2540`](https://bitbucket.org/atlassian/atlassian-frontend/commits/da7b6be2540) - Fixed keyboard support for the interactive element. Changed span to button for avatar-group
8
+
9
+ ## 9.3.6
10
+
11
+ ### Patch Changes
12
+
13
+ - [`71b58da4e00`](https://bitbucket.org/atlassian/atlassian-frontend/commits/71b58da4e00) - set focus to the first avatar when popup is open
14
+
3
15
  ## 9.3.5
4
16
 
5
17
  ### Patch Changes
@@ -29,8 +29,13 @@ var AvatarGroupItem = /*#__PURE__*/(0, _react.forwardRef)(function (props, ref)
29
29
  var CustomComponent = function CustomComponent(_ref) {
30
30
  var children = _ref.children,
31
31
  props = (0, _objectWithoutProperties2.default)(_ref, _excluded2);
32
- // eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props
33
- return /*#__PURE__*/_react.default.createElement("span", props, children);
32
+ return (
33
+ /*#__PURE__*/
34
+ // eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props
35
+ _react.default.createElement("button", (0, _extends2.default)({
36
+ type: "button"
37
+ }, props), children)
38
+ );
34
39
  };
35
40
  var AvatarIcon = /*#__PURE__*/_react.default.createElement(_avatar.default, (0, _extends2.default)({}, rest, {
36
41
  testId: testId && "".concat(testId, "--avatar"),
@@ -10,7 +10,11 @@ var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")
10
10
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
11
  var _react = require("react");
12
12
  var _react2 = require("@emotion/react");
13
+ var _bindEventListener = require("bind-event-listener");
13
14
  var _avatar = _interopRequireDefault(require("@atlaskit/avatar"));
15
+ var _keycodes = require("@atlaskit/ds-lib/keycodes");
16
+ var _noop = _interopRequireDefault(require("@atlaskit/ds-lib/noop"));
17
+ var _useFocusEvent = _interopRequireDefault(require("@atlaskit/ds-lib/use-focus-event"));
14
18
  var _menu = require("@atlaskit/menu");
15
19
  var _popup = _interopRequireDefault(require("@atlaskit/popup"));
16
20
  var _constants = require("@atlaskit/theme/constants");
@@ -18,6 +22,7 @@ var _tooltip = _interopRequireDefault(require("@atlaskit/tooltip"));
18
22
  var _avatarGroupItem = _interopRequireDefault(require("./avatar-group-item"));
19
23
  var _grid = _interopRequireDefault(require("./grid"));
20
24
  var _focusManager = _interopRequireDefault(require("./internal/components/focus-manager"));
25
+ var _popupAvatarGroup = _interopRequireDefault(require("./internal/components/popup-avatar-group"));
21
26
  var _moreIndicator = _interopRequireDefault(require("./more-indicator"));
22
27
  var _stack = _interopRequireDefault(require("./stack"));
23
28
  var _utils = require("./utils");
@@ -79,11 +84,73 @@ var AvatarGroup = function AvatarGroup(_ref) {
79
84
  tooltipPosition = _ref$tooltipPosition === void 0 ? 'bottom' : _ref$tooltipPosition;
80
85
  var _useState = (0, _react.useState)(false),
81
86
  _useState2 = (0, _slicedToArray2.default)(_useState, 2),
82
- isOpen = _useState2[0],
83
- setIsOpen = _useState2[1];
87
+ isTriggeredUsingKeyboard = _useState2[0],
88
+ setTriggeredUsingKeyboard = _useState2[1];
89
+ var _useState3 = (0, _react.useState)(false),
90
+ _useState4 = (0, _slicedToArray2.default)(_useState3, 2),
91
+ isOpen = _useState4[0],
92
+ setIsOpen = _useState4[1];
84
93
  var onClose = (0, _react.useCallback)(function () {
85
94
  return setIsOpen(false);
86
95
  }, []);
96
+ var handleTriggerClicked = (0, _react.useCallback)(function (event) {
97
+ var clientX = event.clientX,
98
+ clientY = event.clientY,
99
+ type = event.type;
100
+ // Hitting enter/space is registered as a click with both clientX and clientY === 0
101
+ if (type === 'keydown' || clientX === 0 || clientY === 0) {
102
+ setTriggeredUsingKeyboard(true);
103
+ }
104
+ setIsOpen(function (isOpen) {
105
+ return !isOpen;
106
+ });
107
+ }, []);
108
+ var _useFocus = (0, _useFocusEvent.default)(),
109
+ isFocused = _useFocus.isFocused,
110
+ bindFocus = _useFocus.bindFocus;
111
+
112
+ // When a trigger is focused, we want to open the popup
113
+ // the user presses the DownArrow
114
+ (0, _react.useEffect)(function () {
115
+ // Set initial value if popup is closed
116
+ if (!isOpen) {
117
+ setTriggeredUsingKeyboard(false);
118
+ }
119
+
120
+ // Only need to listen for keydown when focused
121
+ if (!isFocused) {
122
+ return _noop.default;
123
+ }
124
+
125
+ // Being safe: we don't want to open the popup if it is already open
126
+ // Note: This shouldn't happen as the trigger should not be able to get focus
127
+ if (isOpen) {
128
+ return _noop.default;
129
+ }
130
+ (0, _bindEventListener.bind)(window, {
131
+ type: 'keydown',
132
+ listener: function openOnKeyDown(e) {
133
+ if (e.key === _keycodes.KEY_DOWN) {
134
+ // prevent page scroll
135
+ e.preventDefault();
136
+ handleTriggerClicked(e);
137
+ }
138
+ }
139
+ });
140
+ var unbind = function unbind() {
141
+ (0, _bindEventListener.bind)(window, {
142
+ type: 'keydown',
143
+ listener: function openOnKeyDown(e) {
144
+ if (e.key === _keycodes.KEY_DOWN) {
145
+ // prevent page scroll
146
+ e.preventDefault();
147
+ handleTriggerClicked(e);
148
+ }
149
+ }
150
+ });
151
+ };
152
+ return unbind;
153
+ }, [isFocused, isOpen, handleTriggerClicked]);
87
154
  function renderMoreDropdown(max, total) {
88
155
  if (total <= max) {
89
156
  return null;
@@ -122,13 +189,15 @@ var AvatarGroup = function AvatarGroup(_ref) {
122
189
  rootBoundary: rootBoundary,
123
190
  shouldFlip: true,
124
191
  zIndex: _constants.layers.modal(),
125
- content: function content() {
126
- return (0, _react2.jsx)(_focusManager.default, null, (0, _react2.jsx)(_menu.PopupMenuGroup, {
192
+ content: function content(_ref2) {
193
+ var setInitialFocusRef = _ref2.setInitialFocusRef;
194
+ return (0, _react2.jsx)(_focusManager.default, null, (0, _react2.jsx)(_popupAvatarGroup.default, {
127
195
  onClick: function onClick(e) {
128
196
  return e.stopPropagation();
129
197
  },
130
198
  minWidth: 250,
131
- maxHeight: 300
199
+ maxHeight: 300,
200
+ setInitialFocusRef: isTriggeredUsingKeyboard ? setInitialFocusRef : undefined
132
201
  }, (0, _react2.jsx)(_menu.Section, null, data.slice(max).map(function (avatar, index) {
133
202
  return getOverrides(overrides).AvatarGroupItem.render(_avatarGroupItem.default, {
134
203
  avatar: avatar,
@@ -142,10 +211,8 @@ var AvatarGroup = function AvatarGroup(_ref) {
142
211
  }))));
143
212
  },
144
213
  trigger: function trigger(triggerProps) {
145
- return renderMoreButton(_objectSpread(_objectSpread({}, triggerProps), {}, {
146
- onClick: function onClick() {
147
- return setIsOpen(!isOpen);
148
- }
214
+ return renderMoreButton(_objectSpread(_objectSpread(_objectSpread({}, triggerProps), bindFocus), {}, {
215
+ onClick: handleTriggerClicked
149
216
  }));
150
217
  },
151
218
  testId: testId && "".concat(testId, "--overflow-menu")
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
9
+ var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
10
+ var _react = require("react");
11
+ var _react2 = require("@emotion/react");
12
+ var _menu = require("@atlaskit/menu");
13
+ var _focusManager = require("./focus-manager");
14
+ var _excluded = ["maxWidth", "minWidth", "setInitialFocusRef"];
15
+ /** @jsx jsx */
16
+ /**
17
+ * It sets focus to the first avatar when popup is open.
18
+ */
19
+ var PopupAvatarGroup = function PopupAvatarGroup(_ref) {
20
+ var _ref$maxWidth = _ref.maxWidth,
21
+ maxWidth = _ref$maxWidth === void 0 ? 800 : _ref$maxWidth,
22
+ _ref$minWidth = _ref.minWidth,
23
+ minWidth = _ref$minWidth === void 0 ? 320 : _ref$minWidth,
24
+ setInitialFocusRef = _ref.setInitialFocusRef,
25
+ rest = (0, _objectWithoutProperties2.default)(_ref, _excluded);
26
+ var _useContext = (0, _react.useContext)(_focusManager.FocusManagerContext),
27
+ menuItemRefs = _useContext.menuItemRefs;
28
+ (0, _react.useEffect)(function () {
29
+ setInitialFocusRef === null || setInitialFocusRef === void 0 ? void 0 : setInitialFocusRef(menuItemRefs[0]);
30
+ }, [menuItemRefs, setInitialFocusRef]);
31
+ return (
32
+ // eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props
33
+ (0, _react2.jsx)(_menu.MenuGroup, (0, _extends2.default)({
34
+ maxWidth: maxWidth,
35
+ minWidth: minWidth
36
+ }, rest))
37
+ );
38
+ };
39
+ var _default = PopupAvatarGroup;
40
+ exports.default = _default;
@@ -25,7 +25,7 @@ var FONT_SIZE = {
25
25
  xxlarge: '16px'
26
26
  };
27
27
  var buttonActiveStyles = (0, _react2.css)({
28
- // eslint-disable-next-line @repo/internal/styles/no-nested-styles
28
+ // eslint-disable-next-line @atlaskit/design-system/no-nested-styles
29
29
  '&&': {
30
30
  backgroundColor: "var(--ds-background-selected, ".concat(_colors.B50, ")"),
31
31
  boxShadow: "0 0 0 ".concat(_avatar.BORDER_WIDTH, "px ", "var(--ds-border-selected, ".concat(_colors.B300, ")")),
@@ -42,7 +42,7 @@ var buttonActiveStyles = (0, _react2.css)({
42
42
  }
43
43
  });
44
44
  var buttonStyles = (0, _react2.css)({
45
- // eslint-disable-next-line @repo/internal/styles/no-nested-styles
45
+ // eslint-disable-next-line @atlaskit/design-system/no-nested-styles
46
46
  '&&': {
47
47
  backgroundColor: "var(--ds-background-neutral, ".concat(_colors.N20, ")"),
48
48
  color: "var(--ds-text, ".concat(_colors.N500, ")"),
@@ -21,8 +21,13 @@ const AvatarGroupItem = /*#__PURE__*/forwardRef((props, ref) => {
21
21
  children,
22
22
  ...props
23
23
  }) => {
24
- // eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props
25
- return /*#__PURE__*/React.createElement("span", props, children);
24
+ return (
25
+ /*#__PURE__*/
26
+ // eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props
27
+ React.createElement("button", _extends({
28
+ type: "button"
29
+ }, props), children)
30
+ );
26
31
  };
27
32
  const AvatarIcon = /*#__PURE__*/React.createElement(Avatar, _extends({}, rest, {
28
33
  testId: testId && `${testId}--avatar`,
@@ -1,15 +1,20 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
2
  /** @jsx jsx */
3
- import { useCallback, useState } from 'react';
3
+ import { useCallback, useEffect, useState } from 'react';
4
4
  import { jsx } from '@emotion/react';
5
+ import { bind } from 'bind-event-listener';
5
6
  import Avatar from '@atlaskit/avatar';
6
- import { PopupMenuGroup, Section } from '@atlaskit/menu';
7
+ import { KEY_DOWN } from '@atlaskit/ds-lib/keycodes';
8
+ import noop from '@atlaskit/ds-lib/noop';
9
+ import useFocus from '@atlaskit/ds-lib/use-focus-event';
10
+ import { Section } from '@atlaskit/menu';
7
11
  import Popup from '@atlaskit/popup';
8
12
  import { layers } from '@atlaskit/theme/constants';
9
13
  import Tooltip from '@atlaskit/tooltip';
10
14
  import AvatarGroupItem from './avatar-group-item';
11
15
  import Grid from './grid';
12
16
  import FocusManager from './internal/components/focus-manager';
17
+ import PopupAvatarGroup from './internal/components/popup-avatar-group';
13
18
  import MoreIndicator from './more-indicator';
14
19
  import Stack from './stack';
15
20
  import { composeUniqueKey } from './utils';
@@ -60,8 +65,68 @@ const AvatarGroup = ({
60
65
  label = 'avatar group',
61
66
  tooltipPosition = 'bottom'
62
67
  }) => {
68
+ const [isTriggeredUsingKeyboard, setTriggeredUsingKeyboard] = useState(false);
63
69
  const [isOpen, setIsOpen] = useState(false);
64
70
  const onClose = useCallback(() => setIsOpen(false), []);
71
+ const handleTriggerClicked = useCallback(event => {
72
+ const {
73
+ clientX,
74
+ clientY,
75
+ type
76
+ } = event;
77
+ // Hitting enter/space is registered as a click with both clientX and clientY === 0
78
+ if (type === 'keydown' || clientX === 0 || clientY === 0) {
79
+ setTriggeredUsingKeyboard(true);
80
+ }
81
+ setIsOpen(isOpen => !isOpen);
82
+ }, []);
83
+ const {
84
+ isFocused,
85
+ bindFocus
86
+ } = useFocus();
87
+
88
+ // When a trigger is focused, we want to open the popup
89
+ // the user presses the DownArrow
90
+ useEffect(() => {
91
+ // Set initial value if popup is closed
92
+ if (!isOpen) {
93
+ setTriggeredUsingKeyboard(false);
94
+ }
95
+
96
+ // Only need to listen for keydown when focused
97
+ if (!isFocused) {
98
+ return noop;
99
+ }
100
+
101
+ // Being safe: we don't want to open the popup if it is already open
102
+ // Note: This shouldn't happen as the trigger should not be able to get focus
103
+ if (isOpen) {
104
+ return noop;
105
+ }
106
+ bind(window, {
107
+ type: 'keydown',
108
+ listener: function openOnKeyDown(e) {
109
+ if (e.key === KEY_DOWN) {
110
+ // prevent page scroll
111
+ e.preventDefault();
112
+ handleTriggerClicked(e);
113
+ }
114
+ }
115
+ });
116
+ const unbind = () => {
117
+ bind(window, {
118
+ type: 'keydown',
119
+ listener: function openOnKeyDown(e) {
120
+ if (e.key === KEY_DOWN) {
121
+ // prevent page scroll
122
+ e.preventDefault();
123
+ handleTriggerClicked(e);
124
+ }
125
+ }
126
+ });
127
+ };
128
+ return unbind;
129
+ }, [isFocused, isOpen, handleTriggerClicked]);
65
130
  function renderMoreDropdown(max, total) {
66
131
  if (total <= max) {
67
132
  return null;
@@ -98,10 +163,13 @@ const AvatarGroup = ({
98
163
  rootBoundary: rootBoundary,
99
164
  shouldFlip: true,
100
165
  zIndex: layers.modal(),
101
- content: () => jsx(FocusManager, null, jsx(PopupMenuGroup, {
166
+ content: ({
167
+ setInitialFocusRef
168
+ }) => jsx(FocusManager, null, jsx(PopupAvatarGroup, {
102
169
  onClick: e => e.stopPropagation(),
103
170
  minWidth: 250,
104
- maxHeight: 300
171
+ maxHeight: 300,
172
+ setInitialFocusRef: isTriggeredUsingKeyboard ? setInitialFocusRef : undefined
105
173
  }, jsx(Section, null, data.slice(max).map((avatar, index) => getOverrides(overrides).AvatarGroupItem.render(AvatarGroupItem, {
106
174
  avatar,
107
175
  onAvatarClick,
@@ -113,7 +181,8 @@ const AvatarGroup = ({
113
181
  index + max))))),
114
182
  trigger: triggerProps => renderMoreButton({
115
183
  ...triggerProps,
116
- onClick: () => setIsOpen(!isOpen)
184
+ ...bindFocus,
185
+ onClick: handleTriggerClicked
117
186
  }),
118
187
  testId: testId && `${testId}--overflow-menu`
119
188
  });
@@ -0,0 +1,31 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+ /** @jsx jsx */
3
+ import { useContext, useEffect } from 'react';
4
+ import { jsx } from '@emotion/react';
5
+ import { MenuGroup } from '@atlaskit/menu';
6
+ import { FocusManagerContext } from './focus-manager';
7
+
8
+ /**
9
+ * It sets focus to the first avatar when popup is open.
10
+ */
11
+ const PopupAvatarGroup = ({
12
+ maxWidth = 800,
13
+ minWidth = 320,
14
+ setInitialFocusRef,
15
+ ...rest
16
+ }) => {
17
+ const {
18
+ menuItemRefs
19
+ } = useContext(FocusManagerContext);
20
+ useEffect(() => {
21
+ setInitialFocusRef === null || setInitialFocusRef === void 0 ? void 0 : setInitialFocusRef(menuItemRefs[0]);
22
+ }, [menuItemRefs, setInitialFocusRef]);
23
+ return (
24
+ // eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props
25
+ jsx(MenuGroup, _extends({
26
+ maxWidth: maxWidth,
27
+ minWidth: minWidth
28
+ }, rest))
29
+ );
30
+ };
31
+ export default PopupAvatarGroup;
@@ -13,7 +13,7 @@ const FONT_SIZE = {
13
13
  xxlarge: '16px'
14
14
  };
15
15
  const buttonActiveStyles = css({
16
- // eslint-disable-next-line @repo/internal/styles/no-nested-styles
16
+ // eslint-disable-next-line @atlaskit/design-system/no-nested-styles
17
17
  '&&': {
18
18
  backgroundColor: `var(--ds-background-selected, ${B50})`,
19
19
  boxShadow: `0 0 0 ${BORDER_WIDTH}px ${`var(--ds-border-selected, ${B300})`}`,
@@ -30,7 +30,7 @@ const buttonActiveStyles = css({
30
30
  }
31
31
  });
32
32
  const buttonStyles = css({
33
- // eslint-disable-next-line @repo/internal/styles/no-nested-styles
33
+ // eslint-disable-next-line @atlaskit/design-system/no-nested-styles
34
34
  '&&': {
35
35
  backgroundColor: `var(--ds-background-neutral, ${N20})`,
36
36
  color: `var(--ds-text, ${N500})`,
@@ -19,8 +19,13 @@ var AvatarGroupItem = /*#__PURE__*/forwardRef(function (props, ref) {
19
19
  var CustomComponent = function CustomComponent(_ref) {
20
20
  var children = _ref.children,
21
21
  props = _objectWithoutProperties(_ref, _excluded2);
22
- // eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props
23
- return /*#__PURE__*/React.createElement("span", props, children);
22
+ return (
23
+ /*#__PURE__*/
24
+ // eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props
25
+ React.createElement("button", _extends({
26
+ type: "button"
27
+ }, props), children)
28
+ );
24
29
  };
25
30
  var AvatarIcon = /*#__PURE__*/React.createElement(Avatar, _extends({}, rest, {
26
31
  testId: testId && "".concat(testId, "--avatar"),
@@ -4,16 +4,21 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
4
4
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
5
5
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
6
6
  /** @jsx jsx */
7
- import { useCallback, useState } from 'react';
7
+ import { useCallback, useEffect, useState } from 'react';
8
8
  import { jsx } from '@emotion/react';
9
+ import { bind } from 'bind-event-listener';
9
10
  import Avatar from '@atlaskit/avatar';
10
- import { PopupMenuGroup, Section } from '@atlaskit/menu';
11
+ import { KEY_DOWN } from '@atlaskit/ds-lib/keycodes';
12
+ import noop from '@atlaskit/ds-lib/noop';
13
+ import useFocus from '@atlaskit/ds-lib/use-focus-event';
14
+ import { Section } from '@atlaskit/menu';
11
15
  import Popup from '@atlaskit/popup';
12
16
  import { layers } from '@atlaskit/theme/constants';
13
17
  import Tooltip from '@atlaskit/tooltip';
14
18
  import AvatarGroupItem from './avatar-group-item';
15
19
  import Grid from './grid';
16
20
  import FocusManager from './internal/components/focus-manager';
21
+ import PopupAvatarGroup from './internal/components/popup-avatar-group';
17
22
  import MoreIndicator from './more-indicator';
18
23
  import Stack from './stack';
19
24
  import { composeUniqueKey } from './utils';
@@ -73,11 +78,73 @@ var AvatarGroup = function AvatarGroup(_ref) {
73
78
  tooltipPosition = _ref$tooltipPosition === void 0 ? 'bottom' : _ref$tooltipPosition;
74
79
  var _useState = useState(false),
75
80
  _useState2 = _slicedToArray(_useState, 2),
76
- isOpen = _useState2[0],
77
- setIsOpen = _useState2[1];
81
+ isTriggeredUsingKeyboard = _useState2[0],
82
+ setTriggeredUsingKeyboard = _useState2[1];
83
+ var _useState3 = useState(false),
84
+ _useState4 = _slicedToArray(_useState3, 2),
85
+ isOpen = _useState4[0],
86
+ setIsOpen = _useState4[1];
78
87
  var onClose = useCallback(function () {
79
88
  return setIsOpen(false);
80
89
  }, []);
90
+ var handleTriggerClicked = useCallback(function (event) {
91
+ var clientX = event.clientX,
92
+ clientY = event.clientY,
93
+ type = event.type;
94
+ // Hitting enter/space is registered as a click with both clientX and clientY === 0
95
+ if (type === 'keydown' || clientX === 0 || clientY === 0) {
96
+ setTriggeredUsingKeyboard(true);
97
+ }
98
+ setIsOpen(function (isOpen) {
99
+ return !isOpen;
100
+ });
101
+ }, []);
102
+ var _useFocus = useFocus(),
103
+ isFocused = _useFocus.isFocused,
104
+ bindFocus = _useFocus.bindFocus;
105
+
106
+ // When a trigger is focused, we want to open the popup
107
+ // the user presses the DownArrow
108
+ useEffect(function () {
109
+ // Set initial value if popup is closed
110
+ if (!isOpen) {
111
+ setTriggeredUsingKeyboard(false);
112
+ }
113
+
114
+ // Only need to listen for keydown when focused
115
+ if (!isFocused) {
116
+ return noop;
117
+ }
118
+
119
+ // Being safe: we don't want to open the popup if it is already open
120
+ // Note: This shouldn't happen as the trigger should not be able to get focus
121
+ if (isOpen) {
122
+ return noop;
123
+ }
124
+ bind(window, {
125
+ type: 'keydown',
126
+ listener: function openOnKeyDown(e) {
127
+ if (e.key === KEY_DOWN) {
128
+ // prevent page scroll
129
+ e.preventDefault();
130
+ handleTriggerClicked(e);
131
+ }
132
+ }
133
+ });
134
+ var unbind = function unbind() {
135
+ bind(window, {
136
+ type: 'keydown',
137
+ listener: function openOnKeyDown(e) {
138
+ if (e.key === KEY_DOWN) {
139
+ // prevent page scroll
140
+ e.preventDefault();
141
+ handleTriggerClicked(e);
142
+ }
143
+ }
144
+ });
145
+ };
146
+ return unbind;
147
+ }, [isFocused, isOpen, handleTriggerClicked]);
81
148
  function renderMoreDropdown(max, total) {
82
149
  if (total <= max) {
83
150
  return null;
@@ -116,13 +183,15 @@ var AvatarGroup = function AvatarGroup(_ref) {
116
183
  rootBoundary: rootBoundary,
117
184
  shouldFlip: true,
118
185
  zIndex: layers.modal(),
119
- content: function content() {
120
- return jsx(FocusManager, null, jsx(PopupMenuGroup, {
186
+ content: function content(_ref2) {
187
+ var setInitialFocusRef = _ref2.setInitialFocusRef;
188
+ return jsx(FocusManager, null, jsx(PopupAvatarGroup, {
121
189
  onClick: function onClick(e) {
122
190
  return e.stopPropagation();
123
191
  },
124
192
  minWidth: 250,
125
- maxHeight: 300
193
+ maxHeight: 300,
194
+ setInitialFocusRef: isTriggeredUsingKeyboard ? setInitialFocusRef : undefined
126
195
  }, jsx(Section, null, data.slice(max).map(function (avatar, index) {
127
196
  return getOverrides(overrides).AvatarGroupItem.render(AvatarGroupItem, {
128
197
  avatar: avatar,
@@ -136,10 +205,8 @@ var AvatarGroup = function AvatarGroup(_ref) {
136
205
  }))));
137
206
  },
138
207
  trigger: function trigger(triggerProps) {
139
- return renderMoreButton(_objectSpread(_objectSpread({}, triggerProps), {}, {
140
- onClick: function onClick() {
141
- return setIsOpen(!isOpen);
142
- }
208
+ return renderMoreButton(_objectSpread(_objectSpread(_objectSpread({}, triggerProps), bindFocus), {}, {
209
+ onClick: handleTriggerClicked
143
210
  }));
144
211
  },
145
212
  testId: testId && "".concat(testId, "--overflow-menu")
@@ -0,0 +1,33 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+ import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
3
+ var _excluded = ["maxWidth", "minWidth", "setInitialFocusRef"];
4
+ /** @jsx jsx */
5
+ import { useContext, useEffect } from 'react';
6
+ import { jsx } from '@emotion/react';
7
+ import { MenuGroup } from '@atlaskit/menu';
8
+ import { FocusManagerContext } from './focus-manager';
9
+
10
+ /**
11
+ * It sets focus to the first avatar when popup is open.
12
+ */
13
+ var PopupAvatarGroup = function PopupAvatarGroup(_ref) {
14
+ var _ref$maxWidth = _ref.maxWidth,
15
+ maxWidth = _ref$maxWidth === void 0 ? 800 : _ref$maxWidth,
16
+ _ref$minWidth = _ref.minWidth,
17
+ minWidth = _ref$minWidth === void 0 ? 320 : _ref$minWidth,
18
+ setInitialFocusRef = _ref.setInitialFocusRef,
19
+ rest = _objectWithoutProperties(_ref, _excluded);
20
+ var _useContext = useContext(FocusManagerContext),
21
+ menuItemRefs = _useContext.menuItemRefs;
22
+ useEffect(function () {
23
+ setInitialFocusRef === null || setInitialFocusRef === void 0 ? void 0 : setInitialFocusRef(menuItemRefs[0]);
24
+ }, [menuItemRefs, setInitialFocusRef]);
25
+ return (
26
+ // eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props
27
+ jsx(MenuGroup, _extends({
28
+ maxWidth: maxWidth,
29
+ minWidth: minWidth
30
+ }, rest))
31
+ );
32
+ };
33
+ export default PopupAvatarGroup;
@@ -15,7 +15,7 @@ var FONT_SIZE = {
15
15
  xxlarge: '16px'
16
16
  };
17
17
  var buttonActiveStyles = css({
18
- // eslint-disable-next-line @repo/internal/styles/no-nested-styles
18
+ // eslint-disable-next-line @atlaskit/design-system/no-nested-styles
19
19
  '&&': {
20
20
  backgroundColor: "var(--ds-background-selected, ".concat(B50, ")"),
21
21
  boxShadow: "0 0 0 ".concat(BORDER_WIDTH, "px ", "var(--ds-border-selected, ".concat(B300, ")")),
@@ -32,7 +32,7 @@ var buttonActiveStyles = css({
32
32
  }
33
33
  });
34
34
  var buttonStyles = css({
35
- // eslint-disable-next-line @repo/internal/styles/no-nested-styles
35
+ // eslint-disable-next-line @atlaskit/design-system/no-nested-styles
36
36
  '&&': {
37
37
  backgroundColor: "var(--ds-background-neutral, ".concat(N20, ")"),
38
38
  color: "var(--ds-text, ".concat(N500, ")"),
@@ -0,0 +1,7 @@
1
+ import { jsx } from '@emotion/react';
2
+ import { PopupAvatarGroupProps } from '../../types';
3
+ /**
4
+ * It sets focus to the first avatar when popup is open.
5
+ */
6
+ declare const PopupAvatarGroup: ({ maxWidth, minWidth, setInitialFocusRef, ...rest }: PopupAvatarGroupProps) => jsx.JSX.Element;
7
+ export default PopupAvatarGroup;
@@ -1,6 +1,8 @@
1
1
  import type { ElementType, ReactNode } from 'react';
2
2
  import type { AnalyticsEvent } from '@atlaskit/analytics-next';
3
3
  import type { AvatarPropTypes } from '@atlaskit/avatar';
4
+ import { MenuGroupProps } from '@atlaskit/menu';
5
+ import { ContentProps } from '@atlaskit/popup';
4
6
  import type { AvatarGroupItemProps } from './avatar-group-item';
5
7
  export type DeepRequired<T> = {
6
8
  [P in keyof T]-?: Required<T[P]>;
@@ -20,3 +22,6 @@ export interface AvatarGroupOverrides {
20
22
  export type onAvatarClickHandler = (event: React.MouseEvent, analyticsEvent: AnalyticsEvent | undefined, index: number) => void;
21
23
  export type FocusableElement = HTMLAnchorElement | HTMLButtonElement;
22
24
  export type Action = 'next' | 'prev' | 'first' | 'last';
25
+ export interface PopupAvatarGroupProps extends MenuGroupProps {
26
+ setInitialFocusRef?: ContentProps['setInitialFocusRef'];
27
+ }
@@ -0,0 +1,7 @@
1
+ import { jsx } from '@emotion/react';
2
+ import { PopupAvatarGroupProps } from '../../types';
3
+ /**
4
+ * It sets focus to the first avatar when popup is open.
5
+ */
6
+ declare const PopupAvatarGroup: ({ maxWidth, minWidth, setInitialFocusRef, ...rest }: PopupAvatarGroupProps) => jsx.JSX.Element;
7
+ export default PopupAvatarGroup;
@@ -1,6 +1,8 @@
1
1
  import type { ElementType, ReactNode } from 'react';
2
2
  import type { AnalyticsEvent } from '@atlaskit/analytics-next';
3
3
  import type { AvatarPropTypes } from '@atlaskit/avatar';
4
+ import { MenuGroupProps } from '@atlaskit/menu';
5
+ import { ContentProps } from '@atlaskit/popup';
4
6
  import type { AvatarGroupItemProps } from './avatar-group-item';
5
7
  export type DeepRequired<T> = {
6
8
  [P in keyof T]-?: Required<T[P]>;
@@ -20,3 +22,6 @@ export interface AvatarGroupOverrides {
20
22
  export type onAvatarClickHandler = (event: React.MouseEvent, analyticsEvent: AnalyticsEvent | undefined, index: number) => void;
21
23
  export type FocusableElement = HTMLAnchorElement | HTMLButtonElement;
22
24
  export type Action = 'next' | 'prev' | 'first' | 'last';
25
+ export interface PopupAvatarGroupProps extends MenuGroupProps {
26
+ setInitialFocusRef?: ContentProps['setInitialFocusRef'];
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/avatar-group",
3
- "version": "9.3.5",
3
+ "version": "9.4.0",
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/"
@@ -12,14 +12,6 @@
12
12
  "module": "dist/esm/index.js",
13
13
  "module:es2019": "dist/es2019/index.js",
14
14
  "types": "dist/types/index.d.ts",
15
- "typesVersions": {
16
- ">=4.5 <4.9": {
17
- "*": [
18
- "dist/types-ts4.5/*",
19
- "dist/types-ts4.5/index.d.ts"
20
- ]
21
- }
22
- },
23
15
  "sideEffects": false,
24
16
  "atlaskit:src": "src/index.tsx",
25
17
  "atlassian": {
@@ -30,16 +22,13 @@
30
22
  "category": "Components"
31
23
  }
32
24
  },
33
- "af:exports": {
34
- ".": "./src/index.tsx"
35
- },
36
25
  "dependencies": {
37
26
  "@atlaskit/avatar": "^21.3.0",
38
27
  "@atlaskit/ds-lib": "^2.1.0",
39
28
  "@atlaskit/menu": "^1.9.0",
40
- "@atlaskit/popup": "^1.8.0",
29
+ "@atlaskit/popup": "^1.9.0",
41
30
  "@atlaskit/theme": "^12.5.0",
42
- "@atlaskit/tokens": "^1.11.0",
31
+ "@atlaskit/tokens": "^1.14.0",
43
32
  "@atlaskit/tooltip": "^17.8.0",
44
33
  "@babel/runtime": "^7.0.0",
45
34
  "@emotion/react": "^11.7.1",
@@ -50,6 +39,7 @@
50
39
  },
51
40
  "devDependencies": {
52
41
  "@af/accessibility-testing": "*",
42
+ "@af/visual-regression": "*",
53
43
  "@atlaskit/analytics-next": "^9.1.0",
54
44
  "@atlaskit/ds-lib": "^2.2.0",
55
45
  "@atlaskit/ssr": "*",
@@ -58,6 +48,7 @@
58
48
  "@atlassian/atlassian-frontend-prettier-config-1.0.1": "npm:@atlassian/atlassian-frontend-prettier-config@1.0.1",
59
49
  "@emotion/styled": "^11.0.0",
60
50
  "@testing-library/react": "^12.1.5",
51
+ "@testing-library/user-event": "^14.4.3",
61
52
  "lodash": "^4.17.21",
62
53
  "react-dom": "^16.8.0",
63
54
  "typescript": "~4.9.5",
@@ -88,6 +79,17 @@
88
79
  ]
89
80
  }
90
81
  },
82
+ "typesVersions": {
83
+ ">=4.5 <4.9": {
84
+ "*": [
85
+ "dist/types-ts4.5/*",
86
+ "dist/types-ts4.5/index.d.ts"
87
+ ]
88
+ }
89
+ },
90
+ "af:exports": {
91
+ ".": "./src/index.tsx"
92
+ },
91
93
  "homepage": "https://atlassian.design/components/avatar-group/",
92
94
  "prettier": "@atlassian/atlassian-frontend-prettier-config-1.0.1"
93
95
  }
@@ -0,0 +1,78 @@
1
+ ## API Report File for "@atlaskit/avatar-group"
2
+
3
+ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
4
+
5
+ ```ts
6
+
7
+ import type { AnalyticsEvent } from '@atlaskit/analytics-next';
8
+ import type { AvatarPropTypes } from '@atlaskit/avatar';
9
+ import { ElementType } from 'react';
10
+ import { jsx } from '@emotion/react';
11
+ import { MouseEventHandler } from 'react';
12
+ import { PositionType } from '@atlaskit/tooltip';
13
+ import type { ReactNode } from 'react';
14
+ import { SizeType } from '@atlaskit/avatar';
15
+
16
+ // @public
17
+ const AvatarGroup: ({ appearance, avatar, borderColor, boundariesElement, data, isTooltipDisabled, maxCount, onAvatarClick, onMoreClick, overrides, showMoreButtonProps, size, testId, label, tooltipPosition, }: AvatarGroupProps) => jsx.JSX.Element;
18
+ export default AvatarGroup;
19
+
20
+ // @public (undocumented)
21
+ interface AvatarGroupItemProps {
22
+ // (undocumented)
23
+ avatar: AvatarProps;
24
+ // (undocumented)
25
+ index: number;
26
+ // (undocumented)
27
+ isActive?: boolean;
28
+ // (undocumented)
29
+ isHover?: boolean;
30
+ // (undocumented)
31
+ onAvatarClick?: onAvatarClickHandler;
32
+ // (undocumented)
33
+ testId?: string;
34
+ }
35
+
36
+ // @public (undocumented)
37
+ interface AvatarGroupOverrides {
38
+ // (undocumented)
39
+ Avatar?: {
40
+ render?: (Component: ElementType<AvatarProps>, props: AvatarProps, index: number) => ReactNode;
41
+ };
42
+ // (undocumented)
43
+ AvatarGroupItem?: {
44
+ render?: (Component: ElementType<AvatarGroupItemProps>, props: AvatarGroupItemProps, index: number) => ReactNode;
45
+ };
46
+ }
47
+
48
+ // @public (undocumented)
49
+ export interface AvatarGroupProps {
50
+ appearance?: 'grid' | 'stack';
51
+ avatar?: ElementType<AvatarProps>;
52
+ borderColor?: string;
53
+ boundariesElement?: 'scrollParent' | 'viewport' | 'window';
54
+ data: Array<AvatarProps>;
55
+ isTooltipDisabled?: boolean;
56
+ label?: string;
57
+ maxCount?: number;
58
+ onAvatarClick?: onAvatarClickHandler;
59
+ onMoreClick?: MouseEventHandler;
60
+ overrides?: AvatarGroupOverrides;
61
+ showMoreButtonProps?: Partial<React.HTMLAttributes<HTMLElement>>;
62
+ size?: SizeType;
63
+ testId?: string;
64
+ tooltipPosition?: Extract<PositionType, 'bottom' | 'top'>;
65
+ }
66
+
67
+ // @public (undocumented)
68
+ export type AvatarProps = AvatarPropTypes & {
69
+ name: string;
70
+ key?: number | string;
71
+ };
72
+
73
+ // @public (undocumented)
74
+ type onAvatarClickHandler = (event: React.MouseEvent, analyticsEvent: AnalyticsEvent | undefined, index: number) => void;
75
+
76
+ // (No @packageDocumentation comment for this package)
77
+
78
+ ```
@@ -1,5 +0,0 @@
1
- {
2
- "name": "@atlaskit/avatar-group",
3
- "version": "9.3.5",
4
- "sideEffects": false
5
- }
@@ -1,5 +0,0 @@
1
- {
2
- "name": "@atlaskit/avatar-group",
3
- "version": "9.3.5",
4
- "sideEffects": false
5
- }
@@ -1,5 +0,0 @@
1
- {
2
- "name": "@atlaskit/avatar-group",
3
- "version": "9.3.5",
4
- "sideEffects": false
5
- }