superdesk-ui-framework 3.0.1-beta.3 → 3.0.1-beta.6

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.
Files changed (100) hide show
  1. package/app/styles/_sd-tag-input.scss +201 -296
  2. package/app/styles/components/_list-item.scss +13 -1
  3. package/app/styles/components/_sd-photo-preview.scss +1 -1
  4. package/app/styles/design-tokens/_new-colors.scss +1 -1
  5. package/app/styles/form-elements/_forms-general.scss +22 -7
  6. package/app/styles/form-elements/_inputs.scss +133 -54
  7. package/app-typescript/components/Badge.tsx +3 -2
  8. package/app-typescript/components/DatePicker.tsx +40 -52
  9. package/app-typescript/components/DurationInput.tsx +342 -0
  10. package/app-typescript/components/Form/InputBase.tsx +85 -0
  11. package/app-typescript/components/Form/InputNew.tsx +107 -0
  12. package/app-typescript/components/Form/InputWrapper.tsx +79 -0
  13. package/app-typescript/components/Form/index.tsx +3 -0
  14. package/app-typescript/components/Input.tsx +28 -45
  15. package/app-typescript/components/Label.tsx +49 -10
  16. package/app-typescript/components/Layouts/Layout.tsx +1 -1
  17. package/app-typescript/components/Lists/ContentList.tsx +4 -4
  18. package/app-typescript/components/MultiSelect.tsx +37 -50
  19. package/app-typescript/components/Navigation/BottomNav.tsx +3 -2
  20. package/app-typescript/components/Select.tsx +23 -41
  21. package/app-typescript/components/SelectWithTemplate.tsx +32 -7
  22. package/app-typescript/components/TimePicker.tsx +48 -16
  23. package/app-typescript/components/TreeSelect.tsx +423 -195
  24. package/app-typescript/index.ts +4 -1
  25. package/dist/examples.bundle.js +16733 -15633
  26. package/dist/playgrounds/react-playgrounds/CoreLayout.tsx +64 -54
  27. package/dist/playgrounds/react-playgrounds/RundownEditor.tsx +24 -16
  28. package/dist/playgrounds/react-playgrounds/TestGround.tsx +76 -1
  29. package/dist/playgrounds/react-playgrounds/components/Layout.tsx +1 -1
  30. package/dist/react/Badges.tsx +18 -0
  31. package/dist/react/ContentList.tsx +15 -9
  32. package/dist/react/DatePicker.tsx +21 -1
  33. package/dist/react/DurationInput.tsx +104 -0
  34. package/dist/react/Index.tsx +5 -0
  35. package/dist/react/Inputs.tsx +153 -2
  36. package/dist/react/Labels.tsx +51 -1
  37. package/dist/react/MultiSelect.tsx +4 -1
  38. package/dist/react/SelectWithTemplate.tsx +6 -1
  39. package/dist/react/TableList.tsx +22 -44
  40. package/dist/react/TimePicker.tsx +16 -8
  41. package/dist/react/TreeSelect.tsx +301 -48
  42. package/dist/react/tree-select/TreeSelect.tsx +273 -0
  43. package/dist/react/tree-select/example-1.tsx +71 -0
  44. package/dist/react/tree-select/example-2.tsx +59 -0
  45. package/dist/superdesk-ui.bundle.css +413 -370
  46. package/dist/superdesk-ui.bundle.js +15879 -14782
  47. package/dist/vendor.bundle.js +27 -27
  48. package/examples/pages/playgrounds/react-playgrounds/CoreLayout.tsx +64 -54
  49. package/examples/pages/playgrounds/react-playgrounds/RundownEditor.tsx +24 -16
  50. package/examples/pages/playgrounds/react-playgrounds/TestGround.tsx +76 -1
  51. package/examples/pages/playgrounds/react-playgrounds/components/Layout.tsx +1 -1
  52. package/examples/pages/react/Badges.tsx +18 -0
  53. package/examples/pages/react/ContentList.tsx +15 -9
  54. package/examples/pages/react/DatePicker.tsx +21 -1
  55. package/examples/pages/react/DurationInput.tsx +104 -0
  56. package/examples/pages/react/Index.tsx +5 -0
  57. package/examples/pages/react/Inputs.tsx +153 -2
  58. package/examples/pages/react/Labels.tsx +51 -1
  59. package/examples/pages/react/MultiSelect.tsx +4 -1
  60. package/examples/pages/react/SelectWithTemplate.tsx +6 -1
  61. package/examples/pages/react/TableList.tsx +22 -44
  62. package/examples/pages/react/TimePicker.tsx +16 -8
  63. package/examples/pages/react/TreeSelect.tsx +301 -48
  64. package/examples/pages/react/tree-select/TreeSelect.tsx +273 -0
  65. package/examples/pages/react/tree-select/example-1.tsx +71 -0
  66. package/examples/pages/react/tree-select/example-2.tsx +59 -0
  67. package/package.json +2 -1
  68. package/patches/@superdesk+primereact+5.0.2-4.patch +10 -1
  69. package/react/components/Badge.d.ts +1 -0
  70. package/react/components/Badge.js +2 -2
  71. package/react/components/DatePicker.d.ts +1 -0
  72. package/react/components/DatePicker.js +6 -22
  73. package/react/components/DurationInput.d.ts +41 -0
  74. package/react/components/DurationInput.js +303 -0
  75. package/react/components/Form/InputBase.d.ts +42 -0
  76. package/react/components/Form/InputBase.js +72 -0
  77. package/react/components/Form/InputNew.d.ts +45 -0
  78. package/react/components/Form/InputNew.js +75 -0
  79. package/react/components/Form/InputWrapper.d.ts +28 -0
  80. package/react/components/Form/InputWrapper.js +91 -0
  81. package/react/components/Form/index.d.ts +3 -0
  82. package/react/components/Form/index.js +7 -1
  83. package/react/components/Input.js +5 -34
  84. package/react/components/Label.d.ts +1 -0
  85. package/react/components/Label.js +18 -2
  86. package/react/components/Layouts/Layout.js +1 -1
  87. package/react/components/Lists/ContentList.d.ts +45 -0
  88. package/react/components/Lists/ContentList.js +85 -0
  89. package/react/components/Navigation/BottomNav.d.ts +1 -0
  90. package/react/components/Navigation/BottomNav.js +2 -2
  91. package/react/components/Select.d.ts +1 -1
  92. package/react/components/Select.js +4 -26
  93. package/react/components/SelectWithTemplate.d.ts +11 -1
  94. package/react/components/SelectWithTemplate.js +19 -10
  95. package/react/components/TimePicker.d.ts +15 -2
  96. package/react/components/TimePicker.js +15 -4
  97. package/react/components/TreeSelect.d.ts +75 -0
  98. package/react/components/TreeSelect.js +448 -0
  99. package/react/index.d.ts +4 -0
  100. package/react/index.js +10 -3
@@ -1,46 +1,79 @@
1
- import { any } from "prop-types";
2
1
  import * as React from "react";
3
2
  import { Icon } from "./Icon";
3
+ import { Loader } from "./Loader";
4
+ import classNames from 'classnames';
5
+ import nextId from "react-id-generator";
4
6
 
5
7
  interface IState<T> {
6
- value?: Array<T>;
7
- options?: Array<T>;
8
- firstBranchOptions?: Array<T>; // to return on first branch in dropdown
9
- getLabel?: string; // label from props
10
- getMultilevelArray?: string; // array from props
11
- openDropdown?: boolean; // open/close dropdown
12
- activeTree?: Array<any>; // for filtered array
13
- filterArr?: Array<T>; // for filtered array
14
- searchFieldValue?: string; // filter value of input
15
- buttonTree?: Array<any>; // array of button (for backButton)
16
- buttonValue?: Array<any>; // button for name of category
17
- buttonMouseEvent?: boolean; // valueButton hover
18
- selectBranchWithChildren?: boolean;
8
+ value: Array<T>;
9
+ options: Array<ITreeNode<T>>;
10
+ firstBranchOptions: Array<any>; // to return on first branch in dropdown
11
+ openDropdown: boolean; // open/close dropdown
12
+ activeTree: Array<any>; // for filtered array
13
+ filterArr: Array<any>; // for filtered array
14
+ searchFieldValue: string; // filter value of input
15
+ buttonTree: Array<any>; // array of button (for backButton)
16
+ buttonValue: any; // button for name of category
17
+ buttonMouseEvent: boolean; // valueButton hover
18
+ loading: boolean;
19
+ invalid: boolean;
19
20
  }
20
21
 
21
- interface IProps<T> {
22
+ interface IPropsBase<T> {
22
23
  value?: Array<T>;
23
- options: Array<T>;
24
- getLabel: string;
25
- getMultilevelArray: string;
26
24
  selectBranchWithChildren?: boolean;
27
- readonly?: boolean;
25
+ readOnly?: boolean;
28
26
  width?: string;
27
+ allowMultiple?: boolean;
28
+ loading?: boolean;
29
+ singleLevelSearch?: boolean;
30
+ placeholder?: string;
31
+ invalid?: boolean;
32
+ inlineLabel?: boolean;
33
+ labelHidden?: boolean;
34
+ tabindex?: number;
35
+ fullWidth?: boolean;
36
+ info?: string;
37
+ error?: string;
38
+ required?: boolean;
39
+ label?: string;
40
+ disabled?: boolean;
41
+ getLabel(item: T): string;
42
+ getId(item: T): string;
29
43
  optionTemplate?(item: T): React.ComponentType<T> | JSX.Element;
30
44
  valueTemplate?(item: T): React.ComponentType<T> | JSX.Element;
31
- onChange(): void;
45
+ onChange(e: Array<T>): void;
46
+ }
47
+
48
+ interface IPropsSync<T> extends IPropsBase<T> {
49
+ kind: 'synchronous';
50
+ getOptions(): Array<ITreeNode<T>>;
51
+ }
52
+
53
+ interface IPropsAsync<T> extends IPropsBase<T> {
54
+ kind: 'asynchronous';
55
+ getOptions?(): Array<ITreeNode<T>>;
56
+ searchOptions(term: string, callback?: (options: Array<ITreeNode<T>>) => void): any;
57
+ }
58
+
59
+ type IProps<T> = IPropsSync<T> | IPropsAsync<T>;
60
+
61
+ export interface ITreeNode<T> {
62
+ value: T;
63
+ children?: Array<ITreeNode<T>>;
32
64
  }
33
65
 
34
66
  export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
35
- private dropdownRef: any;
36
- private openDropdownRef: any;
67
+ private dropdownRef: React.RefObject<HTMLInputElement>;
68
+ private openDropdownRef: React.RefObject<HTMLButtonElement>;
69
+ private htmlId: string = nextId();
37
70
 
38
71
  constructor(props: IProps<T>) {
39
72
  super(props);
40
73
  this.state = {
41
74
  value: this.props.value ? this.props.value : [],
42
- options: this.props.options ? this.props.options : [],
43
- firstBranchOptions: this.props.options ? this.props.options : [],
75
+ options: this.props.getOptions ? this.props.getOptions() : [],
76
+ firstBranchOptions: this.props.getOptions ? this.props.getOptions() : [],
44
77
  activeTree: [],
45
78
  filterArr: [],
46
79
  searchFieldValue: '',
@@ -48,6 +81,8 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
48
81
  buttonValue: [],
49
82
  buttonMouseEvent: false,
50
83
  openDropdown: false,
84
+ loading: this.props.loading ? this.props.loading : false,
85
+ invalid: this.props.invalid ? this.props.invalid : false,
51
86
  };
52
87
 
53
88
  this.removeClick = this.removeClick.bind(this);
@@ -55,62 +90,62 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
55
90
  this.backButton = this.backButton.bind(this);
56
91
  this.handleButton = this.handleButton.bind(this);
57
92
  this.backButtonValue = this.backButtonValue.bind(this);
93
+ this.handleTree = this.handleTree.bind(this);
94
+ this.filteredItem = this.filteredItem.bind(this);
95
+ this.banchButton = this.banchButton.bind(this);
58
96
  this.dropdownRef = React.createRef();
59
97
  this.openDropdownRef = React.createRef();
60
98
  }
61
99
 
62
100
  removeClick(i: number) {
63
101
  let newTags = this.state.value;
64
- newTags.splice(i, 1);
102
+ newTags?.splice(i, 1);
65
103
 
66
104
  this.setState({
67
105
  value: newTags,
68
106
  });
69
- this.props.onChange();
107
+ this.props.onChange(this.state.value);
70
108
  }
71
109
 
72
- handleMultiLevel(event, item) {
73
- if (item[this.props.getMultilevelArray]) {
110
+ handleMultiLevel(item: ITreeNode<T>) {
111
+ if (item.children) {
74
112
  this.setState({
75
113
  activeTree: [...this.state.activeTree, this.state.options],
76
- options: item[this.props.getMultilevelArray],
114
+ options: item.children,
77
115
  });
78
116
  }
79
117
  }
80
118
 
81
- handleButton(event, item) {
119
+ handleButton(item: ITreeNode<T>) {
82
120
  this.setState({
83
121
  buttonTree: [...this.state.buttonTree, this.state.buttonValue],
84
122
  buttonValue: item,
85
123
  });
86
124
  }
87
125
 
88
- handleValue(event, item) {
89
- let checkItem = this.state.value.find((valueItem: any) => {
90
- return valueItem === item;
126
+ handleValue(event: React.MouseEvent<HTMLLIElement, MouseEvent>, item: ITreeNode<T>) {
127
+ if (this.props.allowMultiple) {
128
+ let checkItem = this.state.value.find((valueItem: T) => {
129
+ return this.props.getId(valueItem) === this.props.getId(item.value);
91
130
  });
92
-
93
131
  if (!checkItem) {
94
- this.setState({value: [...this.state.value, item]});
132
+ this.setState({value: [...this.state.value, item.value]});
95
133
  }
96
-
97
134
  if (!event.ctrlKey) {
98
- this.setState({options: this.state.firstBranchOptions, activeTree: [], openDropdown: false});
135
+ if (this.props.getOptions) {
136
+ this.setState({options: this.state.firstBranchOptions, activeTree: [], openDropdown: false});
137
+ } else {
138
+ this.setState({activeTree: [], openDropdown: false});
139
+ }
99
140
  }
100
141
  this.setState({buttonMouseEvent: false});
101
- }
102
-
103
- handleBranchValue(event, item) {
104
- if (this.props.selectBranchWithChildren) {
105
-
106
- let checkItem = this.state.value.find((valueItem: any) => {
107
- return valueItem === item;
142
+ } else {
143
+ let checkItem = this.state.value.find((valueItem: T) => {
144
+ return this.props.getId(valueItem) === this.props.getId(item.value);
108
145
  });
109
-
110
146
  if (!checkItem) {
111
- this.setState({value: [...this.state.value, item]});
147
+ this.setState({value: [item.value]});
112
148
  }
113
-
114
149
  if (!event.ctrlKey) {
115
150
  this.setState({options: this.state.firstBranchOptions, activeTree: [], openDropdown: false});
116
151
  }
@@ -118,11 +153,86 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
118
153
  }
119
154
  }
120
155
 
156
+ handleBranchValue(event: React.MouseEvent<HTMLButtonElement, MouseEvent>, item: ITreeNode<T>) {
157
+ if (this.props.allowMultiple) {
158
+ if (this.props.selectBranchWithChildren) {
159
+ let checkItem = this.state.value.find((valueItem: T) => {
160
+ return this.props.getId(valueItem) === this.props.getId(item.value);
161
+ });
162
+ if (!checkItem) {
163
+ this.setState({value: [...this.state.value, item.value]});
164
+ }
165
+ if (!event.ctrlKey) {
166
+ this.setState({options: this.state.firstBranchOptions, activeTree: [], openDropdown: false});
167
+ }
168
+ this.setState({buttonMouseEvent: false});
169
+ }
170
+ } else {
171
+ if (this.props.selectBranchWithChildren) {
172
+ let checkItem = this.state.value.find((valueItem: T) => {
173
+ return this.props.getId(valueItem) === this.props.getId(item.value);
174
+ });
175
+ if (!checkItem) {
176
+ this.setState({value: [item.value]});
177
+ }
178
+ if (!event.ctrlKey) {
179
+ this.setState({options: this.state.firstBranchOptions, activeTree: [], openDropdown: false});
180
+ }
181
+ this.setState({buttonMouseEvent: false});
182
+ }
183
+ }
184
+ }
185
+
186
+ handleTree(event: React.MouseEvent<HTMLLIElement, MouseEvent>, option: ITreeNode<T>) {
187
+ if (option.children) {
188
+ this.handleButton(option);
189
+ this.handleMultiLevel(option);
190
+ if (event.altKey && this.props.allowMultiple) {
191
+ if (this.props.selectBranchWithChildren) {
192
+ let filteredItems: Array<T> = [];
193
+ option.children.forEach((item: { value: T; }) => {
194
+ if (!this.state.value.includes(item.value)) {
195
+ filteredItems.push(item.value);
196
+ }
197
+ });
198
+ this.setState({
199
+ value: [...this.state.value, ...filteredItems],
200
+ options: this.state.firstBranchOptions,
201
+ openDropdown: false,
202
+ activeTree: [],
203
+ });
204
+ } else {
205
+ let filteredItems: Array<T> = [];
206
+ option.children.forEach((item: ITreeNode<T>) => {
207
+ if (!this.state.value.includes(item.value)
208
+ && !item.children) {
209
+ filteredItems.push(item.value);
210
+ }
211
+ });
212
+ if (filteredItems.length > 0) {
213
+ this.setState({
214
+ value: [...this.state.value, ...filteredItems],
215
+ options: this.state.firstBranchOptions,
216
+ openDropdown: false,
217
+ activeTree: [],
218
+ });
219
+ }
220
+ }
221
+ }
222
+ } else {
223
+ this.handleValue(event, option);
224
+ if (!event.ctrlKey) {
225
+ this.setState({openDropdown: false});
226
+ }
227
+ }
228
+ }
229
+
121
230
  backButton = () => {
122
231
  if (this.state.activeTree.length > 0) {
123
232
  this.setState({
124
233
  options: this.state.activeTree.pop(),
125
234
  });
235
+ return;
126
236
  } else {
127
237
  return false;
128
238
  }
@@ -134,28 +244,18 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
134
244
  });
135
245
  }
136
246
 
137
- recursion(arr) {
138
- if (this.props.selectBranchWithChildren) {
139
- arr.map((item: any) => {
140
- this.state.filterArr.push(item);
141
- if (item[this.props.getMultilevelArray]) {
142
- this.recursion(item[this.props.getMultilevelArray]);
143
- }
144
- });
145
- } else {
146
- arr.map((item: any) => {
147
- if (!item[this.props.getMultilevelArray]) {
148
- this.state.filterArr.push(item);
149
- } else {
150
- this.recursion(item[this.props.getMultilevelArray]);
151
- }
152
- });
153
- }
247
+ recursion(arr: Array<ITreeNode<T>>) {
248
+ arr.map((item) => {
249
+ this.state.filterArr.push(item);
250
+ if (item.children) {
251
+ this.recursion(item.children);
252
+ }
253
+ });
154
254
  }
155
255
 
156
256
  componentDidMount = () => {
157
257
  this.recursion(this.state.options);
158
- document.addEventListener("mousedown", (event) => {
258
+ document.addEventListener("mousedown", (event: any) => {
159
259
  if ((this.dropdownRef.current && !this.dropdownRef.current.contains(event.target))
160
260
  && (this.openDropdownRef.current && !this.openDropdownRef.current.contains(event.target))) {
161
261
  this.setState({openDropdown: false});
@@ -165,165 +265,293 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
165
265
 
166
266
  componentDidUpdate(prevProps: Readonly<IProps<T>>, prevState: Readonly<IState<T>>): void {
167
267
  if (prevState.value !== this.state.value) {
168
- this.props.onChange();
268
+ this.props.onChange(this.state.value);
169
269
  } else if (prevProps.value !== this.props.value) {
170
- this.props.onChange();
270
+ this.props.onChange(this.state.value);
271
+ }
272
+ }
273
+
274
+ filteredItem(arr: Array<ITreeNode<T>>) {
275
+ if (this.props.kind === 'synchronous') {
276
+ return arr.filter((item) => {
277
+ if (this.state.searchFieldValue) {
278
+ if (this.props.getLabel(item.value)
279
+ .toLowerCase().includes(this.state.searchFieldValue.toLowerCase())) {
280
+ return item.value;
281
+ } else {
282
+ return;
283
+ }
284
+ } else {
285
+ return item.value;
286
+ }
287
+ }).map((option, i) => {
288
+ let selectedItem = this.state.value.some((obj) =>
289
+ this.props.getId(obj) === this.props.getId(option.value),
290
+ );
291
+ return <li key={i}
292
+ className={`suggestion-item suggestion-item--multi-select`}
293
+ onClick={(event) => {
294
+ this.setState({
295
+ searchFieldValue: '',
296
+ }),
297
+ event.preventDefault();
298
+ event.stopPropagation();
299
+ this.handleTree(event, option);
300
+ }}>
301
+ {this.props.optionTemplate
302
+ ? this.props.optionTemplate(option.value)
303
+ : <span
304
+ className={selectedItem
305
+ ? 'suggestion-item--disabled' : undefined}
306
+ >
307
+ {this.props.getLabel(option.value)}
308
+ </span>}
309
+ {option.children && <span className="suggestion-item__icon">
310
+ <Icon name="chevron-right-thin"></Icon>
311
+ </span>}
312
+ </li>;
313
+ });
314
+ } else if (this.props.kind === 'asynchronous') {
315
+ return this.state.options.map((item, i) => {
316
+ let selectedItem = this.state.value.some((obj) =>
317
+ this.props.getId(obj) === this.props.getId(item.value),
318
+ );
319
+ return (
320
+ <li key={i}
321
+ className={`suggestion-item suggestion-item--multi-select`}
322
+ onClick={(event) => {
323
+ this.handleValue(event, item);
324
+ }}>
325
+ {this.props.optionTemplate
326
+ ? this.props.optionTemplate(item.value)
327
+ : <span
328
+ className={selectedItem
329
+ ? 'suggestion-item--disabled' : undefined}
330
+ >
331
+ {this.props.getLabel(item.value)}
332
+ </span>}
333
+ </li>
334
+ );
335
+ });
336
+ } else {
337
+ return;
338
+ }
339
+ }
340
+
341
+ banchButton() {
342
+ let selectedButton = this.state.value.some((obj) =>
343
+ this.props.getId(obj) === this.props.getId(this.state.buttonValue.value),
344
+ );
345
+
346
+ if (!selectedButton) {
347
+ return <button
348
+ className={'autocomplete__button' + (this.props.selectBranchWithChildren ? ' autocomplete__button--multi-select' : '')}
349
+ onMouseOver={() => this.setState({buttonMouseEvent: true})}
350
+ onMouseOut={() => this.setState({buttonMouseEvent: false})}
351
+ value={this.state.buttonValue}
352
+ onClick={(event) => this.handleBranchValue(event, this.state.buttonValue)}>
353
+ Choose entire category
354
+ </button>;
355
+ } else {
356
+ return <button
357
+ className={'autocomplete__button' + (this.props.selectBranchWithChildren ? ' autocomplete__button--multi-select' : '') + ' autocomplete__button--disabled'}
358
+ value={this.state.buttonValue}>
359
+ Category selected
360
+ </button>;
171
361
  }
172
362
  }
173
363
 
174
364
  render() {
175
365
 
366
+ const labelClasses = classNames('sd-input__label', {
367
+ 'a11y-only': this.props.labelHidden,
368
+ });
369
+
370
+ const classesLabel = classNames('sd-input', {
371
+ 'sd-input--inline-label': this.props.inlineLabel,
372
+ 'sd-input--required': this.props.required,
373
+ 'sd-input--disabled': this.props.disabled,
374
+ 'sd-input--full-width': this.props.fullWidth,
375
+ 'sd-input--invalid': this.props.invalid || this.state.invalid,
376
+ });
377
+
176
378
  return (
177
- <div className="tags-input tags-input--multiselect">
178
- <div className="tags-input__tags">
179
- {this.props.readonly
180
- || <button ref={this.openDropdownRef}
181
- className="tags-input__add-button"
182
- onClick={() => this.setState({openDropdown: !this.state.openDropdown})}>
183
- <i className="icon-plus-large"></i>
184
- </button>}
185
- <ul className="tags-input__tag-list">
186
- {this.state.value.map((item, i) => {
187
- return <React.Fragment key={i}>
188
- <li
189
- className={"tags-input__tag-item tags-input__tag-item-multiselect"
190
- + (this.props.readonly ? ' tags-input__tag-item-readonly' : '')}
191
- onClick={(event) => this.props.readonly || this.removeClick(i)}>
192
- <span className="tags-input__helper-box">
193
- {this.props.valueTemplate
194
- ? this.props.valueTemplate(item)
195
- : <span>{item[this.props.getLabel]}</span>}
196
- {this.props.readonly
197
- || <span className="tags-input__remove-button">
198
- <i className="icon-close-small"></i>
199
- </span>}
200
- </span>
201
- </li>
202
- </React.Fragment>;
203
- })}
204
- </ul>
205
- </div>
379
+ <div className={classesLabel}>
380
+ <label className={labelClasses} htmlFor={this.htmlId} id={this.htmlId + 'label'}
381
+ tabIndex={this.props.tabindex === undefined ? undefined : -1}>
382
+ {this.props.label}
383
+ </label>
206
384
 
207
- {this.state.openDropdown &&
208
- <div className={"autocomplete autocomplete-multiselect" + (this.props.width === 'medium' ? ' autocomplete-multiselect-width' : '')} ref={this.dropdownRef}>
209
- <div className='autocomplete__header'>
210
- <div className="autocomplete__icon" onClick={() => {
211
- this.backButtonValue();
212
- this.backButton();
213
- }}>
214
- {this.state.activeTree.length > 0
215
- ? <Icon name="arrow-left" className="arrow-left"></Icon>
216
- : <Icon name="search" className="search"></Icon>}
385
+ <div className={`tags-input tags-input--${this.props.allowMultiple ? 'multi-select' : 'single-select'} sd-input__input`}>
386
+ {this.props.allowMultiple
387
+ ? <div className="tags-input__tags">
388
+ {this.props.readOnly
389
+ || <button ref={this.openDropdownRef}
390
+ className="tags-input__add-button"
391
+ onClick={() => this.setState({openDropdown: !this.state.openDropdown})}>
392
+ <i className="icon-plus-large"></i>
393
+ </button>}
394
+ <ul className="tags-input__tag-list">
395
+ {this.state.value.map((item, i: number) => {
396
+ return <React.Fragment key={i}>
397
+ <li
398
+ className={"tags-input__tag-item tags-input__tag-item--multi-select"
399
+ + (this.props.readOnly ? ' tags-input__tag-item--readonly' : '')}
400
+ onClick={() => this.props.readOnly || this.removeClick(i)}>
401
+ <span className="tags-input__helper-box">
402
+ {this.props.valueTemplate
403
+ ? this.props.valueTemplate(item)
404
+ : <span>{this.props.getLabel(item)}</span>}
405
+ {this.props.readOnly
406
+ || <span className="tags-input__remove-button">
407
+ <Icon name="close-small"></Icon>
408
+ </span>}
409
+ </span>
410
+ </li>
411
+ </React.Fragment>;
412
+ })}
413
+ </ul>
414
+ {this.state.value.length > 0 ?
415
+ this.props.readOnly
416
+ || <button className="tags-input__remove-value"
417
+ onClick={() => this.setState({value: []})}>
418
+ <Icon name='remove-sign'></Icon>
419
+ </button> : null}
217
420
  </div>
218
- <div className='autocomplete__filter'>
219
- {this.state.activeTree.length > 0
220
- ? <button
221
- className={'autocomplete__button' + (this.props.selectBranchWithChildren ? ' autocomplete__button--multiselect' : '')}
222
- onMouseOver={() => this.setState({buttonMouseEvent: true})}
223
- onMouseOut={() => this.setState({buttonMouseEvent: false})}
224
- value={this.state.buttonValue}
225
- onClick={(event) => this.handleBranchValue(event, this.state.buttonValue)}>
226
- {this.state.buttonMouseEvent && this.props.selectBranchWithChildren ? 'Choose entire category' : this.state.buttonValue[this.props.getLabel]}
227
- </button>
228
- : <input
421
+ : <div className="tags-input__tags">
422
+ <button
423
+ className="tags-input__overlay-button"
424
+ ref={this.openDropdownRef}
425
+ onClick={() => this.setState({openDropdown: !this.state.openDropdown})}>
426
+ </button>
427
+ {this.state.value.length < 1 && <span className={ 'tags-input__single-item'
428
+ + (this.props.readOnly ? ' tags-input__tag-item--readonly' : '')}>
429
+ <span className="tags-input__placeholder">
430
+ {this.props.placeholder}
431
+ </span>
432
+ </span>}
433
+ {this.state.value.map((item, i: number) => {
434
+ return <React.Fragment key={i}>
435
+ <span
436
+ className={ 'tags-input__single-item'
437
+ + (this.props.readOnly ? ' tags-input__tag-item--readonly' : '')}
438
+ onClick={() => this.props.readOnly || this.removeClick(i)}>
439
+ <span className="tags-input__helper-box">
440
+ {this.props.valueTemplate
441
+ ? this.props.valueTemplate(item)
442
+ : <span>{this.props.getLabel(item)}</span>}
443
+ {this.props.readOnly
444
+ || <span className="tags-input__remove-button">
445
+ <Icon name='remove-sign'></Icon>
446
+ </span>}
447
+ </span>
448
+ </span>
449
+ </React.Fragment>;
450
+ })}
451
+ </div>
452
+ }
453
+
454
+ {this.state.openDropdown &&
455
+ <div className={"autocomplete autocomplete--multi-select" + (this.props.width === 'medium' ? ' autocomplete--fixed-width' : '')} ref={this.dropdownRef}>
456
+ <div className='autocomplete__header'>
457
+ <div className="autocomplete__icon" onClick={() => {
458
+ this.backButtonValue();
459
+ this.backButton();
460
+ }}>
461
+ <Icon name="search" className="search"></Icon>
462
+ </div>
463
+ <div className='autocomplete__filter'>
464
+ <input
465
+ placeholder={this.props.singleLevelSearch ? 'Search this category...' : 'Search...'}
229
466
  type="text"
230
467
  className="autocomplete__input"
231
- ref={(input: any) => input && input.focus()}
468
+ ref={(input) => input && input.focus()}
232
469
  value={this.state.searchFieldValue}
233
470
  onChange={(event) => {
234
- this.setState({searchFieldValue: event.target.value});
471
+ const value = event.target.value;
472
+ if (this.props.kind === 'asynchronous') {
473
+ this.setState({
474
+ searchFieldValue: value,
475
+ loading: true,
476
+ }),
477
+ this.props.searchOptions(value, (items) => {
478
+ this.setState({options: items, loading: false});
479
+ });
480
+ } else if (this.props.kind === 'synchronous') {
481
+ this.setState({searchFieldValue: value});
482
+ }
235
483
  }}
236
484
  />
237
- }
485
+ </div>
238
486
  </div>
239
- </div>
240
- <ul className="suggestion-list suggestion-list--multi-select">
241
- {this.state.searchFieldValue === ''
242
- ? this.state.options.map((option, i) => {
487
+ {this.state.activeTree.length > 0 &&
488
+ <div className='autocomplete__category-header'>
489
+ <div className="autocomplete__icon" onClick={() => {
490
+ this.backButtonValue();
491
+ this.backButton();
492
+ }}>
493
+ <Icon name="arrow-left" className="arrow-left"></Icon>
494
+ </div>
495
+ <div className='autocomplete__filter'>
496
+ <button
497
+ className={'autocomplete__category-title'}
498
+ value={this.state.buttonValue}>
499
+ {this.props.optionTemplate
500
+ ? this.props.optionTemplate(this.state.buttonValue.value)
501
+ : this.props.getLabel(this.state.buttonValue.value)}
502
+ </button>
503
+ {this.props.selectBranchWithChildren && this.banchButton()}
504
+ </div>
505
+ </div>}
506
+ {this.state.loading
507
+ ? <ul className="suggestion-list--loader"><Loader overlay={true}></Loader></ul>
508
+ :
509
+ this.state.searchFieldValue === ''
510
+ ? this.props.getOptions ?
511
+ <ul className="suggestion-list suggestion-list--multi-select">
512
+ {this.state.options
513
+ .map((option, i: React.Key | undefined) => {
514
+ let selectedItem = this.state.value.some((obj) =>
515
+ this.props.getId(obj) === this.props.getLabel(option.value),
516
+ );
243
517
  return (
244
518
  <li key={i}
245
519
  className={`suggestion-item suggestion-item--multi-select`}
246
520
  onClick={(event) => {
247
521
  event.preventDefault();
248
522
  event.stopPropagation();
249
- if (option[this.props.getMultilevelArray]) {
250
- this.handleButton(event, option);
251
- this.handleMultiLevel(event, option);
252
- if (event.altKey) {
253
- if (this.props.selectBranchWithChildren) {
254
- let filteredItems = option[this.props.getMultilevelArray]
255
- .filter((item) => {
256
- if (!this.state.value.includes(item)) {
257
- return item;
258
- }
259
- });
260
- this.setState({
261
- value: [...this.state.value, ...filteredItems],
262
- options: this.state.firstBranchOptions,
263
- openDropdown: false,
264
- activeTree: [],
265
- });
266
- } else {
267
- let filteredItems = option[this.props.getMultilevelArray]
268
- .filter((item) => {
269
- if (!this.state.value.includes(item)
270
- && !item[this.props.getMultilevelArray]) {
271
- return item;
272
- }
273
- });
274
- if (filteredItems.length > 0) {
275
- this.setState({
276
- value: [...this.state.value, ...filteredItems],
277
- options: this.state.firstBranchOptions,
278
- openDropdown: false,
279
- activeTree: [],
280
- });
281
- }
282
- }
283
- }
284
- } else {
285
- this.handleValue(event, option);
286
- if (!event.ctrlKey) {
287
- this.setState({openDropdown: false});
288
- }
289
- }
523
+ this.handleTree(event, option);
290
524
  }}>
291
525
  {this.props.optionTemplate
292
- ? this.props.optionTemplate(option)
293
- : <span className={this.state.value.includes(option) ? 'suggestion-item--disabled' : null}>
294
- {option[this.props.getLabel]}
526
+ ? this.props.optionTemplate(option.value)
527
+ : <span
528
+ className={selectedItem
529
+ ? 'suggestion-item--disabled' : undefined}
530
+ >
531
+ {this.props.getLabel(option.value)}
295
532
  </span>}
296
- {option[this.props.getMultilevelArray] && <span className="suggestion-item__icon">
533
+ {option.children && <span className="suggestion-item__icon">
297
534
  <Icon name="chevron-right-thin"></Icon>
298
535
  </span>}
299
536
  </li>
300
537
  );
301
- })
302
- : this.state.filterArr.filter((item: any) => {
303
- if (this.state.searchFieldValue) {
304
- if (item[this.props.getLabel]
305
- .toLowerCase().includes(this.state.searchFieldValue.toLowerCase())) {
306
- return item;
307
- }
308
- } else {
309
- return item;
310
- }
311
- }).map((item, i) => {
312
- return <li key={i}
313
- className={`suggestion-item suggestion-item--multi-select`}
314
- onClick={(event) => {
315
- this.handleValue(event, item);
316
- }}>
317
- <span
318
- className={this.state.value.includes(item)
319
- ? 'suggestion-item--disabled' : null}>
320
- {item[this.props.getLabel]}
321
- </span>
322
- </li>;
323
- })
324
- }
325
- </ul>
326
- </div>}
538
+ })}</ul> : null
539
+ :
540
+ <ul className="suggestion-list suggestion-list--multi-select">
541
+ {this.filteredItem(this.props.singleLevelSearch
542
+ ? this.state.options : this.state.filterArr)}
543
+ </ul>
544
+ }
545
+ </div>}
546
+ </div>
547
+
548
+ <div className='sd-input__message-box'>
549
+ {this.props.info && !this.props.invalid && !this.state.invalid ?
550
+ <div className='sd-input__hint'>{this.props.info}</div> : null}
551
+ {this.props.invalid || this.state.invalid ?
552
+ <div className='sd-input__message'>{this.props.error}</div>
553
+ : null}
554
+ </div>
327
555
  </div>
328
556
  );
329
557
  }