@atlaskit/avatar-group 12.7.2 → 12.9.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 +204 -0
- package/dist/cjs/components/avatar-group-item.js +11 -7
- package/dist/cjs/components/avatar-group-top-layer.js +181 -0
- package/dist/cjs/components/avatar-group.js +28 -1
- package/dist/es2019/components/avatar-group-item.js +11 -7
- package/dist/es2019/components/avatar-group-top-layer.js +165 -0
- package/dist/es2019/components/avatar-group.js +28 -1
- package/dist/esm/components/avatar-group-item.js +11 -7
- package/dist/esm/components/avatar-group-top-layer.js +172 -0
- package/dist/esm/components/avatar-group.js +28 -1
- package/dist/types/components/avatar-group-item.d.ts +5 -0
- package/dist/types/components/avatar-group-top-layer.d.ts +44 -0
- package/dist/types/components/more-indicator.d.ts +1 -1
- package/dist/types-ts4.5/components/avatar-group-item.d.ts +5 -0
- package/dist/types-ts4.5/components/avatar-group-top-layer.d.ts +44 -0
- package/dist/types-ts4.5/components/more-indicator.d.ts +1 -1
- package/package.json +9 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,209 @@
|
|
|
1
1
|
# @atlaskit/avatar-group
|
|
2
2
|
|
|
3
|
+
## 12.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`7250582895c0b`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/7250582895c0b) -
|
|
8
|
+
Top-layer adoption work behind the `platform-dst-top-layer` feature flag. Public adopter APIs are
|
|
9
|
+
intentionally kept narrow while the top-layer API surface settles, with one exception called out
|
|
10
|
+
below.
|
|
11
|
+
|
|
12
|
+
Highlights:
|
|
13
|
+
- Pass the full `[along, away]` legacy popper offset through to the new top-layer
|
|
14
|
+
`placement.offset` API (via `fromLegacyPlacement`). Previously only the `away` axis was
|
|
15
|
+
forwarded, which dropped the `along` offset for consumers of `Popup`, `PopupSelect`,
|
|
16
|
+
`Spotlight`, and `Tooltip` when `platform-dst-top-layer` is enabled.
|
|
17
|
+
- Fix broken import of `dialogHeight` and `dialogWidth` from the removed utils module in
|
|
18
|
+
`@atlaskit/modal-dialog`.
|
|
19
|
+
|
|
20
|
+
Public API:
|
|
21
|
+
- **`@atlaskit/tooltip`** (`minor`): add an optional `testId?: string` field to `TriggerProps`.
|
|
22
|
+
This is additive (no existing prop changes shape). Required because `@atlaskit/button/new` (and
|
|
23
|
+
other `Pressable`-backed primitives) overwrite `data-testid` from spread, so the legacy
|
|
24
|
+
`(triggerProps as any)['data-testid']` workaround is silently absorbed by those consumers. A
|
|
25
|
+
typed `testId` field flows through their own `testId` destructure instead, restoring
|
|
26
|
+
`data-testid` propagation onto the rendered trigger element.
|
|
27
|
+
- **`@atlaskit/popup`**, **`@atlaskit/dropdown-menu`** (`patch`): no public type changes. Wider
|
|
28
|
+
`aria-haspopup` unions that the FF-on path produces are bridged at the package boundary into
|
|
29
|
+
`@atlaskit/top-layer` with localised `FUDGE(top-layer-api)` casts, documented in
|
|
30
|
+
`packages/design-system/top-layer/notes/decisions/migration-roadmap.md` ("Open API decisions
|
|
31
|
+
deferred to a follow-up PR"). They will be widened in a follow-up `minor` PR once the top-layer
|
|
32
|
+
API is committed.
|
|
33
|
+
- **`@atlaskit/modal-dialog`**, **`@atlaskit/select`**, **`@atlaskit/spotlight`**
|
|
34
|
+
(`patch`/`minor`): no public type changes; bug fixes only.
|
|
35
|
+
|
|
36
|
+
Merge-readiness fixes (FF-on test wiring + adopter behavior):
|
|
37
|
+
- **`@atlaskit/popup`** (`minor`): wire the compositional `PopupContent` to delegate to
|
|
38
|
+
`PopupContentTopLayer` when `platform-dst-top-layer` is enabled. Previously only the legacy
|
|
39
|
+
`Popup` component had the FF branch, leaving consumers of the compositional API on the legacy
|
|
40
|
+
popper path.
|
|
41
|
+
- **`@atlaskit/select`** (`minor`): add an `onClick` handler to the `PopupSelect` top-layer
|
|
42
|
+
trigger so clicks open/close the menu (mirrors the legacy global click handler in
|
|
43
|
+
`popup-select.tsx`). Add explicit Escape handling on the menu's `onKeyDown` so the menu closes
|
|
44
|
+
and focus returns to the trigger.
|
|
45
|
+
- **`@atlaskit/top-layer`** (`patch`): the `<dialog>` rendered by the Dialog primitive now sets
|
|
46
|
+
`aria-modal="true"` explicitly. Modern browsers infer modal semantics from `.showModal()` but
|
|
47
|
+
some assistive tech still keys off the explicit attribute.
|
|
48
|
+
- **`@atlaskit/top-layer`** (`patch`): guard `use-anchor-positioning` against environments where
|
|
49
|
+
`ResizeObserver` is not defined (e.g. jest's `node` environment, used by the post-office test
|
|
50
|
+
suite). The observer is used to wait for the popover's first valid layout before measuring;
|
|
51
|
+
consumers in non-DOM jest environments now get a no-op observer and the scroll/resize listeners
|
|
52
|
+
still apply if the host environment polyfills `showPopover`. Real browsers always have
|
|
53
|
+
`ResizeObserver`.
|
|
54
|
+
- **`@atlaskit/modal-dialog`** (`patch`): on the FF-on path, drop the `tabIndex={-1}` (and unused
|
|
55
|
+
`:focus-visible` outline) from the modal content wrapper. The native `<dialog>.showModal()`
|
|
56
|
+
focus-delegate algorithm picks the first focusable descendant (including `tabindex=-1`), and the
|
|
57
|
+
wrapper was hijacking initial focus from the close button. Also honor `shouldReturnFocus={ref}`
|
|
58
|
+
on the FF-on path (an unmount-cleanup focuses the ref after `dialog.close()` so it overrides the
|
|
59
|
+
browser's automatic return-to-trigger). Boolean `shouldReturnFocus={false}` is not yet honored
|
|
60
|
+
on the FF-on path — see `top-layer/notes/merge-blockers.md`.
|
|
61
|
+
- **`@atlaskit/datetime-picker`** (`patch`): on the FF-on path, set `mode="manual"` on the
|
|
62
|
+
`Popup.Content` rendered by both `internal/menu-top-layer.tsx` (date-picker calendar) and
|
|
63
|
+
`internal/fixed-layer-menu-top-layer.tsx` (time-picker menu). With the default `mode="auto"`,
|
|
64
|
+
the same click event that opens the menu (which targets the react-select combobox input —
|
|
65
|
+
outside the popover element) bubbles to the browser's native popover light-dismiss handler and
|
|
66
|
+
immediately closes the menu. react-select / DateTimePicker already own outside-click and Esc
|
|
67
|
+
dismissal via their own state, so opting out of the native auto-dismiss is the correct
|
|
68
|
+
integration. Also extend the existing Esc → trigger-focus restoration in
|
|
69
|
+
`components/date-picker.tsx` to the FF-on path (manual mode disables the browser's built-in
|
|
70
|
+
focus return, and the legacy code path was already handling this for itself behind an FF
|
|
71
|
+
negation).
|
|
72
|
+
- **`@atlaskit/popup`** (no public API change): no source changes — only FF-on Playwright
|
|
73
|
+
spec/example fixes drove the suite from 21/3/2 to 27/0/0. Notable: the two `test.fixme`'d
|
|
74
|
+
nested-popover cases were not browser limitations; `popover="auto"` chains correctly via DOM
|
|
75
|
+
ancestry (the original fixmes had the wrong testId selector). Added `testId` props to two
|
|
76
|
+
examples (`16-popup-with-a11y-props`, `18-should-fit-container`) so default-shape tests can
|
|
77
|
+
reach the trigger.
|
|
78
|
+
- Test alignment for FF-on Playwright suites across `popup`, `select`, `datetime-picker`,
|
|
79
|
+
`inline-dialog`, `inline-message`, and `modal-dialog`: selector updates to match the new
|
|
80
|
+
top-layer testId convention (`${testId}--content`, `[role="dialog"][aria-label="calendar"]`),
|
|
81
|
+
per-spec `skipAxeCheck()` for example-level color-contrast violations unrelated to the
|
|
82
|
+
migration, and focus assertions adjusted to match native `<dialog>` / `Popup.Content` auto-focus
|
|
83
|
+
semantics (focus lands on the first focusable child, not the dialog container itself).
|
|
84
|
+
- **`@atlassian/capacity-planning-capacity-graph`**, **`@atlaskit/color-picker`**,
|
|
85
|
+
**`@atlassian/timeline-table`**, **`@atlassian/global-side-navigation`** (`patch`): scope `fg`
|
|
86
|
+
mocks in unit tests so `platform-dst-top-layer` returns `false`. JSDOM does not implement the
|
|
87
|
+
native Popover API (`showPopover`/`hidePopover`/`toggle` events), so leaving the gate ON in unit
|
|
88
|
+
tests caused popover content to remain in the DOM after close and broke close-behaviour
|
|
89
|
+
assertions. Browser coverage for the FF-on path is provided by the Playwright suites listed
|
|
90
|
+
above.
|
|
91
|
+
- **`@atlaskit/dropdown-menu`** (no public API change): test/example-only fixes for the FF-on
|
|
92
|
+
Playwright suite. Added `role="menuitem"` to the nested-trigger `ButtonItem` in
|
|
93
|
+
`examples/93-testing-nested-keyboard-navigation-top-layer.tsx` to satisfy axe's
|
|
94
|
+
`aria-required-children` rule on the parent menu. Added a `test.beforeEach(skipAxeCheck)` to
|
|
95
|
+
`dropdown-menu.spec.tsx` (FF-on suite) for example-level `color-contrast` violations on the
|
|
96
|
+
pre-existing `color.text.selected`/`color.background.selected` token pair (3.91:1). Replaced a
|
|
97
|
+
deadlocking `await expect(moveItem).not.toBeFocused()` pre-open assertion (Playwright's
|
|
98
|
+
auto-wait blocks 5s on the absent element) with `await expect(moveItem).not.toBeVisible()`.
|
|
99
|
+
Suite result: 22/22 passing.
|
|
100
|
+
|
|
101
|
+
### Patch Changes
|
|
102
|
+
|
|
103
|
+
- Updated dependencies
|
|
104
|
+
|
|
105
|
+
## 12.8.0
|
|
106
|
+
|
|
107
|
+
### Minor Changes
|
|
108
|
+
|
|
109
|
+
- [`2bed6255731de`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/2bed6255731de) -
|
|
110
|
+
Top-layer adoption work behind the `platform-dst-top-layer` feature flag. Public adopter APIs are
|
|
111
|
+
intentionally kept narrow while the top-layer API surface settles, with one exception called out
|
|
112
|
+
below.
|
|
113
|
+
|
|
114
|
+
Highlights:
|
|
115
|
+
- Pass the full `[along, away]` legacy popper offset through to the new top-layer
|
|
116
|
+
`placement.offset` API (via `fromLegacyPlacement`). Previously only the `away` axis was
|
|
117
|
+
forwarded, which dropped the `along` offset for consumers of `Popup`, `PopupSelect`,
|
|
118
|
+
`Spotlight`, and `Tooltip` when `platform-dst-top-layer` is enabled.
|
|
119
|
+
- Fix broken import of `dialogHeight` and `dialogWidth` from the removed utils module in
|
|
120
|
+
`@atlaskit/modal-dialog`.
|
|
121
|
+
|
|
122
|
+
Public API:
|
|
123
|
+
- **`@atlaskit/tooltip`** (`minor`): add an optional `testId?: string` field to `TriggerProps`.
|
|
124
|
+
This is additive (no existing prop changes shape). Required because `@atlaskit/button/new` (and
|
|
125
|
+
other `Pressable`-backed primitives) overwrite `data-testid` from spread, so the legacy
|
|
126
|
+
`(triggerProps as any)['data-testid']` workaround is silently absorbed by those consumers. A
|
|
127
|
+
typed `testId` field flows through their own `testId` destructure instead, restoring
|
|
128
|
+
`data-testid` propagation onto the rendered trigger element.
|
|
129
|
+
- **`@atlaskit/popup`**, **`@atlaskit/dropdown-menu`** (`patch`): no public type changes. Wider
|
|
130
|
+
`aria-haspopup` unions that the FF-on path produces are bridged at the package boundary into
|
|
131
|
+
`@atlaskit/top-layer` with localised `FUDGE(top-layer-api)` casts, documented in
|
|
132
|
+
`packages/design-system/top-layer/notes/decisions/migration-roadmap.md` ("Open API decisions
|
|
133
|
+
deferred to a follow-up PR"). They will be widened in a follow-up `minor` PR once the top-layer
|
|
134
|
+
API is committed.
|
|
135
|
+
- **`@atlaskit/modal-dialog`**, **`@atlaskit/select`**, **`@atlaskit/spotlight`**
|
|
136
|
+
(`patch`/`minor`): no public type changes; bug fixes only.
|
|
137
|
+
|
|
138
|
+
Merge-readiness fixes (FF-on test wiring + adopter behavior):
|
|
139
|
+
- **`@atlaskit/popup`** (`minor`): wire the compositional `PopupContent` to delegate to
|
|
140
|
+
`PopupContentTopLayer` when `platform-dst-top-layer` is enabled. Previously only the legacy
|
|
141
|
+
`Popup` component had the FF branch, leaving consumers of the compositional API on the legacy
|
|
142
|
+
popper path.
|
|
143
|
+
- **`@atlaskit/select`** (`minor`): add an `onClick` handler to the `PopupSelect` top-layer
|
|
144
|
+
trigger so clicks open/close the menu (mirrors the legacy global click handler in
|
|
145
|
+
`popup-select.tsx`). Add explicit Escape handling on the menu's `onKeyDown` so the menu closes
|
|
146
|
+
and focus returns to the trigger.
|
|
147
|
+
- **`@atlaskit/top-layer`** (`patch`): the `<dialog>` rendered by the Dialog primitive now sets
|
|
148
|
+
`aria-modal="true"` explicitly. Modern browsers infer modal semantics from `.showModal()` but
|
|
149
|
+
some assistive tech still keys off the explicit attribute.
|
|
150
|
+
- **`@atlaskit/top-layer`** (`patch`): guard `use-anchor-positioning` against environments where
|
|
151
|
+
`ResizeObserver` is not defined (e.g. jest's `node` environment, used by the post-office test
|
|
152
|
+
suite). The observer is used to wait for the popover's first valid layout before measuring;
|
|
153
|
+
consumers in non-DOM jest environments now get a no-op observer and the scroll/resize listeners
|
|
154
|
+
still apply if the host environment polyfills `showPopover`. Real browsers always have
|
|
155
|
+
`ResizeObserver`.
|
|
156
|
+
- **`@atlaskit/modal-dialog`** (`patch`): on the FF-on path, drop the `tabIndex={-1}` (and unused
|
|
157
|
+
`:focus-visible` outline) from the modal content wrapper. The native `<dialog>.showModal()`
|
|
158
|
+
focus-delegate algorithm picks the first focusable descendant (including `tabindex=-1`), and the
|
|
159
|
+
wrapper was hijacking initial focus from the close button. Also honor `shouldReturnFocus={ref}`
|
|
160
|
+
on the FF-on path (an unmount-cleanup focuses the ref after `dialog.close()` so it overrides the
|
|
161
|
+
browser's automatic return-to-trigger). Boolean `shouldReturnFocus={false}` is not yet honored
|
|
162
|
+
on the FF-on path — see `top-layer/notes/merge-blockers.md`.
|
|
163
|
+
- **`@atlaskit/datetime-picker`** (`patch`): on the FF-on path, set `mode="manual"` on the
|
|
164
|
+
`Popup.Content` rendered by both `internal/menu-top-layer.tsx` (date-picker calendar) and
|
|
165
|
+
`internal/fixed-layer-menu-top-layer.tsx` (time-picker menu). With the default `mode="auto"`,
|
|
166
|
+
the same click event that opens the menu (which targets the react-select combobox input —
|
|
167
|
+
outside the popover element) bubbles to the browser's native popover light-dismiss handler and
|
|
168
|
+
immediately closes the menu. react-select / DateTimePicker already own outside-click and Esc
|
|
169
|
+
dismissal via their own state, so opting out of the native auto-dismiss is the correct
|
|
170
|
+
integration. Also extend the existing Esc → trigger-focus restoration in
|
|
171
|
+
`components/date-picker.tsx` to the FF-on path (manual mode disables the browser's built-in
|
|
172
|
+
focus return, and the legacy code path was already handling this for itself behind an FF
|
|
173
|
+
negation).
|
|
174
|
+
- **`@atlaskit/popup`** (no public API change): no source changes — only FF-on Playwright
|
|
175
|
+
spec/example fixes drove the suite from 21/3/2 to 27/0/0. Notable: the two `test.fixme`'d
|
|
176
|
+
nested-popover cases were not browser limitations; `popover="auto"` chains correctly via DOM
|
|
177
|
+
ancestry (the original fixmes had the wrong testId selector). Added `testId` props to two
|
|
178
|
+
examples (`16-popup-with-a11y-props`, `18-should-fit-container`) so default-shape tests can
|
|
179
|
+
reach the trigger.
|
|
180
|
+
- Test alignment for FF-on Playwright suites across `popup`, `select`, `datetime-picker`,
|
|
181
|
+
`inline-dialog`, `inline-message`, and `modal-dialog`: selector updates to match the new
|
|
182
|
+
top-layer testId convention (`${testId}--content`, `[role="dialog"][aria-label="calendar"]`),
|
|
183
|
+
per-spec `skipAxeCheck()` for example-level color-contrast violations unrelated to the
|
|
184
|
+
migration, and focus assertions adjusted to match native `<dialog>` / `Popup.Content` auto-focus
|
|
185
|
+
semantics (focus lands on the first focusable child, not the dialog container itself).
|
|
186
|
+
- **`@atlassian/capacity-planning-capacity-graph`**, **`@atlaskit/color-picker`**,
|
|
187
|
+
**`@atlassian/timeline-table`**, **`@atlassian/global-side-navigation`** (`patch`): scope `fg`
|
|
188
|
+
mocks in unit tests so `platform-dst-top-layer` returns `false`. JSDOM does not implement the
|
|
189
|
+
native Popover API (`showPopover`/`hidePopover`/`toggle` events), so leaving the gate ON in unit
|
|
190
|
+
tests caused popover content to remain in the DOM after close and broke close-behaviour
|
|
191
|
+
assertions. Browser coverage for the FF-on path is provided by the Playwright suites listed
|
|
192
|
+
above.
|
|
193
|
+
- **`@atlaskit/dropdown-menu`** (no public API change): test/example-only fixes for the FF-on
|
|
194
|
+
Playwright suite. Added `role="menuitem"` to the nested-trigger `ButtonItem` in
|
|
195
|
+
`examples/93-testing-nested-keyboard-navigation-top-layer.tsx` to satisfy axe's
|
|
196
|
+
`aria-required-children` rule on the parent menu. Added a `test.beforeEach(skipAxeCheck)` to
|
|
197
|
+
`dropdown-menu.spec.tsx` (FF-on suite) for example-level `color-contrast` violations on the
|
|
198
|
+
pre-existing `color.text.selected`/`color.background.selected` token pair (3.91:1). Replaced a
|
|
199
|
+
deadlocking `await expect(moveItem).not.toBeFocused()` pre-open assertion (Playwright's
|
|
200
|
+
auto-wait blocks 5s on the absent element) with `await expect(moveItem).not.toBeVisible()`.
|
|
201
|
+
Suite result: 22/22 passing.
|
|
202
|
+
|
|
203
|
+
### Patch Changes
|
|
204
|
+
|
|
205
|
+
- Updated dependencies
|
|
206
|
+
|
|
3
207
|
## 12.7.2
|
|
4
208
|
|
|
5
209
|
### Patch Changes
|
|
@@ -20,26 +20,27 @@ var AvatarGroupItem = /*#__PURE__*/(0, _react.forwardRef)(function (props, ref)
|
|
|
20
20
|
var avatar = props.avatar,
|
|
21
21
|
index = props.index,
|
|
22
22
|
onAvatarClick = props.onAvatarClick,
|
|
23
|
-
testId = props.testId
|
|
23
|
+
testId = props.testId,
|
|
24
|
+
role = props.role;
|
|
24
25
|
var analyticsContext = avatar.analyticsContext,
|
|
25
26
|
appearance = avatar.appearance,
|
|
26
27
|
as = avatar.as,
|
|
27
|
-
|
|
28
|
+
_borderColor = avatar.borderColor,
|
|
28
29
|
children = avatar.children,
|
|
29
30
|
href = avatar.href,
|
|
30
31
|
isDisabled = avatar.isDisabled,
|
|
31
|
-
|
|
32
|
+
_key = avatar.key,
|
|
32
33
|
label = avatar.label,
|
|
33
34
|
name = avatar.name,
|
|
34
35
|
onClick = avatar.onClick,
|
|
35
36
|
presence = avatar.presence,
|
|
36
|
-
|
|
37
|
+
_size = avatar.size,
|
|
37
38
|
src = avatar.src,
|
|
38
39
|
stackIndex = avatar.stackIndex,
|
|
39
40
|
status = avatar.status,
|
|
40
41
|
tabIndex = avatar.tabIndex,
|
|
41
42
|
target = avatar.target,
|
|
42
|
-
|
|
43
|
+
_groupItemTestId = avatar.testId,
|
|
43
44
|
rest = (0, _objectWithoutProperties2.default)(avatar, _excluded);
|
|
44
45
|
var itemRef = (0, _useRegisterItemWithFocusManager.default)();
|
|
45
46
|
var CustomComponent = function CustomComponent(_ref) {
|
|
@@ -68,7 +69,8 @@ var AvatarGroupItem = /*#__PURE__*/(0, _react.forwardRef)(function (props, ref)
|
|
|
68
69
|
onMouseDown: onMouseDown,
|
|
69
70
|
ref: ref,
|
|
70
71
|
tabIndex: tabIndex,
|
|
71
|
-
"data-testid": testId
|
|
72
|
+
"data-testid": testId,
|
|
73
|
+
role: role
|
|
72
74
|
// eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props
|
|
73
75
|
}, props), children);
|
|
74
76
|
};
|
|
@@ -100,6 +102,7 @@ var AvatarGroupItem = /*#__PURE__*/(0, _react.forwardRef)(function (props, ref)
|
|
|
100
102
|
rel: target === '_blank' ? 'noopener noreferrer' : undefined,
|
|
101
103
|
iconBefore: AvatarIcon,
|
|
102
104
|
testId: testId,
|
|
105
|
+
role: role,
|
|
103
106
|
onClick: function onClick(event) {
|
|
104
107
|
return callback && callback(event, undefined, index);
|
|
105
108
|
}
|
|
@@ -112,7 +115,8 @@ var AvatarGroupItem = /*#__PURE__*/(0, _react.forwardRef)(function (props, ref)
|
|
|
112
115
|
return callback && callback(event, undefined, index);
|
|
113
116
|
},
|
|
114
117
|
iconBefore: AvatarIcon,
|
|
115
|
-
testId: testId
|
|
118
|
+
testId: testId,
|
|
119
|
+
role: role
|
|
116
120
|
}, name);
|
|
117
121
|
}
|
|
118
122
|
return /*#__PURE__*/_react.default.createElement(_menu.CustomItem, {
|
|
@@ -0,0 +1,181 @@
|
|
|
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.MoreDropdownTopLayer = MoreDropdownTopLayer;
|
|
9
|
+
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
|
|
10
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
11
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
12
|
+
var _bindEventListener = require("bind-event-listener");
|
|
13
|
+
var _keycodes = require("@atlaskit/ds-lib/keycodes");
|
|
14
|
+
var _useFocusEvent = _interopRequireDefault(require("@atlaskit/ds-lib/use-focus-event"));
|
|
15
|
+
var _menu = require("@atlaskit/menu");
|
|
16
|
+
var _animations = require("@atlaskit/top-layer/animations");
|
|
17
|
+
var _placementMap = require("@atlaskit/top-layer/placement-map");
|
|
18
|
+
var _popup = require("@atlaskit/top-layer/popup");
|
|
19
|
+
var _useArrowNavigation = require("@atlaskit/top-layer/use-arrow-navigation");
|
|
20
|
+
var _avatarGroupItem = _interopRequireDefault(require("./avatar-group-item"));
|
|
21
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(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 (var _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); }
|
|
22
|
+
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; }
|
|
23
|
+
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) { (0, _defineProperty2.default)(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; }
|
|
24
|
+
var animation = (0, _animations.slideAndFade)();
|
|
25
|
+
var topLayerPlacement = (0, _placementMap.fromLegacyPlacement)({
|
|
26
|
+
legacy: 'bottom-end'
|
|
27
|
+
});
|
|
28
|
+
function getOverrides(overrides) {
|
|
29
|
+
return {
|
|
30
|
+
AvatarGroupItem: _objectSpread({
|
|
31
|
+
render: function render(Component, props, index) {
|
|
32
|
+
return /*#__PURE__*/_react.default.createElement(Component, (0, _extends2.default)({
|
|
33
|
+
key: index
|
|
34
|
+
}, props));
|
|
35
|
+
}
|
|
36
|
+
}, overrides === null || overrides === void 0 ? void 0 : overrides.AvatarGroupItem),
|
|
37
|
+
Avatar: _objectSpread({
|
|
38
|
+
render: function render(Component, props, index) {
|
|
39
|
+
return /*#__PURE__*/_react.default.createElement(Component, (0, _extends2.default)({
|
|
40
|
+
key: index
|
|
41
|
+
}, props));
|
|
42
|
+
}
|
|
43
|
+
}, overrides === null || overrides === void 0 ? void 0 : overrides.Avatar),
|
|
44
|
+
MoreIndicator: _objectSpread({
|
|
45
|
+
render: function render(Component, props) {
|
|
46
|
+
return /*#__PURE__*/_react.default.createElement(Component, props);
|
|
47
|
+
}
|
|
48
|
+
}, overrides === null || overrides === void 0 ? void 0 : overrides.MoreIndicator)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Top-layer implementation of the avatar group overflow dropdown.
|
|
53
|
+
*
|
|
54
|
+
* Replaces the legacy `@atlaskit/popup` rendering pipeline
|
|
55
|
+
* (Popper.js + Portal + focus-trap + @atlaskit/layering)
|
|
56
|
+
* with the native Popover API + CSS Anchor Positioning via `@atlaskit/top-layer`.
|
|
57
|
+
*
|
|
58
|
+
* Uses `role="menu"` with arrow key navigation for correct menu semantics.
|
|
59
|
+
*
|
|
60
|
+
* Gated behind the `platform-dst-top-layer` feature flag.
|
|
61
|
+
*
|
|
62
|
+
* Legacy props that are no-ops in the top-layer path (not accepted here):
|
|
63
|
+
* - zIndex: stacking managed by browser top layer
|
|
64
|
+
* - shouldRenderToParent: always renders in top layer
|
|
65
|
+
* - boundary / rootBoundary: viewport is the natural boundary
|
|
66
|
+
* - shouldFlip: CSS Anchor Positioning handles flipping
|
|
67
|
+
*/
|
|
68
|
+
function MoreDropdownTopLayer(_ref) {
|
|
69
|
+
var isOpen = _ref.isOpen,
|
|
70
|
+
onClose = _ref.onClose,
|
|
71
|
+
_isTriggeredUsingKeyboard = _ref.isTriggeredUsingKeyboard,
|
|
72
|
+
data = _ref.data,
|
|
73
|
+
max = _ref.max,
|
|
74
|
+
overrides = _ref.overrides,
|
|
75
|
+
onAvatarClick = _ref.onAvatarClick,
|
|
76
|
+
testId = _ref.testId,
|
|
77
|
+
labelId = _ref.labelId,
|
|
78
|
+
renderMoreButton = _ref.renderMoreButton,
|
|
79
|
+
handleTriggerClicked = _ref.handleTriggerClicked,
|
|
80
|
+
_bindFocus = _ref.bindFocus;
|
|
81
|
+
var handleOnClose = (0, _react.useCallback)(function (_ref2) {
|
|
82
|
+
var _reason = _ref2.reason;
|
|
83
|
+
onClose();
|
|
84
|
+
}, [onClose]);
|
|
85
|
+
var menuRef = (0, _react.useRef)(null);
|
|
86
|
+
var overflowMenuTestId = testId ? "".concat(testId, "--overflow-menu") : undefined;
|
|
87
|
+
|
|
88
|
+
// Arrow key navigation inside the open menu
|
|
89
|
+
(0, _useArrowNavigation.useArrowNavigation)({
|
|
90
|
+
containerRef: menuRef,
|
|
91
|
+
onClose: onClose,
|
|
92
|
+
isEnabled: isOpen
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// ArrowDown-to-open: when the trigger is focused and the menu is closed,
|
|
96
|
+
// pressing ArrowDown opens the menu (WAI-ARIA menu button pattern).
|
|
97
|
+
// We track focus on the trigger wrapper to avoid threading onFocus/onBlur
|
|
98
|
+
// through the renderMoreButton/MoreIndicator prop plumbing.
|
|
99
|
+
var triggerWrapperRef = (0, _react.useRef)(null);
|
|
100
|
+
var _useFocus = (0, _useFocusEvent.default)(),
|
|
101
|
+
isFocused = _useFocus.isFocused,
|
|
102
|
+
triggerFocusBind = _useFocus.bindFocus;
|
|
103
|
+
(0, _react.useEffect)(function () {
|
|
104
|
+
if (!isFocused || isOpen) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
return (0, _bindEventListener.bind)(window, {
|
|
108
|
+
type: 'keydown',
|
|
109
|
+
listener: function openOnArrowDown(e) {
|
|
110
|
+
if (e.key === _keycodes.KEY_DOWN) {
|
|
111
|
+
// Prevent page scroll when opening the menu via ArrowDown.
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
handleTriggerClicked(e);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}, [isFocused, isOpen, handleTriggerClicked]);
|
|
118
|
+
return /*#__PURE__*/_react.default.createElement(_popup.Popup, {
|
|
119
|
+
placement: topLayerPlacement,
|
|
120
|
+
onClose: handleOnClose,
|
|
121
|
+
testId: overflowMenuTestId
|
|
122
|
+
}, /*#__PURE__*/_react.default.createElement(_popup.Popup.TriggerFunction, null, function (_ref3) {
|
|
123
|
+
var ref = _ref3.ref,
|
|
124
|
+
ariaAttributes = _ref3.ariaAttributes;
|
|
125
|
+
return (
|
|
126
|
+
/*#__PURE__*/
|
|
127
|
+
// Workaround: wrapping span to track trigger focus for ArrowDown-to-open.
|
|
128
|
+
//
|
|
129
|
+
// The `useFocus` hook needs onFocus/onBlur on the trigger element,
|
|
130
|
+
// but we cannot thread those through the renderMoreButton → MoreIndicator
|
|
131
|
+
// prop plumbing without changing those APIs (onFocus/onBlur would need
|
|
132
|
+
// to go into MoreIndicator's `buttonProps`, but renderMoreButton does not
|
|
133
|
+
// expose that).
|
|
134
|
+
//
|
|
135
|
+
// Using `display: contents` so the span does not affect layout — the
|
|
136
|
+
// button renders as if the span were not there. Focus events from the
|
|
137
|
+
// button bubble up to this span, allowing useFocus to track state.
|
|
138
|
+
//
|
|
139
|
+
// If MoreIndicator's API is refactored to accept focus handlers via
|
|
140
|
+
// buttonProps, this wrapper can be removed.
|
|
141
|
+
_react.default.createElement("span", {
|
|
142
|
+
ref: triggerWrapperRef,
|
|
143
|
+
onFocus: triggerFocusBind.onFocus,
|
|
144
|
+
onBlur: triggerFocusBind.onBlur
|
|
145
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- display: contents is a layout-neutral wrapper; it cannot affect visual output.
|
|
146
|
+
,
|
|
147
|
+
style: {
|
|
148
|
+
display: 'contents'
|
|
149
|
+
}
|
|
150
|
+
}, renderMoreButton({
|
|
151
|
+
ref: ref,
|
|
152
|
+
'aria-controls': isOpen ? ariaAttributes['aria-controls'] : undefined,
|
|
153
|
+
'aria-expanded': isOpen,
|
|
154
|
+
'aria-haspopup': true,
|
|
155
|
+
onClick: handleTriggerClicked
|
|
156
|
+
}))
|
|
157
|
+
);
|
|
158
|
+
}), /*#__PURE__*/_react.default.createElement(_popup.Popup.Content, {
|
|
159
|
+
role: "menu",
|
|
160
|
+
label: "avatar group",
|
|
161
|
+
isOpen: isOpen,
|
|
162
|
+
animate: animation,
|
|
163
|
+
testId: overflowMenuTestId ? "".concat(overflowMenuTestId, "--content") : undefined
|
|
164
|
+
}, /*#__PURE__*/_react.default.createElement(_popup.Popup.Surface, null, /*#__PURE__*/_react.default.createElement("div", {
|
|
165
|
+
ref: menuRef
|
|
166
|
+
}, /*#__PURE__*/_react.default.createElement(_menu.MenuGroup, {
|
|
167
|
+
minWidth: 250,
|
|
168
|
+
maxHeight: 300
|
|
169
|
+
}, /*#__PURE__*/_react.default.createElement(_menu.Section, {
|
|
170
|
+
titleId: labelId,
|
|
171
|
+
testId: testId ? "".concat(testId, "--section") : undefined
|
|
172
|
+
}, data.slice(max).map(function (avatar, index) {
|
|
173
|
+
return getOverrides(overrides).AvatarGroupItem.render(_avatarGroupItem.default, {
|
|
174
|
+
avatar: avatar,
|
|
175
|
+
onAvatarClick: onAvatarClick,
|
|
176
|
+
testId: testId ? "".concat(testId, "--avatar-group-item-").concat(index + max) : undefined,
|
|
177
|
+
index: index + max,
|
|
178
|
+
role: 'menuitem'
|
|
179
|
+
}, index + max);
|
|
180
|
+
})))))));
|
|
181
|
+
}
|
|
@@ -23,6 +23,7 @@ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
|
23
23
|
var _popup = _interopRequireDefault(require("@atlaskit/popup"));
|
|
24
24
|
var _tooltip = _interopRequireDefault(require("@atlaskit/tooltip"));
|
|
25
25
|
var _avatarGroupItem = _interopRequireDefault(require("./avatar-group-item"));
|
|
26
|
+
var _avatarGroupTopLayer = require("./avatar-group-top-layer");
|
|
26
27
|
var _grid = _interopRequireDefault(require("./grid"));
|
|
27
28
|
var _focusManager = _interopRequireDefault(require("./internal/components/focus-manager"));
|
|
28
29
|
var _popupAvatarGroup = _interopRequireDefault(require("./internal/components/popup-avatar-group"));
|
|
@@ -129,13 +130,21 @@ var AvatarGroup = function AvatarGroup(_ref) {
|
|
|
129
130
|
bindFocus = _useFocus.bindFocus;
|
|
130
131
|
|
|
131
132
|
// When a trigger is focused, we want to open the popup
|
|
132
|
-
// the user presses the DownArrow
|
|
133
|
+
// the user presses the DownArrow.
|
|
134
|
+
// Skipped when top-layer is enabled — DropdownMenu/top-layer handles
|
|
135
|
+
// ArrowDown-to-open and arrow key navigation internally.
|
|
133
136
|
(0, _react.useEffect)(function () {
|
|
134
137
|
// Set initial value if popup is closed
|
|
135
138
|
if (!isOpen) {
|
|
136
139
|
setTriggeredUsingKeyboard(false);
|
|
137
140
|
}
|
|
138
141
|
|
|
142
|
+
// Top-layer path: ArrowDown-to-open is handled by the menu's
|
|
143
|
+
// own focus management, so skip the parent's ArrowDown handler.
|
|
144
|
+
if ((0, _platformFeatureFlags.fg)('platform-dst-top-layer')) {
|
|
145
|
+
return _noop.default;
|
|
146
|
+
}
|
|
147
|
+
|
|
139
148
|
// Only need to listen for keydown when focused
|
|
140
149
|
if (!isFocused) {
|
|
141
150
|
return _noop.default;
|
|
@@ -203,6 +212,24 @@ var AvatarGroup = function AvatarGroup(_ref) {
|
|
|
203
212
|
onClick: onMoreClick
|
|
204
213
|
});
|
|
205
214
|
}
|
|
215
|
+
if ((0, _platformFeatureFlags.fg)('platform-dst-top-layer')) {
|
|
216
|
+
return /*#__PURE__*/_react.default.createElement(_avatarGroupTopLayer.MoreDropdownTopLayer, {
|
|
217
|
+
isOpen: isOpen,
|
|
218
|
+
onClose: onClose,
|
|
219
|
+
isTriggeredUsingKeyboard: isTriggeredUsingKeyboard,
|
|
220
|
+
data: data,
|
|
221
|
+
max: max
|
|
222
|
+
// eslint-disable-next-line @repo/internal/react/no-unsafe-overrides
|
|
223
|
+
,
|
|
224
|
+
overrides: overrides,
|
|
225
|
+
onAvatarClick: onAvatarClick,
|
|
226
|
+
testId: testId,
|
|
227
|
+
labelId: labelId,
|
|
228
|
+
renderMoreButton: renderMoreButton,
|
|
229
|
+
handleTriggerClicked: handleTriggerClicked,
|
|
230
|
+
bindFocus: bindFocus
|
|
231
|
+
});
|
|
232
|
+
}
|
|
206
233
|
|
|
207
234
|
// split boundariesElement into `boundary` and `rootBoundary` props for Popup
|
|
208
235
|
var boundary = boundariesElement === 'scrollParent' ? 'clippingParents' : undefined;
|
|
@@ -9,28 +9,29 @@ const AvatarGroupItem = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
9
9
|
avatar,
|
|
10
10
|
index,
|
|
11
11
|
onAvatarClick,
|
|
12
|
-
testId
|
|
12
|
+
testId,
|
|
13
|
+
role
|
|
13
14
|
} = props;
|
|
14
15
|
const {
|
|
15
16
|
analyticsContext,
|
|
16
17
|
appearance,
|
|
17
18
|
as,
|
|
18
|
-
borderColor,
|
|
19
|
+
borderColor: _borderColor,
|
|
19
20
|
children,
|
|
20
21
|
href,
|
|
21
22
|
isDisabled,
|
|
22
|
-
key,
|
|
23
|
+
key: _key,
|
|
23
24
|
label,
|
|
24
25
|
name,
|
|
25
26
|
onClick,
|
|
26
27
|
presence,
|
|
27
|
-
size,
|
|
28
|
+
size: _size,
|
|
28
29
|
src,
|
|
29
30
|
stackIndex,
|
|
30
31
|
status,
|
|
31
32
|
tabIndex,
|
|
32
33
|
target,
|
|
33
|
-
testId:
|
|
34
|
+
testId: _groupItemTestId,
|
|
34
35
|
...rest
|
|
35
36
|
} = avatar;
|
|
36
37
|
const itemRef = useRegisterItemWithFocusManager();
|
|
@@ -61,7 +62,8 @@ const AvatarGroupItem = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
61
62
|
onMouseDown: onMouseDown,
|
|
62
63
|
ref: ref,
|
|
63
64
|
tabIndex: tabIndex,
|
|
64
|
-
"data-testid": testId
|
|
65
|
+
"data-testid": testId,
|
|
66
|
+
role: role
|
|
65
67
|
// eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props
|
|
66
68
|
}, props), children);
|
|
67
69
|
};
|
|
@@ -93,6 +95,7 @@ const AvatarGroupItem = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
93
95
|
rel: target === '_blank' ? 'noopener noreferrer' : undefined,
|
|
94
96
|
iconBefore: AvatarIcon,
|
|
95
97
|
testId: testId,
|
|
98
|
+
role: role,
|
|
96
99
|
onClick: event => callback && callback(event, undefined, index)
|
|
97
100
|
}, name);
|
|
98
101
|
}
|
|
@@ -101,7 +104,8 @@ const AvatarGroupItem = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
101
104
|
ref: mergeRefs([ref, itemRef]),
|
|
102
105
|
onClick: event => callback && callback(event, undefined, index),
|
|
103
106
|
iconBefore: AvatarIcon,
|
|
104
|
-
testId: testId
|
|
107
|
+
testId: testId,
|
|
108
|
+
role: role
|
|
105
109
|
}, name);
|
|
106
110
|
}
|
|
107
111
|
return /*#__PURE__*/React.createElement(CustomItem, {
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
import React, { useCallback, useEffect, useRef } from 'react';
|
|
3
|
+
import { bind } from 'bind-event-listener';
|
|
4
|
+
import { KEY_DOWN } from '@atlaskit/ds-lib/keycodes';
|
|
5
|
+
import useFocus from '@atlaskit/ds-lib/use-focus-event';
|
|
6
|
+
import { MenuGroup, Section } from '@atlaskit/menu';
|
|
7
|
+
import { slideAndFade } from '@atlaskit/top-layer/animations';
|
|
8
|
+
import { fromLegacyPlacement } from '@atlaskit/top-layer/placement-map';
|
|
9
|
+
import { Popup } from '@atlaskit/top-layer/popup';
|
|
10
|
+
import { useArrowNavigation } from '@atlaskit/top-layer/use-arrow-navigation';
|
|
11
|
+
import AvatarGroupItem from './avatar-group-item';
|
|
12
|
+
const animation = slideAndFade();
|
|
13
|
+
const topLayerPlacement = fromLegacyPlacement({
|
|
14
|
+
legacy: 'bottom-end'
|
|
15
|
+
});
|
|
16
|
+
function getOverrides(overrides) {
|
|
17
|
+
return {
|
|
18
|
+
AvatarGroupItem: {
|
|
19
|
+
render: (Component, props, index) => /*#__PURE__*/React.createElement(Component, _extends({
|
|
20
|
+
key: index
|
|
21
|
+
}, props)),
|
|
22
|
+
...(overrides === null || overrides === void 0 ? void 0 : overrides.AvatarGroupItem)
|
|
23
|
+
},
|
|
24
|
+
Avatar: {
|
|
25
|
+
render: (Component, props, index) => /*#__PURE__*/React.createElement(Component, _extends({
|
|
26
|
+
key: index
|
|
27
|
+
}, props)),
|
|
28
|
+
...(overrides === null || overrides === void 0 ? void 0 : overrides.Avatar)
|
|
29
|
+
},
|
|
30
|
+
MoreIndicator: {
|
|
31
|
+
render: (Component, props) => /*#__PURE__*/React.createElement(Component, props),
|
|
32
|
+
...(overrides === null || overrides === void 0 ? void 0 : overrides.MoreIndicator)
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Top-layer implementation of the avatar group overflow dropdown.
|
|
38
|
+
*
|
|
39
|
+
* Replaces the legacy `@atlaskit/popup` rendering pipeline
|
|
40
|
+
* (Popper.js + Portal + focus-trap + @atlaskit/layering)
|
|
41
|
+
* with the native Popover API + CSS Anchor Positioning via `@atlaskit/top-layer`.
|
|
42
|
+
*
|
|
43
|
+
* Uses `role="menu"` with arrow key navigation for correct menu semantics.
|
|
44
|
+
*
|
|
45
|
+
* Gated behind the `platform-dst-top-layer` feature flag.
|
|
46
|
+
*
|
|
47
|
+
* Legacy props that are no-ops in the top-layer path (not accepted here):
|
|
48
|
+
* - zIndex: stacking managed by browser top layer
|
|
49
|
+
* - shouldRenderToParent: always renders in top layer
|
|
50
|
+
* - boundary / rootBoundary: viewport is the natural boundary
|
|
51
|
+
* - shouldFlip: CSS Anchor Positioning handles flipping
|
|
52
|
+
*/
|
|
53
|
+
export function MoreDropdownTopLayer({
|
|
54
|
+
isOpen,
|
|
55
|
+
onClose,
|
|
56
|
+
isTriggeredUsingKeyboard: _isTriggeredUsingKeyboard,
|
|
57
|
+
data,
|
|
58
|
+
max,
|
|
59
|
+
overrides,
|
|
60
|
+
onAvatarClick,
|
|
61
|
+
testId,
|
|
62
|
+
labelId,
|
|
63
|
+
renderMoreButton,
|
|
64
|
+
handleTriggerClicked,
|
|
65
|
+
bindFocus: _bindFocus
|
|
66
|
+
}) {
|
|
67
|
+
const handleOnClose = useCallback(({
|
|
68
|
+
reason: _reason
|
|
69
|
+
}) => {
|
|
70
|
+
onClose();
|
|
71
|
+
}, [onClose]);
|
|
72
|
+
const menuRef = useRef(null);
|
|
73
|
+
const overflowMenuTestId = testId ? `${testId}--overflow-menu` : undefined;
|
|
74
|
+
|
|
75
|
+
// Arrow key navigation inside the open menu
|
|
76
|
+
useArrowNavigation({
|
|
77
|
+
containerRef: menuRef,
|
|
78
|
+
onClose,
|
|
79
|
+
isEnabled: isOpen
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// ArrowDown-to-open: when the trigger is focused and the menu is closed,
|
|
83
|
+
// pressing ArrowDown opens the menu (WAI-ARIA menu button pattern).
|
|
84
|
+
// We track focus on the trigger wrapper to avoid threading onFocus/onBlur
|
|
85
|
+
// through the renderMoreButton/MoreIndicator prop plumbing.
|
|
86
|
+
const triggerWrapperRef = useRef(null);
|
|
87
|
+
const {
|
|
88
|
+
isFocused,
|
|
89
|
+
bindFocus: triggerFocusBind
|
|
90
|
+
} = useFocus();
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (!isFocused || isOpen) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
return bind(window, {
|
|
96
|
+
type: 'keydown',
|
|
97
|
+
listener: function openOnArrowDown(e) {
|
|
98
|
+
if (e.key === KEY_DOWN) {
|
|
99
|
+
// Prevent page scroll when opening the menu via ArrowDown.
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
handleTriggerClicked(e);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}, [isFocused, isOpen, handleTriggerClicked]);
|
|
106
|
+
return /*#__PURE__*/React.createElement(Popup, {
|
|
107
|
+
placement: topLayerPlacement,
|
|
108
|
+
onClose: handleOnClose,
|
|
109
|
+
testId: overflowMenuTestId
|
|
110
|
+
}, /*#__PURE__*/React.createElement(Popup.TriggerFunction, null, ({
|
|
111
|
+
ref,
|
|
112
|
+
ariaAttributes
|
|
113
|
+
}) =>
|
|
114
|
+
/*#__PURE__*/
|
|
115
|
+
// Workaround: wrapping span to track trigger focus for ArrowDown-to-open.
|
|
116
|
+
//
|
|
117
|
+
// The `useFocus` hook needs onFocus/onBlur on the trigger element,
|
|
118
|
+
// but we cannot thread those through the renderMoreButton → MoreIndicator
|
|
119
|
+
// prop plumbing without changing those APIs (onFocus/onBlur would need
|
|
120
|
+
// to go into MoreIndicator's `buttonProps`, but renderMoreButton does not
|
|
121
|
+
// expose that).
|
|
122
|
+
//
|
|
123
|
+
// Using `display: contents` so the span does not affect layout — the
|
|
124
|
+
// button renders as if the span were not there. Focus events from the
|
|
125
|
+
// button bubble up to this span, allowing useFocus to track state.
|
|
126
|
+
//
|
|
127
|
+
// If MoreIndicator's API is refactored to accept focus handlers via
|
|
128
|
+
// buttonProps, this wrapper can be removed.
|
|
129
|
+
React.createElement("span", {
|
|
130
|
+
ref: triggerWrapperRef,
|
|
131
|
+
onFocus: triggerFocusBind.onFocus,
|
|
132
|
+
onBlur: triggerFocusBind.onBlur
|
|
133
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- display: contents is a layout-neutral wrapper; it cannot affect visual output.
|
|
134
|
+
,
|
|
135
|
+
style: {
|
|
136
|
+
display: 'contents'
|
|
137
|
+
}
|
|
138
|
+
}, renderMoreButton({
|
|
139
|
+
ref,
|
|
140
|
+
'aria-controls': isOpen ? ariaAttributes['aria-controls'] : undefined,
|
|
141
|
+
'aria-expanded': isOpen,
|
|
142
|
+
'aria-haspopup': true,
|
|
143
|
+
onClick: handleTriggerClicked
|
|
144
|
+
}))), /*#__PURE__*/React.createElement(Popup.Content, {
|
|
145
|
+
role: "menu",
|
|
146
|
+
label: "avatar group",
|
|
147
|
+
isOpen: isOpen,
|
|
148
|
+
animate: animation,
|
|
149
|
+
testId: overflowMenuTestId ? `${overflowMenuTestId}--content` : undefined
|
|
150
|
+
}, /*#__PURE__*/React.createElement(Popup.Surface, null, /*#__PURE__*/React.createElement("div", {
|
|
151
|
+
ref: menuRef
|
|
152
|
+
}, /*#__PURE__*/React.createElement(MenuGroup, {
|
|
153
|
+
minWidth: 250,
|
|
154
|
+
maxHeight: 300
|
|
155
|
+
}, /*#__PURE__*/React.createElement(Section, {
|
|
156
|
+
titleId: labelId,
|
|
157
|
+
testId: testId ? `${testId}--section` : undefined
|
|
158
|
+
}, data.slice(max).map((avatar, index) => getOverrides(overrides).AvatarGroupItem.render(AvatarGroupItem, {
|
|
159
|
+
avatar,
|
|
160
|
+
onAvatarClick,
|
|
161
|
+
testId: testId ? `${testId}--avatar-group-item-${index + max}` : undefined,
|
|
162
|
+
index: index + max,
|
|
163
|
+
role: 'menuitem'
|
|
164
|
+
}, index + max))))))));
|
|
165
|
+
}
|
|
@@ -12,6 +12,7 @@ import { fg } from '@atlaskit/platform-feature-flags';
|
|
|
12
12
|
import Popup from '@atlaskit/popup';
|
|
13
13
|
import Tooltip from '@atlaskit/tooltip';
|
|
14
14
|
import AvatarGroupItem from './avatar-group-item';
|
|
15
|
+
import { MoreDropdownTopLayer } from './avatar-group-top-layer';
|
|
15
16
|
import Grid from './grid';
|
|
16
17
|
import FocusManager from './internal/components/focus-manager';
|
|
17
18
|
import PopupAvatarGroup from './internal/components/popup-avatar-group';
|
|
@@ -96,13 +97,21 @@ const AvatarGroup = ({
|
|
|
96
97
|
} = useFocus();
|
|
97
98
|
|
|
98
99
|
// When a trigger is focused, we want to open the popup
|
|
99
|
-
// the user presses the DownArrow
|
|
100
|
+
// the user presses the DownArrow.
|
|
101
|
+
// Skipped when top-layer is enabled — DropdownMenu/top-layer handles
|
|
102
|
+
// ArrowDown-to-open and arrow key navigation internally.
|
|
100
103
|
useEffect(() => {
|
|
101
104
|
// Set initial value if popup is closed
|
|
102
105
|
if (!isOpen) {
|
|
103
106
|
setTriggeredUsingKeyboard(false);
|
|
104
107
|
}
|
|
105
108
|
|
|
109
|
+
// Top-layer path: ArrowDown-to-open is handled by the menu's
|
|
110
|
+
// own focus management, so skip the parent's ArrowDown handler.
|
|
111
|
+
if (fg('platform-dst-top-layer')) {
|
|
112
|
+
return noop;
|
|
113
|
+
}
|
|
114
|
+
|
|
106
115
|
// Only need to listen for keydown when focused
|
|
107
116
|
if (!isFocused) {
|
|
108
117
|
return noop;
|
|
@@ -171,6 +180,24 @@ const AvatarGroup = ({
|
|
|
171
180
|
onClick: onMoreClick
|
|
172
181
|
});
|
|
173
182
|
}
|
|
183
|
+
if (fg('platform-dst-top-layer')) {
|
|
184
|
+
return /*#__PURE__*/React.createElement(MoreDropdownTopLayer, {
|
|
185
|
+
isOpen: isOpen,
|
|
186
|
+
onClose: onClose,
|
|
187
|
+
isTriggeredUsingKeyboard: isTriggeredUsingKeyboard,
|
|
188
|
+
data: data,
|
|
189
|
+
max: max
|
|
190
|
+
// eslint-disable-next-line @repo/internal/react/no-unsafe-overrides
|
|
191
|
+
,
|
|
192
|
+
overrides: overrides,
|
|
193
|
+
onAvatarClick: onAvatarClick,
|
|
194
|
+
testId: testId,
|
|
195
|
+
labelId: labelId,
|
|
196
|
+
renderMoreButton: renderMoreButton,
|
|
197
|
+
handleTriggerClicked: handleTriggerClicked,
|
|
198
|
+
bindFocus: bindFocus
|
|
199
|
+
});
|
|
200
|
+
}
|
|
174
201
|
|
|
175
202
|
// split boundariesElement into `boundary` and `rootBoundary` props for Popup
|
|
176
203
|
const boundary = boundariesElement === 'scrollParent' ? 'clippingParents' : undefined;
|
|
@@ -11,26 +11,27 @@ var AvatarGroupItem = /*#__PURE__*/forwardRef(function (props, ref) {
|
|
|
11
11
|
var avatar = props.avatar,
|
|
12
12
|
index = props.index,
|
|
13
13
|
onAvatarClick = props.onAvatarClick,
|
|
14
|
-
testId = props.testId
|
|
14
|
+
testId = props.testId,
|
|
15
|
+
role = props.role;
|
|
15
16
|
var analyticsContext = avatar.analyticsContext,
|
|
16
17
|
appearance = avatar.appearance,
|
|
17
18
|
as = avatar.as,
|
|
18
|
-
|
|
19
|
+
_borderColor = avatar.borderColor,
|
|
19
20
|
children = avatar.children,
|
|
20
21
|
href = avatar.href,
|
|
21
22
|
isDisabled = avatar.isDisabled,
|
|
22
|
-
|
|
23
|
+
_key = avatar.key,
|
|
23
24
|
label = avatar.label,
|
|
24
25
|
name = avatar.name,
|
|
25
26
|
onClick = avatar.onClick,
|
|
26
27
|
presence = avatar.presence,
|
|
27
|
-
|
|
28
|
+
_size = avatar.size,
|
|
28
29
|
src = avatar.src,
|
|
29
30
|
stackIndex = avatar.stackIndex,
|
|
30
31
|
status = avatar.status,
|
|
31
32
|
tabIndex = avatar.tabIndex,
|
|
32
33
|
target = avatar.target,
|
|
33
|
-
|
|
34
|
+
_groupItemTestId = avatar.testId,
|
|
34
35
|
rest = _objectWithoutProperties(avatar, _excluded);
|
|
35
36
|
var itemRef = useRegisterItemWithFocusManager();
|
|
36
37
|
var CustomComponent = function CustomComponent(_ref) {
|
|
@@ -59,7 +60,8 @@ var AvatarGroupItem = /*#__PURE__*/forwardRef(function (props, ref) {
|
|
|
59
60
|
onMouseDown: onMouseDown,
|
|
60
61
|
ref: ref,
|
|
61
62
|
tabIndex: tabIndex,
|
|
62
|
-
"data-testid": testId
|
|
63
|
+
"data-testid": testId,
|
|
64
|
+
role: role
|
|
63
65
|
// eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props
|
|
64
66
|
}, props), children);
|
|
65
67
|
};
|
|
@@ -91,6 +93,7 @@ var AvatarGroupItem = /*#__PURE__*/forwardRef(function (props, ref) {
|
|
|
91
93
|
rel: target === '_blank' ? 'noopener noreferrer' : undefined,
|
|
92
94
|
iconBefore: AvatarIcon,
|
|
93
95
|
testId: testId,
|
|
96
|
+
role: role,
|
|
94
97
|
onClick: function onClick(event) {
|
|
95
98
|
return callback && callback(event, undefined, index);
|
|
96
99
|
}
|
|
@@ -103,7 +106,8 @@ var AvatarGroupItem = /*#__PURE__*/forwardRef(function (props, ref) {
|
|
|
103
106
|
return callback && callback(event, undefined, index);
|
|
104
107
|
},
|
|
105
108
|
iconBefore: AvatarIcon,
|
|
106
|
-
testId: testId
|
|
109
|
+
testId: testId,
|
|
110
|
+
role: role
|
|
107
111
|
}, name);
|
|
108
112
|
}
|
|
109
113
|
return /*#__PURE__*/React.createElement(CustomItem, {
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
3
|
+
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; }
|
|
4
|
+
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; }
|
|
5
|
+
import React, { useCallback, useEffect, useRef } from 'react';
|
|
6
|
+
import { bind } from 'bind-event-listener';
|
|
7
|
+
import { KEY_DOWN } from '@atlaskit/ds-lib/keycodes';
|
|
8
|
+
import useFocus from '@atlaskit/ds-lib/use-focus-event';
|
|
9
|
+
import { MenuGroup, Section } from '@atlaskit/menu';
|
|
10
|
+
import { slideAndFade } from '@atlaskit/top-layer/animations';
|
|
11
|
+
import { fromLegacyPlacement } from '@atlaskit/top-layer/placement-map';
|
|
12
|
+
import { Popup } from '@atlaskit/top-layer/popup';
|
|
13
|
+
import { useArrowNavigation } from '@atlaskit/top-layer/use-arrow-navigation';
|
|
14
|
+
import AvatarGroupItem from './avatar-group-item';
|
|
15
|
+
var animation = slideAndFade();
|
|
16
|
+
var topLayerPlacement = fromLegacyPlacement({
|
|
17
|
+
legacy: 'bottom-end'
|
|
18
|
+
});
|
|
19
|
+
function getOverrides(overrides) {
|
|
20
|
+
return {
|
|
21
|
+
AvatarGroupItem: _objectSpread({
|
|
22
|
+
render: function render(Component, props, index) {
|
|
23
|
+
return /*#__PURE__*/React.createElement(Component, _extends({
|
|
24
|
+
key: index
|
|
25
|
+
}, props));
|
|
26
|
+
}
|
|
27
|
+
}, overrides === null || overrides === void 0 ? void 0 : overrides.AvatarGroupItem),
|
|
28
|
+
Avatar: _objectSpread({
|
|
29
|
+
render: function render(Component, props, index) {
|
|
30
|
+
return /*#__PURE__*/React.createElement(Component, _extends({
|
|
31
|
+
key: index
|
|
32
|
+
}, props));
|
|
33
|
+
}
|
|
34
|
+
}, overrides === null || overrides === void 0 ? void 0 : overrides.Avatar),
|
|
35
|
+
MoreIndicator: _objectSpread({
|
|
36
|
+
render: function render(Component, props) {
|
|
37
|
+
return /*#__PURE__*/React.createElement(Component, props);
|
|
38
|
+
}
|
|
39
|
+
}, overrides === null || overrides === void 0 ? void 0 : overrides.MoreIndicator)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Top-layer implementation of the avatar group overflow dropdown.
|
|
44
|
+
*
|
|
45
|
+
* Replaces the legacy `@atlaskit/popup` rendering pipeline
|
|
46
|
+
* (Popper.js + Portal + focus-trap + @atlaskit/layering)
|
|
47
|
+
* with the native Popover API + CSS Anchor Positioning via `@atlaskit/top-layer`.
|
|
48
|
+
*
|
|
49
|
+
* Uses `role="menu"` with arrow key navigation for correct menu semantics.
|
|
50
|
+
*
|
|
51
|
+
* Gated behind the `platform-dst-top-layer` feature flag.
|
|
52
|
+
*
|
|
53
|
+
* Legacy props that are no-ops in the top-layer path (not accepted here):
|
|
54
|
+
* - zIndex: stacking managed by browser top layer
|
|
55
|
+
* - shouldRenderToParent: always renders in top layer
|
|
56
|
+
* - boundary / rootBoundary: viewport is the natural boundary
|
|
57
|
+
* - shouldFlip: CSS Anchor Positioning handles flipping
|
|
58
|
+
*/
|
|
59
|
+
export function MoreDropdownTopLayer(_ref) {
|
|
60
|
+
var isOpen = _ref.isOpen,
|
|
61
|
+
onClose = _ref.onClose,
|
|
62
|
+
_isTriggeredUsingKeyboard = _ref.isTriggeredUsingKeyboard,
|
|
63
|
+
data = _ref.data,
|
|
64
|
+
max = _ref.max,
|
|
65
|
+
overrides = _ref.overrides,
|
|
66
|
+
onAvatarClick = _ref.onAvatarClick,
|
|
67
|
+
testId = _ref.testId,
|
|
68
|
+
labelId = _ref.labelId,
|
|
69
|
+
renderMoreButton = _ref.renderMoreButton,
|
|
70
|
+
handleTriggerClicked = _ref.handleTriggerClicked,
|
|
71
|
+
_bindFocus = _ref.bindFocus;
|
|
72
|
+
var handleOnClose = useCallback(function (_ref2) {
|
|
73
|
+
var _reason = _ref2.reason;
|
|
74
|
+
onClose();
|
|
75
|
+
}, [onClose]);
|
|
76
|
+
var menuRef = useRef(null);
|
|
77
|
+
var overflowMenuTestId = testId ? "".concat(testId, "--overflow-menu") : undefined;
|
|
78
|
+
|
|
79
|
+
// Arrow key navigation inside the open menu
|
|
80
|
+
useArrowNavigation({
|
|
81
|
+
containerRef: menuRef,
|
|
82
|
+
onClose: onClose,
|
|
83
|
+
isEnabled: isOpen
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// ArrowDown-to-open: when the trigger is focused and the menu is closed,
|
|
87
|
+
// pressing ArrowDown opens the menu (WAI-ARIA menu button pattern).
|
|
88
|
+
// We track focus on the trigger wrapper to avoid threading onFocus/onBlur
|
|
89
|
+
// through the renderMoreButton/MoreIndicator prop plumbing.
|
|
90
|
+
var triggerWrapperRef = useRef(null);
|
|
91
|
+
var _useFocus = useFocus(),
|
|
92
|
+
isFocused = _useFocus.isFocused,
|
|
93
|
+
triggerFocusBind = _useFocus.bindFocus;
|
|
94
|
+
useEffect(function () {
|
|
95
|
+
if (!isFocused || isOpen) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
return bind(window, {
|
|
99
|
+
type: 'keydown',
|
|
100
|
+
listener: function openOnArrowDown(e) {
|
|
101
|
+
if (e.key === KEY_DOWN) {
|
|
102
|
+
// Prevent page scroll when opening the menu via ArrowDown.
|
|
103
|
+
e.preventDefault();
|
|
104
|
+
handleTriggerClicked(e);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}, [isFocused, isOpen, handleTriggerClicked]);
|
|
109
|
+
return /*#__PURE__*/React.createElement(Popup, {
|
|
110
|
+
placement: topLayerPlacement,
|
|
111
|
+
onClose: handleOnClose,
|
|
112
|
+
testId: overflowMenuTestId
|
|
113
|
+
}, /*#__PURE__*/React.createElement(Popup.TriggerFunction, null, function (_ref3) {
|
|
114
|
+
var ref = _ref3.ref,
|
|
115
|
+
ariaAttributes = _ref3.ariaAttributes;
|
|
116
|
+
return (
|
|
117
|
+
/*#__PURE__*/
|
|
118
|
+
// Workaround: wrapping span to track trigger focus for ArrowDown-to-open.
|
|
119
|
+
//
|
|
120
|
+
// The `useFocus` hook needs onFocus/onBlur on the trigger element,
|
|
121
|
+
// but we cannot thread those through the renderMoreButton → MoreIndicator
|
|
122
|
+
// prop plumbing without changing those APIs (onFocus/onBlur would need
|
|
123
|
+
// to go into MoreIndicator's `buttonProps`, but renderMoreButton does not
|
|
124
|
+
// expose that).
|
|
125
|
+
//
|
|
126
|
+
// Using `display: contents` so the span does not affect layout — the
|
|
127
|
+
// button renders as if the span were not there. Focus events from the
|
|
128
|
+
// button bubble up to this span, allowing useFocus to track state.
|
|
129
|
+
//
|
|
130
|
+
// If MoreIndicator's API is refactored to accept focus handlers via
|
|
131
|
+
// buttonProps, this wrapper can be removed.
|
|
132
|
+
React.createElement("span", {
|
|
133
|
+
ref: triggerWrapperRef,
|
|
134
|
+
onFocus: triggerFocusBind.onFocus,
|
|
135
|
+
onBlur: triggerFocusBind.onBlur
|
|
136
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- display: contents is a layout-neutral wrapper; it cannot affect visual output.
|
|
137
|
+
,
|
|
138
|
+
style: {
|
|
139
|
+
display: 'contents'
|
|
140
|
+
}
|
|
141
|
+
}, renderMoreButton({
|
|
142
|
+
ref: ref,
|
|
143
|
+
'aria-controls': isOpen ? ariaAttributes['aria-controls'] : undefined,
|
|
144
|
+
'aria-expanded': isOpen,
|
|
145
|
+
'aria-haspopup': true,
|
|
146
|
+
onClick: handleTriggerClicked
|
|
147
|
+
}))
|
|
148
|
+
);
|
|
149
|
+
}), /*#__PURE__*/React.createElement(Popup.Content, {
|
|
150
|
+
role: "menu",
|
|
151
|
+
label: "avatar group",
|
|
152
|
+
isOpen: isOpen,
|
|
153
|
+
animate: animation,
|
|
154
|
+
testId: overflowMenuTestId ? "".concat(overflowMenuTestId, "--content") : undefined
|
|
155
|
+
}, /*#__PURE__*/React.createElement(Popup.Surface, null, /*#__PURE__*/React.createElement("div", {
|
|
156
|
+
ref: menuRef
|
|
157
|
+
}, /*#__PURE__*/React.createElement(MenuGroup, {
|
|
158
|
+
minWidth: 250,
|
|
159
|
+
maxHeight: 300
|
|
160
|
+
}, /*#__PURE__*/React.createElement(Section, {
|
|
161
|
+
titleId: labelId,
|
|
162
|
+
testId: testId ? "".concat(testId, "--section") : undefined
|
|
163
|
+
}, data.slice(max).map(function (avatar, index) {
|
|
164
|
+
return getOverrides(overrides).AvatarGroupItem.render(AvatarGroupItem, {
|
|
165
|
+
avatar: avatar,
|
|
166
|
+
onAvatarClick: onAvatarClick,
|
|
167
|
+
testId: testId ? "".concat(testId, "--avatar-group-item-").concat(index + max) : undefined,
|
|
168
|
+
index: index + max,
|
|
169
|
+
role: 'menuitem'
|
|
170
|
+
}, index + max);
|
|
171
|
+
})))))));
|
|
172
|
+
}
|
|
@@ -18,6 +18,7 @@ import { fg } from '@atlaskit/platform-feature-flags';
|
|
|
18
18
|
import Popup from '@atlaskit/popup';
|
|
19
19
|
import Tooltip from '@atlaskit/tooltip';
|
|
20
20
|
import AvatarGroupItem from './avatar-group-item';
|
|
21
|
+
import { MoreDropdownTopLayer } from './avatar-group-top-layer';
|
|
21
22
|
import Grid from './grid';
|
|
22
23
|
import FocusManager from './internal/components/focus-manager';
|
|
23
24
|
import PopupAvatarGroup from './internal/components/popup-avatar-group';
|
|
@@ -120,13 +121,21 @@ var AvatarGroup = function AvatarGroup(_ref) {
|
|
|
120
121
|
bindFocus = _useFocus.bindFocus;
|
|
121
122
|
|
|
122
123
|
// When a trigger is focused, we want to open the popup
|
|
123
|
-
// the user presses the DownArrow
|
|
124
|
+
// the user presses the DownArrow.
|
|
125
|
+
// Skipped when top-layer is enabled — DropdownMenu/top-layer handles
|
|
126
|
+
// ArrowDown-to-open and arrow key navigation internally.
|
|
124
127
|
useEffect(function () {
|
|
125
128
|
// Set initial value if popup is closed
|
|
126
129
|
if (!isOpen) {
|
|
127
130
|
setTriggeredUsingKeyboard(false);
|
|
128
131
|
}
|
|
129
132
|
|
|
133
|
+
// Top-layer path: ArrowDown-to-open is handled by the menu's
|
|
134
|
+
// own focus management, so skip the parent's ArrowDown handler.
|
|
135
|
+
if (fg('platform-dst-top-layer')) {
|
|
136
|
+
return noop;
|
|
137
|
+
}
|
|
138
|
+
|
|
130
139
|
// Only need to listen for keydown when focused
|
|
131
140
|
if (!isFocused) {
|
|
132
141
|
return noop;
|
|
@@ -194,6 +203,24 @@ var AvatarGroup = function AvatarGroup(_ref) {
|
|
|
194
203
|
onClick: onMoreClick
|
|
195
204
|
});
|
|
196
205
|
}
|
|
206
|
+
if (fg('platform-dst-top-layer')) {
|
|
207
|
+
return /*#__PURE__*/React.createElement(MoreDropdownTopLayer, {
|
|
208
|
+
isOpen: isOpen,
|
|
209
|
+
onClose: onClose,
|
|
210
|
+
isTriggeredUsingKeyboard: isTriggeredUsingKeyboard,
|
|
211
|
+
data: data,
|
|
212
|
+
max: max
|
|
213
|
+
// eslint-disable-next-line @repo/internal/react/no-unsafe-overrides
|
|
214
|
+
,
|
|
215
|
+
overrides: overrides,
|
|
216
|
+
onAvatarClick: onAvatarClick,
|
|
217
|
+
testId: testId,
|
|
218
|
+
labelId: labelId,
|
|
219
|
+
renderMoreButton: renderMoreButton,
|
|
220
|
+
handleTriggerClicked: handleTriggerClicked,
|
|
221
|
+
bindFocus: bindFocus
|
|
222
|
+
});
|
|
223
|
+
}
|
|
197
224
|
|
|
198
225
|
// split boundariesElement into `boundary` and `rootBoundary` props for Popup
|
|
199
226
|
var boundary = boundariesElement === 'scrollParent' ? 'clippingParents' : undefined;
|
|
@@ -7,6 +7,11 @@ export interface AvatarGroupItemProps {
|
|
|
7
7
|
index: number;
|
|
8
8
|
onAvatarClick?: onAvatarClickHandler;
|
|
9
9
|
testId?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Use this to override the accessibility role for the element.
|
|
12
|
+
* When used inside a dropdown menu, this should be set to "menuitem".
|
|
13
|
+
*/
|
|
14
|
+
role?: string;
|
|
10
15
|
}
|
|
11
16
|
declare const AvatarGroupItem: React.ForwardRefExoticComponent<React.PropsWithoutRef<AvatarGroupItemProps> & React.RefAttributes<HTMLElement>>;
|
|
12
17
|
export default AvatarGroupItem;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React, { type MouseEventHandler } from 'react';
|
|
2
|
+
import { type AvatarGroupOverrides, type AvatarProps, type onAvatarClickHandler } from './types';
|
|
3
|
+
type TMoreDropdownTopLayerProps = {
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
isTriggeredUsingKeyboard: boolean;
|
|
7
|
+
data: Array<AvatarProps>;
|
|
8
|
+
max: number;
|
|
9
|
+
overrides?: AvatarGroupOverrides;
|
|
10
|
+
onAvatarClick?: onAvatarClickHandler;
|
|
11
|
+
testId?: string;
|
|
12
|
+
labelId: string;
|
|
13
|
+
renderMoreButton: (props: {
|
|
14
|
+
'aria-controls'?: string;
|
|
15
|
+
'aria-expanded'?: boolean;
|
|
16
|
+
'aria-haspopup'?: boolean | 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid';
|
|
17
|
+
onClick: MouseEventHandler;
|
|
18
|
+
ref?: React.Ref<HTMLElement>;
|
|
19
|
+
}) => React.ReactNode;
|
|
20
|
+
handleTriggerClicked: (event: React.MouseEvent | KeyboardEvent) => void;
|
|
21
|
+
bindFocus: {
|
|
22
|
+
onFocus: (event: React.FocusEvent) => void;
|
|
23
|
+
onBlur: (event: React.FocusEvent) => void;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Top-layer implementation of the avatar group overflow dropdown.
|
|
28
|
+
*
|
|
29
|
+
* Replaces the legacy `@atlaskit/popup` rendering pipeline
|
|
30
|
+
* (Popper.js + Portal + focus-trap + @atlaskit/layering)
|
|
31
|
+
* with the native Popover API + CSS Anchor Positioning via `@atlaskit/top-layer`.
|
|
32
|
+
*
|
|
33
|
+
* Uses `role="menu"` with arrow key navigation for correct menu semantics.
|
|
34
|
+
*
|
|
35
|
+
* Gated behind the `platform-dst-top-layer` feature flag.
|
|
36
|
+
*
|
|
37
|
+
* Legacy props that are no-ops in the top-layer path (not accepted here):
|
|
38
|
+
* - zIndex: stacking managed by browser top layer
|
|
39
|
+
* - shouldRenderToParent: always renders in top layer
|
|
40
|
+
* - boundary / rootBoundary: viewport is the natural boundary
|
|
41
|
+
* - shouldFlip: CSS Anchor Positioning handles flipping
|
|
42
|
+
*/
|
|
43
|
+
export declare function MoreDropdownTopLayer({ isOpen, onClose, isTriggeredUsingKeyboard: _isTriggeredUsingKeyboard, data, max, overrides, onAvatarClick, testId, labelId, renderMoreButton, handleTriggerClicked, bindFocus: _bindFocus, }: TMoreDropdownTopLayerProps): React.JSX.Element;
|
|
44
|
+
export {};
|
|
@@ -4,7 +4,7 @@ export interface MoreIndicatorProps {
|
|
|
4
4
|
count: number;
|
|
5
5
|
'aria-controls'?: string;
|
|
6
6
|
'aria-expanded'?: boolean;
|
|
7
|
-
'aria-haspopup'?: boolean | 'dialog';
|
|
7
|
+
'aria-haspopup'?: boolean | 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid';
|
|
8
8
|
moreIndicatorLabel?: string;
|
|
9
9
|
buttonProps: Partial<React.HTMLAttributes<HTMLElement>>;
|
|
10
10
|
onClick: AvatarClickEventHandler;
|
|
@@ -7,6 +7,11 @@ export interface AvatarGroupItemProps {
|
|
|
7
7
|
index: number;
|
|
8
8
|
onAvatarClick?: onAvatarClickHandler;
|
|
9
9
|
testId?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Use this to override the accessibility role for the element.
|
|
12
|
+
* When used inside a dropdown menu, this should be set to "menuitem".
|
|
13
|
+
*/
|
|
14
|
+
role?: string;
|
|
10
15
|
}
|
|
11
16
|
declare const AvatarGroupItem: React.ForwardRefExoticComponent<React.PropsWithoutRef<AvatarGroupItemProps> & React.RefAttributes<HTMLElement>>;
|
|
12
17
|
export default AvatarGroupItem;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React, { type MouseEventHandler } from 'react';
|
|
2
|
+
import { type AvatarGroupOverrides, type AvatarProps, type onAvatarClickHandler } from './types';
|
|
3
|
+
type TMoreDropdownTopLayerProps = {
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
isTriggeredUsingKeyboard: boolean;
|
|
7
|
+
data: Array<AvatarProps>;
|
|
8
|
+
max: number;
|
|
9
|
+
overrides?: AvatarGroupOverrides;
|
|
10
|
+
onAvatarClick?: onAvatarClickHandler;
|
|
11
|
+
testId?: string;
|
|
12
|
+
labelId: string;
|
|
13
|
+
renderMoreButton: (props: {
|
|
14
|
+
'aria-controls'?: string;
|
|
15
|
+
'aria-expanded'?: boolean;
|
|
16
|
+
'aria-haspopup'?: boolean | 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid';
|
|
17
|
+
onClick: MouseEventHandler;
|
|
18
|
+
ref?: React.Ref<HTMLElement>;
|
|
19
|
+
}) => React.ReactNode;
|
|
20
|
+
handleTriggerClicked: (event: React.MouseEvent | KeyboardEvent) => void;
|
|
21
|
+
bindFocus: {
|
|
22
|
+
onFocus: (event: React.FocusEvent) => void;
|
|
23
|
+
onBlur: (event: React.FocusEvent) => void;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Top-layer implementation of the avatar group overflow dropdown.
|
|
28
|
+
*
|
|
29
|
+
* Replaces the legacy `@atlaskit/popup` rendering pipeline
|
|
30
|
+
* (Popper.js + Portal + focus-trap + @atlaskit/layering)
|
|
31
|
+
* with the native Popover API + CSS Anchor Positioning via `@atlaskit/top-layer`.
|
|
32
|
+
*
|
|
33
|
+
* Uses `role="menu"` with arrow key navigation for correct menu semantics.
|
|
34
|
+
*
|
|
35
|
+
* Gated behind the `platform-dst-top-layer` feature flag.
|
|
36
|
+
*
|
|
37
|
+
* Legacy props that are no-ops in the top-layer path (not accepted here):
|
|
38
|
+
* - zIndex: stacking managed by browser top layer
|
|
39
|
+
* - shouldRenderToParent: always renders in top layer
|
|
40
|
+
* - boundary / rootBoundary: viewport is the natural boundary
|
|
41
|
+
* - shouldFlip: CSS Anchor Positioning handles flipping
|
|
42
|
+
*/
|
|
43
|
+
export declare function MoreDropdownTopLayer({ isOpen, onClose, isTriggeredUsingKeyboard: _isTriggeredUsingKeyboard, data, max, overrides, onAvatarClick, testId, labelId, renderMoreButton, handleTriggerClicked, bindFocus: _bindFocus, }: TMoreDropdownTopLayerProps): React.JSX.Element;
|
|
44
|
+
export {};
|
|
@@ -4,7 +4,7 @@ export interface MoreIndicatorProps {
|
|
|
4
4
|
count: number;
|
|
5
5
|
'aria-controls'?: string;
|
|
6
6
|
'aria-expanded'?: boolean;
|
|
7
|
-
'aria-haspopup'?: boolean | 'dialog';
|
|
7
|
+
'aria-haspopup'?: boolean | 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid';
|
|
8
8
|
moreIndicatorLabel?: string;
|
|
9
9
|
buttonProps: Partial<React.HTMLAttributes<HTMLElement>>;
|
|
10
10
|
onClick: AvatarClickEventHandler;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/avatar-group",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.9.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/"
|
|
@@ -37,9 +37,10 @@
|
|
|
37
37
|
"@atlaskit/menu": "^8.5.0",
|
|
38
38
|
"@atlaskit/motion": "^6.2.0",
|
|
39
39
|
"@atlaskit/platform-feature-flags": "^1.1.0",
|
|
40
|
-
"@atlaskit/popup": "^4.
|
|
40
|
+
"@atlaskit/popup": "^4.19.0",
|
|
41
41
|
"@atlaskit/tokens": "^13.0.0",
|
|
42
|
-
"@atlaskit/tooltip": "^22.
|
|
42
|
+
"@atlaskit/tooltip": "^22.2.0",
|
|
43
|
+
"@atlaskit/top-layer": "^0.6.0",
|
|
43
44
|
"@babel/runtime": "^7.0.0",
|
|
44
45
|
"@compiled/react": "^0.20.0",
|
|
45
46
|
"bind-event-listener": "^3.0.0"
|
|
@@ -55,9 +56,9 @@
|
|
|
55
56
|
"@atlaskit/button": "^23.11.0",
|
|
56
57
|
"@atlaskit/docs": "^11.8.0",
|
|
57
58
|
"@atlaskit/form": "^15.5.0",
|
|
58
|
-
"@atlaskit/icon": "^34.
|
|
59
|
+
"@atlaskit/icon": "^34.5.0",
|
|
59
60
|
"@atlaskit/link": "^3.4.0",
|
|
60
|
-
"@atlaskit/modal-dialog": "^
|
|
61
|
+
"@atlaskit/modal-dialog": "^15.0.0",
|
|
61
62
|
"@atlaskit/primitives": "^19.0.0",
|
|
62
63
|
"@atlaskit/section-message": "^8.12.0",
|
|
63
64
|
"@atlaskit/toggle": "^15.6.0",
|
|
@@ -107,6 +108,9 @@
|
|
|
107
108
|
"platform-avatar-group-spacing-fix": {
|
|
108
109
|
"type": "boolean"
|
|
109
110
|
},
|
|
111
|
+
"platform-dst-top-layer": {
|
|
112
|
+
"type": "boolean"
|
|
113
|
+
},
|
|
110
114
|
"jira-ai-agent-stack": {
|
|
111
115
|
"type": "boolean"
|
|
112
116
|
},
|