@axinom/mosaic-ui 0.56.0-rc.5 → 0.56.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 (47) hide show
  1. package/dist/components/Accordion/Accordion.d.ts.map +1 -1
  2. package/dist/components/Accordion/AccordionItem/AccordionItem.d.ts.map +1 -1
  3. package/dist/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.d.ts.map +1 -1
  4. package/dist/components/FormElements/BooleanView/BooleanViewField.d.ts.map +1 -1
  5. package/dist/components/FormElements/Checkbox/Checkbox.d.ts.map +1 -1
  6. package/dist/components/FormElements/CustomTags/CustomTags.d.ts.map +1 -1
  7. package/dist/components/FormElements/DateTimeField/DateTimeText.d.ts.map +1 -1
  8. package/dist/components/FormElements/DynamicDataListControl/DynamicDataListControl.d.ts.map +1 -1
  9. package/dist/components/FormElements/FormElementContainer/FormElementContainer.d.ts +2 -0
  10. package/dist/components/FormElements/FormElementContainer/FormElementContainer.d.ts.map +1 -1
  11. package/dist/components/FormElements/ReadOnlyText/ReadOnlyTextField.d.ts +1 -1
  12. package/dist/components/FormElements/ReadOnlyText/ReadOnlyTextField.d.ts.map +1 -1
  13. package/dist/components/FormElements/Select/Select.d.ts.map +1 -1
  14. package/dist/components/FormElements/SingleLineText/SingleLineText.d.ts.map +1 -1
  15. package/dist/components/FormElements/Tags/Tags.d.ts.map +1 -1
  16. package/dist/components/FormElements/TextArea/TextArea.d.ts.map +1 -1
  17. package/dist/components/FormElements/ToggleButton/ToggleButton.d.ts.map +1 -1
  18. package/dist/index.es.js +4 -4
  19. package/dist/index.es.js.map +1 -1
  20. package/dist/index.js +4 -4
  21. package/dist/index.js.map +1 -1
  22. package/package.json +5 -4
  23. package/src/components/Accordion/Accordion.scss +5 -0
  24. package/src/components/Accordion/Accordion.spec.tsx +5 -2
  25. package/src/components/Accordion/Accordion.tsx +4 -6
  26. package/src/components/Accordion/AccordionItem/AccordionItem.scss +7 -2
  27. package/src/components/Accordion/AccordionItem/AccordionItem.tsx +4 -2
  28. package/src/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.spec.tsx +24 -24
  29. package/src/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.tsx +32 -33
  30. package/src/components/FormElements/BooleanView/BooleanViewField.spec.tsx +4 -4
  31. package/src/components/FormElements/BooleanView/BooleanViewField.tsx +1 -5
  32. package/src/components/FormElements/Checkbox/Checkbox.stories.tsx +2 -2
  33. package/src/components/FormElements/Checkbox/Checkbox.tsx +2 -1
  34. package/src/components/FormElements/CustomTags/CustomTags.scss +2 -1
  35. package/src/components/FormElements/CustomTags/CustomTags.tsx +5 -2
  36. package/src/components/FormElements/DateTimeField/DateTimeText.stories.tsx +2 -2
  37. package/src/components/FormElements/DateTimeField/DateTimeText.tsx +2 -1
  38. package/src/components/FormElements/DynamicDataListControl/DynamicDataListControl.tsx +1 -0
  39. package/src/components/FormElements/FileUploadControl/FileUploadControl.tsx +2 -2
  40. package/src/components/FormElements/FormElementContainer/FormElementContainer.tsx +5 -1
  41. package/src/components/FormElements/ReadOnlyText/ReadOnlyTextField.tsx +3 -0
  42. package/src/components/FormElements/Select/Select.tsx +2 -1
  43. package/src/components/FormElements/SingleLineText/SingleLineText.tsx +2 -1
  44. package/src/components/FormElements/Tags/Tags.tsx +2 -1
  45. package/src/components/FormElements/TextArea/TextArea.tsx +2 -1
  46. package/src/components/FormElements/ToggleButton/ToggleButton.stories.tsx +1 -1
  47. package/src/components/FormElements/ToggleButton/ToggleButton.tsx +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axinom/mosaic-ui",
3
- "version": "0.56.0-rc.5",
3
+ "version": "0.56.0",
4
4
  "description": "UI components for building Axinom Mosaic applications",
5
5
  "author": "Axinom",
6
6
  "license": "PROPRIETARY",
@@ -32,7 +32,7 @@
32
32
  "build-storybook": "storybook build"
33
33
  },
34
34
  "dependencies": {
35
- "@axinom/mosaic-core": "^0.4.29-rc.5",
35
+ "@axinom/mosaic-core": "^0.4.29",
36
36
  "@faker-js/faker": "^7.4.0",
37
37
  "@geoffcox/react-splitter": "^2.1.2",
38
38
  "@mui/base": "5.0.0-beta.40",
@@ -40,6 +40,7 @@
40
40
  "clsx": "^1.1.0",
41
41
  "lodash": "^4.17.21",
42
42
  "luxon": "^3.3.0",
43
+ "postcss": "^8.4.4",
43
44
  "react-beautiful-dnd": "^13.1.1",
44
45
  "react-calendar": "^3.3.1",
45
46
  "react-content-loader": "^6.0.3",
@@ -93,7 +94,7 @@
93
94
  "rimraf": "^3.0.2",
94
95
  "rollup": "^2.28.1",
95
96
  "rollup-plugin-peer-deps-external": "^2.2.3",
96
- "rollup-plugin-postcss": "^2.9.0",
97
+ "rollup-plugin-postcss": "^4.0.2",
97
98
  "rollup-plugin-terser": "^5.3.1",
98
99
  "rollup-plugin-typescript2": "^0.29.0",
99
100
  "rollup-plugin-visualizer": "^5.8.3",
@@ -107,5 +108,5 @@
107
108
  "publishConfig": {
108
109
  "access": "public"
109
110
  },
110
- "gitHead": "02b282719a41213749fe017cb2b2a18bd950a8b6"
111
+ "gitHead": "c3802f0be74822f25268d4fdf7497a1c1577309d"
111
112
  }
@@ -41,6 +41,7 @@
41
41
  }
42
42
 
43
43
  .button {
44
+ transition: transform 200ms ease;
44
45
  svg {
45
46
  height: 40%;
46
47
  }
@@ -51,3 +52,7 @@
51
52
  }
52
53
  }
53
54
  }
55
+
56
+ .rotated {
57
+ transform: rotate(90deg);
58
+ }
@@ -85,7 +85,7 @@ describe('Accordion', () => {
85
85
  });
86
86
  });
87
87
 
88
- it('displays collapse icon when one or more children are expanded', () => {
88
+ it('displays rotated ChevronRight icon when one or more children are expanded', () => {
89
89
  const wrapper = mount(
90
90
  <Accordion header={<b>Header</b>}>
91
91
  <AccordionItem header={<b>Item 1</b>}>
@@ -103,6 +103,7 @@ describe('Accordion', () => {
103
103
  let button = wrapper.find(Button).first();
104
104
 
105
105
  expect(button.prop('icon')).toBe(IconName.ChevronRight);
106
+ expect(button.hasClass('rotated')).toBe(false);
106
107
 
107
108
  const item = wrapper.find(AccordionItem).last();
108
109
 
@@ -110,12 +111,14 @@ describe('Accordion', () => {
110
111
 
111
112
  button = wrapper.find(Button).first();
112
113
 
113
- expect(button.prop('icon')).toBe(IconName.ChevronDown);
114
+ expect(button.prop('icon')).toBe(IconName.ChevronRight);
115
+ expect(button.hasClass('rotated')).toBe(true);
114
116
 
115
117
  item.find('button').simulate('click');
116
118
 
117
119
  button = wrapper.find(Button).first();
118
120
 
119
121
  expect(button.prop('icon')).toBe(IconName.ChevronRight);
122
+ expect(button.hasClass('rotated')).toBe(false);
120
123
  });
121
124
  });
@@ -95,11 +95,7 @@ export const Accordion: React.FC<AccordionProps> = ({
95
95
  {header && (
96
96
  <div className={clsx(classes.header)} data-test-id="accordion-header">
97
97
  <Button
98
- icon={
99
- expandAll.isExpanded
100
- ? IconName.ChevronDown
101
- : IconName.ChevronRight
102
- }
98
+ icon={IconName.ChevronRight}
103
99
  onButtonClicked={() => {
104
100
  const updatedState = { ...expanded };
105
101
 
@@ -110,7 +106,9 @@ export const Accordion: React.FC<AccordionProps> = ({
110
106
  setExpanded(updatedState);
111
107
  expandAll.toggleExpanded();
112
108
  }}
113
- className={clsx(classes.button)}
109
+ className={clsx(classes.button, {
110
+ [classes.rotated]: expandAll.isExpanded,
111
+ })}
114
112
  buttonContext={ButtonContext.None}
115
113
  ></Button>
116
114
  {header}
@@ -6,10 +6,10 @@
6
6
  align-items: center;
7
7
  background-color: $light-gray;
8
8
  margin-top: 4px;
9
- transition: box-shadow 0.15s ease-in-out 0s;
10
9
  height: 50px;
11
10
  cursor: pointer;
12
11
  color: var(--accordion-item-text-color, $accordion-item-text-color);
12
+ transition: background-color 200ms cubic-bezier(0.4, 0, 0.2, 1) 0s;
13
13
 
14
14
  &:hover {
15
15
  background-color: var(
@@ -30,7 +30,7 @@
30
30
 
31
31
  .container {
32
32
  overflow: hidden;
33
- transition: max-height 100ms linear;
33
+ transition: max-height 200ms cubic-bezier(0.4, 0, 0.2, 1);
34
34
  }
35
35
 
36
36
  .expanded {
@@ -52,9 +52,14 @@
52
52
  .button {
53
53
  svg {
54
54
  height: 40%;
55
+ transition: transform 200ms cubic-bezier(0.4, 0, 0.2, 1);
55
56
  }
56
57
 
57
58
  svg * {
58
59
  stroke: var(--accordion-item-arrow-color, $accordion-item-arrow-color);
59
60
  }
61
+
62
+ &.rotated > svg {
63
+ transform: rotate(90deg);
64
+ }
60
65
  }
@@ -62,8 +62,10 @@ export const AccordionItem: React.FC<AccordionItemProps> = ({
62
62
  onClick={toggleExpanded}
63
63
  >
64
64
  <Button
65
- icon={isExpanded ? IconName.ChevronDown : IconName.ChevronRight}
66
- className={classes.button}
65
+ icon={IconName.ChevronRight}
66
+ className={clsx(classes.button, {
67
+ [classes.rotated]: isExpanded,
68
+ })}
67
69
  buttonContext={ButtonContext.None}
68
70
  />
69
71
  {header}
@@ -261,7 +261,7 @@ describe('DynamicListDataEntry', () => {
261
261
  expect(input.prop('disabled')).toBe(true);
262
262
  });
263
263
 
264
- it('accepts default values and sets them', () => {
264
+ it('accepts default values and sets them', async () => {
265
265
  const defaultData: Partial<TestData> = {
266
266
  desc: 'Description',
267
267
  };
@@ -280,15 +280,15 @@ describe('DynamicListDataEntry', () => {
280
280
 
281
281
  const button = wrapper.find(Button);
282
282
 
283
- act(() => {
283
+ await act(async () => {
284
284
  // @ts-expect-error not full event args object
285
- button.prop('onButtonClicked')?.(mockEvent);
285
+ await button.prop('onButtonClicked')?.(mockEvent);
286
286
  });
287
287
 
288
288
  expect(spy).toHaveBeenCalledWith(defaultData);
289
289
  });
290
290
 
291
- it(`does not emit onActionClicked if data is invalid`, () => {
291
+ it(`does not emit onActionClicked if data is invalid`, async () => {
292
292
  const spy = jest.fn();
293
293
  const mockEvent = {
294
294
  e: 'event',
@@ -307,15 +307,15 @@ describe('DynamicListDataEntry', () => {
307
307
 
308
308
  const button = wrapper.find(Button);
309
309
 
310
- act(() => {
310
+ await act(async () => {
311
311
  // @ts-expect-error not full event args object
312
- button.prop('onButtonClicked')?.(mockEvent);
312
+ await button.prop('onButtonClicked')?.(mockEvent);
313
313
  });
314
314
 
315
315
  expect(spy).not.toHaveBeenCalled();
316
316
  });
317
317
 
318
- it(`emits onActionClicked with property name and new value if there is data to send`, () => {
318
+ it(`emits onActionClicked with property name and new value if there is data to send`, async () => {
319
319
  const spy = jest.fn();
320
320
  const mockNewValue = 'test-value';
321
321
  const mockEvent = {
@@ -341,16 +341,16 @@ describe('DynamicListDataEntry', () => {
341
341
 
342
342
  const button = wrapper.find(Button);
343
343
 
344
- act(() => {
344
+ await act(async () => {
345
345
  // @ts-expect-error not full event args object
346
- button.prop('onButtonClicked')?.(mockEvent);
346
+ await button.prop('onButtonClicked')?.(mockEvent);
347
347
  });
348
348
 
349
349
  expect(spy).toHaveBeenCalledTimes(1);
350
350
  expect(spy).toHaveBeenCalledWith({ [propertyName]: mockNewValue });
351
351
  });
352
352
 
353
- it(`emits row data and new position when 'allowReordering' is true, 'newDataPosition' is defined, and is 'positionKey' is defined`, () => {
353
+ it(`emits row data and new position when 'allowReordering' is true, 'newDataPosition' is defined, and is 'positionKey' is defined`, async () => {
354
354
  const spy = jest.fn();
355
355
  const mockEvent = {
356
356
  e: 'event',
@@ -381,9 +381,9 @@ describe('DynamicListDataEntry', () => {
381
381
 
382
382
  const button = wrapper.find(Button);
383
383
 
384
- act(() => {
384
+ await act(async () => {
385
385
  // @ts-expect-error not full event args object
386
- button.prop('onButtonClicked')?.(mockEvent);
386
+ await button.prop('onButtonClicked')?.(mockEvent);
387
387
  });
388
388
 
389
389
  expect(spy).toHaveBeenCalledTimes(1);
@@ -393,7 +393,7 @@ describe('DynamicListDataEntry', () => {
393
393
  });
394
394
  });
395
395
 
396
- it(`emits only row data when 'allowReordering' is false, 'newDataPosition' is defined, and is 'positionKey' is defined`, () => {
396
+ it(`emits only row data when 'allowReordering' is false, 'newDataPosition' is defined, and is 'positionKey' is defined`, async () => {
397
397
  const spy = jest.fn();
398
398
  const mockEvent = {
399
399
  e: 'event',
@@ -423,9 +423,9 @@ describe('DynamicListDataEntry', () => {
423
423
 
424
424
  const button = wrapper.find(Button);
425
425
 
426
- act(() => {
426
+ await act(async () => {
427
427
  // @ts-expect-error not full event args object
428
- button.prop('onButtonClicked')?.(mockEvent);
428
+ await button.prop('onButtonClicked')?.(mockEvent);
429
429
  });
430
430
 
431
431
  expect(spy).toHaveBeenCalledTimes(1);
@@ -434,7 +434,7 @@ describe('DynamicListDataEntry', () => {
434
434
  });
435
435
  });
436
436
 
437
- it(`emits only row data when 'allowReordering' is true, 'newDataPosition' is undefined, and is 'positionKey' is defined`, () => {
437
+ it(`emits only row data when 'allowReordering' is true, 'newDataPosition' is undefined, and is 'positionKey' is defined`, async () => {
438
438
  const spy = jest.fn();
439
439
  const mockEvent = {
440
440
  e: 'event',
@@ -464,9 +464,9 @@ describe('DynamicListDataEntry', () => {
464
464
 
465
465
  const button = wrapper.find(Button);
466
466
 
467
- act(() => {
467
+ await act(async () => {
468
468
  // @ts-expect-error not full event args object
469
- button.prop('onButtonClicked')?.(mockEvent);
469
+ await button.prop('onButtonClicked')?.(mockEvent);
470
470
  });
471
471
 
472
472
  expect(spy).toHaveBeenCalledTimes(1);
@@ -475,7 +475,7 @@ describe('DynamicListDataEntry', () => {
475
475
  });
476
476
  });
477
477
 
478
- it(`emits only row data when 'allowReordering' is true, 'newDataPosition' is defined, and is 'positionKey' is undefined`, () => {
478
+ it(`emits only row data when 'allowReordering' is true, 'newDataPosition' is defined, and is 'positionKey' is undefined`, async () => {
479
479
  const spy = jest.fn();
480
480
  const mockEvent = {
481
481
  e: 'event',
@@ -504,9 +504,9 @@ describe('DynamicListDataEntry', () => {
504
504
 
505
505
  const button = wrapper.find(Button);
506
506
 
507
- act(() => {
507
+ await act(async () => {
508
508
  // @ts-expect-error not full event args object
509
- button.prop('onButtonClicked')?.(mockEvent);
509
+ await button.prop('onButtonClicked')?.(mockEvent);
510
510
  });
511
511
 
512
512
  expect(spy).toHaveBeenCalledTimes(1);
@@ -515,7 +515,7 @@ describe('DynamicListDataEntry', () => {
515
515
  });
516
516
  });
517
517
 
518
- it(`transform property if 'onAddTransformer' is set as a column option`, () => {
518
+ it(`transform property if 'onAddTransformer' is set as a column option`, async () => {
519
519
  const spy = jest.fn();
520
520
  const mockNewValue = 'test-value1';
521
521
  const mockEvent = {
@@ -550,9 +550,9 @@ describe('DynamicListDataEntry', () => {
550
550
 
551
551
  const button = wrapper.find(Button);
552
552
 
553
- act(() => {
553
+ await act(async () => {
554
554
  // @ts-expect-error not full event args object
555
- button.prop('onButtonClicked')?.(mockEvent);
555
+ await button.prop('onButtonClicked')?.(mockEvent);
556
556
  });
557
557
 
558
558
  expect(spy).toHaveBeenCalledTimes(1);
@@ -1,10 +1,5 @@
1
1
  import clsx from 'clsx';
2
- import React, {
3
- PropsWithChildren,
4
- ReactElement,
5
- useEffect,
6
- useState,
7
- } from 'react';
2
+ import React, { PropsWithChildren, ReactElement, useState } from 'react';
8
3
  import { ValidationError } from 'yup';
9
4
  import { OptionalObjectSchema } from 'yup/lib/object';
10
5
  import { noop } from '../../../helpers/utils';
@@ -110,27 +105,29 @@ export const DynamicListDataEntry = <T extends Data>({
110
105
  const [isDirty, setIsDirty] = useState<boolean>(false);
111
106
  const [error, setError] = useState<Record<string, string>>({});
112
107
 
113
- useEffect(() => {
114
- if (rowValidationSchema && isDirty) {
115
- rowValidationSchema
116
- ?.validate(state, { abortEarly: false })
117
- .then(() => {
118
- setError({});
119
- })
120
- .catch((e: ValidationError) => {
121
- const newErrors: Record<string, string> = {};
122
- e.inner.forEach((validationError) => {
123
- if (validationError.path !== undefined) {
124
- const path = validationError.path;
125
- if (newErrors?.[path] === undefined) {
126
- newErrors[path] = validationError.message;
127
- }
128
- }
129
- });
130
- setError(newErrors);
131
- });
108
+ const validateSchema = async (data: T): Promise<boolean> => {
109
+ if (!rowValidationSchema) {
110
+ return true;
132
111
  }
133
- }, [isDirty, rowValidationSchema, state]);
112
+
113
+ try {
114
+ await rowValidationSchema.validate(data, { abortEarly: false });
115
+ setError({});
116
+
117
+ return true;
118
+ } catch (e) {
119
+ const newErrors: Record<string, string> = {};
120
+ (e as ValidationError).inner.forEach((validationError) => {
121
+ const path = validationError.path;
122
+ if (path !== undefined && newErrors?.[path] === undefined) {
123
+ newErrors[path] = validationError.message;
124
+ }
125
+ });
126
+ setError(newErrors);
127
+
128
+ return false;
129
+ }
130
+ };
134
131
 
135
132
  /**
136
133
  * Updates new data object with supplied key/value pair
@@ -138,10 +135,12 @@ export const DynamicListDataEntry = <T extends Data>({
138
135
  * @param value new value
139
136
  */
140
137
  const valueChangedHandler = (property: keyof T, value: unknown): void => {
138
+ const newState = { ...state, [property]: value };
139
+
141
140
  setIsDirty(true);
142
- setState((prevState) => {
143
- return { ...prevState, [property]: value };
144
- });
141
+ setState(newState);
142
+
143
+ validateSchema(newState);
145
144
  };
146
145
 
147
146
  /**
@@ -149,11 +148,11 @@ export const DynamicListDataEntry = <T extends Data>({
149
148
  * @param data T
150
149
  */
151
150
  const onAddItemHandler = async (): Promise<void> => {
152
- // TODO: Display a warning if no data was entered?
153
- if (rowValidationSchema && !(await rowValidationSchema?.isValid(state))) {
151
+ const isValid = await validateSchema(state);
152
+
153
+ if (!isValid) {
154
154
  setIsDirty(true);
155
- // eslint-disable-next-line no-console
156
- return console.warn('Data not valid');
155
+ return;
157
156
  }
158
157
 
159
158
  let transformedState: T = state;
@@ -20,11 +20,11 @@ describe('BooleanViewField', () => {
20
20
 
21
21
  it('displays default true, false label values', () => {
22
22
  const wrapper1 = shallow(<BooleanViewField value={true} />);
23
- const text1 = wrapper1.find('.text');
23
+ const text1 = wrapper1.find('[data-test-id="form-field-value"]');
24
24
  expect(text1.text()).toBe('True');
25
25
 
26
26
  const wrapper2 = shallow(<BooleanViewField value={false} />);
27
- const text2 = wrapper2.find('.text');
27
+ const text2 = wrapper2.find('[data-test-id="form-field-value"]');
28
28
  expect(text2.text()).toBe('False');
29
29
  });
30
30
 
@@ -42,7 +42,7 @@ describe('BooleanViewField', () => {
42
42
 
43
43
  const green = wrapper.find('.true');
44
44
  const red = wrapper.find('.false');
45
- const text = wrapper.find('.text');
45
+ const text = wrapper.find('[data-test-id="form-field-value"]');
46
46
 
47
47
  expect(green.exists()).toBe(true);
48
48
  expect(red.exists()).toBe(false);
@@ -63,7 +63,7 @@ describe('BooleanViewField', () => {
63
63
 
64
64
  const green = wrapper.find('.true');
65
65
  const red = wrapper.find('.false');
66
- const text = wrapper.find('.text');
66
+ const text = wrapper.find('[data-test-id="form-field-value"]');
67
67
 
68
68
  expect(green.exists()).toBe(false);
69
69
  expect(red.exists()).toBe(true);
@@ -32,11 +32,7 @@ export const BooleanViewField: React.FC<BooleanViewFieldProps> = ({
32
32
  >
33
33
  <div className={clsx(classes.value)}>
34
34
  <div className={clsx(value ? classes.true : classes.false)}></div>
35
- <div
36
- className={clsx(classes.text)}
37
- data-test-id="form-field-value"
38
- data-test-value={value}
39
- >
35
+ <div data-test-id="form-field-value" data-test-value={value}>
40
36
  {value ? trueLabel : falseLabel}
41
37
  </div>
42
38
  </div>
@@ -31,9 +31,9 @@ export default meta;
31
31
 
32
32
  export const Main: StoryObj<typeof Checkbox> = {
33
33
  args: {
34
- label: 'isActive',
34
+ label: 'Is Active?',
35
35
  tooltipContent: faker.lorem.paragraph(2),
36
- name: 'number',
36
+ name: 'isActive',
37
37
  },
38
38
  render: (args) =>
39
39
  React.createElement(() => {
@@ -65,9 +65,10 @@ export const Checkbox: React.FC<CheckboxProps> = ({
65
65
  className={clsx(classes.container, 'checkbox-container', className)}
66
66
  error={errorMsg}
67
67
  dataTestFieldType="Checkbox"
68
+ htmlFor={id ?? name}
68
69
  >
69
70
  <input
70
- id={id}
71
+ id={id ?? name}
71
72
  name={name}
72
73
  ref={setReferenceElement as React.LegacyRef<HTMLInputElement>}
73
74
  type="checkbox"
@@ -113,7 +113,8 @@
113
113
  color: var(--live-suggest-text-color, $live-suggest-text-color);
114
114
  }
115
115
 
116
- li:hover {
116
+ li:hover,
117
+ li.selected {
117
118
  background-color: var(
118
119
  --live-suggest-background-selected-color,
119
120
  $live-suggest-background-selected-color
@@ -295,6 +295,7 @@ export const CustomTags: React.FC<CustomTagsProps> = ({
295
295
  className={clsx(classes.container, 'custom-tags-container', className)}
296
296
  error={errorMessage}
297
297
  dataTestFieldType="CustomTags"
298
+ htmlFor={id ?? name}
298
299
  >
299
300
  <div className={clsx(classes.tagsWrapper)} style={styles}>
300
301
  <div className={clsx(classes.inputWrapper)}>
@@ -302,7 +303,7 @@ export const CustomTags: React.FC<CustomTagsProps> = ({
302
303
  className={clsx({
303
304
  [classes.hasError]: errorMessage !== undefined,
304
305
  })}
305
- id={id}
306
+ id={id ?? name}
306
307
  name={name}
307
308
  ref={textInput}
308
309
  value={val}
@@ -324,7 +325,9 @@ export const CustomTags: React.FC<CustomTagsProps> = ({
324
325
  onMouseDown={(event) =>
325
326
  onSuggestionClickedHandler(event, suggestion)
326
327
  }
327
- className={clsx({ [classes.selected]: cursorPos === idx })}
328
+ className={clsx({
329
+ [classes.selected]: cursorPos === idx,
330
+ })}
328
331
  >
329
332
  {suggestion}
330
333
  </li>
@@ -31,8 +31,8 @@ export default meta;
31
31
 
32
32
  export const Main: StoryObj<typeof DateTimeText> = {
33
33
  args: {
34
- label: 'Name',
35
- name: 'name',
34
+ label: 'Start',
35
+ name: 'start',
36
36
  tooltipContent: faker.lorem.sentences(4),
37
37
  },
38
38
  render: (args) =>
@@ -89,11 +89,12 @@ export const DateTimeText: React.FC<DateTimeTextProps> = ({
89
89
  className={clsx(classes.container, 'datetime-container', className)}
90
90
  error={errorMsg}
91
91
  dataTestFieldType="DateTime"
92
+ htmlFor={id ?? name}
92
93
  >
93
94
  <div className={clsx(classes.inputbutton)} ref={container}>
94
95
  <input
95
96
  className={clsx({ [classes.hasError]: errorMsg })}
96
- id={id}
97
+ id={id ?? name}
97
98
  name={name}
98
99
  type={'text'}
99
100
  value={display}
@@ -30,6 +30,7 @@ export const DynamicDataListControl = <T extends Data>({
30
30
  {...rest}
31
31
  className={clsx('dynamic-data-list-control-container', className)}
32
32
  dataTestFieldType="DynamicDataListControl"
33
+ htmlFor={rest.id ?? rest.name}
33
34
  >
34
35
  <DynamicDataList {...rest} />
35
36
  </FormElementContainer>
@@ -134,6 +134,7 @@ export const FileUploadControl: React.FC<FileUploadProps> = ({
134
134
  )}
135
135
  error={error}
136
136
  dataTestFieldType="FileUpload"
137
+ htmlFor={id ?? name}
137
138
  >
138
139
  <div className={clsx(classes.content)}>
139
140
  {dragging && !disabled ? (
@@ -157,12 +158,11 @@ export const FileUploadControl: React.FC<FileUploadProps> = ({
157
158
  [classes.fileuploadbutton]: true,
158
159
  [classes.disabled]: disabled,
159
160
  })}
160
- htmlFor={id}
161
161
  >
162
162
  <Icons icon={IconName.File} />
163
163
  </label>
164
164
  <input
165
- id={id}
165
+ id={id ?? name}
166
166
  type="file"
167
167
  accept={accept}
168
168
  disabled={disabled}
@@ -16,6 +16,9 @@ export type FormElementContainerProps = BaseFormElement & {
16
16
 
17
17
  /** Stick to the top option */
18
18
  stickyLabel?: boolean;
19
+
20
+ /** The value of the 'for' attribute of the label */
21
+ htmlFor?: string;
19
22
  };
20
23
 
21
24
  /**
@@ -35,6 +38,7 @@ export const FormElementContainer: React.FC<FormElementContainerProps> = ({
35
38
  dataTestFieldType,
36
39
  inlineMode = false,
37
40
  stickyLabel = true,
41
+ htmlFor,
38
42
  }) => {
39
43
  const heightStyles: CSSProperties = {};
40
44
 
@@ -63,7 +67,7 @@ export const FormElementContainer: React.FC<FormElementContainerProps> = ({
63
67
  )}
64
68
  style={heightStyles}
65
69
  >
66
- <label data-test-id="form-field-label">
70
+ <label data-test-id="form-field-label" htmlFor={htmlFor}>
67
71
  {label}
68
72
  {tooltipContent && (
69
73
  <InfoTooltip className={clsx(classes.tooltip)}>
@@ -27,6 +27,7 @@ export const ReadOnlyTextField = <T,>({
27
27
  transform,
28
28
  className = '',
29
29
  hideCopyButton = false,
30
+ id,
30
31
  ...rest
31
32
  }: PropsWithChildren<ReadOnlyTextFieldProps<T>>): JSX.Element => {
32
33
  const nonNullableValue = value === null ? '' : value;
@@ -42,12 +43,14 @@ export const ReadOnlyTextField = <T,>({
42
43
  {...rest}
43
44
  className={clsx('read-only-field-container', className)}
44
45
  dataTestFieldType="ReadOnlyText"
46
+ htmlFor={id}
45
47
  >
46
48
  <div className={clsx(classes.container)}>
47
49
  <input
48
50
  value={displayValue}
49
51
  onChange={noop}
50
52
  data-test-id="form-field-value"
53
+ id={id}
51
54
  />
52
55
  {!hideCopyButton && (
53
56
  <Button
@@ -86,13 +86,13 @@ export const Select: React.FC<SelectProps> = (props) => {
86
86
 
87
87
  return (
88
88
  <FormElementContainer
89
- id={id}
90
89
  label={label}
91
90
  tooltipContent={tooltipContent}
92
91
  inlineMode={inlineMode}
93
92
  className={clsx(classes.container, 'select-container', className)}
94
93
  error={error}
95
94
  dataTestFieldType="Select"
95
+ htmlFor={id ?? name}
96
96
  >
97
97
  <div
98
98
  ref={setAnchorEl}
@@ -101,6 +101,7 @@ export const Select: React.FC<SelectProps> = (props) => {
101
101
  >
102
102
  <input
103
103
  {...getInputProps()}
104
+ id={id ?? name}
104
105
  name={name}
105
106
  className={clsx({ [classes.hasError]: Boolean(error) })}
106
107
  autoFocus={autoFocus}