superdesk-ui-framework 2.4.10 → 2.4.15
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/scripts/modals.js +22 -9
- package/app/styles/app.scss +1 -0
- package/app/styles/form-elements/_select-grid.scss +77 -0
- package/app/styles/pr-superdesk-theme.scss +1 -0
- package/app/styles/primereact/_pr-skeleton.scss +35 -0
- package/app-typescript/components/IconPicker.tsx +277 -0
- package/app-typescript/components/ListItemLoader.tsx +30 -0
- package/app-typescript/components/SelectGrid.tsx +233 -0
- package/app-typescript/components/Skeleton.tsx +48 -0
- package/app-typescript/components/Tag.tsx +4 -3
- package/app-typescript/index.ts +5 -0
- package/dist/components/modals.html +1 -0
- package/dist/examples.bundle.js +3051 -1766
- package/dist/react/IconFont.tsx +7 -6
- package/dist/react/IconPicker.tsx +65 -0
- package/dist/react/Index.tsx +16 -1
- package/dist/react/ListItems.tsx +34 -0
- package/dist/react/SelectGrid.tsx +121 -0
- package/dist/react/Tags.tsx +14 -3
- package/dist/superdesk-ui.bundle.css +3356 -0
- package/dist/superdesk-ui.bundle.js +2563 -1480
- package/dist/vendor.bundle.js +49646 -49624
- package/examples/pages/components/modals.html +1 -0
- package/examples/pages/react/IconFont.tsx +7 -6
- package/examples/pages/react/IconPicker.tsx +65 -0
- package/examples/pages/react/Index.tsx +16 -1
- package/examples/pages/react/ListItems.tsx +34 -0
- package/examples/pages/react/SelectGrid.tsx +121 -0
- package/examples/pages/react/Tags.tsx +14 -3
- package/package.json +1 -1
- package/react/components/IconPicker.d.ts +24 -0
- package/react/components/IconPicker.js +283 -0
- package/react/components/ListItemLoader.d.ts +4 -0
- package/react/components/ListItemLoader.js +62 -0
- package/react/components/SelectGrid.d.ts +45 -0
- package/react/components/SelectGrid.js +179 -0
- package/react/components/Skeleton.d.ts +30 -0
- package/react/components/Skeleton.js +55 -0
- package/react/components/Tag.d.ts +2 -1
- package/react/components/Tag.js +3 -3
- package/react/index.d.ts +4 -0
- package/react/index.js +8 -0
@@ -0,0 +1,233 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import nextId from "react-id-generator";
|
3
|
+
import { OverlayPanel } from '@superdesk/primereact/overlaypanel';
|
4
|
+
import { Loader } from "./Loader";
|
5
|
+
|
6
|
+
/**
|
7
|
+
* @ngdoc react
|
8
|
+
* @name SelectGrid
|
9
|
+
* @description searchable select component with grid view of items
|
10
|
+
*/
|
11
|
+
|
12
|
+
export interface IItem {
|
13
|
+
value: string;
|
14
|
+
label: string;
|
15
|
+
[extra: string]: any;
|
16
|
+
}
|
17
|
+
|
18
|
+
interface IProps {
|
19
|
+
getItems(searchString: string | null): Promise<Array<IItem>>;
|
20
|
+
onChange(value: IItem): void;
|
21
|
+
itemTemplate: React.ComponentType<{ item: IItem | null }>;
|
22
|
+
triggerTemplate: React.ComponentType<any>;
|
23
|
+
label: string;
|
24
|
+
filterPlaceholder?: string;
|
25
|
+
}
|
26
|
+
|
27
|
+
interface IState {
|
28
|
+
items: Array<IItem>;
|
29
|
+
loading: boolean;
|
30
|
+
isOpen: boolean;
|
31
|
+
}
|
32
|
+
|
33
|
+
export class SelectGrid extends React.PureComponent<IProps, IState> {
|
34
|
+
htmlId = nextId();
|
35
|
+
buttonContainer: React.RefObject<HTMLDivElement>;
|
36
|
+
overlayPanel: React.RefObject<OverlayPanel>;
|
37
|
+
searchInput: React.RefObject<HTMLInputElement>;
|
38
|
+
gridContainer: React.RefObject<HTMLDivElement>;
|
39
|
+
|
40
|
+
constructor(props: IProps) {
|
41
|
+
super(props);
|
42
|
+
|
43
|
+
this.state = { items: [], loading: true, isOpen: false };
|
44
|
+
|
45
|
+
this.buttonContainer = React.createRef();
|
46
|
+
this.overlayPanel = React.createRef();
|
47
|
+
this.searchInput = React.createRef();
|
48
|
+
this.gridContainer = React.createRef();
|
49
|
+
}
|
50
|
+
|
51
|
+
componentDidMount() {
|
52
|
+
this.props.getItems(null).then((items) => {
|
53
|
+
this.setState({ items, loading: false });
|
54
|
+
});
|
55
|
+
}
|
56
|
+
|
57
|
+
componentWillUnmount() {
|
58
|
+
document.removeEventListener('keydown', this.handleKeydown);
|
59
|
+
}
|
60
|
+
|
61
|
+
togglePopup = (event?: React.SyntheticEvent) => {
|
62
|
+
if (!event) {
|
63
|
+
// @ts-ignore
|
64
|
+
this.overlayPanel.current.hide();
|
65
|
+
} else {
|
66
|
+
// @ts-ignore
|
67
|
+
this.overlayPanel.current.toggle(event);
|
68
|
+
}
|
69
|
+
|
70
|
+
setTimeout(() => {
|
71
|
+
// Now that the popup has (dis)appeared handle items and events
|
72
|
+
|
73
|
+
if (this.state.isOpen) {
|
74
|
+
document.removeEventListener('keydown', this.handleKeydown);
|
75
|
+
// @ts-ignore
|
76
|
+
this.buttonContainer.current.querySelector('button')?.focus();
|
77
|
+
} else {
|
78
|
+
document.addEventListener('keydown', this.handleKeydown);
|
79
|
+
this.loadItems();
|
80
|
+
// @ts-ignore
|
81
|
+
this.searchInput.current.focus();
|
82
|
+
}
|
83
|
+
|
84
|
+
this.setState({ isOpen: !this.state.isOpen });
|
85
|
+
});
|
86
|
+
|
87
|
+
}
|
88
|
+
|
89
|
+
search = (event: React.ChangeEvent<HTMLInputElement>) => {
|
90
|
+
const searchString: string = event.target.value.toLowerCase();
|
91
|
+
this.loadItems(searchString);
|
92
|
+
}
|
93
|
+
|
94
|
+
loadItems = (searchString: string | null = null) => {
|
95
|
+
this.setState({ loading: true });
|
96
|
+
this.props.getItems(searchString).then((items) => {
|
97
|
+
this.setState({ items, loading: false });
|
98
|
+
});
|
99
|
+
}
|
100
|
+
|
101
|
+
select = (item: IItem) => {
|
102
|
+
this.props.onChange(item);
|
103
|
+
this.togglePopup();
|
104
|
+
}
|
105
|
+
|
106
|
+
getItemElement = (index: number): HTMLDivElement | null | undefined => {
|
107
|
+
return this.gridContainer.current?.querySelector(`[data-item-index="${index}"]`);
|
108
|
+
}
|
109
|
+
|
110
|
+
handleKeydown = (event: KeyboardEvent) => {
|
111
|
+
const navKeys = [
|
112
|
+
"Enter",
|
113
|
+
"ArrowRight",
|
114
|
+
"ArrowLeft",
|
115
|
+
"ArrowUp",
|
116
|
+
"ArrowDown",
|
117
|
+
"PageDown",
|
118
|
+
"PageUp",
|
119
|
+
];
|
120
|
+
const activeElement = document.activeElement;
|
121
|
+
|
122
|
+
if (event.code === "Escape") {
|
123
|
+
event.preventDefault();
|
124
|
+
event.stopPropagation();
|
125
|
+
this.togglePopup();
|
126
|
+
} else if (activeElement === this.searchInput?.current) {
|
127
|
+
if (event.code === "ArrowDown") {
|
128
|
+
event.preventDefault();
|
129
|
+
this.getItemElement(0)?.focus();
|
130
|
+
} else if (event.code === "Enter" && this.state.items.length === 1) {
|
131
|
+
event.preventDefault();
|
132
|
+
this.select(this.state.items[0]);
|
133
|
+
}
|
134
|
+
// @ts-ignore
|
135
|
+
} else if (document.activeElement.getAttribute('data-item-index') && navKeys.includes(event.code)) {
|
136
|
+
// @ts-ignore
|
137
|
+
let itemIndex = parseInt(activeElement.getAttribute('data-item-index'), 10);
|
138
|
+
|
139
|
+
// Prevent default behaviour, such as scrolling
|
140
|
+
event.preventDefault();
|
141
|
+
if (event.code === "Enter") {
|
142
|
+
this.select(this.state.items[itemIndex]);
|
143
|
+
return;
|
144
|
+
} else if (event.code === "ArrowRight") {
|
145
|
+
itemIndex += 1;
|
146
|
+
} else if (event.code === "ArrowLeft") {
|
147
|
+
itemIndex -= 1;
|
148
|
+
} else if (event.code === "ArrowDown") {
|
149
|
+
itemIndex += 4;
|
150
|
+
} else if (event.code === "ArrowUp") {
|
151
|
+
if (itemIndex === 0) {
|
152
|
+
this.searchInput?.current?.focus();
|
153
|
+
return;
|
154
|
+
}
|
155
|
+
itemIndex -= 4;
|
156
|
+
} else if (event.code === "PageDown") {
|
157
|
+
itemIndex += 16;
|
158
|
+
} else if (event.code === "PageUp") {
|
159
|
+
itemIndex -= 16;
|
160
|
+
}
|
161
|
+
|
162
|
+
if (itemIndex < 0) {
|
163
|
+
itemIndex = 0;
|
164
|
+
} else if (itemIndex >= this.state.items.length) {
|
165
|
+
itemIndex = this.state.items.length - 1;
|
166
|
+
}
|
167
|
+
|
168
|
+
this.getItemElement(itemIndex)?.focus();
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
render() {
|
173
|
+
const ItemTemplate = this.props.itemTemplate;
|
174
|
+
const TriggerTemplate = this.props.triggerTemplate;
|
175
|
+
|
176
|
+
return (
|
177
|
+
<React.Fragment>
|
178
|
+
<div
|
179
|
+
className="sd-input sd-input--grid-select"
|
180
|
+
ref={this.buttonContainer}
|
181
|
+
aria-label={this.props.label}
|
182
|
+
>
|
183
|
+
<label className="sd-input__label">
|
184
|
+
{this.props.label}
|
185
|
+
</label>
|
186
|
+
<TriggerTemplate onClick={this.togglePopup} />
|
187
|
+
|
188
|
+
</div>
|
189
|
+
<OverlayPanel
|
190
|
+
ref={this.overlayPanel}
|
191
|
+
dismissable={true}
|
192
|
+
className="select-grid__overlay-panel"
|
193
|
+
appendTo={document.body} // making it work inside `overflow:hidden`
|
194
|
+
>
|
195
|
+
<div className="sd-shadow--z3 select-grid__panel">
|
196
|
+
<div className="select-grid__header">
|
197
|
+
<div className="sd-searchbar sd-searchbar--boxed">
|
198
|
+
<label className="sd-searchbar__icon" />
|
199
|
+
<input
|
200
|
+
className="sd-searchbar__input"
|
201
|
+
placeholder={this.props.filterPlaceholder || 'Search...'}
|
202
|
+
type="text"
|
203
|
+
onChange={this.search}
|
204
|
+
ref={this.searchInput}
|
205
|
+
/>
|
206
|
+
</div>
|
207
|
+
</div>
|
208
|
+
<div
|
209
|
+
className="select-grid__body flex-grid flex-grid--wrap-items flex-grid--small-4 flex-grid--boxed"
|
210
|
+
ref={this.gridContainer}
|
211
|
+
|
212
|
+
>
|
213
|
+
<Loader overlay={this.state.loading} />
|
214
|
+
{this.state.items.map((item, index) => (
|
215
|
+
<div
|
216
|
+
key={this.htmlId + item.label}
|
217
|
+
data-item-index={index}
|
218
|
+
className="flex-grid__item select-grid__item sd-padding-y--2"
|
219
|
+
tabIndex={0}
|
220
|
+
role="button"
|
221
|
+
aria-label={item.name}
|
222
|
+
onClick={() => this.select(item)}
|
223
|
+
>
|
224
|
+
<ItemTemplate item={item} />
|
225
|
+
</div>
|
226
|
+
))}
|
227
|
+
</div>
|
228
|
+
</div>
|
229
|
+
</OverlayPanel>
|
230
|
+
</React.Fragment>
|
231
|
+
);
|
232
|
+
}
|
233
|
+
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
|
2
|
+
import React from 'react';
|
3
|
+
import classNames from 'classnames';
|
4
|
+
|
5
|
+
interface IProps {
|
6
|
+
shape: string;
|
7
|
+
size: string;
|
8
|
+
width: string;
|
9
|
+
height: string;
|
10
|
+
borderRadius: string;
|
11
|
+
animation: string;
|
12
|
+
style: object;
|
13
|
+
className: string;
|
14
|
+
}
|
15
|
+
|
16
|
+
export class Skeleton extends React.Component<IProps> {
|
17
|
+
static defaultProps = {
|
18
|
+
shape: 'rectangle',
|
19
|
+
size: null,
|
20
|
+
width: '100%',
|
21
|
+
height: '1.2rem',
|
22
|
+
borderRadius: null,
|
23
|
+
animation: 'wave',
|
24
|
+
style: null,
|
25
|
+
className: null,
|
26
|
+
};
|
27
|
+
|
28
|
+
skeletonStyle() {
|
29
|
+
if (this.props.size) {
|
30
|
+
return { width: this.props.size, height: this.props.size, borderRadius: this.props.borderRadius };
|
31
|
+
} else {
|
32
|
+
return { width: this.props.width, height: this.props.height, borderRadius: this.props.borderRadius };
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
render() {
|
37
|
+
const skeletonClassName = classNames('p-skeleton p-component', {
|
38
|
+
'p-skeleton-circle': this.props.shape === 'circle',
|
39
|
+
'p-skeleton-none': this.props.animation === 'none',
|
40
|
+
}, this.props.className);
|
41
|
+
|
42
|
+
const style = this.skeletonStyle();
|
43
|
+
|
44
|
+
return (
|
45
|
+
<div style={style} className={skeletonClassName}></div>
|
46
|
+
);
|
47
|
+
}
|
48
|
+
}
|
@@ -6,10 +6,11 @@ interface IProps {
|
|
6
6
|
keyValue?: number;
|
7
7
|
shade?: 'light' | 'darker' | 'highlight1' | 'highlight2'; // default light
|
8
8
|
shape?: 'round' | 'square'; // default round
|
9
|
+
readOnly?: boolean;
|
9
10
|
onClick(): void;
|
10
11
|
}
|
11
12
|
|
12
|
-
export const Tag = ({ text, keyValue, shade, shape, onClick }: IProps) => {
|
13
|
+
export const Tag = ({ text, keyValue, shade, shape, readOnly, onClick }: IProps) => {
|
13
14
|
let classes = classNames('tag-label', {
|
14
15
|
[`tag-label--${shade}`]: shade && shade !== 'light',
|
15
16
|
'tag-label--square': shape === 'square',
|
@@ -17,9 +18,9 @@ export const Tag = ({ text, keyValue, shade, shape, onClick }: IProps) => {
|
|
17
18
|
return (
|
18
19
|
<div className={classes} key={keyValue}>
|
19
20
|
{text}
|
20
|
-
<button className='tag-label__remove' onClick={onClick}>
|
21
|
+
{!readOnly ? <button className='tag-label__remove' onClick={onClick}>
|
21
22
|
<i className='icon-close-small'></i>
|
22
|
-
</button>
|
23
|
+
</button> : null}
|
23
24
|
</div>
|
24
25
|
);
|
25
26
|
};
|
package/app-typescript/index.ts
CHANGED
@@ -63,6 +63,11 @@ export { GridItem, GridItemContent, GridItemMedia, GridItemFooter, GridItemConte
|
|
63
63
|
export { toasted } from './components/Toast';
|
64
64
|
export { Menu } from './components/Menu';
|
65
65
|
export { ToggleBox } from './components/Togglebox';
|
66
|
+
export { SelectGrid } from './components/SelectGrid';
|
67
|
+
export { IconPicker } from './components/IconPicker';
|
68
|
+
|
69
|
+
export { Skeleton } from './components/Skeleton';
|
70
|
+
export { ListItemLoader } from './components/ListItemLoader';
|
66
71
|
|
67
72
|
// declare non-typescript exports to prevent errors
|
68
73
|
export declare const ToggleBoxNext: any;
|
@@ -20,6 +20,7 @@
|
|
20
20
|
<div class="modal__body">
|
21
21
|
<p>This is sample message inside modal!</p>
|
22
22
|
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. </p>
|
23
|
+
<button class="btn" ng-click="openModal('modalActive6')">Open modal inside modal</button>
|
23
24
|
</div>
|
24
25
|
<div class="modal__footer">
|
25
26
|
<button class="btn" ng-click="closeModal('modalActive')">Cancel</button>
|