module-package-comp 1.0.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/component/ComponentDisplayer.d.ts +9 -0
- package/dist/component/ComponentDisplayer.js +59 -0
- package/dist/component/ComponentDisplayer.js.map +1 -0
- package/dist/component/PrimaryBtn.d.ts +8 -0
- package/dist/component/PrimaryBtn.js +7 -0
- package/dist/component/PrimaryBtn.js.map +1 -0
- package/dist/component/ShowAttr.d.ts +9 -0
- package/dist/component/ShowAttr.js +38 -0
- package/dist/component/ShowAttr.js.map +1 -0
- package/dist/component/inputs/Input.d.ts +13 -0
- package/dist/component/inputs/Input.js +7 -0
- package/dist/component/inputs/Input.js.map +1 -0
- package/dist/component/inputs/LabeledMutliSelect.d.ts +6 -0
- package/dist/component/inputs/LabeledMutliSelect.js +18 -0
- package/dist/component/inputs/LabeledMutliSelect.js.map +1 -0
- package/dist/component/inputs/LableledInput.d.ts +6 -0
- package/dist/component/inputs/LableledInput.js +18 -0
- package/dist/component/inputs/LableledInput.js.map +1 -0
- package/dist/component/inputs/LableledSelect.d.ts +6 -0
- package/dist/component/inputs/LableledSelect.js +18 -0
- package/dist/component/inputs/LableledSelect.js.map +1 -0
- package/dist/component/inputs/MultiSelect.d.ts +8 -0
- package/dist/component/inputs/MultiSelect.js +23 -0
- package/dist/component/inputs/MultiSelect.js.map +1 -0
- package/dist/component/inputs/Select.d.ts +7 -0
- package/dist/component/inputs/Select.js +5 -0
- package/dist/component/inputs/Select.js.map +1 -0
- package/dist/index.css +783 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/util/type.d.ts +92 -0
- package/dist/util/type.js +39 -0
- package/dist/util/type.js.map +1 -0
- package/package.json +23 -0
- package/postcss.config.js +6 -0
- package/src/component/ComponentDisplayer.tsx +107 -0
- package/src/component/PrimaryBtn.tsx +30 -0
- package/src/component/ShowAttr.tsx +120 -0
- package/src/component/inputs/Input.tsx +43 -0
- package/src/component/inputs/LabeledMutliSelect.tsx +15 -0
- package/src/component/inputs/LableledInput.tsx +14 -0
- package/src/component/inputs/LableledSelect.tsx +14 -0
- package/src/component/inputs/MultiSelect.tsx +86 -0
- package/src/component/inputs/Select.tsx +28 -0
- package/src/index.css +3 -0
- package/src/index.tsx +6 -0
- package/src/util/type.ts +107 -0
- package/tailwind.config.js +12 -0
- package/tsconfig.json +113 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAErB,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sBAAsB,CAAC;AAErC,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export type DataJSON = Record<string, string | object>;
|
|
2
|
+
export type DateTime = string;
|
|
3
|
+
export declare enum ConditionEnum {
|
|
4
|
+
EQ = "equal",
|
|
5
|
+
SUP = "superior",
|
|
6
|
+
INF = "inferior"
|
|
7
|
+
}
|
|
8
|
+
export declare enum AttributeTypeEnum {
|
|
9
|
+
TEXT = "text",
|
|
10
|
+
LIST = "list",
|
|
11
|
+
NUMBER = "number",
|
|
12
|
+
BOOLEAN = "boolean",
|
|
13
|
+
DATE = "date",
|
|
14
|
+
NOTE = "note",
|
|
15
|
+
IMAGE = "image",
|
|
16
|
+
LINK = "link",
|
|
17
|
+
MULTIPLE = "multiple",
|
|
18
|
+
AUTOGEN = "autogen",
|
|
19
|
+
FILE = "file",
|
|
20
|
+
TITRE = "titre",
|
|
21
|
+
TEXT_OUTPUT = "text_output",
|
|
22
|
+
IMAGE_OUTPUT = "image_output",
|
|
23
|
+
VIDEO_OUTPUT = "video_output"
|
|
24
|
+
}
|
|
25
|
+
export declare enum ComponentType {
|
|
26
|
+
GENERAL = "general",
|
|
27
|
+
ACCESS = "access",
|
|
28
|
+
PEC = "PEC",
|
|
29
|
+
DOSSIER = "dossier",
|
|
30
|
+
PATIENT = "patient",
|
|
31
|
+
PRESCRIPTION = "prescription",
|
|
32
|
+
WORKFLOW = "workflow",
|
|
33
|
+
FOLDER = "folder",
|
|
34
|
+
CONSULTATION = "consultation",
|
|
35
|
+
TREE = "tree"
|
|
36
|
+
}
|
|
37
|
+
export interface Condition {
|
|
38
|
+
attribute: string;
|
|
39
|
+
cond: keyof typeof ConditionEnum;
|
|
40
|
+
value: string;
|
|
41
|
+
delimiter: string;
|
|
42
|
+
}
|
|
43
|
+
export interface SettingsAttribute {
|
|
44
|
+
max?: string;
|
|
45
|
+
min?: string;
|
|
46
|
+
color?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface Attribute {
|
|
49
|
+
name: string;
|
|
50
|
+
label?: string;
|
|
51
|
+
type?: keyof typeof AttributeTypeEnum;
|
|
52
|
+
content?: Content;
|
|
53
|
+
settings?: SettingsAttribute;
|
|
54
|
+
}
|
|
55
|
+
export type Content = string | string[];
|
|
56
|
+
export interface SettingsComponent {
|
|
57
|
+
required?: boolean;
|
|
58
|
+
}
|
|
59
|
+
export interface Component {
|
|
60
|
+
_id: string;
|
|
61
|
+
name: string;
|
|
62
|
+
label: string;
|
|
63
|
+
type: keyof typeof ComponentType;
|
|
64
|
+
attributes: Attribute[];
|
|
65
|
+
conditions: Condition[][];
|
|
66
|
+
settings?: SettingsComponent;
|
|
67
|
+
filter?: DataJSON;
|
|
68
|
+
createdAt?: DateTime;
|
|
69
|
+
updatedAt?: DateTime;
|
|
70
|
+
}
|
|
71
|
+
export interface ComponentResult {
|
|
72
|
+
total: number;
|
|
73
|
+
page: number;
|
|
74
|
+
lastPage: number;
|
|
75
|
+
data: Component[];
|
|
76
|
+
}
|
|
77
|
+
export interface Data {
|
|
78
|
+
_id: string;
|
|
79
|
+
patient_dmi: string;
|
|
80
|
+
from: string;
|
|
81
|
+
component_id: Component;
|
|
82
|
+
value: DataJSON;
|
|
83
|
+
extra: DataJSON;
|
|
84
|
+
createdAt: DateTime;
|
|
85
|
+
updatedAt: DateTime;
|
|
86
|
+
}
|
|
87
|
+
export interface DataResult {
|
|
88
|
+
total: number;
|
|
89
|
+
page: number;
|
|
90
|
+
lastPage: number;
|
|
91
|
+
data: Data[];
|
|
92
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Enums
|
|
2
|
+
export var ConditionEnum;
|
|
3
|
+
(function (ConditionEnum) {
|
|
4
|
+
ConditionEnum["EQ"] = "equal";
|
|
5
|
+
ConditionEnum["SUP"] = "superior";
|
|
6
|
+
ConditionEnum["INF"] = "inferior";
|
|
7
|
+
})(ConditionEnum || (ConditionEnum = {}));
|
|
8
|
+
export var AttributeTypeEnum;
|
|
9
|
+
(function (AttributeTypeEnum) {
|
|
10
|
+
AttributeTypeEnum["TEXT"] = "text";
|
|
11
|
+
AttributeTypeEnum["LIST"] = "list";
|
|
12
|
+
AttributeTypeEnum["NUMBER"] = "number";
|
|
13
|
+
AttributeTypeEnum["BOOLEAN"] = "boolean";
|
|
14
|
+
AttributeTypeEnum["DATE"] = "date";
|
|
15
|
+
AttributeTypeEnum["NOTE"] = "note";
|
|
16
|
+
AttributeTypeEnum["IMAGE"] = "image";
|
|
17
|
+
AttributeTypeEnum["LINK"] = "link";
|
|
18
|
+
AttributeTypeEnum["MULTIPLE"] = "multiple";
|
|
19
|
+
AttributeTypeEnum["AUTOGEN"] = "autogen";
|
|
20
|
+
AttributeTypeEnum["FILE"] = "file";
|
|
21
|
+
AttributeTypeEnum["TITRE"] = "titre";
|
|
22
|
+
AttributeTypeEnum["TEXT_OUTPUT"] = "text_output";
|
|
23
|
+
AttributeTypeEnum["IMAGE_OUTPUT"] = "image_output";
|
|
24
|
+
AttributeTypeEnum["VIDEO_OUTPUT"] = "video_output";
|
|
25
|
+
})(AttributeTypeEnum || (AttributeTypeEnum = {}));
|
|
26
|
+
export var ComponentType;
|
|
27
|
+
(function (ComponentType) {
|
|
28
|
+
ComponentType["GENERAL"] = "general";
|
|
29
|
+
ComponentType["ACCESS"] = "access";
|
|
30
|
+
ComponentType["PEC"] = "PEC";
|
|
31
|
+
ComponentType["DOSSIER"] = "dossier";
|
|
32
|
+
ComponentType["PATIENT"] = "patient";
|
|
33
|
+
ComponentType["PRESCRIPTION"] = "prescription";
|
|
34
|
+
ComponentType["WORKFLOW"] = "workflow";
|
|
35
|
+
ComponentType["FOLDER"] = "folder";
|
|
36
|
+
ComponentType["CONSULTATION"] = "consultation";
|
|
37
|
+
ComponentType["TREE"] = "tree";
|
|
38
|
+
})(ComponentType || (ComponentType = {}));
|
|
39
|
+
//# sourceMappingURL=type.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type.js","sourceRoot":"","sources":["../../src/util/type.ts"],"names":[],"mappings":"AAIA,QAAQ;AACR,MAAM,CAAN,IAAY,aAIX;AAJD,WAAY,aAAa;IACvB,6BAAY,CAAA;IACZ,iCAAgB,CAAA;IAChB,iCAAgB,CAAA;AAClB,CAAC,EAJW,aAAa,KAAb,aAAa,QAIxB;AAED,MAAM,CAAN,IAAY,iBAgBX;AAhBD,WAAY,iBAAiB;IAC3B,kCAAa,CAAA;IACb,kCAAa,CAAA;IACb,sCAAiB,CAAA;IACjB,wCAAmB,CAAA;IACnB,kCAAa,CAAA;IACb,kCAAa,CAAA;IACb,oCAAe,CAAA;IACf,kCAAa,CAAA;IACb,0CAAqB,CAAA;IACrB,wCAAmB,CAAA;IACnB,kCAAa,CAAA;IACb,oCAAe,CAAA;IACf,gDAA2B,CAAA;IAC3B,kDAA6B,CAAA;IAC7B,kDAA6B,CAAA;AAC/B,CAAC,EAhBW,iBAAiB,KAAjB,iBAAiB,QAgB5B;AAED,MAAM,CAAN,IAAY,aAWX;AAXD,WAAY,aAAa;IACvB,oCAAmB,CAAA;IACnB,kCAAiB,CAAA;IACjB,4BAAW,CAAA;IACX,oCAAmB,CAAA;IACnB,oCAAmB,CAAA;IACnB,8CAA6B,CAAA;IAC7B,sCAAqB,CAAA;IACrB,kCAAiB,CAAA;IACjB,8CAA6B,CAAA;IAC7B,8BAAa,CAAA;AACf,CAAC,EAXW,aAAa,KAAb,aAAa,QAWxB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "module-package-comp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "npx tailwindcss -i ./src/index.css -o ./dist/index.css && tsc",
|
|
9
|
+
"dev": "concurrently \"npx tailwindcss -i ./src/index.css -o ./dist/index.css --watch\" \"tsc -watch\""
|
|
10
|
+
},
|
|
11
|
+
"keywords": [],
|
|
12
|
+
"author": "",
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/react": "^18.3.3",
|
|
16
|
+
"@types/react-dom": "^18.3.0",
|
|
17
|
+
"concurrently": "^8.2.2",
|
|
18
|
+
"react": "^18.3.1",
|
|
19
|
+
"react-dom": "^18.3.1",
|
|
20
|
+
"tailwindcss": "^3.4.4",
|
|
21
|
+
"typescript": "^5.5.3"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Attribute,
|
|
4
|
+
Component,
|
|
5
|
+
Condition,
|
|
6
|
+
ConditionEnum,
|
|
7
|
+
DataJSON,
|
|
8
|
+
} from "../util/type";
|
|
9
|
+
import { ShowAttr } from "./ShowAttr";
|
|
10
|
+
import PrimaryBtn from "./PrimaryBtn";
|
|
11
|
+
|
|
12
|
+
type ComponentDisplayerProps = {
|
|
13
|
+
components: Component[];
|
|
14
|
+
data: DataJSON;
|
|
15
|
+
setData: (data: DataJSON) => void;
|
|
16
|
+
onSubmit: () => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function ComponentDisplayer({
|
|
20
|
+
components,
|
|
21
|
+
data,
|
|
22
|
+
setData,
|
|
23
|
+
onSubmit,
|
|
24
|
+
}: ComponentDisplayerProps) {
|
|
25
|
+
const [page, setPage] = useState(0);
|
|
26
|
+
return (
|
|
27
|
+
<form onSubmit={onSubmit} className="min-w-72">
|
|
28
|
+
<h1 className="font-bold text-center text-blue-800 text-4xl mb-8">
|
|
29
|
+
{components[page].label}
|
|
30
|
+
</h1>
|
|
31
|
+
<div className="space-y-3">
|
|
32
|
+
{components[page].attributes.map((attr) => {
|
|
33
|
+
if (canShow(attr, data, components[page].conditions || [])) {
|
|
34
|
+
return (
|
|
35
|
+
<ShowAttr
|
|
36
|
+
key={attr.name}
|
|
37
|
+
attribute={attr}
|
|
38
|
+
value={data[attr.name] ? String(data[attr.name]) : ""}
|
|
39
|
+
setValue={(s) => {
|
|
40
|
+
const temp = { ...data };
|
|
41
|
+
temp[attr.name] = s;
|
|
42
|
+
setData(temp);
|
|
43
|
+
}}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
} else {
|
|
47
|
+
if (data[attr.name]) {
|
|
48
|
+
const temp = { ...data };
|
|
49
|
+
temp[attr.name] = "";
|
|
50
|
+
setData(temp);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
})}
|
|
54
|
+
</div>
|
|
55
|
+
<div className="flex justify-center my-8">
|
|
56
|
+
<PrimaryBtn clickFn={onSubmit} text="Submit" />
|
|
57
|
+
</div>
|
|
58
|
+
</form>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function canShow(
|
|
63
|
+
attr: Attribute,
|
|
64
|
+
data: DataJSON,
|
|
65
|
+
conds: Condition[][],
|
|
66
|
+
): boolean {
|
|
67
|
+
const conditions = conds.filter(
|
|
68
|
+
(c) => c[c.length - 1].delimiter === attr.name,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (!conditions || conditions.length === 0) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return conditions.some((conditionSet) => {
|
|
76
|
+
let lastDelimiter = "ou"; // Default to OR
|
|
77
|
+
return conditionSet.every((condition, index) => {
|
|
78
|
+
const result = evaluateCondition(condition, data);
|
|
79
|
+
|
|
80
|
+
if (index < conditionSet.length - 1) {
|
|
81
|
+
lastDelimiter = condition.delimiter;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (lastDelimiter === "et") {
|
|
85
|
+
return result;
|
|
86
|
+
} else {
|
|
87
|
+
return result || index === conditionSet.length - 1;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function evaluateCondition(condition: Condition, data: DataJSON): boolean {
|
|
94
|
+
const { attribute, cond, value } = condition;
|
|
95
|
+
const dataValue = data[attribute];
|
|
96
|
+
|
|
97
|
+
switch (cond) {
|
|
98
|
+
case "EQ":
|
|
99
|
+
return value === dataValue;
|
|
100
|
+
case "INF":
|
|
101
|
+
return value < dataValue;
|
|
102
|
+
case "SUP":
|
|
103
|
+
return value > dataValue;
|
|
104
|
+
default:
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
|
|
3
|
+
type props = {
|
|
4
|
+
text: string;
|
|
5
|
+
clickFn: () => void;
|
|
6
|
+
className?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default function PrimaryBtn({
|
|
11
|
+
text,
|
|
12
|
+
clickFn,
|
|
13
|
+
className,
|
|
14
|
+
disabled,
|
|
15
|
+
}: props) {
|
|
16
|
+
const btn = useRef<HTMLButtonElement>(null);
|
|
17
|
+
return (
|
|
18
|
+
<button
|
|
19
|
+
type="submit"
|
|
20
|
+
ref={btn}
|
|
21
|
+
onClick={clickFn}
|
|
22
|
+
className={
|
|
23
|
+
"rounded-2xl bg-secondary-blue bg-opacity-85 px-8 py-2 font-bold text-white-shade hover:bg-opacity-100"
|
|
24
|
+
}
|
|
25
|
+
disabled={disabled}
|
|
26
|
+
>
|
|
27
|
+
{text}
|
|
28
|
+
</button>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Attribute, AttributeTypeEnum, Content } from "../util/type";
|
|
2
|
+
import LabeledSelect from "./inputs/LableledSelect";
|
|
3
|
+
import LabeledInput from "./inputs/LableledInput";
|
|
4
|
+
import MultiSelect from "./inputs/MultiSelect";
|
|
5
|
+
import LabeledMutliSelect from "./inputs/LabeledMutliSelect";
|
|
6
|
+
import { useMemo } from "react";
|
|
7
|
+
|
|
8
|
+
type ShowAttrProps = {
|
|
9
|
+
attribute: Attribute;
|
|
10
|
+
value: string;
|
|
11
|
+
setValue: (s: string) => void;
|
|
12
|
+
showLabel?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function ShowAttr({
|
|
16
|
+
attribute,
|
|
17
|
+
value,
|
|
18
|
+
setValue,
|
|
19
|
+
showLabel = true,
|
|
20
|
+
}: ShowAttrProps) {
|
|
21
|
+
const label = useMemo(
|
|
22
|
+
() => (showLabel ? attribute.label || "" : ""),
|
|
23
|
+
[attribute.label],
|
|
24
|
+
);
|
|
25
|
+
const type = useMemo(() => {
|
|
26
|
+
if (attribute.type) return AttributeTypeEnum[attribute.type];
|
|
27
|
+
}, [attribute.type]);
|
|
28
|
+
switch (type) {
|
|
29
|
+
case AttributeTypeEnum.NUMBER:
|
|
30
|
+
return (
|
|
31
|
+
<LabeledInput
|
|
32
|
+
label={label}
|
|
33
|
+
value={value}
|
|
34
|
+
setValue={setValue}
|
|
35
|
+
type="number"
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
case AttributeTypeEnum.TEXT:
|
|
39
|
+
return (
|
|
40
|
+
<LabeledInput
|
|
41
|
+
label={label}
|
|
42
|
+
value={value}
|
|
43
|
+
setValue={setValue}
|
|
44
|
+
type="text"
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
case AttributeTypeEnum.LIST:
|
|
48
|
+
return (
|
|
49
|
+
<LabeledSelect
|
|
50
|
+
label={label}
|
|
51
|
+
selected={value}
|
|
52
|
+
setSelected={setValue}
|
|
53
|
+
options={(attribute.content as string[]) || []}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
case AttributeTypeEnum.BOOLEAN:
|
|
57
|
+
return (
|
|
58
|
+
<LabeledInput
|
|
59
|
+
type="checkbox"
|
|
60
|
+
label={label}
|
|
61
|
+
checked={value === "true"}
|
|
62
|
+
setValue={(s) => setValue(s)}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
case AttributeTypeEnum.DATE:
|
|
66
|
+
return (
|
|
67
|
+
<LabeledInput
|
|
68
|
+
type="date"
|
|
69
|
+
label={label}
|
|
70
|
+
value={value}
|
|
71
|
+
setValue={(date) => setValue(date)}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
case AttributeTypeEnum.LINK:
|
|
75
|
+
return (
|
|
76
|
+
<div className="w-full">
|
|
77
|
+
<label className="block text-sm font-bold mb-1">{label}</label>
|
|
78
|
+
<a href={attribute.content as string}>{attribute.content}</a>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
case AttributeTypeEnum.MULTIPLE:
|
|
82
|
+
return (
|
|
83
|
+
<LabeledMutliSelect
|
|
84
|
+
label={label}
|
|
85
|
+
selectedOptions={value !== "" ? value.split(",") : []}
|
|
86
|
+
onChange={(selected) => setValue(selected.join(","))}
|
|
87
|
+
options={(attribute.content as string[]) || []}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
case AttributeTypeEnum.FILE:
|
|
91
|
+
return (
|
|
92
|
+
<LabeledInput
|
|
93
|
+
label={label}
|
|
94
|
+
type="file"
|
|
95
|
+
value={value}
|
|
96
|
+
setValue={setValue}
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
case AttributeTypeEnum.IMAGE_OUTPUT:
|
|
100
|
+
return (
|
|
101
|
+
<div className="w-full">
|
|
102
|
+
<label className="block text-sm font-bold mb-1">{label}</label>
|
|
103
|
+
<img className="w-64" src={attribute.content as string} />
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
case AttributeTypeEnum.TEXT_OUTPUT:
|
|
107
|
+
return (
|
|
108
|
+
<div className="w-full">
|
|
109
|
+
<label className="block text-sm font-bold mb-1">{label}</label>
|
|
110
|
+
<p>{attribute.content as string}</p>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
case AttributeTypeEnum.TITRE:
|
|
114
|
+
return (
|
|
115
|
+
<div className="w-full">
|
|
116
|
+
<h2 className="text-3xl font-semibold">{attribute.label}</h2>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { HTMLInputTypeAttribute } from "react";
|
|
2
|
+
|
|
3
|
+
export type InputProps = {
|
|
4
|
+
value?: string;
|
|
5
|
+
setValue: (s: string) => void;
|
|
6
|
+
type: HTMLInputTypeAttribute;
|
|
7
|
+
className?: string;
|
|
8
|
+
onBlur?: () => void;
|
|
9
|
+
maxLength?: number;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
checked?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default function Input({
|
|
16
|
+
value,
|
|
17
|
+
setValue,
|
|
18
|
+
className,
|
|
19
|
+
type,
|
|
20
|
+
onBlur,
|
|
21
|
+
maxLength,
|
|
22
|
+
placeholder,
|
|
23
|
+
disabled,
|
|
24
|
+
checked,
|
|
25
|
+
}: InputProps) {
|
|
26
|
+
return (
|
|
27
|
+
<input
|
|
28
|
+
type={type}
|
|
29
|
+
className="py-2 px-3 block w-full border-2 border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none"
|
|
30
|
+
value={value}
|
|
31
|
+
onChange={(e) =>
|
|
32
|
+
type === "checkbox"
|
|
33
|
+
? setValue(String(e.target.checked))
|
|
34
|
+
: setValue(e.target.value)
|
|
35
|
+
}
|
|
36
|
+
disabled={disabled}
|
|
37
|
+
onBlur={onBlur}
|
|
38
|
+
maxLength={maxLength}
|
|
39
|
+
placeholder={placeholder}
|
|
40
|
+
checked={checked}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Input, { InputProps } from "./Input";
|
|
2
|
+
import MultiSelect, { MultiSelectProps } from "./MultiSelect";
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
label: string;
|
|
6
|
+
} & MultiSelectProps;
|
|
7
|
+
|
|
8
|
+
export default function LabeledMutliSelect({ label, ...inputprops }: Props) {
|
|
9
|
+
return (
|
|
10
|
+
<div className="w-full">
|
|
11
|
+
<label className="block text-sm font-bold mb-1">{label}</label>
|
|
12
|
+
<MultiSelect {...inputprops} />
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import Input, { InputProps } from "./Input";
|
|
2
|
+
|
|
3
|
+
type Props = {
|
|
4
|
+
label: string;
|
|
5
|
+
} & InputProps;
|
|
6
|
+
|
|
7
|
+
export default function LabeledInput({ label, ...inputProps }: Props) {
|
|
8
|
+
return (
|
|
9
|
+
<div className="w-full">
|
|
10
|
+
<label className="block text-sm font-bold mb-1">{label}</label>
|
|
11
|
+
<Input {...inputProps} />
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import Select, { SelectProps } from "./Select";
|
|
2
|
+
|
|
3
|
+
type Props = {
|
|
4
|
+
label: string;
|
|
5
|
+
} & SelectProps;
|
|
6
|
+
|
|
7
|
+
export default function LabeledSelect({ label, ...inputProps }: Props) {
|
|
8
|
+
return (
|
|
9
|
+
<div className="w-full">
|
|
10
|
+
<label className="block text-sm font-bold mb-1">{label}</label>
|
|
11
|
+
<Select {...inputProps} />
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
|
|
3
|
+
export type MultiSelectProps = {
|
|
4
|
+
options: string[];
|
|
5
|
+
selectedOptions: string[];
|
|
6
|
+
onChange: (selected: string[]) => void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const MultiSelect: React.FC<MultiSelectProps> = ({
|
|
10
|
+
options,
|
|
11
|
+
selectedOptions,
|
|
12
|
+
onChange,
|
|
13
|
+
}) => {
|
|
14
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
15
|
+
const [filter, setFilter] = useState("");
|
|
16
|
+
|
|
17
|
+
const toggleDropdown = () => setIsOpen(!isOpen);
|
|
18
|
+
|
|
19
|
+
const handleOptionClick = (option: string) => {
|
|
20
|
+
if (selectedOptions.includes(option)) {
|
|
21
|
+
onChange(selectedOptions.filter((selected) => selected !== option));
|
|
22
|
+
} else {
|
|
23
|
+
onChange([...selectedOptions, option]);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
console.log(selectedOptions);
|
|
27
|
+
const handleFilterChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
28
|
+
setFilter(e.target.value);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const filteredOptions = options.filter((option) =>
|
|
32
|
+
option.toLowerCase().includes(filter.toLowerCase()),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="relative inline-block w-full">
|
|
37
|
+
<div
|
|
38
|
+
className="py-2 px-3 bg-white block w-full border-2 border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none"
|
|
39
|
+
onClick={toggleDropdown}
|
|
40
|
+
>
|
|
41
|
+
{Array.isArray(selectedOptions) && selectedOptions.length > 0 ? (
|
|
42
|
+
selectedOptions.map((option) => (
|
|
43
|
+
<span
|
|
44
|
+
key={option}
|
|
45
|
+
className="mr-1 inline-block rounded-full bg-secondary-blue px-2 py-1 text-sm text-white-shade"
|
|
46
|
+
>
|
|
47
|
+
{option}
|
|
48
|
+
</span>
|
|
49
|
+
))
|
|
50
|
+
) : (
|
|
51
|
+
<span className="text-placeholder-gray">Select options</span>
|
|
52
|
+
)}
|
|
53
|
+
</div>
|
|
54
|
+
{isOpen && (
|
|
55
|
+
<div className="absolute left-0 right-0 z-10 mt-2 rounded-md border border-secondary-gray bg-white-shade">
|
|
56
|
+
<input
|
|
57
|
+
type="text"
|
|
58
|
+
className="w-full border-b border-secondary-gray p-2"
|
|
59
|
+
placeholder="Filter options..."
|
|
60
|
+
value={filter}
|
|
61
|
+
onChange={handleFilterChange}
|
|
62
|
+
/>
|
|
63
|
+
<div className="max-h-60 overflow-y-auto">
|
|
64
|
+
{filteredOptions.map((option) => (
|
|
65
|
+
<div
|
|
66
|
+
key={option}
|
|
67
|
+
className="cursor-pointer p-2 hover:bg-primary-gray"
|
|
68
|
+
onClick={() => handleOptionClick(option)}
|
|
69
|
+
>
|
|
70
|
+
<input
|
|
71
|
+
type="checkbox"
|
|
72
|
+
checked={selectedOptions.includes(option)}
|
|
73
|
+
readOnly
|
|
74
|
+
className="mr-2"
|
|
75
|
+
/>
|
|
76
|
+
<span className="ml-2">{option}</span>
|
|
77
|
+
</div>
|
|
78
|
+
))}
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export default MultiSelect;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type SelectProps = {
|
|
2
|
+
options: string[];
|
|
3
|
+
selected: string;
|
|
4
|
+
setSelected: (s: string) => void;
|
|
5
|
+
canDefault?: boolean;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export default function Select({
|
|
9
|
+
options,
|
|
10
|
+
selected,
|
|
11
|
+
setSelected,
|
|
12
|
+
canDefault,
|
|
13
|
+
}: SelectProps) {
|
|
14
|
+
return (
|
|
15
|
+
<select
|
|
16
|
+
value={selected || ""}
|
|
17
|
+
onChange={(e) => setSelected(e.target.value)}
|
|
18
|
+
className="py-2 px-3 bg-white block w-full border-2 border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none"
|
|
19
|
+
>
|
|
20
|
+
<option value="" disabled={!canDefault} hidden={!canDefault}>
|
|
21
|
+
selection
|
|
22
|
+
</option>
|
|
23
|
+
{options.map((opt) => (
|
|
24
|
+
<option value={opt}>{opt}</option>
|
|
25
|
+
))}
|
|
26
|
+
</select>
|
|
27
|
+
);
|
|
28
|
+
}
|
package/src/index.css
ADDED