@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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.1.0",
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.1",
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 = itemIdsWithOldOrder.indexOf(
52
- draggedItemId
53
- );
54
- const newIndexOfDraggedItemId = itemIdsWithNewOrder.indexOf(
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}
@@ -37,6 +37,7 @@ export type ListProps = {
37
37
  onAssetLoadError?: (payload: OnAssetLoadErrorPayload) => void;
38
38
  onDragEnd?: (itemId: string | number, newIndex: number) => void;
39
39
  emptyStateMessage?: string;
40
+ isLoading?: boolean;
40
41
  };
41
42
 
42
43
  export interface Dragula {
@@ -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,5 @@
1
+ @import "../../styles/skeleton";
2
+
3
+ .list-skeleton {
4
+ @include skeleton();
5
+ }
@@ -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-testid or key
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;