@allurereport/web-awesome 3.0.0-beta.3 → 3.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +1 -1
- package/CONTRIBUTING.md +34 -0
- package/dist/multi/{141.app-b6362ca0.js → 141.app-71d7f77e.js} +1 -1
- package/dist/multi/222.app-71d7f77e.js +1 -0
- package/dist/multi/335.app-71d7f77e.js +1 -0
- package/dist/multi/{34.app-b6362ca0.js → 34.app-71d7f77e.js} +1 -1
- package/dist/multi/349.app-71d7f77e.js +1 -0
- package/dist/multi/378.app-71d7f77e.js +1 -0
- package/dist/multi/{406.app-b6362ca0.js → 406.app-71d7f77e.js} +1 -1
- package/dist/multi/476.app-71d7f77e.js +1 -0
- package/dist/multi/{53.app-b6362ca0.js → 53.app-71d7f77e.js} +1 -1
- package/dist/multi/{584.app-b6362ca0.js → 584.app-71d7f77e.js} +1 -1
- package/dist/multi/690.app-71d7f77e.js +1 -0
- package/dist/multi/{747.app-b6362ca0.js → 747.app-71d7f77e.js} +1 -1
- package/dist/multi/{767.app-b6362ca0.js → 767.app-71d7f77e.js} +1 -1
- package/dist/multi/{816.app-b6362ca0.js → 816.app-71d7f77e.js} +1 -1
- package/dist/multi/83.app-71d7f77e.js +1 -0
- package/dist/multi/{873.app-b6362ca0.js → 873.app-71d7f77e.js} +1 -1
- package/dist/multi/{920.app-b6362ca0.js → 920.app-71d7f77e.js} +1 -1
- package/dist/multi/{991.app-b6362ca0.js → 991.app-71d7f77e.js} +1 -1
- package/dist/multi/app-71d7f77e.js +2 -0
- package/dist/multi/manifest.json +20 -20
- package/dist/multi/{styles-b6362ca0.css → styles-71d7f77e.css} +6 -6
- package/dist/single/app-7aa8b012.js +2 -0
- package/dist/single/manifest.json +1 -1
- package/package.json +11 -4
- package/src/assets/scss/_common.scss +9 -0
- package/src/components/app/ArrowButton/index.tsx +3 -2
- package/src/components/app/BaseLayout/index.tsx +5 -5
- package/src/components/app/ReportBody/Filters.tsx +12 -10
- package/src/components/app/ReportBody/HeaderActions.tsx +3 -3
- package/src/components/app/ReportBody/SortBy.tsx +10 -10
- package/src/components/app/ReportBody/context.tsx +0 -1
- package/src/components/app/ReportHeader/index.tsx +1 -1
- package/src/components/app/Tabs/index.tsx +2 -3
- package/src/components/app/TestResult/TestResultDescription/index.tsx +3 -3
- package/src/components/app/TestResult/TestResultNavigation/index.tsx +34 -37
- package/src/components/app/TestResult/TestResultNavigation/styles.scss +1 -1
- package/src/components/app/TestResult/TestResultSteps/attachment.tsx +4 -6
- package/src/components/app/Tree/Tree.tsx +54 -101
- package/src/components/app/Tree/TreeHeader.tsx +13 -12
- package/src/components/app/Tree/TreeItem.tsx +3 -1
- package/src/components/app/Tree/index.tsx +31 -7
- package/src/components/app/Tree/styles.scss +9 -3
- package/src/components/commons/Menu/index.tsx +44 -19
- package/src/components/commons/SearchBox/index.tsx +8 -5
- package/src/components/commons/SuccessRatePieChart/styles.scss +0 -1
- package/src/components/commons/Toggle/index.tsx +3 -2
- package/src/components/commons/Tooltip/index.tsx +3 -3
- package/src/i18n/constants.ts +21 -2
- package/src/i18n/locales/am.json +3 -1
- package/src/i18n/locales/az.json +3 -1
- package/src/i18n/locales/de.json +3 -1
- package/src/i18n/locales/en.json +4 -2
- package/src/i18n/locales/es.json +3 -0
- package/src/i18n/locales/fr.json +3 -1
- package/src/i18n/locales/he.json +3 -1
- package/src/i18n/locales/it.json +3 -1
- package/src/i18n/locales/ja.json +3 -1
- package/src/i18n/locales/ka.json +3 -1
- package/src/i18n/locales/kr.json +3 -1
- package/src/i18n/locales/nl.json +3 -1
- package/src/i18n/locales/pl.json +3 -1
- package/src/i18n/locales/pt.json +3 -1
- package/src/i18n/locales/ru.json +3 -1
- package/src/i18n/locales/sv.json +3 -1
- package/src/i18n/locales/tr.json +3 -1
- package/src/i18n/locales/zh.json +4 -2
- package/src/index.html +1 -0
- package/src/stores/chart.ts +2 -2
- package/src/stores/testResults.ts +26 -4
- package/src/stores/tree.ts +98 -4
- package/src/types/globals.d.ts +6 -1
- package/src/utils/capitalize.ts +5 -3
- package/src/utils/treeFilters.ts +73 -120
- package/test/utils/treeFilters.test.ts +424 -0
- package/types.d.ts +25 -4
- package/vitest.config.ts +12 -0
- package/dist/multi/222.app-b6362ca0.js +0 -1
- package/dist/multi/335.app-b6362ca0.js +0 -1
- package/dist/multi/349.app-b6362ca0.js +0 -1
- package/dist/multi/378.app-b6362ca0.js +0 -1
- package/dist/multi/476.app-b6362ca0.js +0 -1
- package/dist/multi/690.app-b6362ca0.js +0 -1
- package/dist/multi/83.app-b6362ca0.js +0 -1
- package/dist/multi/app-b6362ca0.js +0 -2
- package/dist/single/app-57ae0a60.js +0 -2
- /package/dist/multi/{app-b6362ca0.js.LICENSE.txt → app-71d7f77e.js.LICENSE.txt} +0 -0
- /package/dist/single/{app-57ae0a60.js.LICENSE.txt → app-7aa8b012.js.LICENSE.txt} +0 -0
|
@@ -1,121 +1,74 @@
|
|
|
1
|
-
import type { Statistic
|
|
1
|
+
import type { Statistic } from "@allurereport/core-api";
|
|
2
2
|
import cx from "clsx";
|
|
3
3
|
import type { FunctionComponent } from "preact";
|
|
4
4
|
import { useState } from "preact/hooks";
|
|
5
|
-
import { useReportContentContext } from "@/components/app/ReportBody/context";
|
|
6
5
|
import TreeItem from "@/components/app/Tree/TreeItem";
|
|
7
|
-
import {
|
|
8
|
-
import { PageLoader } from "@/components/commons/PageLoader";
|
|
9
|
-
import { Text } from "@/components/commons/Typography";
|
|
10
|
-
import { useI18n } from "@/stores";
|
|
11
|
-
import { treeStore } from "@/stores/tree";
|
|
12
|
-
import { filterGroups, filterLeaves } from "@/utils/treeFilters";
|
|
6
|
+
import type { AllureAwesomeRecursiveTree, AllureAwesomeStatus } from "../../../../types";
|
|
13
7
|
import TreeHeader from "./TreeHeader";
|
|
14
8
|
import * as styles from "./styles.scss";
|
|
15
9
|
|
|
16
10
|
interface TreeProps {
|
|
17
11
|
statistic?: Statistic;
|
|
18
|
-
|
|
19
|
-
groups?: WithChildren["groups"];
|
|
12
|
+
tree: AllureAwesomeRecursiveTree;
|
|
20
13
|
name?: string;
|
|
21
14
|
root?: boolean;
|
|
22
|
-
statusFilter?:
|
|
15
|
+
statusFilter?: AllureAwesomeStatus;
|
|
23
16
|
}
|
|
24
17
|
|
|
25
|
-
const Tree: FunctionComponent<TreeProps> = ({ statusFilter, root, name,
|
|
18
|
+
const Tree: FunctionComponent<TreeProps> = ({ tree, statusFilter, root, name, statistic }) => {
|
|
26
19
|
const [isOpened, setIsOpen] = useState(statistic === undefined || !!statistic.failed || !!statistic.broken);
|
|
27
|
-
const
|
|
20
|
+
const toggleTree = () => {
|
|
21
|
+
setIsOpen(!isOpened);
|
|
22
|
+
};
|
|
23
|
+
const emptyTree = !tree?.trees?.length && !tree?.leaves?.length;
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
renderLoader={() => <PageLoader />}
|
|
33
|
-
renderData={(treeData) => {
|
|
34
|
-
const reportContext = useReportContentContext();
|
|
35
|
-
const toggleTree = () => {
|
|
36
|
-
setIsOpen(!isOpened);
|
|
37
|
-
};
|
|
38
|
-
const leavesToRender = filterLeaves(leaves, treeData?.leavesById, statusFilter, reportContext);
|
|
39
|
-
const groupsToRender = filterGroups(
|
|
40
|
-
groups,
|
|
41
|
-
treeData?.groupsById,
|
|
42
|
-
treeData?.leavesById,
|
|
43
|
-
statusFilter,
|
|
44
|
-
reportContext,
|
|
45
|
-
);
|
|
46
|
-
if (!groupsToRender.length && !leavesToRender.length) {
|
|
47
|
-
return (
|
|
48
|
-
<div className={styles["tree-list"]}>
|
|
49
|
-
<div className={styles["tree-empty-results"]}>
|
|
50
|
-
<Text className={styles["tree-empty-results-title"]}>{t("no-results")}</Text>
|
|
51
|
-
</div>
|
|
52
|
-
</div>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const treeContent = isOpened && (
|
|
57
|
-
<div
|
|
58
|
-
className={cx({
|
|
59
|
-
[styles["tree-content"]]: true,
|
|
60
|
-
[styles.root]: root,
|
|
61
|
-
})}
|
|
62
|
-
>
|
|
63
|
-
{groupsToRender.map((groupId) => {
|
|
64
|
-
const group = treeData?.groupsById?.[groupId];
|
|
65
|
-
|
|
66
|
-
if (!group) {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return (
|
|
71
|
-
<Tree
|
|
72
|
-
key={group.nodeId}
|
|
73
|
-
name={group.name}
|
|
74
|
-
leaves={group.leaves}
|
|
75
|
-
groups={group.groups}
|
|
76
|
-
statistic={group.statistic}
|
|
77
|
-
statusFilter={statusFilter}
|
|
78
|
-
/>
|
|
79
|
-
);
|
|
80
|
-
})}
|
|
81
|
-
{leavesToRender.map((leafId) => {
|
|
82
|
-
const leaf = treeData?.leavesById?.[leafId];
|
|
25
|
+
if (emptyTree) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
83
28
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
29
|
+
const treeContent = isOpened && (
|
|
30
|
+
<div
|
|
31
|
+
data-testid="tree-content"
|
|
32
|
+
className={cx({
|
|
33
|
+
[styles["tree-content"]]: true,
|
|
34
|
+
[styles.root]: root,
|
|
35
|
+
})}
|
|
36
|
+
>
|
|
37
|
+
{tree?.trees?.map?.((subTree) => (
|
|
38
|
+
<Tree
|
|
39
|
+
key={subTree.nodeId}
|
|
40
|
+
name={subTree.name}
|
|
41
|
+
tree={subTree}
|
|
42
|
+
statistic={subTree.statistic}
|
|
43
|
+
statusFilter={statusFilter}
|
|
44
|
+
/>
|
|
45
|
+
))}
|
|
46
|
+
{tree?.leaves?.map?.((leaf) => (
|
|
47
|
+
<TreeItem
|
|
48
|
+
data-testid="tree-leaf"
|
|
49
|
+
key={leaf.nodeId}
|
|
50
|
+
id={leaf.nodeId}
|
|
51
|
+
name={leaf.name}
|
|
52
|
+
status={leaf.status}
|
|
53
|
+
groupOrder={leaf.groupOrder}
|
|
54
|
+
duration={leaf.duration}
|
|
55
|
+
/>
|
|
56
|
+
))}
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
101
59
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
{treeContent}
|
|
115
|
-
</div>
|
|
116
|
-
);
|
|
117
|
-
}}
|
|
118
|
-
/>
|
|
60
|
+
return (
|
|
61
|
+
<div className={styles.tree}>
|
|
62
|
+
{name && (
|
|
63
|
+
<TreeHeader
|
|
64
|
+
categoryTitle={name}
|
|
65
|
+
isOpened={isOpened}
|
|
66
|
+
toggleTree={toggleTree}
|
|
67
|
+
statistic={statistic}
|
|
68
|
+
/>
|
|
69
|
+
)}
|
|
70
|
+
{treeContent}
|
|
71
|
+
</div>
|
|
119
72
|
);
|
|
120
73
|
};
|
|
121
74
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Statistic, statusesList } from "@allurereport/core-api";
|
|
1
|
+
import { type Statistic, statusesList } from "@allurereport/core-api";
|
|
2
2
|
import { clsx } from "clsx";
|
|
3
|
-
import { FunctionComponent } from "preact";
|
|
3
|
+
import { type FunctionComponent } from "preact";
|
|
4
4
|
import { ArrowButton } from "@/components/app/ArrowButton";
|
|
5
5
|
import { Loadable } from "@/components/commons/Loadable";
|
|
6
6
|
import { Text } from "@/components/commons/Typography";
|
|
7
7
|
import { statsStore } from "@/stores";
|
|
8
|
+
import { treeFiltersStore } from "@/stores/tree";
|
|
8
9
|
import * as styles from "./styles.scss";
|
|
9
10
|
|
|
10
11
|
interface TreeHeaderProps {
|
|
@@ -12,15 +13,14 @@ interface TreeHeaderProps {
|
|
|
12
13
|
categoryTitle: string;
|
|
13
14
|
isOpened: boolean;
|
|
14
15
|
toggleTree: () => void;
|
|
15
|
-
statusFilter?: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const maxWidthTab
|
|
19
|
-
const minWidthTab
|
|
18
|
+
const maxWidthTab = 140;
|
|
19
|
+
const minWidthTab = 46;
|
|
20
20
|
// to make the progress bar more visually responsive for smaller values,
|
|
21
21
|
// we can adjust the formula by adding an offset to stretch the lower part
|
|
22
22
|
// of the logarithmic scale
|
|
23
|
-
const offset
|
|
23
|
+
const offset = 10;
|
|
24
24
|
|
|
25
25
|
const progress = (current: number, total: number) => {
|
|
26
26
|
const logOffset = Math.log(offset);
|
|
@@ -31,10 +31,11 @@ const TreeHeader: FunctionComponent<TreeHeaderProps> = ({
|
|
|
31
31
|
categoryTitle,
|
|
32
32
|
isOpened,
|
|
33
33
|
toggleTree,
|
|
34
|
-
statusFilter = "total",
|
|
35
34
|
statistic,
|
|
36
35
|
...rest
|
|
37
36
|
}) => {
|
|
37
|
+
const { status: statusFilter } = treeFiltersStore.value;
|
|
38
|
+
|
|
38
39
|
return (
|
|
39
40
|
<Loadable
|
|
40
41
|
source={statsStore}
|
|
@@ -49,8 +50,8 @@ const TreeHeader: FunctionComponent<TreeHeaderProps> = ({
|
|
|
49
50
|
value !== undefined && (statusFilter === "total" || (statusFilter === status && value > 0)),
|
|
50
51
|
)
|
|
51
52
|
.map(({ status, value }) => {
|
|
52
|
-
const className = clsx(styles[
|
|
53
|
-
const style = { flexGrow:
|
|
53
|
+
const className = clsx(styles["tree-header-bar-item"], styles[status]);
|
|
54
|
+
const style = { flexGrow: value };
|
|
54
55
|
|
|
55
56
|
return (
|
|
56
57
|
<div key={status} className={className} style={style}>
|
|
@@ -61,9 +62,9 @@ const TreeHeader: FunctionComponent<TreeHeaderProps> = ({
|
|
|
61
62
|
: null;
|
|
62
63
|
|
|
63
64
|
return (
|
|
64
|
-
<div {...rest} className={styles["tree-header"]} onClick={toggleTree}>
|
|
65
|
-
<ArrowButton isOpened={isOpened} />
|
|
66
|
-
<Text size="m" bold className={styles["tree-header-title"]}>
|
|
65
|
+
<div data-testid="tree-header" {...rest} className={styles["tree-header"]} onClick={toggleTree}>
|
|
66
|
+
<ArrowButton data-testid="tree-arrow" isOpened={isOpened} />
|
|
67
|
+
<Text data-testid="tree-header-title" size="m" bold className={styles["tree-header-title"]}>
|
|
67
68
|
{categoryTitle}
|
|
68
69
|
</Text>
|
|
69
70
|
{treeHeaderBar && (
|
|
@@ -10,14 +10,16 @@ interface TreeItemProps {
|
|
|
10
10
|
status: TestStatus;
|
|
11
11
|
duration?: number;
|
|
12
12
|
id: string;
|
|
13
|
+
groupOrder: number;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
export const TreeItem: FunctionComponent<TreeItemProps> = ({ name, status, duration, id, ...rest }) => {
|
|
16
|
+
export const TreeItem: FunctionComponent<TreeItemProps> = ({ name, groupOrder, status, duration, id, ...rest }) => {
|
|
16
17
|
const formattedDuration = formatDuration(duration);
|
|
17
18
|
|
|
18
19
|
return (
|
|
19
20
|
<div {...rest} className={styles["tree-item"]} onClick={() => navigateTo(id)}>
|
|
20
21
|
<TreeItemIcon status={status} />
|
|
22
|
+
<span data-testid="tree-leaf-order" class={styles.order}>#{groupOrder}</span>
|
|
21
23
|
<Text data-testid="tree-leaf-title" className={styles["item-title"]}>
|
|
22
24
|
{name}
|
|
23
25
|
</Text>
|
|
@@ -1,25 +1,29 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEffect } from "preact/hooks";
|
|
2
2
|
import { useTabsContext } from "@/components/app/Tabs";
|
|
3
3
|
import Tree from "@/components/app/Tree/Tree";
|
|
4
|
+
import { Button } from "@/components/commons/Button";
|
|
4
5
|
import { Loadable } from "@/components/commons/Loadable";
|
|
5
6
|
import { PageLoader } from "@/components/commons/PageLoader";
|
|
6
7
|
import { Text } from "@/components/commons/Typography";
|
|
7
8
|
import { useI18n } from "@/stores/locale";
|
|
8
|
-
import { treeStore } from "@/stores/tree";
|
|
9
|
+
import { clearTreeFilters, filteredTree, noTests, noTestsFound, setTreeStatus, treeStore } from "@/stores/tree";
|
|
10
|
+
import type { AllureAwesomeStatus } from "../../../../types";
|
|
9
11
|
import * as styles from "./styles.scss";
|
|
10
12
|
|
|
11
13
|
export const TreeList = () => {
|
|
12
14
|
const { t } = useI18n("empty");
|
|
13
15
|
const { currentTab } = useTabsContext();
|
|
14
16
|
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
setTreeStatus(currentTab as AllureAwesomeStatus);
|
|
19
|
+
}, [currentTab]);
|
|
20
|
+
|
|
15
21
|
return (
|
|
16
22
|
<Loadable
|
|
17
23
|
source={treeStore}
|
|
18
24
|
renderLoader={() => <PageLoader />}
|
|
19
|
-
renderData={(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (!groups && !leaves) {
|
|
25
|
+
renderData={() => {
|
|
26
|
+
if (noTests.value) {
|
|
23
27
|
return (
|
|
24
28
|
<div className={styles["tree-list"]}>
|
|
25
29
|
<div className={styles["tree-empty-results"]}>
|
|
@@ -29,9 +33,29 @@ export const TreeList = () => {
|
|
|
29
33
|
);
|
|
30
34
|
}
|
|
31
35
|
|
|
36
|
+
if (noTestsFound.value) {
|
|
37
|
+
return (
|
|
38
|
+
<div className={styles["tree-list"]}>
|
|
39
|
+
<div className={styles["tree-empty-results"]}>
|
|
40
|
+
<Text tag="p" className={styles["tree-empty-results-title"]}>
|
|
41
|
+
{t("no-tests-found")}
|
|
42
|
+
</Text>
|
|
43
|
+
<Button
|
|
44
|
+
className={styles["tree-empty-results-clear-button"]}
|
|
45
|
+
type="button"
|
|
46
|
+
text={t("clear-filters")}
|
|
47
|
+
size={"s"}
|
|
48
|
+
style={"outline"}
|
|
49
|
+
onClick={() => clearTreeFilters()}
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
32
56
|
return (
|
|
33
57
|
<div className={styles["tree-list"]}>
|
|
34
|
-
<Tree
|
|
58
|
+
<Tree tree={filteredTree.value} statusFilter={currentTab as AllureAwesomeStatus} root />
|
|
35
59
|
</div>
|
|
36
60
|
);
|
|
37
61
|
}}
|
|
@@ -159,12 +159,18 @@
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
.tree-empty-results {
|
|
162
|
-
display: flex;
|
|
163
162
|
padding: 44px 24px;
|
|
164
|
-
align
|
|
165
|
-
justify-content: center;
|
|
163
|
+
text-align: center;
|
|
166
164
|
}
|
|
167
165
|
|
|
168
166
|
.tree-empty-results-title {
|
|
169
167
|
color: var(--on-text-secondary);
|
|
170
168
|
}
|
|
169
|
+
|
|
170
|
+
.tree-empty-results-clear-button {
|
|
171
|
+
margin-top: 4px;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.order {
|
|
175
|
+
user-select: none;
|
|
176
|
+
}
|
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
import { autoUpdate, computePosition, flip, offset, shift } from "@floating-ui/dom";
|
|
2
2
|
import { clsx } from "clsx";
|
|
3
3
|
import type { ComponentChildren, VNode } from "preact";
|
|
4
|
-
import {
|
|
4
|
+
import { createContext } from "preact";
|
|
5
|
+
import { useContext, useEffect, useRef, useState } from "preact/hooks";
|
|
5
6
|
import check from "@/assets/svg/line-general-check.svg";
|
|
6
7
|
import { SvgIcon } from "@/components/commons/SvgIcon";
|
|
7
8
|
import { Text } from "@/components/commons/Typography";
|
|
8
9
|
import * as styles from "./styles.scss";
|
|
9
10
|
|
|
11
|
+
type MenuContextT = {
|
|
12
|
+
setIsOpened: (isOpened: boolean) => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const MenuContext = createContext<MenuContextT | null>(null);
|
|
16
|
+
|
|
17
|
+
export const useMenuContext = () => {
|
|
18
|
+
const context = useContext(MenuContext);
|
|
19
|
+
|
|
20
|
+
if (!context) {
|
|
21
|
+
throw new Error("useMenuContext must be used within a Menu");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return context;
|
|
25
|
+
};
|
|
26
|
+
|
|
10
27
|
export const Menu = (props: {
|
|
11
28
|
children: ComponentChildren;
|
|
12
29
|
isInitialOpened?: boolean;
|
|
@@ -82,9 +99,9 @@ export const Menu = (props: {
|
|
|
82
99
|
}).then(({ x, y }) => {
|
|
83
100
|
if (menuRef.current) {
|
|
84
101
|
Object.assign(menuRef.current.style, {
|
|
85
|
-
left: `${x}px`,
|
|
86
|
-
top: `${y}px`,
|
|
87
|
-
position: "absolute",
|
|
102
|
+
"left": `${x}px`,
|
|
103
|
+
"top": `${y}px`,
|
|
104
|
+
"position": "absolute",
|
|
88
105
|
"z-index": 10,
|
|
89
106
|
});
|
|
90
107
|
}
|
|
@@ -96,20 +113,26 @@ export const Menu = (props: {
|
|
|
96
113
|
}, [menuRef.current, triggerRef.current]);
|
|
97
114
|
|
|
98
115
|
return (
|
|
99
|
-
|
|
100
|
-
{
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
116
|
+
<MenuContext.Provider
|
|
117
|
+
value={{
|
|
118
|
+
setIsOpened,
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
<>
|
|
122
|
+
{typeof menuTrigger === "function" && (
|
|
123
|
+
<MenuTriggerWrapper ref={triggerRef}>
|
|
124
|
+
{menuTrigger({
|
|
125
|
+
isOpened,
|
|
126
|
+
onClick: handleTriggerClick,
|
|
127
|
+
setIsOpened,
|
|
128
|
+
})}
|
|
129
|
+
</MenuTriggerWrapper>
|
|
130
|
+
)}
|
|
131
|
+
<div ref={menuRef}>
|
|
132
|
+
{isOpened && <aside className={clsx(styles.menu, styles[`size-${size}`])}>{children}</aside>}
|
|
133
|
+
</div>
|
|
134
|
+
</>
|
|
135
|
+
</MenuContext.Provider>
|
|
113
136
|
);
|
|
114
137
|
};
|
|
115
138
|
|
|
@@ -124,10 +147,11 @@ type ItemProps = {
|
|
|
124
147
|
rightSlot?: ComponentChildren;
|
|
125
148
|
closeMenuOnClick?: boolean;
|
|
126
149
|
ariaLabel?: string;
|
|
127
|
-
setIsOpened
|
|
150
|
+
setIsOpened?: (isOpened: boolean) => void;
|
|
128
151
|
};
|
|
129
152
|
|
|
130
153
|
Menu.Item = (props: ItemProps) => {
|
|
154
|
+
const { setIsOpened } = useMenuContext();
|
|
131
155
|
const { children, onClick, leadingIcon, rightSlot, ariaLabel, closeMenuOnClick = true } = props;
|
|
132
156
|
const isInteractive = typeof onClick === "function";
|
|
133
157
|
const hasLeadingIcon = typeof leadingIcon === "string";
|
|
@@ -135,6 +159,7 @@ Menu.Item = (props: ItemProps) => {
|
|
|
135
159
|
const handleItemClick = (e: MouseEvent) => {
|
|
136
160
|
if (isInteractive && closeMenuOnClick) {
|
|
137
161
|
e.stopPropagation();
|
|
162
|
+
setIsOpened(false);
|
|
138
163
|
}
|
|
139
164
|
|
|
140
165
|
if (isInteractive) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { clsx } from "clsx";
|
|
2
|
-
import { useState
|
|
2
|
+
import {useEffect, useState} from "preact/hooks";
|
|
3
3
|
import searchIcon from "@/assets/svg/line-general-search-md.svg";
|
|
4
4
|
import closeIcon from "@/assets/svg/line-general-x-close.svg";
|
|
5
5
|
import { useDebouncedCallback } from "@/hooks/useDebouncedCallback";
|
|
@@ -20,25 +20,27 @@ type Props = {
|
|
|
20
20
|
export const SearchBox = (props: Props) => {
|
|
21
21
|
const { placeholder, value, onChange, changeDebounce = 300 } = props;
|
|
22
22
|
const [localValue, setLocalValue] = useState(value);
|
|
23
|
-
|
|
24
23
|
const onChangeDebounced = useDebouncedCallback(onChange, changeDebounce);
|
|
25
|
-
|
|
26
24
|
const handleChange = (e: Event) => {
|
|
27
25
|
const newValue = (e.target as HTMLInputElement).value;
|
|
28
26
|
|
|
29
27
|
setLocalValue(newValue);
|
|
30
28
|
onChangeDebounced(newValue);
|
|
31
29
|
};
|
|
32
|
-
|
|
33
30
|
const handleClear = (e: PointerEvent) => {
|
|
34
31
|
e.preventDefault();
|
|
35
32
|
e.stopPropagation();
|
|
36
33
|
setLocalValue("");
|
|
37
34
|
onChangeDebounced("");
|
|
38
35
|
};
|
|
39
|
-
|
|
40
36
|
const showClear = !!localValue;
|
|
41
37
|
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (localValue !== value) {
|
|
40
|
+
setLocalValue(value);
|
|
41
|
+
}
|
|
42
|
+
}, [value]);
|
|
43
|
+
|
|
42
44
|
return (
|
|
43
45
|
<Text className={styles.inputWrap} type="ui" size="m" tag="div">
|
|
44
46
|
<SvgIcon id={searchIcon.id} size="s" className={styles.leadingIcon} />
|
|
@@ -50,6 +52,7 @@ export const SearchBox = (props: Props) => {
|
|
|
50
52
|
value={localValue}
|
|
51
53
|
name="search"
|
|
52
54
|
autocomplete="off"
|
|
55
|
+
data-testid="search-input"
|
|
53
56
|
/>
|
|
54
57
|
{showClear && (
|
|
55
58
|
<div className={styles.clearButton}>
|
|
@@ -8,7 +8,7 @@ type Props = {
|
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
export const Toggle = (props: Props) => {
|
|
11
|
-
const { value, label, onChange, focusable = true } = props;
|
|
11
|
+
const { value, label, onChange, focusable = true, ...rest } = props;
|
|
12
12
|
|
|
13
13
|
const handleChange = (e: Event) => {
|
|
14
14
|
const newValue = !(e.target as HTMLInputElement).checked;
|
|
@@ -17,13 +17,14 @@ export const Toggle = (props: Props) => {
|
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
19
|
<input
|
|
20
|
+
{...rest}
|
|
20
21
|
tabIndex={focusable ? 0 : -1}
|
|
21
22
|
className={styles.toggle}
|
|
22
23
|
role="switch"
|
|
23
24
|
type="checkbox"
|
|
24
25
|
checked={value}
|
|
25
26
|
aria-label={label}
|
|
26
|
-
|
|
27
|
+
onToggle={handleChange}
|
|
27
28
|
/>
|
|
28
29
|
);
|
|
29
30
|
};
|
|
@@ -49,9 +49,9 @@ export const TooltipWrapper: FunctionalComponent<TooltipWrapperProps> = ({
|
|
|
49
49
|
}).then(({ x, y }) => {
|
|
50
50
|
if (tooltipRef.current) {
|
|
51
51
|
Object.assign(tooltipRef.current.style, {
|
|
52
|
-
left: `${x}px`,
|
|
53
|
-
top: `${y}px`,
|
|
54
|
-
position: "absolute",
|
|
52
|
+
"left": `${x}px`,
|
|
53
|
+
"top": `${y}px`,
|
|
54
|
+
"position": "absolute",
|
|
55
55
|
"z-index": 100,
|
|
56
56
|
});
|
|
57
57
|
}
|
package/src/i18n/constants.ts
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
|
-
export const AVAILABLE_LOCALES = [
|
|
1
|
+
export const AVAILABLE_LOCALES = [
|
|
2
|
+
"en",
|
|
3
|
+
"ru",
|
|
4
|
+
"pl",
|
|
5
|
+
"es",
|
|
6
|
+
"pt",
|
|
7
|
+
"de",
|
|
8
|
+
"am",
|
|
9
|
+
"az",
|
|
10
|
+
"fr",
|
|
11
|
+
"it",
|
|
12
|
+
"ja",
|
|
13
|
+
"he",
|
|
14
|
+
"ka",
|
|
15
|
+
"kr",
|
|
16
|
+
"nl",
|
|
17
|
+
"sv",
|
|
18
|
+
"tr",
|
|
19
|
+
"zh",
|
|
20
|
+
] as const;
|
|
2
21
|
|
|
3
22
|
export const DEFAULT_LOCALE = "en";
|
|
4
23
|
|
|
@@ -101,5 +120,5 @@ export const LANG_LOCALE: Record<
|
|
|
101
120
|
short: "Zh",
|
|
102
121
|
full: "中文",
|
|
103
122
|
iso: "zh-CN",
|
|
104
|
-
}
|
|
123
|
+
},
|
|
105
124
|
};
|
package/src/i18n/locales/am.json
CHANGED
|
@@ -60,7 +60,9 @@
|
|
|
60
60
|
"status-desc-short": "Հակադարձված"
|
|
61
61
|
},
|
|
62
62
|
"empty": {
|
|
63
|
-
"no-results": "
|
|
63
|
+
"no-results": "Ոչ մի արդյունք",
|
|
64
|
+
"no-tests-found": "Արդյունքներ չեն գտնվել",
|
|
65
|
+
"clear-filters": "Մաքրել ֆիլտրները",
|
|
64
66
|
"no-attachments-results": "Կցորդների մասին տեղեկություններ չկան",
|
|
65
67
|
"no-history-results": "Պատմության մասին տեղեկություններ չկան",
|
|
66
68
|
"no-retries-results": "Կրկնությունների մասին տեղեկություններ չկան"
|
package/src/i18n/locales/az.json
CHANGED
|
@@ -61,6 +61,8 @@
|
|
|
61
61
|
},
|
|
62
62
|
"empty": {
|
|
63
63
|
"no-results": "Nəticə tapılmadı",
|
|
64
|
+
"no-tests-found": "Nəticə tapılmadı",
|
|
65
|
+
"clear-filters": "Filtrləri təmizlə",
|
|
64
66
|
"no-attachments-results": "Əlavə məlumatı mövcud deyil",
|
|
65
67
|
"no-history-results": "Tarixçə məlumatı mövcud deyil",
|
|
66
68
|
"no-retries-results": "Təkrar məlumatı mövcud deyil"
|
|
@@ -112,4 +114,4 @@
|
|
|
112
114
|
"errors": {
|
|
113
115
|
"missedAttachment": "Əlavə tapılmadı"
|
|
114
116
|
}
|
|
115
|
-
}
|
|
117
|
+
}
|
package/src/i18n/locales/de.json
CHANGED
|
@@ -60,7 +60,9 @@
|
|
|
60
60
|
"status-desc-short": "Umgekehrt"
|
|
61
61
|
},
|
|
62
62
|
"empty": {
|
|
63
|
-
"no-results": "Keine Ergebnisse
|
|
63
|
+
"no-results": "Keine Ergebnisse",
|
|
64
|
+
"no-tests-found": "Keine Ergebnisse gefunden",
|
|
65
|
+
"clear-filters": "Filter löschen",
|
|
64
66
|
"no-attachments-results": "Keine Anhängeinformationen verfügbar",
|
|
65
67
|
"no-history-results": "Keine Verlaufsinformationen verfügbar",
|
|
66
68
|
"no-retries-results": "Keine Wiederholungsinformationen verfügbar"
|
package/src/i18n/locales/en.json
CHANGED
|
@@ -60,7 +60,9 @@
|
|
|
60
60
|
"status-desc-short": "Reversed"
|
|
61
61
|
},
|
|
62
62
|
"empty": {
|
|
63
|
-
"no-results": "No results
|
|
63
|
+
"no-results": "No results",
|
|
64
|
+
"no-tests-found": "No results found",
|
|
65
|
+
"clear-filters": "Clear filters",
|
|
64
66
|
"no-attachments-results": "No attachments information available",
|
|
65
67
|
"no-history-results": "No history information available",
|
|
66
68
|
"no-retries-results": "No retries information available"
|
|
@@ -112,4 +114,4 @@
|
|
|
112
114
|
"errors": {
|
|
113
115
|
"missedAttachment": "Attachment not found"
|
|
114
116
|
}
|
|
115
|
-
}
|
|
117
|
+
}
|
package/src/i18n/locales/es.json
CHANGED
|
@@ -60,6 +60,9 @@
|
|
|
60
60
|
"status-desc-short": "Invertido"
|
|
61
61
|
},
|
|
62
62
|
"empty": {
|
|
63
|
+
"no-results": "Sin resultados",
|
|
64
|
+
"no-tests-found": "No se encontraron resultados",
|
|
65
|
+
"clear-filters": "Limpiar filtros",
|
|
63
66
|
"no-attachments-results": "No hay información de adjuntos disponible",
|
|
64
67
|
"no-history-results": "No hay información de historial disponible",
|
|
65
68
|
"no-retries-results": "No hay información de reintentos disponible"
|
package/src/i18n/locales/fr.json
CHANGED
|
@@ -60,7 +60,9 @@
|
|
|
60
60
|
"status-desc-short": "Inversé"
|
|
61
61
|
},
|
|
62
62
|
"empty": {
|
|
63
|
-
"no-results": "Aucun résultat
|
|
63
|
+
"no-results": "Aucun résultat",
|
|
64
|
+
"no-tests-found": "Aucun résultat trouvé",
|
|
65
|
+
"clear-filters": "Effacer les filtres",
|
|
64
66
|
"no-attachments-results": "Aucune information sur les pièces jointes disponible",
|
|
65
67
|
"no-history-results": "Aucune information sur l'historique disponible",
|
|
66
68
|
"no-retries-results": "Aucune information sur les réessais disponible"
|