reshaped 3.4.6 → 3.5.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +0 -13
- package/dist/bundle.css +1 -1
- package/dist/bundle.d.ts +2 -0
- package/dist/bundle.js +11 -11
- package/dist/components/Autocomplete/Autocomplete.js +25 -8
- package/dist/components/Autocomplete/Autocomplete.module.css +1 -0
- package/dist/components/Autocomplete/tests/Autocomplete.stories.d.ts +14 -1
- package/dist/components/Autocomplete/tests/Autocomplete.stories.js +181 -64
- package/dist/components/Badge/Badge.types.d.ts +4 -2
- package/dist/components/Button/Button.types.d.ts +4 -2
- package/dist/components/Calendar/Calendar.module.css +1 -1
- package/dist/components/Checkbox/Checkbox.js +17 -4
- package/dist/components/Checkbox/Checkbox.module.css +1 -1
- package/dist/components/Checkbox/Checkbox.types.d.ts +1 -0
- package/dist/components/Checkbox/tests/Checkbox.stories.d.ts +1 -0
- package/dist/components/Checkbox/tests/Checkbox.stories.js +40 -0
- package/dist/components/Dismissible/Dismissible.js +1 -0
- package/dist/components/DropdownMenu/DropdownMenu.js +1 -1
- package/dist/components/FormControl/FormControlLabel.js +2 -7
- package/dist/components/MenuItem/MenuItem.types.d.ts +4 -2
- package/dist/components/NumberField/NumberField.d.ts +6 -0
- package/dist/components/NumberField/NumberField.js +11 -0
- package/dist/components/NumberField/NumberField.module.css +1 -0
- package/dist/components/NumberField/NumberField.types.d.ts +19 -0
- package/dist/components/NumberField/NumberField.types.js +1 -0
- package/dist/components/NumberField/NumberFieldControlled.d.ts +6 -0
- package/dist/components/NumberField/NumberFieldControlled.js +162 -0
- package/dist/components/NumberField/NumberFieldUncontrolled.d.ts +6 -0
- package/dist/components/NumberField/NumberFieldUncontrolled.js +16 -0
- package/dist/components/NumberField/index.d.ts +2 -0
- package/dist/components/NumberField/index.js +1 -0
- package/dist/components/NumberField/tests/NumberField.stories.d.ts +30 -0
- package/dist/components/NumberField/tests/NumberField.stories.js +240 -0
- package/dist/components/Overlay/Overlay.js +2 -2
- package/dist/components/PinField/PinField.module.css +1 -1
- package/dist/components/PinField/PinField.types.d.ts +1 -1
- package/dist/components/PinField/PinFieldControlled.js +1 -0
- package/dist/components/PinField/tests/PinField.stories.js +8 -0
- package/dist/components/Radio/Radio.js +11 -4
- package/dist/components/Radio/Radio.module.css +1 -1
- package/dist/components/Radio/Radio.types.d.ts +1 -0
- package/dist/components/Radio/tests/Radio.stories.d.ts +1 -0
- package/dist/components/Radio/tests/Radio.stories.js +31 -0
- package/dist/components/Select/Select.module.css +1 -1
- package/dist/components/Select/Select.types.d.ts +1 -1
- package/dist/components/Select/tests/Select.stories.js +42 -16
- package/dist/components/Switch/Switch.js +10 -4
- package/dist/components/Switch/Switch.module.css +1 -1
- package/dist/components/Switch/Switch.types.d.ts +1 -1
- package/dist/components/Switch/tests/Switch.stories.js +30 -15
- package/dist/components/TextField/TextField.js +1 -1
- package/dist/components/TextField/TextField.module.css +1 -1
- package/dist/components/TextField/TextField.types.d.ts +2 -2
- package/dist/components/TextField/index.d.ts +1 -1
- package/dist/components/TextField/tests/TextField.stories.js +4 -0
- package/dist/components/Toast/ToastContainer.js +2 -2
- package/dist/components/_private/Flyout/FlyoutControlled.js +2 -2
- package/dist/hooks/tests/useScrollLock.stories.d.ts +1 -0
- package/dist/hooks/tests/useScrollLock.stories.js +29 -0
- package/dist/icons/ChevronUp.d.ts +2 -0
- package/dist/icons/ChevronUp.js +5 -0
- package/dist/icons/Minus.d.ts +2 -0
- package/dist/icons/Minus.js +3 -0
- package/dist/icons/Plus.d.ts +2 -2
- package/dist/icons/Plus.js +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/utilities/a11y/TrapFocus.d.ts +3 -17
- package/dist/utilities/a11y/TrapFocus.js +54 -49
- package/dist/utilities/a11y/tests/TrapFocus.stories.js +20 -20
- package/dist/utilities/scroll/lock.js +15 -7
- package/dist/utilities/scroll/lockStandard.d.ts +1 -2
- package/dist/utilities/scroll/lockStandard.js +2 -9
- package/package.json +31 -31
- package/dist/components/Autocomplete/tests/Autocomplete.test.stories.d.ts +0 -27
- package/dist/components/Autocomplete/tests/Autocomplete.test.stories.js +0 -86
@@ -1,3 +1,4 @@
|
|
1
|
+
var _a;
|
1
2
|
import Chain from "../Chain.js";
|
2
3
|
import * as keys from "../../constants/keys.js";
|
3
4
|
import TrapScreenReader from "./TrapScreenReader.js";
|
@@ -6,26 +7,25 @@ import { getShadowRoot } from "../dom/index.js";
|
|
6
7
|
import { checkKeyboardMode } from "./keyboardMode.js";
|
7
8
|
class TrapFocus {
|
8
9
|
static chain = new Chain();
|
9
|
-
chainId;
|
10
|
-
root;
|
11
|
-
trigger = null;
|
12
|
-
options = {};
|
10
|
+
#chainId;
|
11
|
+
#root = null;
|
12
|
+
#trigger = null;
|
13
|
+
#options = {};
|
13
14
|
trapped;
|
14
|
-
screenReaderTrap;
|
15
|
-
mutationObserver = null;
|
16
|
-
constructor(
|
17
|
-
this.root = root;
|
18
|
-
this.screenReaderTrap = new TrapScreenReader(root);
|
19
|
-
}
|
15
|
+
#screenReaderTrap = null;
|
16
|
+
#mutationObserver = null;
|
17
|
+
constructor() { }
|
20
18
|
/**
|
21
19
|
* Handle keyboard navigation while focus is trapped
|
22
20
|
*/
|
23
|
-
handleKeyDown = (event) => {
|
21
|
+
#handleKeyDown = (event) => {
|
24
22
|
if (event.defaultPrevented)
|
25
23
|
return;
|
26
|
-
if (
|
24
|
+
if (_a.chain.tailId !== this.#chainId)
|
27
25
|
return;
|
28
|
-
|
26
|
+
if (!this.#root)
|
27
|
+
return;
|
28
|
+
const { mode, onRelease, pseudoFocus, includeTrigger } = this.#options;
|
29
29
|
let navigationMode = "tabs";
|
30
30
|
if (mode === "action-menu" || mode === "selection-menu" || mode === "action-bar") {
|
31
31
|
navigationMode = "arrows";
|
@@ -39,12 +39,12 @@ class TrapFocus {
|
|
39
39
|
const isNextArrow = navigationMode === "arrows" && key === (mode === "action-bar" ? keys.RIGHT : keys.DOWN);
|
40
40
|
const isPrev = (isPrevTab && navigationMode === "tabs") || isPrevArrow;
|
41
41
|
const isNext = (isNextTab && navigationMode === "tabs") || isNextArrow;
|
42
|
-
const isFocusedOnTrigger = getActiveElement(this
|
42
|
+
const isFocusedOnTrigger = getActiveElement(this.#root) === this.#trigger;
|
43
43
|
const focusData = getFocusData({
|
44
|
-
root: this
|
44
|
+
root: this.#root,
|
45
45
|
target: isPrev ? "prev" : "next",
|
46
46
|
options: {
|
47
|
-
additionalElement: includeTrigger ? this
|
47
|
+
additionalElement: includeTrigger ? this.#trigger : undefined,
|
48
48
|
circular: mode !== "action-menu" && mode !== "action-bar",
|
49
49
|
},
|
50
50
|
});
|
@@ -70,53 +70,57 @@ class TrapFocus {
|
|
70
70
|
return;
|
71
71
|
focusElement(focusData.el, { pseudoFocus });
|
72
72
|
};
|
73
|
-
addListeners = () => {
|
74
|
-
const shadowRoot = getShadowRoot(this
|
73
|
+
#addListeners = () => {
|
74
|
+
const shadowRoot = getShadowRoot(this.#root);
|
75
75
|
const el = shadowRoot ?? document;
|
76
|
-
el.addEventListener("keydown", this
|
76
|
+
el.addEventListener("keydown", this.#handleKeyDown);
|
77
77
|
};
|
78
|
-
removeListeners = () => {
|
79
|
-
const shadowRoot = getShadowRoot(this
|
78
|
+
#removeListeners = () => {
|
79
|
+
const shadowRoot = getShadowRoot(this.#root);
|
80
80
|
const el = shadowRoot ?? document;
|
81
|
-
el.removeEventListener("keydown", this
|
81
|
+
el.removeEventListener("keydown", this.#handleKeyDown);
|
82
82
|
};
|
83
83
|
/**
|
84
84
|
* Trap the focus, add observer and keyboard event listeners
|
85
85
|
* and create a chain item
|
86
86
|
*/
|
87
|
-
trap = (options = {}) => {
|
87
|
+
trap = (root, options = {}) => {
|
88
88
|
const { mode = "dialog", includeTrigger, initialFocusEl } = options;
|
89
|
-
|
90
|
-
|
89
|
+
this.#root = root;
|
90
|
+
this.#screenReaderTrap = new TrapScreenReader(root);
|
91
|
+
const trigger = getActiveElement(this.#root);
|
92
|
+
const focusable = getFocusableElements(this.#root, {
|
91
93
|
additionalElement: includeTrigger ? trigger : undefined,
|
92
94
|
});
|
93
95
|
const pseudoFocus = mode === "selection-menu";
|
94
|
-
this
|
95
|
-
this
|
96
|
-
this
|
97
|
-
|
96
|
+
this.#options = { ...options, pseudoFocus };
|
97
|
+
this.#trigger = trigger;
|
98
|
+
this.#mutationObserver = new MutationObserver(() => {
|
99
|
+
if (!this.#root)
|
100
|
+
return;
|
101
|
+
const currentActiveElement = getActiveElement(this.#root);
|
98
102
|
// Focus stayed inside the wrapper, no need to refocus
|
99
|
-
if (this
|
103
|
+
if (this.#root.contains(currentActiveElement))
|
100
104
|
return;
|
101
|
-
const focusable = getFocusableElements(this
|
105
|
+
const focusable = getFocusableElements(this.#root, {
|
102
106
|
additionalElement: includeTrigger ? trigger : undefined,
|
103
107
|
});
|
104
108
|
if (!focusable.length)
|
105
109
|
return;
|
106
110
|
focusElement(focusable[0], { pseudoFocus });
|
107
111
|
});
|
108
|
-
this
|
109
|
-
this
|
112
|
+
this.#removeListeners();
|
113
|
+
this.#mutationObserver.observe(this.#root, { childList: true, subtree: true });
|
110
114
|
// Don't trap in case there is nothing to focus inside
|
111
115
|
if (!focusable.length && !initialFocusEl)
|
112
116
|
return;
|
113
|
-
this
|
117
|
+
this.#addListeners();
|
114
118
|
if (mode === "dialog")
|
115
|
-
this
|
119
|
+
this.#screenReaderTrap.trap();
|
116
120
|
// Don't add back to the chain if we're traversing back
|
117
|
-
const tailItem =
|
118
|
-
if (!tailItem || this
|
119
|
-
this
|
121
|
+
const tailItem = _a.chain.tailId && _a.chain.get(_a.chain.tailId);
|
122
|
+
if (!tailItem || this.#root !== tailItem.data.#root) {
|
123
|
+
this.#chainId = _a.chain.add(this);
|
120
124
|
focusElement(initialFocusEl || focusable[0], { pseudoFocus });
|
121
125
|
}
|
122
126
|
this.trapped = true;
|
@@ -127,21 +131,22 @@ class TrapFocus {
|
|
127
131
|
*/
|
128
132
|
release = (releaseOptions = {}) => {
|
129
133
|
const { withoutFocusReturn } = releaseOptions;
|
130
|
-
if (!this.trapped || !this
|
134
|
+
if (!this.trapped || !this.#chainId || !this.#root)
|
131
135
|
return;
|
132
136
|
this.trapped = false;
|
133
|
-
if (this
|
134
|
-
this
|
137
|
+
if (this.#trigger && !withoutFocusReturn) {
|
138
|
+
this.#trigger.focus({ preventScroll: !checkKeyboardMode() });
|
135
139
|
}
|
136
|
-
|
137
|
-
this
|
138
|
-
this
|
139
|
-
this
|
140
|
-
const previousItem =
|
141
|
-
if (previousItem) {
|
142
|
-
const trapInstance = new
|
143
|
-
trapInstance.trap(previousItem.data.options);
|
140
|
+
_a.chain.removePreviousTill(this.#chainId, (item) => document.body.contains(item.data.#trigger));
|
141
|
+
this.#mutationObserver?.disconnect();
|
142
|
+
this.#removeListeners();
|
143
|
+
this.#screenReaderTrap?.release();
|
144
|
+
const previousItem = _a.chain.tailId && _a.chain.get(_a.chain.tailId);
|
145
|
+
if (previousItem && previousItem.data.#root) {
|
146
|
+
const trapInstance = new _a();
|
147
|
+
trapInstance.trap(previousItem.data.#root, previousItem.data.#options);
|
144
148
|
}
|
145
149
|
};
|
146
150
|
}
|
151
|
+
_a = TrapFocus;
|
147
152
|
export default TrapFocus;
|
@@ -34,8 +34,8 @@ export const modeDialog = {
|
|
34
34
|
return;
|
35
35
|
if (!rootRef.current)
|
36
36
|
return;
|
37
|
-
const trapFocus = new TrapFocus(
|
38
|
-
trapFocus.trap({ mode: "dialog" });
|
37
|
+
const trapFocus = new TrapFocus();
|
38
|
+
trapFocus.trap(rootRef.current, { mode: "dialog" });
|
39
39
|
return () => trapFocus.release();
|
40
40
|
}, [trapToggle.active]);
|
41
41
|
return (<Example>
|
@@ -92,8 +92,8 @@ export const modeActionMenu = {
|
|
92
92
|
return;
|
93
93
|
if (!rootRef.current)
|
94
94
|
return;
|
95
|
-
const trapFocus = new TrapFocus(
|
96
|
-
trapFocus.trap({
|
95
|
+
const trapFocus = new TrapFocus();
|
96
|
+
trapFocus.trap(rootRef.current, {
|
97
97
|
mode: "action-menu",
|
98
98
|
onRelease: () => {
|
99
99
|
trapToggle.deactivate();
|
@@ -154,8 +154,8 @@ export const modeActionBar = {
|
|
154
154
|
return;
|
155
155
|
if (!rootRef.current)
|
156
156
|
return;
|
157
|
-
const trapFocus = new TrapFocus(
|
158
|
-
trapFocus.trap({
|
157
|
+
const trapFocus = new TrapFocus();
|
158
|
+
trapFocus.trap(rootRef.current, {
|
159
159
|
mode: "action-bar",
|
160
160
|
onRelease: () => {
|
161
161
|
trapToggle.deactivate();
|
@@ -216,8 +216,8 @@ export const modeContentMenu = {
|
|
216
216
|
return;
|
217
217
|
if (!rootRef.current)
|
218
218
|
return;
|
219
|
-
const trapFocus = new TrapFocus(
|
220
|
-
trapFocus.trap({
|
219
|
+
const trapFocus = new TrapFocus();
|
220
|
+
trapFocus.trap(rootRef.current, {
|
221
221
|
mode: "content-menu",
|
222
222
|
onRelease: () => {
|
223
223
|
trapToggle.deactivate();
|
@@ -279,8 +279,8 @@ export const includeTrigger = {
|
|
279
279
|
return;
|
280
280
|
if (!rootRef.current)
|
281
281
|
return;
|
282
|
-
const trapFocus = new TrapFocus(
|
283
|
-
trapFocus.trap({
|
282
|
+
const trapFocus = new TrapFocus();
|
283
|
+
trapFocus.trap(rootRef.current, {
|
284
284
|
mode: "dialog",
|
285
285
|
includeTrigger: true,
|
286
286
|
});
|
@@ -327,8 +327,8 @@ export const initialFocusEl = {
|
|
327
327
|
return;
|
328
328
|
if (!rootRef.current)
|
329
329
|
return;
|
330
|
-
const trapFocus = new TrapFocus(
|
331
|
-
trapFocus.trap({
|
330
|
+
const trapFocus = new TrapFocus();
|
331
|
+
trapFocus.trap(rootRef.current, {
|
332
332
|
mode: "dialog",
|
333
333
|
initialFocusEl: initialFocusRef.current,
|
334
334
|
});
|
@@ -388,8 +388,8 @@ export const focusableElements = {
|
|
388
388
|
return;
|
389
389
|
if (!rootRef.current)
|
390
390
|
return;
|
391
|
-
const trapFocus = new TrapFocus(
|
392
|
-
trapFocus.trap();
|
391
|
+
const trapFocus = new TrapFocus();
|
392
|
+
trapFocus.trap(rootRef.current);
|
393
393
|
return () => trapFocus.release();
|
394
394
|
}, [trapToggle.active]);
|
395
395
|
return (<Example>
|
@@ -498,8 +498,8 @@ export const nested = {
|
|
498
498
|
return;
|
499
499
|
if (!rootRef.current)
|
500
500
|
return;
|
501
|
-
const trapFocus = new TrapFocus(
|
502
|
-
trapFocus.trap({
|
501
|
+
const trapFocus = new TrapFocus();
|
502
|
+
trapFocus.trap(rootRef.current, {
|
503
503
|
mode: "dialog",
|
504
504
|
});
|
505
505
|
return () => trapFocus.release();
|
@@ -509,8 +509,8 @@ export const nested = {
|
|
509
509
|
return;
|
510
510
|
if (!innerRootRef.current)
|
511
511
|
return;
|
512
|
-
const trapFocus = new TrapFocus(
|
513
|
-
trapFocus.trap({
|
512
|
+
const trapFocus = new TrapFocus();
|
513
|
+
trapFocus.trap(innerRootRef.current, {
|
514
514
|
mode: "dialog",
|
515
515
|
});
|
516
516
|
return () => trapFocus.release();
|
@@ -567,8 +567,8 @@ export const mutationObserver = {
|
|
567
567
|
return;
|
568
568
|
if (!rootRef.current)
|
569
569
|
return;
|
570
|
-
const trapFocus = new TrapFocus(
|
571
|
-
trapFocus.trap({
|
570
|
+
const trapFocus = new TrapFocus();
|
571
|
+
trapFocus.trap(rootRef.current, {
|
572
572
|
mode: "dialog",
|
573
573
|
});
|
574
574
|
setTimeout(() => {
|
@@ -1,23 +1,31 @@
|
|
1
1
|
import { isIOS } from "../platform.js";
|
2
|
+
import { findClosestRenderContainer } from "../dom/index.js";
|
2
3
|
import lockSafariScroll from "./lockSafari.js";
|
3
4
|
import lockStandardScroll from "./lockStandard.js";
|
4
|
-
let
|
5
|
+
let bodyLockedCount = 0;
|
5
6
|
let reset = () => { };
|
6
7
|
export const lockScroll = (args) => {
|
7
|
-
|
8
|
-
|
8
|
+
const isIOSLock = isIOS() && !args.containerEl && !args.originEl;
|
9
|
+
let container = document.body;
|
10
|
+
if (args.originEl && !isIOSLock)
|
11
|
+
container = findClosestRenderContainer({ el: args.originEl }).el;
|
12
|
+
if (args.containerEl && !isIOSLock)
|
13
|
+
container = args.containerEl;
|
14
|
+
if (container === document.body)
|
15
|
+
bodyLockedCount += 1;
|
16
|
+
if (bodyLockedCount > 1)
|
9
17
|
return;
|
10
|
-
if (
|
18
|
+
if (isIOSLock) {
|
11
19
|
reset = lockSafariScroll();
|
12
20
|
}
|
13
21
|
else {
|
14
|
-
reset = lockStandardScroll({
|
22
|
+
reset = lockStandardScroll({ container });
|
15
23
|
}
|
16
24
|
args.cb?.();
|
17
25
|
};
|
18
26
|
export const unlockScroll = (cb) => {
|
19
|
-
|
20
|
-
if (
|
27
|
+
bodyLockedCount -= 1;
|
28
|
+
if (bodyLockedCount > 0)
|
21
29
|
return;
|
22
30
|
reset();
|
23
31
|
cb?.();
|
@@ -1,13 +1,8 @@
|
|
1
|
-
import { findClosestRenderContainer } from "../dom/index.js";
|
2
1
|
import { getScrollbarWidth } from "./helpers.js";
|
3
2
|
import { StyleCache } from "../css.js";
|
4
3
|
const styleCache = new StyleCache();
|
5
4
|
const lockStandardScroll = (args) => {
|
6
|
-
|
7
|
-
if (args.originEl)
|
8
|
-
container = findClosestRenderContainer({ el: args.originEl }).el;
|
9
|
-
if (args.containerEl)
|
10
|
-
container = args.containerEl;
|
5
|
+
const { container } = args;
|
11
6
|
const rect = container.getBoundingClientRect();
|
12
7
|
const isOverflowing = rect.left + rect.right < window.innerWidth;
|
13
8
|
styleCache.set(container, { overflow: "hidden" });
|
@@ -15,8 +10,6 @@ const lockStandardScroll = (args) => {
|
|
15
10
|
const scrollBarWidth = getScrollbarWidth();
|
16
11
|
styleCache.set(container, { paddingRight: `${scrollBarWidth}px` });
|
17
12
|
}
|
18
|
-
return () =>
|
19
|
-
styleCache.reset();
|
20
|
-
};
|
13
|
+
return () => styleCache.reset();
|
21
14
|
};
|
22
15
|
export default lockStandardScroll;
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "reshaped",
|
3
3
|
"description": "Professionally crafted design system in React & Figma for building products of any scale and complexity",
|
4
|
-
"version": "3.
|
4
|
+
"version": "3.5.0-rc.0",
|
5
5
|
"license": "MIT",
|
6
6
|
"email": "hello@reshaped.so",
|
7
7
|
"homepage": "https://reshaped.so",
|
@@ -98,35 +98,35 @@
|
|
98
98
|
"@commitlint/cli": "19.8.0",
|
99
99
|
"@commitlint/config-conventional": "19.8.0",
|
100
100
|
"@commitlint/types": "19.8.0",
|
101
|
-
"@eslint/js": "9.
|
101
|
+
"@eslint/js": "9.25.0",
|
102
102
|
"@size-limit/preset-big-lib": "11.2.0",
|
103
|
-
"@storybook/addon-a11y": "8.6.
|
104
|
-
"@storybook/addon-actions": "8.6.
|
105
|
-
"@storybook/addon-controls": "8.6.
|
106
|
-
"@storybook/addon-docs": "8.6.
|
107
|
-
"@storybook/addon-storysource": "8.6.
|
108
|
-
"@storybook/experimental-addon-test": "8.6.
|
109
|
-
"@storybook/react": "8.6.
|
110
|
-
"@storybook/react-vite": "8.6.
|
103
|
+
"@storybook/addon-a11y": "8.6.12",
|
104
|
+
"@storybook/addon-actions": "8.6.12",
|
105
|
+
"@storybook/addon-controls": "8.6.12",
|
106
|
+
"@storybook/addon-docs": "8.6.12",
|
107
|
+
"@storybook/addon-storysource": "8.6.12",
|
108
|
+
"@storybook/experimental-addon-test": "8.6.12",
|
109
|
+
"@storybook/react": "8.6.12",
|
110
|
+
"@storybook/react-vite": "8.6.12",
|
111
111
|
"@types/culori": "2.1.1",
|
112
112
|
"@types/events": "3.0.3",
|
113
|
-
"@types/node": "22.
|
114
|
-
"@types/react": "19.
|
115
|
-
"@types/react-dom": "19.
|
116
|
-
"@vitejs/plugin-react": "4.
|
117
|
-
"@vitest/browser": "3.
|
118
|
-
"@vitest/coverage-istanbul": "3.
|
119
|
-
"@vitest/coverage-v8": "3.
|
120
|
-
"chromatic": "11.
|
113
|
+
"@types/node": "22.14.1",
|
114
|
+
"@types/react": "19.1.2",
|
115
|
+
"@types/react-dom": "19.1.2",
|
116
|
+
"@vitejs/plugin-react": "4.4.0",
|
117
|
+
"@vitest/browser": "3.1.1",
|
118
|
+
"@vitest/coverage-istanbul": "3.1.1",
|
119
|
+
"@vitest/coverage-v8": "3.1.1",
|
120
|
+
"chromatic": "11.28.2",
|
121
121
|
"cz-conventional-changelog": "3.3.0",
|
122
|
-
"eslint": "9.
|
123
|
-
"eslint-config-prettier": "10.1.
|
122
|
+
"eslint": "9.25.0",
|
123
|
+
"eslint-config-prettier": "10.1.2",
|
124
124
|
"eslint-plugin-jsx-a11y": "6.10.2",
|
125
|
-
"eslint-plugin-prettier": "5.2.
|
126
|
-
"eslint-plugin-react": "7.37.
|
125
|
+
"eslint-plugin-prettier": "5.2.6",
|
126
|
+
"eslint-plugin-react": "7.37.5",
|
127
127
|
"eslint-plugin-react-hooks": "5.2.0",
|
128
|
-
"lefthook": "1.11.
|
129
|
-
"playwright": "1.
|
128
|
+
"lefthook": "1.11.10",
|
129
|
+
"playwright": "1.52.0",
|
130
130
|
"postcss": "8.5.3",
|
131
131
|
"postcss-cli": "11.0.1",
|
132
132
|
"postcss-each": "1.1.0",
|
@@ -137,16 +137,16 @@
|
|
137
137
|
"react-shadow": "20.6.0",
|
138
138
|
"resolve-tspaths": "0.8.23",
|
139
139
|
"size-limit": "11.2.0",
|
140
|
-
"storybook": "8.6.
|
141
|
-
"stylelint": "16.
|
140
|
+
"storybook": "8.6.12",
|
141
|
+
"stylelint": "16.18.0",
|
142
142
|
"stylelint-config-prettier": "9.0.5",
|
143
|
-
"stylelint-config-standard": "
|
143
|
+
"stylelint-config-standard": "38.0.0",
|
144
144
|
"ts-node": "10.9.2",
|
145
|
-
"typescript": "5.8.
|
146
|
-
"typescript-eslint": "8.
|
147
|
-
"vite": "6.2
|
145
|
+
"typescript": "5.8.3",
|
146
|
+
"typescript-eslint": "8.30.1",
|
147
|
+
"vite": "6.3.2",
|
148
148
|
"vite-tsconfig-paths": "5.1.4",
|
149
|
-
"vitest": "3.
|
149
|
+
"vitest": "3.1.1",
|
150
150
|
"vitest-browser-react": "0.1.1"
|
151
151
|
},
|
152
152
|
"peerDependencies": {
|
@@ -1,27 +0,0 @@
|
|
1
|
-
import React from "react";
|
2
|
-
import { StoryObj } from "@storybook/react";
|
3
|
-
import { fn } from "@storybook/test";
|
4
|
-
declare const _default: {
|
5
|
-
title: string;
|
6
|
-
component: {
|
7
|
-
(props: import("./..").AutocompleteProps): React.JSX.Element;
|
8
|
-
Item: {
|
9
|
-
(props: import("../Autocomplete.types").ItemProps): React.JSX.Element;
|
10
|
-
displayName: string;
|
11
|
-
};
|
12
|
-
displayName: string;
|
13
|
-
};
|
14
|
-
parameters: {
|
15
|
-
iframe: {
|
16
|
-
url: string;
|
17
|
-
};
|
18
|
-
chromatic: {
|
19
|
-
disableSnapshot: boolean;
|
20
|
-
};
|
21
|
-
};
|
22
|
-
};
|
23
|
-
export default _default;
|
24
|
-
export declare const base: StoryObj<{
|
25
|
-
handleItemSelect: ReturnType<typeof fn>;
|
26
|
-
handleBackspace: ReturnType<typeof fn>;
|
27
|
-
}>;
|
@@ -1,86 +0,0 @@
|
|
1
|
-
import React from "react";
|
2
|
-
import { expect, waitFor, within, fn } from "@storybook/test";
|
3
|
-
import { Example } from "../../../utilities/storybook/index.js";
|
4
|
-
import Autocomplete from "../index.js";
|
5
|
-
import FormControl from "../../FormControl/index.js";
|
6
|
-
import { sleep } from "../../../utilities/helpers.js";
|
7
|
-
import userEvent from "@testing-library/user-event";
|
8
|
-
export default {
|
9
|
-
title: "Components/Autocomplete/tests",
|
10
|
-
component: Autocomplete,
|
11
|
-
parameters: {
|
12
|
-
iframe: {
|
13
|
-
url: "https://reshaped.so/docs/components/autocomplete",
|
14
|
-
},
|
15
|
-
chromatic: { disableSnapshot: true },
|
16
|
-
},
|
17
|
-
};
|
18
|
-
export const base = {
|
19
|
-
name: "base",
|
20
|
-
args: {
|
21
|
-
handleBackspace: fn(),
|
22
|
-
handleItemSelect: fn(),
|
23
|
-
},
|
24
|
-
render: (args) => {
|
25
|
-
const [value, setValue] = React.useState("");
|
26
|
-
return (<Example>
|
27
|
-
<Example.Item title="Base">
|
28
|
-
<FormControl>
|
29
|
-
<FormControl.Label>Food</FormControl.Label>
|
30
|
-
<Autocomplete name="fruit" placeholder="Pick your food" value={value} onChange={(args) => setValue(args.value)} onBackspace={args.handleBackspace} onItemSelect={args.handleItemSelect}>
|
31
|
-
{["Pizza", "Pie", "Ice-cream"].map((v, i) => {
|
32
|
-
return (<Autocomplete.Item key={v} value={v} data={i === 0 ? { foo: "bar" } : undefined}>
|
33
|
-
{v}
|
34
|
-
</Autocomplete.Item>);
|
35
|
-
})}
|
36
|
-
</Autocomplete>
|
37
|
-
</FormControl>
|
38
|
-
</Example.Item>
|
39
|
-
</Example>);
|
40
|
-
},
|
41
|
-
play: async ({ canvasElement, args }) => {
|
42
|
-
const canvas = within(canvasElement.ownerDocument.body);
|
43
|
-
const input = canvas.getByRole("combobox");
|
44
|
-
// Reset the focus
|
45
|
-
document.body.focus();
|
46
|
-
// Test keyboard selection after focusing the input
|
47
|
-
input.focus();
|
48
|
-
let options = [];
|
49
|
-
await waitFor(() => {
|
50
|
-
options = canvas.getAllByRole("option");
|
51
|
-
});
|
52
|
-
expect(options).toHaveLength(3);
|
53
|
-
await waitFor(() => {
|
54
|
-
expect(options[0]).toHaveAttribute("data-rs-focus");
|
55
|
-
});
|
56
|
-
expect(options[1]).not.toHaveAttribute("data-rs-focus");
|
57
|
-
await userEvent.keyboard("{ArrowDown/}");
|
58
|
-
await userEvent.keyboard("{Enter/}");
|
59
|
-
expect(input).toHaveValue("Pie");
|
60
|
-
expect(args.handleItemSelect).toHaveBeenCalledTimes(1);
|
61
|
-
expect(args.handleItemSelect).toHaveBeenCalledWith({
|
62
|
-
value: "Pie",
|
63
|
-
data: undefined,
|
64
|
-
});
|
65
|
-
// Give browser time to focus on the input
|
66
|
-
await sleep(100);
|
67
|
-
// Test click selection after opening with down arrow
|
68
|
-
await userEvent.keyboard("{ArrowDown/}");
|
69
|
-
await waitFor(() => {
|
70
|
-
options = canvas.getAllByRole("option");
|
71
|
-
});
|
72
|
-
await userEvent.click(options[0]);
|
73
|
-
expect(input).toHaveValue("Pizza");
|
74
|
-
expect(args.handleItemSelect).toHaveBeenCalledTimes(2);
|
75
|
-
expect(args.handleItemSelect).toHaveBeenCalledWith({
|
76
|
-
value: "Pizza",
|
77
|
-
data: { foo: "bar" },
|
78
|
-
});
|
79
|
-
input.focus();
|
80
|
-
await userEvent.keyboard("{Backspace/}");
|
81
|
-
await waitFor(() => {
|
82
|
-
expect(args.handleBackspace).toHaveBeenCalledTimes(1);
|
83
|
-
expect(args.handleBackspace).toHaveBeenCalledWith();
|
84
|
-
});
|
85
|
-
},
|
86
|
-
};
|