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.
Files changed (42) hide show
  1. package/app/scripts/modals.js +22 -9
  2. package/app/styles/app.scss +1 -0
  3. package/app/styles/form-elements/_select-grid.scss +77 -0
  4. package/app/styles/pr-superdesk-theme.scss +1 -0
  5. package/app/styles/primereact/_pr-skeleton.scss +35 -0
  6. package/app-typescript/components/IconPicker.tsx +277 -0
  7. package/app-typescript/components/ListItemLoader.tsx +30 -0
  8. package/app-typescript/components/SelectGrid.tsx +233 -0
  9. package/app-typescript/components/Skeleton.tsx +48 -0
  10. package/app-typescript/components/Tag.tsx +4 -3
  11. package/app-typescript/index.ts +5 -0
  12. package/dist/components/modals.html +1 -0
  13. package/dist/examples.bundle.js +3051 -1766
  14. package/dist/react/IconFont.tsx +7 -6
  15. package/dist/react/IconPicker.tsx +65 -0
  16. package/dist/react/Index.tsx +16 -1
  17. package/dist/react/ListItems.tsx +34 -0
  18. package/dist/react/SelectGrid.tsx +121 -0
  19. package/dist/react/Tags.tsx +14 -3
  20. package/dist/superdesk-ui.bundle.css +3356 -0
  21. package/dist/superdesk-ui.bundle.js +2563 -1480
  22. package/dist/vendor.bundle.js +49646 -49624
  23. package/examples/pages/components/modals.html +1 -0
  24. package/examples/pages/react/IconFont.tsx +7 -6
  25. package/examples/pages/react/IconPicker.tsx +65 -0
  26. package/examples/pages/react/Index.tsx +16 -1
  27. package/examples/pages/react/ListItems.tsx +34 -0
  28. package/examples/pages/react/SelectGrid.tsx +121 -0
  29. package/examples/pages/react/Tags.tsx +14 -3
  30. package/package.json +1 -1
  31. package/react/components/IconPicker.d.ts +24 -0
  32. package/react/components/IconPicker.js +283 -0
  33. package/react/components/ListItemLoader.d.ts +4 -0
  34. package/react/components/ListItemLoader.js +62 -0
  35. package/react/components/SelectGrid.d.ts +45 -0
  36. package/react/components/SelectGrid.js +179 -0
  37. package/react/components/Skeleton.d.ts +30 -0
  38. package/react/components/Skeleton.js +55 -0
  39. package/react/components/Tag.d.ts +2 -1
  40. package/react/components/Tag.js +3 -3
  41. package/react/index.d.ts +4 -0
  42. 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
  };
@@ -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>