@kaizen/components 1.64.3 → 1.64.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Filter/FilterSelect/FilterSelect.cjs +11 -4
- package/dist/cjs/Notification/InlineNotification/InlineNotification.cjs +4 -3
- package/dist/cjs/Notification/subcomponents/GenericNotification/GenericNotification.cjs +5 -3
- package/dist/cjs/__future__/Select/subcomponents/ListBox/ListBox.cjs +42 -5
- package/dist/cjs/__future__/Select/subcomponents/Overlay/Overlay.cjs +1 -1
- package/dist/esm/Filter/FilterSelect/FilterSelect.mjs +11 -4
- package/dist/esm/Notification/InlineNotification/InlineNotification.mjs +5 -4
- package/dist/esm/Notification/subcomponents/GenericNotification/GenericNotification.mjs +6 -4
- package/dist/esm/__future__/Select/subcomponents/ListBox/ListBox.mjs +44 -6
- package/dist/esm/__future__/Select/subcomponents/Overlay/Overlay.mjs +1 -1
- package/dist/styles.css +71 -67
- package/dist/types/Notification/InlineNotification/InlineNotification.d.ts +1 -4
- package/dist/types/Notification/subcomponents/GenericNotification/GenericNotification.d.ts +1 -4
- package/package.json +3 -3
- package/src/Filter/FilterSelect/FilterSelect.spec.tsx +2 -2
- package/src/Filter/FilterSelect/FilterSelect.tsx +18 -8
- package/src/Filter/FilterSelect/_docs/FilterSelect.stories.tsx +1 -0
- package/src/Notification/InlineNotification/InlineNotification.tsx +25 -16
- package/src/Notification/InlineNotification/_docs/InlineNotification.stickersheet.stories.tsx +18 -0
- package/src/Notification/subcomponents/GenericNotification/GenericNotification.stories.tsx +44 -1
- package/src/Notification/subcomponents/GenericNotification/GenericNotification.tsx +93 -72
- package/src/Notification/subcomponents/GenericNotification/_mixins.scss +5 -0
- package/src/__future__/Select/Select.spec.tsx +5 -3
- package/src/__future__/Select/_docs/Select.mdx +1 -3
- package/src/__future__/Select/_docs/Select.stories.tsx +33 -16
- package/src/__future__/Select/subcomponents/ListBox/ListBox.tsx +45 -2
- package/src/__future__/Select/subcomponents/Overlay/Overlay.tsx +1 -1
|
@@ -57,6 +57,11 @@ var FilterSelect = function (_a) {
|
|
|
57
57
|
triggerProps = _c.triggerProps,
|
|
58
58
|
menuProps = _c.menuProps;
|
|
59
59
|
var buttonProps = button.useButton(triggerProps, triggerRef).buttonProps;
|
|
60
|
+
// The id is being remapped because the buttonProps id points to nowhere. This should ideally be refactored but will point the aria attributes tot he right components
|
|
61
|
+
var renderTriggerButtonProps = tslib.__assign(tslib.__assign({}, buttonProps), {
|
|
62
|
+
"aria-labelledby": undefined,
|
|
63
|
+
"aria-controls": menuProps.id
|
|
64
|
+
});
|
|
60
65
|
return React__default.default.createElement(React__default.default.Fragment, null, React__default.default.createElement(select$1.HiddenSelect, {
|
|
61
66
|
label: label,
|
|
62
67
|
state: state,
|
|
@@ -70,17 +75,19 @@ var FilterSelect = function (_a) {
|
|
|
70
75
|
selectedValue: ((_a = state.selectedItem) === null || _a === void 0 ? void 0 : _a.textValue) || undefined,
|
|
71
76
|
label: label,
|
|
72
77
|
isOpen: isOpen
|
|
73
|
-
},
|
|
78
|
+
}, renderTriggerButtonProps));
|
|
74
79
|
},
|
|
75
80
|
onMount: setTriggerRef,
|
|
76
81
|
classNameOverride: classNameOverride
|
|
77
|
-
}, React__default.default.createElement(FilterContents.FilterContents, {
|
|
82
|
+
}, React__default.default.createElement(React__default.default.Fragment, null, React__default.default.createElement(FilterContents.FilterContents, {
|
|
78
83
|
classNameOverride: FilterSelect_module.filterContents
|
|
79
84
|
}, React__default.default.createElement(SelectContext.SelectProvider, {
|
|
80
85
|
state: state
|
|
81
86
|
}, React__default.default.createElement(SelectPopoverContents.SelectPopoverContents, {
|
|
82
|
-
menuProps: menuProps
|
|
83
|
-
|
|
87
|
+
menuProps: tslib.__assign(tslib.__assign({}, menuProps), {
|
|
88
|
+
"aria-labelledby": buttonProps.id
|
|
89
|
+
})
|
|
90
|
+
}, children))))));
|
|
84
91
|
};
|
|
85
92
|
FilterSelect.displayName = "FilterSelect";
|
|
86
93
|
FilterSelect.Section = ListBoxSection.ListBoxSection;
|
|
@@ -17,7 +17,7 @@ var classnames__default = /*#__PURE__*/_interopDefault(classnames);
|
|
|
17
17
|
* {@link https://cultureamp.atlassian.net/wiki/spaces/DesignSystem/pages/3082093392/Inline+Notification Guidance} |
|
|
18
18
|
* {@link https://cultureamp.design/storybook/?path=/docs/components-notifications-inline-notification--docs Storybook}
|
|
19
19
|
*/
|
|
20
|
-
var InlineNotification = function (_a) {
|
|
20
|
+
var InlineNotification = React.forwardRef(function (_a, ref) {
|
|
21
21
|
var isSubtle = _a.isSubtle,
|
|
22
22
|
_b = _a.hideCloseIcon,
|
|
23
23
|
hideCloseIcon = _b === void 0 ? false : _b,
|
|
@@ -28,8 +28,9 @@ var InlineNotification = function (_a) {
|
|
|
28
28
|
return React__default.default.createElement(GenericNotification.GenericNotification, tslib.__assign({
|
|
29
29
|
style: "inline",
|
|
30
30
|
persistent: persistent || hideCloseIcon,
|
|
31
|
-
classNameOverride: classnames__default.default(classNameOverride, [isSubtle && GenericNotification_module.subtle])
|
|
31
|
+
classNameOverride: classnames__default.default(classNameOverride, [isSubtle && GenericNotification_module.subtle]),
|
|
32
|
+
ref: ref
|
|
32
33
|
}, otherProps));
|
|
33
|
-
};
|
|
34
|
+
});
|
|
34
35
|
InlineNotification.displayName = "InlineNotification";
|
|
35
36
|
exports.InlineNotification = InlineNotification;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var tslib = require('tslib');
|
|
4
4
|
var React = require('react');
|
|
5
5
|
var classnames = require('classnames');
|
|
6
|
+
var isRefObject = require('../../../utils/isRefObject.cjs');
|
|
6
7
|
var CancelButton = require('../CancelButton/CancelButton.cjs');
|
|
7
8
|
var NotificationHeading = require('../NotificationHeading/NotificationHeading.cjs');
|
|
8
9
|
var NotificationIcon = require('../NotificationIcon/NotificationIcon.cjs');
|
|
@@ -14,7 +15,7 @@ function _interopDefault(e) {
|
|
|
14
15
|
}
|
|
15
16
|
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
16
17
|
var classnames__default = /*#__PURE__*/_interopDefault(classnames);
|
|
17
|
-
var GenericNotification = function (_a) {
|
|
18
|
+
var GenericNotification = React.forwardRef(function (_a, ref) {
|
|
18
19
|
var type = _a.type,
|
|
19
20
|
variant = _a.variant,
|
|
20
21
|
style = _a.style,
|
|
@@ -34,7 +35,8 @@ var GenericNotification = function (_a) {
|
|
|
34
35
|
var _d = React.useState(false),
|
|
35
36
|
isRemoved = _d[0],
|
|
36
37
|
setIsRemoved = _d[1];
|
|
37
|
-
var
|
|
38
|
+
var fallbackRef = React.useRef(null);
|
|
39
|
+
var containerRef = isRefObject.isRefObject(ref) ? ref : fallbackRef;
|
|
38
40
|
React.useEffect(function () {
|
|
39
41
|
requestAnimationFrame(function () {
|
|
40
42
|
if (containerRef.current) {
|
|
@@ -83,6 +85,6 @@ var GenericNotification = function (_a) {
|
|
|
83
85
|
return setIsHidden(true);
|
|
84
86
|
}
|
|
85
87
|
}));
|
|
86
|
-
};
|
|
88
|
+
});
|
|
87
89
|
GenericNotification.displayName = "GenericNotification";
|
|
88
90
|
exports.GenericNotification = GenericNotification;
|
|
@@ -13,6 +13,19 @@ function _interopDefault(e) {
|
|
|
13
13
|
}
|
|
14
14
|
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
15
15
|
var classnames__default = /*#__PURE__*/_interopDefault(classnames);
|
|
16
|
+
|
|
17
|
+
/** A util to retrieve the key of the correct focusable items based of the focus strategy
|
|
18
|
+
* This is used to determine which element from the collection to focus to on open base on the keyboard event
|
|
19
|
+
* ie: UpArrow will set the focusStrategy to "last"
|
|
20
|
+
*/
|
|
21
|
+
var getOptionKeyFromCollection = function (state) {
|
|
22
|
+
if (state.selectedItem) {
|
|
23
|
+
return state.selectedItem.key;
|
|
24
|
+
} else if (state.focusStrategy === "last") {
|
|
25
|
+
return state.collection.getLastKey();
|
|
26
|
+
}
|
|
27
|
+
return state.collection.getFirstKey();
|
|
28
|
+
};
|
|
16
29
|
var ListBox = function (_a) {
|
|
17
30
|
var children = _a.children,
|
|
18
31
|
menuProps = _a.menuProps,
|
|
@@ -20,13 +33,37 @@ var ListBox = function (_a) {
|
|
|
20
33
|
restProps = tslib.__rest(_a, ["children", "menuProps", "classNameOverride"]);
|
|
21
34
|
var state = SelectContext.useSelectContext().state;
|
|
22
35
|
var ref = React__default.default.useRef(null);
|
|
36
|
+
var _b = React__default.default.useState(false),
|
|
37
|
+
isListboxReady = _b[0],
|
|
38
|
+
setListboxReady = _b[1];
|
|
23
39
|
var listBoxProps = listbox.useListBox(tslib.__assign(tslib.__assign({}, menuProps), {
|
|
24
|
-
disallowEmptySelection: true
|
|
40
|
+
disallowEmptySelection: true,
|
|
41
|
+
// This is to ensure that the listbox use React Aria's auto focus feature for Listbox, which creates a visual bug
|
|
42
|
+
autoFocus: false
|
|
25
43
|
}), state, ref).listBoxProps;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
44
|
+
/**
|
|
45
|
+
* This is a slightly hacky way to ensure the Listbox is aware of its position without using timeout.
|
|
46
|
+
* This solves the page from refocusing to the top of the DOM when it is opened for the first time with keyboard.
|
|
47
|
+
*/
|
|
48
|
+
React.useEffect(function () {
|
|
49
|
+
setListboxReady(true);
|
|
50
|
+
}, []);
|
|
51
|
+
React.useEffect(function () {
|
|
52
|
+
if (isListboxReady) {
|
|
53
|
+
var optionKey = getOptionKeyFromCollection(state);
|
|
54
|
+
var focusToElement = document.querySelector("[data-key=\"".concat(optionKey, "\"]"));
|
|
55
|
+
if (focusToElement) {
|
|
56
|
+
focusToElement.focus();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}, [isListboxReady]);
|
|
60
|
+
return (
|
|
61
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
|
62
|
+
React__default.default.createElement("ul", tslib.__assign({
|
|
63
|
+
ref: ref,
|
|
64
|
+
className: classnames__default.default(ListBox_module.listBox, classNameOverride)
|
|
65
|
+
}, listBoxProps, restProps), children)
|
|
66
|
+
);
|
|
30
67
|
};
|
|
31
68
|
ListBox.displayName = "ListBox";
|
|
32
69
|
exports.ListBox = ListBox;
|
|
@@ -32,7 +32,7 @@ var Overlay = function (_a) {
|
|
|
32
32
|
ref: overlayRef,
|
|
33
33
|
className: classNameOverride
|
|
34
34
|
}, overlayProps, restProps), React__default.default.createElement(focus.FocusScope, {
|
|
35
|
-
autoFocus:
|
|
35
|
+
autoFocus: false,
|
|
36
36
|
restoreFocus: true
|
|
37
37
|
}, React__default.default.createElement(overlays.DismissButton, {
|
|
38
38
|
onDismiss: state.close
|
|
@@ -50,6 +50,11 @@ const FilterSelect = /*#__PURE__*/function () {
|
|
|
50
50
|
triggerProps = _c.triggerProps,
|
|
51
51
|
menuProps = _c.menuProps;
|
|
52
52
|
var buttonProps = useButton(triggerProps, triggerRef).buttonProps;
|
|
53
|
+
// The id is being remapped because the buttonProps id points to nowhere. This should ideally be refactored but will point the aria attributes tot he right components
|
|
54
|
+
var renderTriggerButtonProps = __assign(__assign({}, buttonProps), {
|
|
55
|
+
"aria-labelledby": undefined,
|
|
56
|
+
"aria-controls": menuProps.id
|
|
57
|
+
});
|
|
53
58
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(HiddenSelect, {
|
|
54
59
|
label: label,
|
|
55
60
|
state: state,
|
|
@@ -63,17 +68,19 @@ const FilterSelect = /*#__PURE__*/function () {
|
|
|
63
68
|
selectedValue: ((_a = state.selectedItem) === null || _a === void 0 ? void 0 : _a.textValue) || undefined,
|
|
64
69
|
label: label,
|
|
65
70
|
isOpen: isOpen
|
|
66
|
-
},
|
|
71
|
+
}, renderTriggerButtonProps));
|
|
67
72
|
},
|
|
68
73
|
onMount: setTriggerRef,
|
|
69
74
|
classNameOverride: classNameOverride
|
|
70
|
-
}, /*#__PURE__*/React.createElement(FilterContents, {
|
|
75
|
+
}, /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(FilterContents, {
|
|
71
76
|
classNameOverride: styles.filterContents
|
|
72
77
|
}, /*#__PURE__*/React.createElement(SelectProvider, {
|
|
73
78
|
state: state
|
|
74
79
|
}, /*#__PURE__*/React.createElement(SelectPopoverContents, {
|
|
75
|
-
menuProps: menuProps
|
|
76
|
-
|
|
80
|
+
menuProps: __assign(__assign({}, menuProps), {
|
|
81
|
+
"aria-labelledby": buttonProps.id
|
|
82
|
+
})
|
|
83
|
+
}, children))))));
|
|
77
84
|
};
|
|
78
85
|
FilterSelect.displayName = "FilterSelect";
|
|
79
86
|
FilterSelect.Section = ListBoxSection;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { __rest, __assign } from 'tslib';
|
|
2
|
-
import React from 'react';
|
|
2
|
+
import React, { forwardRef } from 'react';
|
|
3
3
|
import classnames from 'classnames';
|
|
4
4
|
import { GenericNotification } from '../subcomponents/GenericNotification/GenericNotification.mjs';
|
|
5
5
|
import styles from '../subcomponents/GenericNotification/GenericNotification.module.scss.mjs';
|
|
@@ -9,7 +9,7 @@ import styles from '../subcomponents/GenericNotification/GenericNotification.mod
|
|
|
9
9
|
* {@link https://cultureamp.design/storybook/?path=/docs/components-notifications-inline-notification--docs Storybook}
|
|
10
10
|
*/
|
|
11
11
|
const InlineNotification = /*#__PURE__*/function () {
|
|
12
|
-
const InlineNotification = function (_a) {
|
|
12
|
+
const InlineNotification = /*#__PURE__*/forwardRef(function (_a, ref) {
|
|
13
13
|
var isSubtle = _a.isSubtle,
|
|
14
14
|
_b = _a.hideCloseIcon,
|
|
15
15
|
hideCloseIcon = _b === void 0 ? false : _b,
|
|
@@ -20,9 +20,10 @@ const InlineNotification = /*#__PURE__*/function () {
|
|
|
20
20
|
return /*#__PURE__*/React.createElement(GenericNotification, __assign({
|
|
21
21
|
style: "inline",
|
|
22
22
|
persistent: persistent || hideCloseIcon,
|
|
23
|
-
classNameOverride: classnames(classNameOverride, [isSubtle && styles.subtle])
|
|
23
|
+
classNameOverride: classnames(classNameOverride, [isSubtle && styles.subtle]),
|
|
24
|
+
ref: ref
|
|
24
25
|
}, otherProps));
|
|
25
|
-
};
|
|
26
|
+
});
|
|
26
27
|
InlineNotification.displayName = "InlineNotification";
|
|
27
28
|
return InlineNotification;
|
|
28
29
|
}();
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { __rest, __assign } from 'tslib';
|
|
2
|
-
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import React, { forwardRef, useState, useRef, useEffect } from 'react';
|
|
3
3
|
import classnames from 'classnames';
|
|
4
|
+
import { isRefObject } from '../../../utils/isRefObject.mjs';
|
|
4
5
|
import { CancelButton } from '../CancelButton/CancelButton.mjs';
|
|
5
6
|
import { NotificationHeading } from '../NotificationHeading/NotificationHeading.mjs';
|
|
6
7
|
import { NotificationIconType, NotificationIconVariant } from '../NotificationIcon/NotificationIcon.mjs';
|
|
7
8
|
import styles from './GenericNotification.module.scss.mjs';
|
|
8
9
|
const GenericNotification = /*#__PURE__*/function () {
|
|
9
|
-
const GenericNotification = function (_a) {
|
|
10
|
+
const GenericNotification = /*#__PURE__*/forwardRef(function (_a, ref) {
|
|
10
11
|
var type = _a.type,
|
|
11
12
|
variant = _a.variant,
|
|
12
13
|
style = _a.style,
|
|
@@ -26,7 +27,8 @@ const GenericNotification = /*#__PURE__*/function () {
|
|
|
26
27
|
var _d = useState(false),
|
|
27
28
|
isRemoved = _d[0],
|
|
28
29
|
setIsRemoved = _d[1];
|
|
29
|
-
var
|
|
30
|
+
var fallbackRef = useRef(null);
|
|
31
|
+
var containerRef = isRefObject(ref) ? ref : fallbackRef;
|
|
30
32
|
useEffect(function () {
|
|
31
33
|
requestAnimationFrame(function () {
|
|
32
34
|
if (containerRef.current) {
|
|
@@ -75,7 +77,7 @@ const GenericNotification = /*#__PURE__*/function () {
|
|
|
75
77
|
return setIsHidden(true);
|
|
76
78
|
}
|
|
77
79
|
}));
|
|
78
|
-
};
|
|
80
|
+
});
|
|
79
81
|
GenericNotification.displayName = "GenericNotification";
|
|
80
82
|
return GenericNotification;
|
|
81
83
|
}();
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import { __rest, __assign } from 'tslib';
|
|
2
|
-
import React from 'react';
|
|
2
|
+
import React, { useEffect } from 'react';
|
|
3
3
|
import { useListBox } from '@react-aria/listbox';
|
|
4
4
|
import classnames from 'classnames';
|
|
5
5
|
import { useSelectContext } from '../../context/SelectContext.mjs';
|
|
6
6
|
import styles from './ListBox.module.scss.mjs';
|
|
7
|
+
|
|
8
|
+
/** A util to retrieve the key of the correct focusable items based of the focus strategy
|
|
9
|
+
* This is used to determine which element from the collection to focus to on open base on the keyboard event
|
|
10
|
+
* ie: UpArrow will set the focusStrategy to "last"
|
|
11
|
+
*/
|
|
12
|
+
var getOptionKeyFromCollection = function (state) {
|
|
13
|
+
if (state.selectedItem) {
|
|
14
|
+
return state.selectedItem.key;
|
|
15
|
+
} else if (state.focusStrategy === "last") {
|
|
16
|
+
return state.collection.getLastKey();
|
|
17
|
+
}
|
|
18
|
+
return state.collection.getFirstKey();
|
|
19
|
+
};
|
|
7
20
|
const ListBox = /*#__PURE__*/function () {
|
|
8
21
|
const ListBox = function (_a) {
|
|
9
22
|
var children = _a.children,
|
|
@@ -12,13 +25,38 @@ const ListBox = /*#__PURE__*/function () {
|
|
|
12
25
|
restProps = __rest(_a, ["children", "menuProps", "classNameOverride"]);
|
|
13
26
|
var state = useSelectContext().state;
|
|
14
27
|
var ref = React.useRef(null);
|
|
28
|
+
var _b = React.useState(false),
|
|
29
|
+
isListboxReady = _b[0],
|
|
30
|
+
setListboxReady = _b[1];
|
|
15
31
|
var listBoxProps = useListBox(__assign(__assign({}, menuProps), {
|
|
16
|
-
disallowEmptySelection: true
|
|
32
|
+
disallowEmptySelection: true,
|
|
33
|
+
// This is to ensure that the listbox use React Aria's auto focus feature for Listbox, which creates a visual bug
|
|
34
|
+
autoFocus: false
|
|
17
35
|
}), state, ref).listBoxProps;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
36
|
+
/**
|
|
37
|
+
* This is a slightly hacky way to ensure the Listbox is aware of its position without using timeout.
|
|
38
|
+
* This solves the page from refocusing to the top of the DOM when it is opened for the first time with keyboard.
|
|
39
|
+
*/
|
|
40
|
+
useEffect(function () {
|
|
41
|
+
setListboxReady(true);
|
|
42
|
+
}, []);
|
|
43
|
+
useEffect(function () {
|
|
44
|
+
if (isListboxReady) {
|
|
45
|
+
var optionKey = getOptionKeyFromCollection(state);
|
|
46
|
+
var focusToElement = document.querySelector("[data-key=\"".concat(optionKey, "\"]"));
|
|
47
|
+
if (focusToElement) {
|
|
48
|
+
focusToElement.focus();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}, [isListboxReady]);
|
|
52
|
+
return (
|
|
53
|
+
/*#__PURE__*/
|
|
54
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
|
55
|
+
React.createElement("ul", __assign({
|
|
56
|
+
ref: ref,
|
|
57
|
+
className: classnames(styles.listBox, classNameOverride)
|
|
58
|
+
}, listBoxProps, restProps), children)
|
|
59
|
+
);
|
|
22
60
|
};
|
|
23
61
|
ListBox.displayName = "ListBox";
|
|
24
62
|
return ListBox;
|
|
@@ -25,7 +25,7 @@ const Overlay = /*#__PURE__*/function () {
|
|
|
25
25
|
ref: overlayRef,
|
|
26
26
|
className: classNameOverride
|
|
27
27
|
}, overlayProps, restProps), /*#__PURE__*/React.createElement(FocusScope, {
|
|
28
|
-
autoFocus:
|
|
28
|
+
autoFocus: false,
|
|
29
29
|
restoreFocus: true
|
|
30
30
|
}, /*#__PURE__*/React.createElement(DismissButton, {
|
|
31
31
|
onDismiss: state.close
|
package/dist/styles.css
CHANGED
|
@@ -27,73 +27,6 @@
|
|
|
27
27
|
.OverlayArrow-module_overlayArrow__hoDyK.OverlayArrow-module_reversed__-WGcR path {
|
|
28
28
|
fill: var(--color-white, #ffffff);
|
|
29
29
|
}
|
|
30
|
-
.Menu-module_menu__iHYqh {
|
|
31
|
-
background-color: var(--color-white);
|
|
32
|
-
color: var(--color-purple-800);
|
|
33
|
-
width: 248px;
|
|
34
|
-
max-height: 22rem;
|
|
35
|
-
overflow: auto;
|
|
36
|
-
padding-block: var(--spacing-6);
|
|
37
|
-
outline: none;
|
|
38
|
-
border-radius: var(--border-solid-border-radius);
|
|
39
|
-
box-shadow: var(--shadow-large-box-shadow);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.Menu-module_menu__iHYqh .react-aria-Header {
|
|
43
|
-
font-family: var(--typography-heading-6-font-family);
|
|
44
|
-
font-size: var(--typography-heading-6-font-size);
|
|
45
|
-
letter-spacing: var(--typography-heading-6-letter-spacing);
|
|
46
|
-
font-weight: var(--typography-heading-6-font-weight);
|
|
47
|
-
line-height: var(--typography-heading-6-line-height);
|
|
48
|
-
padding: var(--spacing-6) 10px;
|
|
49
|
-
margin-inline: var(--spacing-6);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.Menu-module_menu__iHYqh section:not(:last-of-type)::after {
|
|
53
|
-
width: 100%;
|
|
54
|
-
height: 1px;
|
|
55
|
-
background-color: var(--border-solid-border-color);
|
|
56
|
-
content: "";
|
|
57
|
-
display: block;
|
|
58
|
-
margin-block: var(--spacing-6);
|
|
59
|
-
}
|
|
60
|
-
.MenuItem-module_item__DPerF {
|
|
61
|
-
font-family: var(--typography-paragraph-body-font-family);
|
|
62
|
-
font-size: var(--typography-paragraph-body-font-size);
|
|
63
|
-
letter-spacing: var(--typography-paragraph-body-letter-spacing);
|
|
64
|
-
font-weight: var(--typography-paragraph-body-font-weight);
|
|
65
|
-
line-height: var(--typography-paragraph-body-line-height);
|
|
66
|
-
color: rgba(var(--color-purple-800-rgb), 0.7);
|
|
67
|
-
padding: var(--spacing-6) var(--spacing-8);
|
|
68
|
-
border: var(--border-focus-ring-border-width) var(--border-focus-ring-border-style) transparent;
|
|
69
|
-
border-radius: 4px;
|
|
70
|
-
display: flex;
|
|
71
|
-
gap: 0 var(--spacing-8);
|
|
72
|
-
align-items: center;
|
|
73
|
-
margin-inline: var(--spacing-6);
|
|
74
|
-
text-decoration: none;
|
|
75
|
-
cursor: pointer;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.MenuItem-module_iconWrapper__bRdQN {
|
|
79
|
-
flex-shrink: 0;
|
|
80
|
-
display: flex;
|
|
81
|
-
align-items: center;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.MenuItem-module_item__DPerF[data-focused] {
|
|
85
|
-
background-color: var(--color-blue-100);
|
|
86
|
-
color: var(--color-blue-500);
|
|
87
|
-
outline: none;
|
|
88
|
-
border-color: var(--color-blue-500);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.MenuItem-module_item__DPerF[data-disabled] {
|
|
92
|
-
opacity: 0.3;
|
|
93
|
-
}
|
|
94
|
-
.Focusable-module_focusableWrapper__NfuIi {
|
|
95
|
-
display: inline-flex;
|
|
96
|
-
}
|
|
97
30
|
.Button-module_button__QOSYH {
|
|
98
31
|
--button-min-height-width: var(--spacing-48);
|
|
99
32
|
--button-padding-x: calc(
|
|
@@ -182,6 +115,73 @@
|
|
|
182
115
|
--button-icon-size: var(--spacing-16);
|
|
183
116
|
gap: var(--spacing-8);
|
|
184
117
|
}
|
|
118
|
+
.Menu-module_menu__iHYqh {
|
|
119
|
+
background-color: var(--color-white);
|
|
120
|
+
color: var(--color-purple-800);
|
|
121
|
+
width: 248px;
|
|
122
|
+
max-height: 22rem;
|
|
123
|
+
overflow: auto;
|
|
124
|
+
padding-block: var(--spacing-6);
|
|
125
|
+
outline: none;
|
|
126
|
+
border-radius: var(--border-solid-border-radius);
|
|
127
|
+
box-shadow: var(--shadow-large-box-shadow);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.Menu-module_menu__iHYqh .react-aria-Header {
|
|
131
|
+
font-family: var(--typography-heading-6-font-family);
|
|
132
|
+
font-size: var(--typography-heading-6-font-size);
|
|
133
|
+
letter-spacing: var(--typography-heading-6-letter-spacing);
|
|
134
|
+
font-weight: var(--typography-heading-6-font-weight);
|
|
135
|
+
line-height: var(--typography-heading-6-line-height);
|
|
136
|
+
padding: var(--spacing-6) 10px;
|
|
137
|
+
margin-inline: var(--spacing-6);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.Menu-module_menu__iHYqh section:not(:last-of-type)::after {
|
|
141
|
+
width: 100%;
|
|
142
|
+
height: 1px;
|
|
143
|
+
background-color: var(--border-solid-border-color);
|
|
144
|
+
content: "";
|
|
145
|
+
display: block;
|
|
146
|
+
margin-block: var(--spacing-6);
|
|
147
|
+
}
|
|
148
|
+
.MenuItem-module_item__DPerF {
|
|
149
|
+
font-family: var(--typography-paragraph-body-font-family);
|
|
150
|
+
font-size: var(--typography-paragraph-body-font-size);
|
|
151
|
+
letter-spacing: var(--typography-paragraph-body-letter-spacing);
|
|
152
|
+
font-weight: var(--typography-paragraph-body-font-weight);
|
|
153
|
+
line-height: var(--typography-paragraph-body-line-height);
|
|
154
|
+
color: rgba(var(--color-purple-800-rgb), 0.7);
|
|
155
|
+
padding: var(--spacing-6) var(--spacing-8);
|
|
156
|
+
border: var(--border-focus-ring-border-width) var(--border-focus-ring-border-style) transparent;
|
|
157
|
+
border-radius: 4px;
|
|
158
|
+
display: flex;
|
|
159
|
+
gap: 0 var(--spacing-8);
|
|
160
|
+
align-items: center;
|
|
161
|
+
margin-inline: var(--spacing-6);
|
|
162
|
+
text-decoration: none;
|
|
163
|
+
cursor: pointer;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.MenuItem-module_iconWrapper__bRdQN {
|
|
167
|
+
flex-shrink: 0;
|
|
168
|
+
display: flex;
|
|
169
|
+
align-items: center;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.MenuItem-module_item__DPerF[data-focused] {
|
|
173
|
+
background-color: var(--color-blue-100);
|
|
174
|
+
color: var(--color-blue-500);
|
|
175
|
+
outline: none;
|
|
176
|
+
border-color: var(--color-blue-500);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.MenuItem-module_item__DPerF[data-disabled] {
|
|
180
|
+
opacity: 0.3;
|
|
181
|
+
}
|
|
182
|
+
.Focusable-module_focusableWrapper__NfuIi {
|
|
183
|
+
display: inline-flex;
|
|
184
|
+
}
|
|
185
185
|
/** THIS IS AN AUTOGENERATED FILE **/
|
|
186
186
|
/** THIS IS AN AUTOGENERATED FILE **/
|
|
187
187
|
/** THIS IS AN AUTOGENERATED FILE **/
|
|
@@ -5408,6 +5408,10 @@ input[type=range].InputRange-module_ratingScaleRange__gI-rs::-ms-thumb:not(:disa
|
|
|
5408
5408
|
box-sizing: border-box;
|
|
5409
5409
|
pointer-events: all;
|
|
5410
5410
|
}
|
|
5411
|
+
.GenericNotification-module_notification__-JP1M:focus {
|
|
5412
|
+
outline-offset: 1px;
|
|
5413
|
+
outline: 2px solid var(--color-blue-500);
|
|
5414
|
+
}
|
|
5411
5415
|
.GenericNotification-module_notification__-JP1M.GenericNotification-module_inline__OciO5, .GenericNotification-module_notification__-JP1M.GenericNotification-module_toast__GOVfC {
|
|
5412
5416
|
margin-bottom: var(--spacing-24, 1.5rem);
|
|
5413
5417
|
border-width: var(--border-width-1);
|
|
@@ -19,7 +19,4 @@ export type InlineNotificationProps = InlineNotificationBase & (GenericNotificat
|
|
|
19
19
|
* {@link https://cultureamp.atlassian.net/wiki/spaces/DesignSystem/pages/3082093392/Inline+Notification Guidance} |
|
|
20
20
|
* {@link https://cultureamp.design/storybook/?path=/docs/components-notifications-inline-notification--docs Storybook}
|
|
21
21
|
*/
|
|
22
|
-
export declare const InlineNotification:
|
|
23
|
-
({ isSubtle, hideCloseIcon, persistent, classNameOverride, ...otherProps }: InlineNotificationProps): JSX.Element;
|
|
24
|
-
displayName: string;
|
|
25
|
-
};
|
|
22
|
+
export declare const InlineNotification: React.ForwardRefExoticComponent<InlineNotificationProps & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -32,8 +32,5 @@ export type GenericNotificationVariant = {
|
|
|
32
32
|
variant: NotificationVariant;
|
|
33
33
|
};
|
|
34
34
|
export type GenericNotificationProps = GenericNotificationBase & (GenericNotificationType | GenericNotificationVariant);
|
|
35
|
-
export declare const GenericNotification:
|
|
36
|
-
({ type, variant, style, children, title, persistent, onHide, noBottomMargin, forceMultiline, headingProps, classNameOverride, ...restProps }: GenericNotificationProps): JSX.Element | null;
|
|
37
|
-
displayName: string;
|
|
38
|
-
};
|
|
35
|
+
export declare const GenericNotification: React.ForwardRefExoticComponent<GenericNotificationProps & React.RefAttributes<HTMLDivElement>>;
|
|
39
36
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaizen/components",
|
|
3
|
-
"version": "1.64.
|
|
3
|
+
"version": "1.64.5",
|
|
4
4
|
"description": "Kaizen component library",
|
|
5
5
|
"author": "Geoffrey Chong <geoff.chong@cultureamp.com>",
|
|
6
6
|
"homepage": "https://cultureamp.design",
|
|
@@ -120,8 +120,8 @@
|
|
|
120
120
|
"svgo": "^3.3.2",
|
|
121
121
|
"tslib": "^2.6.3",
|
|
122
122
|
"tsx": "^4.17.0",
|
|
123
|
-
"@kaizen/
|
|
124
|
-
"@kaizen/
|
|
123
|
+
"@kaizen/design-tokens": "10.6.1",
|
|
124
|
+
"@kaizen/package-bundler": "1.1.5"
|
|
125
125
|
},
|
|
126
126
|
"peerDependencies": {
|
|
127
127
|
"@cultureamp/i18n-react-intl": "^2.5.9",
|
|
@@ -48,7 +48,7 @@ describe("<FilterSelect>", () => {
|
|
|
48
48
|
it("shows the options initially when isOpen is true", async () => {
|
|
49
49
|
render(<FilterSelectWrapper isOpen />)
|
|
50
50
|
await waitFor(() => {
|
|
51
|
-
expect(screen.
|
|
51
|
+
expect(screen.getByRole("listbox")).toBeVisible()
|
|
52
52
|
})
|
|
53
53
|
})
|
|
54
54
|
|
|
@@ -107,7 +107,7 @@ describe("<FilterSelect>", () => {
|
|
|
107
107
|
render(<FilterSelectWrapper isOpen />)
|
|
108
108
|
expect(screen.queryByRole("listbox")).toBeVisible()
|
|
109
109
|
await waitFor(() => {
|
|
110
|
-
expect(screen.
|
|
110
|
+
expect(screen.getAllByRole("option")[0]).toHaveFocus()
|
|
111
111
|
})
|
|
112
112
|
})
|
|
113
113
|
|
|
@@ -79,6 +79,12 @@ export const FilterSelect = <Option extends SelectOption = SelectOption>({
|
|
|
79
79
|
|
|
80
80
|
const { buttonProps } = useButton(triggerProps, triggerRef)
|
|
81
81
|
|
|
82
|
+
// The id is being remapped because the buttonProps id points to nowhere. This should ideally be refactored but will point the aria attributes tot he right components
|
|
83
|
+
const renderTriggerButtonProps = {
|
|
84
|
+
...buttonProps,
|
|
85
|
+
"aria-labelledby": undefined,
|
|
86
|
+
"aria-controls": menuProps.id,
|
|
87
|
+
}
|
|
82
88
|
return (
|
|
83
89
|
<>
|
|
84
90
|
<HiddenSelect label={label} state={state} triggerRef={triggerRef} />
|
|
@@ -90,19 +96,23 @@ export const FilterSelect = <Option extends SelectOption = SelectOption>({
|
|
|
90
96
|
selectedValue: state.selectedItem?.textValue || undefined,
|
|
91
97
|
label,
|
|
92
98
|
isOpen,
|
|
93
|
-
...
|
|
99
|
+
...renderTriggerButtonProps,
|
|
94
100
|
})
|
|
95
101
|
}
|
|
96
102
|
onMount={setTriggerRef}
|
|
97
103
|
classNameOverride={classNameOverride}
|
|
98
104
|
>
|
|
99
|
-
|
|
100
|
-
<
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
<>
|
|
106
|
+
<FilterContents classNameOverride={styles.filterContents}>
|
|
107
|
+
<SelectProvider<Option> state={state}>
|
|
108
|
+
<SelectPopoverContents
|
|
109
|
+
menuProps={{ ...menuProps, "aria-labelledby": buttonProps.id }}
|
|
110
|
+
>
|
|
111
|
+
{children}
|
|
112
|
+
</SelectPopoverContents>
|
|
113
|
+
</SelectProvider>
|
|
114
|
+
</FilterContents>
|
|
115
|
+
</>
|
|
106
116
|
</Filter>
|
|
107
117
|
</>
|
|
108
118
|
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useState } from "react"
|
|
2
|
+
import { Story } from "@storybook/blocks"
|
|
2
3
|
import { Meta, StoryObj } from "@storybook/react"
|
|
3
4
|
import { fn } from "@storybook/test"
|
|
4
5
|
import { renderTriggerControls } from "~components/Filter/_docs/controls/renderTriggerControls"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { HTMLAttributes } from "react"
|
|
1
|
+
import React, { forwardRef, HTMLAttributes } from "react"
|
|
2
2
|
import classnames from "classnames"
|
|
3
3
|
import { HeadingProps } from "~components/Heading"
|
|
4
4
|
import { OverrideClassName } from "~components/types/OverrideClassName"
|
|
@@ -30,21 +30,30 @@ export type InlineNotificationProps = InlineNotificationBase &
|
|
|
30
30
|
* {@link https://cultureamp.atlassian.net/wiki/spaces/DesignSystem/pages/3082093392/Inline+Notification Guidance} |
|
|
31
31
|
* {@link https://cultureamp.design/storybook/?path=/docs/components-notifications-inline-notification--docs Storybook}
|
|
32
32
|
*/
|
|
33
|
-
export const InlineNotification =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
33
|
+
export const InlineNotification = forwardRef<
|
|
34
|
+
HTMLDivElement,
|
|
35
|
+
InlineNotificationProps
|
|
36
|
+
>(
|
|
37
|
+
(
|
|
38
|
+
{
|
|
39
|
+
isSubtle,
|
|
40
|
+
hideCloseIcon = false,
|
|
41
|
+
persistent = false,
|
|
42
|
+
classNameOverride,
|
|
43
|
+
...otherProps
|
|
44
|
+
},
|
|
45
|
+
ref
|
|
46
|
+
): JSX.Element => (
|
|
47
|
+
<GenericNotification
|
|
48
|
+
style="inline"
|
|
49
|
+
persistent={persistent || hideCloseIcon}
|
|
50
|
+
classNameOverride={classnames(classNameOverride, [
|
|
51
|
+
isSubtle && styles.subtle,
|
|
52
|
+
])}
|
|
53
|
+
ref={ref}
|
|
54
|
+
{...otherProps}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
48
57
|
)
|
|
49
58
|
|
|
50
59
|
InlineNotification.displayName = "InlineNotification"
|
package/src/Notification/InlineNotification/_docs/InlineNotification.stickersheet.stories.tsx
CHANGED
|
@@ -131,6 +131,19 @@ const VARIANTS_PROPS: Array<{
|
|
|
131
131
|
forceMultiline: true,
|
|
132
132
|
},
|
|
133
133
|
},
|
|
134
|
+
{
|
|
135
|
+
title: "Focus",
|
|
136
|
+
props: {
|
|
137
|
+
// @ts-ignore
|
|
138
|
+
"data-sb-pseudo-styles": "focus",
|
|
139
|
+
variant: "informative",
|
|
140
|
+
headingProps: {
|
|
141
|
+
variant: "heading-6",
|
|
142
|
+
tag: "span",
|
|
143
|
+
children: "Focused title",
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
134
147
|
]
|
|
135
148
|
|
|
136
149
|
const TYPE_PROPS: Array<{
|
|
@@ -206,6 +219,11 @@ const StickerSheetTemplate: StickerSheetStory = {
|
|
|
206
219
|
</StickerSheet>
|
|
207
220
|
</>
|
|
208
221
|
),
|
|
222
|
+
parameters: {
|
|
223
|
+
pseudo: {
|
|
224
|
+
focus: '[data-sb-pseudo-styles="focus"]',
|
|
225
|
+
},
|
|
226
|
+
},
|
|
209
227
|
}
|
|
210
228
|
|
|
211
229
|
export const StickerSheetDefault: StickerSheetStory = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState } from "react"
|
|
1
|
+
import React, { useRef, useState } from "react"
|
|
2
2
|
import { Meta, StoryObj } from "@storybook/react"
|
|
3
3
|
import { userEvent, within, expect, waitFor } from "@storybook/test"
|
|
4
4
|
import { GenericNotification } from "./index"
|
|
@@ -58,3 +58,46 @@ export const GenericNotificationTest: Story = {
|
|
|
58
58
|
})
|
|
59
59
|
},
|
|
60
60
|
}
|
|
61
|
+
|
|
62
|
+
export const RefTest: Story = {
|
|
63
|
+
render: () => {
|
|
64
|
+
const customRef = useRef<HTMLDivElement>(null)
|
|
65
|
+
const [isHidden, setIsHidden] = useState<boolean>(false)
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div>
|
|
69
|
+
<span data-testid="hidden-state">{isHidden ? "Hidden" : "Shown"}</span>
|
|
70
|
+
<GenericNotification
|
|
71
|
+
ref={customRef}
|
|
72
|
+
variant="success"
|
|
73
|
+
style="inline"
|
|
74
|
+
title="Success"
|
|
75
|
+
data-testid="generic-notification"
|
|
76
|
+
onHide={() => setIsHidden(true)}
|
|
77
|
+
>
|
|
78
|
+
This is my positive notification
|
|
79
|
+
</GenericNotification>
|
|
80
|
+
</div>
|
|
81
|
+
)
|
|
82
|
+
},
|
|
83
|
+
name: "Test: still renders and closes properly when custom ref passed in",
|
|
84
|
+
play: async ({ canvasElement }) => {
|
|
85
|
+
const canvas = within(canvasElement)
|
|
86
|
+
const element = canvas.getByTestId("generic-notification")
|
|
87
|
+
const hiddenState = canvas.getByTestId("hidden-state")
|
|
88
|
+
|
|
89
|
+
await waitFor(() => {
|
|
90
|
+
expect(element).toBeInTheDocument()
|
|
91
|
+
expect(hiddenState).toHaveTextContent("Shown")
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
await userEvent.click(canvas.getByTestId("close-button"))
|
|
95
|
+
|
|
96
|
+
await waitFor(() => {
|
|
97
|
+
setTimeout(() => {
|
|
98
|
+
expect(hiddenState).toHaveTextContent("Hidden")
|
|
99
|
+
expect(element).not.toBeInTheDocument()
|
|
100
|
+
}, 1000)
|
|
101
|
+
})
|
|
102
|
+
},
|
|
103
|
+
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
HTMLAttributes,
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from "react"
|
|
2
8
|
import classnames from "classnames"
|
|
3
9
|
import { HeadingProps } from "~components/Heading"
|
|
4
10
|
import {
|
|
@@ -6,6 +12,7 @@ import {
|
|
|
6
12
|
NotificationVariant,
|
|
7
13
|
} from "~components/Notification/types"
|
|
8
14
|
import { OverrideClassName } from "~components/types/OverrideClassName"
|
|
15
|
+
import { isRefObject } from "~components/utils/isRefObject"
|
|
9
16
|
import { CancelButton } from "../CancelButton"
|
|
10
17
|
import { NotificationHeading } from "../NotificationHeading"
|
|
11
18
|
import {
|
|
@@ -49,89 +56,103 @@ export type GenericNotificationVariant = {
|
|
|
49
56
|
export type GenericNotificationProps = GenericNotificationBase &
|
|
50
57
|
(GenericNotificationType | GenericNotificationVariant)
|
|
51
58
|
|
|
52
|
-
export const GenericNotification =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
export const GenericNotification = forwardRef<
|
|
60
|
+
HTMLDivElement,
|
|
61
|
+
GenericNotificationProps
|
|
62
|
+
>(
|
|
63
|
+
(
|
|
64
|
+
{
|
|
65
|
+
type,
|
|
66
|
+
variant,
|
|
67
|
+
style,
|
|
68
|
+
children,
|
|
69
|
+
title,
|
|
70
|
+
persistent = false,
|
|
71
|
+
onHide,
|
|
72
|
+
noBottomMargin,
|
|
73
|
+
forceMultiline,
|
|
74
|
+
headingProps,
|
|
75
|
+
classNameOverride,
|
|
76
|
+
...restProps
|
|
77
|
+
},
|
|
78
|
+
ref
|
|
79
|
+
): JSX.Element | null => {
|
|
80
|
+
const [isHidden, setIsHidden] = useState<boolean>(true)
|
|
81
|
+
const [isRemoved, setIsRemoved] = useState<boolean>(false)
|
|
68
82
|
|
|
69
|
-
|
|
83
|
+
const fallbackRef = useRef<HTMLDivElement>(null)
|
|
84
|
+
const containerRef = isRefObject(ref) ? ref : fallbackRef
|
|
70
85
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
requestAnimationFrame(() => {
|
|
88
|
+
if (containerRef.current) {
|
|
89
|
+
setIsHidden(false)
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
}, [])
|
|
78
93
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
94
|
+
const getMarginTop = (): string => {
|
|
95
|
+
if (isHidden && containerRef.current) {
|
|
96
|
+
return -containerRef.current.clientHeight + "px"
|
|
97
|
+
}
|
|
98
|
+
return "0"
|
|
82
99
|
}
|
|
83
|
-
return "0"
|
|
84
|
-
}
|
|
85
100
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
const onTransitionEnd = (
|
|
102
|
+
e: React.TransitionEvent<HTMLDivElement>
|
|
103
|
+
): void => {
|
|
104
|
+
// Be careful: this assumes the final CSS property to be animated is "margin-top".
|
|
105
|
+
if (isHidden && e.propertyName === "margin-top") {
|
|
106
|
+
setIsRemoved(true)
|
|
107
|
+
onHide?.()
|
|
108
|
+
}
|
|
91
109
|
}
|
|
92
|
-
}
|
|
93
110
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
111
|
+
if (isRemoved) {
|
|
112
|
+
return null
|
|
113
|
+
}
|
|
97
114
|
|
|
98
|
-
|
|
99
|
-
<div
|
|
100
|
-
ref={containerRef}
|
|
101
|
-
className={classnames(
|
|
102
|
-
styles.notification,
|
|
103
|
-
variant ? styles[variant] : styles[type],
|
|
104
|
-
styles[style],
|
|
105
|
-
isHidden && styles.hidden,
|
|
106
|
-
noBottomMargin && styles.noBottomMargin,
|
|
107
|
-
classNameOverride,
|
|
108
|
-
persistent && styles.persistent
|
|
109
|
-
)}
|
|
110
|
-
style={{ marginTop: getMarginTop() }}
|
|
111
|
-
onTransitionEnd={onTransitionEnd}
|
|
112
|
-
{...restProps}
|
|
113
|
-
>
|
|
114
|
-
<div className={styles.icon}>
|
|
115
|
-
{type ? (
|
|
116
|
-
<NotificationIconType type={type} />
|
|
117
|
-
) : (
|
|
118
|
-
<NotificationIconVariant variant={variant} />
|
|
119
|
-
)}
|
|
120
|
-
</div>
|
|
115
|
+
return (
|
|
121
116
|
<div
|
|
117
|
+
ref={containerRef}
|
|
122
118
|
className={classnames(
|
|
123
|
-
styles.
|
|
124
|
-
|
|
119
|
+
styles.notification,
|
|
120
|
+
variant ? styles[variant] : styles[type],
|
|
121
|
+
styles[style],
|
|
122
|
+
isHidden && styles.hidden,
|
|
123
|
+
noBottomMargin && styles.noBottomMargin,
|
|
124
|
+
classNameOverride,
|
|
125
|
+
persistent && styles.persistent
|
|
125
126
|
)}
|
|
127
|
+
style={{ marginTop: getMarginTop() }}
|
|
128
|
+
onTransitionEnd={onTransitionEnd}
|
|
129
|
+
{...restProps}
|
|
126
130
|
>
|
|
127
|
-
{
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
+
<div className={styles.icon}>
|
|
132
|
+
{type ? (
|
|
133
|
+
<NotificationIconType type={type} />
|
|
134
|
+
) : (
|
|
135
|
+
<NotificationIconVariant variant={variant} />
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
<div
|
|
139
|
+
className={classnames(
|
|
140
|
+
styles.textContainer,
|
|
141
|
+
forceMultiline && styles.forceMultiline
|
|
142
|
+
)}
|
|
143
|
+
>
|
|
144
|
+
{style !== "global" && (
|
|
145
|
+
<NotificationHeading
|
|
146
|
+
titleProp={title}
|
|
147
|
+
headingProps={headingProps}
|
|
148
|
+
/>
|
|
149
|
+
)}
|
|
150
|
+
{children && <div className={styles.text}>{children}</div>}
|
|
151
|
+
</div>
|
|
152
|
+
{!persistent && <CancelButton onClick={() => setIsHidden(true)} />}
|
|
131
153
|
</div>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
)
|
|
136
157
|
|
|
137
158
|
GenericNotification.displayName = "GenericNotification"
|
|
@@ -22,6 +22,11 @@ $notification-slide-right: transform 300ms ease-out;
|
|
|
22
22
|
box-sizing: border-box;
|
|
23
23
|
pointer-events: all;
|
|
24
24
|
|
|
25
|
+
&:focus {
|
|
26
|
+
outline-offset: 1px;
|
|
27
|
+
outline: 2px solid var(--color-blue-500);
|
|
28
|
+
}
|
|
29
|
+
|
|
25
30
|
// Variants
|
|
26
31
|
&%ca-notification---inline,
|
|
27
32
|
&%ca-notification---toast {
|
|
@@ -194,11 +194,13 @@ describe("<Select />", () => {
|
|
|
194
194
|
})
|
|
195
195
|
|
|
196
196
|
describe("Given the menu is opened", () => {
|
|
197
|
-
it("focuses the
|
|
198
|
-
const { getByRole } = render(
|
|
197
|
+
it("focuses on the first item", async () => {
|
|
198
|
+
const { getByRole, getAllByRole } = render(
|
|
199
|
+
<SelectWrapper defaultOpen />
|
|
200
|
+
)
|
|
199
201
|
expect(getByRole("listbox")).toBeVisible()
|
|
200
202
|
await waitFor(() => {
|
|
201
|
-
expect(
|
|
203
|
+
expect(getAllByRole("option")[0]).toHaveFocus()
|
|
202
204
|
})
|
|
203
205
|
})
|
|
204
206
|
it("is closed when hits the escape key", async () => {
|
|
@@ -98,8 +98,6 @@ Set `isFullWidth` to `true` to have the Select span the full width of its contai
|
|
|
98
98
|
|
|
99
99
|
By default, the Select's popover will attach itself to the `body` of the document using React's `createPortal`.
|
|
100
100
|
|
|
101
|
-
You can change the default
|
|
101
|
+
You can change the default behavior by providing a `portalContainerId` to attach this to different element in the DOM. This can help to resolve issues that may arise with `z-index` or having a Select in a modal.
|
|
102
102
|
|
|
103
103
|
<Canvas of={SelectStories.PortalContainer} />
|
|
104
|
-
|
|
105
|
-
There is currently a known issue whereby a selected option will cause the page to scroll to the top of the window on open (click on [default to see example](https://cultureamp.design/?path=/docs/components-select-future--docs#portals)). This can be solved by setting a `portalContainerId` to the closest parent of the Select.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react"
|
|
2
2
|
import { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { ContextModal } from "~components/Modal"
|
|
3
4
|
import { Select } from "../Select"
|
|
4
5
|
import { SelectOption } from "../types"
|
|
5
6
|
import {
|
|
@@ -165,25 +166,41 @@ export const FullWidth: Story = {
|
|
|
165
166
|
export const PortalContainer: Story = {
|
|
166
167
|
render: args => {
|
|
167
168
|
const portalContainerId = "id--portal-container"
|
|
169
|
+
|
|
170
|
+
const [isOpen, setIsOpen] = React.useState(false)
|
|
171
|
+
|
|
172
|
+
const handleOpen = (): void => setIsOpen(true)
|
|
173
|
+
const handleClose = (): void => setIsOpen(false)
|
|
168
174
|
return (
|
|
169
175
|
<>
|
|
170
|
-
<div
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
{
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
<Select
|
|
176
|
+
<div className=" h-[500px] mb-24 block bg-gray-100 flex flex-col gap-16 justify-center items-center">
|
|
177
|
+
Page content
|
|
178
|
+
<button
|
|
179
|
+
type="button"
|
|
180
|
+
className="border border-gray-500"
|
|
181
|
+
onClick={handleOpen}
|
|
182
|
+
>
|
|
183
|
+
Open Modal
|
|
184
|
+
</button>
|
|
185
|
+
<ContextModal
|
|
181
186
|
{...args}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
+
isOpen={isOpen}
|
|
188
|
+
onConfirm={handleClose}
|
|
189
|
+
onDismiss={handleClose}
|
|
190
|
+
title="Select test"
|
|
191
|
+
>
|
|
192
|
+
<div
|
|
193
|
+
className="flex gap-24 bg-gray-200 p-12 h-[500px] relative"
|
|
194
|
+
id={portalContainerId}
|
|
195
|
+
>
|
|
196
|
+
<Select
|
|
197
|
+
{...args}
|
|
198
|
+
label="Select within a modal"
|
|
199
|
+
id="id--select-inner"
|
|
200
|
+
portalContainerId={portalContainerId}
|
|
201
|
+
/>
|
|
202
|
+
</div>
|
|
203
|
+
</ContextModal>
|
|
187
204
|
</div>
|
|
188
205
|
</>
|
|
189
206
|
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import React, { HTMLAttributes } from "react"
|
|
1
|
+
import React, { HTMLAttributes, Key, useEffect } from "react"
|
|
2
2
|
import { AriaListBoxOptions, useListBox } from "@react-aria/listbox"
|
|
3
|
+
import { SelectState } from "@react-stately/select"
|
|
3
4
|
import classnames from "classnames"
|
|
4
5
|
import { OverrideClassName } from "~components/types/OverrideClassName"
|
|
5
6
|
import { useSelectContext } from "../../context"
|
|
@@ -14,6 +15,21 @@ export type SingleListBoxProps<Option extends SelectOption> = OverrideClassName<
|
|
|
14
15
|
menuProps: AriaListBoxOptions<SelectItem<Option>>
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
/** A util to retrieve the key of the correct focusable items based of the focus strategy
|
|
19
|
+
* This is used to determine which element from the collection to focus to on open base on the keyboard event
|
|
20
|
+
* ie: UpArrow will set the focusStrategy to "last"
|
|
21
|
+
*/
|
|
22
|
+
const getOptionKeyFromCollection = (
|
|
23
|
+
state: SelectState<SelectItem<any>>
|
|
24
|
+
): Key | null => {
|
|
25
|
+
if (state.selectedItem) {
|
|
26
|
+
return state.selectedItem.key
|
|
27
|
+
} else if (state.focusStrategy === "last") {
|
|
28
|
+
return state.collection.getLastKey()
|
|
29
|
+
}
|
|
30
|
+
return state.collection.getFirstKey()
|
|
31
|
+
}
|
|
32
|
+
|
|
17
33
|
export const ListBox = <Option extends SelectOption>({
|
|
18
34
|
children,
|
|
19
35
|
menuProps,
|
|
@@ -22,13 +38,40 @@ export const ListBox = <Option extends SelectOption>({
|
|
|
22
38
|
}: SingleListBoxProps<Option>): JSX.Element => {
|
|
23
39
|
const { state } = useSelectContext<Option>()
|
|
24
40
|
const ref = React.useRef<HTMLUListElement>(null)
|
|
41
|
+
const [isListboxReady, setListboxReady] = React.useState(false)
|
|
25
42
|
const { listBoxProps } = useListBox(
|
|
26
|
-
{
|
|
43
|
+
{
|
|
44
|
+
...menuProps,
|
|
45
|
+
disallowEmptySelection: true,
|
|
46
|
+
// This is to ensure that the listbox use React Aria's auto focus feature for Listbox, which creates a visual bug
|
|
47
|
+
autoFocus: false,
|
|
48
|
+
},
|
|
27
49
|
state,
|
|
28
50
|
ref
|
|
29
51
|
)
|
|
30
52
|
|
|
53
|
+
/**
|
|
54
|
+
* This is a slightly hacky way to ensure the Listbox is aware of its position without using timeout.
|
|
55
|
+
* This solves the page from refocusing to the top of the DOM when it is opened for the first time with keyboard.
|
|
56
|
+
*/
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
setListboxReady(true)
|
|
59
|
+
}, [])
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (isListboxReady) {
|
|
63
|
+
const optionKey = getOptionKeyFromCollection(state)
|
|
64
|
+
const focusToElement = document.querySelector(
|
|
65
|
+
`[data-key="${optionKey}"]`
|
|
66
|
+
) as HTMLElement
|
|
67
|
+
if (focusToElement) {
|
|
68
|
+
focusToElement.focus()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}, [isListboxReady])
|
|
72
|
+
|
|
31
73
|
return (
|
|
74
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
|
32
75
|
<ul
|
|
33
76
|
ref={ref}
|
|
34
77
|
className={classnames(styles.listBox, classNameOverride)}
|
|
@@ -36,7 +36,7 @@ export const Overlay = <Option extends SelectOption>({
|
|
|
36
36
|
{...restProps}
|
|
37
37
|
>
|
|
38
38
|
{/* eslint-disable-next-line jsx-a11y/no-autofocus */}
|
|
39
|
-
<FocusScope autoFocus restoreFocus>
|
|
39
|
+
<FocusScope autoFocus={false} restoreFocus>
|
|
40
40
|
<DismissButton onDismiss={state.close} />
|
|
41
41
|
{children}
|
|
42
42
|
<DismissButton onDismiss={state.close} />
|