@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.
Files changed (153) hide show
  1. package/.release-it.json +3 -0
  2. package/CHANGELOG.md +24 -0
  3. package/locales/af/LC_MESSAGES/volto.po +30 -0
  4. package/locales/af.json +1 -1
  5. package/locales/ar/LC_MESSAGES/volto.po +30 -0
  6. package/locales/ar.json +1 -1
  7. package/locales/bg/LC_MESSAGES/volto.po +30 -0
  8. package/locales/bg.json +1 -1
  9. package/locales/bn/LC_MESSAGES/volto.po +30 -0
  10. package/locales/bn.json +1 -1
  11. package/locales/ca/LC_MESSAGES/volto.po +32 -2
  12. package/locales/ca.json +1 -1
  13. package/locales/cs/LC_MESSAGES/volto.po +30 -0
  14. package/locales/cs.json +1 -1
  15. package/locales/cy/LC_MESSAGES/volto.po +30 -0
  16. package/locales/cy.json +1 -1
  17. package/locales/da/LC_MESSAGES/volto.po +30 -0
  18. package/locales/da.json +1 -1
  19. package/locales/de/LC_MESSAGES/volto.po +34 -4
  20. package/locales/de.json +1 -1
  21. package/locales/el/LC_MESSAGES/volto.po +30 -0
  22. package/locales/el.json +1 -1
  23. package/locales/en/LC_MESSAGES/volto.po +30 -0
  24. package/locales/en.json +1 -1
  25. package/locales/en_AU/LC_MESSAGES/volto.po +30 -0
  26. package/locales/en_AU.json +1 -1
  27. package/locales/en_GB/LC_MESSAGES/volto.po +30 -0
  28. package/locales/en_GB.json +1 -1
  29. package/locales/eo/LC_MESSAGES/volto.po +30 -0
  30. package/locales/eo.json +1 -1
  31. package/locales/es/LC_MESSAGES/volto.po +31 -1
  32. package/locales/es.json +1 -1
  33. package/locales/et/LC_MESSAGES/volto.po +30 -0
  34. package/locales/et.json +1 -1
  35. package/locales/eu/LC_MESSAGES/volto.po +33 -3
  36. package/locales/eu.json +1 -1
  37. package/locales/fa/LC_MESSAGES/volto.po +30 -0
  38. package/locales/fa.json +1 -1
  39. package/locales/fi/LC_MESSAGES/volto.po +30 -0
  40. package/locales/fi.json +1 -1
  41. package/locales/fr/LC_MESSAGES/volto.po +31 -1
  42. package/locales/fr.json +1 -1
  43. package/locales/fu/LC_MESSAGES/volto.po +30 -0
  44. package/locales/fu.json +1 -1
  45. package/locales/gl/LC_MESSAGES/volto.po +30 -0
  46. package/locales/gl.json +1 -1
  47. package/locales/he/LC_MESSAGES/volto.po +30 -0
  48. package/locales/he.json +1 -1
  49. package/locales/hi/LC_MESSAGES/volto.po +33 -3
  50. package/locales/hi.json +1 -1
  51. package/locales/hr/LC_MESSAGES/volto.po +30 -0
  52. package/locales/hr.json +1 -1
  53. package/locales/hu/LC_MESSAGES/volto.po +30 -0
  54. package/locales/hu.json +1 -1
  55. package/locales/hy/LC_MESSAGES/volto.po +30 -0
  56. package/locales/hy.json +1 -1
  57. package/locales/id/LC_MESSAGES/volto.po +30 -0
  58. package/locales/id.json +1 -1
  59. package/locales/it/LC_MESSAGES/volto.po +31 -1
  60. package/locales/it.json +1 -1
  61. package/locales/ja/LC_MESSAGES/volto.po +30 -0
  62. package/locales/ja.json +1 -1
  63. package/locales/ka/LC_MESSAGES/volto.po +30 -0
  64. package/locales/ka.json +1 -1
  65. package/locales/kn/LC_MESSAGES/volto.po +30 -0
  66. package/locales/kn.json +1 -1
  67. package/locales/ko/LC_MESSAGES/volto.po +30 -0
  68. package/locales/ko.json +1 -1
  69. package/locales/lt/LC_MESSAGES/volto.po +30 -0
  70. package/locales/lt.json +1 -1
  71. package/locales/lv/LC_MESSAGES/volto.po +30 -0
  72. package/locales/lv.json +1 -1
  73. package/locales/mi/LC_MESSAGES/volto.po +30 -0
  74. package/locales/mi.json +1 -1
  75. package/locales/mk/LC_MESSAGES/volto.po +30 -0
  76. package/locales/mk.json +1 -1
  77. package/locales/my/LC_MESSAGES/volto.po +30 -0
  78. package/locales/my.json +1 -1
  79. package/locales/nb_NO/LC_MESSAGES/volto.po +30 -0
  80. package/locales/nb_NO.json +1 -1
  81. package/locales/nl/LC_MESSAGES/volto.po +31 -1
  82. package/locales/nl.json +1 -1
  83. package/locales/nn/LC_MESSAGES/volto.po +30 -0
  84. package/locales/nn.json +1 -1
  85. package/locales/pl/LC_MESSAGES/volto.po +30 -0
  86. package/locales/pl.json +1 -1
  87. package/locales/pt/LC_MESSAGES/volto.po +30 -0
  88. package/locales/pt.json +1 -1
  89. package/locales/pt_BR/LC_MESSAGES/volto.po +44 -14
  90. package/locales/pt_BR.json +1 -1
  91. package/locales/rm/LC_MESSAGES/volto.po +30 -0
  92. package/locales/rm.json +1 -1
  93. package/locales/ro/LC_MESSAGES/volto.po +31 -1
  94. package/locales/ro.json +1 -1
  95. package/locales/ru/LC_MESSAGES/volto.po +31 -1
  96. package/locales/ru.json +1 -1
  97. package/locales/sk/LC_MESSAGES/volto.po +30 -0
  98. package/locales/sk.json +1 -1
  99. package/locales/sl/LC_MESSAGES/volto.po +30 -0
  100. package/locales/sl.json +1 -1
  101. package/locales/sm/LC_MESSAGES/volto.po +30 -0
  102. package/locales/sm.json +1 -1
  103. package/locales/sq/LC_MESSAGES/volto.po +30 -0
  104. package/locales/sq.json +1 -1
  105. package/locales/sr/LC_MESSAGES/volto.po +30 -0
  106. package/locales/sr.json +1 -1
  107. package/locales/sr@cyrl/LC_MESSAGES/volto.po +30 -0
  108. package/locales/sr@cyrl.json +1 -1
  109. package/locales/sr@latn/LC_MESSAGES/volto.po +30 -0
  110. package/locales/sr@latn.json +1 -1
  111. package/locales/sv/LC_MESSAGES/volto.po +30 -0
  112. package/locales/sv.json +1 -1
  113. package/locales/ta/LC_MESSAGES/volto.po +31 -1
  114. package/locales/ta.json +1 -1
  115. package/locales/te/LC_MESSAGES/volto.po +30 -0
  116. package/locales/te.json +1 -1
  117. package/locales/th/LC_MESSAGES/volto.po +30 -0
  118. package/locales/th.json +1 -1
  119. package/locales/to/LC_MESSAGES/volto.po +30 -0
  120. package/locales/to.json +1 -1
  121. package/locales/tr/LC_MESSAGES/volto.po +30 -0
  122. package/locales/tr.json +1 -1
  123. package/locales/uk/LC_MESSAGES/volto.po +30 -0
  124. package/locales/uk.json +1 -1
  125. package/locales/vi/LC_MESSAGES/volto.po +30 -0
  126. package/locales/vi.json +1 -1
  127. package/locales/volto.pot +31 -1
  128. package/locales/zh_CN/LC_MESSAGES/volto.po +30 -0
  129. package/locales/zh_CN.json +1 -1
  130. package/locales/zh_Hant/LC_MESSAGES/volto.po +30 -0
  131. package/locales/zh_Hant.json +1 -1
  132. package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +30 -0
  133. package/locales/zh_Hant_HK.json +1 -1
  134. package/news/7308.fix +1 -0
  135. package/news/8084.fix +1 -0
  136. package/package.json +14 -14
  137. package/src/components/manage/Controlpanels/ContentTypes.jsx +9 -2
  138. package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +58 -5
  139. package/src/components/manage/Controlpanels/Users/UsersControlpanel.ssr.test.jsx +624 -0
  140. package/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx +8 -0
  141. package/src/components/manage/Form/Form.jsx +6 -1
  142. package/src/components/manage/Form/ModalForm.jsx +165 -87
  143. package/src/components/manage/Sidebar/Sidebar.jsx +1 -0
  144. package/src/components/manage/Toast/Toast.jsx +35 -1
  145. package/src/components/manage/Toast/Toast.test.jsx +8 -5
  146. package/src/components/theme/Search/Search.jsx +24 -1
  147. package/src/helpers/FormValidation/validators.ts +15 -2
  148. package/theme/themes/pastanaga/collections/form.overrides +21 -0
  149. package/theme/themes/pastanaga/elements/button.overrides +30 -3
  150. package/theme/themes/pastanaga/extras/main.less +1 -0
  151. package/types/components/manage/Controlpanels/Users/UsersControlpanel.d.ts +2 -6
  152. package/types/components/manage/Controlpanels/Users/UsersControlpanel.ssr.test.d.ts +1 -0
  153. 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
- async componentDidUpdate(prevProps, prevState) {
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
- <Modal
263
- dimmer={this.props.dimmer}
264
- open={this.props.open}
265
- className={this.props.className}
266
- >
267
- <Header>{this.props.title}</Header>
268
- <Dimmer active={this.props.loading}>
269
- <Loader>
270
- {this.props.loadingMessage || (
271
- <FormattedMessage id="Loading" defaultMessage="Loading." />
272
- )}
273
- </Loader>
274
- </Dimmer>
275
- <Modal.Content scrolling>
276
- <UiForm
277
- method="post"
278
- onSubmit={this.onSubmit}
279
- error={state_errors || Boolean(this.props.submitError)}
280
- >
281
- {description}
282
- <Message error>
283
- {state_errors ? (
284
- <FormattedMessage
285
- id="There were some errors."
286
- defaultMessage="There were some errors."
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
- <div>{this.props.submitError}</div>
292
- </Message>
293
- {schema.fieldsets?.length > 1 && (
294
- <Menu tabular stackable>
295
- {map(schema.fieldsets, (item, index) => (
296
- <Menu.Item
297
- name={item.id}
298
- index={index}
299
- key={item.id}
300
- active={this.state.currentTab === index}
301
- onClick={this.selectTab}
302
- >
303
- {item.title}
304
- </Menu.Item>
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
- </Menu>
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
- secondary
346
- aria-label={this.props.intl.formatMessage(messages.cancel)}
347
- title={this.props.intl.formatMessage(messages.cancel)}
348
- floated="right"
349
- onClick={onCancel}
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={clearSVG} className="circled" size="30px" />
429
+ <Icon name={aheadSVG} className="contents circled" size="30px" />
352
430
  </Button>
353
- )}
354
- </Modal.Actions>
355
- </Modal>
431
+ </Modal.Actions>
432
+ </Modal>
433
+ </>
356
434
  );
357
435
  }
358
436
  }
@@ -155,6 +155,7 @@ const Sidebar = (props) => {
155
155
  key: 'documentTab',
156
156
  as: 'button',
157
157
  className: 'ui button',
158
+ type: 'button',
158
159
  content: type || intl.formatMessage(messages.document),
159
160
  },
160
161
  pane: (
@@ -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
- <Toast
8
- info
9
- title="I'm a title"
10
- content="This is the content, lorem ipsum"
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 className="container">
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 || '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 || '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
+ }
@@ -360,6 +360,7 @@ button {
360
360
  margin-left: 8px;
361
361
 
362
362
  h4 {
363
+ display: inline;
363
364
  margin-bottom: 0;
364
365
  font-weight: @bold;
365
366
  }
@@ -1,6 +1,2 @@
1
- export default UsersControlpanel;
2
- /**
3
- * UsersControlpanel functional component.
4
- * @function UsersControlpanel
5
- */
6
- declare function UsersControlpanel(): import("react/jsx-runtime").JSX.Element;
1
+ declare const _default: any;
2
+ export default _default;
@@ -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").LoadableComponent<unknown>;
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>;