@dvrd/dvr-controls 1.0.79 → 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dvrd/dvr-controls",
3
- "version": "1.0.79",
3
+ "version": "1.0.82",
4
4
  "description": "Custom web controls",
5
5
  "main": "index.ts",
6
6
  "files": [
@@ -31,7 +31,7 @@ interface Props {
31
31
  onChange: ChangeFunction<IDate>;
32
32
  closeOnChange?: boolean;
33
33
  value: IDate | null;
34
- label: string;
34
+ label?: string;
35
35
  timeMode?: DatePickerTimeMode;
36
36
  error?: ErrorType;
37
37
  className?: string;
@@ -111,7 +111,7 @@ function DvrdDatePicker(props: Props, ref: ForwardedRef<DVRDDatePickerRef>) {
111
111
  return (
112
112
  <div className={classNames('dvrd-date-picker', className, error && 'error', disabled && 'disabled')}>
113
113
  {!pickersOnly && <>
114
- <label className='field-label'>{label}</label>
114
+ {!!label && <label className='field-label'>{label}</label>}
115
115
  {renderMobilePicker()}
116
116
  <div className={classNames('value-container', useMobileNative && 'no-mob')} onClick={onClickContainer}>
117
117
  <label className={classNames('value', !value && 'placeholder')}>{value?.format(dateFormat) ??
@@ -531,6 +531,10 @@ function TimePicker(props: TimePickerProps) {
531
531
  )
532
532
  }
533
533
 
534
+ useEffect(() => {
535
+ if (value) internalValue.current = value;
536
+ }, [value]);
537
+
534
538
  return (
535
539
  <WithBackground active={open} onClose={onClose}>
536
540
  <div className='picker time'>
@@ -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
- {hasValue ?
132
- canRender ? renderCustomValue(selectedLabel as React.ReactElement) :
133
- <label className='grouped-select-value'>{selectedLabel}</label> :
134
- <label className={classNames('grouped-select-placeholder', placeholderHidden && 'hidden')}>
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 renderCustomValue(value: React.ReactElement) {
144
- return React.cloneElement(value, {
145
- ...value.props,
146
- className: classNames(value.props?.className, 'grouped-select-value')
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
- {items.map(renderItem())}
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 {