@atlaskit/avatar-group
Version:
An avatar group displays a number of avatars grouped together in a stack or grid.
159 lines (157 loc) • 6.73 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
import React, { useCallback, useEffect, useRef } from 'react';
import { bind } from 'bind-event-listener';
import { KEY_DOWN } from '@atlaskit/ds-lib/keycodes';
import MenuGroup from '@atlaskit/menu/menu-group';
import Section from '@atlaskit/menu/section';
import { getAriaForTrigger } from '@atlaskit/top-layer/get-aria-for-trigger';
import { fromLegacyPlacement } from '@atlaskit/top-layer/placement-map/index';
import { PopoverSurface } from '@atlaskit/top-layer/popover-surface';
import { Popover } from '@atlaskit/top-layer/popover/popover';
import { useAnchoredPopover } from '@atlaskit/top-layer/use-anchored-popover';
import { useArrowNavigation } from '@atlaskit/top-layer/use-arrow-navigation/use-arrow-navigation';
import { usePopoverId } from '@atlaskit/top-layer/use-popover-id';
import AvatarGroupItem from './avatar-group-item';
var topLayerPlacement = fromLegacyPlacement({
legacy: 'bottom-end'
});
function getOverrides(overrides) {
return {
AvatarGroupItem: _objectSpread({
render: function render(Component, props, index) {
return /*#__PURE__*/React.createElement(Component, _extends({
key: index
}, props));
}
}, overrides === null || overrides === void 0 ? void 0 : overrides.AvatarGroupItem),
Avatar: _objectSpread({
render: function render(Component, props, index) {
return /*#__PURE__*/React.createElement(Component, _extends({
key: index
}, props));
}
}, overrides === null || overrides === void 0 ? void 0 : overrides.Avatar),
MoreIndicator: _objectSpread({
render: function render(Component, props) {
return /*#__PURE__*/React.createElement(Component, props);
}
}, overrides === null || overrides === void 0 ? void 0 : overrides.MoreIndicator)
};
}
/**
* Top-layer implementation of the avatar group overflow dropdown.
*
* Replaces the legacy `@atlaskit/popup` rendering pipeline
* (Popper.js + Portal + focus-trap + @atlaskit/layering)
* with the native Popover API + CSS Anchor Positioning via `@atlaskit/top-layer`.
*
* Uses `role="menu"` with arrow key navigation for correct menu semantics.
*
* Gated behind the `platform-dst-top-layer` feature flag.
*
* Legacy props that are no-ops in the top-layer path (not accepted here):
* - zIndex: stacking managed by browser top layer
* - shouldRenderToParent: always renders in top layer
* - boundary / rootBoundary: viewport is the natural boundary
* - shouldFlip: CSS Anchor Positioning handles flipping
*/
export function MoreDropdownTopLayer(_ref) {
var isOpen = _ref.isOpen,
onClose = _ref.onClose,
_isTriggeredUsingKeyboard = _ref.isTriggeredUsingKeyboard,
data = _ref.data,
max = _ref.max,
overrides = _ref.overrides,
onAvatarClick = _ref.onAvatarClick,
testId = _ref.testId,
labelId = _ref.labelId,
renderMoreButton = _ref.renderMoreButton,
handleTriggerClicked = _ref.handleTriggerClicked,
_bindFocus = _ref.bindFocus;
var handleOnClose = useCallback(function (_ref2) {
var _reason = _ref2.reason;
onClose();
}, [onClose]);
var triggerRef = useRef(null);
var popoverRef = useRef(null);
var menuRef = useRef(null);
var overflowMenuTestId = testId ? "".concat(testId, "--overflow-menu") : undefined;
var popoverId = usePopoverId();
useAnchoredPopover({
anchorRef: triggerRef,
popoverRef: popoverRef,
placement: topLayerPlacement,
isOpen: isOpen
});
// Arrow key navigation inside the open menu
useArrowNavigation({
containerRef: menuRef,
onClose: onClose,
isEnabled: isOpen
});
// ArrowDown-to-open: when the trigger is focused and the menu is closed,
// pressing ArrowDown opens the menu (WAI-ARIA menu button pattern).
//
// Bind the keydown listener directly to the trigger element via its ref so
// that opening the menu does not trigger a focus-driven re-render of the
// host component, which would unmount and remount the menu items and drop
// menuitem focus.
useEffect(function () {
var trigger = triggerRef.current;
if (!trigger || isOpen) {
return;
}
return bind(trigger, {
type: 'keydown',
listener: function openOnArrowDown(event) {
if (event.key === KEY_DOWN) {
// Prevent page scroll when opening the menu via ArrowDown.
event.preventDefault();
handleTriggerClicked(event);
}
}
});
}, [isOpen, handleTriggerClicked]);
var triggerAria = getAriaForTrigger({
role: 'menu',
isOpen: isOpen,
popoverId: popoverId
});
var setTriggerRef = useCallback(function (node) {
triggerRef.current = node;
}, []);
return /*#__PURE__*/React.createElement(React.Fragment, null, renderMoreButton(_objectSpread(_objectSpread({
ref: setTriggerRef
}, triggerAria), {}, {
onClick: handleTriggerClicked
})), /*#__PURE__*/React.createElement(Popover, {
ref: popoverRef,
id: popoverId,
role: "menu",
label: "avatar group",
isOpen: isOpen,
onClose: handleOnClose,
shouldAnimate: true,
placement: topLayerPlacement,
testId: overflowMenuTestId ? "".concat(overflowMenuTestId, "--content") : undefined
}, /*#__PURE__*/React.createElement(PopoverSurface, null, /*#__PURE__*/React.createElement("div", {
ref: menuRef
}, /*#__PURE__*/React.createElement(MenuGroup, {
minWidth: 250,
maxHeight: 300
}, /*#__PURE__*/React.createElement(Section, {
titleId: labelId,
testId: testId ? "".concat(testId, "--section") : undefined
}, data.slice(max).map(function (avatar, index) {
return getOverrides(overrides).AvatarGroupItem.render(AvatarGroupItem, {
avatar: avatar,
onAvatarClick: onAvatarClick,
testId: testId ? "".concat(testId, "--avatar-group-item-").concat(index + max) : undefined,
index: index + max,
role: 'menuitem'
}, index + max);
})))))));
}