@plone/volto 16.22.1 → 16.23.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 (49) hide show
  1. package/.changelog.draft +8 -15
  2. package/.yarn/install-state.gz +0 -0
  3. package/CHANGELOG.md +91 -1
  4. package/CONTRIBUTING.md +5 -1
  5. package/README.md +6 -0
  6. package/locales/es/LC_MESSAGES/volto.po +10 -10
  7. package/locales/es.json +1 -1
  8. package/package.json +1 -1
  9. package/packages/volto-slate/build/messages/src/blocks/Table/TableBlockEdit.json +90 -0
  10. package/packages/volto-slate/build/messages/src/blocks/Text/DefaultTextBlockEditor.json +6 -0
  11. package/packages/volto-slate/build/messages/src/blocks/Text/DetachedTextBlockEditor.json +6 -0
  12. package/packages/volto-slate/build/messages/src/blocks/Text/SlashMenu.json +6 -0
  13. package/packages/volto-slate/build/messages/src/editor/plugins/AdvancedLink/index.json +10 -0
  14. package/packages/volto-slate/build/messages/src/editor/plugins/Link/index.json +10 -0
  15. package/packages/volto-slate/build/messages/src/editor/plugins/Table/index.json +30 -0
  16. package/packages/volto-slate/build/messages/src/elementEditor/messages.json +10 -0
  17. package/packages/volto-slate/build/messages/src/widgets/HtmlSlateWidget.json +6 -0
  18. package/packages/volto-slate/build/messages/src/widgets/RichTextWidgetView.json +6 -0
  19. package/packages/volto-slate/package.json +1 -1
  20. package/pyvenv.cfg +3 -0
  21. package/share/man/man1/ttx.1 +225 -0
  22. package/src/components/manage/Blocks/Block/Settings.jsx +2 -0
  23. package/src/components/manage/Blocks/Block/Settings.test.jsx +90 -0
  24. package/src/components/manage/Blocks/Image/View.jsx +2 -1
  25. package/src/components/manage/Blocks/Listing/withQuerystringResults.jsx +18 -11
  26. package/src/components/manage/Contents/Contents.jsx +27 -0
  27. package/src/components/manage/Controlpanels/Rules/AddRule.jsx +1 -1
  28. package/src/components/manage/Controlpanels/Rules/EditRule.jsx +1 -1
  29. package/src/components/manage/Diff/DiffField.jsx +25 -1
  30. package/src/components/manage/Form/BlockDataForm.jsx +3 -2
  31. package/src/components/manage/Form/BlockDataForm.test.jsx +34 -2
  32. package/src/components/manage/Sharing/Sharing.jsx +11 -5
  33. package/src/components/manage/Widgets/ArrayWidget.jsx +3 -1
  34. package/src/components/manage/Widgets/ArrayWidget.test.jsx +45 -1
  35. package/src/components/manage/Widgets/FormFieldWrapper.jsx +1 -1
  36. package/src/components/manage/Widgets/SelectWidget.jsx +15 -1
  37. package/src/components/manage/Widgets/SelectWidget.test.jsx +45 -1
  38. package/src/components/theme/Sitemap/Sitemap.jsx +24 -13
  39. package/src/components/theme/Sitemap/Sitemap.test.jsx +23 -2
  40. package/styles/Vocab/Plone/accept.txt +9 -2
  41. package/styles/Vocab/Plone/reject.txt +2 -5
  42. package/theme/themes/pastanaga/collections/form.overrides +46 -0
  43. package/theme/themes/pastanaga/elements/input.overrides +6 -0
  44. package/theme/themes/pastanaga/elements/label.overrides +10 -0
  45. package/.gitignore~ +0 -71
  46. package/locales/volto.pot~ +0 -4705
  47. package/news/4547.breaking~ +0 -1
  48. package/package.json~ +0 -444
  49. package/src/config/index.js~ +0 -223
@@ -1334,6 +1334,9 @@ class Contents extends Component {
1334
1334
  as={Button}
1335
1335
  onClick={this.upload}
1336
1336
  className="upload"
1337
+ aria-label={this.props.intl.formatMessage(
1338
+ messages.upload,
1339
+ )}
1337
1340
  >
1338
1341
  <Icon
1339
1342
  name={uploadSVG}
@@ -1358,6 +1361,9 @@ class Contents extends Component {
1358
1361
  as={Button}
1359
1362
  onClick={this.rename}
1360
1363
  disabled={!selected}
1364
+ aria-label={this.props.intl.formatMessage(
1365
+ messages.rename,
1366
+ )}
1361
1367
  >
1362
1368
  <Icon
1363
1369
  name={renameSVG}
@@ -1380,6 +1386,9 @@ class Contents extends Component {
1380
1386
  as={Button}
1381
1387
  onClick={this.workflow}
1382
1388
  disabled={!selected}
1389
+ aria-label={this.props.intl.formatMessage(
1390
+ messages.state,
1391
+ )}
1383
1392
  >
1384
1393
  <Icon
1385
1394
  name={semaphoreSVG}
@@ -1402,6 +1411,9 @@ class Contents extends Component {
1402
1411
  as={Button}
1403
1412
  onClick={this.tags}
1404
1413
  disabled={!selected}
1414
+ aria-label={this.props.intl.formatMessage(
1415
+ messages.tags,
1416
+ )}
1405
1417
  >
1406
1418
  <Icon
1407
1419
  name={tagSVG}
@@ -1425,6 +1437,9 @@ class Contents extends Component {
1425
1437
  as={Button}
1426
1438
  onClick={this.properties}
1427
1439
  disabled={!selected}
1440
+ aria-label={this.props.intl.formatMessage(
1441
+ messages.properties,
1442
+ )}
1428
1443
  >
1429
1444
  <Icon
1430
1445
  name={propertiesSVG}
@@ -1449,6 +1464,9 @@ class Contents extends Component {
1449
1464
  as={Button}
1450
1465
  onClick={this.cut}
1451
1466
  disabled={!selected}
1467
+ aria-label={this.props.intl.formatMessage(
1468
+ messages.cut,
1469
+ )}
1452
1470
  >
1453
1471
  <Icon
1454
1472
  name={cutSVG}
@@ -1471,6 +1489,9 @@ class Contents extends Component {
1471
1489
  as={Button}
1472
1490
  onClick={this.copy}
1473
1491
  disabled={!selected}
1492
+ aria-label={this.props.intl.formatMessage(
1493
+ messages.copy,
1494
+ )}
1474
1495
  >
1475
1496
  <Icon
1476
1497
  name={copySVG}
@@ -1494,6 +1515,9 @@ class Contents extends Component {
1494
1515
  as={Button}
1495
1516
  onClick={this.paste}
1496
1517
  disabled={!this.props.action}
1518
+ aria-label={this.props.intl.formatMessage(
1519
+ messages.paste,
1520
+ )}
1497
1521
  >
1498
1522
  <Icon
1499
1523
  name={pasteSVG}
@@ -1517,6 +1541,9 @@ class Contents extends Component {
1517
1541
  as={Button}
1518
1542
  onClick={this.delete}
1519
1543
  disabled={!selected}
1544
+ aria-label={this.props.intl.formatMessage(
1545
+ messages.delete,
1546
+ )}
1520
1547
  >
1521
1548
  <Icon
1522
1549
  name={deleteSVG}
@@ -179,7 +179,7 @@ class AddRule extends Component {
179
179
  const { title, description, event, cascading, stop, enabled } = this.state;
180
180
  const triggeringEvents =
181
181
  this.props.events?.items && this.props.events?.items.length > 0
182
- ? this.props.events?.items.map((event) => [event.title, event.token])
182
+ ? this.props.events?.items.map((event) => [event.token, event.title])
183
183
  : '';
184
184
 
185
185
  return (
@@ -209,7 +209,7 @@ class EditRule extends Component {
209
209
 
210
210
  const triggeringEvents =
211
211
  this.props.events?.items && this.props.events?.items.length > 0
212
- ? this.props.events?.items.map((event) => [event.title, event.token])
212
+ ? this.props.events?.items.map((event) => [event.token, event.title])
213
213
  : '';
214
214
 
215
215
  return (
@@ -17,6 +17,7 @@ import { useSelector } from 'react-redux';
17
17
  import { Api } from '@plone/volto/helpers';
18
18
  import configureStore from '@plone/volto/store';
19
19
  import { DefaultView } from '@plone/volto/components/';
20
+ import { serializeNodes } from '@plone/volto-slate/editor/render';
20
21
 
21
22
  import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
22
23
 
@@ -69,7 +70,7 @@ const DiffField = ({
69
70
  .replace('\u202F', ' '),
70
71
  );
71
72
  break;
72
- case 'json':
73
+ case 'json': {
73
74
  const api = new Api();
74
75
  const history = createBrowserHistory();
75
76
  const store = configureStore(window.__data, history, api);
@@ -90,6 +91,29 @@ const DiffField = ({
90
91
  ),
91
92
  );
92
93
  break;
94
+ }
95
+ case 'slate': {
96
+ const api = new Api();
97
+ const history = createBrowserHistory();
98
+ const store = configureStore(window.__data, history, api);
99
+ parts = diffWords(
100
+ ReactDOMServer.renderToStaticMarkup(
101
+ <Provider store={store}>
102
+ <ConnectedRouter history={history}>
103
+ {serializeNodes(one)}
104
+ </ConnectedRouter>
105
+ </Provider>,
106
+ ),
107
+ ReactDOMServer.renderToStaticMarkup(
108
+ <Provider store={store}>
109
+ <ConnectedRouter history={history}>
110
+ {serializeNodes(two)}
111
+ </ConnectedRouter>
112
+ </Provider>,
113
+ ),
114
+ );
115
+ break;
116
+ }
93
117
  case 'textarea':
94
118
  default:
95
119
  parts = diffWords(one, two);
@@ -5,7 +5,7 @@ import { withVariationSchemaEnhancer } from '@plone/volto/helpers';
5
5
  const EnhancedBlockDataForm = withVariationSchemaEnhancer(InlineForm);
6
6
 
7
7
  export default function BlockDataForm(props) {
8
- const { onChangeBlock, block } = props;
8
+ const { onChangeBlock, block, applySchemaEnhancers = true } = props;
9
9
 
10
10
  if (!onChangeBlock) {
11
11
  // eslint-disable-next-line no-console
@@ -19,8 +19,9 @@ export default function BlockDataForm(props) {
19
19
  [block, onChangeBlock],
20
20
  );
21
21
 
22
+ const Form = applySchemaEnhancers ? EnhancedBlockDataForm : InlineForm;
22
23
  return (
23
- <EnhancedBlockDataForm
24
+ <Form
24
25
  {...props}
25
26
  onChangeFormData={onChangeBlock ? onChangeFormData : undefined}
26
27
  />
@@ -72,7 +72,7 @@ beforeAll(() => {
72
72
  });
73
73
 
74
74
  describe('BlockDataForm', () => {
75
- it('should does not add variations to schema when unneeded', () => {
75
+ it('should not add variations to schema when unneeded', () => {
76
76
  const WrappedBlockDataForm = withStateManagement(BlockDataForm);
77
77
  const store = mockStore({
78
78
  intl: {
@@ -103,7 +103,7 @@ describe('BlockDataForm', () => {
103
103
  expect(testSchema.fieldsets[0].fields).toStrictEqual([]);
104
104
  });
105
105
 
106
- it('should does not add variations when only one variation', () => {
106
+ it('should not add variations when only one variation', () => {
107
107
  const WrappedBlockDataForm = withStateManagement(BlockDataForm);
108
108
  const store = mockStore({
109
109
  intl: {
@@ -164,4 +164,36 @@ describe('BlockDataForm', () => {
164
164
  // schema is cloned, not mutated in place
165
165
  expect(testSchema.fieldsets[0].fields).toStrictEqual([]);
166
166
  });
167
+
168
+ it('should not add variations to schema when explicitly disabled', () => {
169
+ const WrappedBlockDataForm = withStateManagement(BlockDataForm);
170
+ const store = mockStore({
171
+ intl: {
172
+ locale: 'en',
173
+ messages: {},
174
+ },
175
+ });
176
+ const testSchema = {
177
+ fieldsets: [{ title: 'Default', id: 'default', fields: [] }],
178
+ properties: {},
179
+ required: [],
180
+ };
181
+ const formData = {
182
+ '@type': 'testBlock',
183
+ };
184
+ const { container } = render(
185
+ <Provider store={store}>
186
+ <WrappedBlockDataForm
187
+ formData={formData}
188
+ schema={testSchema}
189
+ onChangeField={(id, value) => {}}
190
+ applySchemaEnhancers={false}
191
+ />
192
+ </Provider>,
193
+ );
194
+ expect(container).toMatchSnapshot();
195
+
196
+ // schema is cloned, not mutated in place
197
+ expect(testSchema.fieldsets[0].fields).toStrictEqual([]);
198
+ });
167
199
  });
@@ -246,9 +246,9 @@ class SharingComponent extends Component {
246
246
  * @returns {undefined}
247
247
  */
248
248
  onToggleInherit() {
249
- this.setState({
250
- inherit: !this.state.inherit,
251
- });
249
+ this.setState((state) => ({
250
+ inherit: !state.inherit,
251
+ }));
252
252
  }
253
253
 
254
254
  /**
@@ -404,9 +404,15 @@ class SharingComponent extends Component {
404
404
  <Segment attached>
405
405
  <Form.Field>
406
406
  <Checkbox
407
- checked={this.state.inherit}
407
+ id="inherit-permissions-checkbox"
408
+ name="inherit-permissions-checkbox"
409
+ defaultChecked={this.state.inherit}
408
410
  onChange={this.onToggleInherit}
409
- label={this.props.intl.formatMessage(messages.inherit)}
411
+ label={
412
+ <label htmlFor="inherit-permissions-checkbox">
413
+ {this.props.intl.formatMessage(messages.inherit)}
414
+ </label>
415
+ }
410
416
  />
411
417
  </Form.Field>
412
418
  <p className="help">
@@ -325,7 +325,9 @@ class ArrayWidget extends Component {
325
325
  : this.props.choices
326
326
  ? [
327
327
  ...choices,
328
- ...(this.props.noValueOption && !this.props.default
328
+ ...(this.props.noValueOption &&
329
+ (this.props.default === undefined ||
330
+ this.props.default === null)
329
331
  ? [
330
332
  {
331
333
  label: this.props.intl.formatMessage(
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import renderer from 'react-test-renderer';
3
3
  import configureStore from 'redux-mock-store';
4
4
  import { Provider } from 'react-intl-redux';
5
- import { waitFor } from '@testing-library/react';
5
+ import { waitFor, render, fireEvent } from '@testing-library/react';
6
6
 
7
7
  import ArrayWidget from './ArrayWidget';
8
8
 
@@ -41,3 +41,47 @@ test('renders an array widget component', async () => {
41
41
  await waitFor(() => {});
42
42
  expect(component.toJSON()).toMatchSnapshot();
43
43
  });
44
+
45
+ test("No 'No value' option when default value is 0", async () => {
46
+ const store = mockStore({
47
+ intl: {
48
+ locale: 'en',
49
+ messages: {},
50
+ },
51
+ });
52
+
53
+ const choices = [
54
+ ['0', 'None'],
55
+ ['1', 'One'],
56
+ ];
57
+
58
+ const value = {
59
+ value: '0',
60
+ label: 'None',
61
+ };
62
+
63
+ const _default = 0;
64
+
65
+ const { container } = render(
66
+ <Provider store={store}>
67
+ <ArrayWidget
68
+ id="my-field"
69
+ title="My field"
70
+ fieldSet="default"
71
+ choices={choices}
72
+ default={_default}
73
+ value={value}
74
+ noValueOption={true}
75
+ onChange={() => {}}
76
+ onBlur={() => {}}
77
+ onClick={() => {}}
78
+ />
79
+ </Provider>,
80
+ );
81
+
82
+ fireEvent.mouseDown(
83
+ container.querySelector('.react-select__dropdown-indicator'),
84
+ { button: 0 },
85
+ );
86
+ expect(container).toMatchSnapshot();
87
+ });
@@ -96,7 +96,7 @@ class FormFieldWrapper extends Component {
96
96
  {this.props.children}
97
97
 
98
98
  {map(error, (message) => (
99
- <Label key={message} basic color="red" pointing>
99
+ <Label key={message} basic color="red" className="form-error-label">
100
100
  {message}
101
101
  </Label>
102
102
  ))}
@@ -167,6 +167,19 @@ class SelectWidget extends Component {
167
167
  }
168
168
  }
169
169
 
170
+ componentDidUpdate(prevProps) {
171
+ if (
172
+ this.props.vocabBaseUrl !== prevProps.vocabBaseUrl &&
173
+ (!this.props.choices || this.props.choices?.length === 0)
174
+ ) {
175
+ this.props.getVocabulary({
176
+ vocabNameOrURL: this.props.vocabBaseUrl,
177
+ size: -1,
178
+ subrequest: this.props.lang,
179
+ });
180
+ }
181
+ }
182
+
170
183
  /**
171
184
  * Render method.
172
185
  * @method render
@@ -190,7 +203,8 @@ class SelectWidget extends Component {
190
203
  })),
191
204
  // Only set "no-value" option if there's no default in the field
192
205
  // TODO: also if this.props.defaultValue?
193
- ...(this.props.noValueOption && !this.props.default
206
+ ...(this.props.noValueOption &&
207
+ (this.props.default === undefined || this.props.default === null)
194
208
  ? [
195
209
  {
196
210
  label: this.props.intl.formatMessage(messages.no_value),
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import configureStore from 'redux-mock-store';
3
3
  import { Provider } from 'react-intl-redux';
4
- import { waitFor, render, screen } from '@testing-library/react';
4
+ import { waitFor, render, screen, fireEvent } from '@testing-library/react';
5
5
 
6
6
  import SelectWidget from './SelectWidget';
7
7
 
@@ -43,3 +43,47 @@ test('renders a select widget component', async () => {
43
43
  await waitFor(() => screen.getByText('My field'));
44
44
  expect(container).toMatchSnapshot();
45
45
  });
46
+
47
+ test("No 'No value' option when default value is 0", async () => {
48
+ const store = mockStore({
49
+ intl: {
50
+ locale: 'en',
51
+ messages: {},
52
+ },
53
+ });
54
+
55
+ const choices = [
56
+ ['0', 'None'],
57
+ ['1', 'One'],
58
+ ];
59
+
60
+ const value = {
61
+ value: '0',
62
+ label: 'None',
63
+ };
64
+
65
+ const _default = 0;
66
+
67
+ const { container } = render(
68
+ <Provider store={store}>
69
+ <SelectWidget
70
+ id="my-field"
71
+ title="My field"
72
+ fieldSet="default"
73
+ choices={choices}
74
+ default={_default}
75
+ value={value}
76
+ onChange={() => {}}
77
+ onBlur={() => {}}
78
+ onClick={() => {}}
79
+ />
80
+ </Provider>,
81
+ );
82
+
83
+ await waitFor(() => screen.getByText('None'));
84
+ fireEvent.mouseDown(
85
+ container.querySelector('.react-select__dropdown-indicator'),
86
+ { button: 0 },
87
+ );
88
+ expect(container).toMatchSnapshot();
89
+ });
@@ -22,6 +22,13 @@ const messages = defineMessages({
22
22
  defaultMessage: 'Sitemap',
23
23
  },
24
24
  });
25
+
26
+ export function getSitemapPath(pathname = '', lang) {
27
+ const prefix = pathname.replace(/\/sitemap$/gm, '').replace(/^\//, '');
28
+ const path = prefix || lang || '';
29
+ return path;
30
+ }
31
+
25
32
  /**
26
33
  * Sitemap class.
27
34
  * @class Sitemap
@@ -39,11 +46,13 @@ class Sitemap extends Component {
39
46
 
40
47
  componentDidMount() {
41
48
  const { settings } = config;
42
- if (settings.isMultilingual) {
43
- this.props.getNavigation(`${toBackendLang(this.props.lang)}`, 4);
44
- } else {
45
- this.props.getNavigation('', 4);
46
- }
49
+
50
+ const lang = settings.isMultilingual
51
+ ? `${toBackendLang(this.props.lang)}`
52
+ : null;
53
+
54
+ const path = getSitemapPath(this.props.location.pathname, lang);
55
+ this.props.getNavigation(path, 4);
47
56
  }
48
57
 
49
58
  /**
@@ -105,15 +114,17 @@ export default compose(
105
114
  {
106
115
  key: 'navigation',
107
116
  promise: ({ location, store: { dispatch, getState } }) => {
117
+ if (!__SERVER__) return;
108
118
  const { settings } = config;
109
- const lang = getState().intl.locale;
110
- if (settings.isMultilingual) {
111
- return (
112
- __SERVER__ && dispatch(getNavigation(`${toBackendLang(lang)}`, 4))
113
- );
114
- } else {
115
- return __SERVER__ && dispatch(getNavigation('', 4));
116
- }
119
+
120
+ const path = getSitemapPath(
121
+ location.pathname,
122
+ settings.isMultilingual
123
+ ? toBackendLang(getState().intl.locale)
124
+ : null,
125
+ );
126
+
127
+ return dispatch(getNavigation(path, 4));
117
128
  },
118
129
  },
119
130
  ]),
@@ -4,7 +4,7 @@ import configureStore from 'redux-mock-store';
4
4
  import { Provider } from 'react-intl-redux';
5
5
  import { MemoryRouter } from 'react-router-dom';
6
6
 
7
- import { __test__ as Sitemap } from './Sitemap';
7
+ import { __test__ as Sitemap, getSitemapPath } from './Sitemap';
8
8
 
9
9
  const mockStore = configureStore();
10
10
 
@@ -46,7 +46,7 @@ describe('Sitemap', () => {
46
46
  const component = renderer.create(
47
47
  <Provider store={store}>
48
48
  <MemoryRouter>
49
- <Sitemap />
49
+ <Sitemap location={{ pathname: '/page-1' }} />
50
50
  </MemoryRouter>
51
51
  </Provider>,
52
52
  );
@@ -54,3 +54,24 @@ describe('Sitemap', () => {
54
54
  expect(json).toMatchSnapshot();
55
55
  });
56
56
  });
57
+
58
+ describe('getSitemapPath', () => {
59
+ it('accepts empty path', () => {
60
+ expect(getSitemapPath('', null)).toBe('');
61
+ });
62
+
63
+ it('accepts a path', () => {
64
+ expect(getSitemapPath('/page-1/sitemap', null)).toBe('page-1');
65
+ });
66
+ it('accepts a path', () => {
67
+ expect(getSitemapPath('/page-1/sitemap', null)).toBe('page-1');
68
+ });
69
+
70
+ it('uses a language as default root', () => {
71
+ expect(getSitemapPath('/', 'de')).toBe('de');
72
+ });
73
+
74
+ it('accepts language-rooted paths', () => {
75
+ expect(getSitemapPath('/de/mission', 'de')).toBe('de/mission');
76
+ });
77
+ });
@@ -1,10 +1,17 @@
1
+ -{0,1}plone-{0,1}
2
+ -{0,1}volto-{0,1}
1
3
  `plone.restapi`
2
4
  `plone.volto`
5
+ [Aa]sync
6
+ [Bb]ackend
7
+ JavaScript
3
8
  npm
9
+ nvm
10
+ Pastanaga
4
11
  Plone
5
12
  Razzle
6
13
  RichText
14
+ Sass
7
15
  Volto
16
+ Vue
8
17
  Zope
9
- JavaScript
10
- NodeJS
@@ -1,5 +1,2 @@
1
- node
2
- nodejs
3
- javascript
4
- js
5
- Javascript
1
+ [^.]js
2
+ NodeJS
@@ -49,6 +49,52 @@
49
49
  }
50
50
  }
51
51
 
52
+ .ui.form .fields.error .field textarea,
53
+ .ui.form .fields.error .field select,
54
+ .ui.form .fields.error .field input:not([type]),
55
+ .ui.form .fields.error .field input[type='date'],
56
+ .ui.form .fields.error .field input[type='datetime-local'],
57
+ .ui.form .fields.error .field input[type='email'],
58
+ .ui.form .fields.error .field input[type='number'],
59
+ .ui.form .fields.error .field input[type='password'],
60
+ .ui.form .fields.error .field input[type='search'],
61
+ .ui.form .fields.error .field input[type='tel'],
62
+ .ui.form .fields.error .field input[type='time'],
63
+ .ui.form .fields.error .field input[type='text'],
64
+ .ui.form .fields.error .field input[type='file'],
65
+ .ui.form .fields.error .field input[type='url'],
66
+ .ui.form .field.error textarea,
67
+ .ui.form .field.error select,
68
+ .ui.form .field.error input:not([type]),
69
+ .ui.form .field.error input[type='date'],
70
+ .ui.form .field.error input[type='datetime-local'],
71
+ .ui.form .field.error input[type='email'],
72
+ .ui.form .field.error input[type='number'],
73
+ .ui.form .field.error input[type='password'],
74
+ .ui.form .field.error input[type='search'],
75
+ .ui.form .field.error input[type='tel'],
76
+ .ui.form .field.error input[type='time'],
77
+ .ui.form .field.error input[type='text'],
78
+ .ui.form .field.error input[type='file'],
79
+ .ui.form .field.error input[type='url'],
80
+ .ui.form .field.error textarea:focus,
81
+ .ui.form .field.error select:focus,
82
+ .ui.form .field.error input:not([type]):focus,
83
+ .ui.form .field.error input[type='date']:focus,
84
+ .ui.form .field.error input[type='datetime-local']:focus,
85
+ .ui.form .field.error input[type='email']:focus,
86
+ .ui.form .field.error input[type='number']:focus,
87
+ .ui.form .field.error input[type='password']:focus,
88
+ .ui.form .field.error input[type='search']:focus,
89
+ .ui.form .field.error input[type='tel']:focus,
90
+ .ui.form .field.error input[type='time']:focus,
91
+ .ui.form .field.error input[type='text']:focus,
92
+ .ui.form .field.error input[type='file']:focus,
93
+ .ui.form .field.error input[type='url']:focus {
94
+ border-color: #d01157;
95
+ background: none;
96
+ }
97
+
52
98
  .ui.form .field > .selection.dropdown {
53
99
  height: 60px;
54
100
  }
@@ -6,6 +6,12 @@
6
6
  font-weight: @inputFontWeight;
7
7
  }
8
8
 
9
+ /* This aligns the height of the field name label to the other side in case
10
+ of an error is present, it overrides a default from SemanticUI grid definitions. */
11
+ .ui.grid > .stretched.row > .column > .wrapper {
12
+ flex-grow: 0;
13
+ }
14
+
9
15
  .inline.field {
10
16
  .wrapper {
11
17
  display: flex;
@@ -6,3 +6,13 @@
6
6
  margin: 0em 0em 0em 0.75em;
7
7
  }
8
8
  }
9
+
10
+ .ui.form {
11
+ .ui.basic.red.label.form-error-label {
12
+ padding-left: 0;
13
+ border: none;
14
+ margin-top: 0;
15
+ color: #d01157 !important;
16
+ font-weight: normal;
17
+ }
18
+ }