@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.
Files changed (115) hide show
  1. package/dist/hooks/keyboard.constants.d.ts +38 -0
  2. package/dist/hooks/keyboard.constants.js +7 -0
  3. package/dist/hooks/keyboard.constants.js.map +1 -0
  4. package/dist/hooks/useKeyboardControls.d.ts +12 -0
  5. package/dist/hooks/useKeyboardControls.js +35 -0
  6. package/dist/hooks/useKeyboardControls.js.map +1 -0
  7. package/dist/interfaces/Operation.d.ts +12 -2
  8. package/dist/molecules/button/Button.d.ts +18 -5
  9. package/dist/molecules/button/Button.js +29 -30
  10. package/dist/molecules/button/Button.js.map +1 -1
  11. package/dist/molecules/card/Card.js +7 -5
  12. package/dist/molecules/card/Card.js.map +1 -1
  13. package/dist/molecules/forms/input-tags/InputTags.js +14 -14
  14. package/dist/molecules/forms/input-tags/InputTags.js.map +1 -1
  15. package/dist/molecules/table/Table.d.ts +11 -3
  16. package/dist/molecules/table/Table.js +8 -3
  17. package/dist/molecules/table/Table.js.map +1 -1
  18. package/dist/molecules/table/components/DefaultCellOperations.d.ts +12 -4
  19. package/dist/molecules/table/components/DefaultCellOperations.js +12 -6
  20. package/dist/molecules/table/components/DefaultCellOperations.js.map +1 -1
  21. package/dist/molecules/table/components/DefaultOperationButton.d.ts +12 -4
  22. package/dist/molecules/table/components/DefaultOperationButton.js.map +1 -1
  23. package/dist/molecules/table/hooks/useTable.d.ts +12 -4
  24. package/dist/molecules/table/hooks/useTable.js.map +1 -1
  25. package/dist/molecules/tabs/Tab.d.ts +13 -0
  26. package/dist/molecules/tabs/Tab.js +67 -0
  27. package/dist/molecules/tabs/Tab.js.map +1 -0
  28. package/dist/molecules/tabs/TabList.d.ts +2 -0
  29. package/dist/molecules/tabs/TabList.js +24 -0
  30. package/dist/molecules/tabs/TabList.js.map +1 -0
  31. package/dist/molecules/tabs/TabPanel.d.ts +9 -0
  32. package/dist/molecules/tabs/TabPanel.js +27 -0
  33. package/dist/molecules/tabs/TabPanel.js.map +1 -0
  34. package/dist/molecules/tabs/Tabs.d.ts +4 -16
  35. package/dist/molecules/tabs/Tabs.js +7 -67
  36. package/dist/molecules/tabs/Tabs.js.map +1 -1
  37. package/dist/molecules/tabs/TabsBody.d.ts +1 -0
  38. package/dist/molecules/tabs/TabsBody.js +10 -0
  39. package/dist/molecules/tabs/TabsBody.js.map +1 -0
  40. package/dist/molecules/tabs/TabsLegacy.d.ts +17 -0
  41. package/dist/molecules/tabs/TabsLegacy.js +49 -0
  42. package/dist/molecules/tabs/TabsLegacy.js.map +1 -0
  43. package/dist/molecules/tabs/all.d.ts +5 -0
  44. package/dist/molecules/tabs/all.js +13 -0
  45. package/dist/molecules/tabs/all.js.map +1 -0
  46. package/dist/molecules/tabs/context/TabControl.d.ts +52 -0
  47. package/dist/molecules/tabs/context/TabControl.js +85 -0
  48. package/dist/molecules/tabs/context/TabControl.js.map +1 -0
  49. package/dist/molecules/tabs/hooks/tabControl.d.ts +44 -0
  50. package/dist/molecules/tabs/hooks/tabControl.js +34 -0
  51. package/dist/molecules/tabs/hooks/tabControl.js.map +1 -0
  52. package/dist/organisms/form/actions/FormAction.js.map +1 -0
  53. package/dist/organisms/form/builder/FormEdit.d.ts +3 -1
  54. package/dist/organisms/form/builder/FormEdit.js +38 -35
  55. package/dist/organisms/form/builder/FormEdit.js.map +1 -1
  56. package/dist/organisms/form/builder/useFormEdit.js +17 -17
  57. package/dist/organisms/form/builder/useFormEdit.js.map +1 -1
  58. package/dist/organisms/form/exports/FormExport.d.ts +5 -0
  59. package/dist/organisms/form/exports/FormExport.js +55 -0
  60. package/dist/organisms/form/exports/FormExport.js.map +1 -0
  61. package/dist/organisms/form/preview/FormPreview.d.ts +6 -0
  62. package/dist/organisms/form/preview/FormPreview.js +11 -0
  63. package/dist/organisms/form/preview/FormPreview.js.map +1 -0
  64. package/dist/organisms/table/submissions/SubmissionsTable.d.ts +11 -3
  65. package/dist/organisms/table/submissions/SubmissionsTable.js +4 -1
  66. package/dist/organisms/table/submissions/SubmissionsTable.js.map +1 -1
  67. package/dist/organisms/views/FormViews.d.ts +24 -0
  68. package/dist/organisms/views/FormViews.js +96 -0
  69. package/dist/organisms/views/FormViews.js.map +1 -0
  70. package/package.json +3 -3
  71. package/src/hooks/keyboard.constants.ts +40 -0
  72. package/src/hooks/useKeyboardControls.spec.tsx +208 -0
  73. package/src/hooks/useKeyboardControls.ts +84 -0
  74. package/src/interfaces/Operation.ts +9 -3
  75. package/src/molecules/button/Button.tsx +43 -24
  76. package/src/molecules/card/Card.tsx +4 -0
  77. package/src/molecules/forms/input-tags/InputTags.tsx +1 -1
  78. package/src/molecules/pagination/Pagination.stories.tsx +0 -7
  79. package/src/molecules/table/Table.stories.tsx +34 -1
  80. package/src/molecules/table/Table.tsx +12 -6
  81. package/src/molecules/table/components/DefaultCellOperations.tsx +13 -7
  82. package/src/molecules/table/components/DefaultOperationButton.tsx +5 -4
  83. package/src/molecules/table/hooks/useTable.tsx +5 -5
  84. package/src/molecules/tabs/Tab.tsx +106 -0
  85. package/src/molecules/tabs/TabList.tsx +37 -0
  86. package/src/molecules/tabs/TabPanel.tsx +37 -0
  87. package/src/molecules/tabs/Tabs.spec.tsx +126 -73
  88. package/src/molecules/tabs/Tabs.stories.tsx +298 -65
  89. package/src/molecules/tabs/Tabs.tsx +10 -81
  90. package/src/molecules/tabs/TabsBody.tsx +11 -0
  91. package/src/molecules/tabs/TabsLegacy.stories.tsx +103 -0
  92. package/src/molecules/tabs/TabsLegacy.tsx +84 -0
  93. package/src/molecules/tabs/all.ts +5 -0
  94. package/src/molecules/tabs/context/TabControl.tsx +166 -0
  95. package/src/molecules/tabs/hooks/tabControl.spec.tsx +388 -0
  96. package/src/molecules/tabs/hooks/tabControl.ts +52 -0
  97. package/src/organisms/__fixtures__/form-firstname.fixture.json +1 -0
  98. package/src/organisms/__fixtures__/form.fixture.json +1 -0
  99. package/src/organisms/form/actions/FormAction.stories.tsx +422 -0
  100. package/src/organisms/form/builder/FormEdit.tsx +7 -1
  101. package/src/organisms/form/builder/useFormEdit.ts +1 -1
  102. package/src/organisms/form/exports/FormExport.stories.tsx +71 -0
  103. package/src/organisms/form/exports/FormExport.tsx +58 -0
  104. package/src/organisms/form/preview/FormPreview.stories.tsx +61 -0
  105. package/src/organisms/form/preview/FormPreview.tsx +21 -0
  106. package/src/organisms/table/actions/ActionsTable.stories.tsx +36 -34
  107. package/src/organisms/table/submissions/SubmissionsTable.stories.tsx +103 -57
  108. package/src/organisms/table/submissions/SubmissionsTable.tsx +10 -4
  109. package/src/organisms/views/FormViews.stories.tsx +224 -0
  110. package/src/organisms/views/FormViews.tsx +146 -0
  111. package/dist/organisms/form/action/FormAction.js.map +0 -1
  112. package/src/organisms/form/action/FormAction.stories.tsx +0 -364
  113. /package/dist/organisms/form/{action → actions}/FormAction.d.ts +0 -0
  114. /package/dist/organisms/form/{action → actions}/FormAction.js +0 -0
  115. /package/src/organisms/form/{action → actions}/FormAction.tsx +0 -0
@@ -0,0 +1,106 @@
1
+ import classnames from "classnames";
2
+ import { PropsWithChildren, useCallback, useEffect, useRef } from "react";
3
+
4
+ import { useKeyboardControls } from "../../hooks/useKeyboardControls.js";
5
+ import { registerComponent } from "../../registries/components.js";
6
+ import { iconClass } from "../../utils/iconClass.js";
7
+ import { useActiveTab, useRegisterTabControl, useTabsUid } from "./hooks/tabControl.js";
8
+
9
+ export interface TabProps {
10
+ icon?: string;
11
+ isActive?: boolean;
12
+ className?: string;
13
+ after?: React.ReactNode;
14
+ value: number;
15
+ /**
16
+ * on tab select handler
17
+ */
18
+ onClick?: (value: number) => void;
19
+ }
20
+
21
+ export function Tab({ onClick, icon, value, children, className, after }: PropsWithChildren<TabProps>) {
22
+ const uid = useTabsUid();
23
+ const ref = useRef<HTMLButtonElement>(null);
24
+ const activeTab = useActiveTab();
25
+ const dispatch = useRegisterTabControl({ value, ref });
26
+ const previousActiveTab = useRef(activeTab);
27
+
28
+ const isActive = useRef(activeTab === value);
29
+ isActive.current = activeTab === value;
30
+
31
+ useEffect(() => {
32
+ if (previousActiveTab.current !== activeTab && value === activeTab) {
33
+ ref.current?.focus();
34
+ onClick?.(value);
35
+ }
36
+ if (previousActiveTab.current !== activeTab) {
37
+ previousActiveTab.current = activeTab;
38
+ }
39
+ }, [value, onClick, activeTab]);
40
+
41
+ const start = useCallback(() => {
42
+ dispatch({ type: "start" });
43
+ }, [dispatch]);
44
+
45
+ const end = useCallback(() => {
46
+ dispatch({ type: "end" });
47
+ }, [dispatch]);
48
+
49
+ const up = useCallback(() => {
50
+ dispatch({ type: "previous" });
51
+ }, [dispatch]);
52
+
53
+ const down = useCallback(() => {
54
+ dispatch({ type: "next" });
55
+ }, [dispatch]);
56
+
57
+ const left = useCallback(() => {
58
+ const isRTL = document.documentElement.dir === "rtl";
59
+
60
+ dispatch({ type: isRTL ? "next" : "previous" });
61
+ }, [dispatch]);
62
+
63
+ const right = useCallback(() => {
64
+ const isRTL = document.documentElement.dir === "rtl";
65
+
66
+ dispatch({ type: isRTL ? "previous" : "next" });
67
+ }, [dispatch]);
68
+
69
+ const handleKeyDown = useKeyboardControls({
70
+ start,
71
+ end,
72
+ up,
73
+ down,
74
+ left,
75
+ right
76
+ });
77
+
78
+ const handleClick = useCallback(() => {
79
+ dispatch({ type: "update", payload: value });
80
+ }, [dispatch, value]);
81
+
82
+ return (
83
+ <div title={"button-wrapper"} className={classnames("tw-tabs__button-wrapper", className, { "-active": isActive.current }, className)}>
84
+ <button
85
+ ref={ref}
86
+ id={`Tab_${value}_${uid}`}
87
+ data-name='Tab'
88
+ title='button-tab'
89
+ role='tab'
90
+ aria-selected={isActive.current}
91
+ aria-controls={`TabPanel_${value}_${uid}`}
92
+ tabIndex={isActive.current ? 0 : -1}
93
+ className={"tw-tabs__button"}
94
+ onClick={handleClick}
95
+ onKeyDown={handleKeyDown}
96
+ >
97
+ {icon && <i className={classnames(iconClass(undefined, icon), "tw-tabs__button-icon")} />}
98
+ <span className={"tw-tabs__button-label"}>{children}</span>
99
+ {after}
100
+ </button>
101
+ <div className='tw-tabs__button-border' />
102
+ </div>
103
+ );
104
+ }
105
+
106
+ registerComponent("Tab", Tab);
@@ -0,0 +1,37 @@
1
+ import { Children, isValidElement, PropsWithChildren, ReactNode } from "react";
2
+
3
+ import { registerComponent } from "../../registries/components.js";
4
+
5
+ export function TabList(props: PropsWithChildren<{}>) {
6
+ const before: ReactNode[] = [];
7
+ const after: ReactNode[] = [];
8
+ const items: ReactNode[] = [];
9
+
10
+ Children.forEach(props.children, (child) => {
11
+ if (isValidElement(child)) {
12
+ if ((child.type as any)?.name === "Tab") {
13
+ items.push(child);
14
+ return;
15
+ }
16
+ }
17
+
18
+ if (items.length > 0) {
19
+ after.push(child);
20
+ } else {
21
+ before.push(child);
22
+ }
23
+ });
24
+
25
+ return (
26
+ <nav className='tw-tabs__header'>
27
+ <div className='tw-tabs__header-wrapper'>
28
+ <div className='tw-tabs__header-border' />
29
+ {before}
30
+ <div role='tablist'>{items}</div>
31
+ {after}
32
+ </div>
33
+ </nav>
34
+ );
35
+ }
36
+
37
+ registerComponent("TabList", TabList);
@@ -0,0 +1,37 @@
1
+ import classnames from "classnames";
2
+
3
+ import { registerComponent } from "../../registries/components.js";
4
+ import { useActiveTab, useTabsUid } from "./hooks/tabControl.js";
5
+
6
+ export interface TabPanelProps {
7
+ className?: string;
8
+ /**
9
+ * Tab panel index<br/>
10
+ * _Can be **0** or **1** indexed_
11
+ */
12
+ value: number;
13
+ }
14
+
15
+ export function TabPanel({ value, className, children }: React.PropsWithChildren<TabPanelProps>) {
16
+ const tabSelected = useActiveTab();
17
+ const uid = useTabsUid();
18
+ const isActive = tabSelected === value;
19
+
20
+ return (
21
+ <div
22
+ id={`TabPanel_${value}_${uid}`}
23
+ data-name={`TabPanel_${value}`}
24
+ role='tabpanel'
25
+ aria-hidden={!isActive}
26
+ aria-labelledby={`Tab_${value}_${uid}`}
27
+ tabIndex={isActive ? 0 : -1}
28
+ className={classnames("tw-tabs__panel", className, {
29
+ "-active": isActive
30
+ })}
31
+ >
32
+ {children}
33
+ </div>
34
+ );
35
+ }
36
+
37
+ registerComponent("TabPanel", TabPanel);
@@ -1,87 +1,140 @@
1
- import { fireEvent, render, screen } from "@testing-library/react";
1
+ import { render, screen, waitFor } from "@testing-library/react";
2
+ import { userEvent } from "@testing-library/user-event";
2
3
  import { expect, vi } from "vitest";
3
4
 
4
- import { Tabs } from "./Tabs";
5
- import { Sandbox } from "./Tabs.stories";
5
+ import { Tab } from "./Tab.js";
6
+ import { TabList } from "./TabList.js";
7
+ import { TabPanel } from "./TabPanel.js";
8
+ import { Tabs } from "./Tabs.js";
9
+ import { TabsBody } from "./TabsBody.js";
10
+
11
+ const props = {
12
+ reverse: false,
13
+ items: [
14
+ {
15
+ action: "edit",
16
+ exact: true,
17
+ icon: "edit",
18
+ label: "Edit",
19
+ children: <div className='bg-red-100 p-5'>Edit</div>
20
+ },
21
+ {
22
+ action: "submissions",
23
+ exact: false,
24
+ icon: "data",
25
+ label: "Data",
26
+ children: <div className='bg-orange-100 p-5'>Data</div>
27
+ },
28
+ {
29
+ action: "preview",
30
+ exact: true,
31
+ icon: "test-tube",
32
+ label: "Preview",
33
+ children: <div className='bg-yellow-100 p-5'>Preview</div>
34
+ },
35
+ {
36
+ action: "actions",
37
+ exact: false,
38
+ icon: "paper-plane",
39
+ label: "Actions",
40
+ children: <div className='bg-green-100 p-5'>Actions</div>
41
+ },
42
+ {
43
+ action: "access",
44
+ exact: true,
45
+ icon: "lock",
46
+ label: "Access",
47
+ children: <div className='bg-blue-100 p-5'>Access</div>
48
+ },
49
+ {
50
+ action: "export",
51
+ exact: true,
52
+ icon: "download",
53
+ label: "Export",
54
+ children: <div className='bg-purple-100 p-5'>Export</div>
55
+ },
56
+ {
57
+ action: "delete",
58
+ exact: true,
59
+ icon: "trash",
60
+ label: "Delete",
61
+ roles: ["administrator", "owner"],
62
+ children: <div className='bg-gray-100 p-5'>Trash</div>
63
+ }
64
+ ],
65
+ onClick: vi.fn()
66
+ };
67
+
68
+ function TestComponent({
69
+ selected,
70
+ onClick,
71
+ reverse,
72
+ items
73
+ }: {
74
+ selected?: number;
75
+ onClick?: (item: any) => void;
76
+ reverse?: boolean;
77
+ items: any[];
78
+ }) {
79
+ return (
80
+ <Tabs selected={selected}>
81
+ <TabList>
82
+ {items.map((item, index) => {
83
+ return (
84
+ <Tab onClick={() => onClick?.(item)} key={index} icon={item.icon} value={index} className={reverse ? "-reverse" : ""}>
85
+ {item.label}
86
+ </Tab>
87
+ );
88
+ })}
89
+ </TabList>
90
+ <TabsBody>
91
+ {items.map((item, index) => {
92
+ return (
93
+ <TabPanel key={index} value={index}>
94
+ {item.children}
95
+ </TabPanel>
96
+ );
97
+ })}
98
+ </TabsBody>
99
+ </Tabs>
100
+ );
101
+ }
6
102
 
7
103
  describe("<Tabs>", () => {
8
- it("should display the tabs component and children", () => {
9
- const items = [
10
- {
11
- action: "back",
12
- exact: true,
13
- icon: "chevron-left",
14
- back: true
15
- },
16
- {
17
- action: "edit",
18
- exact: true,
19
- icon: "edit",
20
- label: "Edit"
21
- }
22
- ];
104
+ it("should display the tabs component and children", async () => {
105
+ render(<TestComponent {...props} />);
23
106
 
24
- render(<Tabs {...Sandbox.args} items={items} />);
107
+ // Vérifie que tous les onglets sont présents
108
+ expect(screen.getByRole("tab", { name: "Edit" })).toBeInTheDocument();
109
+ expect(screen.getByRole("tab", { name: "Data" })).toBeInTheDocument();
110
+ expect(screen.getByRole("tab", { name: "Preview" })).toBeInTheDocument();
111
+ expect(screen.getByRole("tab", { name: "Actions" })).toBeInTheDocument();
112
+ expect(screen.getByRole("tab", { name: "Access" })).toBeInTheDocument();
113
+ expect(screen.getByRole("tab", { name: "Export" })).toBeInTheDocument();
114
+ expect(screen.getByRole("tab", { name: "Delete" })).toBeInTheDocument();
25
115
 
26
- const tabsComponent = screen.getByTestId("tabs-comp");
116
+ await waitFor(() => expect(screen.getByRole("tabpanel")).toHaveTextContent("Edit"));
27
117
 
28
- const buttonsTabWrapper = screen.getAllByTitle("button-wrapper");
29
- const chevronLeftButtonTabWrapper = buttonsTabWrapper[0];
30
- const editButtonTabWrapper = buttonsTabWrapper[1];
118
+ // Clique sur l'onglet "Preview" et vérifie le contenu
119
+ await userEvent.click(screen.getByRole("tab", { name: "Preview" }));
31
120
 
32
- const buttonsTab = screen.getAllByTitle("button-tab");
33
- const chevronLeftButtonTab = buttonsTab[0];
34
- const editButtonTab = buttonsTab[1];
121
+ expect(props.onClick).toHaveBeenCalledWith(
122
+ expect.objectContaining({
123
+ action: "preview"
124
+ })
125
+ );
35
126
 
36
- const fontAwsomeChevronLeftIcon = "fa fa-chevron-left";
37
- const fontAwsomeEditIcon = "fa fa-edit";
127
+ await waitFor(() => expect(screen.getByRole("tabpanel")).toHaveTextContent("Preview"));
38
128
 
39
- expect(tabsComponent).toBeInTheDocument();
129
+ // Clique sur l'onglet "Delete" et vérifie le contenu
130
+ await userEvent.click(screen.getByRole("tab", { name: "Delete" }));
40
131
 
41
- expect(chevronLeftButtonTabWrapper).toContainElement(chevronLeftButtonTab);
42
- expect(chevronLeftButtonTabWrapper).toContainHTML("-back");
43
- expect(chevronLeftButtonTab).toBeInTheDocument();
44
- expect(chevronLeftButtonTab).toContainHTML(fontAwsomeChevronLeftIcon);
45
- expect(chevronLeftButtonTab).toHaveTextContent("");
132
+ expect(props.onClick).toHaveBeenCalledWith(
133
+ expect.objectContaining({
134
+ action: "delete"
135
+ })
136
+ );
46
137
 
47
- expect(editButtonTabWrapper).toContainElement(editButtonTab);
48
- expect(editButtonTabWrapper).not.toContainHTML("-back");
49
- expect(editButtonTab).toBeInTheDocument();
50
- expect(editButtonTab).toContainHTML(fontAwsomeEditIcon);
51
- expect(editButtonTab).toHaveTextContent("Edit");
52
- });
53
-
54
- it("should call dispatcher when clicking on a button tab", () => {
55
- const items = [
56
- {
57
- action: "back",
58
- exact: true,
59
- icon: "chevron-left",
60
- back: true
61
- },
62
- {
63
- action: "edit",
64
- exact: true,
65
- icon: "edit",
66
- label: "Edit"
67
- }
68
- ];
69
- const onClick = vi.fn();
70
-
71
- render(<Tabs items={items} onClick={onClick} />);
72
-
73
- const buttonsTab = screen.getAllByTitle("button-tab");
74
- const chevronLeftButtonTab = buttonsTab[0];
75
- const editButtonTab = buttonsTab[1];
76
-
77
- fireEvent.click(chevronLeftButtonTab);
78
-
79
- expect(onClick).toHaveBeenCalledTimes(1);
80
- expect(onClick).toHaveBeenCalledWith(items[0]);
81
-
82
- fireEvent.click(editButtonTab);
83
-
84
- expect(onClick).toHaveBeenCalledTimes(2);
85
- expect(onClick).toHaveBeenCalledWith(items[1]);
138
+ await waitFor(() => expect(screen.getByRole("tabpanel")).toHaveTextContent("Trash"));
86
139
  });
87
140
  });