@luscii-healthtech/web-ui 2.1.0 → 2.3.1
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/components/Accordion/Accordion.d.ts +10 -0
- package/dist/components/Accordion/AccordionItem.d.ts +9 -0
- package/dist/components/List/List.d.ts +1 -1
- package/dist/components/List/List.types.d.ts +1 -0
- package/dist/components/List/ListItemSkeleton.d.ts +2 -0
- package/dist/components/List/ListSkeleton.d.ts +7 -0
- package/dist/web-ui-tailwind.css +16 -0
- package/dist/web-ui.cjs.development.js +96 -25
- package/dist/web-ui.cjs.development.js.map +1 -1
- package/dist/web-ui.cjs.production.min.js +1 -1
- package/dist/web-ui.cjs.production.min.js.map +1 -1
- package/dist/web-ui.esm.js +96 -25
- package/dist/web-ui.esm.js.map +1 -1
- package/package.json +5 -8
- package/src/components/Accordion/Accordion.tsx +33 -0
- package/src/components/Accordion/AccordionItem.tsx +50 -0
- package/src/components/List/List.tsx +13 -9
- package/src/components/List/List.types.ts +1 -0
- package/src/components/List/ListItemSkeleton.tsx +26 -0
- package/src/components/List/ListSkeleton.scss +5 -0
- package/src/components/List/ListSkeleton.tsx +30 -0
- package/src/styles/_skeleton.scss +63 -0
- package/src/types/general.types.ts +1 -1
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.1
|
|
2
|
+
"version": "2.3.1",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"typings": "dist/index.d.ts",
|
|
@@ -22,17 +22,13 @@
|
|
|
22
22
|
"build-storybook": "build-storybook",
|
|
23
23
|
"chromatic": "chromatic --exit-zero-on-changes",
|
|
24
24
|
"build-tailwind": "NODE_ENV=production yarn run tailwindcss build tailwind.css -o ./dist/web-ui-tailwind.css",
|
|
25
|
-
"test-copy-build": "yarn build && cp -a ./dist/. ../cVitals-Web/node_modules/@luscii-healthtech/web-ui/dist"
|
|
25
|
+
"test-copy-build": "yarn build && cp -a ./dist/. ../cVitals-Web/node_modules/@luscii-healthtech/web-ui/dist",
|
|
26
|
+
"prepare": "husky install"
|
|
26
27
|
},
|
|
27
28
|
"peerDependencies": {
|
|
28
29
|
"react": ">=16",
|
|
29
30
|
"react-tooltip": "^4.2.21"
|
|
30
31
|
},
|
|
31
|
-
"husky": {
|
|
32
|
-
"hooks": {
|
|
33
|
-
"pre-commit": "tsdx lint"
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
32
|
"prettier": {
|
|
37
33
|
"printWidth": 80,
|
|
38
34
|
"semi": true,
|
|
@@ -78,11 +74,12 @@
|
|
|
78
74
|
"css-loader": "^0.28.11",
|
|
79
75
|
"eslint": "^8.18.0",
|
|
80
76
|
"eslint-plugin-chai-friendly": "^0.7.2",
|
|
81
|
-
"husky": "^8.0.
|
|
77
|
+
"husky": "^8.0.0",
|
|
82
78
|
"postcss": "^8.4.14",
|
|
83
79
|
"postcss-loader": "4.2",
|
|
84
80
|
"postcss-url": "^10.1.3",
|
|
85
81
|
"prettier": "^2.7.1",
|
|
82
|
+
"pretty-quick": "^3.1.3",
|
|
86
83
|
"react": "^18.2.0",
|
|
87
84
|
"react-dom": "^18.2.0",
|
|
88
85
|
"react-is": "^18.2.0",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { AccordionItem, AccordionItemProps } from "./AccordionItem";
|
|
4
|
+
|
|
5
|
+
export interface AccordionProps {
|
|
6
|
+
dataTestId?: string;
|
|
7
|
+
items: AccordionItemProps[];
|
|
8
|
+
className?: string;
|
|
9
|
+
isCollapsedByDefault?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const Accordion: React.VFC<AccordionProps> = ({
|
|
13
|
+
dataTestId,
|
|
14
|
+
isCollapsedByDefault = false,
|
|
15
|
+
items,
|
|
16
|
+
className,
|
|
17
|
+
}) => {
|
|
18
|
+
return (
|
|
19
|
+
<ul data-test-id={dataTestId} className={className}>
|
|
20
|
+
{items.map?.((item) => (
|
|
21
|
+
<AccordionItem
|
|
22
|
+
{...item}
|
|
23
|
+
key={item.id}
|
|
24
|
+
isCollapsedByDefault={
|
|
25
|
+
item.isCollapsedByDefault ?? isCollapsedByDefault
|
|
26
|
+
}
|
|
27
|
+
/>
|
|
28
|
+
))}
|
|
29
|
+
</ul>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default Accordion;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React, { useReducer } from "react";
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
|
|
4
|
+
import { ChevronRightIcon } from "../Icons/ChevronRightIcon";
|
|
5
|
+
import { ChevronDownIcon } from "../Icons/ChevronDownIcon";
|
|
6
|
+
import Text from "../Text/Text";
|
|
7
|
+
|
|
8
|
+
export interface AccordionItemProps {
|
|
9
|
+
id: string;
|
|
10
|
+
title: string;
|
|
11
|
+
content: React.ReactNode;
|
|
12
|
+
className?: string;
|
|
13
|
+
isCollapsedByDefault?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const AccordionItem: React.VFC<AccordionItemProps> = ({
|
|
17
|
+
id,
|
|
18
|
+
title,
|
|
19
|
+
content,
|
|
20
|
+
isCollapsedByDefault = false,
|
|
21
|
+
}) => {
|
|
22
|
+
const [isCollapsed, toggleIsCollapsed] = useReducer(
|
|
23
|
+
(state) => !state,
|
|
24
|
+
isCollapsedByDefault
|
|
25
|
+
);
|
|
26
|
+
const Chevron = isCollapsed ? ChevronRightIcon : ChevronDownIcon;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<li
|
|
30
|
+
className={"bg-white border-b last:border-b-0 border-slate-100"}
|
|
31
|
+
data-test-id={id}
|
|
32
|
+
>
|
|
33
|
+
<div
|
|
34
|
+
onClick={toggleIsCollapsed}
|
|
35
|
+
className={classNames(
|
|
36
|
+
"p-4 w-full flex flex-row space-x-4 select-none",
|
|
37
|
+
"cursor-pointer hover:bg-blue-50 transition-colors ease-in-out duration-300",
|
|
38
|
+
{
|
|
39
|
+
"border-b border-slate-100": !isCollapsed,
|
|
40
|
+
}
|
|
41
|
+
)}
|
|
42
|
+
>
|
|
43
|
+
<Chevron className={"text-slate-300"} />
|
|
44
|
+
<Text text={title} type={"lg-strong"} />
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div className={classNames({ hidden: isCollapsed })}>{content}</div>
|
|
48
|
+
</li>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
OnAssetLoadErrorPayload,
|
|
13
13
|
Dragula,
|
|
14
14
|
} from "./List.types";
|
|
15
|
+
import { ListSkeleton } from "./ListSkeleton";
|
|
15
16
|
|
|
16
17
|
export { ListProps, ListItemProps, OnAssetLoadErrorPayload };
|
|
17
18
|
|
|
@@ -23,6 +24,7 @@ export const List = ({
|
|
|
23
24
|
onAssetLoadError,
|
|
24
25
|
onDragEnd,
|
|
25
26
|
emptyStateMessage,
|
|
27
|
+
isLoading,
|
|
26
28
|
}: ListProps): JSX.Element => {
|
|
27
29
|
const listRef = useRef<HTMLUListElement | null>(null);
|
|
28
30
|
const dragulaRef = useRef<Dragula | null>(null);
|
|
@@ -43,17 +45,15 @@ export const List = ({
|
|
|
43
45
|
const draggedItemId = element.dataset["id"];
|
|
44
46
|
|
|
45
47
|
if (listRef.current && draggedItemId) {
|
|
46
|
-
const itemIdsWithOldOrder = items.map(item => item.itemId.toString());
|
|
48
|
+
const itemIdsWithOldOrder = items.map((item) => item.itemId.toString());
|
|
47
49
|
const itemIdsWithNewOrder = Array.from(listRef.current.children)
|
|
48
|
-
.map(child => (child as HTMLElement).dataset["id"])
|
|
50
|
+
.map((child) => (child as HTMLElement).dataset["id"])
|
|
49
51
|
.filter((itemId): itemId is string => !!itemId);
|
|
50
52
|
|
|
51
|
-
const oldIndexOfDraggedItemId =
|
|
52
|
-
draggedItemId
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
draggedItemId
|
|
56
|
-
);
|
|
53
|
+
const oldIndexOfDraggedItemId =
|
|
54
|
+
itemIdsWithOldOrder.indexOf(draggedItemId);
|
|
55
|
+
const newIndexOfDraggedItemId =
|
|
56
|
+
itemIdsWithNewOrder.indexOf(draggedItemId);
|
|
57
57
|
|
|
58
58
|
if (oldIndexOfDraggedItemId !== newIndexOfDraggedItemId) {
|
|
59
59
|
onDragEnd?.(draggedItemId, newIndexOfDraggedItemId);
|
|
@@ -72,6 +72,10 @@ export const List = ({
|
|
|
72
72
|
|
|
73
73
|
const roundTop = !hasHeader || (hasHeader && headerTransparent);
|
|
74
74
|
|
|
75
|
+
if (isLoading) {
|
|
76
|
+
return <ListSkeleton items={items.length} />;
|
|
77
|
+
}
|
|
78
|
+
|
|
75
79
|
return (
|
|
76
80
|
<div data-test-id="list-component">
|
|
77
81
|
{(title || headerButton) && (
|
|
@@ -103,7 +107,7 @@ export const List = ({
|
|
|
103
107
|
)}
|
|
104
108
|
|
|
105
109
|
<ul ref={listRef}>
|
|
106
|
-
{items.map(item => (
|
|
110
|
+
{items.map((item) => (
|
|
107
111
|
<ListItem
|
|
108
112
|
{...item}
|
|
109
113
|
roundTop={roundTop}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export const ListItemSkeleton = (): JSX.Element => {
|
|
4
|
+
return (
|
|
5
|
+
<div className="flex flex-row items-center p-4">
|
|
6
|
+
<div
|
|
7
|
+
className="skeleton-box is-circle mr-2"
|
|
8
|
+
style={{ width: `${32}px`, height: `${32}px` }}
|
|
9
|
+
/>
|
|
10
|
+
<div className="flex flex-col">
|
|
11
|
+
<div
|
|
12
|
+
className="skeleton-box mb-1"
|
|
13
|
+
style={{ width: `${160}px`, height: `${14}px` }}
|
|
14
|
+
/>
|
|
15
|
+
<div
|
|
16
|
+
className="skeleton-box"
|
|
17
|
+
style={{ width: `${110}px`, height: `${14}px` }}
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
<div
|
|
21
|
+
className="skeleton-box is-button ml-auto rounded-full"
|
|
22
|
+
style={{ width: `${24}px`, height: `${24}px` }}
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import "./ListSkeleton.scss";
|
|
4
|
+
import { ListItemSkeleton } from "./ListItemSkeleton";
|
|
5
|
+
|
|
6
|
+
type ListSkeletonProps = {
|
|
7
|
+
items: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const ListSkeleton = ({ items }: ListSkeletonProps): JSX.Element => {
|
|
11
|
+
const skeletonItems = Array.from({ length: items || 5 }, (_, i) => {
|
|
12
|
+
return <ListItemSkeleton key={i} />;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="flex flex-col divide-y divide-slate-200 bg-white border-slate-50 border rounded-lg shadow list-skeleton">
|
|
17
|
+
<div className="flex flex-row items-center px-4 py-2">
|
|
18
|
+
<div
|
|
19
|
+
className="skeleton-box"
|
|
20
|
+
style={{ width: `${160}px`, height: `${14}px` }}
|
|
21
|
+
/>
|
|
22
|
+
<div
|
|
23
|
+
className="skeleton-box is-button ml-auto"
|
|
24
|
+
style={{ width: `${110}px`, height: `${44}px` }}
|
|
25
|
+
/>
|
|
26
|
+
</div>
|
|
27
|
+
{skeletonItems}
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
@mixin skeleton() {
|
|
2
|
+
.skeleton-box {
|
|
3
|
+
display: inline-block;
|
|
4
|
+
height: 1em;
|
|
5
|
+
position: relative;
|
|
6
|
+
overflow: hidden;
|
|
7
|
+
background-color: #cbd5e1; //slate 300
|
|
8
|
+
border-radius: 3px;
|
|
9
|
+
|
|
10
|
+
&::after {
|
|
11
|
+
position: absolute;
|
|
12
|
+
top: 0;
|
|
13
|
+
right: 0;
|
|
14
|
+
bottom: 0;
|
|
15
|
+
left: 0;
|
|
16
|
+
transform: translateX(-100%);
|
|
17
|
+
background-image: linear-gradient(
|
|
18
|
+
90deg,
|
|
19
|
+
rgba(#fff, 0) 0,
|
|
20
|
+
rgba(#fff, 0.2) 20%,
|
|
21
|
+
rgba(#fff, 0.5) 60%,
|
|
22
|
+
rgba(#fff, 0)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
animation: shimmer 800ms infinite;
|
|
26
|
+
content: "";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&.is-circle {
|
|
30
|
+
border-radius: 50%;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&.is-button {
|
|
34
|
+
background-color: #e2e8f0; //secondary button color
|
|
35
|
+
border-radius: 9999px;
|
|
36
|
+
|
|
37
|
+
&::after {
|
|
38
|
+
position: absolute;
|
|
39
|
+
top: 0;
|
|
40
|
+
right: 0;
|
|
41
|
+
bottom: 0;
|
|
42
|
+
left: 0;
|
|
43
|
+
transform: translateX(-100%);
|
|
44
|
+
background-image: linear-gradient(
|
|
45
|
+
90deg,
|
|
46
|
+
rgba(#ddd, 0) 0,
|
|
47
|
+
rgba(#ddd, 0.2) 20%,
|
|
48
|
+
rgba(#ddd, 0.5) 60%,
|
|
49
|
+
rgba(#ddd, 0)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
animation: shimmer 800ms infinite;
|
|
53
|
+
content: "";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@keyframes shimmer {
|
|
58
|
+
100% {
|
|
59
|
+
transform: translateX(100%);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// This type is used to allow for other props to be injected into the component
|
|
2
|
-
// For instance, for a data-
|
|
2
|
+
// For instance, for a data-test-id or key
|
|
3
3
|
export interface RestPropped {
|
|
4
4
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
5
|
[key: string]: any;
|