@arkyn/components 1.3.157 → 1.3.158
Sign up to get free protection for your applications and to get access to all the features.
- package/base-variables.css +6 -5
- package/dist/bundle.js +90050 -77673
- package/dist/bundle.umd.cjs +1485 -1225
- package/dist/components/MultiSelect/components/MultiSelectChevron/index.d.ts +11 -0
- package/dist/components/MultiSelect/components/MultiSelectChevron/index.d.ts.map +1 -0
- package/dist/components/MultiSelect/components/MultiSelectChevron/index.js +13 -0
- package/dist/components/MultiSelect/components/MultiSelectContainer/index.d.ts +19 -0
- package/dist/components/MultiSelect/components/MultiSelectContainer/index.d.ts.map +1 -0
- package/dist/components/MultiSelect/components/MultiSelectContainer/index.js +11 -0
- package/dist/components/MultiSelect/components/MultiSelectContent/index.d.ts +9 -0
- package/dist/components/MultiSelect/components/MultiSelectContent/index.d.ts.map +1 -0
- package/dist/components/MultiSelect/components/MultiSelectContent/index.js +8 -0
- package/dist/components/MultiSelect/components/MultiSelectMark/index.d.ts +9 -0
- package/dist/components/MultiSelect/components/MultiSelectMark/index.d.ts.map +1 -0
- package/dist/components/MultiSelect/components/MultiSelectMark/index.js +11 -0
- package/dist/components/MultiSelect/components/MultiSelectOption/index.d.ts +11 -0
- package/dist/components/MultiSelect/components/MultiSelectOption/index.d.ts.map +1 -0
- package/dist/components/MultiSelect/components/MultiSelectOption/index.js +10 -0
- package/dist/components/MultiSelect/components/MultiSelectOptionsContainer/index.d.ts +11 -0
- package/dist/components/MultiSelect/components/MultiSelectOptionsContainer/index.d.ts.map +1 -0
- package/dist/components/MultiSelect/components/MultiSelectOptionsContainer/index.js +16 -0
- package/dist/components/MultiSelect/components/MultiSelectOverlay/index.d.ts +8 -0
- package/dist/components/MultiSelect/components/MultiSelectOverlay/index.d.ts.map +1 -0
- package/dist/components/MultiSelect/components/MultiSelectOverlay/index.js +9 -0
- package/dist/components/MultiSelect/components/MultiSelectSpinner/index.d.ts +8 -0
- package/dist/components/MultiSelect/components/MultiSelectSpinner/index.d.ts.map +1 -0
- package/dist/components/MultiSelect/components/MultiSelectSpinner/index.js +10 -0
- package/dist/components/MultiSelect/index.d.ts +4 -0
- package/dist/components/MultiSelect/index.d.ts.map +1 -0
- package/dist/components/MultiSelect/index.js +73 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/MultiSelect/components/MultiSelectChevron/index.tsx +26 -0
- package/src/components/MultiSelect/components/MultiSelectChevron/styles.css +7 -0
- package/src/components/MultiSelect/components/MultiSelectContainer/index.tsx +51 -0
- package/src/components/MultiSelect/components/MultiSelectContainer/styles.css +134 -0
- package/src/components/MultiSelect/components/MultiSelectContent/index.tsx +15 -0
- package/src/components/MultiSelect/components/MultiSelectContent/styles.css +24 -0
- package/src/components/MultiSelect/components/MultiSelectMark/index.tsx +30 -0
- package/src/components/MultiSelect/components/MultiSelectMark/styles.css +40 -0
- package/src/components/MultiSelect/components/MultiSelectOption/index.tsx +25 -0
- package/src/components/MultiSelect/components/MultiSelectOption/styles.css +37 -0
- package/src/components/MultiSelect/components/MultiSelectOptionsContainer/index.tsx +41 -0
- package/src/components/MultiSelect/components/MultiSelectOptionsContainer/styles.css +36 -0
- package/src/components/MultiSelect/components/MultiSelectOverlay/index.tsx +14 -0
- package/src/components/MultiSelect/components/MultiSelectOverlay/styles.css +8 -0
- package/src/components/MultiSelect/components/MultiSelectSpinner/index.tsx +22 -0
- package/src/components/MultiSelect/components/MultiSelectSpinner/styles.css +12 -0
- package/src/components/MultiSelect/index.tsx +171 -0
- package/src/index.ts +1 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
import { ReactNode } from "react";
|
2
|
+
import "./styles.css";
|
3
|
+
|
4
|
+
type MultiSelectContainerProps = {
|
5
|
+
children: ReactNode;
|
6
|
+
handleContainerFocus: () => void;
|
7
|
+
prefixExists: boolean;
|
8
|
+
isError: boolean;
|
9
|
+
disabled: boolean;
|
10
|
+
readOnly: boolean;
|
11
|
+
isLoading: boolean;
|
12
|
+
isFocused: boolean;
|
13
|
+
className?: string;
|
14
|
+
id: string;
|
15
|
+
variant: "solid" | "outline" | "underline";
|
16
|
+
size: "md" | "lg";
|
17
|
+
};
|
18
|
+
|
19
|
+
function MultiSelectContainer(props: MultiSelectContainerProps) {
|
20
|
+
const {
|
21
|
+
children,
|
22
|
+
handleContainerFocus,
|
23
|
+
disabled,
|
24
|
+
isError,
|
25
|
+
isLoading,
|
26
|
+
isFocused,
|
27
|
+
className,
|
28
|
+
readOnly,
|
29
|
+
variant,
|
30
|
+
size,
|
31
|
+
id,
|
32
|
+
prefixExists,
|
33
|
+
} = props;
|
34
|
+
|
35
|
+
const hasPrefix = prefixExists ? "hasPrefix" : "";
|
36
|
+
const errored = isError ? "errored" : "";
|
37
|
+
const opacity = disabled || readOnly || isLoading ? "opacity" : "";
|
38
|
+
const focused = isFocused ? "focused" : "";
|
39
|
+
|
40
|
+
return (
|
41
|
+
<section
|
42
|
+
onClick={handleContainerFocus}
|
43
|
+
id={id}
|
44
|
+
className={`arkynMultiSelectContainer ${hasPrefix} ${variant} ${size} ${opacity} ${errored} ${focused} ${className}`}
|
45
|
+
>
|
46
|
+
{children}
|
47
|
+
</section>
|
48
|
+
);
|
49
|
+
}
|
50
|
+
|
51
|
+
export { MultiSelectContainer };
|
@@ -0,0 +1,134 @@
|
|
1
|
+
/* BASE CSS */
|
2
|
+
.arkynMultiSelectContainer {
|
3
|
+
flex: 1;
|
4
|
+
position: relative;
|
5
|
+
display: flex;
|
6
|
+
align-items: center;
|
7
|
+
|
8
|
+
padding: 0 16px;
|
9
|
+
gap: 8px;
|
10
|
+
border-radius: 6px;
|
11
|
+
|
12
|
+
border: 1px solid transparent;
|
13
|
+
outline: 1px solid transparent;
|
14
|
+
}
|
15
|
+
.arkynMultiSelectContainer:hover {
|
16
|
+
cursor: pointer;
|
17
|
+
}
|
18
|
+
.arkynMultiSelectContainer.opacity {
|
19
|
+
opacity: 0.5;
|
20
|
+
}
|
21
|
+
|
22
|
+
/* PREFIX */
|
23
|
+
.arkynMultiSelectContainer .prefix {
|
24
|
+
color: var(--text-body);
|
25
|
+
background: var(--border);
|
26
|
+
font-weight: 400;
|
27
|
+
|
28
|
+
display: flex;
|
29
|
+
align-items: center;
|
30
|
+
justify-content: center;
|
31
|
+
|
32
|
+
position: absolute;
|
33
|
+
}
|
34
|
+
.arkynMultiSelectContainer .prefix {
|
35
|
+
left: 0;
|
36
|
+
top: 0;
|
37
|
+
bottom: 0;
|
38
|
+
border-radius: 5px 0 0 5px;
|
39
|
+
border-right: 1px solid var(--border);
|
40
|
+
}
|
41
|
+
|
42
|
+
/* LEFT ICON */
|
43
|
+
.arkynMultiSelectContainer > svg {
|
44
|
+
color: var(--text-muted);
|
45
|
+
}
|
46
|
+
.arkynMultiSelectContainer.errored > svg {
|
47
|
+
color: rgba(var(--spotlight-danger), 1);
|
48
|
+
}
|
49
|
+
.arkynMultiSelectContainer:not(.opacity).focused > svg {
|
50
|
+
color: rgba(var(--spotlight-primary), 1);
|
51
|
+
}
|
52
|
+
|
53
|
+
/* VARIANTS */
|
54
|
+
.arkynMultiSelectContainer.solid {
|
55
|
+
border-color: var(--border);
|
56
|
+
background-color: rgba(var(--input-background), 1);
|
57
|
+
}
|
58
|
+
.arkynMultiSelectContainer.solid.errored {
|
59
|
+
border-color: rgba(var(--spotlight-danger), 1);
|
60
|
+
outline-color: rgba(var(--spotlight-danger), 1);
|
61
|
+
}
|
62
|
+
.arkynMultiSelectContainer:not(.opacity).solid.focused {
|
63
|
+
border-color: rgba(var(--spotlight-primary), 1);
|
64
|
+
outline-color: rgba(var(--spotlight-primary), 1);
|
65
|
+
}
|
66
|
+
.arkynMultiSelectContainer.outline {
|
67
|
+
border-color: var(--border);
|
68
|
+
}
|
69
|
+
.arkynMultiSelectContainer.outline.errored {
|
70
|
+
border-color: rgba(var(--spotlight-danger), 1);
|
71
|
+
outline-color: rgba(var(--spotlight-danger), 1);
|
72
|
+
}
|
73
|
+
.arkynMultiSelectContainer:not(.opacity).outline.focused {
|
74
|
+
border-color: rgba(var(--spotlight-primary), 1);
|
75
|
+
outline-color: rgba(var(--spotlight-primary), 1);
|
76
|
+
}
|
77
|
+
.arkynMultiSelectContainer.underline {
|
78
|
+
border-radius: 0;
|
79
|
+
border-top: none;
|
80
|
+
border-left: none;
|
81
|
+
border-right: none;
|
82
|
+
outline: none;
|
83
|
+
border-color: var(--border);
|
84
|
+
}
|
85
|
+
.arkynMultiSelectContainer.underline .prefix {
|
86
|
+
display: none;
|
87
|
+
}
|
88
|
+
.arkynMultiSelectContainer.underline::before {
|
89
|
+
content: " ";
|
90
|
+
position: absolute;
|
91
|
+
height: 1px;
|
92
|
+
left: 0;
|
93
|
+
right: 0;
|
94
|
+
bottom: -2px;
|
95
|
+
background: transparent;
|
96
|
+
}
|
97
|
+
.arkynMultiSelectContainer.underline.errored {
|
98
|
+
border-color: rgba(var(--spotlight-danger), 1);
|
99
|
+
}
|
100
|
+
.arkynMultiSelectContainer.underline.errored::before {
|
101
|
+
background: rgba(var(--spotlight-danger), 1);
|
102
|
+
}
|
103
|
+
.arkynMultiSelectContainer:not(.opacity).underline.focused {
|
104
|
+
border-color: rgba(var(--spotlight-primary), 1);
|
105
|
+
}
|
106
|
+
.arkynMultiSelectContainer:not(.opacity).underline.focused::before {
|
107
|
+
background: rgba(var(--spotlight-primary), 1);
|
108
|
+
}
|
109
|
+
|
110
|
+
/* SIZE */
|
111
|
+
.arkynMultiSelectContainer.md {
|
112
|
+
min-height: 40px;
|
113
|
+
max-height: 40px;
|
114
|
+
}
|
115
|
+
.arkynMultiSelectContainer.md.hasPrefix {
|
116
|
+
padding-left: 60px;
|
117
|
+
}
|
118
|
+
.arkynMultiSelectContainer.md .prefix {
|
119
|
+
height: 40px;
|
120
|
+
width: 44px;
|
121
|
+
font-size: 14px;
|
122
|
+
}
|
123
|
+
.arkynMultiSelectContainer.lg {
|
124
|
+
min-height: 44px;
|
125
|
+
max-height: 44px;
|
126
|
+
}
|
127
|
+
.arkynMultiSelectContainer.lg .prefix {
|
128
|
+
height: 44px;
|
129
|
+
width: 48px;
|
130
|
+
font-size: 16px;
|
131
|
+
}
|
132
|
+
.arkynMultiSelectContainer.lg.hasPrefix {
|
133
|
+
padding-left: 64px;
|
134
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import { ReactNode } from "react";
|
2
|
+
import "./styles.css";
|
3
|
+
|
4
|
+
type MultiSelectContentProps = {
|
5
|
+
children: ReactNode;
|
6
|
+
size: "md" | "lg";
|
7
|
+
};
|
8
|
+
|
9
|
+
function MultiSelectContent(props: MultiSelectContentProps) {
|
10
|
+
const { children, size } = props;
|
11
|
+
const className = `arkynMultiSelectContent ${size}`;
|
12
|
+
return <div className={className}>{children}</div>;
|
13
|
+
}
|
14
|
+
|
15
|
+
export { MultiSelectContent };
|
@@ -0,0 +1,24 @@
|
|
1
|
+
.arkynMultiSelectContent {
|
2
|
+
display: flex;
|
3
|
+
align-items: start;
|
4
|
+
gap: 4px;
|
5
|
+
flex-wrap: wrap;
|
6
|
+
|
7
|
+
flex: 1;
|
8
|
+
}
|
9
|
+
|
10
|
+
.arkynMultiSelectContent > p {
|
11
|
+
font-weight: 400;
|
12
|
+
color: var(--text-muted);
|
13
|
+
user-select: none;
|
14
|
+
}
|
15
|
+
|
16
|
+
.arkynMultiSelectContent.md > p {
|
17
|
+
font-size: 14px;
|
18
|
+
line-height: 14px;
|
19
|
+
}
|
20
|
+
|
21
|
+
.arkynMultiSelectContent.lg > p {
|
22
|
+
font-size: 16px;
|
23
|
+
line-height: 16px;
|
24
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import { X } from "lucide-react";
|
2
|
+
import "./styles.css";
|
3
|
+
|
4
|
+
type MultiSelectMarkProps = {
|
5
|
+
label: string;
|
6
|
+
value: string;
|
7
|
+
handleChangeValue: (value: string) => void;
|
8
|
+
};
|
9
|
+
|
10
|
+
function MultiSelectMark(props: MultiSelectMarkProps) {
|
11
|
+
const { label, value, handleChangeValue } = props;
|
12
|
+
|
13
|
+
return (
|
14
|
+
<div className="arkynMultiSelectMark">
|
15
|
+
{label}
|
16
|
+
|
17
|
+
<button
|
18
|
+
type="button"
|
19
|
+
onClick={(e) => {
|
20
|
+
e.stopPropagation();
|
21
|
+
handleChangeValue(value);
|
22
|
+
}}
|
23
|
+
>
|
24
|
+
<X />
|
25
|
+
</button>
|
26
|
+
</div>
|
27
|
+
);
|
28
|
+
}
|
29
|
+
|
30
|
+
export { MultiSelectMark };
|
@@ -0,0 +1,40 @@
|
|
1
|
+
.arkynMultiSelectMark {
|
2
|
+
display: flex;
|
3
|
+
align-items: center;
|
4
|
+
gap: 4px;
|
5
|
+
|
6
|
+
border-radius: 4px;
|
7
|
+
background: var(--card-foreground-terceary);
|
8
|
+
|
9
|
+
padding: 2px 6px;
|
10
|
+
|
11
|
+
font-size: 14px;
|
12
|
+
font-weight: 400;
|
13
|
+
line-height: 19.07px;
|
14
|
+
text-align: left;
|
15
|
+
z-index: 6;
|
16
|
+
user-select: none;
|
17
|
+
}
|
18
|
+
|
19
|
+
.arkynMultiSelectMark button {
|
20
|
+
display: flex;
|
21
|
+
align-items: center;
|
22
|
+
justify-content: center;
|
23
|
+
|
24
|
+
border: none;
|
25
|
+
background: inherit;
|
26
|
+
|
27
|
+
width: 15px;
|
28
|
+
height: 15px;
|
29
|
+
}
|
30
|
+
|
31
|
+
.arkynMultiSelectMark button:hover {
|
32
|
+
cursor: pointer;
|
33
|
+
filter: brightness(0.9);
|
34
|
+
}
|
35
|
+
|
36
|
+
.arkynMultiSelectMark button svg {
|
37
|
+
min-width: 10px;
|
38
|
+
min-height: 10px;
|
39
|
+
color: var(--text-muted);
|
40
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import { Check } from "lucide-react";
|
2
|
+
import "./styles.css";
|
3
|
+
|
4
|
+
type MultiSelectOptionProps = {
|
5
|
+
value: string;
|
6
|
+
label: string;
|
7
|
+
size: "md" | "lg";
|
8
|
+
optionHasSelected: (value: string) => boolean;
|
9
|
+
handleChangeValue: (value: string) => void;
|
10
|
+
};
|
11
|
+
|
12
|
+
function MultiSelectOption(props: MultiSelectOptionProps) {
|
13
|
+
const { label, optionHasSelected, handleChangeValue, value, size } = props;
|
14
|
+
|
15
|
+
const hasActive = optionHasSelected(value) ? "active" : "";
|
16
|
+
const className = `arkynMultiSelectOption ${size} ${hasActive}`;
|
17
|
+
|
18
|
+
return (
|
19
|
+
<div onClick={() => handleChangeValue(value)} className={className}>
|
20
|
+
{label} <Check />
|
21
|
+
</div>
|
22
|
+
);
|
23
|
+
}
|
24
|
+
|
25
|
+
export { MultiSelectOption };
|
@@ -0,0 +1,37 @@
|
|
1
|
+
.arkynMultiSelectOption {
|
2
|
+
display: flex;
|
3
|
+
align-items: center;
|
4
|
+
justify-content: space-between;
|
5
|
+
|
6
|
+
font-weight: 400;
|
7
|
+
line-height: 21.79px;
|
8
|
+
|
9
|
+
color: var(--text-body);
|
10
|
+
user-select: none;
|
11
|
+
}
|
12
|
+
.arkynMultiSelectOption.md {
|
13
|
+
font-size: 14px;
|
14
|
+
padding: 8px 16px;
|
15
|
+
}
|
16
|
+
.arkynMultiSelectOption.lg {
|
17
|
+
font-size: 16px;
|
18
|
+
padding: 8px 16px;
|
19
|
+
}
|
20
|
+
.arkynMultiSelectOption svg {
|
21
|
+
display: none;
|
22
|
+
height: 20px;
|
23
|
+
width: 20px;
|
24
|
+
color: rgba(var(--spotlight-primary), 1);
|
25
|
+
}
|
26
|
+
.arkynMultiSelectOption:hover {
|
27
|
+
cursor: pointer;
|
28
|
+
background-color: var(--card-foreground-primary);
|
29
|
+
}
|
30
|
+
.arkynMultiSelectOption.active {
|
31
|
+
font-weight: 600;
|
32
|
+
color: var(--text-heading);
|
33
|
+
background-color: var(--card-foreground-primary);
|
34
|
+
}
|
35
|
+
.arkynMultiSelectOption.active svg {
|
36
|
+
display: unset;
|
37
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import { Input } from "@arkyn/components";
|
2
|
+
import { Search } from "lucide-react";
|
3
|
+
import { ChangeEvent, ReactNode } from "react";
|
4
|
+
|
5
|
+
import "./styles.css";
|
6
|
+
|
7
|
+
type MultiSelectOptionsContainerProps = {
|
8
|
+
isFocused: boolean;
|
9
|
+
isSearchable: boolean;
|
10
|
+
children: ReactNode;
|
11
|
+
onSearch: (value: string) => void;
|
12
|
+
};
|
13
|
+
|
14
|
+
function MultiSelectOptionsContainer(props: MultiSelectOptionsContainerProps) {
|
15
|
+
const { children, isFocused, isSearchable, onSearch } = props;
|
16
|
+
|
17
|
+
function handleSearch(e: ChangeEvent<HTMLInputElement>) {
|
18
|
+
if (!isSearchable) return;
|
19
|
+
onSearch(e.target.value);
|
20
|
+
}
|
21
|
+
|
22
|
+
if (!isFocused) return <></>;
|
23
|
+
|
24
|
+
return (
|
25
|
+
<div className="arkynMultiSelectOptionsContainer">
|
26
|
+
{isSearchable && (
|
27
|
+
<Input
|
28
|
+
type="search"
|
29
|
+
name="search-select"
|
30
|
+
variant="underline"
|
31
|
+
leftIcon={Search}
|
32
|
+
onChange={handleSearch}
|
33
|
+
/>
|
34
|
+
)}
|
35
|
+
|
36
|
+
{children}
|
37
|
+
</div>
|
38
|
+
);
|
39
|
+
}
|
40
|
+
|
41
|
+
export { MultiSelectOptionsContainer };
|
@@ -0,0 +1,36 @@
|
|
1
|
+
/* BASE CSS */
|
2
|
+
.arkynMultiSelectOptionsContainer {
|
3
|
+
position: absolute;
|
4
|
+
z-index: 6;
|
5
|
+
|
6
|
+
top: calc(100% + 5px);
|
7
|
+
left: -2px;
|
8
|
+
right: -2px;
|
9
|
+
|
10
|
+
border-radius: 6px;
|
11
|
+
list-style: none;
|
12
|
+
|
13
|
+
display: flex;
|
14
|
+
flex-direction: column;
|
15
|
+
|
16
|
+
flex: 1;
|
17
|
+
overflow: hidden;
|
18
|
+
height: max-content;
|
19
|
+
|
20
|
+
border: 1px solid var(--border);
|
21
|
+
background-color: var(--card);
|
22
|
+
|
23
|
+
max-height: 300px;
|
24
|
+
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
|
25
|
+
}
|
26
|
+
|
27
|
+
/* NOT FOUND TEXT */
|
28
|
+
.arkynMultiSelectOptionsContainer > p {
|
29
|
+
font-weight: 400;
|
30
|
+
font-size: 14px;
|
31
|
+
padding: 16px;
|
32
|
+
text-align: center;
|
33
|
+
line-height: 21.79px;
|
34
|
+
|
35
|
+
color: var(--text-body);
|
36
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import "./styles.css";
|
2
|
+
|
3
|
+
type MultiSelectOverlayProps = {
|
4
|
+
isFocused: boolean;
|
5
|
+
handleBlur: () => void;
|
6
|
+
};
|
7
|
+
|
8
|
+
function MultiSelectOverlay(props: MultiSelectOverlayProps) {
|
9
|
+
const { isFocused, handleBlur } = props;
|
10
|
+
if (!isFocused) return <></>;
|
11
|
+
return <aside className="arkynMultiSelectOverlay" onClick={handleBlur} />;
|
12
|
+
}
|
13
|
+
|
14
|
+
export { MultiSelectOverlay };
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { Loader2 } from "lucide-react";
|
2
|
+
import "./styles.css";
|
3
|
+
|
4
|
+
type MultiSelectSpinnerProps = {
|
5
|
+
iconSize: number;
|
6
|
+
isLoading: boolean;
|
7
|
+
};
|
8
|
+
|
9
|
+
function MultiSelectSpinner(props: MultiSelectSpinnerProps) {
|
10
|
+
const { iconSize, isLoading } = props;
|
11
|
+
|
12
|
+
if (!isLoading) return <></>;
|
13
|
+
return (
|
14
|
+
<Loader2
|
15
|
+
className="arkynMultiSelectSpinner"
|
16
|
+
size={iconSize}
|
17
|
+
strokeWidth={2.5}
|
18
|
+
/>
|
19
|
+
);
|
20
|
+
}
|
21
|
+
|
22
|
+
export { MultiSelectSpinner };
|
@@ -0,0 +1,171 @@
|
|
1
|
+
import { MultiSelectProps } from "@arkyn/types";
|
2
|
+
import { useRef, useState } from "react";
|
3
|
+
|
4
|
+
import { morpheme } from "../../services";
|
5
|
+
import { useFormController } from "../Form/FormController";
|
6
|
+
|
7
|
+
import { MultiSelectChevron } from "./components/MultiSelectChevron";
|
8
|
+
import { MultiSelectContainer } from "./components/MultiSelectContainer";
|
9
|
+
import { MultiSelectContent } from "./components/MultiSelectContent";
|
10
|
+
import { MultiSelectMark } from "./components/MultiSelectMark";
|
11
|
+
import { MultiSelectOption } from "./components/MultiSelectOption";
|
12
|
+
import { MultiSelectOptionsContainer } from "./components/MultiSelectOptionsContainer";
|
13
|
+
import { MultiSelectOverlay } from "./components/MultiSelectOverlay";
|
14
|
+
import { MultiSelectSpinner } from "./components/MultiSelectSpinner";
|
15
|
+
|
16
|
+
function MultiSelect(props: MultiSelectProps) {
|
17
|
+
const {
|
18
|
+
name,
|
19
|
+
options,
|
20
|
+
className = "",
|
21
|
+
placeholder = "Selecione...",
|
22
|
+
closeOnSelect = false,
|
23
|
+
defaultValue = [],
|
24
|
+
isError: baseIsError,
|
25
|
+
isLoading = false,
|
26
|
+
readOnly = false,
|
27
|
+
isSearchable = false,
|
28
|
+
leftIcon: LeftIcon,
|
29
|
+
onSearch,
|
30
|
+
onSelect,
|
31
|
+
onBlur,
|
32
|
+
notFoundText = "Sem opções disponíveis",
|
33
|
+
onFocus,
|
34
|
+
disabled = false,
|
35
|
+
prefix: basePrefix,
|
36
|
+
size = "md",
|
37
|
+
value,
|
38
|
+
variant = "solid",
|
39
|
+
} = props;
|
40
|
+
|
41
|
+
const formController = useFormController();
|
42
|
+
const baseRef = useRef<HTMLInputElement>(null);
|
43
|
+
|
44
|
+
const multiSelectRef = formController.inputRef || baseRef;
|
45
|
+
const multiSelectId = formController.id;
|
46
|
+
|
47
|
+
const isError = baseIsError || !!formController.error;
|
48
|
+
|
49
|
+
const iconSizes = { md: 20, lg: 20 };
|
50
|
+
const iconSize = iconSizes[size];
|
51
|
+
const prefix = morpheme(basePrefix, iconSize, "prefix");
|
52
|
+
|
53
|
+
const [search, setSearch] = useState("");
|
54
|
+
const [isFocused, setIsFocused] = useState(false);
|
55
|
+
const [selectedOptions, setSelectedOptions] = useState(defaultValue);
|
56
|
+
|
57
|
+
const forceSelectedOptions = value || selectedOptions;
|
58
|
+
|
59
|
+
function optionHasSelected(value: string) {
|
60
|
+
return forceSelectedOptions.includes(value);
|
61
|
+
}
|
62
|
+
|
63
|
+
function getOptionLabel(value: string) {
|
64
|
+
const option = options.find((option) => option.value === value);
|
65
|
+
return option?.label || "";
|
66
|
+
}
|
67
|
+
|
68
|
+
function handleContainerFocus() {
|
69
|
+
if (disabled || !multiSelectRef?.current || isFocused) return;
|
70
|
+
setIsFocused(true);
|
71
|
+
multiSelectRef.current.focus();
|
72
|
+
onFocus && onFocus();
|
73
|
+
}
|
74
|
+
|
75
|
+
function handleBlur() {
|
76
|
+
setIsFocused(false);
|
77
|
+
if (onBlur && multiSelectRef.current) multiSelectRef.current.blur();
|
78
|
+
}
|
79
|
+
|
80
|
+
function handleSearch(value: string) {
|
81
|
+
setSearch(value);
|
82
|
+
if (onSearch) onSearch(value);
|
83
|
+
}
|
84
|
+
|
85
|
+
function handleChangeValue(value: string) {
|
86
|
+
if (optionHasSelected(value)) {
|
87
|
+
setSelectedOptions(selectedOptions.filter((v) => v !== value));
|
88
|
+
} else setSelectedOptions([...selectedOptions, value]);
|
89
|
+
|
90
|
+
if (onSelect) onSelect(selectedOptions);
|
91
|
+
if (closeOnSelect) handleBlur();
|
92
|
+
}
|
93
|
+
|
94
|
+
const mappedOptions = options.filter((option) => {
|
95
|
+
if (props.onSearch) return true;
|
96
|
+
if (!props.isSearchable) return true;
|
97
|
+
if (option.label.toLowerCase().includes(search.toLowerCase())) return true;
|
98
|
+
return false;
|
99
|
+
});
|
100
|
+
|
101
|
+
return (
|
102
|
+
<MultiSelectContainer
|
103
|
+
handleContainerFocus={handleContainerFocus}
|
104
|
+
disabled={disabled}
|
105
|
+
isError={isError}
|
106
|
+
isFocused={isFocused}
|
107
|
+
isLoading={isLoading}
|
108
|
+
readOnly={readOnly}
|
109
|
+
size={size}
|
110
|
+
variant={variant}
|
111
|
+
className={className}
|
112
|
+
prefixExists={!!basePrefix}
|
113
|
+
id={multiSelectId}
|
114
|
+
>
|
115
|
+
<input
|
116
|
+
ref={multiSelectRef}
|
117
|
+
name={name}
|
118
|
+
value={JSON.stringify(forceSelectedOptions)}
|
119
|
+
type="hidden"
|
120
|
+
/>
|
121
|
+
|
122
|
+
{prefix}
|
123
|
+
{LeftIcon && <LeftIcon size={iconSize} strokeWidth={2.5} />}
|
124
|
+
|
125
|
+
<MultiSelectContent size={size}>
|
126
|
+
{forceSelectedOptions.map((value) => (
|
127
|
+
<MultiSelectMark
|
128
|
+
key={value}
|
129
|
+
label={getOptionLabel(value)}
|
130
|
+
value={value}
|
131
|
+
handleChangeValue={handleChangeValue}
|
132
|
+
/>
|
133
|
+
))}
|
134
|
+
|
135
|
+
{forceSelectedOptions.length <= 0 && <p>{placeholder}</p>}
|
136
|
+
</MultiSelectContent>
|
137
|
+
|
138
|
+
<MultiSelectOptionsContainer
|
139
|
+
isFocused={isFocused}
|
140
|
+
isSearchable={isSearchable}
|
141
|
+
onSearch={handleSearch}
|
142
|
+
>
|
143
|
+
{mappedOptions.map(({ label, value }) => (
|
144
|
+
<MultiSelectOption
|
145
|
+
key={value}
|
146
|
+
label={label}
|
147
|
+
value={value}
|
148
|
+
size={size}
|
149
|
+
handleChangeValue={handleChangeValue}
|
150
|
+
optionHasSelected={optionHasSelected}
|
151
|
+
/>
|
152
|
+
))}
|
153
|
+
|
154
|
+
{mappedOptions.length <= 0 && <p>{notFoundText}</p>}
|
155
|
+
</MultiSelectOptionsContainer>
|
156
|
+
|
157
|
+
<MultiSelectChevron
|
158
|
+
disabled={disabled}
|
159
|
+
isFocused={isFocused}
|
160
|
+
readOnly={readOnly}
|
161
|
+
iconSize={iconSize}
|
162
|
+
isLoading={isLoading}
|
163
|
+
/>
|
164
|
+
|
165
|
+
<MultiSelectSpinner iconSize={iconSize} isLoading={isLoading} />
|
166
|
+
<MultiSelectOverlay handleBlur={handleBlur} isFocused={isFocused} />
|
167
|
+
</MultiSelectContainer>
|
168
|
+
);
|
169
|
+
}
|
170
|
+
|
171
|
+
export { MultiSelect };
|
package/src/index.ts
CHANGED
@@ -25,6 +25,7 @@ export { FormController, FormError, FormLabel } from "./components/Form";
|
|
25
25
|
export { IconButton } from "./components/IconButton";
|
26
26
|
export { ImageUpload } from "./components/ImageUpload";
|
27
27
|
export { Input } from "./components/Input";
|
28
|
+
export { MultiSelect } from "./components/MultiSelect";
|
28
29
|
export { RadioBox, RadioGroup } from "./components/Radio";
|
29
30
|
export { RichText } from "./components/RichText";
|
30
31
|
export { Select } from "./components/Select";
|