@tsed/react-formio 3.0.0-alpha.10 → 3.0.0-alpha.11
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/hooks/keyboard.constants.d.ts +38 -0
- package/dist/hooks/keyboard.constants.js +7 -0
- package/dist/hooks/keyboard.constants.js.map +1 -0
- package/dist/hooks/useKeyboardControls.d.ts +12 -0
- package/dist/hooks/useKeyboardControls.js +35 -0
- package/dist/hooks/useKeyboardControls.js.map +1 -0
- package/dist/interfaces/Operation.d.ts +12 -2
- package/dist/molecules/button/Button.d.ts +18 -5
- package/dist/molecules/button/Button.js +29 -30
- package/dist/molecules/button/Button.js.map +1 -1
- package/dist/molecules/card/Card.js +7 -5
- package/dist/molecules/card/Card.js.map +1 -1
- package/dist/molecules/forms/input-tags/InputTags.js +14 -14
- package/dist/molecules/forms/input-tags/InputTags.js.map +1 -1
- package/dist/molecules/table/Table.d.ts +11 -3
- package/dist/molecules/table/Table.js +8 -3
- package/dist/molecules/table/Table.js.map +1 -1
- package/dist/molecules/table/components/DefaultCellOperations.d.ts +12 -4
- package/dist/molecules/table/components/DefaultCellOperations.js +12 -6
- package/dist/molecules/table/components/DefaultCellOperations.js.map +1 -1
- package/dist/molecules/table/components/DefaultOperationButton.d.ts +12 -4
- package/dist/molecules/table/components/DefaultOperationButton.js.map +1 -1
- package/dist/molecules/table/hooks/useTable.d.ts +12 -4
- package/dist/molecules/table/hooks/useTable.js.map +1 -1
- package/dist/molecules/tabs/Tab.d.ts +13 -0
- package/dist/molecules/tabs/Tab.js +67 -0
- package/dist/molecules/tabs/Tab.js.map +1 -0
- package/dist/molecules/tabs/TabList.d.ts +2 -0
- package/dist/molecules/tabs/TabList.js +24 -0
- package/dist/molecules/tabs/TabList.js.map +1 -0
- package/dist/molecules/tabs/TabPanel.d.ts +9 -0
- package/dist/molecules/tabs/TabPanel.js +27 -0
- package/dist/molecules/tabs/TabPanel.js.map +1 -0
- package/dist/molecules/tabs/Tabs.d.ts +4 -16
- package/dist/molecules/tabs/Tabs.js +7 -67
- package/dist/molecules/tabs/Tabs.js.map +1 -1
- package/dist/molecules/tabs/TabsBody.d.ts +1 -0
- package/dist/molecules/tabs/TabsBody.js +10 -0
- package/dist/molecules/tabs/TabsBody.js.map +1 -0
- package/dist/molecules/tabs/TabsLegacy.d.ts +17 -0
- package/dist/molecules/tabs/TabsLegacy.js +49 -0
- package/dist/molecules/tabs/TabsLegacy.js.map +1 -0
- package/dist/molecules/tabs/all.d.ts +5 -0
- package/dist/molecules/tabs/all.js +13 -0
- package/dist/molecules/tabs/all.js.map +1 -0
- package/dist/molecules/tabs/context/TabControl.d.ts +52 -0
- package/dist/molecules/tabs/context/TabControl.js +85 -0
- package/dist/molecules/tabs/context/TabControl.js.map +1 -0
- package/dist/molecules/tabs/hooks/tabControl.d.ts +44 -0
- package/dist/molecules/tabs/hooks/tabControl.js +34 -0
- package/dist/molecules/tabs/hooks/tabControl.js.map +1 -0
- package/dist/organisms/form/actions/FormAction.js.map +1 -0
- package/dist/organisms/form/builder/FormEdit.d.ts +3 -1
- package/dist/organisms/form/builder/FormEdit.js +38 -35
- package/dist/organisms/form/builder/FormEdit.js.map +1 -1
- package/dist/organisms/form/builder/useFormEdit.js +17 -17
- package/dist/organisms/form/builder/useFormEdit.js.map +1 -1
- package/dist/organisms/form/exports/FormExport.d.ts +5 -0
- package/dist/organisms/form/exports/FormExport.js +55 -0
- package/dist/organisms/form/exports/FormExport.js.map +1 -0
- package/dist/organisms/form/preview/FormPreview.d.ts +6 -0
- package/dist/organisms/form/preview/FormPreview.js +11 -0
- package/dist/organisms/form/preview/FormPreview.js.map +1 -0
- package/dist/organisms/table/submissions/SubmissionsTable.d.ts +11 -3
- package/dist/organisms/table/submissions/SubmissionsTable.js +4 -1
- package/dist/organisms/table/submissions/SubmissionsTable.js.map +1 -1
- package/dist/organisms/views/FormViews.d.ts +24 -0
- package/dist/organisms/views/FormViews.js +96 -0
- package/dist/organisms/views/FormViews.js.map +1 -0
- package/package.json +3 -3
- package/src/hooks/keyboard.constants.ts +40 -0
- package/src/hooks/useKeyboardControls.spec.tsx +208 -0
- package/src/hooks/useKeyboardControls.ts +84 -0
- package/src/interfaces/Operation.ts +9 -3
- package/src/molecules/button/Button.tsx +43 -24
- package/src/molecules/card/Card.tsx +4 -0
- package/src/molecules/forms/input-tags/InputTags.tsx +1 -1
- package/src/molecules/pagination/Pagination.stories.tsx +0 -7
- package/src/molecules/table/Table.stories.tsx +34 -1
- package/src/molecules/table/Table.tsx +12 -6
- package/src/molecules/table/components/DefaultCellOperations.tsx +13 -7
- package/src/molecules/table/components/DefaultOperationButton.tsx +5 -4
- package/src/molecules/table/hooks/useTable.tsx +5 -5
- package/src/molecules/tabs/Tab.tsx +106 -0
- package/src/molecules/tabs/TabList.tsx +37 -0
- package/src/molecules/tabs/TabPanel.tsx +37 -0
- package/src/molecules/tabs/Tabs.spec.tsx +126 -73
- package/src/molecules/tabs/Tabs.stories.tsx +298 -65
- package/src/molecules/tabs/Tabs.tsx +10 -81
- package/src/molecules/tabs/TabsBody.tsx +11 -0
- package/src/molecules/tabs/TabsLegacy.stories.tsx +103 -0
- package/src/molecules/tabs/TabsLegacy.tsx +84 -0
- package/src/molecules/tabs/all.ts +5 -0
- package/src/molecules/tabs/context/TabControl.tsx +166 -0
- package/src/molecules/tabs/hooks/tabControl.spec.tsx +388 -0
- package/src/molecules/tabs/hooks/tabControl.ts +52 -0
- package/src/organisms/__fixtures__/form-firstname.fixture.json +1 -0
- package/src/organisms/__fixtures__/form.fixture.json +1 -0
- package/src/organisms/form/actions/FormAction.stories.tsx +422 -0
- package/src/organisms/form/builder/FormEdit.tsx +7 -1
- package/src/organisms/form/builder/useFormEdit.ts +1 -1
- package/src/organisms/form/exports/FormExport.stories.tsx +71 -0
- package/src/organisms/form/exports/FormExport.tsx +58 -0
- package/src/organisms/form/preview/FormPreview.stories.tsx +61 -0
- package/src/organisms/form/preview/FormPreview.tsx +21 -0
- package/src/organisms/table/actions/ActionsTable.stories.tsx +36 -34
- package/src/organisms/table/submissions/SubmissionsTable.stories.tsx +103 -57
- package/src/organisms/table/submissions/SubmissionsTable.tsx +10 -4
- package/src/organisms/views/FormViews.stories.tsx +224 -0
- package/src/organisms/views/FormViews.tsx +146 -0
- package/dist/organisms/form/action/FormAction.js.map +0 -1
- package/src/organisms/form/action/FormAction.stories.tsx +0 -364
- /package/dist/organisms/form/{action → actions}/FormAction.d.ts +0 -0
- /package/dist/organisms/form/{action → actions}/FormAction.js +0 -0
- /package/src/organisms/form/{action → actions}/FormAction.tsx +0 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import "./all.js";
|
|
2
|
+
|
|
3
|
+
import { CSSProperties, ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
import { getComponent } from "../../registries/components.js";
|
|
6
|
+
import type { Tab as DefaultTab } from "./Tab.js";
|
|
7
|
+
import type { TabList as DefaultTabList } from "./TabList.js";
|
|
8
|
+
import type { TabPanel as DefaultTabPanel } from "./TabPanel.js";
|
|
9
|
+
import type { Tabs as DefaultTabs } from "./Tabs.js";
|
|
10
|
+
import type { TabsBody as DefaultTabsBody } from "./TabsBody.js";
|
|
11
|
+
|
|
12
|
+
export interface TabsItemProps extends Record<string, any> {
|
|
13
|
+
label?: string;
|
|
14
|
+
icon?: string;
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TabsLegacyProps extends Record<string, any> {
|
|
19
|
+
AddButton?: any;
|
|
20
|
+
current?: TabsItemProps;
|
|
21
|
+
items?: TabsItemProps[];
|
|
22
|
+
style?: CSSProperties;
|
|
23
|
+
className?: string;
|
|
24
|
+
reverse?: boolean;
|
|
25
|
+
onClick?: (item: TabsItemProps) => void;
|
|
26
|
+
i18n?: (f: string) => string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function TabsLegacy({
|
|
30
|
+
style,
|
|
31
|
+
current,
|
|
32
|
+
items = [],
|
|
33
|
+
HeaderChildren,
|
|
34
|
+
AddButton,
|
|
35
|
+
className,
|
|
36
|
+
onClick,
|
|
37
|
+
i18n = (f) => f,
|
|
38
|
+
reverse,
|
|
39
|
+
after,
|
|
40
|
+
...additionalProps
|
|
41
|
+
}: TabsLegacyProps) {
|
|
42
|
+
const Tab = getComponent<typeof DefaultTab>("Tab");
|
|
43
|
+
const TabsBody = getComponent<typeof DefaultTabsBody>("TabsBody");
|
|
44
|
+
const TabList = getComponent<typeof DefaultTabList>("TabList");
|
|
45
|
+
const TabPanel = getComponent<typeof DefaultTabPanel>("TabPanel");
|
|
46
|
+
const Tabs = getComponent<typeof DefaultTabs>("Tabs");
|
|
47
|
+
const tabs = items.filter((item) => item.label || item.icon);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Tabs className={className} style={style}>
|
|
51
|
+
<div>
|
|
52
|
+
<TabList>
|
|
53
|
+
{tabs.map((item, index) => {
|
|
54
|
+
return (
|
|
55
|
+
<Tab
|
|
56
|
+
key={index}
|
|
57
|
+
onClick={() => {
|
|
58
|
+
onClick && onClick(item);
|
|
59
|
+
}}
|
|
60
|
+
icon={item.icon}
|
|
61
|
+
value={index}
|
|
62
|
+
className={reverse ? "-reverse" : ""}
|
|
63
|
+
after={after}
|
|
64
|
+
>
|
|
65
|
+
{i18n(item.label || "")}
|
|
66
|
+
</Tab>
|
|
67
|
+
);
|
|
68
|
+
})}
|
|
69
|
+
{AddButton && <AddButton {...additionalProps} current={current} />}
|
|
70
|
+
</TabList>
|
|
71
|
+
{HeaderChildren && <HeaderChildren {...additionalProps} current={current} />}
|
|
72
|
+
</div>
|
|
73
|
+
<TabsBody>
|
|
74
|
+
{tabs.map((item, index) => {
|
|
75
|
+
return (
|
|
76
|
+
<TabPanel key={index} value={index}>
|
|
77
|
+
{item.children || item.content}
|
|
78
|
+
</TabPanel>
|
|
79
|
+
);
|
|
80
|
+
})}
|
|
81
|
+
</TabsBody>
|
|
82
|
+
</Tabs>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createContext, type Dispatch, type PropsWithChildren, type RefObject, useCallback, useId, useReducer } from "react";
|
|
4
|
+
|
|
5
|
+
type RefTab<T extends HTMLElement = HTMLElement> = RefObject<T>;
|
|
6
|
+
type RefTabs<T extends HTMLElement = HTMLElement> = Map<number, RefTab<T>>;
|
|
7
|
+
|
|
8
|
+
type TabsUpdateAction = {
|
|
9
|
+
type: "update";
|
|
10
|
+
payload: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type TabsRegisterAction<T extends HTMLElement = HTMLElement> = {
|
|
14
|
+
type: "register";
|
|
15
|
+
payload: { value: number; ref: RefObject<T> };
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type TabsUnregisterAction = {
|
|
19
|
+
type: "unregister";
|
|
20
|
+
payload: { value: number };
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type TabsAction<T extends HTMLElement = HTMLElement> =
|
|
24
|
+
| { type: "start" | "end" | "previous" | "next" }
|
|
25
|
+
| TabsUpdateAction
|
|
26
|
+
| TabsRegisterAction<T>
|
|
27
|
+
| TabsUnregisterAction;
|
|
28
|
+
|
|
29
|
+
interface TabsState<T extends HTMLElement = HTMLElement> {
|
|
30
|
+
uid: string;
|
|
31
|
+
value: number;
|
|
32
|
+
refs: RefTabs<T>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface TabControl<T extends HTMLElement = HTMLElement> extends TabsState<T> {
|
|
36
|
+
uid: string;
|
|
37
|
+
value: number;
|
|
38
|
+
refs: RefTabs<T>;
|
|
39
|
+
dispatch: Dispatch<TabsAction>;
|
|
40
|
+
register: (value: number, ref: RefTab<T>) => void;
|
|
41
|
+
unregister: (value: number) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function next(state: TabsState) {
|
|
45
|
+
const keys = [...state.refs.keys()];
|
|
46
|
+
const index = keys.findIndex((value) => value === state.value);
|
|
47
|
+
const nextIndex = (index + 1) % keys.length;
|
|
48
|
+
|
|
49
|
+
return { ...state, value: keys[nextIndex] };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function previous(state: TabsState) {
|
|
53
|
+
const keys = [...state.refs.keys()];
|
|
54
|
+
const index = keys.findIndex((value) => value === state.value);
|
|
55
|
+
const previousIndex = (index - 1 + keys.length) % keys.length;
|
|
56
|
+
|
|
57
|
+
return { ...state, value: keys[previousIndex] };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function start(state: TabsState) {
|
|
61
|
+
const keys = [...state.refs.keys()];
|
|
62
|
+
const firstIndex = keys[0];
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
...state,
|
|
66
|
+
value: firstIndex
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function end(state: TabsState) {
|
|
71
|
+
const keys = [...state.refs.keys()];
|
|
72
|
+
const lastIndex = keys[keys.length - 1];
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
...state,
|
|
76
|
+
value: lastIndex
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function unregister(state: TabsState, action: TabsUnregisterAction) {
|
|
81
|
+
const deletedRefs = new Map(state.refs);
|
|
82
|
+
deletedRefs.delete(action.payload.value);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
...state,
|
|
86
|
+
refs: deletedRefs
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function register(state: TabsState, action: TabsRegisterAction) {
|
|
91
|
+
return {
|
|
92
|
+
...state,
|
|
93
|
+
refs: new Map(state.refs).set(action.payload.value, action.payload.ref)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function update(state: TabsState, action: TabsUpdateAction) {
|
|
98
|
+
return { ...state, value: action.payload };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const actions = {
|
|
102
|
+
next,
|
|
103
|
+
previous,
|
|
104
|
+
start,
|
|
105
|
+
end,
|
|
106
|
+
register,
|
|
107
|
+
unregister,
|
|
108
|
+
update
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
function tabsReducer(state: TabsState, action: TabsAction): TabsState {
|
|
112
|
+
if (action.type in actions) {
|
|
113
|
+
const actionFunction = actions[action.type as keyof typeof actions] as (state: TabsState, action: TabsAction) => TabsState;
|
|
114
|
+
|
|
115
|
+
return actionFunction(state, action);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return state;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const TabControlContext = createContext<TabControl>({
|
|
122
|
+
uid: "",
|
|
123
|
+
value: 0,
|
|
124
|
+
refs: new Map(),
|
|
125
|
+
dispatch: () => {
|
|
126
|
+
console.warn("Tab Controller Context dispatch used outside of Provider");
|
|
127
|
+
},
|
|
128
|
+
register: () => {
|
|
129
|
+
console.warn("Tab Controller Context register used outside of Provider");
|
|
130
|
+
},
|
|
131
|
+
unregister: () => {
|
|
132
|
+
console.warn("Tab Controller Context unregister used outside of Provider");
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
TabControlContext.displayName = "TabControlContext";
|
|
137
|
+
|
|
138
|
+
export interface TabsProviderProps {
|
|
139
|
+
selected?: number;
|
|
140
|
+
/**
|
|
141
|
+
* The selected tab value
|
|
142
|
+
* @deprecated Min props as no effect on the Tabs component and will be removed in future versions
|
|
143
|
+
*/
|
|
144
|
+
min?: number;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The selected tab value
|
|
148
|
+
* @deprecated Max props as no effect on the Tabs component and will be removed in future versions
|
|
149
|
+
*/
|
|
150
|
+
max?: number;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function TabsProvider({ selected = 0, children }: PropsWithChildren<TabsProviderProps>) {
|
|
154
|
+
const uid = useId();
|
|
155
|
+
const [state, dispatch] = useReducer(tabsReducer, { uid, value: selected, refs: new Map() });
|
|
156
|
+
|
|
157
|
+
const register = useCallback(<T extends HTMLElement>(value: number, ref: RefTab<T>) => {
|
|
158
|
+
dispatch({ type: "register", payload: { value, ref } });
|
|
159
|
+
}, []);
|
|
160
|
+
|
|
161
|
+
const unregister = useCallback((value: number) => {
|
|
162
|
+
dispatch({ type: "unregister", payload: { value } });
|
|
163
|
+
}, []);
|
|
164
|
+
|
|
165
|
+
return <TabControlContext.Provider value={{ ...state, dispatch, register, unregister }}>{children}</TabControlContext.Provider>;
|
|
166
|
+
}
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { act, render, renderHook, screen } from "@testing-library/react";
|
|
2
|
+
import { userEvent } from "@testing-library/user-event";
|
|
3
|
+
import { type PropsWithChildren, useContext, useRef } from "react";
|
|
4
|
+
|
|
5
|
+
import { TabControlContext, TabsProvider } from "../context/TabControl";
|
|
6
|
+
import { Tab } from "../Tab.js";
|
|
7
|
+
import { useActiveTab, useRegisterTabControl, useTabControls, useTabDispatch } from "./tabControl";
|
|
8
|
+
|
|
9
|
+
interface Config {
|
|
10
|
+
selected: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const createWrapper = ({ selected }: Config = { selected: 3 }) => {
|
|
14
|
+
return ({ children }: PropsWithChildren) => {
|
|
15
|
+
return <TabsProvider selected={selected}>{children}</TabsProvider>;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
describe("tab control hooks", () => {
|
|
20
|
+
beforeAll(() => {
|
|
21
|
+
vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterAll(() => {
|
|
25
|
+
vi.mocked(console.warn).mockReset();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("useActiveTab()", () => {
|
|
29
|
+
it("returns the current tab value", () => {
|
|
30
|
+
const { result } = renderHook(useActiveTab, { wrapper: createWrapper() });
|
|
31
|
+
|
|
32
|
+
expect(result.current).toBe(3);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("useRegisterTabControl()", () => {
|
|
37
|
+
it("returns the dispatch function", () => {
|
|
38
|
+
const { result } = renderHook(() => useRegisterTabControl({ value: 0, ref: useRef(null) }), {
|
|
39
|
+
wrapper: createWrapper()
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(result.current).toEqual(expect.any(Function));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("register and unregister", () => {
|
|
46
|
+
const { result, unmount } = renderHook(() => {
|
|
47
|
+
const div = document.createElement("div");
|
|
48
|
+
|
|
49
|
+
return useRegisterTabControl({ value: 0, ref: useRef<HTMLDivElement>(div) });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
unmount();
|
|
53
|
+
|
|
54
|
+
expect(result.current).toEqual(expect.any(Function));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("without provider", () => {
|
|
58
|
+
it("log a warning", async () => {
|
|
59
|
+
const { result, unmount } = renderHook(() => {
|
|
60
|
+
const div = document.createElement("div");
|
|
61
|
+
return useRegisterTabControl({ value: 0, ref: useRef<HTMLDivElement>(div) });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
await act(() => result.current({ type: "update", payload: 4 }));
|
|
65
|
+
|
|
66
|
+
unmount();
|
|
67
|
+
|
|
68
|
+
expect(console.warn).toHaveBeenCalledWith("Tab Controller Context dispatch used outside of Provider");
|
|
69
|
+
expect(console.warn).toHaveBeenCalledWith("Tab Controller Context register used outside of Provider");
|
|
70
|
+
expect(console.warn).toHaveBeenCalledWith("Tab Controller Context unregister used outside of Provider");
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("useTabDispatch()", () => {
|
|
76
|
+
it("returns the dispatch function", () => {
|
|
77
|
+
const { result } = renderHook(useTabDispatch, {
|
|
78
|
+
wrapper: createWrapper()
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(result.current).toEqual(expect.any(Function));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("without provider", () => {
|
|
85
|
+
it("log a warning", async () => {
|
|
86
|
+
const { result } = renderHook(useTabDispatch);
|
|
87
|
+
|
|
88
|
+
await act(() => result.current({ type: "update", payload: 4 }));
|
|
89
|
+
|
|
90
|
+
expect(console.warn).toHaveBeenCalledWith("Tab Controller Context dispatch used outside of Provider");
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("dispatch update", () => {
|
|
95
|
+
it("update the state", async () => {
|
|
96
|
+
const Test = () => {
|
|
97
|
+
const dispatch = useTabDispatch();
|
|
98
|
+
const tab = useActiveTab();
|
|
99
|
+
|
|
100
|
+
const update = () => {
|
|
101
|
+
dispatch({ type: "update", payload: 4 });
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<>
|
|
106
|
+
<button onClick={update}>update</button>
|
|
107
|
+
<output name='result'>{tab}</output>
|
|
108
|
+
</>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
render(<Test />, { wrapper: createWrapper() });
|
|
113
|
+
|
|
114
|
+
const button = screen.getByRole("button");
|
|
115
|
+
const output = screen.getByRole("status");
|
|
116
|
+
|
|
117
|
+
expect(output).toHaveValue("3");
|
|
118
|
+
|
|
119
|
+
await userEvent.click(button);
|
|
120
|
+
|
|
121
|
+
expect(output).toHaveValue("4");
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe("dispatch end", () => {
|
|
126
|
+
it("update the state", async () => {
|
|
127
|
+
const Test = () => {
|
|
128
|
+
const dispatch = useTabDispatch();
|
|
129
|
+
const tab = useActiveTab();
|
|
130
|
+
|
|
131
|
+
const end = () => {
|
|
132
|
+
dispatch({ type: "end" });
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<>
|
|
137
|
+
<Tab value={0}>Label 0</Tab>
|
|
138
|
+
<Tab value={3}>Label 3</Tab>
|
|
139
|
+
<Tab value={5}>Label 5</Tab>
|
|
140
|
+
|
|
141
|
+
<button onClick={end}>end</button>
|
|
142
|
+
|
|
143
|
+
<output name='result'>{tab}</output>
|
|
144
|
+
</>
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
render(<Test />, { wrapper: createWrapper() });
|
|
149
|
+
|
|
150
|
+
const button = screen.getByRole("button");
|
|
151
|
+
const output = screen.getByRole("status");
|
|
152
|
+
|
|
153
|
+
expect(output).toHaveValue("3");
|
|
154
|
+
|
|
155
|
+
await userEvent.click(button);
|
|
156
|
+
expect(output).toHaveValue("5");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("dispatch start", () => {
|
|
161
|
+
it("update the state", async () => {
|
|
162
|
+
const Test = () => {
|
|
163
|
+
const dispatch = useTabDispatch();
|
|
164
|
+
const tab = useActiveTab();
|
|
165
|
+
|
|
166
|
+
const start = () => {
|
|
167
|
+
dispatch({ type: "start" });
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<>
|
|
172
|
+
<Tab value={0}>Label 0</Tab>
|
|
173
|
+
<Tab value={3}>Label 3</Tab>
|
|
174
|
+
<Tab value={5}>Label 5</Tab>
|
|
175
|
+
<button onClick={start}>start</button>
|
|
176
|
+
<output name='result'>{tab}</output>
|
|
177
|
+
</>
|
|
178
|
+
);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
render(<Test />, { wrapper: createWrapper() });
|
|
182
|
+
|
|
183
|
+
const button = screen.getByRole("button");
|
|
184
|
+
const output = screen.getByRole("status");
|
|
185
|
+
|
|
186
|
+
expect(output).toHaveValue("3");
|
|
187
|
+
|
|
188
|
+
await userEvent.click(button);
|
|
189
|
+
|
|
190
|
+
expect(output).toHaveValue("0");
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe("dispatch next", () => {
|
|
195
|
+
it("update the state", async () => {
|
|
196
|
+
const Test = () => {
|
|
197
|
+
const dispatch = useTabDispatch();
|
|
198
|
+
const tab = useActiveTab();
|
|
199
|
+
|
|
200
|
+
const next = () => {
|
|
201
|
+
dispatch({ type: "next" });
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<>
|
|
206
|
+
<Tab value={0}>Label 0</Tab>
|
|
207
|
+
<Tab value={3}>Label 3</Tab>
|
|
208
|
+
<Tab value={4}>Label 4</Tab>
|
|
209
|
+
<Tab value={5}>Label 5</Tab>
|
|
210
|
+
<button onClick={next}>next</button>
|
|
211
|
+
<output name='result'>{tab}</output>
|
|
212
|
+
</>
|
|
213
|
+
);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
render(<Test />, { wrapper: createWrapper() });
|
|
217
|
+
|
|
218
|
+
const button = screen.getByRole("button");
|
|
219
|
+
const output = screen.getByRole("status");
|
|
220
|
+
|
|
221
|
+
expect(output).toHaveValue("3");
|
|
222
|
+
|
|
223
|
+
await userEvent.click(button);
|
|
224
|
+
|
|
225
|
+
expect(output).toHaveValue("4");
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe("when current tab is last one", () => {
|
|
229
|
+
it("loop to the first tab", async () => {
|
|
230
|
+
const Test = () => {
|
|
231
|
+
const dispatch = useTabDispatch();
|
|
232
|
+
const tab = useActiveTab();
|
|
233
|
+
|
|
234
|
+
const next = () => {
|
|
235
|
+
dispatch({ type: "next" });
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
return (
|
|
239
|
+
<>
|
|
240
|
+
<Tab value={0}>Label 0</Tab>
|
|
241
|
+
<Tab value={3}>Label 3</Tab>
|
|
242
|
+
<Tab value={4}>Label 4</Tab>
|
|
243
|
+
<Tab value={5}>Label 5</Tab>
|
|
244
|
+
<button onClick={next}>next</button>
|
|
245
|
+
<output name='result'>{tab}</output>
|
|
246
|
+
</>
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
render(<Test />, { wrapper: createWrapper({ selected: 5 }) });
|
|
251
|
+
|
|
252
|
+
const button = screen.getByRole("button");
|
|
253
|
+
const output = screen.getByRole("status");
|
|
254
|
+
|
|
255
|
+
expect(output).toHaveValue("5");
|
|
256
|
+
|
|
257
|
+
await userEvent.click(button);
|
|
258
|
+
|
|
259
|
+
expect(output).toHaveValue("0");
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe("dispatch previous", () => {
|
|
265
|
+
it("update the state", async () => {
|
|
266
|
+
const Test = () => {
|
|
267
|
+
const dispatch = useTabDispatch();
|
|
268
|
+
const tab = useActiveTab();
|
|
269
|
+
|
|
270
|
+
const previous = () => {
|
|
271
|
+
dispatch({ type: "previous" });
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
return (
|
|
275
|
+
<>
|
|
276
|
+
<Tab value={0}>Label 0</Tab>
|
|
277
|
+
<Tab value={2}>Label 2</Tab>
|
|
278
|
+
<Tab value={3}>Label 3</Tab>
|
|
279
|
+
<Tab value={4}>Label 4</Tab>
|
|
280
|
+
<Tab value={5}>Label 5</Tab>
|
|
281
|
+
<button onClick={previous}>previous</button>
|
|
282
|
+
<output name='result'>{tab}</output>
|
|
283
|
+
</>
|
|
284
|
+
);
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
render(<Test />, { wrapper: createWrapper() });
|
|
288
|
+
|
|
289
|
+
const button = screen.getByRole("button");
|
|
290
|
+
const output = screen.getByRole("status");
|
|
291
|
+
|
|
292
|
+
expect(output).toHaveValue("3");
|
|
293
|
+
|
|
294
|
+
await userEvent.click(button);
|
|
295
|
+
|
|
296
|
+
expect(output).toHaveValue("2");
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe("when current tab is first one", () => {
|
|
300
|
+
it("loop to the last tab", async () => {
|
|
301
|
+
const Test = () => {
|
|
302
|
+
const dispatch = useTabDispatch();
|
|
303
|
+
const tab = useActiveTab();
|
|
304
|
+
|
|
305
|
+
const previous = () => {
|
|
306
|
+
dispatch({ type: "previous" });
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
return (
|
|
310
|
+
<>
|
|
311
|
+
<Tab value={0}>Label 0</Tab>
|
|
312
|
+
<Tab value={2}>Label 2</Tab>
|
|
313
|
+
<Tab value={3}>Label 3</Tab>
|
|
314
|
+
<Tab value={4}>Label 4</Tab>
|
|
315
|
+
<Tab value={5}>Label 5</Tab>
|
|
316
|
+
<button onClick={previous}>previous</button>
|
|
317
|
+
<output name='result'>{tab}</output>
|
|
318
|
+
</>
|
|
319
|
+
);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
render(<Test />, { wrapper: createWrapper({ selected: 0 }) });
|
|
323
|
+
|
|
324
|
+
const button = screen.getByRole("button");
|
|
325
|
+
const output = screen.getByRole("status");
|
|
326
|
+
|
|
327
|
+
expect(output).toHaveValue("0");
|
|
328
|
+
|
|
329
|
+
await userEvent.click(button);
|
|
330
|
+
|
|
331
|
+
expect(output).toHaveValue("5");
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
describe("useTabControl()", () => {
|
|
338
|
+
it("retuns the elements registered map", () => {
|
|
339
|
+
const Test = () => {
|
|
340
|
+
const div = document.createElement("div");
|
|
341
|
+
const ref = useRef(div);
|
|
342
|
+
|
|
343
|
+
useRegisterTabControl({ value: 0, ref });
|
|
344
|
+
|
|
345
|
+
const controls = useTabControls();
|
|
346
|
+
|
|
347
|
+
return <output name='result'>{controls.size}</output>;
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
render(<Test />, { wrapper: createWrapper() });
|
|
351
|
+
|
|
352
|
+
const output = screen.getByRole("status");
|
|
353
|
+
|
|
354
|
+
expect(output).toHaveValue("1");
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("allow unregister", async () => {
|
|
358
|
+
const Test = () => {
|
|
359
|
+
const div = document.createElement("div");
|
|
360
|
+
const ref = useRef(div);
|
|
361
|
+
|
|
362
|
+
const { register, unregister } = useContext(TabControlContext);
|
|
363
|
+
const controls = useTabControls();
|
|
364
|
+
|
|
365
|
+
return (
|
|
366
|
+
<>
|
|
367
|
+
<button onClick={() => register(0, ref)}>REGISTER</button>
|
|
368
|
+
<button onClick={() => unregister(0)}>UNREGISTER</button>
|
|
369
|
+
<output name='result'>{controls.size}</output>
|
|
370
|
+
</>
|
|
371
|
+
);
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
render(<Test />, { wrapper: createWrapper() });
|
|
375
|
+
|
|
376
|
+
const register = screen.getByRole("button", { name: "REGISTER" });
|
|
377
|
+
const unregister = screen.getByRole("button", { name: "UNREGISTER" });
|
|
378
|
+
|
|
379
|
+
await userEvent.click(register);
|
|
380
|
+
|
|
381
|
+
expect(screen.getByRole("status")).toHaveValue("1");
|
|
382
|
+
|
|
383
|
+
await userEvent.click(unregister);
|
|
384
|
+
|
|
385
|
+
expect(screen.getByRole("status")).toHaveValue("0");
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type RefObject, useContext, useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
import { TabControlContext } from "../context/TabControl";
|
|
4
|
+
|
|
5
|
+
export const useActiveTab = () => {
|
|
6
|
+
const { value } = useContext(TabControlContext);
|
|
7
|
+
|
|
8
|
+
return value;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const useTabDispatch = () => {
|
|
12
|
+
const { dispatch } = useContext(TabControlContext);
|
|
13
|
+
|
|
14
|
+
return dispatch;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const useTabsUid = () => {
|
|
18
|
+
const { uid } = useContext(TabControlContext);
|
|
19
|
+
|
|
20
|
+
return uid;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
interface Props<T extends HTMLElement> {
|
|
24
|
+
value: number;
|
|
25
|
+
ref: RefObject<T>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function useRegisterTabControl<T extends HTMLElement>({ value, ref }: Props<T>) {
|
|
29
|
+
const { dispatch, register, unregister } = useContext(TabControlContext);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
register(value, ref);
|
|
33
|
+
|
|
34
|
+
return () => {
|
|
35
|
+
unregister(value);
|
|
36
|
+
};
|
|
37
|
+
}, [value, ref, register, unregister]);
|
|
38
|
+
|
|
39
|
+
return dispatch;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const useTabControls = () => {
|
|
43
|
+
const { refs } = useContext(TabControlContext);
|
|
44
|
+
|
|
45
|
+
return refs;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const useActiveTabControl = () => {
|
|
49
|
+
const { value, refs } = useContext(TabControlContext);
|
|
50
|
+
|
|
51
|
+
return [value, refs.get(value)] as const;
|
|
52
|
+
};
|