superdesk-ui-framework 3.1.4 → 3.1.5
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/app/styles/_sd-tag-input.scss +1 -1
- package/app-typescript/components/TreeSelect/TreeSelect.tsx +118 -38
- package/app-typescript/components/TreeSelect/TreeSelectPill.tsx +20 -9
- package/dist/examples.bundle.js +78139 -78057
- package/dist/react/TreeSelect.tsx +61 -3
- package/dist/superdesk-ui.bundle.css +1 -1
- package/dist/superdesk-ui.bundle.js +78175 -78103
- package/examples/pages/react/TreeSelect.tsx +61 -3
- package/package.json +1 -1
- package/react/components/TreeSelect/TreeSelect.d.ts +5 -0
- package/react/components/TreeSelect/TreeSelect.js +80 -17
- package/react/components/TreeSelect/TreeSelectPill.d.ts +1 -0
- package/react/components/TreeSelect/TreeSelectPill.js +13 -4
@@ -13,6 +13,15 @@ import {TreeSelectPill} from './TreeSelectPill';
|
|
13
13
|
import {getPrefixedItemId, TreeSelectItem} from './TreeSelectItem';
|
14
14
|
import {keyboardNavigation} from './KeyboardNavigation';
|
15
15
|
import {WithPortal} from '../WithPortal';
|
16
|
+
import { DragDropContext, Droppable, Draggable, DropResult } from "react-beautiful-dnd";
|
17
|
+
|
18
|
+
const reorder = (list: Array<any>, startIndex: number, endIndex: number) => {
|
19
|
+
const result = Array.from(list);
|
20
|
+
const [removed] = result.splice(startIndex, 1);
|
21
|
+
result.splice(endIndex, 0, removed);
|
22
|
+
|
23
|
+
return result;
|
24
|
+
};
|
16
25
|
|
17
26
|
interface IState<T> {
|
18
27
|
value: Array<T>;
|
@@ -42,7 +51,10 @@ interface IPropsBase<T> extends IInputWrapper {
|
|
42
51
|
singleLevelSearch?: boolean;
|
43
52
|
placeholder?: string;
|
44
53
|
searchPlaceholder?: string;
|
54
|
+
noResultsFoundMessage?: string;
|
55
|
+
dropdownInitiallyOpen?: boolean;
|
45
56
|
zIndex?: number;
|
57
|
+
sortable?: boolean;
|
46
58
|
'data-test-id'?: string;
|
47
59
|
getLabel(item: T): string;
|
48
60
|
getId(item: T): string;
|
@@ -112,7 +124,6 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
112
124
|
this.onMouseDown = this.onMouseDown.bind(this);
|
113
125
|
this.onKeyDown = this.onKeyDown.bind(this);
|
114
126
|
this.onPressEsc = this.onPressEsc.bind(this);
|
115
|
-
|
116
127
|
this.dropdownRef = React.createRef();
|
117
128
|
this.ref = React.createRef();
|
118
129
|
this.inputRef = React.createRef();
|
@@ -120,6 +131,7 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
120
131
|
this.openDropdownRef = React.createRef();
|
121
132
|
this.treeSelectRef = React.createRef();
|
122
133
|
this.popperInstance = null;
|
134
|
+
this.onDragEnd = this.onDragEnd.bind(this);
|
123
135
|
}
|
124
136
|
|
125
137
|
inputFocus = () => {
|
@@ -185,6 +197,10 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
185
197
|
document.addEventListener("mousedown", this.onMouseDown);
|
186
198
|
document.addEventListener("keydown", this.onKeyDown);
|
187
199
|
document.addEventListener("keydown", this.onPressEsc);
|
200
|
+
|
201
|
+
if (this.props.dropdownInitiallyOpen) {
|
202
|
+
this.setState({openDropdown: true});
|
203
|
+
}
|
188
204
|
}
|
189
205
|
|
190
206
|
componentWillUnmount(): void {
|
@@ -501,34 +517,39 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
501
517
|
});
|
502
518
|
}
|
503
519
|
} else if (this.props.kind === 'asynchronous') {
|
504
|
-
|
505
|
-
|
506
|
-
this.
|
507
|
-
|
520
|
+
if (this.state.options.length > 0) {
|
521
|
+
return this.state.options.map((item, i) => {
|
522
|
+
let selectedItem = this.state.value.some((obj) =>
|
523
|
+
this.props.getId(obj) === this.props.getId(item.value),
|
524
|
+
);
|
508
525
|
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
526
|
+
return (
|
527
|
+
<li
|
528
|
+
key={i}
|
529
|
+
className='suggestion-item suggestion-item--multi-select'
|
530
|
+
onClick={(event) => {
|
531
|
+
this.handleValue(event, item);
|
532
|
+
}}
|
533
|
+
>
|
534
|
+
<button className="suggestion-item--btn" data-test-id="option">
|
535
|
+
{this.props.optionTemplate
|
536
|
+
? this.props.optionTemplate(item.value)
|
537
|
+
: (
|
538
|
+
<span
|
539
|
+
className={selectedItem ? 'suggestion-item--selected' : undefined}
|
540
|
+
>
|
541
|
+
{this.props.getLabel(item.value)}
|
542
|
+
</span>
|
543
|
+
)
|
544
|
+
}
|
545
|
+
</button>
|
546
|
+
</li>
|
547
|
+
);
|
548
|
+
});
|
549
|
+
|
550
|
+
} else {
|
551
|
+
return <li className="suggestion-item--nothing-found">{this.props.noResultsFoundMessage ?? 'Nothing found'}</li>;
|
552
|
+
}
|
532
553
|
} else {
|
533
554
|
return;
|
534
555
|
}
|
@@ -591,18 +612,31 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
591
612
|
|
592
613
|
if (this.props.kind === 'asynchronous') {
|
593
614
|
if (this.state.searchFieldValue) {
|
594
|
-
this.setState({
|
595
|
-
loading: true,
|
596
|
-
});
|
597
|
-
|
598
615
|
this.ICancelFn = this.props.searchOptions(this.state.searchFieldValue, (items) => {
|
599
616
|
this.setState({options: items, loading: false});
|
600
617
|
this.popperInstance?.update();
|
601
618
|
});
|
619
|
+
} else {
|
620
|
+
this.setState({options: this.state.firstBranchOptions, loading: false});
|
602
621
|
}
|
603
622
|
}
|
604
623
|
}
|
605
624
|
|
625
|
+
onDragEnd(result: DropResult) {
|
626
|
+
if (!result.destination) {
|
627
|
+
return;
|
628
|
+
}
|
629
|
+
|
630
|
+
const value = reorder(
|
631
|
+
this.state.value,
|
632
|
+
result.source.index,
|
633
|
+
result.destination.index,
|
634
|
+
);
|
635
|
+
this.setState({
|
636
|
+
value: value,
|
637
|
+
});
|
638
|
+
}
|
639
|
+
|
606
640
|
render() {
|
607
641
|
if (this.props.preview) {
|
608
642
|
return (
|
@@ -624,6 +658,43 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
624
658
|
);
|
625
659
|
}
|
626
660
|
|
661
|
+
const ListWrapper = this.props.sortable
|
662
|
+
? ({children}: {children: React.ReactNode}) => (
|
663
|
+
<DragDropContext onDragEnd={this.onDragEnd}>
|
664
|
+
<Droppable droppableId="droppable" direction="horizontal">
|
665
|
+
{(provided, _snapshot) => (
|
666
|
+
<ul
|
667
|
+
className="tags-input__tag-list"
|
668
|
+
ref={provided.innerRef}
|
669
|
+
{...provided.droppableProps}
|
670
|
+
>
|
671
|
+
{children}
|
672
|
+
{provided.placeholder}
|
673
|
+
</ul>
|
674
|
+
)}
|
675
|
+
</Droppable>
|
676
|
+
</DragDropContext>
|
677
|
+
)
|
678
|
+
: ({children}: {children: React.ReactNode}) => <ul className="tags-input__tag-list">{children}</ul>;
|
679
|
+
|
680
|
+
const ItemWrapper = this.props.sortable
|
681
|
+
? ({children, itemId, i}: {children: React.ReactNode, itemId: string, i: number}) => {
|
682
|
+
return (
|
683
|
+
<Draggable draggableId={itemId} index={i}>
|
684
|
+
{(provided2) => (
|
685
|
+
<div
|
686
|
+
ref={provided2.innerRef}
|
687
|
+
{...provided2.draggableProps}
|
688
|
+
{...provided2.dragHandleProps}
|
689
|
+
>
|
690
|
+
{children}
|
691
|
+
</div>
|
692
|
+
)}
|
693
|
+
</Draggable>
|
694
|
+
);
|
695
|
+
}
|
696
|
+
: ({children}: {children: React.ReactNode}) => <React.Fragment>{children}</React.Fragment>;
|
697
|
+
|
627
698
|
return (
|
628
699
|
<InputWrapper
|
629
700
|
label={this.props.label}
|
@@ -672,7 +743,7 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
672
743
|
</button>
|
673
744
|
}
|
674
745
|
|
675
|
-
<
|
746
|
+
<ListWrapper>
|
676
747
|
{this.state.value.map((item, i: number) => {
|
677
748
|
const Wrapper: React.ComponentType<{backgroundColor?: string}>
|
678
749
|
= ({backgroundColor, children}) => (
|
@@ -684,25 +755,30 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
684
755
|
backgroundColor={backgroundColor}
|
685
756
|
onRemove={() => this.removeClick(i)}
|
686
757
|
getBackgroundColor={this.props.getBackgroundColor}
|
758
|
+
draggable={this.props.sortable}
|
687
759
|
>
|
688
760
|
{children}
|
689
761
|
</TreeSelectPill>
|
690
762
|
);
|
691
763
|
|
764
|
+
const itemId = this.props.getId(item);
|
765
|
+
|
692
766
|
return (
|
693
|
-
<
|
767
|
+
<ItemWrapper itemId={itemId} key={itemId} i={i}>
|
694
768
|
{this.props.valueTemplate
|
695
769
|
? this.props.valueTemplate(item, Wrapper)
|
696
770
|
: (
|
697
771
|
<Wrapper>
|
698
|
-
<span>
|
772
|
+
<span>
|
773
|
+
{this.props.getLabel(item)}
|
774
|
+
</span>
|
699
775
|
</Wrapper>
|
700
776
|
)
|
701
777
|
}
|
702
|
-
</
|
778
|
+
</ItemWrapper>
|
703
779
|
);
|
704
780
|
})}
|
705
|
-
</
|
781
|
+
</ListWrapper>
|
706
782
|
|
707
783
|
{this.state.value.length > 0
|
708
784
|
? (this.props.readOnly || this.props.disabled)
|
@@ -842,7 +918,11 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
842
918
|
this.ICancelFn();
|
843
919
|
}
|
844
920
|
|
845
|
-
this.setState({
|
921
|
+
this.setState({
|
922
|
+
searchFieldValue: event.target.value,
|
923
|
+
options: [],
|
924
|
+
loading: true,
|
925
|
+
});
|
846
926
|
this.popperInstance?.update();
|
847
927
|
this.debounceFn();
|
848
928
|
} else {
|
@@ -1,4 +1,6 @@
|
|
1
|
+
import classNames from 'classnames';
|
1
2
|
import * as React from "react";
|
3
|
+
import {DragHandle} from '../DragHandle';
|
2
4
|
import {Icon} from "../Icon";
|
3
5
|
import {getTextColor} from '../Label';
|
4
6
|
|
@@ -10,19 +12,19 @@ interface IProps<T> {
|
|
10
12
|
onRemove(): void;
|
11
13
|
valueTemplate?(item: T, Wrapper: React.ElementType): React.ComponentType<T> | JSX.Element;
|
12
14
|
getBackgroundColor?(item: T): string;
|
15
|
+
draggable?: boolean;
|
13
16
|
}
|
14
17
|
|
15
18
|
export class TreeSelectPill<T> extends React.Component<IProps<T>> {
|
16
19
|
render() {
|
20
|
+
const classes = classNames('tags-input__tag-item tags-input__tag-item--multi-select', {
|
21
|
+
'tags-input__tag-item--readonly': this.props.readOnly,
|
22
|
+
'tags-input__tag-item--draggable': this.props.draggable,
|
23
|
+
});
|
24
|
+
|
17
25
|
return (
|
18
26
|
<li
|
19
|
-
className={
|
20
|
-
"tags-input__tag-item tags-input__tag-item--multi-select"
|
21
|
-
+ (this.props.readOnly ? ' tags-input__tag-item--readonly' : '')
|
22
|
-
}
|
23
|
-
onClick={() => (!this.props.readOnly && !this.props.disabled)
|
24
|
-
&& this.props.onRemove()
|
25
|
-
}
|
27
|
+
className={classes}
|
26
28
|
style={
|
27
29
|
this.props.valueTemplate
|
28
30
|
? {backgroundColor: this.props.backgroundColor}
|
@@ -31,6 +33,9 @@ export class TreeSelectPill<T> extends React.Component<IProps<T>> {
|
|
31
33
|
}
|
32
34
|
data-test-id="item"
|
33
35
|
>
|
36
|
+
{this.props.draggable && (
|
37
|
+
<DragHandle blank={true} dotsInRow='3' dotRows='4' />
|
38
|
+
)}
|
34
39
|
<span
|
35
40
|
className="tags-input__helper-box"
|
36
41
|
style={{
|
@@ -43,9 +48,15 @@ export class TreeSelectPill<T> extends React.Component<IProps<T>> {
|
|
43
48
|
{this.props.children}
|
44
49
|
|
45
50
|
{!this.props.readOnly
|
46
|
-
&& <
|
51
|
+
&& <button
|
52
|
+
className="tags-input__remove-button"
|
53
|
+
data-test-id="remove"
|
54
|
+
onClick={() => (!this.props.readOnly && !this.props.disabled)
|
55
|
+
&& this.props.onRemove()
|
56
|
+
}
|
57
|
+
>
|
47
58
|
<Icon name="close-small"></Icon>
|
48
|
-
</
|
59
|
+
</button>
|
49
60
|
}
|
50
61
|
</span>
|
51
62
|
</li>
|