@dvrd/dvr-controls 1.0.81 → 1.0.82
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
|
@@ -8,6 +8,7 @@ import {ErrorType, GroupedSelectItem, SelectValueType} from "../util/interfaces"
|
|
|
8
8
|
import classNames from 'classnames';
|
|
9
9
|
import AwesomeIcon from "../icon/awesomeIcon";
|
|
10
10
|
import defer from 'lodash.defer';
|
|
11
|
+
import {stopPropagation} from "../util/controlUtil";
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
interface Props {
|
|
@@ -27,6 +28,7 @@ interface Props {
|
|
|
27
28
|
maxItemsHeight?: number | string;
|
|
28
29
|
highlightSelected?: true;
|
|
29
30
|
placeholder?: string;
|
|
31
|
+
searchable?: boolean;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export type GroupedSelectRef = { open: VoidFunction; close: VoidFunction; toggle: (forcedValue?: boolean) => void };
|
|
@@ -42,12 +44,19 @@ function findInItems(items: Array<GroupedSelectItem>, value: string | number): G
|
|
|
42
44
|
return null;
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
function itemMatchesSearch(item: GroupedSelectItem, search: string): boolean {
|
|
48
|
+
if (!search) return true;
|
|
49
|
+
return item.label.toString().toLowerCase().includes(search.toLowerCase());
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
function DVRDGroupedSelect(props: Props, ref: ForwardedRef<GroupedSelectRef>) {
|
|
46
53
|
const {
|
|
47
54
|
className, label, labelClassName, value, placeholder, valueClassName, error, itemContainerClassName,
|
|
48
|
-
onChange, items, itemClassName, disabled, errorClassName, highlightSelected
|
|
55
|
+
onChange, items, itemClassName, disabled, errorClassName, highlightSelected, searchable
|
|
49
56
|
} = props;
|
|
50
57
|
const [open, setOpen] = useState(false);
|
|
58
|
+
const [search, setSearch] = useState('');
|
|
59
|
+
const [searchActive, setSearchActive] = useState(false);
|
|
51
60
|
const maxItemsHeight: string | undefined = useMemo(() => {
|
|
52
61
|
const {maxItemsHeight} = props;
|
|
53
62
|
if (maxItemsHeight) {
|
|
@@ -71,8 +80,36 @@ function DVRDGroupedSelect(props: Props, ref: ForwardedRef<GroupedSelectRef>) {
|
|
|
71
80
|
longest = item.label.toString();
|
|
72
81
|
return longest;
|
|
73
82
|
}, [items]);
|
|
83
|
+
const availableItems = useMemo(() => {
|
|
84
|
+
if (!searchable || !search.trim()) return items;
|
|
85
|
+
const _items: Array<GroupedSelectItem> = [];
|
|
86
|
+
for (const item of items) {
|
|
87
|
+
if (itemMatchesSearch(item, search)) _items.push(item);
|
|
88
|
+
else if (item.children) {
|
|
89
|
+
if (!!item.children.find((child: GroupedSelectItem) => itemMatchesSearch(child, search)))
|
|
90
|
+
_items.push(item);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return _items;
|
|
94
|
+
}, [items, searchable, search]);
|
|
74
95
|
const itemsContainer = useRef<HTMLDivElement>(null);
|
|
75
96
|
|
|
97
|
+
function onChangeSearch(evt: React.ChangeEvent<HTMLInputElement>) {
|
|
98
|
+
const {value} = evt.target;
|
|
99
|
+
setSearch(value);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function onFocusSearch() {
|
|
103
|
+
if (searchable) {
|
|
104
|
+
setSearchActive(true);
|
|
105
|
+
setOpen(true);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function onBlurSearch() {
|
|
110
|
+
if (searchable) setSearchActive(false);
|
|
111
|
+
}
|
|
112
|
+
|
|
76
113
|
function onSelectItem(item: GroupedSelectItem) {
|
|
77
114
|
return function (evt: React.MouseEvent) {
|
|
78
115
|
evt.stopPropagation();
|
|
@@ -122,36 +159,61 @@ function DVRDGroupedSelect(props: Props, ref: ForwardedRef<GroupedSelectRef>) {
|
|
|
122
159
|
}
|
|
123
160
|
|
|
124
161
|
function renderValue() {
|
|
125
|
-
const hasValue = selectedLabel !== null;
|
|
126
|
-
const canRender = hasValue && !['string', 'number'].includes(typeof selectedLabel)
|
|
127
162
|
const chevIcon = itemsPosition === 'bottom' ? 'chevron-down' : 'chevron-up';
|
|
128
|
-
const placeholderHidden = !placeholder?.length;
|
|
129
163
|
return (
|
|
130
|
-
<div className={classNames('grouped-select-value-container', valueClassName)}>
|
|
131
|
-
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
{placeholderHidden ? 'placeholder' : placeholder}</label>
|
|
136
|
-
}
|
|
164
|
+
<div className={classNames('grouped-select-value-container', searchable && 'searchable', valueClassName)}>
|
|
165
|
+
{renderValueInput()}
|
|
166
|
+
{renderValueCustom()}
|
|
167
|
+
{renderValueLabel()}
|
|
168
|
+
{renderValuePlaceholder()}
|
|
137
169
|
<AwesomeIcon name={chevIcon} className='chev-icon'/>
|
|
138
170
|
<div style={{height: 0, visibility: 'hidden'}}>{longestValue}</div>
|
|
139
171
|
</div>
|
|
140
172
|
);
|
|
141
173
|
}
|
|
142
174
|
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
175
|
+
function renderValueInput() {
|
|
176
|
+
if (!searchable) return null;
|
|
177
|
+
const _value = searchActive ? search : selectedLabel?.toString();
|
|
178
|
+
return (
|
|
179
|
+
<input onChange={onChangeSearch} value={_value} onFocus={onFocusSearch}
|
|
180
|
+
onBlur={onBlurSearch} className='dvrd-grouped-select-search' disabled={disabled}
|
|
181
|
+
onClick={stopPropagation} placeholder={placeholder}/>
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function renderValueCustom() {
|
|
186
|
+
if (selectedLabel === null || searchable) return null; // Nothing selected, or using a search input
|
|
187
|
+
if (['string', 'number'].includes(typeof selectedLabel)) return null; // Not a custom element
|
|
188
|
+
const element = selectedLabel as React.ReactElement;
|
|
189
|
+
return React.cloneElement(element, {
|
|
190
|
+
...element.props,
|
|
191
|
+
className: classNames(element.props?.className, 'grouped-select-value')
|
|
147
192
|
});
|
|
148
193
|
}
|
|
149
194
|
|
|
195
|
+
function renderValueLabel() {
|
|
196
|
+
if (selectedLabel === null || searchable) return null; // Nothing selected, or using a search input
|
|
197
|
+
if (!['string', 'number'].includes(typeof selectedLabel)) return null; // A custom element
|
|
198
|
+
return (
|
|
199
|
+
<label className='grouped-select-value'>{selectedLabel}</label>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function renderValuePlaceholder() {
|
|
204
|
+
if (selectedLabel !== null || searchable) return null; // Something is selected, or we are using a search input
|
|
205
|
+
const placeholderHidden = !placeholder?.length;
|
|
206
|
+
return (
|
|
207
|
+
<label className={classNames('grouped-select-placeholder', placeholderHidden && 'hidden')}>
|
|
208
|
+
{placeholderHidden ? 'placeholder' : placeholder}</label>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
150
212
|
function renderItemsContainer() {
|
|
151
213
|
return (
|
|
152
214
|
<div className={classNames('grouped-select-items', itemContainerClassName)}
|
|
153
215
|
style={{maxHeight: maxItemsHeight}} ref={itemsContainer}>
|
|
154
|
-
{
|
|
216
|
+
{availableItems.map(renderItem())}
|
|
155
217
|
</div>
|
|
156
218
|
)
|
|
157
219
|
}
|
|
@@ -33,6 +33,16 @@
|
|
|
33
33
|
cursor: pointer;
|
|
34
34
|
background-color: white;
|
|
35
35
|
|
|
36
|
+
.dvrd-grouped-select-search {
|
|
37
|
+
width: 100%;
|
|
38
|
+
outline: none;
|
|
39
|
+
border: none;
|
|
40
|
+
padding: .75rem;
|
|
41
|
+
color: #2A435F;
|
|
42
|
+
border-radius: inherit;
|
|
43
|
+
font-family: avenir-light, sans-serif;
|
|
44
|
+
}
|
|
45
|
+
|
|
36
46
|
.grouped-select-placeholder, .grouped-select-value {
|
|
37
47
|
user-select: none;
|
|
38
48
|
vertical-align: middle;
|
|
@@ -55,6 +65,10 @@
|
|
|
55
65
|
transition: transform .2s ease-in-out;
|
|
56
66
|
color: $color-gray-3;
|
|
57
67
|
}
|
|
68
|
+
|
|
69
|
+
&.searchable {
|
|
70
|
+
padding: 0 .75rem 0 0;
|
|
71
|
+
}
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
.grouped-select-items {
|