@reshaped/headless 3.10.0-canary.10
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/LICENSE.md +21 -0
- package/dist/components/Actionable/Actionable.d.ts +4 -0
- package/dist/components/Actionable/Actionable.js +72 -0
- package/dist/components/Actionable/Actionable.types.d.ts +35 -0
- package/dist/components/Actionable/Actionable.types.js +1 -0
- package/dist/components/Actionable/index.d.ts +2 -0
- package/dist/components/Actionable/index.js +1 -0
- package/dist/components/Reshaped/Reshaped.d.ts +4 -0
- package/dist/components/Reshaped/Reshaped.js +11 -0
- package/dist/components/Reshaped/Reshaped.types.d.ts +5 -0
- package/dist/components/Reshaped/Reshaped.types.js +1 -0
- package/dist/components/Reshaped/index.d.ts +2 -0
- package/dist/components/Reshaped/index.js +1 -0
- package/dist/hooks/_internal/useSingletonHotkeys.d.ts +31 -0
- package/dist/hooks/_internal/useSingletonHotkeys.js +191 -0
- package/dist/hooks/_internal/useSingletonKeyboardMode.d.ts +13 -0
- package/dist/hooks/_internal/useSingletonKeyboardMode.js +59 -0
- package/dist/hooks/_internal/useSingletonRTL.d.ts +6 -0
- package/dist/hooks/_internal/useSingletonRTL.js +40 -0
- package/dist/hooks/tests/useHandlerRef.stories.d.ts +14 -0
- package/dist/hooks/tests/useHandlerRef.stories.js +40 -0
- package/dist/hooks/tests/useHotkeys.stories.d.ts +43 -0
- package/dist/hooks/tests/useHotkeys.stories.js +165 -0
- package/dist/hooks/tests/useKeyboardArrowNavigation.stories.d.ts +15 -0
- package/dist/hooks/tests/useKeyboardArrowNavigation.stories.js +107 -0
- package/dist/hooks/tests/useKeyboardMode.stories.d.ts +11 -0
- package/dist/hooks/tests/useKeyboardMode.stories.js +36 -0
- package/dist/hooks/tests/useOnClickOutside.stories.d.ts +23 -0
- package/dist/hooks/tests/useOnClickOutside.stories.js +98 -0
- package/dist/hooks/tests/useRTL.stories.d.ts +11 -0
- package/dist/hooks/tests/useRTL.stories.js +24 -0
- package/dist/hooks/tests/useScrollLock.stories.d.ts +14 -0
- package/dist/hooks/tests/useScrollLock.stories.js +75 -0
- package/dist/hooks/tests/useToggle.stories.d.ts +13 -0
- package/dist/hooks/tests/useToggle.stories.js +50 -0
- package/dist/hooks/useHandlerRef.d.ts +8 -0
- package/dist/hooks/useHandlerRef.js +16 -0
- package/dist/hooks/useHotkeys.d.ts +11 -0
- package/dist/hooks/useHotkeys.js +27 -0
- package/dist/hooks/useIsomorphicLayoutEffect.d.ts +3 -0
- package/dist/hooks/useIsomorphicLayoutEffect.js +4 -0
- package/dist/hooks/useKeyboardArrowNavigation.d.ts +9 -0
- package/dist/hooks/useKeyboardArrowNavigation.js +62 -0
- package/dist/hooks/useKeyboardMode.d.ts +2 -0
- package/dist/hooks/useKeyboardMode.js +2 -0
- package/dist/hooks/useOnClickOutside.d.ts +5 -0
- package/dist/hooks/useOnClickOutside.js +63 -0
- package/dist/hooks/useRTL.d.ts +2 -0
- package/dist/hooks/useRTL.js +2 -0
- package/dist/hooks/useScrollLock.d.ts +10 -0
- package/dist/hooks/useScrollLock.js +25 -0
- package/dist/hooks/useToggle.d.ts +7 -0
- package/dist/hooks/useToggle.js +19 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +15 -0
- package/dist/internal.d.ts +8 -0
- package/dist/internal.js +8 -0
- package/dist/types/global.d.ts +7 -0
- package/dist/types/global.js +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { expect, fn, userEvent } from "storybook/test";
|
|
3
|
+
import useHotkeys from "../useHotkeys.js";
|
|
4
|
+
export default {
|
|
5
|
+
title: "Headless/Hooks/useHotkeys",
|
|
6
|
+
parameters: {
|
|
7
|
+
chromatic: { disableSnapshot: true },
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
export const base = {
|
|
11
|
+
name: "base",
|
|
12
|
+
render: () => {
|
|
13
|
+
const { checkHotkeyState } = useHotkeys({
|
|
14
|
+
"shift + b + n": () => console.log("pressed"),
|
|
15
|
+
"c + v": () => console.log("c + v"),
|
|
16
|
+
"Meta + k": () => console.log("meta + k"),
|
|
17
|
+
"Meta + f": () => console.log("meta + f"),
|
|
18
|
+
"Meta + v": () => console.log("meta + v"),
|
|
19
|
+
"Meta + b": () => console.log("meta + b"),
|
|
20
|
+
"control + enter": () => console.log("control + enter"),
|
|
21
|
+
"meta + enter": () => console.log("meta + enter"),
|
|
22
|
+
"mod + enter": () => console.log("mod + enter"),
|
|
23
|
+
"mod + ArrowRight": () => console.log("right"),
|
|
24
|
+
"mod + ArrowUp": () => console.log("top"),
|
|
25
|
+
"shift + ArrowRight": () => console.log("right"),
|
|
26
|
+
"shift + ArrowUp": () => console.log("top"),
|
|
27
|
+
"alt+shift+n": () => console.log("alt+shift+n"),
|
|
28
|
+
"shift+alt+n": () => console.log("shift+alt+n"),
|
|
29
|
+
"alt+shiftLeft+n": () => console.log("alt+shiftLeft+n"),
|
|
30
|
+
});
|
|
31
|
+
const active = checkHotkeyState("shift + b + n");
|
|
32
|
+
const shiftActive = checkHotkeyState("shift");
|
|
33
|
+
const bActive = checkHotkeyState("b");
|
|
34
|
+
const nActive = checkHotkeyState("n");
|
|
35
|
+
return (_jsxs("div", { style: {
|
|
36
|
+
display: "flex",
|
|
37
|
+
gap: 8,
|
|
38
|
+
padding: 8,
|
|
39
|
+
background: active ? "violet" : "transparent",
|
|
40
|
+
}, children: [_jsx("div", { style: {
|
|
41
|
+
padding: 4,
|
|
42
|
+
borderRadius: 4,
|
|
43
|
+
backgroundColor: shiftActive ? "tomato" : "transparent",
|
|
44
|
+
}, children: "Shift" }), _jsx("div", { style: {
|
|
45
|
+
padding: 4,
|
|
46
|
+
borderRadius: 4,
|
|
47
|
+
backgroundColor: bActive ? "tomato" : "transparent",
|
|
48
|
+
}, children: "b" }), _jsx("div", { style: {
|
|
49
|
+
padding: 4,
|
|
50
|
+
borderRadius: 4,
|
|
51
|
+
backgroundColor: nActive ? "tomato" : "transparent",
|
|
52
|
+
}, children: "n" })] }));
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const Component = (props) => {
|
|
56
|
+
const { hotkeys } = props;
|
|
57
|
+
useHotkeys(hotkeys);
|
|
58
|
+
return _jsx("div", {});
|
|
59
|
+
};
|
|
60
|
+
export const singleKey = {
|
|
61
|
+
name: "single key",
|
|
62
|
+
args: {
|
|
63
|
+
handleHotkey: fn(),
|
|
64
|
+
},
|
|
65
|
+
render: (args) => _jsx(Component, { hotkeys: { a: args.handleHotkey } }),
|
|
66
|
+
play: async ({ args }) => {
|
|
67
|
+
await userEvent.keyboard("a");
|
|
68
|
+
await userEvent.keyboard("b");
|
|
69
|
+
expect(args.handleHotkey).toHaveBeenCalledTimes(1);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
export const modKey = {
|
|
73
|
+
name: "mod key",
|
|
74
|
+
args: {
|
|
75
|
+
handleHotkey: fn(),
|
|
76
|
+
},
|
|
77
|
+
render: (args) => _jsx(Component, { hotkeys: { mod: args.handleHotkey } }),
|
|
78
|
+
play: async ({ args }) => {
|
|
79
|
+
await userEvent.keyboard("{Meta/}");
|
|
80
|
+
expect(args.handleHotkey).toHaveBeenCalledTimes(1);
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
export const modKeyHold = {
|
|
84
|
+
name: "mod key on hold",
|
|
85
|
+
args: {
|
|
86
|
+
handleHotkey: fn(),
|
|
87
|
+
},
|
|
88
|
+
render: (args) => _jsx(Component, { hotkeys: { "Meta + b": args.handleHotkey } }),
|
|
89
|
+
play: async ({ args }) => {
|
|
90
|
+
await userEvent.keyboard("{Meta>}bb{/Meta}");
|
|
91
|
+
expect(args.handleHotkey).toHaveBeenCalledTimes(2);
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
export const keyList = {
|
|
95
|
+
name: "key list",
|
|
96
|
+
args: {
|
|
97
|
+
handleHotkey: fn(),
|
|
98
|
+
},
|
|
99
|
+
render: (args) => _jsx(Component, { hotkeys: { "a,b": args.handleHotkey } }),
|
|
100
|
+
play: async ({ args }) => {
|
|
101
|
+
await userEvent.keyboard("a");
|
|
102
|
+
expect(args.handleHotkey).toHaveBeenCalledTimes(1);
|
|
103
|
+
await userEvent.keyboard("b");
|
|
104
|
+
expect(args.handleHotkey).toHaveBeenCalledTimes(2);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
export const keyCombination = {
|
|
108
|
+
name: "key combination",
|
|
109
|
+
args: {
|
|
110
|
+
handleHotkey: fn(),
|
|
111
|
+
},
|
|
112
|
+
render: (args) => _jsx(Component, { hotkeys: { "a+b": args.handleHotkey } }),
|
|
113
|
+
play: async ({ args }) => {
|
|
114
|
+
await userEvent.keyboard("{a>}b{/a}");
|
|
115
|
+
expect(args.handleHotkey).toHaveBeenCalledTimes(1);
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
export const keyCombinationFormat = {
|
|
119
|
+
name: "key combination without formatting",
|
|
120
|
+
args: {
|
|
121
|
+
handleHotkey: fn(),
|
|
122
|
+
},
|
|
123
|
+
render: (args) => _jsx(Component, { hotkeys: { "A + b": args.handleHotkey } }),
|
|
124
|
+
play: async ({ args }) => {
|
|
125
|
+
await userEvent.keyboard("{a>}b{/a}");
|
|
126
|
+
expect(args.handleHotkey).toHaveBeenCalledTimes(1);
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
export const keyCombinationOrder = {
|
|
130
|
+
name: "key combination without order",
|
|
131
|
+
args: {
|
|
132
|
+
handleHotkey: fn(),
|
|
133
|
+
},
|
|
134
|
+
render: (args) => _jsx(Component, { hotkeys: { "b+a": args.handleHotkey } }),
|
|
135
|
+
play: async ({ args }) => {
|
|
136
|
+
await userEvent.keyboard("{a>}b{/a}");
|
|
137
|
+
expect(args.handleHotkey).toHaveBeenCalledTimes(1);
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
export const keyCombinationMoreThanRequired = {
|
|
141
|
+
name: "key combination, more keys pressed",
|
|
142
|
+
args: {
|
|
143
|
+
handleHotkey: fn(),
|
|
144
|
+
},
|
|
145
|
+
render: (args) => _jsx(Component, { hotkeys: { "z + x": args.handleHotkey } }),
|
|
146
|
+
play: async ({ args }) => {
|
|
147
|
+
await userEvent.keyboard("{z>}{x>}c{/x}{/z}");
|
|
148
|
+
// When c is pressed, it doesn't trigger a+b for the second time
|
|
149
|
+
expect(args.handleHotkey).toHaveBeenCalledTimes(1);
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
export const optionModified = {
|
|
153
|
+
name: "modified with alt/option",
|
|
154
|
+
args: {
|
|
155
|
+
handleHotkey: fn(),
|
|
156
|
+
handleHotkeyModified: fn(),
|
|
157
|
+
},
|
|
158
|
+
render: (args) => (_jsx(Component, { hotkeys: { "alt+n": args.handleHotkeyModified, "alt+shift": args.handleHotkey } })),
|
|
159
|
+
play: async ({ args }) => {
|
|
160
|
+
await userEvent.keyboard("{Alt>}n{/Alt}");
|
|
161
|
+
expect(args.handleHotkeyModified).toHaveBeenCalledTimes(1);
|
|
162
|
+
await userEvent.keyboard("{Alt>}{Shift}{/Alt}");
|
|
163
|
+
expect(args.handleHotkey).toHaveBeenCalledTimes(1);
|
|
164
|
+
},
|
|
165
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { StoryObj } from "@storybook/react-vite";
|
|
2
|
+
declare const _default: {
|
|
3
|
+
title: string;
|
|
4
|
+
parameters: {
|
|
5
|
+
chromatic: {
|
|
6
|
+
disableSnapshot: boolean;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
export default _default;
|
|
11
|
+
export declare const base: StoryObj;
|
|
12
|
+
export declare const horizontal: StoryObj;
|
|
13
|
+
export declare const vertical: StoryObj;
|
|
14
|
+
export declare const circular: StoryObj;
|
|
15
|
+
export declare const disabled: StoryObj;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef } from "react";
|
|
3
|
+
import { expect, userEvent } from "storybook/test";
|
|
4
|
+
import useKeyboardArrowNavigation from "../useKeyboardArrowNavigation.js";
|
|
5
|
+
export default {
|
|
6
|
+
title: "Headless/Hooks/useKeyboardArrowNavigation",
|
|
7
|
+
parameters: {
|
|
8
|
+
chromatic: { disableSnapshot: true },
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
export const base = {
|
|
12
|
+
name: "base",
|
|
13
|
+
render: () => {
|
|
14
|
+
const ref = useRef(null);
|
|
15
|
+
useKeyboardArrowNavigation({ ref });
|
|
16
|
+
return (_jsxs("div", { style: { display: "flex", gap: 8, flexDirection: "row" }, ref: ref, children: [_jsx("button", { onClick: () => { }, children: "Action 1" }), _jsx("button", { onClick: () => { }, children: "Action 2" }), _jsx("button", { onClick: () => { }, children: "Action 3" })] }));
|
|
17
|
+
},
|
|
18
|
+
play: async ({ canvas }) => {
|
|
19
|
+
const buttons = canvas.getAllByRole("button");
|
|
20
|
+
buttons[0].focus();
|
|
21
|
+
await userEvent.keyboard("{ArrowRight/}");
|
|
22
|
+
expect(document.activeElement).toBe(buttons[1]);
|
|
23
|
+
await userEvent.keyboard("{ArrowDown/}");
|
|
24
|
+
expect(document.activeElement).toBe(buttons[2]);
|
|
25
|
+
await userEvent.keyboard("{ArrowUp/}");
|
|
26
|
+
expect(document.activeElement).toBe(buttons[1]);
|
|
27
|
+
await userEvent.keyboard("{ArrowLeft/}");
|
|
28
|
+
expect(document.activeElement).toBe(buttons[0]);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export const horizontal = {
|
|
32
|
+
name: "orientation: horizontal",
|
|
33
|
+
render: () => {
|
|
34
|
+
const ref = useRef(null);
|
|
35
|
+
useKeyboardArrowNavigation({ ref, orientation: "horizontal" });
|
|
36
|
+
return (_jsxs("div", { style: { display: "flex", gap: 8, flexDirection: "row" }, ref: ref, children: [_jsx("button", { onClick: () => { }, children: "Action 1" }), _jsx("button", { onClick: () => { }, children: "Action 2" }), _jsx("button", { onClick: () => { }, children: "Action 3" })] }));
|
|
37
|
+
},
|
|
38
|
+
play: async ({ canvas }) => {
|
|
39
|
+
const buttons = canvas.getAllByRole("button");
|
|
40
|
+
expect(buttons[0]).toHaveAttribute("tabindex", "0");
|
|
41
|
+
expect(buttons[1]).toHaveAttribute("tabindex", "-1");
|
|
42
|
+
expect(buttons[2]).toHaveAttribute("tabindex", "-1");
|
|
43
|
+
buttons[0].focus();
|
|
44
|
+
await userEvent.keyboard("{ArrowRight/}");
|
|
45
|
+
expect(document.activeElement).toBe(buttons[1]);
|
|
46
|
+
await userEvent.keyboard("{ArrowLeft/}");
|
|
47
|
+
expect(document.activeElement).toBe(buttons[0]);
|
|
48
|
+
await userEvent.keyboard("{ArrowDown/}");
|
|
49
|
+
expect(document.activeElement).toBe(buttons[0]);
|
|
50
|
+
await userEvent.keyboard("{ArrowUp/}");
|
|
51
|
+
expect(document.activeElement).toBe(buttons[0]);
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
export const vertical = {
|
|
55
|
+
name: "orientation: vertical",
|
|
56
|
+
render: () => {
|
|
57
|
+
const ref = useRef(null);
|
|
58
|
+
useKeyboardArrowNavigation({ ref, orientation: "vertical" });
|
|
59
|
+
return (_jsxs("div", { style: { display: "flex", gap: 8, flexDirection: "column" }, ref: ref, children: [_jsx("button", { onClick: () => { }, children: "Action 1" }), _jsx("button", { onClick: () => { }, children: "Action 2" }), _jsx("button", { onClick: () => { }, children: "Action 3" })] }));
|
|
60
|
+
},
|
|
61
|
+
play: async ({ canvas }) => {
|
|
62
|
+
const buttons = canvas.getAllByRole("button");
|
|
63
|
+
buttons[0].focus();
|
|
64
|
+
await userEvent.keyboard("{ArrowDown/}");
|
|
65
|
+
expect(document.activeElement).toBe(buttons[1]);
|
|
66
|
+
await userEvent.keyboard("{ArrowUp/}");
|
|
67
|
+
expect(document.activeElement).toBe(buttons[0]);
|
|
68
|
+
await userEvent.keyboard("{ArrowRight/}");
|
|
69
|
+
expect(document.activeElement).toBe(buttons[0]);
|
|
70
|
+
await userEvent.keyboard("{ArrowLeft/}");
|
|
71
|
+
expect(document.activeElement).toBe(buttons[0]);
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
export const circular = {
|
|
75
|
+
name: "circular",
|
|
76
|
+
render: () => {
|
|
77
|
+
const ref = useRef(null);
|
|
78
|
+
useKeyboardArrowNavigation({ ref, circular: true });
|
|
79
|
+
return (_jsxs("div", { style: { display: "flex", gap: 8, flexDirection: "row" }, ref: ref, children: [_jsx("button", { onClick: () => { }, children: "Action 1" }), _jsx("button", { onClick: () => { }, children: "Action 2" }), _jsx("button", { onClick: () => { }, children: "Action 3" })] }));
|
|
80
|
+
},
|
|
81
|
+
play: async ({ canvas }) => {
|
|
82
|
+
const buttons = canvas.getAllByRole("button");
|
|
83
|
+
buttons[0].focus();
|
|
84
|
+
await userEvent.keyboard("{ArrowRight/}");
|
|
85
|
+
await userEvent.keyboard("{ArrowRight/}");
|
|
86
|
+
expect(document.activeElement).toBe(buttons[2]);
|
|
87
|
+
await userEvent.keyboard("{ArrowRight/}");
|
|
88
|
+
expect(document.activeElement).toBe(buttons[0]);
|
|
89
|
+
await userEvent.keyboard("{ArrowLeft/}");
|
|
90
|
+
expect(document.activeElement).toBe(buttons[2]);
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
export const disabled = {
|
|
94
|
+
name: "disabled",
|
|
95
|
+
render: () => {
|
|
96
|
+
const ref = useRef(null);
|
|
97
|
+
useKeyboardArrowNavigation({ ref, disabled: true });
|
|
98
|
+
return (_jsxs("div", { style: { display: "flex", gap: 8, flexDirection: "row" }, ref: ref, children: [_jsx("button", { onClick: () => { }, children: "Action 1" }), _jsx("button", { onClick: () => { }, children: "Action 2" }), _jsx("button", { onClick: () => { }, children: "Action 3" })] }));
|
|
99
|
+
},
|
|
100
|
+
play: async ({ canvas }) => {
|
|
101
|
+
const buttons = canvas.getAllByRole("button");
|
|
102
|
+
buttons[0].focus();
|
|
103
|
+
await userEvent.keyboard("{ArrowRight/}");
|
|
104
|
+
expect(document.activeElement).toBe(buttons[0]);
|
|
105
|
+
expect(buttons[0]).not.toHaveAttribute("tabindex");
|
|
106
|
+
},
|
|
107
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { expect, userEvent } from "storybook/test";
|
|
3
|
+
import useKeyboardMode from "../useKeyboardMode.js";
|
|
4
|
+
export default {
|
|
5
|
+
title: "Headless/Hooks/useKeyboardMode",
|
|
6
|
+
parameters: {
|
|
7
|
+
chromatic: { disableSnapshot: true },
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
const Component = () => {
|
|
11
|
+
const { activate, deactivate, disable, enable } = useKeyboardMode();
|
|
12
|
+
return (_jsxs("div", { style: { display: "flex", gap: 8, flexDirection: "row" }, children: [_jsx("button", { onClick: activate, children: "Activate" }), _jsx("button", { onClick: deactivate, children: "Deactivate" }), _jsx("button", { onClick: disable, children: "Disable" }), _jsx("button", { onClick: enable, children: "Enable" })] }));
|
|
13
|
+
};
|
|
14
|
+
export const base = {
|
|
15
|
+
name: "base",
|
|
16
|
+
render: () => _jsx(Component, {}),
|
|
17
|
+
play: async ({ canvas }) => {
|
|
18
|
+
const attribute = "data-rs-keyboard";
|
|
19
|
+
const root = document.documentElement;
|
|
20
|
+
const activateTrigger = canvas.getAllByRole("button")[0];
|
|
21
|
+
const deactivateTrigger = canvas.getAllByRole("button")[1];
|
|
22
|
+
const disableTrigger = canvas.getAllByRole("button")[2];
|
|
23
|
+
const enableTrigger = canvas.getAllByRole("button")[3];
|
|
24
|
+
expect(root).not.toHaveAttribute(attribute);
|
|
25
|
+
await userEvent.click(activateTrigger);
|
|
26
|
+
expect(root).toHaveAttribute(attribute);
|
|
27
|
+
await userEvent.click(deactivateTrigger);
|
|
28
|
+
expect(root).not.toHaveAttribute(attribute);
|
|
29
|
+
await userEvent.click(disableTrigger);
|
|
30
|
+
await userEvent.click(activateTrigger);
|
|
31
|
+
expect(root).not.toHaveAttribute(attribute);
|
|
32
|
+
await userEvent.click(enableTrigger);
|
|
33
|
+
await userEvent.click(activateTrigger);
|
|
34
|
+
expect(root).toHaveAttribute(attribute);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { type Mock } from "storybook/test";
|
|
3
|
+
declare const _default: {
|
|
4
|
+
title: string;
|
|
5
|
+
parameters: {
|
|
6
|
+
chromatic: {
|
|
7
|
+
disableSnapshot: boolean;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export default _default;
|
|
12
|
+
export declare const base: StoryObj<{
|
|
13
|
+
handleOutsideClick: Mock;
|
|
14
|
+
}>;
|
|
15
|
+
export declare const refs: StoryObj<{
|
|
16
|
+
handleOutsideClick: Mock;
|
|
17
|
+
}>;
|
|
18
|
+
export declare const disabled: StoryObj<{
|
|
19
|
+
handleOutsideClick: Mock;
|
|
20
|
+
}>;
|
|
21
|
+
export declare const deps: StoryObj<{
|
|
22
|
+
handleOutsideClick: Mock;
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { expect, fn, userEvent } from "storybook/test";
|
|
4
|
+
import useOnClickOutside from "../useOnClickOutside.js";
|
|
5
|
+
export default {
|
|
6
|
+
title: "Headless/Hooks/useOnClickOutside",
|
|
7
|
+
parameters: {
|
|
8
|
+
chromatic: { disableSnapshot: true },
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
export const base = {
|
|
12
|
+
name: "base",
|
|
13
|
+
args: {
|
|
14
|
+
handleOutsideClick: fn(),
|
|
15
|
+
},
|
|
16
|
+
render: (args) => {
|
|
17
|
+
const ref = React.useRef(null);
|
|
18
|
+
const [target, setTarget] = React.useState(null);
|
|
19
|
+
useOnClickOutside([ref], () => {
|
|
20
|
+
args.handleOutsideClick();
|
|
21
|
+
setTarget("outside");
|
|
22
|
+
});
|
|
23
|
+
return (_jsxs("div", { style: { display: "flex", gap: 16, flexDirection: "column", alignItems: "flex-start" }, children: [_jsx("button", { ref: ref, onClick: () => setTarget("inside"), children: "Trigger" }), target && `Clicked ${target}`] }));
|
|
24
|
+
},
|
|
25
|
+
play: async ({ canvas, args }) => {
|
|
26
|
+
const button = canvas.getAllByRole("button")[0];
|
|
27
|
+
await userEvent.click(button);
|
|
28
|
+
expect(args.handleOutsideClick).not.toHaveBeenCalled();
|
|
29
|
+
await userEvent.click(document.body);
|
|
30
|
+
expect(args.handleOutsideClick).toHaveBeenCalledTimes(1);
|
|
31
|
+
expect(args.handleOutsideClick).toHaveBeenCalledWith();
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
export const refs = {
|
|
35
|
+
name: "multiple refs",
|
|
36
|
+
args: {
|
|
37
|
+
handleOutsideClick: fn(),
|
|
38
|
+
},
|
|
39
|
+
render: (args) => {
|
|
40
|
+
const ref = React.useRef(null);
|
|
41
|
+
const ref2 = React.useRef(null);
|
|
42
|
+
useOnClickOutside([ref, ref2], () => {
|
|
43
|
+
args.handleOutsideClick();
|
|
44
|
+
});
|
|
45
|
+
return (_jsxs("div", { style: { display: "flex", gap: 16, flexDirection: "column", alignItems: "flex-start" }, children: [_jsx("button", { ref: ref, children: "Trigger" }), _jsx("button", { ref: ref2, children: "Trigger 2" })] }));
|
|
46
|
+
},
|
|
47
|
+
play: async ({ canvas, args }) => {
|
|
48
|
+
const [button, button2] = canvas.getAllByRole("button");
|
|
49
|
+
await userEvent.click(button);
|
|
50
|
+
expect(args.handleOutsideClick).not.toHaveBeenCalled();
|
|
51
|
+
await userEvent.click(button2);
|
|
52
|
+
expect(args.handleOutsideClick).not.toHaveBeenCalled();
|
|
53
|
+
await userEvent.click(document.body);
|
|
54
|
+
expect(args.handleOutsideClick).toHaveBeenCalledTimes(1);
|
|
55
|
+
expect(args.handleOutsideClick).toHaveBeenCalledWith();
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
export const disabled = {
|
|
59
|
+
name: "disabled",
|
|
60
|
+
args: {
|
|
61
|
+
handleOutsideClick: fn(),
|
|
62
|
+
},
|
|
63
|
+
render: (args) => {
|
|
64
|
+
const ref = React.useRef(null);
|
|
65
|
+
useOnClickOutside([ref], () => {
|
|
66
|
+
args.handleOutsideClick();
|
|
67
|
+
}, {
|
|
68
|
+
disabled: true,
|
|
69
|
+
});
|
|
70
|
+
return _jsx("button", { ref: ref, children: "Trigger" });
|
|
71
|
+
},
|
|
72
|
+
play: async ({ args }) => {
|
|
73
|
+
await userEvent.click(document.body);
|
|
74
|
+
expect(args.handleOutsideClick).not.toHaveBeenCalled();
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
export const deps = {
|
|
78
|
+
name: "test: handler uses latest state",
|
|
79
|
+
args: {
|
|
80
|
+
handleOutsideClick: fn(),
|
|
81
|
+
},
|
|
82
|
+
render: (args) => {
|
|
83
|
+
const ref = React.useRef(null);
|
|
84
|
+
const [count, setCount] = React.useState(0);
|
|
85
|
+
useOnClickOutside([ref], () => {
|
|
86
|
+
args.handleOutsideClick({ count });
|
|
87
|
+
});
|
|
88
|
+
return (_jsx("button", { ref: ref, onClick: () => setCount((prev) => prev + 1), children: "Trigger" }));
|
|
89
|
+
},
|
|
90
|
+
play: async ({ canvas, args }) => {
|
|
91
|
+
const button = canvas.getAllByRole("button")[0];
|
|
92
|
+
await userEvent.click(document.body);
|
|
93
|
+
expect(args.handleOutsideClick).toHaveBeenLastCalledWith({ count: 0 });
|
|
94
|
+
await userEvent.click(button);
|
|
95
|
+
await userEvent.click(document.body);
|
|
96
|
+
expect(args.handleOutsideClick).toHaveBeenLastCalledWith({ count: 1 });
|
|
97
|
+
},
|
|
98
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { expect } from "storybook/test";
|
|
4
|
+
import useRTL from "../useRTL.js";
|
|
5
|
+
export default {
|
|
6
|
+
title: "Headless/Hooks/useRTL",
|
|
7
|
+
parameters: {
|
|
8
|
+
chromatic: { disableSnapshot: true },
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
const Component = () => {
|
|
12
|
+
const [rtl, setRTL] = useRTL();
|
|
13
|
+
React.useEffect(() => {
|
|
14
|
+
setRTL(true);
|
|
15
|
+
}, [setRTL]);
|
|
16
|
+
return _jsx("div", { children: rtl ? "RTL" : "LTR" });
|
|
17
|
+
};
|
|
18
|
+
export const setRTL = {
|
|
19
|
+
name: "setRTL",
|
|
20
|
+
render: () => _jsx(Component, {}),
|
|
21
|
+
play: async () => {
|
|
22
|
+
expect(document.documentElement).toHaveAttribute("dir", "rtl");
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { StoryObj } from "@storybook/react-vite";
|
|
2
|
+
declare const _default: {
|
|
3
|
+
title: string;
|
|
4
|
+
parameters: {
|
|
5
|
+
chromatic: {
|
|
6
|
+
disableSnapshot: boolean;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
export default _default;
|
|
11
|
+
export declare const base: StoryObj;
|
|
12
|
+
export declare const origin: StoryObj;
|
|
13
|
+
export declare const container: StoryObj;
|
|
14
|
+
export declare const testContainerAsync: StoryObj;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { expect, userEvent } from "storybook/test";
|
|
4
|
+
import useScrollLock from "../useScrollLock.js";
|
|
5
|
+
export default {
|
|
6
|
+
title: "Headless/Hooks/useScrollLock",
|
|
7
|
+
parameters: {
|
|
8
|
+
chromatic: { disableSnapshot: true },
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
export const base = {
|
|
12
|
+
name: "base",
|
|
13
|
+
render: () => {
|
|
14
|
+
const { lockScroll, unlockScroll, scrollLocked } = useScrollLock();
|
|
15
|
+
return (_jsxs(React.Fragment, { children: [_jsx("button", { onClick: scrollLocked ? unlockScroll : lockScroll, children: scrollLocked ? "Unlock" : "Lock" }), _jsx("div", { style: { height: "150vh" } })] }));
|
|
16
|
+
},
|
|
17
|
+
play: async ({ canvas }) => {
|
|
18
|
+
const button = canvas.getAllByRole("button")[0];
|
|
19
|
+
await userEvent.click(button);
|
|
20
|
+
expect(document.body).toHaveStyle("overflow: hidden");
|
|
21
|
+
await userEvent.click(button);
|
|
22
|
+
expect(document.body).not.toHaveStyle("overflow: hidden");
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
export const origin = {
|
|
26
|
+
name: "originRef",
|
|
27
|
+
render: () => {
|
|
28
|
+
const originRef = React.useRef(null);
|
|
29
|
+
const { lockScroll, unlockScroll, scrollLocked } = useScrollLock({ originRef });
|
|
30
|
+
return (_jsx("div", { style: { overflow: "auto", height: 100 }, ref: originRef, "data-testid": "root", children: _jsx("div", { style: { height: 200, padding: 15, backgroundColor: "#1f1f1f", borderRadius: 8 }, children: _jsx("button", { onClick: scrollLocked ? unlockScroll : lockScroll, children: "Toggle" }) }) }));
|
|
31
|
+
},
|
|
32
|
+
play: async ({ canvas }) => {
|
|
33
|
+
const button = canvas.getAllByRole("button")[0];
|
|
34
|
+
const root = canvas.getByTestId("root");
|
|
35
|
+
await userEvent.click(button);
|
|
36
|
+
expect(document.body).not.toHaveStyle("overflow: hidden");
|
|
37
|
+
expect(root).toHaveStyle("overflow: hidden");
|
|
38
|
+
await userEvent.click(button);
|
|
39
|
+
expect(root).not.toHaveStyle("overflow: hidden");
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
export const container = {
|
|
43
|
+
name: "containerRef",
|
|
44
|
+
render: () => {
|
|
45
|
+
const containerRef = React.useRef(null);
|
|
46
|
+
const { lockScroll, unlockScroll, scrollLocked } = useScrollLock({ containerRef });
|
|
47
|
+
return (_jsx("div", { style: { overflow: "auto", height: 100 }, ref: containerRef, "data-testid": "root", children: _jsx("button", { onClick: scrollLocked ? unlockScroll : lockScroll, children: "Toggle" }) }));
|
|
48
|
+
},
|
|
49
|
+
play: async ({ canvas }) => {
|
|
50
|
+
const button = canvas.getAllByRole("button")[0];
|
|
51
|
+
const root = canvas.getByTestId("root");
|
|
52
|
+
await userEvent.click(button);
|
|
53
|
+
expect(document.body).not.toHaveStyle("overflow: hidden");
|
|
54
|
+
expect(root).toHaveStyle("overflow: hidden");
|
|
55
|
+
await userEvent.click(button);
|
|
56
|
+
expect(root).not.toHaveStyle("overflow: hidden");
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
export const testContainerAsync = {
|
|
60
|
+
name: "test: containerRef locked count",
|
|
61
|
+
render: () => {
|
|
62
|
+
const containerRef = React.useRef(null);
|
|
63
|
+
const globalLock = useScrollLock();
|
|
64
|
+
const scopedLock = useScrollLock({ containerRef });
|
|
65
|
+
return (_jsxs("div", { style: { display: "flex", gap: 16, flexDirection: "row" }, ref: containerRef, children: [_jsx("button", { onClick: globalLock.scrollLocked ? globalLock.unlockScroll : globalLock.lockScroll, children: "Toggle" }), _jsx("button", { onClick: scopedLock.scrollLocked ? scopedLock.unlockScroll : scopedLock.lockScroll, children: "Toggle" })] }));
|
|
66
|
+
},
|
|
67
|
+
play: async ({ canvas }) => {
|
|
68
|
+
const [buttonGlobal, buttonScoped] = canvas.getAllByRole("button");
|
|
69
|
+
await userEvent.click(buttonGlobal);
|
|
70
|
+
expect(document.body).toHaveStyle("overflow: hidden");
|
|
71
|
+
await userEvent.click(buttonScoped);
|
|
72
|
+
await userEvent.click(buttonGlobal);
|
|
73
|
+
expect(document.body).not.toHaveStyle("overflow: hidden");
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { StoryObj } from "@storybook/react-vite";
|
|
2
|
+
declare const _default: {
|
|
3
|
+
title: string;
|
|
4
|
+
parameters: {
|
|
5
|
+
chromatic: {
|
|
6
|
+
disableSnapshot: boolean;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
export default _default;
|
|
11
|
+
export declare const toggle: StoryObj;
|
|
12
|
+
export declare const activate: StoryObj;
|
|
13
|
+
export declare const deactivate: StoryObj;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { expect, userEvent } from "storybook/test";
|
|
3
|
+
import useToggle from "../useToggle.js";
|
|
4
|
+
export default {
|
|
5
|
+
title: "Headless/Hooks/useToggle",
|
|
6
|
+
parameters: {
|
|
7
|
+
chromatic: { disableSnapshot: true },
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
export const toggle = {
|
|
11
|
+
name: "toggle",
|
|
12
|
+
render: () => {
|
|
13
|
+
const { toggle, active } = useToggle();
|
|
14
|
+
return (_jsx("button", { onClick: () => toggle(), "data-active": active, children: active ? "Deactivate" : "Activate" }));
|
|
15
|
+
},
|
|
16
|
+
play: async ({ canvas }) => {
|
|
17
|
+
const button = canvas.getAllByRole("button")[0];
|
|
18
|
+
expect(button.getAttribute("data-active")).toBe("false");
|
|
19
|
+
await userEvent.click(button);
|
|
20
|
+
expect(button.getAttribute("data-active")).toBe("true");
|
|
21
|
+
await userEvent.click(button);
|
|
22
|
+
expect(button.getAttribute("data-active")).toBe("false");
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
export const activate = {
|
|
26
|
+
name: "activate",
|
|
27
|
+
render: () => {
|
|
28
|
+
const { activate, active } = useToggle();
|
|
29
|
+
return (_jsx("button", { onClick: activate, "data-active": active, children: "Activate" }));
|
|
30
|
+
},
|
|
31
|
+
play: async ({ canvas }) => {
|
|
32
|
+
const button = canvas.getAllByRole("button")[0];
|
|
33
|
+
expect(button.getAttribute("data-active")).toBe("false");
|
|
34
|
+
await userEvent.click(button);
|
|
35
|
+
expect(button.getAttribute("data-active")).toBe("true");
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
export const deactivate = {
|
|
39
|
+
name: "deactivate",
|
|
40
|
+
render: () => {
|
|
41
|
+
const { deactivate, active } = useToggle(true);
|
|
42
|
+
return (_jsx("button", { onClick: deactivate, "data-active": active, children: "Deactivate" }));
|
|
43
|
+
},
|
|
44
|
+
play: async ({ canvas }) => {
|
|
45
|
+
const button = canvas.getAllByRole("button")[0];
|
|
46
|
+
expect(button.getAttribute("data-active")).toBe("true");
|
|
47
|
+
await userEvent.click(button);
|
|
48
|
+
expect(button.getAttribute("data-active")).toBe("false");
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Hook for wrapping event handlers passed as props with a ref
|
|
4
|
+
* This way we can keep the instance of the ref the same and pass this ref to the effects dependency array
|
|
5
|
+
* While also making sure that function implementation stays up-to-date
|
|
6
|
+
*/
|
|
7
|
+
declare const useHandlerRef: <T>(cb: T) => React.RefObject<T>;
|
|
8
|
+
export default useHandlerRef;
|