@luscii-healthtech/web-ui 2.7.0 → 2.9.0
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/CheckBoxListModal/CheckboxListModal.d.ts +32 -0
- package/dist/components/Container/FlexColumn.d.ts +7 -0
- package/dist/components/Container/FlexContainer.d.ts +9 -0
- package/dist/components/Container/FlexRow.d.ts +7 -0
- package/dist/components/Container/types/FlexContainerProps.type.d.ts +15 -0
- package/dist/index.d.ts +4 -0
- package/dist/web-ui-tailwind.css +12 -0
- package/dist/web-ui.cjs.development.js +333 -121
- 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 +331 -122
- package/dist/web-ui.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/CheckBoxListModal/CheckboxListModal.tsx +180 -0
- package/src/components/CheckboxList/CheckboxGroup.tsx +0 -1
- package/src/components/CheckboxList/CheckboxListItem.tsx +45 -41
- package/src/components/Container/FlexColumn.tsx +18 -0
- package/src/components/Container/FlexContainer.tsx +46 -0
- package/src/components/Container/FlexRow.tsx +18 -0
- package/src/components/Container/types/FlexContainerProps.type.ts +18 -0
- package/src/components/List/ListItem.tsx +1 -0
- package/src/components/LoadingIndicator/LoadingIndicator.tsx +1 -0
- package/src/components/Radio/RadioV2.tsx +1 -1
- package/src/index.tsx +9 -0
package/package.json
CHANGED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
import groupBy from "lodash/groupBy";
|
|
5
|
+
|
|
6
|
+
import { CheckboxChangeEvent } from "../CheckboxList/CheckboxList.types";
|
|
7
|
+
import { SecondaryButton } from "../ButtonV2/SecondaryButton";
|
|
8
|
+
import { PrimaryButton } from "../ButtonV2/PrimaryButton";
|
|
9
|
+
import { ModalWithButtons } from "../Modal/ModalWithButtons";
|
|
10
|
+
import LoadingIndicator from "../LoadingIndicator/LoadingIndicator";
|
|
11
|
+
import { SearchInput } from "../Input/SearchInput";
|
|
12
|
+
import Text from "../Text/Text";
|
|
13
|
+
import CheckboxList from "../CheckboxList/CheckboxList";
|
|
14
|
+
|
|
15
|
+
export interface Item {
|
|
16
|
+
id: string;
|
|
17
|
+
label: string;
|
|
18
|
+
isChecked: boolean;
|
|
19
|
+
group?: string;
|
|
20
|
+
isPinned?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface Group {
|
|
24
|
+
title?: string;
|
|
25
|
+
items: Item[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface CheckboxListModalTexts {
|
|
29
|
+
title: string;
|
|
30
|
+
confirmLabel: string;
|
|
31
|
+
cancelLabel: string;
|
|
32
|
+
searchPlaceHolder?: string;
|
|
33
|
+
emptyState: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CheckboxListModalProps {
|
|
37
|
+
texts: CheckboxListModalTexts;
|
|
38
|
+
initialItems: Array<Item>;
|
|
39
|
+
isLoadingInitialItems?: boolean;
|
|
40
|
+
isOpen: boolean;
|
|
41
|
+
onSave: (newItems: Item[]) => void;
|
|
42
|
+
onCloseClick: () => void;
|
|
43
|
+
filterItem: (item: Item, searchQuery: string) => boolean;
|
|
44
|
+
className?: string;
|
|
45
|
+
hasSearch: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const transformToGroups = (items: Item[]): Group[] => {
|
|
49
|
+
const pinned = items.find((option) => option.isPinned);
|
|
50
|
+
|
|
51
|
+
const itemsWithGroup = items.filter((option) => option.group);
|
|
52
|
+
const groups = Object.entries(groupBy(itemsWithGroup, "group")).map(
|
|
53
|
+
(group) => ({
|
|
54
|
+
title: group[0],
|
|
55
|
+
items: group[1].sort((a, b) =>
|
|
56
|
+
a.label > b.label ? 1 : b.label > a.label ? -1 : 0
|
|
57
|
+
),
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const itemsWithoutGroup = items.filter((option) => !option.group);
|
|
62
|
+
const restGroup = {
|
|
63
|
+
name: undefined,
|
|
64
|
+
items: itemsWithoutGroup,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (pinned) {
|
|
68
|
+
const myGroup = {
|
|
69
|
+
items: [pinned],
|
|
70
|
+
};
|
|
71
|
+
return [myGroup, ...groups, restGroup];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return [...groups, restGroup];
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const CheckboxListModal = ({
|
|
78
|
+
texts,
|
|
79
|
+
initialItems,
|
|
80
|
+
isLoadingInitialItems,
|
|
81
|
+
isOpen,
|
|
82
|
+
onSave,
|
|
83
|
+
onCloseClick,
|
|
84
|
+
filterItem = () => true,
|
|
85
|
+
className,
|
|
86
|
+
hasSearch = false,
|
|
87
|
+
}: CheckboxListModalProps): JSX.Element => {
|
|
88
|
+
const containerClassName = classNames(className);
|
|
89
|
+
|
|
90
|
+
const [items, setItems] = useState<Item[]>([]);
|
|
91
|
+
const [visibleItems, setVisibleItems] = useState<Item[]>([]);
|
|
92
|
+
const [groups, setGroups] = useState<Group[]>([]);
|
|
93
|
+
const [search, setSearch] = useState("");
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
setItems(initialItems);
|
|
97
|
+
}, [initialItems]);
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
setVisibleItems(items.filter((item) => filterItem(item, search)));
|
|
101
|
+
}, [items, search]);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
setGroups(transformToGroups(visibleItems));
|
|
105
|
+
}, [visibleItems]);
|
|
106
|
+
|
|
107
|
+
const handleOnItemChange = (event: CheckboxChangeEvent) => {
|
|
108
|
+
const updatedItems = [...items];
|
|
109
|
+
const index = updatedItems.findIndex((item) => item.id === event.id);
|
|
110
|
+
updatedItems[index].isChecked = event.newCheckedValue;
|
|
111
|
+
setItems(updatedItems);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const handleOnCloseClick = (
|
|
115
|
+
event: React.MouseEvent<Element, MouseEvent> | React.KeyboardEvent
|
|
116
|
+
) => {
|
|
117
|
+
event.stopPropagation();
|
|
118
|
+
onCloseClick();
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const handleOnSaveClick = () => {
|
|
122
|
+
onSave(items.filter((item) => item.isChecked));
|
|
123
|
+
onCloseClick();
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const handleOnSearchChange = (event) => {
|
|
127
|
+
setSearch(event.currentTarget.value ?? "");
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const closeButton = (
|
|
131
|
+
<SecondaryButton
|
|
132
|
+
text={texts.cancelLabel}
|
|
133
|
+
onClick={handleOnCloseClick}
|
|
134
|
+
key="close-checkbox-list-modal"
|
|
135
|
+
className="mr-2"
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const saveButton = (
|
|
140
|
+
<PrimaryButton
|
|
141
|
+
text={texts.confirmLabel}
|
|
142
|
+
onClick={handleOnSaveClick}
|
|
143
|
+
key="save-selected-checkbox-list-items"
|
|
144
|
+
/>
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<ModalWithButtons
|
|
149
|
+
isOpen={isOpen}
|
|
150
|
+
title={texts.title}
|
|
151
|
+
onCloseClick={handleOnCloseClick}
|
|
152
|
+
withPadding
|
|
153
|
+
shouldCloseOnOverlayClick
|
|
154
|
+
className={containerClassName}
|
|
155
|
+
buttons={[closeButton, saveButton]}
|
|
156
|
+
buttonsAlignment="justify-end"
|
|
157
|
+
>
|
|
158
|
+
<div className="max-h-135 overflow-y-auto pr-4">
|
|
159
|
+
{isLoadingInitialItems && <LoadingIndicator />}
|
|
160
|
+
|
|
161
|
+
{hasSearch && !isLoadingInitialItems && (
|
|
162
|
+
<SearchInput
|
|
163
|
+
className="w-130 mb-2"
|
|
164
|
+
value={search}
|
|
165
|
+
placeholder={texts.searchPlaceHolder}
|
|
166
|
+
onChange={handleOnSearchChange}
|
|
167
|
+
/>
|
|
168
|
+
)}
|
|
169
|
+
|
|
170
|
+
{!isLoadingInitialItems && items?.length > 0 && (
|
|
171
|
+
<CheckboxList groups={groups} onChange={handleOnItemChange} />
|
|
172
|
+
)}
|
|
173
|
+
|
|
174
|
+
{!isLoadingInitialItems && items?.length === 0 && (
|
|
175
|
+
<Text text={texts.emptyState} />
|
|
176
|
+
)}
|
|
177
|
+
</div>
|
|
178
|
+
</ModalWithButtons>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {useEffect, useState } from "react";
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
2
|
import classNames from "classnames";
|
|
3
3
|
|
|
4
4
|
import { Text } from "../Text/Text";
|
|
@@ -6,45 +6,49 @@ import { Checkbox } from "../Checkbox/Checkbox";
|
|
|
6
6
|
|
|
7
7
|
import { CheckboxGroupItemProps } from "./CheckboxList.types";
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
export const CheckboxListItem = ({
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
10
|
+
id,
|
|
11
|
+
label,
|
|
12
|
+
isChecked,
|
|
13
|
+
onChange,
|
|
14
|
+
className,
|
|
15
|
+
}: CheckboxGroupItemProps): JSX.Element => {
|
|
16
|
+
const [checked, setChecked] = useState(isChecked || false);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
setChecked(isChecked || false);
|
|
20
|
+
}, [isChecked]);
|
|
21
|
+
|
|
22
|
+
const handleItemClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
|
23
|
+
event.stopPropagation();
|
|
24
|
+
|
|
25
|
+
if (onChange) {
|
|
26
|
+
onChange({ id, newCheckedValue: !checked });
|
|
27
|
+
}
|
|
28
|
+
setChecked(!checked);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handleCheckboxClick = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
32
|
+
event.stopPropagation();
|
|
33
|
+
const targetId = event.target.id;
|
|
34
|
+
const newCheckedValue = event.target.checked;
|
|
35
|
+
|
|
36
|
+
if (onChange) {
|
|
37
|
+
onChange({ id: targetId, newCheckedValue });
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
className={classNames(
|
|
44
|
+
"flex flex-row space-between cursor-pointer",
|
|
45
|
+
className
|
|
46
|
+
)}
|
|
47
|
+
item-id={id}
|
|
48
|
+
onClick={handleItemClick}
|
|
49
|
+
>
|
|
50
|
+
<Text text={label} className={"mr-auto"} />
|
|
51
|
+
<Checkbox isChecked={checked} onChange={handleCheckboxClick} id={id} />
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
49
54
|
};
|
|
50
|
-
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { FlexContainer } from "./FlexContainer";
|
|
4
|
+
import { FlexContainerProps } from "./types/FlexContainerProps.type";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Container to be used for layouting instead of divs around the project.
|
|
8
|
+
* The spacing here has been coded according to our guidelines.
|
|
9
|
+
*/
|
|
10
|
+
export const FlexColumn: React.FC<FlexContainerProps> = (props) => {
|
|
11
|
+
const { children, spaced = true, ...rest } = props;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<FlexContainer {...rest} type="column" verticallySpaced={spaced}>
|
|
15
|
+
{children}
|
|
16
|
+
</FlexContainer>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import classNames from "classnames";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
import { FlexContainerBaseProps } from "./types/FlexContainerProps.type";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Container to be used for layouting instead of divs around the project.
|
|
8
|
+
* The spacing here has been coded according to our guidelines.
|
|
9
|
+
*/
|
|
10
|
+
export const FlexContainer: React.FC<
|
|
11
|
+
FlexContainerBaseProps & { type: "column" | "row" }
|
|
12
|
+
> = (props) => {
|
|
13
|
+
const {
|
|
14
|
+
children,
|
|
15
|
+
alignItems,
|
|
16
|
+
verticallySpaced,
|
|
17
|
+
horitontallySpaced,
|
|
18
|
+
justifyContent,
|
|
19
|
+
hasPadding,
|
|
20
|
+
type,
|
|
21
|
+
} = props;
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
className={classNames("flex w-full", {
|
|
26
|
+
"flex-row": type === "row",
|
|
27
|
+
"flex-col": type === "column",
|
|
28
|
+
|
|
29
|
+
"justify-center": justifyContent === "center",
|
|
30
|
+
"justify-start": justifyContent === "start",
|
|
31
|
+
"justify-end": justifyContent === "end",
|
|
32
|
+
"justify-between": justifyContent === "between",
|
|
33
|
+
|
|
34
|
+
"items-start": alignItems === "start",
|
|
35
|
+
"items-center": alignItems === "center",
|
|
36
|
+
"items-end": alignItems === "end",
|
|
37
|
+
|
|
38
|
+
"space-x-4": horitontallySpaced,
|
|
39
|
+
"space-y-4": verticallySpaced,
|
|
40
|
+
"p-4": hasPadding,
|
|
41
|
+
})}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { FlexContainer } from "./FlexContainer";
|
|
4
|
+
import { FlexContainerProps } from "./types/FlexContainerProps.type";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Container to be used for layouting instead of divs around the project.
|
|
8
|
+
* The spacing here has been coded according to our guidelines.
|
|
9
|
+
*/
|
|
10
|
+
export const FlexRow: React.FC<FlexContainerProps> = (props) => {
|
|
11
|
+
const { children, spaced = true, ...rest } = props;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<FlexContainer {...rest} type="row" horitontallySpaced={spaced}>
|
|
15
|
+
{children}
|
|
16
|
+
</FlexContainer>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
type FlexContainerBase = {
|
|
4
|
+
alignItems?: "center" | "start" | "end";
|
|
5
|
+
justifyContent?: "center" | "start" | "end" | "between";
|
|
6
|
+
verticallySpaced?: boolean;
|
|
7
|
+
horitontallySpaced?: boolean;
|
|
8
|
+
hasPadding?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type FlexContainerBaseProps = FlexContainerBase & {
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type FlexContainerProps = Omit<
|
|
16
|
+
FlexContainerBaseProps,
|
|
17
|
+
"verticallySpaced" | "horitontallySpaced"
|
|
18
|
+
> & { spaced?: boolean };
|
|
@@ -33,6 +33,7 @@ export function LoadingIndicator({
|
|
|
33
33
|
<div {...restProps} className={containerClassName}>
|
|
34
34
|
<img
|
|
35
35
|
src={asSpinner ? spinnerToRender : loadingImage}
|
|
36
|
+
data-chromatic="ignore"
|
|
36
37
|
className={classNames("text-gray-600 fill-current stroke-current", {
|
|
37
38
|
"h-4 w-4": asSpinner,
|
|
38
39
|
"h-12 w-12": !asSpinner,
|
|
@@ -8,6 +8,7 @@ import "./RadioV2.css";
|
|
|
8
8
|
export interface RadioProps
|
|
9
9
|
extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
10
10
|
name: string;
|
|
11
|
+
// value field is used by react-hook-form as the value returned
|
|
11
12
|
value: string | number;
|
|
12
13
|
// text shown to the user to explain the option
|
|
13
14
|
text?: string;
|
|
@@ -15,7 +16,6 @@ export interface RadioProps
|
|
|
15
16
|
info?: string;
|
|
16
17
|
isError?: boolean;
|
|
17
18
|
innerRef?: React.Ref<HTMLInputElement>;
|
|
18
|
-
// value field is used by react-hook-form as the value returned
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
function RadioInner({
|
package/src/index.tsx
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export { FlexColumn } from "./components/Container/FlexColumn";
|
|
2
|
+
export { FlexRow } from "./components/Container/FlexRow";
|
|
3
|
+
export type { FlexContainerProps } from "./components/Container/types/FlexContainerProps.type";
|
|
4
|
+
|
|
1
5
|
export {
|
|
2
6
|
default as Toaster,
|
|
3
7
|
TOASTER_TYPE_OPTIONS,
|
|
@@ -49,6 +53,11 @@ export {
|
|
|
49
53
|
CheckboxListProps,
|
|
50
54
|
} from "./components/CheckboxList/CheckboxList";
|
|
51
55
|
|
|
56
|
+
export {
|
|
57
|
+
CheckboxListModal,
|
|
58
|
+
CheckboxListModalProps,
|
|
59
|
+
} from "./components/CheckBoxListModal/CheckboxListModal";
|
|
60
|
+
|
|
52
61
|
export { MultiSelect } from "./components/MultiSelect/MultiSelect";
|
|
53
62
|
export { NavLayout, NavMenuLayoutProps } from "./components/NavMenu/NavLayout";
|
|
54
63
|
export { NavMenu, NavMenuElement } from "./components/NavMenu/NavMenu";
|