@performant-software/semantic-components 1.0.6-beta.0 → 1.0.6-beta.1

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/build/main.css CHANGED
@@ -913,6 +913,49 @@ div.react-calendar {
913
913
  opacity: 0.87;
914
914
  }
915
915
 
916
+ .selectize-image-header.ui.segment .container {
917
+ display: flex;
918
+ flex-direction: row;
919
+ align-items: center;
920
+ }
921
+ .selectize-image-header.ui.segment .container > .ui.cards {
922
+ display: flex;
923
+ flex-direction: row;
924
+ flex-grow: 1;
925
+ margin: 0em 0.2rem;
926
+ }
927
+ .selectize-image-header.ui.segment > .bottom-container {
928
+ display: flex;
929
+ flex-direction: row;
930
+ justify-content: space-between;
931
+ align-items: center;
932
+ width: 100%;
933
+ }
934
+ .selectize-image-header.ui.segment > .bottom-container .ui.button.link {
935
+ box-shadow: 0 0;
936
+ -moz-box-shadow: 0 0;
937
+ -webkit-box-shadow: 0 0;
938
+ }
939
+ .selectize-image-header.ui.segment .container > .ui.cards > .ui.card > .content > .meta {
940
+ font-size: 0.7em;
941
+ }
942
+ .selectize-image-header.ui.segment .container > .ui.cards > .ui.card > .image {
943
+ aspect-ratio: 1;
944
+ object-fit: cover;
945
+ width: 100%;
946
+ }
947
+ .selectize-image-header.ui.segment .container > .ui.cards > .ui.card > .ui.segment.placeholder {
948
+ align-items: center;
949
+ aspect-ratio: 1;
950
+ margin-bottom: 0;
951
+ max-width: 100%;
952
+ min-height: unset;
953
+ }
954
+ .selectize-image-header.ui.segment .container > .ui.cards > .ui.card > .ui.segment.placeholder > .icon {
955
+ color: rgba(0, 0, 0, 0.87);
956
+ height: unset;
957
+ }
958
+
916
959
  .tabbed-modal.ui .modal-header .ui.menu {
917
960
  max-width: 100%;
918
961
  overflow: auto;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@performant-software/semantic-components",
3
- "version": "1.0.6-beta.0",
3
+ "version": "1.0.6-beta.1",
4
4
  "description": "A package of shared components based on the Semantic UI Framework.",
5
5
  "license": "MIT",
6
6
  "main": "./build/index.js",
@@ -12,7 +12,7 @@
12
12
  "build": "webpack --mode production && flow-copy-source -v src types"
13
13
  },
14
14
  "dependencies": {
15
- "@performant-software/shared-components": "^1.0.6-beta.0",
15
+ "@performant-software/shared-components": "^1.0.6-beta.1",
16
16
  "@react-google-maps/api": "^2.8.1",
17
17
  "axios": "^0.26.1",
18
18
  "citeproc": "^2.4.62",
@@ -0,0 +1,49 @@
1
+ .selectize-image-header.ui.segment .container {
2
+ display: flex;
3
+ flex-direction: row;
4
+ align-items: center;
5
+ }
6
+
7
+ .selectize-image-header.ui.segment .container > .ui.cards {
8
+ display: flex;
9
+ flex-direction: row;
10
+ flex-grow: 1;
11
+ margin: 0em 0.2rem;
12
+ }
13
+
14
+ .selectize-image-header.ui.segment > .bottom-container {
15
+ display: flex;
16
+ flex-direction: row;
17
+ justify-content: space-between;
18
+ align-items: center;
19
+ width: 100%;
20
+ }
21
+
22
+ .selectize-image-header.ui.segment > .bottom-container .ui.button.link {
23
+ box-shadow: 0 0;
24
+ -moz-box-shadow: 0 0;
25
+ -webkit-box-shadow: 0 0;
26
+ }
27
+
28
+ .selectize-image-header.ui.segment .container > .ui.cards > .ui.card > .content > .meta {
29
+ font-size: 0.7em;
30
+ }
31
+
32
+ .selectize-image-header.ui.segment .container > .ui.cards > .ui.card > .image {
33
+ aspect-ratio: 1;
34
+ object-fit: cover;
35
+ width: 100%;
36
+ }
37
+
38
+ .selectize-image-header.ui.segment .container > .ui.cards > .ui.card > .ui.segment.placeholder {
39
+ align-items: center;
40
+ aspect-ratio: 1;
41
+ margin-bottom: 0;
42
+ max-width: 100%;
43
+ min-height: unset;
44
+ }
45
+
46
+ .selectize-image-header.ui.segment .container > .ui.cards > .ui.card > .ui.segment.placeholder > .icon {
47
+ color: rgba(0, 0, 0, 0.87);
48
+ height: unset;
49
+ }
@@ -0,0 +1,276 @@
1
+ // @flow
2
+
3
+ import React, {
4
+ useCallback,
5
+ useEffect,
6
+ useMemo,
7
+ useState,
8
+ type Element
9
+ } from 'react';
10
+ import {
11
+ Button,
12
+ Card,
13
+ Header,
14
+ Icon,
15
+ Image,
16
+ Segment,
17
+ Transition
18
+ } from 'semantic-ui-react';
19
+ import _ from 'underscore';
20
+ import i18n from '../i18n/i18n';
21
+ import './SelectizeImageHeader.css';
22
+
23
+ type Props = {
24
+ collapsable?: boolean,
25
+ onItemClick: (item: any) => void,
26
+ perPage?: number,
27
+ renderDescription?: (item: any) => Element<any> | string,
28
+ renderExtra?: (item: any) => Element<any> | string,
29
+ renderHeader?: (item: any) => Element<any> | string,
30
+ renderImage?: (item: any) => Element<any> | string,
31
+ renderMeta?: (item: any) => Element<any> | string,
32
+ selectedItem: any,
33
+ selectedItems: Array<any>
34
+ };
35
+
36
+ type CardProps = {
37
+ onClick: () => void,
38
+ onDelete: () => void,
39
+ description?: Element<any> | string,
40
+ extra?: Element<any> | string,
41
+ header?: Element<any> | string,
42
+ image?: Element<any> | string,
43
+ meta?: Element<any> | string,
44
+ selected: boolean
45
+ };
46
+
47
+ const DEFAULT_PER_PAGE = 5;
48
+
49
+ const SelectizeCard = (props: CardProps) => (
50
+ <Card
51
+ raised={props.selected}
52
+ link
53
+ onClick={props.onClick}
54
+ >
55
+ { props.image && _.isString(props.image) && (
56
+ <Image
57
+ size='small'
58
+ src={props.image}
59
+ />
60
+ )}
61
+ { !!props.image && !_.isString(props.image) && props.image }
62
+ { !props.image && (
63
+ <Segment
64
+ placeholder
65
+ >
66
+ <Icon
67
+ name='image'
68
+ size='large'
69
+ />
70
+ </Segment>
71
+ )}
72
+ <Card.Content>
73
+ { props.header && (
74
+ <Card.Header>
75
+ <Header
76
+ as='h6'
77
+ content={props.header}
78
+ />
79
+ </Card.Header>
80
+ )}
81
+ { props.meta && (
82
+ <Card.Meta
83
+ content={props.meta}
84
+ />
85
+ )}
86
+ { props.description && (
87
+ <Card.Description
88
+ content={props.description}
89
+ />
90
+ )}
91
+ </Card.Content>
92
+ { props.extra && (
93
+ <Card.Content
94
+ extra
95
+ >
96
+ { props.extra }
97
+ </Card.Content>
98
+ )}
99
+ { props.selected && (
100
+ <Card.Content
101
+ extra
102
+ >
103
+ <Button
104
+ basic
105
+ circular
106
+ compact
107
+ icon='times'
108
+ onClick={props.onDelete}
109
+ />
110
+ </Card.Content>
111
+ )}
112
+ </Card>
113
+ );
114
+
115
+ SelectizeCard.defaultProps = {
116
+ header: undefined,
117
+ image: undefined,
118
+ meta: undefined,
119
+ renderExtra: undefined
120
+ };
121
+
122
+ const SelectizeImageHeader = (props: Props) => {
123
+ const [page, setPage] = useState(1);
124
+ const [pages, setPages] = useState(0);
125
+ const [collapsed, setCollapsed] = useState(false);
126
+
127
+ /**
128
+ * Sets the items for the current page.
129
+ *
130
+ * @type {null}
131
+ */
132
+ const items = useMemo(() => {
133
+ let records = null;
134
+
135
+ if (page && props.selectedItems && props.selectedItems.length) {
136
+ const startIndex = (page - 1) * (props.perPage || DEFAULT_PER_PAGE);
137
+ const endIndex = startIndex + (props.perPage || DEFAULT_PER_PAGE);
138
+
139
+ records = props.selectedItems.slice(startIndex, endIndex);
140
+ }
141
+
142
+ return records;
143
+ }, [page, props.perPage, props.selectedItems]);
144
+
145
+ /**
146
+ * Sets the pagination label.
147
+ *
148
+ * @type {string}
149
+ */
150
+ const pagination = useMemo(() => {
151
+ const startIndex = (page - 1) * (props.perPage || DEFAULT_PER_PAGE);
152
+ const endIndex = startIndex + (props.perPage || DEFAULT_PER_PAGE);
153
+ const total = props.selectedItems.length;
154
+
155
+ return `${startIndex + 1} - ${Math.min(endIndex, total)} of ${total}`;
156
+ }, [page, props.perPage, props.selectedItems]);
157
+
158
+ /**
159
+ * Changes the current page number. If the next page is invalid, we move to the first page or last page.
160
+ *
161
+ * @type {(function(*): void)|*}
162
+ */
163
+ const onPageChange = useCallback((increment) => {
164
+ let nextPage = page + increment;
165
+
166
+ if (nextPage < 1) {
167
+ nextPage = pages;
168
+ } else if (nextPage > pages) {
169
+ nextPage = 1;
170
+ }
171
+
172
+ setPage(nextPage);
173
+ }, [page, pages]);
174
+
175
+ /**
176
+ * Set the number of pages when the perPage or selectedItems props change.
177
+ */
178
+ useEffect(() => {
179
+ if (props.perPage && props.selectedItems) {
180
+ setPages(Math.ceil(props.selectedItems.length / props.perPage));
181
+ }
182
+ }, [props.perPage, props.selectedItems]);
183
+
184
+ /**
185
+ * If we're removing the last item on the page, decrement the page by 1.
186
+ */
187
+ useEffect(() => {
188
+ if (!(items && items.length) && props.selectedItems && props.selectedItems.length && page > 1) {
189
+ setPage(page - 1);
190
+ }
191
+ }, [items, page, props.selectedItems]);
192
+
193
+ if (_.isEmpty(items)) {
194
+ return null;
195
+ }
196
+
197
+ return (
198
+ <Segment
199
+ className='selectize-image-header'
200
+ >
201
+ <Transition
202
+ visible={!collapsed}
203
+ >
204
+ <div>
205
+ <div
206
+ className='container'
207
+ >
208
+ <Button
209
+ basic
210
+ circular
211
+ disabled={pages <= 1}
212
+ icon='arrow left'
213
+ onClick={onPageChange.bind(this, -1)}
214
+ />
215
+ <Card.Group
216
+ itemsPerRow={props.perPage}
217
+ >
218
+ { _.map(items, (item) => (
219
+ <SelectizeCard
220
+ description={props.renderDescription && props.renderDescription(item)}
221
+ extra={props.renderExtra && props.renderExtra(item)}
222
+ header={props.renderHeader && props.renderHeader(item)}
223
+ image={props.renderImage && props.renderImage(item)}
224
+ meta={props.renderMeta && props.renderMeta(item)}
225
+ key={item.id}
226
+ onClick={() => (
227
+ props.selectedItem === item
228
+ ? props.onItemClick(null)
229
+ : props.onItemClick(item)
230
+ )}
231
+ onDelete={() => props.onItemClick(item)}
232
+ selected={item === props.selectedItem}
233
+ />
234
+ ))}
235
+ </Card.Group>
236
+ <Button
237
+ basic
238
+ circular
239
+ disabled={pages <= 1}
240
+ icon='arrow right'
241
+ onClick={onPageChange.bind(this, 1)}
242
+ />
243
+ </div>
244
+ </div>
245
+ </Transition>
246
+ <div
247
+ className='bottom-container'
248
+ >
249
+ <div />
250
+ <div>{ pagination }</div>
251
+ <div>
252
+ { props.collapsable && (
253
+ <Button
254
+ as='a'
255
+ basic
256
+ circular
257
+ className='link'
258
+ compact
259
+ content={collapsed ? i18n.t('Common.buttons.show') : i18n.t('Common.buttons.hide')}
260
+ onClick={() => setCollapsed((prevCollapsed) => !prevCollapsed)}
261
+ />
262
+ )}
263
+ </div>
264
+ </div>
265
+ </Segment>
266
+ );
267
+ };
268
+
269
+ SelectizeImageHeader.defaultProps = {
270
+ collapsable: true,
271
+ onItemClick: () => {},
272
+ perPage: DEFAULT_PER_PAGE,
273
+ renderExtra: undefined
274
+ };
275
+
276
+ export default SelectizeImageHeader;
package/src/i18n/en.json CHANGED
@@ -44,11 +44,13 @@
44
44
  "close": "Close",
45
45
  "download": "Download",
46
46
  "edit": "Edit",
47
+ "hide": "Hide",
47
48
  "iiif": "View IIIF",
48
49
  "ok": "OK",
49
50
  "open": "Open",
50
51
  "save": "Save",
51
52
  "search": "Search",
53
+ "show": "Show",
52
54
  "upload": "Upload"
53
55
  },
54
56
  "errors": {
package/src/index.js CHANGED
@@ -70,6 +70,7 @@ export { default as SaveButton } from './components/SaveButton';
70
70
  export { default as Section } from './components/Section';
71
71
  export { default as Selectize } from './components/Selectize';
72
72
  export { default as SelectizeHeader } from './components/SelectizeHeader';
73
+ export { default as SelectizeImageHeader } from './components/SelectizeImageHeader';
73
74
  export { default as TabbedModal } from './components/TabbedModal';
74
75
  export { default as TabsMenu } from './components/TabsMenu';
75
76
  export { default as TagsList } from './components/TagsList';
@@ -0,0 +1,276 @@
1
+ // @flow
2
+
3
+ import React, {
4
+ useCallback,
5
+ useEffect,
6
+ useMemo,
7
+ useState,
8
+ type Element
9
+ } from 'react';
10
+ import {
11
+ Button,
12
+ Card,
13
+ Header,
14
+ Icon,
15
+ Image,
16
+ Segment,
17
+ Transition
18
+ } from 'semantic-ui-react';
19
+ import _ from 'underscore';
20
+ import i18n from '../i18n/i18n';
21
+ import './SelectizeImageHeader.css';
22
+
23
+ type Props = {
24
+ collapsable?: boolean,
25
+ onItemClick: (item: any) => void,
26
+ perPage?: number,
27
+ renderDescription?: (item: any) => Element<any> | string,
28
+ renderExtra?: (item: any) => Element<any> | string,
29
+ renderHeader?: (item: any) => Element<any> | string,
30
+ renderImage?: (item: any) => Element<any> | string,
31
+ renderMeta?: (item: any) => Element<any> | string,
32
+ selectedItem: any,
33
+ selectedItems: Array<any>
34
+ };
35
+
36
+ type CardProps = {
37
+ onClick: () => void,
38
+ onDelete: () => void,
39
+ description?: Element<any> | string,
40
+ extra?: Element<any> | string,
41
+ header?: Element<any> | string,
42
+ image?: Element<any> | string,
43
+ meta?: Element<any> | string,
44
+ selected: boolean
45
+ };
46
+
47
+ const DEFAULT_PER_PAGE = 5;
48
+
49
+ const SelectizeCard = (props: CardProps) => (
50
+ <Card
51
+ raised={props.selected}
52
+ link
53
+ onClick={props.onClick}
54
+ >
55
+ { props.image && _.isString(props.image) && (
56
+ <Image
57
+ size='small'
58
+ src={props.image}
59
+ />
60
+ )}
61
+ { !!props.image && !_.isString(props.image) && props.image }
62
+ { !props.image && (
63
+ <Segment
64
+ placeholder
65
+ >
66
+ <Icon
67
+ name='image'
68
+ size='large'
69
+ />
70
+ </Segment>
71
+ )}
72
+ <Card.Content>
73
+ { props.header && (
74
+ <Card.Header>
75
+ <Header
76
+ as='h6'
77
+ content={props.header}
78
+ />
79
+ </Card.Header>
80
+ )}
81
+ { props.meta && (
82
+ <Card.Meta
83
+ content={props.meta}
84
+ />
85
+ )}
86
+ { props.description && (
87
+ <Card.Description
88
+ content={props.description}
89
+ />
90
+ )}
91
+ </Card.Content>
92
+ { props.extra && (
93
+ <Card.Content
94
+ extra
95
+ >
96
+ { props.extra }
97
+ </Card.Content>
98
+ )}
99
+ { props.selected && (
100
+ <Card.Content
101
+ extra
102
+ >
103
+ <Button
104
+ basic
105
+ circular
106
+ compact
107
+ icon='times'
108
+ onClick={props.onDelete}
109
+ />
110
+ </Card.Content>
111
+ )}
112
+ </Card>
113
+ );
114
+
115
+ SelectizeCard.defaultProps = {
116
+ header: undefined,
117
+ image: undefined,
118
+ meta: undefined,
119
+ renderExtra: undefined
120
+ };
121
+
122
+ const SelectizeImageHeader = (props: Props) => {
123
+ const [page, setPage] = useState(1);
124
+ const [pages, setPages] = useState(0);
125
+ const [collapsed, setCollapsed] = useState(false);
126
+
127
+ /**
128
+ * Sets the items for the current page.
129
+ *
130
+ * @type {null}
131
+ */
132
+ const items = useMemo(() => {
133
+ let records = null;
134
+
135
+ if (page && props.selectedItems && props.selectedItems.length) {
136
+ const startIndex = (page - 1) * (props.perPage || DEFAULT_PER_PAGE);
137
+ const endIndex = startIndex + (props.perPage || DEFAULT_PER_PAGE);
138
+
139
+ records = props.selectedItems.slice(startIndex, endIndex);
140
+ }
141
+
142
+ return records;
143
+ }, [page, props.perPage, props.selectedItems]);
144
+
145
+ /**
146
+ * Sets the pagination label.
147
+ *
148
+ * @type {string}
149
+ */
150
+ const pagination = useMemo(() => {
151
+ const startIndex = (page - 1) * (props.perPage || DEFAULT_PER_PAGE);
152
+ const endIndex = startIndex + (props.perPage || DEFAULT_PER_PAGE);
153
+ const total = props.selectedItems.length;
154
+
155
+ return `${startIndex + 1} - ${Math.min(endIndex, total)} of ${total}`;
156
+ }, [page, props.perPage, props.selectedItems]);
157
+
158
+ /**
159
+ * Changes the current page number. If the next page is invalid, we move to the first page or last page.
160
+ *
161
+ * @type {(function(*): void)|*}
162
+ */
163
+ const onPageChange = useCallback((increment) => {
164
+ let nextPage = page + increment;
165
+
166
+ if (nextPage < 1) {
167
+ nextPage = pages;
168
+ } else if (nextPage > pages) {
169
+ nextPage = 1;
170
+ }
171
+
172
+ setPage(nextPage);
173
+ }, [page, pages]);
174
+
175
+ /**
176
+ * Set the number of pages when the perPage or selectedItems props change.
177
+ */
178
+ useEffect(() => {
179
+ if (props.perPage && props.selectedItems) {
180
+ setPages(Math.ceil(props.selectedItems.length / props.perPage));
181
+ }
182
+ }, [props.perPage, props.selectedItems]);
183
+
184
+ /**
185
+ * If we're removing the last item on the page, decrement the page by 1.
186
+ */
187
+ useEffect(() => {
188
+ if (!(items && items.length) && props.selectedItems && props.selectedItems.length && page > 1) {
189
+ setPage(page - 1);
190
+ }
191
+ }, [items, page, props.selectedItems]);
192
+
193
+ if (_.isEmpty(items)) {
194
+ return null;
195
+ }
196
+
197
+ return (
198
+ <Segment
199
+ className='selectize-image-header'
200
+ >
201
+ <Transition
202
+ visible={!collapsed}
203
+ >
204
+ <div>
205
+ <div
206
+ className='container'
207
+ >
208
+ <Button
209
+ basic
210
+ circular
211
+ disabled={pages <= 1}
212
+ icon='arrow left'
213
+ onClick={onPageChange.bind(this, -1)}
214
+ />
215
+ <Card.Group
216
+ itemsPerRow={props.perPage}
217
+ >
218
+ { _.map(items, (item) => (
219
+ <SelectizeCard
220
+ description={props.renderDescription && props.renderDescription(item)}
221
+ extra={props.renderExtra && props.renderExtra(item)}
222
+ header={props.renderHeader && props.renderHeader(item)}
223
+ image={props.renderImage && props.renderImage(item)}
224
+ meta={props.renderMeta && props.renderMeta(item)}
225
+ key={item.id}
226
+ onClick={() => (
227
+ props.selectedItem === item
228
+ ? props.onItemClick(null)
229
+ : props.onItemClick(item)
230
+ )}
231
+ onDelete={() => props.onItemClick(item)}
232
+ selected={item === props.selectedItem}
233
+ />
234
+ ))}
235
+ </Card.Group>
236
+ <Button
237
+ basic
238
+ circular
239
+ disabled={pages <= 1}
240
+ icon='arrow right'
241
+ onClick={onPageChange.bind(this, 1)}
242
+ />
243
+ </div>
244
+ </div>
245
+ </Transition>
246
+ <div
247
+ className='bottom-container'
248
+ >
249
+ <div />
250
+ <div>{ pagination }</div>
251
+ <div>
252
+ { props.collapsable && (
253
+ <Button
254
+ as='a'
255
+ basic
256
+ circular
257
+ className='link'
258
+ compact
259
+ content={collapsed ? i18n.t('Common.buttons.show') : i18n.t('Common.buttons.hide')}
260
+ onClick={() => setCollapsed((prevCollapsed) => !prevCollapsed)}
261
+ />
262
+ )}
263
+ </div>
264
+ </div>
265
+ </Segment>
266
+ );
267
+ };
268
+
269
+ SelectizeImageHeader.defaultProps = {
270
+ collapsable: true,
271
+ onItemClick: () => {},
272
+ perPage: DEFAULT_PER_PAGE,
273
+ renderExtra: undefined
274
+ };
275
+
276
+ export default SelectizeImageHeader;
@@ -70,6 +70,7 @@ export { default as SaveButton } from './components/SaveButton';
70
70
  export { default as Section } from './components/Section';
71
71
  export { default as Selectize } from './components/Selectize';
72
72
  export { default as SelectizeHeader } from './components/SelectizeHeader';
73
+ export { default as SelectizeImageHeader } from './components/SelectizeImageHeader';
73
74
  export { default as TabbedModal } from './components/TabbedModal';
74
75
  export { default as TabsMenu } from './components/TabsMenu';
75
76
  export { default as TagsList } from './components/TagsList';