@kaizen/components 0.0.0-canary-test-select-aria-props-update-20250616233358 → 0.0.0-canary-test-fms-popover-api-update-20250624021814
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/dist/cjs/src/Filter/FilterMultiSelect/FilterMultiSelect.cjs +7 -3
- package/dist/cjs/src/Filter/FilterMultiSelect/subcomponents/ListBox/ListBox.cjs +1 -1
- package/dist/cjs/src/Filter/FilterMultiSelect/subcomponents/ListBox/ListBox.module.css.cjs +9 -0
- package/dist/cjs/src/Filter/FilterMultiSelect/subcomponents/MenuPopup/MenuPopup.cjs +57 -38
- package/dist/cjs/src/Filter/FilterMultiSelect/subcomponents/MenuPopup/{MenuPopup.module.scss.cjs → MenuPopup.module.css.cjs} +1 -1
- package/dist/cjs/src/LikertScaleLegacy/LikertScaleLegacy.cjs +5 -3
- package/dist/esm/src/Filter/FilterMultiSelect/FilterMultiSelect.mjs +7 -3
- package/dist/esm/src/Filter/FilterMultiSelect/subcomponents/ListBox/ListBox.mjs +1 -1
- package/dist/esm/src/Filter/FilterMultiSelect/subcomponents/ListBox/ListBox.module.css.mjs +7 -0
- package/dist/esm/src/Filter/FilterMultiSelect/subcomponents/MenuPopup/MenuPopup.mjs +58 -41
- package/dist/esm/src/Filter/FilterMultiSelect/subcomponents/MenuPopup/MenuPopup.module.css.mjs +4 -0
- package/dist/esm/src/LikertScaleLegacy/LikertScaleLegacy.mjs +5 -3
- package/dist/styles.css +28 -21
- package/dist/types/Filter/FilterMultiSelect/FilterMultiSelect.d.ts +1 -1
- package/dist/types/Filter/FilterMultiSelect/_docs/MockData.d.ts +1 -0
- package/dist/types/Filter/FilterMultiSelect/subcomponents/MenuPopup/MenuPopup.d.ts +7 -4
- package/dist/types/LikertScaleLegacy/LikertScaleLegacy.d.ts +5 -1
- package/package.json +1 -1
- package/src/Filter/FilterBar/subcomponents/FilterBarMultiSelect/FilterBarMultiSelect.spec.tsx +1 -0
- package/src/Filter/FilterMultiSelect/FilterMultiSelect.tsx +3 -2
- package/src/Filter/FilterMultiSelect/_docs/FilterMultiSelect.mdx +8 -0
- package/src/Filter/FilterMultiSelect/_docs/FilterMultiSelect.stories.tsx +89 -1
- package/src/Filter/FilterMultiSelect/_docs/MockData.ts +39 -0
- package/src/Filter/FilterMultiSelect/context/MenuTriggerProvider/MenuTriggerProvider.spec.tsx +2 -18
- package/src/Filter/FilterMultiSelect/subcomponents/ListBox/ListBox.module.css +22 -0
- package/src/Filter/FilterMultiSelect/subcomponents/ListBox/ListBox.tsx +1 -1
- package/src/Filter/FilterMultiSelect/subcomponents/MenuPopup/MenuPopup.module.css +22 -0
- package/src/Filter/FilterMultiSelect/subcomponents/MenuPopup/MenuPopup.tsx +62 -42
- package/src/LikertScaleLegacy/LikertScaleLegacy.spec.tsx +1 -0
- package/src/LikertScaleLegacy/LikertScaleLegacy.tsx +7 -1
- package/src/LikertScaleLegacy/_docs/LikertScaleLegacy.mdx +8 -0
- package/src/LikertScaleLegacy/_docs/LikertScaleLegacy.stories.tsx +30 -1
- package/src/__next__/Menu/_docs/Menu--migration-guide.mdx +91 -0
- package/src/__next__/Select/_docs/Select.mdx +1 -1
- package/dist/cjs/src/Filter/FilterMultiSelect/subcomponents/ListBox/ListBox.module.scss.cjs +0 -9
- package/dist/esm/src/Filter/FilterMultiSelect/subcomponents/ListBox/ListBox.module.scss.mjs +0 -7
- package/dist/esm/src/Filter/FilterMultiSelect/subcomponents/MenuPopup/MenuPopup.module.scss.mjs +0 -4
- package/src/Filter/FilterMultiSelect/subcomponents/ListBox/ListBox.module.scss +0 -25
- package/src/Filter/FilterMultiSelect/subcomponents/MenuPopup/MenuPopup.module.scss +0 -24
|
@@ -41,7 +41,8 @@ var FilterMultiSelect = function (_a) {
|
|
|
41
41
|
selectionMode = _b === void 0 ? 'multiple' : _b,
|
|
42
42
|
onSearchInputChange = _a.onSearchInputChange,
|
|
43
43
|
triggerRef = _a.triggerRef,
|
|
44
|
-
className = _a.className
|
|
44
|
+
className = _a.className,
|
|
45
|
+
floatingOptions = _a.floatingOptions;
|
|
45
46
|
var menuTriggerProps = {
|
|
46
47
|
isOpen: isOpen,
|
|
47
48
|
defaultOpen: defaultOpen,
|
|
@@ -50,7 +51,8 @@ var FilterMultiSelect = function (_a) {
|
|
|
50
51
|
};
|
|
51
52
|
var menuPopupProps = {
|
|
52
53
|
isLoading: isLoading,
|
|
53
|
-
loadingSkeleton: loadingSkeleton
|
|
54
|
+
loadingSkeleton: loadingSkeleton,
|
|
55
|
+
floatingOptions: floatingOptions
|
|
54
56
|
};
|
|
55
57
|
var disabledKeys = new Set(items === null || items === void 0 ? void 0 : items.filter(function (item) {
|
|
56
58
|
return item.isDisabled === true;
|
|
@@ -69,7 +71,9 @@ var FilterMultiSelect = function (_a) {
|
|
|
69
71
|
};
|
|
70
72
|
return React__default.default.createElement(MenuTriggerProvider.MenuTriggerProvider, tslib.__assign({}, menuTriggerProps), React__default.default.createElement("div", {
|
|
71
73
|
className: className
|
|
72
|
-
}, React__default.default.createElement(MenuTriggerProvider.MenuTriggerConsumer, null, trigger), React__default.default.createElement(MenuPopup.MenuPopup, tslib.__assign({
|
|
74
|
+
}, React__default.default.createElement(MenuTriggerProvider.MenuTriggerConsumer, null, trigger), React__default.default.createElement(MenuPopup.MenuPopup, tslib.__assign({
|
|
75
|
+
"aria-label": label
|
|
76
|
+
}, menuPopupProps), React__default.default.createElement(SelectionProvider.SelectionProvider, tslib.__assign({}, selectionProps), React__default.default.createElement(SelectionProvider.SelectionConsumer, null, children)))));
|
|
73
77
|
};
|
|
74
78
|
FilterMultiSelect.displayName = 'FilterMultiSelect';
|
|
75
79
|
FilterMultiSelect.TriggerButton = FilterTriggerButton.FilterTriggerButton;
|
|
@@ -4,7 +4,7 @@ var tslib = require('tslib');
|
|
|
4
4
|
var React = require('react');
|
|
5
5
|
var classnames = require('classnames');
|
|
6
6
|
var SelectionProvider = require('../../context/SelectionProvider/SelectionProvider.cjs');
|
|
7
|
-
var ListBox_module = require('./ListBox.module.
|
|
7
|
+
var ListBox_module = require('./ListBox.module.css.cjs');
|
|
8
8
|
function _interopDefault(e) {
|
|
9
9
|
return e && e.__esModule ? e : {
|
|
10
10
|
default: e
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var styles = {
|
|
4
|
+
"listBox": "ListBox-module_listBox__HBScm",
|
|
5
|
+
"overflown": "ListBox-module_overflown__PdKED",
|
|
6
|
+
"hidden": "ListBox-module_hidden__mO-oL",
|
|
7
|
+
"noResultsWrapper": "ListBox-module_noResultsWrapper__RnMj0"
|
|
8
|
+
};
|
|
9
|
+
module.exports = styles;
|
|
@@ -2,55 +2,74 @@
|
|
|
2
2
|
|
|
3
3
|
var tslib = require('tslib');
|
|
4
4
|
var React = require('react');
|
|
5
|
-
var
|
|
6
|
-
var
|
|
5
|
+
var reactDom = require('@floating-ui/react-dom');
|
|
6
|
+
var classnames = require('classnames');
|
|
7
|
+
var reactFocusOn = require('react-focus-on');
|
|
7
8
|
var MenuTriggerProvider = require('../../context/MenuTriggerProvider/MenuTriggerProvider.cjs');
|
|
8
9
|
require('../../context/SelectionProvider/SelectionProvider.cjs');
|
|
9
|
-
var MenuPopup_module = require('./MenuPopup.module.
|
|
10
|
+
var MenuPopup_module = require('./MenuPopup.module.css.cjs');
|
|
10
11
|
function _interopDefault(e) {
|
|
11
12
|
return e && e.__esModule ? e : {
|
|
12
13
|
default: e
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
17
|
+
var classnames__default = /*#__PURE__*/_interopDefault(classnames);
|
|
16
18
|
var MenuPopup = function (_a) {
|
|
17
|
-
var
|
|
19
|
+
var children = _a.children,
|
|
20
|
+
floatingOptions = _a.floatingOptions,
|
|
21
|
+
classNameOverride = _a.classNameOverride,
|
|
22
|
+
isLoading = _a.isLoading,
|
|
18
23
|
loadingSkeleton = _a.loadingSkeleton,
|
|
19
|
-
|
|
20
|
-
var
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
restProps = tslib.__rest(_a, ["children", "floatingOptions", "classNameOverride", "isLoading", "loadingSkeleton"]);
|
|
25
|
+
var _b = React.useState(null),
|
|
26
|
+
floatingElement = _b[0],
|
|
27
|
+
setFloatingElement = _b[1];
|
|
28
|
+
var _c = MenuTriggerProvider.useMenuTriggerContext(),
|
|
29
|
+
menuTriggerState = _c.menuTriggerState,
|
|
30
|
+
buttonRef = _c.buttonRef;
|
|
31
|
+
var referenceElement = buttonRef.current;
|
|
32
|
+
var _d = reactDom.useFloating(tslib.__assign({
|
|
33
|
+
placement: 'bottom-start',
|
|
34
|
+
elements: {
|
|
35
|
+
reference: referenceElement,
|
|
36
|
+
floating: floatingElement
|
|
37
|
+
},
|
|
38
|
+
strategy: 'absolute',
|
|
39
|
+
middleware: [reactDom.offset(6)],
|
|
40
|
+
whileElementsMounted: reactDom.autoUpdate
|
|
41
|
+
}, floatingOptions)),
|
|
42
|
+
floatingStyles = _d.floatingStyles,
|
|
43
|
+
update = _d.update;
|
|
44
|
+
var handleReturnFocus = function () {
|
|
45
|
+
requestAnimationFrame(function () {
|
|
46
|
+
var _a;
|
|
47
|
+
(_a = buttonRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
48
|
+
});
|
|
23
49
|
};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
autoFocus: true,
|
|
48
|
-
restoreFocus: true
|
|
49
|
-
}, React__default.default.createElement(overlays.DismissButton, {
|
|
50
|
-
onDismiss: onClose
|
|
51
|
-
}), children, React__default.default.createElement(overlays.DismissButton, {
|
|
52
|
-
onDismiss: onClose
|
|
53
|
-
}))) : React__default.default.createElement(React__default.default.Fragment, null);
|
|
50
|
+
React.useEffect(function () {
|
|
51
|
+
var _a;
|
|
52
|
+
if (floatingElement && referenceElement) {
|
|
53
|
+
(_a = floatingElement.showPopover) === null || _a === void 0 ? void 0 : _a.call(floatingElement);
|
|
54
|
+
update();
|
|
55
|
+
}
|
|
56
|
+
}, [floatingElement, referenceElement, update]);
|
|
57
|
+
return menuTriggerState.isOpen ? React__default.default.createElement(reactFocusOn.FocusOn, {
|
|
58
|
+
enabled: menuTriggerState.isOpen,
|
|
59
|
+
scrollLock: false,
|
|
60
|
+
returnFocus: false,
|
|
61
|
+
onClickOutside: menuTriggerState.close,
|
|
62
|
+
onEscapeKey: menuTriggerState.close,
|
|
63
|
+
onDeactivation: handleReturnFocus
|
|
64
|
+
}, React__default.default.createElement("div", tslib.__assign({
|
|
65
|
+
ref: setFloatingElement,
|
|
66
|
+
style: floatingStyles,
|
|
67
|
+
className: classnames__default.default(MenuPopup_module.menuPopup, classNameOverride),
|
|
68
|
+
role: "dialog",
|
|
69
|
+
"aria-modal": "true",
|
|
70
|
+
// @ts-expect-error: popover is valid in supported browsers
|
|
71
|
+
popover: "manual"
|
|
72
|
+
}, restProps), isLoading && loadingSkeleton ? loadingSkeleton : children)) : React__default.default.createElement(React__default.default.Fragment, null);
|
|
54
73
|
};
|
|
55
74
|
MenuPopup.displayName = 'FilterMultiSelect.MenuPopup';
|
|
56
75
|
exports.MenuPopup = MenuPopup;
|
|
@@ -36,7 +36,8 @@ var LikertScaleLegacy = function (_a) {
|
|
|
36
36
|
onSelect = _a.onSelect,
|
|
37
37
|
validationMessage = _a.validationMessage,
|
|
38
38
|
status = _a.status,
|
|
39
|
-
labelId = _a.labelId
|
|
39
|
+
labelId = _a.labelId,
|
|
40
|
+
isRequired = _a.isRequired;
|
|
40
41
|
var _e = React.useState(null),
|
|
41
42
|
hoveredItem = _e[0],
|
|
42
43
|
setHoveredItem = _e[1];
|
|
@@ -83,11 +84,12 @@ var LikertScaleLegacy = function (_a) {
|
|
|
83
84
|
var isRated = selectedItem && selectedItem.value > 0;
|
|
84
85
|
return React__default.default.createElement("div", {
|
|
85
86
|
className: classnames__default.default(LikertScaleLegacy_module.container, isRated && LikertScaleLegacy_module.rated, reversed && [LikertScaleLegacy_module.reversed], hoveredItem !== null && LikertScaleLegacy_module.hovered),
|
|
86
|
-
"aria-labelledby": labelId,
|
|
87
|
+
"aria-labelledby": isRequired ? "".concat(labelId) : labelId,
|
|
87
88
|
role: "radiogroup",
|
|
88
89
|
tabIndex: -1,
|
|
89
90
|
"aria-describedby": validationMessageId,
|
|
90
|
-
"data-testid": dataTestId
|
|
91
|
+
"data-testid": dataTestId,
|
|
92
|
+
"aria-required": isRequired
|
|
91
93
|
}, React__default.default.createElement("div", {
|
|
92
94
|
className: LikertScaleLegacy_module.legend,
|
|
93
95
|
"data-testid": dataTestId && "".concat(dataTestId, "-legend")
|
|
@@ -34,7 +34,8 @@ const FilterMultiSelect = /*#__PURE__*/function () {
|
|
|
34
34
|
selectionMode = _b === void 0 ? 'multiple' : _b,
|
|
35
35
|
onSearchInputChange = _a.onSearchInputChange,
|
|
36
36
|
triggerRef = _a.triggerRef,
|
|
37
|
-
className = _a.className
|
|
37
|
+
className = _a.className,
|
|
38
|
+
floatingOptions = _a.floatingOptions;
|
|
38
39
|
var menuTriggerProps = {
|
|
39
40
|
isOpen: isOpen,
|
|
40
41
|
defaultOpen: defaultOpen,
|
|
@@ -43,7 +44,8 @@ const FilterMultiSelect = /*#__PURE__*/function () {
|
|
|
43
44
|
};
|
|
44
45
|
var menuPopupProps = {
|
|
45
46
|
isLoading: isLoading,
|
|
46
|
-
loadingSkeleton: loadingSkeleton
|
|
47
|
+
loadingSkeleton: loadingSkeleton,
|
|
48
|
+
floatingOptions: floatingOptions
|
|
47
49
|
};
|
|
48
50
|
var disabledKeys = new Set(items === null || items === void 0 ? void 0 : items.filter(function (item) {
|
|
49
51
|
return item.isDisabled === true;
|
|
@@ -62,7 +64,9 @@ const FilterMultiSelect = /*#__PURE__*/function () {
|
|
|
62
64
|
};
|
|
63
65
|
return /*#__PURE__*/React.createElement(MenuTriggerProvider, __assign({}, menuTriggerProps), /*#__PURE__*/React.createElement("div", {
|
|
64
66
|
className: className
|
|
65
|
-
}, /*#__PURE__*/React.createElement(MenuTriggerConsumer, null, trigger), /*#__PURE__*/React.createElement(MenuPopup, __assign({
|
|
67
|
+
}, /*#__PURE__*/React.createElement(MenuTriggerConsumer, null, trigger), /*#__PURE__*/React.createElement(MenuPopup, __assign({
|
|
68
|
+
"aria-label": label
|
|
69
|
+
}, menuPopupProps), /*#__PURE__*/React.createElement(SelectionProvider, __assign({}, selectionProps), /*#__PURE__*/React.createElement(SelectionConsumer, null, children)))));
|
|
66
70
|
};
|
|
67
71
|
FilterMultiSelect.displayName = 'FilterMultiSelect';
|
|
68
72
|
FilterMultiSelect.TriggerButton = FilterTriggerButton;
|
|
@@ -2,7 +2,7 @@ import { __assign, __spreadArray } from 'tslib';
|
|
|
2
2
|
import React, { useState, useEffect } from 'react';
|
|
3
3
|
import classnames from 'classnames';
|
|
4
4
|
import { useSelectionContext } from '../../context/SelectionProvider/SelectionProvider.mjs';
|
|
5
|
-
import styles from './ListBox.module.
|
|
5
|
+
import styles from './ListBox.module.css.mjs';
|
|
6
6
|
var getItemsFromKeys = function (items, keys) {
|
|
7
7
|
var itemKeys = Array.from(keys);
|
|
8
8
|
return itemKeys.reduce(function (acc, itemKey) {
|
|
@@ -1,50 +1,67 @@
|
|
|
1
|
-
import { __assign } from 'tslib';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
1
|
+
import { __rest, __assign } from 'tslib';
|
|
2
|
+
import React, { useState, useEffect } from 'react';
|
|
3
|
+
import { useFloating, autoUpdate, offset } from '@floating-ui/react-dom';
|
|
4
|
+
import classnames from 'classnames';
|
|
5
|
+
import { FocusOn } from 'react-focus-on';
|
|
5
6
|
import { useMenuTriggerContext } from '../../context/MenuTriggerProvider/MenuTriggerProvider.mjs';
|
|
6
7
|
import '../../context/SelectionProvider/SelectionProvider.mjs';
|
|
7
|
-
import styles from './MenuPopup.module.
|
|
8
|
+
import styles from './MenuPopup.module.css.mjs';
|
|
8
9
|
const MenuPopup = /*#__PURE__*/function () {
|
|
9
10
|
const MenuPopup = function (_a) {
|
|
10
|
-
var
|
|
11
|
+
var children = _a.children,
|
|
12
|
+
floatingOptions = _a.floatingOptions,
|
|
13
|
+
classNameOverride = _a.classNameOverride,
|
|
14
|
+
isLoading = _a.isLoading,
|
|
11
15
|
loadingSkeleton = _a.loadingSkeleton,
|
|
12
|
-
|
|
13
|
-
var
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
restProps = __rest(_a, ["children", "floatingOptions", "classNameOverride", "isLoading", "loadingSkeleton"]);
|
|
17
|
+
var _b = useState(null),
|
|
18
|
+
floatingElement = _b[0],
|
|
19
|
+
setFloatingElement = _b[1];
|
|
20
|
+
var _c = useMenuTriggerContext(),
|
|
21
|
+
menuTriggerState = _c.menuTriggerState,
|
|
22
|
+
buttonRef = _c.buttonRef;
|
|
23
|
+
var referenceElement = buttonRef.current;
|
|
24
|
+
var _d = useFloating(__assign({
|
|
25
|
+
placement: 'bottom-start',
|
|
26
|
+
elements: {
|
|
27
|
+
reference: referenceElement,
|
|
28
|
+
floating: floatingElement
|
|
29
|
+
},
|
|
30
|
+
strategy: 'absolute',
|
|
31
|
+
middleware: [offset(6)],
|
|
32
|
+
whileElementsMounted: autoUpdate
|
|
33
|
+
}, floatingOptions)),
|
|
34
|
+
floatingStyles = _d.floatingStyles,
|
|
35
|
+
update = _d.update;
|
|
36
|
+
var handleReturnFocus = function () {
|
|
37
|
+
requestAnimationFrame(function () {
|
|
38
|
+
var _a;
|
|
39
|
+
(_a = buttonRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
40
|
+
});
|
|
16
41
|
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
React.createElement(
|
|
40
|
-
contain: true,
|
|
41
|
-
autoFocus: true,
|
|
42
|
-
restoreFocus: true
|
|
43
|
-
}, /*#__PURE__*/React.createElement(DismissButton, {
|
|
44
|
-
onDismiss: onClose
|
|
45
|
-
}), children, /*#__PURE__*/React.createElement(DismissButton, {
|
|
46
|
-
onDismiss: onClose
|
|
47
|
-
}))))) : (/*#__PURE__*/React.createElement(React.Fragment, null));
|
|
42
|
+
useEffect(function () {
|
|
43
|
+
var _a;
|
|
44
|
+
if (floatingElement && referenceElement) {
|
|
45
|
+
(_a = floatingElement.showPopover) === null || _a === void 0 ? void 0 : _a.call(floatingElement);
|
|
46
|
+
update();
|
|
47
|
+
}
|
|
48
|
+
}, [floatingElement, referenceElement, update]);
|
|
49
|
+
return menuTriggerState.isOpen ? (/*#__PURE__*/React.createElement(FocusOn, {
|
|
50
|
+
enabled: menuTriggerState.isOpen,
|
|
51
|
+
scrollLock: false,
|
|
52
|
+
returnFocus: false,
|
|
53
|
+
onClickOutside: menuTriggerState.close,
|
|
54
|
+
onEscapeKey: menuTriggerState.close,
|
|
55
|
+
onDeactivation: handleReturnFocus
|
|
56
|
+
}, /*#__PURE__*/React.createElement("div", __assign({
|
|
57
|
+
ref: setFloatingElement,
|
|
58
|
+
style: floatingStyles,
|
|
59
|
+
className: classnames(styles.menuPopup, classNameOverride),
|
|
60
|
+
role: "dialog",
|
|
61
|
+
"aria-modal": "true",
|
|
62
|
+
// @ts-expect-error: popover is valid in supported browsers
|
|
63
|
+
popover: "manual"
|
|
64
|
+
}, restProps), isLoading && loadingSkeleton ? loadingSkeleton : children))) : (/*#__PURE__*/React.createElement(React.Fragment, null));
|
|
48
65
|
};
|
|
49
66
|
MenuPopup.displayName = 'FilterMultiSelect.MenuPopup';
|
|
50
67
|
return MenuPopup;
|
|
@@ -27,7 +27,8 @@ var LikertScaleLegacy = function (_a) {
|
|
|
27
27
|
onSelect = _a.onSelect,
|
|
28
28
|
validationMessage = _a.validationMessage,
|
|
29
29
|
status = _a.status,
|
|
30
|
-
labelId = _a.labelId
|
|
30
|
+
labelId = _a.labelId,
|
|
31
|
+
isRequired = _a.isRequired;
|
|
31
32
|
var _e = useState(null),
|
|
32
33
|
hoveredItem = _e[0],
|
|
33
34
|
setHoveredItem = _e[1];
|
|
@@ -74,11 +75,12 @@ var LikertScaleLegacy = function (_a) {
|
|
|
74
75
|
var isRated = selectedItem && selectedItem.value > 0;
|
|
75
76
|
return /*#__PURE__*/React.createElement("div", {
|
|
76
77
|
className: classnames(styles.container, isRated && styles.rated, reversed && [styles.reversed], hoveredItem !== null && styles.hovered),
|
|
77
|
-
"aria-labelledby": labelId,
|
|
78
|
+
"aria-labelledby": isRequired ? "".concat(labelId) : labelId,
|
|
78
79
|
role: "radiogroup",
|
|
79
80
|
tabIndex: -1,
|
|
80
81
|
"aria-describedby": validationMessageId,
|
|
81
|
-
"data-testid": dataTestId
|
|
82
|
+
"data-testid": dataTestId,
|
|
83
|
+
"aria-required": isRequired
|
|
82
84
|
}, /*#__PURE__*/React.createElement("div", {
|
|
83
85
|
className: styles.legend,
|
|
84
86
|
"data-testid": dataTestId && "".concat(dataTestId, "-legend")
|
package/dist/styles.css
CHANGED
|
@@ -3743,26 +3743,29 @@
|
|
|
3743
3743
|
}
|
|
3744
3744
|
}
|
|
3745
3745
|
|
|
3746
|
-
/** THIS IS AN AUTOGENERATED FILE **/
|
|
3747
3746
|
@layer kz-components {
|
|
3748
|
-
.ListBox-
|
|
3747
|
+
.ListBox-module_listBox__HBScm {
|
|
3749
3748
|
list-style: none;
|
|
3750
|
-
padding: var(--spacing-
|
|
3751
|
-
margin: 0 var(--spacing-
|
|
3749
|
+
padding: var(--spacing-12);
|
|
3750
|
+
margin: 0 var(--spacing-12) 0 0;
|
|
3752
3751
|
display: grid;
|
|
3753
3752
|
max-height: 22rem;
|
|
3754
3753
|
overflow-y: auto;
|
|
3755
3754
|
}
|
|
3756
|
-
|
|
3757
|
-
|
|
3755
|
+
|
|
3756
|
+
.ListBox-module_overflown__PdKED {
|
|
3757
|
+
padding-right: var(--spacing-12);
|
|
3758
3758
|
}
|
|
3759
|
-
|
|
3759
|
+
|
|
3760
|
+
.ListBox-module_hidden__mO-oL {
|
|
3760
3761
|
display: none;
|
|
3761
3762
|
}
|
|
3762
|
-
|
|
3763
|
+
|
|
3764
|
+
.ListBox-module_noResultsWrapper__RnMj0 {
|
|
3763
3765
|
list-style: none;
|
|
3764
3766
|
}
|
|
3765
3767
|
}
|
|
3768
|
+
|
|
3766
3769
|
/** THIS IS AN AUTOGENERATED FILE **/
|
|
3767
3770
|
/** THIS IS AN AUTOGENERATED FILE **/
|
|
3768
3771
|
/** THIS IS AN AUTOGENERATED FILE **/
|
|
@@ -3807,25 +3810,29 @@
|
|
|
3807
3810
|
margin-right: var(--spacing-sm, 0.75rem);
|
|
3808
3811
|
}
|
|
3809
3812
|
}
|
|
3810
|
-
/** THIS IS AN AUTOGENERATED FILE **/
|
|
3811
|
-
/** THIS IS AN AUTOGENERATED FILE **/
|
|
3812
|
-
/** THIS IS AN AUTOGENERATED FILE **/
|
|
3813
|
-
/** THIS IS AN AUTOGENERATED FILE **/
|
|
3814
3813
|
@layer kz-components {
|
|
3815
|
-
.MenuPopup-
|
|
3816
|
-
|
|
3814
|
+
.MenuPopup-module_menuPopup__QgGEa {
|
|
3815
|
+
/* from $ca-z-index-dropdown */
|
|
3817
3816
|
z-index: 1000;
|
|
3818
3817
|
box-sizing: border-box;
|
|
3819
|
-
background: var(--color-white
|
|
3820
|
-
color: var(--color-purple-800
|
|
3821
|
-
border-radius: var(--border-solid-border-radius
|
|
3822
|
-
box-shadow: var(--shadow-large-box-shadow
|
|
3823
|
-
padding: var(--spacing-
|
|
3824
|
-
margin-top: var(--spacing-
|
|
3818
|
+
background: var(--color-white);
|
|
3819
|
+
color: var(--color-purple-800);
|
|
3820
|
+
border-radius: var(--border-solid-border-radius);
|
|
3821
|
+
box-shadow: var(--shadow-large-box-shadow);
|
|
3822
|
+
padding: var(--spacing-6) 0;
|
|
3823
|
+
margin-top: var(--spacing-6);
|
|
3825
3824
|
text-align: start;
|
|
3826
|
-
width: 294px;
|
|
3825
|
+
width: var(--menu-container-width, 294px);
|
|
3826
|
+
max-height: var(--menu-container-height, 500px);
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
.MenuPopup-module_menuPopup__QgGEa[popover]:popover-open {
|
|
3830
|
+
z-index: unset;
|
|
3831
|
+
margin: 0;
|
|
3832
|
+
inset: unset;
|
|
3827
3833
|
}
|
|
3828
3834
|
}
|
|
3835
|
+
|
|
3829
3836
|
/** THIS IS AN AUTOGENERATED FILE **/
|
|
3830
3837
|
/** THIS IS AN AUTOGENERATED FILE **/
|
|
3831
3838
|
/** THIS IS AN AUTOGENERATED FILE **/
|
|
@@ -19,7 +19,7 @@ export type FilterMultiSelectProps = {
|
|
|
19
19
|
className?: string;
|
|
20
20
|
} & Omit<MenuPopupProps, 'children'> & Omit<MenuTriggerProviderProps, 'children'> & SelectionProps;
|
|
21
21
|
export declare const FilterMultiSelect: {
|
|
22
|
-
({ trigger, children, isOpen, defaultOpen, onOpenChange, isLoading, loadingSkeleton, label, items, selectedKeys, defaultSelectedKeys, onSelectionChange, selectionMode, onSearchInputChange, triggerRef, className, }: FilterMultiSelectProps): JSX.Element;
|
|
22
|
+
({ trigger, children, isOpen, defaultOpen, onOpenChange, isLoading, loadingSkeleton, label, items, selectedKeys, defaultSelectedKeys, onSelectionChange, selectionMode, onSearchInputChange, triggerRef, className, floatingOptions, }: FilterMultiSelectProps): JSX.Element;
|
|
23
23
|
displayName: string;
|
|
24
24
|
TriggerButton: {
|
|
25
25
|
({ selectedOptionLabels, label, classNameOverride, labelCharacterLimitBeforeTruncate, }: import("./subcomponents/Trigger").FilterTriggerButtonProps): JSX.Element;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { type HTMLAttributes } from 'react';
|
|
2
|
+
import { type UseFloatingOptions } from '@floating-ui/react-dom';
|
|
3
|
+
import { type OverrideClassName } from "../../../../types/OverrideClassName";
|
|
2
4
|
export type MenuPopupProps = {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
floatingOptions?: Partial<UseFloatingOptions>;
|
|
3
7
|
isLoading?: boolean;
|
|
4
8
|
loadingSkeleton?: React.ReactNode;
|
|
5
|
-
|
|
6
|
-
};
|
|
9
|
+
} & OverrideClassName<HTMLAttributes<HTMLDivElement>>;
|
|
7
10
|
export declare const MenuPopup: {
|
|
8
|
-
({ isLoading, loadingSkeleton,
|
|
11
|
+
({ children, floatingOptions, classNameOverride, isLoading, loadingSkeleton, ...restProps }: MenuPopupProps): JSX.Element;
|
|
9
12
|
displayName: string;
|
|
10
13
|
};
|
|
@@ -12,10 +12,14 @@ export type LikertScaleProps = {
|
|
|
12
12
|
'colorSchema'?: ColorSchema | 'classical';
|
|
13
13
|
'validationMessage'?: string;
|
|
14
14
|
'status'?: 'default' | 'error';
|
|
15
|
+
/**
|
|
16
|
+
* Sets aria-required value on radiogroup for assistive technologies. Validation must still be handled.
|
|
17
|
+
*/
|
|
18
|
+
'isRequired'?: boolean;
|
|
15
19
|
'onSelect': (value: ScaleItem | null) => void;
|
|
16
20
|
};
|
|
17
21
|
/**
|
|
18
22
|
* {@link https://cultureamp.atlassian.net/wiki/spaces/DesignSystem/pages/3082060201/Likert+Scale Guidance} |
|
|
19
23
|
* {@link https://cultureamp.design/?path=/docs/components-likertscalelegacy--docs Storybook}
|
|
20
24
|
*/
|
|
21
|
-
export declare const LikertScaleLegacy: ({ scale, selectedItem, reversed, colorSchema, "data-testid": dataTestId, onSelect, validationMessage, status, labelId, }: LikertScaleProps) => JSX.Element;
|
|
25
|
+
export declare const LikertScaleLegacy: ({ scale, selectedItem, reversed, colorSchema, "data-testid": dataTestId, onSelect, validationMessage, status, labelId, isRequired, }: LikertScaleProps) => JSX.Element;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaizen/components",
|
|
3
|
-
"version": "0.0.0-canary-test-
|
|
3
|
+
"version": "0.0.0-canary-test-fms-popover-api-update-20250624021814",
|
|
4
4
|
"description": "Kaizen component library",
|
|
5
5
|
"author": "Geoffrey Chong <geoff.chong@cultureamp.com>",
|
|
6
6
|
"homepage": "https://cultureamp.design",
|
package/src/Filter/FilterBar/subcomponents/FilterBarMultiSelect/FilterBarMultiSelect.spec.tsx
CHANGED
|
@@ -180,6 +180,7 @@ describe('<FilterBarMultiSelect />', () => {
|
|
|
180
180
|
})
|
|
181
181
|
|
|
182
182
|
await user.click(getByRole('option', { name: 'Fruit Jelly' }))
|
|
183
|
+
await user.keyboard('{Escape}')
|
|
183
184
|
await waitFor(() => {
|
|
184
185
|
expect(getByRole('button', { name: 'Toppings : Pearls, Fruit Jelly' })).toBeInTheDocument()
|
|
185
186
|
})
|
|
@@ -58,9 +58,10 @@ export const FilterMultiSelect = ({
|
|
|
58
58
|
onSearchInputChange,
|
|
59
59
|
triggerRef,
|
|
60
60
|
className,
|
|
61
|
+
floatingOptions,
|
|
61
62
|
}: FilterMultiSelectProps): JSX.Element => {
|
|
62
63
|
const menuTriggerProps = { isOpen, defaultOpen, onOpenChange, triggerRef }
|
|
63
|
-
const menuPopupProps = { isLoading, loadingSkeleton }
|
|
64
|
+
const menuPopupProps = { isLoading, loadingSkeleton, floatingOptions }
|
|
64
65
|
const disabledKeys: Selection = new Set(
|
|
65
66
|
items?.filter((item) => item.isDisabled === true).map((disabledItem) => disabledItem.value),
|
|
66
67
|
)
|
|
@@ -79,7 +80,7 @@ export const FilterMultiSelect = ({
|
|
|
79
80
|
<MenuTriggerProvider {...menuTriggerProps}>
|
|
80
81
|
<div className={className}>
|
|
81
82
|
<MenuTriggerConsumer>{trigger}</MenuTriggerConsumer>
|
|
82
|
-
<MenuPopup {...menuPopupProps}>
|
|
83
|
+
<MenuPopup aria-label={label} {...menuPopupProps}>
|
|
83
84
|
<SelectionProvider {...selectionProps}>
|
|
84
85
|
<SelectionConsumer>{children}</SelectionConsumer>
|
|
85
86
|
</SelectionProvider>
|
|
@@ -30,6 +30,14 @@ The FilterMultiSelect is a component relies heavily on consumer implemntation. I
|
|
|
30
30
|
|
|
31
31
|
<Canvas of={FilterMultiSelectStories.WithSectionHeaders} />
|
|
32
32
|
|
|
33
|
+
### With floatingOptions
|
|
34
|
+
|
|
35
|
+
You can also use the `floatingOptions` to leverage any of `Floating UI's` [configurable options](https://floating-ui.com/docs/usefloating). While in most cases the default behavior should satisfy most scenarios, the `middleware` for `autoplace` and `size` can be useful when there is limited screen space available.
|
|
36
|
+
|
|
37
|
+
The following example showcases how to use the [autoPlacement](https://floating-ui.com/docs/autoPlacement) and [size](https://floating-ui.com/docs/size) and spread the options in with the default values.
|
|
38
|
+
|
|
39
|
+
<Canvas of={FilterMultiSelectStories.WithFloatingOptions} />
|
|
40
|
+
|
|
33
41
|
### Async
|
|
34
42
|
|
|
35
43
|
The following is an example of how you may create an async FilterMultiSelect using `@tanstack/react-query`.
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
|
+
import { autoPlacement, offset, size } from '@floating-ui/react-dom'
|
|
2
3
|
import type { Selection } from '@react-types/shared'
|
|
3
4
|
import type { Meta, StoryObj } from '@storybook/react'
|
|
4
5
|
import isChromatic from 'chromatic'
|
|
5
6
|
import { InlineNotification } from '~components/Notification'
|
|
6
7
|
import { TextField } from '~components/TextField'
|
|
7
8
|
import { FilterMultiSelect, getSelectedOptionLabels } from '..'
|
|
8
|
-
import { mockItems } from './MockData'
|
|
9
|
+
import { mockItems, mockManyItems } from './MockData'
|
|
9
10
|
|
|
10
11
|
const IS_CHROMATIC = isChromatic()
|
|
11
12
|
|
|
@@ -308,3 +309,90 @@ export const WithSectionNotification: Story = {
|
|
|
308
309
|
chromatic: { disable: false },
|
|
309
310
|
},
|
|
310
311
|
}
|
|
312
|
+
|
|
313
|
+
export const WithManyOptions: Story = {
|
|
314
|
+
...FilterMultiSelectTemplate,
|
|
315
|
+
name: 'With many options',
|
|
316
|
+
args: {
|
|
317
|
+
items: mockManyItems,
|
|
318
|
+
},
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const floatingOptionsSourceCode = `
|
|
322
|
+
import { autoPlacement, size, offset } from '@floating-ui/react-dom'
|
|
323
|
+
|
|
324
|
+
// ...source code
|
|
325
|
+
|
|
326
|
+
<FilterMultiSelect
|
|
327
|
+
{...args}
|
|
328
|
+
floatingOptions={{
|
|
329
|
+
...{
|
|
330
|
+
middleware: [
|
|
331
|
+
size({
|
|
332
|
+
apply({ availableHeight, elements }) {
|
|
333
|
+
Object.assign(elements.floating.style, {
|
|
334
|
+
maxHeight: Math.max(250, Math.min(availableHeight - 12, 500)) + "px",
|
|
335
|
+
})
|
|
336
|
+
},
|
|
337
|
+
}),
|
|
338
|
+
autoPlacement({
|
|
339
|
+
allowedPlacements: ['bottom-start', 'top-start'],
|
|
340
|
+
}),
|
|
341
|
+
offset(6),
|
|
342
|
+
],
|
|
343
|
+
},
|
|
344
|
+
}}
|
|
345
|
+
/>
|
|
346
|
+
`
|
|
347
|
+
|
|
348
|
+
export const WithFloatingOptions: Story = {
|
|
349
|
+
...FilterMultiSelectTemplate,
|
|
350
|
+
name: 'With floatingOptions',
|
|
351
|
+
args: {
|
|
352
|
+
floatingOptions: {
|
|
353
|
+
middleware: [
|
|
354
|
+
size({
|
|
355
|
+
apply({ availableHeight, elements }) {
|
|
356
|
+
Object.assign(elements.floating.style, {
|
|
357
|
+
maxHeight: Math.max(250, Math.min(availableHeight - 12, 500)) + 'px',
|
|
358
|
+
})
|
|
359
|
+
},
|
|
360
|
+
}),
|
|
361
|
+
autoPlacement({
|
|
362
|
+
allowedPlacements: ['bottom-start', 'top-start'],
|
|
363
|
+
}),
|
|
364
|
+
offset(6),
|
|
365
|
+
],
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
parameters: {
|
|
369
|
+
docs: { source: { code: floatingOptionsSourceCode } },
|
|
370
|
+
},
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export const AboveIfAvailable: Story = {
|
|
374
|
+
...FilterMultiSelectTemplate,
|
|
375
|
+
...WithFloatingOptions,
|
|
376
|
+
name: 'With limited viewport and autoplacement above',
|
|
377
|
+
parameters: {
|
|
378
|
+
viewport: {
|
|
379
|
+
viewports: {
|
|
380
|
+
LimitedViewportAutoPlace: {
|
|
381
|
+
name: 'Limited vertical space',
|
|
382
|
+
styles: {
|
|
383
|
+
width: '1024px',
|
|
384
|
+
height: '500px',
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
defaultViewport: 'LimitedViewportAutoPlace',
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
decorators: [
|
|
392
|
+
(Story) => (
|
|
393
|
+
<div className="mt-[350px]">
|
|
394
|
+
<Story />
|
|
395
|
+
</div>
|
|
396
|
+
),
|
|
397
|
+
],
|
|
398
|
+
}
|
|
@@ -58,3 +58,42 @@ export const locationDemographicValues = [
|
|
|
58
58
|
label: 'London',
|
|
59
59
|
},
|
|
60
60
|
]
|
|
61
|
+
|
|
62
|
+
export const mockManyItems: ItemType[] = [
|
|
63
|
+
{ label: 'Front-End', value: 'id-fe', count: '1245' },
|
|
64
|
+
{ label: 'Back-End', value: 'id-be', count: '4', isDisabled: true },
|
|
65
|
+
{ label: 'SRE', value: 'id-sre', count: '4', isDisabled: true },
|
|
66
|
+
{ label: 'Dev-ops', value: 'id-devops' },
|
|
67
|
+
{ label: 'Others', value: 'id-others' },
|
|
68
|
+
{
|
|
69
|
+
label: 'Engineer-type-1 has a really really long label',
|
|
70
|
+
value: 'id-type-1',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
label: 'Engineer-type-2 also has a really really long label',
|
|
74
|
+
value: 'id-type-2',
|
|
75
|
+
count: '156',
|
|
76
|
+
},
|
|
77
|
+
{ label: 'Engineer-type-3', value: 'id-type-3' },
|
|
78
|
+
{
|
|
79
|
+
label: 'Engineer-type-4',
|
|
80
|
+
value: 'id-type-4',
|
|
81
|
+
count: '4',
|
|
82
|
+
isDisabled: true,
|
|
83
|
+
},
|
|
84
|
+
{ label: 'Engineer-type-5', value: 'id-type-5' },
|
|
85
|
+
{ label: 'UI Designer', value: 'id-ui', count: '42' },
|
|
86
|
+
{ label: 'UX Researcher', value: 'id-ux', count: '15' },
|
|
87
|
+
{ label: 'Product Manager', value: 'id-pm', count: '28' },
|
|
88
|
+
{ label: 'Project Manager', value: 'id-project', count: '19', isDisabled: true },
|
|
89
|
+
{ label: 'Data Scientist', value: 'id-ds', count: '11' },
|
|
90
|
+
{ label: 'Machine Learning Engineer', value: 'id-ml', count: '7' },
|
|
91
|
+
{ label: 'QA Tester', value: 'id-qa', count: '22' },
|
|
92
|
+
{
|
|
93
|
+
label: 'Technical Writer with documentation expertise',
|
|
94
|
+
value: 'id-tech-writer',
|
|
95
|
+
count: '5',
|
|
96
|
+
},
|
|
97
|
+
{ label: 'DevSecOps Engineer', value: 'id-devsecops', count: '3', isDisabled: true },
|
|
98
|
+
{ label: 'Cloud Architect', value: 'id-cloud', count: '8' },
|
|
99
|
+
]
|
package/src/Filter/FilterMultiSelect/context/MenuTriggerProvider/MenuTriggerProvider.spec.tsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { render, screen, waitFor } from '@testing-library/react'
|
|
3
3
|
import userEvent from '@testing-library/user-event'
|
|
4
|
-
import { vi } from 'vitest'
|
|
5
4
|
import { FilterTriggerButton } from '~components/Filter/FilterMultiSelect/subcomponents/Trigger'
|
|
6
5
|
import { MenuPopup } from '../../subcomponents/MenuPopup'
|
|
7
6
|
import { MenuTriggerProvider, type MenuTriggerProviderProps } from './MenuTriggerProvider'
|
|
@@ -53,15 +52,11 @@ describe('<MenuTriggerProvider /> - Visual content', () => {
|
|
|
53
52
|
rerender(<MenuTriggerProviderWrapper isOpen={false} />)
|
|
54
53
|
expect(screen.queryByText('menu-content-mock')).not.toBeInTheDocument()
|
|
55
54
|
})
|
|
56
|
-
|
|
57
|
-
it('fires the onOpenChange callback when the trigger is interacted', async () => {
|
|
55
|
+
it('fires the onOpenChange callback on user interaction to close the menu', async () => {
|
|
58
56
|
const onOpenChange = vi.fn()
|
|
59
57
|
render(<MenuTriggerProviderWrapper isOpen onOpenChange={onOpenChange} />)
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
name: 'trigger-display-label-mock',
|
|
63
|
-
})
|
|
64
|
-
await user.click(trigger)
|
|
59
|
+
await user.keyboard('{Escape}')
|
|
65
60
|
|
|
66
61
|
await waitFor(() => {
|
|
67
62
|
expect(onOpenChange).toBeCalledTimes(1)
|
|
@@ -86,17 +81,6 @@ describe('<MenuTriggerProvider /> - Mouse interaction', () => {
|
|
|
86
81
|
})
|
|
87
82
|
|
|
88
83
|
describe('Given the menu is opened', () => {
|
|
89
|
-
it('is closed when user clicks on the trigger', async () => {
|
|
90
|
-
render(<MenuTriggerProviderWrapper defaultOpen />)
|
|
91
|
-
const trigger = screen.getByRole('button', {
|
|
92
|
-
name: 'trigger-display-label-mock',
|
|
93
|
-
})
|
|
94
|
-
await user.click(trigger)
|
|
95
|
-
await waitFor(() => {
|
|
96
|
-
expect(screen.queryByText('menu-content-mock')).not.toBeInTheDocument()
|
|
97
|
-
})
|
|
98
|
-
})
|
|
99
|
-
|
|
100
84
|
it('is closed when user clicks outside of the menu', async () => {
|
|
101
85
|
render(<MenuTriggerProviderWrapper defaultOpen />)
|
|
102
86
|
await user.click(document.body)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
@layer kz-components {
|
|
2
|
+
.listBox {
|
|
3
|
+
list-style: none;
|
|
4
|
+
padding: var(--spacing-12);
|
|
5
|
+
margin: 0 var(--spacing-12) 0 0;
|
|
6
|
+
display: grid;
|
|
7
|
+
max-height: 22rem;
|
|
8
|
+
overflow-y: auto;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.overflown {
|
|
12
|
+
padding-right: var(--spacing-12);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.hidden {
|
|
16
|
+
display: none;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.noResultsWrapper {
|
|
20
|
+
list-style: none;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -3,7 +3,7 @@ import { type Collection, type Key } from '@react-types/shared'
|
|
|
3
3
|
import classnames from 'classnames'
|
|
4
4
|
import { useSelectionContext } from '../../context/SelectionProvider'
|
|
5
5
|
import { type MultiSelectItem } from '../../types'
|
|
6
|
-
import styles from './ListBox.module.
|
|
6
|
+
import styles from './ListBox.module.css'
|
|
7
7
|
|
|
8
8
|
export type ListBoxItems = {
|
|
9
9
|
selectedItems: MultiSelectItem[]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
@layer kz-components {
|
|
2
|
+
.menuPopup {
|
|
3
|
+
/* from $ca-z-index-dropdown */
|
|
4
|
+
z-index: 1000;
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
background: var(--color-white);
|
|
7
|
+
color: var(--color-purple-800);
|
|
8
|
+
border-radius: var(--border-solid-border-radius);
|
|
9
|
+
box-shadow: var(--shadow-large-box-shadow);
|
|
10
|
+
padding: var(--spacing-6) 0;
|
|
11
|
+
margin-top: var(--spacing-6);
|
|
12
|
+
text-align: start;
|
|
13
|
+
width: var(--menu-container-width, 294px);
|
|
14
|
+
max-height: var(--menu-container-height, 500px);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.menuPopup[popover]:popover-open {
|
|
18
|
+
z-index: unset;
|
|
19
|
+
margin: 0;
|
|
20
|
+
inset: unset;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -1,58 +1,78 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
1
|
+
import React, { useEffect, useState, type HTMLAttributes } from 'react'
|
|
2
|
+
import { autoUpdate, offset, useFloating, type UseFloatingOptions } from '@floating-ui/react-dom'
|
|
3
|
+
import classnames from 'classnames'
|
|
4
|
+
import { FocusOn } from 'react-focus-on'
|
|
5
|
+
import { type OverrideClassName } from '~components/types/OverrideClassName'
|
|
4
6
|
import { useMenuTriggerContext } from '../../context'
|
|
5
|
-
import styles from './MenuPopup.module.
|
|
7
|
+
import styles from './MenuPopup.module.css'
|
|
6
8
|
|
|
7
9
|
export type MenuPopupProps = {
|
|
10
|
+
children: React.ReactNode
|
|
11
|
+
floatingOptions?: Partial<UseFloatingOptions>
|
|
8
12
|
isLoading?: boolean
|
|
9
13
|
loadingSkeleton?: React.ReactNode
|
|
10
|
-
|
|
11
|
-
}
|
|
14
|
+
} & OverrideClassName<HTMLAttributes<HTMLDivElement>>
|
|
12
15
|
|
|
13
16
|
export const MenuPopup = ({
|
|
17
|
+
children,
|
|
18
|
+
floatingOptions,
|
|
19
|
+
classNameOverride,
|
|
14
20
|
isLoading,
|
|
15
21
|
loadingSkeleton,
|
|
16
|
-
|
|
22
|
+
...restProps
|
|
17
23
|
}: MenuPopupProps): JSX.Element => {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
isOpen: menuTriggerState.isOpen,
|
|
29
|
-
isDismissable: true,
|
|
24
|
+
const [floatingElement, setFloatingElement] = useState<HTMLDivElement | null>(null)
|
|
25
|
+
const { menuTriggerState, buttonRef } = useMenuTriggerContext()
|
|
26
|
+
|
|
27
|
+
const referenceElement = buttonRef.current
|
|
28
|
+
|
|
29
|
+
const { floatingStyles, update } = useFloating({
|
|
30
|
+
placement: 'bottom-start',
|
|
31
|
+
elements: {
|
|
32
|
+
reference: referenceElement,
|
|
33
|
+
floating: floatingElement,
|
|
30
34
|
},
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
strategy: 'absolute',
|
|
36
|
+
middleware: [offset(6)],
|
|
37
|
+
whileElementsMounted: autoUpdate,
|
|
38
|
+
...floatingOptions,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const handleReturnFocus = (): void => {
|
|
42
|
+
requestAnimationFrame(() => {
|
|
43
|
+
buttonRef.current?.focus()
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (floatingElement && referenceElement) {
|
|
49
|
+
floatingElement.showPopover?.()
|
|
50
|
+
update()
|
|
51
|
+
}
|
|
52
|
+
}, [floatingElement, referenceElement, update])
|
|
33
53
|
|
|
34
|
-
// Wrap in <FocusScope> so that focus is restored back to the trigger when the menu is closed
|
|
35
|
-
// and auto focus on the first focusable item after loading. (disable eslint no-autofocus error for it)
|
|
36
|
-
// In addition, add hidden <DismissButton> components at the start and end of the list
|
|
37
|
-
// to allow screen reader users to dismiss the popup easily.
|
|
38
54
|
return menuTriggerState.isOpen ? (
|
|
39
|
-
<
|
|
40
|
-
{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
<FocusOn
|
|
56
|
+
enabled={menuTriggerState.isOpen}
|
|
57
|
+
scrollLock={false}
|
|
58
|
+
returnFocus={false}
|
|
59
|
+
onClickOutside={menuTriggerState.close}
|
|
60
|
+
onEscapeKey={menuTriggerState.close}
|
|
61
|
+
onDeactivation={handleReturnFocus}
|
|
62
|
+
>
|
|
63
|
+
<div
|
|
64
|
+
ref={setFloatingElement}
|
|
65
|
+
style={floatingStyles}
|
|
66
|
+
className={classnames(styles.menuPopup, classNameOverride)}
|
|
67
|
+
role="dialog"
|
|
68
|
+
aria-modal="true"
|
|
69
|
+
// @ts-expect-error: popover is valid in supported browsers
|
|
70
|
+
popover="manual"
|
|
71
|
+
{...restProps}
|
|
72
|
+
>
|
|
73
|
+
{isLoading && loadingSkeleton ? loadingSkeleton : children}
|
|
74
|
+
</div>
|
|
75
|
+
</FocusOn>
|
|
56
76
|
) : (
|
|
57
77
|
<></>
|
|
58
78
|
)
|
|
@@ -25,6 +25,10 @@ export type LikertScaleProps = {
|
|
|
25
25
|
'colorSchema'?: ColorSchema | 'classical'
|
|
26
26
|
'validationMessage'?: string
|
|
27
27
|
'status'?: 'default' | 'error'
|
|
28
|
+
/**
|
|
29
|
+
* Sets aria-required value on radiogroup for assistive technologies. Validation must still be handled.
|
|
30
|
+
*/
|
|
31
|
+
'isRequired'?: boolean
|
|
28
32
|
'onSelect': (value: ScaleItem | null) => void
|
|
29
33
|
}
|
|
30
34
|
|
|
@@ -46,6 +50,7 @@ export const LikertScaleLegacy = ({
|
|
|
46
50
|
validationMessage,
|
|
47
51
|
status,
|
|
48
52
|
labelId,
|
|
53
|
+
isRequired,
|
|
49
54
|
}: LikertScaleProps): JSX.Element => {
|
|
50
55
|
const [hoveredItem, setHoveredItem] = useState<ScaleItem | null>(null)
|
|
51
56
|
const itemRefs: ItemRefs = scale.map((s) => ({
|
|
@@ -104,11 +109,12 @@ export const LikertScaleLegacy = ({
|
|
|
104
109
|
reversed && [styles.reversed],
|
|
105
110
|
hoveredItem !== null && styles.hovered,
|
|
106
111
|
)}
|
|
107
|
-
aria-labelledby={labelId}
|
|
112
|
+
aria-labelledby={isRequired ? `${labelId}` : labelId}
|
|
108
113
|
role="radiogroup"
|
|
109
114
|
tabIndex={-1}
|
|
110
115
|
aria-describedby={validationMessageId}
|
|
111
116
|
data-testid={dataTestId}
|
|
117
|
+
aria-required={isRequired}
|
|
112
118
|
>
|
|
113
119
|
<div className={styles.legend} data-testid={dataTestId && `${dataTestId}-legend`}>
|
|
114
120
|
<Text variant="small" color={reversed ? 'white' : 'dark'}>
|
|
@@ -21,3 +21,11 @@ Likert scale radio buttons let people select one option in a Likert scale rangin
|
|
|
21
21
|
|
|
22
22
|
<Canvas of={LikertScaleLegacyStories.Playground} />
|
|
23
23
|
<Controls of={LikertScaleLegacyStories.Playground} />
|
|
24
|
+
|
|
25
|
+
## API
|
|
26
|
+
|
|
27
|
+
### isRequired
|
|
28
|
+
|
|
29
|
+
Sets aria-required value on radiogroup for assistive technologies. An accessible label must be provided and validation must still be handled within implementations.
|
|
30
|
+
|
|
31
|
+
<Canvas of={LikertScaleLegacyStories.IsRequired} />
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { type Meta, type StoryObj } from '@storybook/react'
|
|
3
|
+
import { expect, within } from '@storybook/test'
|
|
4
|
+
import { VisuallyHidden } from '~components/VisuallyHidden'
|
|
3
5
|
import { LikertScaleLegacy } from '../index'
|
|
4
6
|
import { type Scale, type ScaleItem } from '../types'
|
|
5
7
|
|
|
@@ -57,7 +59,7 @@ export const Playground: Story = {
|
|
|
57
59
|
code: `
|
|
58
60
|
const SatisfactionExample = () => {
|
|
59
61
|
const [selectedItem, setSelectedItem] = useState<ScaleItem | null>(null)
|
|
60
|
-
|
|
62
|
+
|
|
61
63
|
return (
|
|
62
64
|
<LikertScaleLegacy
|
|
63
65
|
scale={[
|
|
@@ -82,3 +84,30 @@ export const Playground: Story = {
|
|
|
82
84
|
},
|
|
83
85
|
},
|
|
84
86
|
}
|
|
87
|
+
|
|
88
|
+
export const IsRequired: Story = {
|
|
89
|
+
render: (args) => {
|
|
90
|
+
const [selectedItem, setSelectedItem] = useState<ScaleItem | null>(null)
|
|
91
|
+
const labelId = React.useId()
|
|
92
|
+
return (
|
|
93
|
+
<div>
|
|
94
|
+
<VisuallyHidden id={labelId}>Likert scale label</VisuallyHidden>
|
|
95
|
+
<LikertScaleLegacy
|
|
96
|
+
{...args}
|
|
97
|
+
labelId={labelId}
|
|
98
|
+
selectedItem={selectedItem}
|
|
99
|
+
onSelect={setSelectedItem}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
)
|
|
103
|
+
},
|
|
104
|
+
args: {
|
|
105
|
+
isRequired: true,
|
|
106
|
+
},
|
|
107
|
+
play: async ({ canvasElement }) => {
|
|
108
|
+
const canvas = within(canvasElement.parentElement!)
|
|
109
|
+
const likertScale = canvas.getByRole('radiogroup', { name: 'Likert scale label' })
|
|
110
|
+
|
|
111
|
+
expect(likertScale).toHaveAttribute('aria-required', 'true')
|
|
112
|
+
},
|
|
113
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Meta } from '@storybook/blocks'
|
|
2
|
+
|
|
3
|
+
<Meta title="Components/Menu/Migration guide" />
|
|
4
|
+
|
|
5
|
+
# Menu migration guide
|
|
6
|
+
|
|
7
|
+
## Audience
|
|
8
|
+
|
|
9
|
+
This guide is relevant for Kaizen All-In-One (KAIO) v1 consumers.
|
|
10
|
+
|
|
11
|
+
## Purpose
|
|
12
|
+
|
|
13
|
+
This guide provides instructions for migrating menu usage from the `deprecated` (`@kaizen/components`) `Menu` component to the `next` (`@kaizen/components/next`) `Menu` component.
|
|
14
|
+
|
|
15
|
+
This migration is a prerequisite for [migrating to KAIO v2](/docs/releases-upcoming-major-releases--docs).
|
|
16
|
+
|
|
17
|
+
## Key API changes
|
|
18
|
+
|
|
19
|
+
`next/Menu` separates its functionality into the following components:
|
|
20
|
+
|
|
21
|
+
- `MenuTrigger` wraps the `MenuPopover` component and its trigger element.
|
|
22
|
+
- `MenuPopover` contains a `Menu` component, and controls the popover placement and open and close interactions.
|
|
23
|
+
- `Menu` contains one or more `MenuItem` and `MenuSection` components.
|
|
24
|
+
- `MenuSection` enables menu items to be grouped into sections.
|
|
25
|
+
- `MenuHeader` provides a section's header content.
|
|
26
|
+
- `MenuItem` provides a menu item's content, and handles item selection.
|
|
27
|
+
|
|
28
|
+
Other notable changes:
|
|
29
|
+
|
|
30
|
+
- `Menu.align` prop becomes MenuPopover.placement, and values are mapped as follows:
|
|
31
|
+
- `left` becomes `start`
|
|
32
|
+
- `right` becomes `end`
|
|
33
|
+
- `Menu.autoHide` prop is retired
|
|
34
|
+
- `Menu.button` prop becomes `MenuTrigger.children`
|
|
35
|
+
- The trigger element must be a `next/Button`
|
|
36
|
+
- `Menu.dropdownWidth` prop is retired
|
|
37
|
+
- `Menu.portalSelector` prop is retired
|
|
38
|
+
- Where needed, [PortalProvider](https://react-spectrum.adobe.com/react-aria/PortalProvider.html) can be used to control portalling behaviour
|
|
39
|
+
- `MenuItem.destructive` prop is retired
|
|
40
|
+
- This change aligns with a broader move towards more judicious use of colour
|
|
41
|
+
- `MenuItem.disabled` prop becomes `MenuItem.isDisabled`
|
|
42
|
+
- `MenuItem.label` prop becomes `MenuItem.children`
|
|
43
|
+
- `MenuItem.onClick` prop becomes `MenuItem.onAction`
|
|
44
|
+
- React Aria's `Menu` does not expose native click events, e.g. `MenuItem.onAction` cannot call `e.preventDefault()`
|
|
45
|
+
- See React Aria [Menu documentation](https://react-spectrum.adobe.com/react-aria/Menu.html) for more details on working with `Menu` and `MenuItem` events
|
|
46
|
+
- `MenuList.heading` prop becomes `MenuHeader` in a `MenuSection`
|
|
47
|
+
|
|
48
|
+
## Migration example
|
|
49
|
+
|
|
50
|
+
### Before
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<Menu button={<Button>Trigger</Button>}>
|
|
54
|
+
<MenuList>
|
|
55
|
+
<MenuList heading={<MenuHeading>Section One</MenuHeading>}>
|
|
56
|
+
<MenuItem onClick={() => alert('1')} label="Item 1" />
|
|
57
|
+
<MenuItem onClick={() => alert('2')} label="Item 2" />
|
|
58
|
+
</MenuList>
|
|
59
|
+
<MenuList heading={<MenuHeading>Section Two</MenuHeading>}>
|
|
60
|
+
<MenuItem onClick={() => alert('3')} label="Item 3" />
|
|
61
|
+
<MenuItem onClick={() => alert('4')} label="Item 4" />
|
|
62
|
+
</MenuList>
|
|
63
|
+
</MenuList>
|
|
64
|
+
</Menu>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### After
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
<MenuTrigger>
|
|
71
|
+
<Button>Trigger</Button>
|
|
72
|
+
<MenuPopover>
|
|
73
|
+
<Menu>
|
|
74
|
+
<MenuSection>
|
|
75
|
+
<MenuHeader>Section One</MenuHeader>
|
|
76
|
+
<MenuItem onAction={() => alert('1')}>Item 1</MenuItem>
|
|
77
|
+
<MenuItem onAction={() => alert('2')}>Item 2</MenuItem>
|
|
78
|
+
</MenuSection>
|
|
79
|
+
<MenuSection>
|
|
80
|
+
<MenuHeader>Section Two</MenuHeader>
|
|
81
|
+
<MenuItem onAction={() => alert('3')}>Item 3</MenuItem>
|
|
82
|
+
<MenuItem onAction={() => alert('4')}>Item 4</MenuItem>
|
|
83
|
+
</MenuSection>
|
|
84
|
+
</Menu>
|
|
85
|
+
</MenuPopover>
|
|
86
|
+
</MenuTrigger>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## More information
|
|
90
|
+
|
|
91
|
+
More information about `next/Menu` can be found at [API Specification](/docs/components-menu-menu-next-api-specification--docs) and [Usage Guidelines](/docs/components-menu-menu-next-usage-guidelines--docs).
|
|
@@ -98,7 +98,7 @@ When using the `isRequired` property you can also specify the `validationBehavio
|
|
|
98
98
|
|
|
99
99
|
<Canvas of={SelectStories.SelectNativeValidationBehavior} />
|
|
100
100
|
|
|
101
|
-
While both use `aria-required` to announce whether the field has to have value to assistive technologies, the `native` will option will prevent form submissions if the `selectedKey` is `undefined`.
|
|
101
|
+
While both use `aria-required` to announce whether the field has to have a value to assistive technologies, the `native` will option will prevent form submissions if the `selectedKey` is `undefined`.
|
|
102
102
|
|
|
103
103
|
### Full width
|
|
104
104
|
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var styles = {
|
|
4
|
-
"listBox": "ListBox-module_listBox__q95MO",
|
|
5
|
-
"overflown": "ListBox-module_overflown__wChQA",
|
|
6
|
-
"hidden": "ListBox-module_hidden__eYdXv",
|
|
7
|
-
"noResultsWrapper": "ListBox-module_noResultsWrapper__WcLRm"
|
|
8
|
-
};
|
|
9
|
-
module.exports = styles;
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
@import '~@kaizen/design-tokens/sass/spacing';
|
|
2
|
-
|
|
3
|
-
@layer kz-components {
|
|
4
|
-
.listBox {
|
|
5
|
-
list-style: none;
|
|
6
|
-
padding: $spacing-sm;
|
|
7
|
-
margin: 0 $spacing-sm 0 0;
|
|
8
|
-
display: grid;
|
|
9
|
-
max-height: 22rem;
|
|
10
|
-
overflow-y: auto;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
.overflown {
|
|
14
|
-
padding-right: $spacing-sm;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.hidden {
|
|
18
|
-
display: none;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// this is a div but remove styles briefly flickering to a bullet list as the sections are removed
|
|
22
|
-
.noResultsWrapper {
|
|
23
|
-
list-style: none;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
@import '~@kaizen/design-tokens/sass/spacing';
|
|
2
|
-
@import '~@kaizen/design-tokens/sass/shadow';
|
|
3
|
-
@import '~@kaizen/design-tokens/sass/border';
|
|
4
|
-
@import '~@kaizen/design-tokens/sass/color';
|
|
5
|
-
|
|
6
|
-
@layer kz-components {
|
|
7
|
-
// figma hard coded: https://www.figma.com/file/eZKEE5kXbEMY3lx84oz8iN/%E2%9D%A4%EF%B8%8F-UI-Kit%3A-Heart?node-id=22814%3A96966
|
|
8
|
-
$menu-container-width: 294px;
|
|
9
|
-
$menu-container-max-height: 312px;
|
|
10
|
-
|
|
11
|
-
.menuPopup {
|
|
12
|
-
position: absolute;
|
|
13
|
-
z-index: 1000; // from $ca-z-index-dropdown
|
|
14
|
-
box-sizing: border-box;
|
|
15
|
-
background: $color-white;
|
|
16
|
-
color: $color-purple-800;
|
|
17
|
-
border-radius: $border-solid-border-radius;
|
|
18
|
-
box-shadow: $shadow-large-box-shadow;
|
|
19
|
-
padding: $spacing-sm 0;
|
|
20
|
-
margin-top: $spacing-xs;
|
|
21
|
-
text-align: start;
|
|
22
|
-
width: $menu-container-width;
|
|
23
|
-
}
|
|
24
|
-
}
|