@luscii-healthtech/web-ui 2.0.0 → 2.3.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/Accordion/Accordion.d.ts +10 -0
- package/dist/components/Accordion/AccordionItem.d.ts +9 -0
- package/dist/components/Form/Form.d.ts +9 -0
- package/dist/components/Form/FormFieldDecorator.d.ts +8 -0
- package/dist/components/Form/FormInput.d.ts +3 -0
- package/dist/components/Form/FormRadioGroup.d.ts +3 -0
- package/dist/components/Form/FormSelect.d.ts +3 -0
- package/dist/components/Form/form.transformer.d.ts +20 -0
- package/dist/components/Form/form.types.d.ts +54 -0
- package/dist/components/Input/Input.d.ts +8 -7
- package/dist/components/Input/SearchInput.d.ts +1 -1
- 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/components/Radio/Radio.d.ts +3 -0
- package/dist/components/Radio/RadioV2.d.ts +17 -0
- package/dist/components/RadioGroup/RadioGroup.d.ts +3 -0
- package/dist/components/RadioGroup/RadioGroupV2.d.ts +9 -0
- package/dist/components/Select/Select.d.ts +3 -0
- package/dist/components/Select/SelectV2.d.ts +31 -0
- package/dist/index.d.ts +2 -0
- package/dist/web-ui-tailwind.css +50 -0
- package/dist/web-ui.cjs.development.js +604 -40
- 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 +604 -41
- package/dist/web-ui.esm.js.map +1 -1
- package/package.json +6 -3
- package/src/components/Accordion/Accordion.tsx +33 -0
- package/src/components/Accordion/AccordionItem.tsx +50 -0
- package/src/components/Form/Form.tsx +106 -0
- package/src/components/Form/FormFieldDecorator.tsx +66 -0
- package/src/components/Form/FormInput.tsx +47 -0
- package/src/components/Form/FormRadioGroup.tsx +23 -0
- package/src/components/Form/FormSelect.tsx +32 -0
- package/src/components/Form/form.transformer.ts +9 -0
- package/src/components/Form/form.types.ts +132 -0
- package/src/components/Input/Input.tsx +160 -165
- package/src/components/Input/SearchInput.tsx +13 -3
- 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/components/Radio/Radio.js +3 -0
- package/src/components/Radio/RadioV2.css +15 -0
- package/src/components/Radio/RadioV2.tsx +87 -0
- package/src/components/RadioGroup/RadioGroup.js +3 -0
- package/src/components/RadioGroup/RadioGroupV2.tsx +35 -0
- package/src/components/Select/Select.tsx +38 -12
- package/src/components/Select/SelectV2.tsx +171 -0
- package/src/index.tsx +3 -0
- package/src/styles/_skeleton.scss +63 -0
- package/src/types/general.types.ts +1 -1
- package/src/components/Select/Select.examples.md +0 -161
|
@@ -1,24 +1,15 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
HTMLInputTypeAttribute,
|
|
3
|
-
useEffect,
|
|
4
|
-
useRef,
|
|
5
|
-
useState,
|
|
6
|
-
} from "react";
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
7
2
|
import classNames from "classnames";
|
|
8
3
|
|
|
9
4
|
import { CrossIcon } from "../Icons/CrossIcon";
|
|
5
|
+
import { AllowedTextInputTypes } from "../Form/form.types";
|
|
10
6
|
import { IconProps } from "../Icons/types/IconProps.type";
|
|
11
7
|
|
|
12
8
|
import "./Input.css";
|
|
13
9
|
|
|
14
|
-
type AllowedInputTypes = Extract<
|
|
15
|
-
HTMLInputTypeAttribute,
|
|
16
|
-
"email" | "number" | "password" | "text"
|
|
17
|
-
>;
|
|
18
|
-
|
|
19
10
|
// FIXME: something in the tsdx.js build doesn't allow me to use `Uppercase<AllowedInputTypes>`
|
|
20
11
|
// Don't know why yet but it can be fixed later.
|
|
21
|
-
export const INPUT_TYPES: Record<string,
|
|
12
|
+
export const INPUT_TYPES: Record<string, AllowedTextInputTypes> = {
|
|
22
13
|
EMAIL: "email",
|
|
23
14
|
NUMBER: "number",
|
|
24
15
|
PASSWORD: "password",
|
|
@@ -29,7 +20,10 @@ export const INPUT_TYPES: Record<string, AllowedInputTypes> = {
|
|
|
29
20
|
* It's very complicated to tap into the onChange events of inputs,
|
|
30
21
|
* so I omit the type it requires and add a easier one to deal with.
|
|
31
22
|
*/
|
|
32
|
-
type CustomHTMLInputProps = Omit<
|
|
23
|
+
type CustomHTMLInputProps = Omit<
|
|
24
|
+
React.HTMLProps<HTMLInputElement>,
|
|
25
|
+
"onChange"
|
|
26
|
+
> & { isError?: boolean };
|
|
33
27
|
|
|
34
28
|
type FakeEventTarget = {
|
|
35
29
|
target: {
|
|
@@ -43,174 +37,175 @@ type FakeEventTarget = {
|
|
|
43
37
|
};
|
|
44
38
|
|
|
45
39
|
export interface InputProps extends CustomHTMLInputProps {
|
|
46
|
-
type?:
|
|
40
|
+
type?: AllowedTextInputTypes;
|
|
47
41
|
clearable?: boolean;
|
|
48
42
|
isDisabled?: boolean;
|
|
49
43
|
withPrefix?: string;
|
|
50
44
|
withSuffix?: string;
|
|
51
45
|
icon?: React.VoidFunctionComponent<IconProps>;
|
|
52
46
|
onChange?: (event: FakeEventTarget) => void;
|
|
53
|
-
// FIXME: this is used in the SearchAndFilterBar in Vitals, but we can use forwardRef instead
|
|
54
|
-
// and lose this function later.
|
|
55
|
-
setRef?: (refTarget: HTMLInputElement) => void;
|
|
56
47
|
}
|
|
57
48
|
|
|
58
|
-
const Input
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
setInnerValue(value);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
"
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
49
|
+
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
50
|
+
(
|
|
51
|
+
{
|
|
52
|
+
withSuffix = "",
|
|
53
|
+
withPrefix = "",
|
|
54
|
+
className,
|
|
55
|
+
clearable = false,
|
|
56
|
+
type = "text",
|
|
57
|
+
isDisabled = false,
|
|
58
|
+
icon,
|
|
59
|
+
name,
|
|
60
|
+
value = "",
|
|
61
|
+
onChange,
|
|
62
|
+
isError,
|
|
63
|
+
...otherProps
|
|
64
|
+
},
|
|
65
|
+
ref
|
|
66
|
+
) => {
|
|
67
|
+
const hasNoExtraContent = withPrefix === "" && withSuffix === "";
|
|
68
|
+
const [innerValue, setInnerValue] = useState(value);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
setInnerValue(value);
|
|
72
|
+
}, [value]);
|
|
73
|
+
|
|
74
|
+
const clearField = () => {
|
|
75
|
+
setInnerValue("");
|
|
76
|
+
|
|
77
|
+
// This allows backwards compatibility with our usages of the input
|
|
78
|
+
onChange?.({
|
|
79
|
+
target: {
|
|
80
|
+
name: name || "",
|
|
81
|
+
value: "",
|
|
82
|
+
},
|
|
83
|
+
currentTarget: {
|
|
84
|
+
name: name || "",
|
|
85
|
+
value: "",
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const handleChangeEvent = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
91
|
+
setInnerValue(event.currentTarget.value);
|
|
92
|
+
|
|
93
|
+
// This allows backwards compatibility with our usages of the input
|
|
94
|
+
onChange?.({
|
|
95
|
+
target: {
|
|
96
|
+
name: name || "",
|
|
97
|
+
value: event.currentTarget.value,
|
|
98
|
+
},
|
|
99
|
+
currentTarget: {
|
|
100
|
+
name: name || "",
|
|
101
|
+
value: event.currentTarget.value,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const isClearIconVisible = clearable && value;
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div
|
|
110
|
+
data-test-id="input-component-container"
|
|
111
|
+
className="relative inline-block"
|
|
112
|
+
>
|
|
113
|
+
<div className="flex flex-row">
|
|
114
|
+
<span
|
|
115
|
+
className={classNames(
|
|
116
|
+
"h-11 border-t border-b border-solid border-input-border",
|
|
117
|
+
{
|
|
118
|
+
hidden: hasNoExtraContent,
|
|
119
|
+
"order-1": withPrefix !== "",
|
|
120
|
+
"order-2": withSuffix !== "",
|
|
121
|
+
"rounded-l border-l": withPrefix !== "",
|
|
122
|
+
"rounded-r border-r": withSuffix !== "",
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
"flex flex-col items-center p-3",
|
|
126
|
+
"text-sm",
|
|
127
|
+
"text-slate-500",
|
|
128
|
+
"bg-main-background"
|
|
129
|
+
)}
|
|
130
|
+
>
|
|
131
|
+
{withSuffix || withPrefix}
|
|
132
|
+
</span>
|
|
133
|
+
{icon && (
|
|
134
|
+
<div
|
|
135
|
+
className="absolute top-1/2 left-4"
|
|
136
|
+
style={{
|
|
137
|
+
transform: "translateY(-50%)",
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
{React.createElement(icon, {
|
|
141
|
+
className: "w-6 h-6 text-slate-300 ",
|
|
142
|
+
})}
|
|
143
|
+
</div>
|
|
135
144
|
)}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
<input
|
|
146
|
+
{...otherProps}
|
|
147
|
+
data-test-id="input-component"
|
|
148
|
+
name={name}
|
|
149
|
+
value={innerValue}
|
|
150
|
+
onChange={handleChangeEvent}
|
|
151
|
+
ref={ref}
|
|
152
|
+
size={otherProps.maxLength}
|
|
153
|
+
type={type}
|
|
154
|
+
disabled={isDisabled}
|
|
155
|
+
className={classNames(
|
|
156
|
+
"input",
|
|
157
|
+
"block",
|
|
158
|
+
{ "pl-12": icon, "pr-11": clearable },
|
|
159
|
+
{
|
|
160
|
+
"text-slate-400 bg-slate-50": isDisabled,
|
|
161
|
+
"text-slate-700": !isDisabled,
|
|
162
|
+
"border-input-border": !isDisabled && !isError,
|
|
163
|
+
"hover:border-input-border-dark": !isDisabled,
|
|
164
|
+
"border-red-700": isError,
|
|
165
|
+
"focus:outline-negative": isError,
|
|
166
|
+
"focus:border-blue-800": !isError,
|
|
167
|
+
"focus:outline-primary": !isError,
|
|
168
|
+
"bg-white": !isDisabled,
|
|
169
|
+
},
|
|
170
|
+
"h-11",
|
|
171
|
+
"p-2",
|
|
172
|
+
"border",
|
|
173
|
+
"text-sm",
|
|
174
|
+
"placeholder-slate-500",
|
|
175
|
+
"border-solid",
|
|
176
|
+
|
|
177
|
+
"transition-colors",
|
|
178
|
+
"duration-300",
|
|
179
|
+
{
|
|
180
|
+
"z-10": withSuffix !== "" || withPrefix !== "", // to make sure the outline is displayed completely
|
|
181
|
+
rounded: hasNoExtraContent,
|
|
182
|
+
"rounded-l": withSuffix !== "",
|
|
183
|
+
"rounded-r": withPrefix !== "",
|
|
184
|
+
"order-2": withPrefix !== "",
|
|
185
|
+
"order-1": withSuffix !== "",
|
|
186
|
+
},
|
|
187
|
+
"shadow-default",
|
|
188
|
+
className
|
|
189
|
+
)}
|
|
190
|
+
/>
|
|
140
191
|
<div
|
|
141
|
-
className="absolute top-1/2
|
|
192
|
+
className="absolute right-0 top-1/2"
|
|
142
193
|
style={{
|
|
143
194
|
transform: "translateY(-50%)",
|
|
144
195
|
}}
|
|
145
196
|
>
|
|
146
|
-
|
|
147
|
-
className
|
|
148
|
-
|
|
197
|
+
<CrossIcon
|
|
198
|
+
className={classNames(
|
|
199
|
+
"w-6 h-6 mr-3 cursor-pointer text-slate-300",
|
|
200
|
+
{ block: isClearIconVisible, hidden: !isClearIconVisible }
|
|
201
|
+
)}
|
|
202
|
+
onClick={clearField}
|
|
203
|
+
/>
|
|
149
204
|
</div>
|
|
150
|
-
)}
|
|
151
|
-
<input
|
|
152
|
-
{...otherProps}
|
|
153
|
-
data-test-id="input-component"
|
|
154
|
-
name={name}
|
|
155
|
-
value={innerValue}
|
|
156
|
-
onChange={handleChangeEvent}
|
|
157
|
-
ref={element => {
|
|
158
|
-
inputRef.current = element;
|
|
159
|
-
element && setRef?.(element);
|
|
160
|
-
}}
|
|
161
|
-
size={otherProps.maxLength}
|
|
162
|
-
type={type}
|
|
163
|
-
disabled={isDisabled}
|
|
164
|
-
className={classNames(
|
|
165
|
-
"input",
|
|
166
|
-
"block",
|
|
167
|
-
{ "pl-12": icon, "pr-11": clearable },
|
|
168
|
-
{
|
|
169
|
-
"text-slate-400 bg-slate-50": isDisabled,
|
|
170
|
-
"text-slate-700": !isDisabled,
|
|
171
|
-
"border-input-border": !isDisabled,
|
|
172
|
-
"hover:border-input-border-dark": !isDisabled,
|
|
173
|
-
"bg-white": !isDisabled,
|
|
174
|
-
},
|
|
175
|
-
"h-11",
|
|
176
|
-
"p-2",
|
|
177
|
-
"border",
|
|
178
|
-
"text-sm",
|
|
179
|
-
"placeholder-slate-500",
|
|
180
|
-
"border-solid",
|
|
181
|
-
"focus:outline-primary",
|
|
182
|
-
"transition-colors",
|
|
183
|
-
"duration-300",
|
|
184
|
-
"focus:border-blue-800",
|
|
185
|
-
{
|
|
186
|
-
"z-10": withSuffix !== "" || withPrefix !== "", // to make sure the outline is displayed completely
|
|
187
|
-
rounded: hasNoExtraContent,
|
|
188
|
-
"rounded-l": withSuffix !== "",
|
|
189
|
-
"rounded-r": withPrefix !== "",
|
|
190
|
-
"order-2": withPrefix !== "",
|
|
191
|
-
"order-1": withSuffix !== "",
|
|
192
|
-
},
|
|
193
|
-
"shadow-default",
|
|
194
|
-
className
|
|
195
|
-
)}
|
|
196
|
-
/>
|
|
197
|
-
<div
|
|
198
|
-
className="absolute right-0 top-1/2"
|
|
199
|
-
style={{
|
|
200
|
-
transform: "translateY(-50%)",
|
|
201
|
-
}}
|
|
202
|
-
>
|
|
203
|
-
<CrossIcon
|
|
204
|
-
className={classNames(
|
|
205
|
-
"w-6 h-6 mr-3 cursor-pointer text-slate-300",
|
|
206
|
-
{ block: isClearIconVisible, hidden: !isClearIconVisible }
|
|
207
|
-
)}
|
|
208
|
-
onClick={clearField}
|
|
209
|
-
/>
|
|
210
205
|
</div>
|
|
211
206
|
</div>
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
);
|
|
215
210
|
|
|
216
211
|
export default Input;
|
|
@@ -9,6 +9,16 @@ export type SearchInputProps = Omit<
|
|
|
9
9
|
"icon" | "withSuffix" | "withPrefix" | "type" | "clearable"
|
|
10
10
|
>;
|
|
11
11
|
|
|
12
|
-
export const SearchInput
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
export const SearchInput = React.forwardRef<HTMLInputElement, SearchInputProps>(
|
|
13
|
+
(props, ref) => {
|
|
14
|
+
return (
|
|
15
|
+
<Input
|
|
16
|
+
{...props}
|
|
17
|
+
icon={SearchIcon}
|
|
18
|
+
type="text"
|
|
19
|
+
clearable={true}
|
|
20
|
+
ref={ref}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
);
|
|
@@ -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,15 @@
|
|
|
1
|
+
.radio-form-field-label input[type="radio"]:checked + .radio-circle {
|
|
2
|
+
@apply bg-primary;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.radio-form-field-label[data-has-error="true"] .radio-circle {
|
|
6
|
+
@apply border-red-700;
|
|
7
|
+
@apply outline-negative;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.radio-form-field-label
|
|
11
|
+
input[type="radio"]:checked
|
|
12
|
+
+ .radio-circle
|
|
13
|
+
.radio-inner-circle {
|
|
14
|
+
@apply bg-white;
|
|
15
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
|
|
4
|
+
import Text from "../Text/Text";
|
|
5
|
+
|
|
6
|
+
import "./RadioV2.css";
|
|
7
|
+
|
|
8
|
+
export interface RadioProps
|
|
9
|
+
extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
10
|
+
name: string;
|
|
11
|
+
value: string | number;
|
|
12
|
+
// text shown to the user to explain the option
|
|
13
|
+
text?: string;
|
|
14
|
+
// text shown to the user underneath the text for extra information
|
|
15
|
+
info?: string;
|
|
16
|
+
isError?: boolean;
|
|
17
|
+
innerRef?: React.Ref<HTMLInputElement>;
|
|
18
|
+
// value field is used by react-hook-form as the value returned
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function RadioInner({
|
|
22
|
+
text,
|
|
23
|
+
info,
|
|
24
|
+
isError,
|
|
25
|
+
innerRef,
|
|
26
|
+
className,
|
|
27
|
+
...otherProps
|
|
28
|
+
}: RadioProps): JSX.Element {
|
|
29
|
+
const { value, disabled } = otherProps;
|
|
30
|
+
const nameHtmlFor = `field-${value}`;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<label
|
|
34
|
+
className="radio-form-field-label leading-tight"
|
|
35
|
+
htmlFor={nameHtmlFor}
|
|
36
|
+
data-has-error={isError}
|
|
37
|
+
data-test-id={nameHtmlFor}
|
|
38
|
+
>
|
|
39
|
+
<div className="flex flex-row items-center ">
|
|
40
|
+
<input
|
|
41
|
+
{...otherProps}
|
|
42
|
+
className={classNames("appearance-none", className)}
|
|
43
|
+
ref={innerRef}
|
|
44
|
+
type="radio"
|
|
45
|
+
id={nameHtmlFor}
|
|
46
|
+
disabled={disabled}
|
|
47
|
+
/>
|
|
48
|
+
<span
|
|
49
|
+
className={classNames(
|
|
50
|
+
"flex flex-col items-center justify-center w-4 h-4 transition-colors duration-300 ease-in-out border radio-circle rounded-xl border-slate-300"
|
|
51
|
+
)}
|
|
52
|
+
>
|
|
53
|
+
<span className="block transition-colors duration-300 ease-in-out radio-inner-circle w-1.5 h-1.5 rounded-xl"></span>
|
|
54
|
+
</span>
|
|
55
|
+
{text && (
|
|
56
|
+
<div className="ml-2">
|
|
57
|
+
<Text
|
|
58
|
+
inline={true}
|
|
59
|
+
text={text}
|
|
60
|
+
type="base"
|
|
61
|
+
color={disabled ? "slate-500" : undefined}
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
{info && (
|
|
67
|
+
<Text
|
|
68
|
+
inline={true}
|
|
69
|
+
className="ml-6"
|
|
70
|
+
text={info}
|
|
71
|
+
type="sm"
|
|
72
|
+
color={disabled ? "slate-200" : "slate-500"}
|
|
73
|
+
/>
|
|
74
|
+
)}
|
|
75
|
+
</label>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* TODO: The CSS styling is all messed up, including isError
|
|
81
|
+
* Warning: don't use this prop before this is resolved
|
|
82
|
+
* Issue to track: https://github.com/Luscii/web-ui/issues/57
|
|
83
|
+
* TODO: remove this comment once this is resolved
|
|
84
|
+
*/
|
|
85
|
+
export const RadioV2 = React.forwardRef<HTMLInputElement, RadioProps>(
|
|
86
|
+
(props, ref) => <RadioInner {...props} innerRef={ref} />
|
|
87
|
+
);
|