@plone/volto 18.34.0 → 18.35.0
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/.release-it.json +3 -0
- package/CHANGELOG.md +24 -0
- package/locales/af/LC_MESSAGES/volto.po +30 -0
- package/locales/af.json +1 -1
- package/locales/ar/LC_MESSAGES/volto.po +30 -0
- package/locales/ar.json +1 -1
- package/locales/bg/LC_MESSAGES/volto.po +30 -0
- package/locales/bg.json +1 -1
- package/locales/bn/LC_MESSAGES/volto.po +30 -0
- package/locales/bn.json +1 -1
- package/locales/ca/LC_MESSAGES/volto.po +32 -2
- package/locales/ca.json +1 -1
- package/locales/cs/LC_MESSAGES/volto.po +30 -0
- package/locales/cs.json +1 -1
- package/locales/cy/LC_MESSAGES/volto.po +30 -0
- package/locales/cy.json +1 -1
- package/locales/da/LC_MESSAGES/volto.po +30 -0
- package/locales/da.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +34 -4
- package/locales/de.json +1 -1
- package/locales/el/LC_MESSAGES/volto.po +30 -0
- package/locales/el.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +30 -0
- package/locales/en.json +1 -1
- package/locales/en_AU/LC_MESSAGES/volto.po +30 -0
- package/locales/en_AU.json +1 -1
- package/locales/en_GB/LC_MESSAGES/volto.po +30 -0
- package/locales/en_GB.json +1 -1
- package/locales/eo/LC_MESSAGES/volto.po +30 -0
- package/locales/eo.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +31 -1
- package/locales/es.json +1 -1
- package/locales/et/LC_MESSAGES/volto.po +30 -0
- package/locales/et.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +33 -3
- package/locales/eu.json +1 -1
- package/locales/fa/LC_MESSAGES/volto.po +30 -0
- package/locales/fa.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +30 -0
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +31 -1
- package/locales/fr.json +1 -1
- package/locales/fu/LC_MESSAGES/volto.po +30 -0
- package/locales/fu.json +1 -1
- package/locales/gl/LC_MESSAGES/volto.po +30 -0
- package/locales/gl.json +1 -1
- package/locales/he/LC_MESSAGES/volto.po +30 -0
- package/locales/he.json +1 -1
- package/locales/hi/LC_MESSAGES/volto.po +33 -3
- package/locales/hi.json +1 -1
- package/locales/hr/LC_MESSAGES/volto.po +30 -0
- package/locales/hr.json +1 -1
- package/locales/hu/LC_MESSAGES/volto.po +30 -0
- package/locales/hu.json +1 -1
- package/locales/hy/LC_MESSAGES/volto.po +30 -0
- package/locales/hy.json +1 -1
- package/locales/id/LC_MESSAGES/volto.po +30 -0
- package/locales/id.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +31 -1
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +30 -0
- package/locales/ja.json +1 -1
- package/locales/ka/LC_MESSAGES/volto.po +30 -0
- package/locales/ka.json +1 -1
- package/locales/kn/LC_MESSAGES/volto.po +30 -0
- package/locales/kn.json +1 -1
- package/locales/ko/LC_MESSAGES/volto.po +30 -0
- package/locales/ko.json +1 -1
- package/locales/lt/LC_MESSAGES/volto.po +30 -0
- package/locales/lt.json +1 -1
- package/locales/lv/LC_MESSAGES/volto.po +30 -0
- package/locales/lv.json +1 -1
- package/locales/mi/LC_MESSAGES/volto.po +30 -0
- package/locales/mi.json +1 -1
- package/locales/mk/LC_MESSAGES/volto.po +30 -0
- package/locales/mk.json +1 -1
- package/locales/my/LC_MESSAGES/volto.po +30 -0
- package/locales/my.json +1 -1
- package/locales/nb_NO/LC_MESSAGES/volto.po +30 -0
- package/locales/nb_NO.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +31 -1
- package/locales/nl.json +1 -1
- package/locales/nn/LC_MESSAGES/volto.po +30 -0
- package/locales/nn.json +1 -1
- package/locales/pl/LC_MESSAGES/volto.po +30 -0
- package/locales/pl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +30 -0
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +44 -14
- package/locales/pt_BR.json +1 -1
- package/locales/rm/LC_MESSAGES/volto.po +30 -0
- package/locales/rm.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +31 -1
- package/locales/ro.json +1 -1
- package/locales/ru/LC_MESSAGES/volto.po +31 -1
- package/locales/ru.json +1 -1
- package/locales/sk/LC_MESSAGES/volto.po +30 -0
- package/locales/sk.json +1 -1
- package/locales/sl/LC_MESSAGES/volto.po +30 -0
- package/locales/sl.json +1 -1
- package/locales/sm/LC_MESSAGES/volto.po +30 -0
- package/locales/sm.json +1 -1
- package/locales/sq/LC_MESSAGES/volto.po +30 -0
- package/locales/sq.json +1 -1
- package/locales/sr/LC_MESSAGES/volto.po +30 -0
- package/locales/sr.json +1 -1
- package/locales/sr@cyrl/LC_MESSAGES/volto.po +30 -0
- package/locales/sr@cyrl.json +1 -1
- package/locales/sr@latn/LC_MESSAGES/volto.po +30 -0
- package/locales/sr@latn.json +1 -1
- package/locales/sv/LC_MESSAGES/volto.po +30 -0
- package/locales/sv.json +1 -1
- package/locales/ta/LC_MESSAGES/volto.po +31 -1
- package/locales/ta.json +1 -1
- package/locales/te/LC_MESSAGES/volto.po +30 -0
- package/locales/te.json +1 -1
- package/locales/th/LC_MESSAGES/volto.po +30 -0
- package/locales/th.json +1 -1
- package/locales/to/LC_MESSAGES/volto.po +30 -0
- package/locales/to.json +1 -1
- package/locales/tr/LC_MESSAGES/volto.po +30 -0
- package/locales/tr.json +1 -1
- package/locales/uk/LC_MESSAGES/volto.po +30 -0
- package/locales/uk.json +1 -1
- package/locales/vi/LC_MESSAGES/volto.po +30 -0
- package/locales/vi.json +1 -1
- package/locales/volto.pot +31 -1
- package/locales/zh_CN/LC_MESSAGES/volto.po +30 -0
- package/locales/zh_CN.json +1 -1
- package/locales/zh_Hant/LC_MESSAGES/volto.po +30 -0
- package/locales/zh_Hant.json +1 -1
- package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +30 -0
- package/locales/zh_Hant_HK.json +1 -1
- package/news/7308.fix +1 -0
- package/news/8084.fix +1 -0
- package/package.json +14 -14
- package/src/components/manage/Controlpanels/ContentTypes.jsx +9 -2
- package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +58 -5
- package/src/components/manage/Controlpanels/Users/UsersControlpanel.ssr.test.jsx +624 -0
- package/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx +8 -0
- package/src/components/manage/Form/Form.jsx +6 -1
- package/src/components/manage/Form/ModalForm.jsx +165 -87
- package/src/components/manage/Sidebar/Sidebar.jsx +1 -0
- package/src/components/manage/Toast/Toast.jsx +35 -1
- package/src/components/manage/Toast/Toast.test.jsx +8 -5
- package/src/components/theme/Search/Search.jsx +24 -1
- package/src/helpers/FormValidation/validators.ts +15 -2
- package/theme/themes/pastanaga/collections/form.overrides +21 -0
- package/theme/themes/pastanaga/elements/button.overrides +30 -3
- package/theme/themes/pastanaga/extras/main.less +1 -0
- package/types/components/manage/Controlpanels/Users/UsersControlpanel.d.ts +2 -6
- package/types/components/manage/Controlpanels/Users/UsersControlpanel.ssr.test.d.ts +1 -0
- package/types/components/manage/Controlpanels/index.d.ts +1 -1
|
@@ -46,6 +46,14 @@ const messages = defineMessages({
|
|
|
46
46
|
id: 'Cancel',
|
|
47
47
|
defaultMessage: 'Cancel',
|
|
48
48
|
},
|
|
49
|
+
dialogOpened: {
|
|
50
|
+
id: 'Pop-up opened: {title}',
|
|
51
|
+
defaultMessage: 'Pop-up opened: {title}',
|
|
52
|
+
},
|
|
53
|
+
dialogClosed: {
|
|
54
|
+
id: 'Pop-up closed.',
|
|
55
|
+
defaultMessage: 'Pop-up closed.',
|
|
56
|
+
},
|
|
49
57
|
});
|
|
50
58
|
|
|
51
59
|
/**
|
|
@@ -54,6 +62,7 @@ const messages = defineMessages({
|
|
|
54
62
|
* @extends Component
|
|
55
63
|
*/
|
|
56
64
|
class ModalForm extends Component {
|
|
65
|
+
static idCounter = 0;
|
|
57
66
|
/**
|
|
58
67
|
* Property types.
|
|
59
68
|
* @property {Object} propTypes Property types.
|
|
@@ -110,17 +119,21 @@ class ModalForm extends Component {
|
|
|
110
119
|
*/
|
|
111
120
|
constructor(props) {
|
|
112
121
|
super(props);
|
|
122
|
+
this.headerId = `modal-title-${++ModalForm.idCounter}`;
|
|
113
123
|
this.state = {
|
|
114
124
|
currentTab: 0,
|
|
115
125
|
errors: {},
|
|
116
126
|
isFormPristine: true,
|
|
117
127
|
formData: props.formData,
|
|
118
128
|
};
|
|
129
|
+
this.modalRef = React.createRef();
|
|
130
|
+
this.announceRef = React.createRef();
|
|
119
131
|
this.selectTab = this.selectTab.bind(this);
|
|
120
132
|
this.onChangeField = this.onChangeField.bind(this);
|
|
121
133
|
this.onBlurField = this.onBlurField.bind(this);
|
|
122
134
|
this.onClickInput = this.onClickInput.bind(this);
|
|
123
135
|
this.onSubmit = this.onSubmit.bind(this);
|
|
136
|
+
this.onKeyDown = this.onKeyDown.bind(this);
|
|
124
137
|
}
|
|
125
138
|
|
|
126
139
|
/**
|
|
@@ -209,12 +222,57 @@ class ModalForm extends Component {
|
|
|
209
222
|
});
|
|
210
223
|
}
|
|
211
224
|
|
|
225
|
+
onKeyDown(event) {
|
|
226
|
+
if (event.key !== 'Tab') return;
|
|
227
|
+
const modal = document.getElementById(this.headerId)?.closest('.ui.modal');
|
|
228
|
+
if (!modal) return;
|
|
229
|
+
const focusable = modal.querySelectorAll(
|
|
230
|
+
'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])',
|
|
231
|
+
);
|
|
232
|
+
if (!focusable.length) return;
|
|
233
|
+
const first = focusable[0];
|
|
234
|
+
const last = focusable[focusable.length - 1];
|
|
235
|
+
if (event.shiftKey) {
|
|
236
|
+
if (document.activeElement === first) {
|
|
237
|
+
event.preventDefault();
|
|
238
|
+
last.focus();
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
if (document.activeElement === last) {
|
|
242
|
+
event.preventDefault();
|
|
243
|
+
first.focus();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
212
248
|
/**
|
|
213
249
|
* Component did update lifecycle handler
|
|
214
250
|
* @param {Object} prevProps
|
|
215
251
|
* @param {Object} prevState
|
|
216
252
|
*/
|
|
217
|
-
|
|
253
|
+
componentWillUnmount() {
|
|
254
|
+
document.removeEventListener('keydown', this.onKeyDown);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
componentDidUpdate(prevProps, prevState) {
|
|
258
|
+
if (!prevProps.open && this.props.open) {
|
|
259
|
+
document.addEventListener('keydown', this.onKeyDown);
|
|
260
|
+
this.modalRef.current?.focus();
|
|
261
|
+
if (this.announceRef.current) {
|
|
262
|
+
this.announceRef.current.textContent = this.props.intl.formatMessage(
|
|
263
|
+
messages.dialogOpened,
|
|
264
|
+
{ title: this.props.title },
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (prevProps.open && !this.props.open) {
|
|
269
|
+
document.removeEventListener('keydown', this.onKeyDown);
|
|
270
|
+
if (this.announceRef.current) {
|
|
271
|
+
this.announceRef.current.textContent = this.props.intl.formatMessage(
|
|
272
|
+
messages.dialogClosed,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
218
276
|
if (this.props.onChangeFormData) {
|
|
219
277
|
if (!isEqual(prevState?.formData, this.state.formData)) {
|
|
220
278
|
this.props.onChangeFormData(this.state.formData);
|
|
@@ -259,100 +317,120 @@ class ModalForm extends Component {
|
|
|
259
317
|
|
|
260
318
|
const state_errors = keys(this.state.errors).length > 0;
|
|
261
319
|
return (
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
''
|
|
320
|
+
<>
|
|
321
|
+
{/* aria-live region outside Modal so it persists through open/close cycles */}
|
|
322
|
+
<div
|
|
323
|
+
ref={this.announceRef}
|
|
324
|
+
aria-live="assertive"
|
|
325
|
+
aria-atomic="true"
|
|
326
|
+
style={{
|
|
327
|
+
position: 'absolute',
|
|
328
|
+
width: '1px',
|
|
329
|
+
height: '1px',
|
|
330
|
+
overflow: 'hidden',
|
|
331
|
+
opacity: 0,
|
|
332
|
+
}}
|
|
333
|
+
/>
|
|
334
|
+
<Modal
|
|
335
|
+
role="dialog"
|
|
336
|
+
dimmer={this.props.dimmer}
|
|
337
|
+
open={this.props.open}
|
|
338
|
+
className={this.props.className}
|
|
339
|
+
aria-labelledby={this.headerId}
|
|
340
|
+
aria-modal="true"
|
|
341
|
+
>
|
|
342
|
+
<Header id={this.headerId}>{this.props.title}</Header>
|
|
343
|
+
<Dimmer active={this.props.loading}>
|
|
344
|
+
<Loader>
|
|
345
|
+
{this.props.loadingMessage || (
|
|
346
|
+
<FormattedMessage id="Loading" defaultMessage="Loading." />
|
|
290
347
|
)}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
348
|
+
</Loader>
|
|
349
|
+
</Dimmer>
|
|
350
|
+
<Modal.Content scrolling>
|
|
351
|
+
{/* outline suppressed for programmatic focus via CSS :focus:not(:focus-visible) on .modal-focus-trap */}
|
|
352
|
+
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
|
|
353
|
+
<div ref={this.modalRef} tabIndex={-1} className="modal-focus-trap">
|
|
354
|
+
<UiForm
|
|
355
|
+
method="post"
|
|
356
|
+
onSubmit={this.onSubmit}
|
|
357
|
+
error={state_errors || Boolean(this.props.submitError)}
|
|
358
|
+
>
|
|
359
|
+
{description}
|
|
360
|
+
<Message error>
|
|
361
|
+
{state_errors ? (
|
|
362
|
+
<FormattedMessage
|
|
363
|
+
id="There were some errors."
|
|
364
|
+
defaultMessage="There were some errors."
|
|
365
|
+
/>
|
|
366
|
+
) : (
|
|
367
|
+
''
|
|
368
|
+
)}
|
|
369
|
+
<div>{this.props.submitError}</div>
|
|
370
|
+
</Message>
|
|
371
|
+
{schema.fieldsets?.length > 1 && (
|
|
372
|
+
<Menu tabular stackable>
|
|
373
|
+
{map(schema.fieldsets, (item, index) => (
|
|
374
|
+
<Menu.Item
|
|
375
|
+
name={item.id}
|
|
376
|
+
index={index}
|
|
377
|
+
key={item.id}
|
|
378
|
+
active={this.state.currentTab === index}
|
|
379
|
+
onClick={this.selectTab}
|
|
380
|
+
>
|
|
381
|
+
{item.title}
|
|
382
|
+
</Menu.Item>
|
|
383
|
+
))}
|
|
384
|
+
</Menu>
|
|
385
|
+
)}
|
|
386
|
+
{fields.map((field) => (
|
|
387
|
+
<Field
|
|
388
|
+
{...field}
|
|
389
|
+
key={field.id}
|
|
390
|
+
onBlur={this.onBlurField}
|
|
391
|
+
onClick={this.onClickInput}
|
|
392
|
+
error={this.state.errors[field.id]}
|
|
393
|
+
/>
|
|
305
394
|
))}
|
|
306
|
-
</
|
|
395
|
+
</UiForm>
|
|
396
|
+
</div>
|
|
397
|
+
</Modal.Content>
|
|
398
|
+
<Modal.Actions>
|
|
399
|
+
{onCancel && (
|
|
400
|
+
<Button
|
|
401
|
+
type="button"
|
|
402
|
+
basic
|
|
403
|
+
circular
|
|
404
|
+
secondary
|
|
405
|
+
aria-label={this.props.intl.formatMessage(messages.cancel)}
|
|
406
|
+
title={this.props.intl.formatMessage(messages.cancel)}
|
|
407
|
+
onClick={onCancel}
|
|
408
|
+
>
|
|
409
|
+
<Icon name={clearSVG} className="circled" size="30px" />
|
|
410
|
+
</Button>
|
|
307
411
|
)}
|
|
308
|
-
{fields.map((field) => (
|
|
309
|
-
<Field
|
|
310
|
-
{...field}
|
|
311
|
-
key={field.id}
|
|
312
|
-
onBlur={this.onBlurField}
|
|
313
|
-
onClick={this.onClickInput}
|
|
314
|
-
error={this.state.errors[field.id]}
|
|
315
|
-
/>
|
|
316
|
-
))}
|
|
317
|
-
</UiForm>
|
|
318
|
-
</Modal.Content>
|
|
319
|
-
<Modal.Actions>
|
|
320
|
-
<Button
|
|
321
|
-
basic
|
|
322
|
-
circular
|
|
323
|
-
primary
|
|
324
|
-
floated="right"
|
|
325
|
-
aria-label={
|
|
326
|
-
this.props.submitLabel
|
|
327
|
-
? this.props.submitLabel
|
|
328
|
-
: this.props.intl.formatMessage(messages.save)
|
|
329
|
-
}
|
|
330
|
-
title={
|
|
331
|
-
this.props.submitLabel
|
|
332
|
-
? this.props.submitLabel
|
|
333
|
-
: this.props.intl.formatMessage(messages.save)
|
|
334
|
-
}
|
|
335
|
-
onClick={this.onSubmit}
|
|
336
|
-
loading={this.props.loading}
|
|
337
|
-
>
|
|
338
|
-
<Icon name={aheadSVG} className="contents circled" size="30px" />
|
|
339
|
-
</Button>
|
|
340
|
-
{onCancel && (
|
|
341
412
|
<Button
|
|
342
|
-
type="button"
|
|
343
413
|
basic
|
|
344
414
|
circular
|
|
345
|
-
|
|
346
|
-
aria-label={
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
415
|
+
primary
|
|
416
|
+
aria-label={
|
|
417
|
+
this.props.submitLabel
|
|
418
|
+
? this.props.submitLabel
|
|
419
|
+
: this.props.intl.formatMessage(messages.save)
|
|
420
|
+
}
|
|
421
|
+
title={
|
|
422
|
+
this.props.submitLabel
|
|
423
|
+
? this.props.submitLabel
|
|
424
|
+
: this.props.intl.formatMessage(messages.save)
|
|
425
|
+
}
|
|
426
|
+
onClick={this.onSubmit}
|
|
427
|
+
loading={this.props.loading}
|
|
350
428
|
>
|
|
351
|
-
<Icon name={
|
|
429
|
+
<Icon name={aheadSVG} className="contents circled" size="30px" />
|
|
352
430
|
</Button>
|
|
353
|
-
|
|
354
|
-
</Modal
|
|
355
|
-
|
|
431
|
+
</Modal.Actions>
|
|
432
|
+
</Modal>
|
|
433
|
+
</>
|
|
356
434
|
);
|
|
357
435
|
}
|
|
358
436
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
3
4
|
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
4
5
|
|
|
5
6
|
import successSVG from '@plone/volto/icons/ready.svg';
|
|
@@ -7,7 +8,28 @@ import infoSVG from '@plone/volto/icons/info.svg';
|
|
|
7
8
|
import errorSVG from '@plone/volto/icons/error.svg';
|
|
8
9
|
import warningSVG from '@plone/volto/icons/warning.svg';
|
|
9
10
|
|
|
11
|
+
const messages = defineMessages({
|
|
12
|
+
success: {
|
|
13
|
+
id: 'toast_type_success',
|
|
14
|
+
defaultMessage: 'Success',
|
|
15
|
+
},
|
|
16
|
+
error: {
|
|
17
|
+
id: 'toast_type_error',
|
|
18
|
+
defaultMessage: 'Error',
|
|
19
|
+
},
|
|
20
|
+
warning: {
|
|
21
|
+
id: 'toast_type_warning',
|
|
22
|
+
defaultMessage: 'Warning',
|
|
23
|
+
},
|
|
24
|
+
info: {
|
|
25
|
+
id: 'toast_type_info',
|
|
26
|
+
defaultMessage: 'Information',
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
10
30
|
const Toast = (props) => {
|
|
31
|
+
const intl = useIntl();
|
|
32
|
+
|
|
11
33
|
function getIcon(props) {
|
|
12
34
|
if (props.info) {
|
|
13
35
|
return infoSVG;
|
|
@@ -22,12 +44,24 @@ const Toast = (props) => {
|
|
|
22
44
|
}
|
|
23
45
|
}
|
|
24
46
|
|
|
47
|
+
function getTypeLabel(props) {
|
|
48
|
+
if (props.error) return intl.formatMessage(messages.error);
|
|
49
|
+
if (props.warning) return intl.formatMessage(messages.warning);
|
|
50
|
+
if (props.info) return intl.formatMessage(messages.info);
|
|
51
|
+
if (props.success) return intl.formatMessage(messages.success);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
25
55
|
const { title, content } = props;
|
|
56
|
+
const typeLabel = getTypeLabel(props);
|
|
26
57
|
|
|
27
58
|
return (
|
|
28
59
|
<>
|
|
29
|
-
<Icon name={getIcon(props)} size="18px" />
|
|
60
|
+
<Icon name={getIcon(props)} size="18px" ariaHidden={true} />
|
|
30
61
|
<div className="toast-inner-content">
|
|
62
|
+
{typeLabel && (
|
|
63
|
+
<span className="visually-hidden-volto">{typeLabel}</span>
|
|
64
|
+
)}
|
|
31
65
|
{title && <h4>{title}</h4>}
|
|
32
66
|
<div>{content}</div>
|
|
33
67
|
</div>
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import renderer from 'react-test-renderer';
|
|
3
|
+
import { IntlProvider } from 'react-intl';
|
|
3
4
|
import Toast from './Toast';
|
|
4
5
|
|
|
5
6
|
test('renders a Toast info component', () => {
|
|
6
7
|
const component = renderer.create(
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
<IntlProvider locale="en">
|
|
9
|
+
<Toast
|
|
10
|
+
info
|
|
11
|
+
title="I'm a title"
|
|
12
|
+
content="This is the content, lorem ipsum"
|
|
13
|
+
/>
|
|
14
|
+
</IntlProvider>,
|
|
12
15
|
);
|
|
13
16
|
const json = component.toJSON();
|
|
14
17
|
expect(json).toMatchSnapshot();
|
|
@@ -85,6 +85,7 @@ class Search extends Component {
|
|
|
85
85
|
componentDidMount() {
|
|
86
86
|
this.doSearch();
|
|
87
87
|
this.setState({ isClient: true });
|
|
88
|
+
this.focusResults();
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
/**
|
|
@@ -97,6 +98,22 @@ class Search extends Component {
|
|
|
97
98
|
if (this.props.location.search !== nextProps.location.search) {
|
|
98
99
|
this.doSearch();
|
|
99
100
|
}
|
|
101
|
+
|
|
102
|
+
if (
|
|
103
|
+
JSON.stringify(this.props.search) !== JSON.stringify(nextProps.search)
|
|
104
|
+
) {
|
|
105
|
+
//on opening results page, move focus on results
|
|
106
|
+
this.focusResults();
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
focusResults = () => {
|
|
111
|
+
//on opening results page, move focus on results
|
|
112
|
+
setTimeout(function () {
|
|
113
|
+
if (document.querySelector('#page-search #search-results')) {
|
|
114
|
+
document.querySelector('#page-search #search-results').focus();
|
|
115
|
+
}
|
|
116
|
+
}, 100);
|
|
100
117
|
};
|
|
101
118
|
|
|
102
119
|
/**
|
|
@@ -162,7 +179,13 @@ class Search extends Component {
|
|
|
162
179
|
return (
|
|
163
180
|
<Container id="page-search">
|
|
164
181
|
<Helmet title={this.props.intl.formatMessage(messages.Search)} />
|
|
165
|
-
<div
|
|
182
|
+
<div
|
|
183
|
+
className="container"
|
|
184
|
+
role="region"
|
|
185
|
+
aria-live="polite"
|
|
186
|
+
id="search-results"
|
|
187
|
+
tabIndex={-1}
|
|
188
|
+
>
|
|
166
189
|
<article id="content">
|
|
167
190
|
<header>
|
|
168
191
|
<h1 className="documentFirstHeading">
|
|
@@ -153,6 +153,15 @@ export const hasUniqueItemsValidator = ({
|
|
|
153
153
|
return !isValid ? formatMessage(messages.uniqueItems) : null;
|
|
154
154
|
};
|
|
155
155
|
|
|
156
|
+
const formatDateValue = (isoString: string) => {
|
|
157
|
+
const date = new Date(isoString);
|
|
158
|
+
if (isNaN(date.getTime())) return isoString;
|
|
159
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
160
|
+
dateStyle: 'medium',
|
|
161
|
+
timeStyle: 'short',
|
|
162
|
+
}).format(date);
|
|
163
|
+
};
|
|
164
|
+
|
|
156
165
|
export const startEventDateRangeValidator = ({
|
|
157
166
|
value,
|
|
158
167
|
field,
|
|
@@ -163,7 +172,9 @@ export const startEventDateRangeValidator = ({
|
|
|
163
172
|
value && formData.end && new Date(value) < new Date(formData.end);
|
|
164
173
|
return !isValid
|
|
165
174
|
? formatMessage(messages.startEventRange, {
|
|
166
|
-
endDateValueOrEndFieldName: formData.end
|
|
175
|
+
endDateValueOrEndFieldName: formData.end
|
|
176
|
+
? formatDateValue(formData.end)
|
|
177
|
+
: 'end',
|
|
167
178
|
})
|
|
168
179
|
: null;
|
|
169
180
|
};
|
|
@@ -178,7 +189,9 @@ export const endEventDateRangeValidator = ({
|
|
|
178
189
|
value && formData.start && new Date(value) > new Date(formData.start);
|
|
179
190
|
return !isValid
|
|
180
191
|
? formatMessage(messages.endEventRange, {
|
|
181
|
-
startDateValueOrStartFieldName: formData.start
|
|
192
|
+
startDateValueOrStartFieldName: formData.start
|
|
193
|
+
? formatDateValue(formData.start)
|
|
194
|
+
: 'start',
|
|
182
195
|
})
|
|
183
196
|
: null;
|
|
184
197
|
};
|
|
@@ -31,6 +31,27 @@
|
|
|
31
31
|
line-height: initial;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// Restore visible focus for keyboard navigation — overrides Semantic UI's `outline: none` on form inputs
|
|
35
|
+
.ui.form input:not([type]),
|
|
36
|
+
.ui.form input[type='date'],
|
|
37
|
+
.ui.form input[type='datetime-local'],
|
|
38
|
+
.ui.form input[type='email'],
|
|
39
|
+
.ui.form input[type='number'],
|
|
40
|
+
.ui.form input[type='password'],
|
|
41
|
+
.ui.form input[type='search'],
|
|
42
|
+
.ui.form input[type='tel'],
|
|
43
|
+
.ui.form input[type='time'],
|
|
44
|
+
.ui.form input[type='text'],
|
|
45
|
+
.ui.form input[type='file'],
|
|
46
|
+
.ui.form input[type='url'],
|
|
47
|
+
.ui.form textarea {
|
|
48
|
+
padding-left: 5px;
|
|
49
|
+
|
|
50
|
+
&:focus-visible {
|
|
51
|
+
outline: revert;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
34
55
|
.ui.form .ui.input input:not([type]),
|
|
35
56
|
.ui.form .ui.input input[type='date'],
|
|
36
57
|
.ui.form .ui.input input[type='datetime-local'],
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
#main,
|
|
30
|
-
.slate-inline-toolbar.slate-toolbar
|
|
30
|
+
.slate-inline-toolbar.slate-toolbar,
|
|
31
|
+
.contenttype-plone-site .ui.page.modals {
|
|
31
32
|
.ui.basic.buttons .button,
|
|
32
33
|
.ui.basic.button {
|
|
33
34
|
-webkit-box-shadow: 0px 0px 0px @basicBorderSize transparent inset !important;
|
|
@@ -42,11 +43,16 @@
|
|
|
42
43
|
cursor: pointer;
|
|
43
44
|
text-align: initial;
|
|
44
45
|
|
|
45
|
-
&:focus {
|
|
46
|
+
&:focus:not(:focus-visible) {
|
|
46
47
|
outline: none;
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
.ui.basic.buttons .button:focus-visible,
|
|
52
|
+
.ui.basic.button:focus-visible {
|
|
53
|
+
outline: revert;
|
|
54
|
+
}
|
|
55
|
+
|
|
50
56
|
.ui.basic.primary.button,
|
|
51
57
|
.ui.basic.secondary.button {
|
|
52
58
|
box-shadow: none !important;
|
|
@@ -69,7 +75,7 @@
|
|
|
69
75
|
cursor: pointer;
|
|
70
76
|
text-align: initial;
|
|
71
77
|
|
|
72
|
-
&:focus {
|
|
78
|
+
&:focus:not(:focus-visible) {
|
|
73
79
|
outline: none;
|
|
74
80
|
}
|
|
75
81
|
}
|
|
@@ -85,3 +91,24 @@
|
|
|
85
91
|
margin: 0;
|
|
86
92
|
}
|
|
87
93
|
}
|
|
94
|
+
|
|
95
|
+
.modals {
|
|
96
|
+
.modal {
|
|
97
|
+
.actions {
|
|
98
|
+
display: flex;
|
|
99
|
+
justify-content: flex-end;
|
|
100
|
+
gap: 0.5em;
|
|
101
|
+
|
|
102
|
+
.ui.basic.button {
|
|
103
|
+
display: flex;
|
|
104
|
+
justify-content: center;
|
|
105
|
+
padding: 0.3em;
|
|
106
|
+
margin: 0;
|
|
107
|
+
|
|
108
|
+
svg {
|
|
109
|
+
margin: 0;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -4,7 +4,7 @@ export declare const RulesControlpanel: import("@loadable/component").LoadableCl
|
|
|
4
4
|
export declare const AddRuleControlpanel: import("@loadable/component").LoadableClassComponent<any>;
|
|
5
5
|
export declare const EditRuleControlpanel: import("@loadable/component").LoadableClassComponent<any>;
|
|
6
6
|
export declare const ConfigureRuleControlpanel: import("@loadable/component").LoadableClassComponent<any>;
|
|
7
|
-
export declare const UsersControlpanel: import("@loadable/component").
|
|
7
|
+
export declare const UsersControlpanel: import("@loadable/component").LoadableClassComponent<any>;
|
|
8
8
|
export declare const RenderUsers: import("@loadable/component").LoadableComponent<any>;
|
|
9
9
|
export declare const UserGroupMembershipControlPanel: import("@loadable/component").LoadableComponent<unknown>;
|
|
10
10
|
export declare const GroupsControlpanel: import("@loadable/component").LoadableClassComponent<any>;
|