@axinom/mosaic-ui 0.66.0-rc.2 → 0.66.0-rc.20

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 (122) hide show
  1. package/dist/components/DynamicDataList/DynamicListHeader/DynamicListHeader.d.ts.map +1 -1
  2. package/dist/components/Explorer/BulkEdit/FormFieldsConfigConverter.d.ts.map +1 -1
  3. package/dist/components/FieldSelection/FieldSelection.d.ts.map +1 -1
  4. package/dist/components/Filters/Filter/Filter.d.ts.map +1 -1
  5. package/dist/components/Filters/Filters.model.d.ts +5 -0
  6. package/dist/components/Filters/Filters.model.d.ts.map +1 -1
  7. package/dist/components/Filters/SelectionTypes/DateTimeFilter/DateTimeFilter.d.ts +2 -0
  8. package/dist/components/Filters/SelectionTypes/DateTimeFilter/DateTimeFilter.d.ts.map +1 -1
  9. package/dist/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.d.ts +2 -0
  10. package/dist/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.d.ts.map +1 -1
  11. package/dist/components/Filters/SelectionTypes/MultiOptionFilter/MultiOptionFilter.d.ts +2 -0
  12. package/dist/components/Filters/SelectionTypes/MultiOptionFilter/MultiOptionFilter.d.ts.map +1 -1
  13. package/dist/components/Filters/SelectionTypes/NumericTextFilter/NumericTextFilter.d.ts +2 -0
  14. package/dist/components/Filters/SelectionTypes/NumericTextFilter/NumericTextFilter.d.ts.map +1 -1
  15. package/dist/components/Filters/SelectionTypes/OptionsFilter/OptionsFilter.d.ts +2 -0
  16. package/dist/components/Filters/SelectionTypes/OptionsFilter/OptionsFilter.d.ts.map +1 -1
  17. package/dist/components/Filters/SelectionTypes/SearcheableOptionsFilter/SearcheableOptionsFilter.d.ts +2 -0
  18. package/dist/components/Filters/SelectionTypes/SearcheableOptionsFilter/SearcheableOptionsFilter.d.ts.map +1 -1
  19. package/dist/components/FormElements/Radio/Radio.d.ts.map +1 -1
  20. package/dist/components/FormElements/ToggleButton/ToggleButton.d.ts.map +1 -1
  21. package/dist/components/Hub/Tile/Tile.d.ts.map +1 -1
  22. package/dist/components/Icons/Icons.d.ts +4 -9
  23. package/dist/components/Icons/Icons.d.ts.map +1 -1
  24. package/dist/components/LandingPageTiles/TileLarge/TileLarge.d.ts.map +1 -1
  25. package/dist/components/LandingPageTiles/TileSmall/TileSmall.d.ts.map +1 -1
  26. package/dist/components/List/ListCheckBox/ListCheckBox.d.ts.map +1 -1
  27. package/dist/components/List/ListHeader/ColumnLabel/ColumnLabel.d.ts.map +1 -1
  28. package/dist/components/List/ListHeader/ListHeader.d.ts.map +1 -1
  29. package/dist/components/List/ListRow/ListRow.d.ts.map +1 -1
  30. package/dist/components/List/ListRow/ListRowCell/ListRowCell.d.ts +15 -0
  31. package/dist/components/List/ListRow/ListRowCell/ListRowCell.d.ts.map +1 -0
  32. package/dist/components/List/ListRow/ListRowCell/renderData.d.ts +9 -0
  33. package/dist/components/List/ListRow/ListRowCell/renderData.d.ts.map +1 -0
  34. package/dist/components/List/ListRow/Renderers/TagsRenderer/TagsRenderer.d.ts.map +1 -1
  35. package/dist/components/Loaders/ImageLoader/ImageLoader.d.ts.map +1 -1
  36. package/dist/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts.map +1 -1
  37. package/dist/components/VisualElements/ImgElement.d.ts +50 -0
  38. package/dist/components/VisualElements/ImgElement.d.ts.map +1 -0
  39. package/dist/components/VisualElements/SvgElement.d.ts +14 -0
  40. package/dist/components/VisualElements/SvgElement.d.ts.map +1 -0
  41. package/dist/components/VisualElements/index.d.ts +3 -0
  42. package/dist/components/VisualElements/index.d.ts.map +1 -0
  43. package/dist/components/index.d.ts +1 -0
  44. package/dist/components/index.d.ts.map +1 -1
  45. package/dist/helpers/idleCallbackHelpers.d.ts +42 -0
  46. package/dist/helpers/idleCallbackHelpers.d.ts.map +1 -0
  47. package/dist/helpers/index.d.ts +1 -0
  48. package/dist/helpers/index.d.ts.map +1 -1
  49. package/dist/hooks/useResize/ResizeIndicator.d.ts +8 -0
  50. package/dist/hooks/useResize/ResizeIndicator.d.ts.map +1 -0
  51. package/dist/hooks/useResize/useResize.d.ts +5 -2
  52. package/dist/hooks/useResize/useResize.d.ts.map +1 -1
  53. package/dist/index.es.js +4 -4
  54. package/dist/index.es.js.map +1 -1
  55. package/dist/index.js +4 -4
  56. package/dist/index.js.map +1 -1
  57. package/package.json +2 -2
  58. package/src/components/DynamicDataList/DynamicListHeader/DynamicListHeader.spec.tsx +2 -0
  59. package/src/components/DynamicDataList/DynamicListHeader/DynamicListHeader.tsx +62 -50
  60. package/src/components/Explorer/BulkEdit/FormFieldsConfigConverter.tsx +5 -21
  61. package/src/components/Explorer/Explorer.stories.tsx +17 -0
  62. package/src/components/FieldSelection/FieldSelection.scss +4 -0
  63. package/src/components/FieldSelection/FieldSelection.tsx +1 -0
  64. package/src/components/Filters/Filter/Filter.scss +34 -15
  65. package/src/components/Filters/Filter/Filter.spec.tsx +1 -1
  66. package/src/components/Filters/Filter/Filter.tsx +46 -34
  67. package/src/components/Filters/Filters.model.ts +6 -0
  68. package/src/components/Filters/SelectionTypes/DateTimeFilter/DateTimeFilter.tsx +6 -1
  69. package/src/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.tsx +4 -0
  70. package/src/components/Filters/SelectionTypes/MultiOptionFilter/MultiOptionFilter.tsx +9 -1
  71. package/src/components/Filters/SelectionTypes/NumericTextFilter/NumericTextFilter.tsx +5 -0
  72. package/src/components/Filters/SelectionTypes/OptionsFilter/OptionsFilter.scss +6 -10
  73. package/src/components/Filters/SelectionTypes/OptionsFilter/OptionsFilter.tsx +8 -0
  74. package/src/components/Filters/SelectionTypes/SearcheableOptionsFilter/SearcheableOptionsFilter.tsx +6 -1
  75. package/src/components/FormElements/Radio/Radio.tsx +3 -2
  76. package/src/components/FormElements/Select/Select.scss +11 -6
  77. package/src/components/FormElements/ToggleButton/ToggleButton.tsx +32 -27
  78. package/src/components/Hub/Hub.stories.tsx +3 -2
  79. package/src/components/Hub/Tile/Tile.spec.tsx +7 -2
  80. package/src/components/Hub/Tile/Tile.tsx +2 -1
  81. package/src/components/Icons/Icons.scss +1 -0
  82. package/src/components/Icons/Icons.spec.tsx +90 -41
  83. package/src/components/Icons/Icons.tsx +357 -765
  84. package/src/components/InfoTooltip/InfoTooltip.scss +1 -1
  85. package/src/components/InlineMenu/InlineMenu.scss +1 -1
  86. package/src/components/LandingPageTiles/LandingPageTiles.stories.tsx +3 -2
  87. package/src/components/LandingPageTiles/TileLarge/TileLarge.spec.tsx +5 -1
  88. package/src/components/LandingPageTiles/TileLarge/TileLarge.tsx +2 -1
  89. package/src/components/LandingPageTiles/TileSmall/TileSmall.spec.tsx +7 -2
  90. package/src/components/LandingPageTiles/TileSmall/TileSmall.tsx +2 -1
  91. package/src/components/List/ListCheckBox/ListCheckBox.tsx +1 -0
  92. package/src/components/List/ListHeader/ColumnLabel/ColumnLabel.spec.tsx +6 -6
  93. package/src/components/List/ListHeader/ColumnLabel/ColumnLabel.tsx +10 -13
  94. package/src/components/List/ListHeader/ListHeader.scss +0 -1
  95. package/src/components/List/ListHeader/ListHeader.spec.tsx +2 -0
  96. package/src/components/List/ListHeader/ListHeader.tsx +57 -51
  97. package/src/components/List/ListRow/ListRow.scss +0 -27
  98. package/src/components/List/ListRow/ListRow.spec.tsx +10 -8
  99. package/src/components/List/ListRow/ListRow.tsx +20 -152
  100. package/src/components/List/ListRow/ListRowCell/ListRowCell.scss +26 -0
  101. package/src/components/List/ListRow/ListRowCell/ListRowCell.spec.tsx +491 -0
  102. package/src/components/List/ListRow/ListRowCell/ListRowCell.tsx +57 -0
  103. package/src/components/List/ListRow/ListRowCell/renderData.tsx +124 -0
  104. package/src/components/List/ListRow/Renderers/TagsRenderer/TagsRenderer.scss +2 -1
  105. package/src/components/List/ListRow/Renderers/TagsRenderer/TagsRenderer.spec.tsx +187 -104
  106. package/src/components/List/ListRow/Renderers/TagsRenderer/TagsRenderer.tsx +134 -80
  107. package/src/components/Loaders/ImageLoader/ImageLoader.spec.tsx +13 -14
  108. package/src/components/Loaders/ImageLoader/ImageLoader.tsx +5 -3
  109. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.tsx +13 -2
  110. package/src/components/Utils/Postgraphile/CreateConnectionRenderer.spec.ts +22 -75
  111. package/src/components/VisualElements/ImgElement.spec.tsx +92 -0
  112. package/src/components/VisualElements/ImgElement.tsx +72 -0
  113. package/src/components/VisualElements/SvgElement.spec.tsx +160 -0
  114. package/src/components/VisualElements/SvgElement.tsx +40 -0
  115. package/src/components/VisualElements/index.ts +7 -0
  116. package/src/components/index.ts +1 -0
  117. package/src/helpers/idleCallbackHelpers.ts +66 -0
  118. package/src/helpers/index.ts +5 -0
  119. package/src/hooks/useResize/ResizeIndicator.scss +7 -0
  120. package/src/hooks/useResize/ResizeIndicator.tsx +39 -0
  121. package/src/hooks/useResize/{useResize.ts → useResize.tsx} +38 -6
  122. package/src/styles/variables.scss +7 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axinom/mosaic-ui",
3
- "version": "0.66.0-rc.2",
3
+ "version": "0.66.0-rc.20",
4
4
  "description": "UI components for building Axinom Mosaic applications",
5
5
  "author": "Axinom",
6
6
  "license": "PROPRIETARY",
@@ -112,5 +112,5 @@
112
112
  "publishConfig": {
113
113
  "access": "public"
114
114
  },
115
- "gitHead": "8367444991c09b4d3c680988cbb1d8feabefbb62"
115
+ "gitHead": "b3810ba1b0cda52a789d68955d474086d8745eda"
116
116
  }
@@ -238,6 +238,8 @@ describe('DynamicListHeader', () => {
238
238
  } as any),
239
239
  );
240
240
 
241
+ window.dispatchEvent(new Event('mouseup', {} as any));
242
+
241
243
  expect(spy).toHaveBeenCalledTimes(1);
242
244
  });
243
245
  });
@@ -70,60 +70,72 @@ export const DynamicListHeader = <T extends Data>({
70
70
  customStyles.top = 0;
71
71
  }
72
72
 
73
- const { cols, mouseDown } = useResize(columns, onColumnSizesChanged);
73
+ const { cols, mouseDown, ResizeIndicator } = useResize(
74
+ columns,
75
+ onColumnSizesChanged,
76
+ );
74
77
 
75
78
  return (
76
- <div
77
- className={clsx(
78
- classes.container,
79
- 'dynamic-list-header-container',
80
- className,
81
- )}
82
- style={customStyles}
83
- data-test-id="dynamic-list-header"
84
- >
85
- {showPositionColumn && (
86
- <div
87
- className={clsx(classes.column)}
88
- data-test-id="dynamic-list-header-cell"
89
- >
90
- <p className={clsx(classes.position)}>
91
- {positionLabel ?? (allowDragging ? 'Position' : 'Pos')}
92
- </p>
79
+ <>
80
+ {ResizeIndicator}
81
+ <div
82
+ className={clsx(
83
+ classes.container,
84
+ 'dynamic-list-header-container',
85
+ className,
86
+ )}
87
+ style={customStyles}
88
+ data-test-id="dynamic-list-header"
89
+ >
90
+ {showPositionColumn && (
93
91
  <div
94
- className={clsx(classes.resizeHandle, classes.resizeHandleDisabled)}
95
- />
96
- </div>
97
- )}
98
- {columns.map((column, i) => (
99
- <div
100
- key={column.key ?? (column.propertyName as string)}
101
- className={clsx(classes.column)}
102
- ref={cols[i].ref}
103
- data-test-id={`dynamic-list-header-property:${
104
- column.propertyName as string
105
- }`}
106
- >
107
- <p>{column.label}</p>
92
+ className={clsx(classes.column)}
93
+ data-test-id="dynamic-list-header-cell"
94
+ >
95
+ <p className={clsx(classes.position)}>
96
+ {positionLabel ?? (allowDragging ? 'Position' : 'Pos')}
97
+ </p>
98
+ <div
99
+ className={clsx(
100
+ classes.resizeHandle,
101
+ classes.resizeHandleDisabled,
102
+ )}
103
+ />
104
+ </div>
105
+ )}
106
+ {columns.map((column, i) => (
107
+ <div
108
+ key={column.key ?? (column.propertyName as string)}
109
+ className={clsx(classes.column)}
110
+ ref={cols[i].ref}
111
+ data-test-id={`dynamic-list-header-property:${
112
+ column.propertyName as string
113
+ }`}
114
+ >
115
+ <p>{column.label}</p>
116
+ <div
117
+ onMouseDown={
118
+ !column.disableResizing
119
+ ? (e) => {
120
+ e.preventDefault();
121
+ mouseDown(i);
122
+ }
123
+ : undefined
124
+ }
125
+ onDoubleClick={onResetColumnSizes}
126
+ className={clsx(classes.resizeHandle, {
127
+ [classes.resizeHandleDisabled]: column.disableResizing ?? false,
128
+ })}
129
+ />
130
+ </div>
131
+ ))}
132
+ {showActionColumn && (
108
133
  <div
109
- onMouseDown={
110
- !column.disableResizing
111
- ? (e) => {
112
- e.preventDefault();
113
- mouseDown(i);
114
- }
115
- : undefined
116
- }
117
- onDoubleClick={onResetColumnSizes}
118
- className={clsx(classes.resizeHandle, {
119
- [classes.resizeHandleDisabled]: column.disableResizing ?? false,
120
- })}
134
+ className={clsx(classes.column)}
135
+ ref={cols[cols.length - 1].ref}
121
136
  />
122
- </div>
123
- ))}
124
- {showActionColumn && (
125
- <div className={clsx(classes.column)} ref={cols[cols.length - 1].ref} />
126
- )}
127
- </div>
137
+ )}
138
+ </div>
139
+ </>
128
140
  );
129
141
  };
@@ -23,14 +23,7 @@ export const BulkEditFormFieldsConfigConverter = (
23
23
  const keys = Object.keys(config);
24
24
 
25
25
  const FormFields: React.FC = () => {
26
- const {
27
- setFieldValue,
28
- setFieldTouched,
29
- setErrors,
30
- errors,
31
- validateForm,
32
- values,
33
- } = useFormikContext<Data>();
26
+ const { setFieldValue, setFieldTouched, values } = useFormikContext<Data>();
34
27
 
35
28
  // Effect to clear empty fields
36
29
  // This will set fields with empty strings or empty arrays to undefined
@@ -46,19 +39,10 @@ export const BulkEditFormFieldsConfigConverter = (
46
39
  });
47
40
  }, [setFieldValue, values]);
48
41
 
49
- const onFieldRemoved = (field: string): void => {
50
- setFieldValue(field, undefined, false); // Clear the field value when removed
51
- setFieldTouched(field, false, false); // Mark the field as not touched
52
-
53
- if (errors[field]) {
54
- // If there was an error for this field, clear it
55
- const newErrors = { ...errors };
56
- delete newErrors[field];
57
-
58
- setErrors(newErrors);
59
-
60
- validateForm();
61
- }
42
+ const onFieldRemoved = async (field: string): Promise<void> => {
43
+ // TODO: Update Formik to get the latest types for setFieldValue
44
+ await setFieldValue(field, undefined, false); // setFieldTouched will validate the form
45
+ await setFieldTouched(field, false);
62
46
  };
63
47
 
64
48
  const onFieldAdded = (field: string): void => {
@@ -21,6 +21,7 @@ import {
21
21
  } from '../FormStation';
22
22
  import { IconName } from '../Icons';
23
23
  import { ListSelectMode } from '../List';
24
+ import { createConnectionRenderer } from '../Utils';
24
25
  import { generateBulkEditMutation } from './BulkEdit/GenerateMutation';
25
26
  import { Explorer } from './Explorer';
26
27
  import { QuickEditContext } from './QuickEdit/QuickEditContext';
@@ -34,6 +35,7 @@ interface ExplorerStoryData {
34
35
  title: string;
35
36
  date?: Date;
36
37
  desc: string;
38
+ tags?: { nodes: { name: string }[] };
37
39
  }
38
40
 
39
41
  type ExplorerStoryType = typeof Explorer<ExplorerStoryData>;
@@ -120,6 +122,14 @@ const generateData = (
120
122
  title: `${usePrefix ? `Index ${index}: ` : ''}${faker.random.words(
121
123
  faker.datatype.number({ min: 1, max: 3 }),
122
124
  )}`,
125
+ tags: {
126
+ nodes: Array.from(
127
+ { length: faker.datatype.number({ min: 0, max: 10 }) },
128
+ () => ({
129
+ name: faker.random.word(),
130
+ }),
131
+ ),
132
+ },
123
133
  };
124
134
  });
125
135
 
@@ -140,6 +150,13 @@ export const Default: StoryObj<ExplorerStoryType> = {
140
150
  propertyName: 'title',
141
151
  label: 'Title',
142
152
  },
153
+ {
154
+ propertyName: 'tags',
155
+ label: 'Tags',
156
+ render: createConnectionRenderer<{ nodes: { name: string }[] }>(
157
+ (tag) => tag.name,
158
+ ),
159
+ },
143
160
  {
144
161
  propertyName: 'date',
145
162
  label: 'Date',
@@ -7,6 +7,10 @@
7
7
  align-items: center;
8
8
  }
9
9
 
10
+ .item {
11
+ z-index: 0;
12
+ }
13
+
10
14
  .selectionHeader {
11
15
  display: grid;
12
16
  grid-template-columns: 1fr;
@@ -84,6 +84,7 @@ export const FieldSelection: React.FC<FieldSelectionProps> = ({
84
84
  {selectedFields.map((field) => (
85
85
  <AccordionItem
86
86
  key={field.value}
87
+ className={classes.item}
87
88
  header={
88
89
  <FieldSelectionItemHeader
89
90
  label={field.label as string}
@@ -15,24 +15,31 @@ $input-inactive: 1px solid var(--input-border-color, $input-border-color);
15
15
  border: $input-inactive;
16
16
  }
17
17
 
18
+ &.hasValue {
19
+ background: color-mix(
20
+ in srgb,
21
+ var(--filter-background-color, $filter-background-color) 50%,
22
+ transparent
23
+ );
24
+ }
25
+
18
26
  .title {
19
27
  display: grid;
20
28
  grid-template-columns: auto auto;
21
- grid-template-rows: 48px;
29
+ grid-template-rows: 38px;
22
30
  place-content: space-between;
23
31
  place-items: center;
24
32
  color: var(--filter-title-color, $filter-title-color);
25
33
  font-size: var(--filter-font-size, $filter-font-size);
26
- border: 1px solid white;
34
+ border: 1px solid transparent;
27
35
 
28
- transition: border 0.15s ease-in-out 0s;
29
- transition: background-color 100ms linear;
36
+ transition: border 0.15s ease-in-out 0s, background-color 100ms linear;
30
37
 
31
38
  &:hover {
32
39
  border: $border;
33
40
  }
34
41
 
35
- &.expanded {
42
+ &.active {
36
43
  background-color: var(
37
44
  --filter-background-selected-color,
38
45
  $filter-background-selected-color
@@ -46,13 +53,20 @@ $input-inactive: 1px solid var(--input-border-color, $input-border-color);
46
53
  var(--input-invalid-border-color, $input-invalid-border-color);
47
54
  }
48
55
 
49
- &.active {
56
+ &.hasValue {
50
57
  font-weight: bold;
51
58
  }
52
59
 
53
60
  .button {
61
+ height: 40px;
62
+ width: 40px;
63
+
64
+ &.rotated svg {
65
+ transform: rotate(90deg);
66
+ }
67
+
54
68
  svg {
55
- height: 50%;
69
+ transition: transform 200ms cubic-bezier(0.4, 0, 0.2, 1);
56
70
  }
57
71
 
58
72
  svg * {
@@ -63,7 +77,7 @@ $input-inactive: 1px solid var(--input-border-color, $input-border-color);
63
77
  }
64
78
  }
65
79
 
66
- span {
80
+ label {
67
81
  padding-left: 15px;
68
82
  }
69
83
  }
@@ -78,9 +92,8 @@ $input-inactive: 1px solid var(--input-border-color, $input-border-color);
78
92
  place-items: center;
79
93
 
80
94
  .button {
81
- svg {
82
- height: 50%;
83
- }
95
+ height: 40px;
96
+ width: 40px;
84
97
 
85
98
  svg * {
86
99
  stroke: var(
@@ -92,17 +105,23 @@ $input-inactive: 1px solid var(--input-border-color, $input-border-color);
92
105
 
93
106
  span {
94
107
  padding-left: 30px;
95
- padding-bottom: 10px;
96
108
  }
97
109
  }
98
110
  }
99
111
 
100
112
  .content {
101
- transition: max-height 100ms linear;
102
- overflow: hidden;
103
- max-height: fit-content !important;
113
+ display: grid;
114
+ grid-template-rows: 0fr;
115
+ transition: grid-template-rows 200ms cubic-bezier(0.4, 0, 0.2, 1);
116
+
117
+ &.expanded {
118
+ grid-template-rows: 1fr;
119
+ }
104
120
 
105
121
  .active {
122
+ display: grid;
123
+ overflow: hidden;
124
+
106
125
  border-right: $border;
107
126
  border-bottom: $border;
108
127
  border-left: $border;
@@ -95,7 +95,7 @@ describe('Filter', () => {
95
95
 
96
96
  const filterTitle = wrapper.find('.title');
97
97
 
98
- expect(filterTitle.hasClass('expanded')).toBe(expected);
98
+ expect(filterTitle.hasClass('active')).toBe(expected);
99
99
  });
100
100
 
101
101
  it('passes active state to custom filter', () => {
@@ -1,11 +1,5 @@
1
1
  import clsx from 'clsx';
2
- import React, {
3
- PropsWithChildren,
4
- ReactNode,
5
- useEffect,
6
- useRef,
7
- useState,
8
- } from 'react';
2
+ import React, { PropsWithChildren, ReactNode, useState } from 'react';
9
3
  import { assertNever } from '../../../helpers/utils';
10
4
  import { Data } from '../../../types/data';
11
5
  import { Button, ButtonContext } from '../../Buttons';
@@ -58,18 +52,16 @@ export const Filter = <T extends Data>({
58
52
  }: PropsWithChildren<FilterProps<T>>): JSX.Element => {
59
53
  const [isExpanded, setIsExpanded] = useState<boolean>(false);
60
54
  const [hasError, setHasError] = useState<boolean>(false);
61
- const [contentHeight, setContentHeight] = useState<number>(0);
62
55
  const [stringValue, setStringValue] = useState<string | undefined>();
63
56
 
64
- const contentRef = useRef<HTMLDivElement>(null);
65
- const valueRef = useRef<HTMLDivElement>(null);
66
-
67
- useEffect(() => {
68
- setContentHeight(
69
- (contentRef.current?.scrollHeight ?? 0) +
70
- (valueRef.current?.scrollHeight ?? 0),
71
- );
72
- }, [isExpanded, hasError, value]);
57
+ const inputId = `${String(options.property)}-filter-input`;
58
+ const labelId = `${String(options.property)}-filter-label`;
59
+ const inputBasedFilter = [
60
+ FilterTypes.FreeText,
61
+ FilterTypes.Numeric,
62
+ FilterTypes.Date,
63
+ FilterTypes.DateTime,
64
+ ];
73
65
 
74
66
  const onFilterValueChange = (
75
67
  prop: keyof T,
@@ -135,6 +127,7 @@ export const Filter = <T extends Data>({
135
127
  onError={onError}
136
128
  onValidate={onValidate}
137
129
  selectOnFocus={selectOnFocus}
130
+ inputId={inputId}
138
131
  />
139
132
  );
140
133
 
@@ -147,6 +140,7 @@ export const Filter = <T extends Data>({
147
140
  }
148
141
  onError={onError}
149
142
  onValidate={onValidate}
143
+ inputId={inputId}
150
144
  />
151
145
  );
152
146
 
@@ -158,6 +152,7 @@ export const Filter = <T extends Data>({
158
152
  onSelect={(value: FilterValue) =>
159
153
  onFilterValueChange(options.property, value)
160
154
  }
155
+ labelId={labelId}
161
156
  />
162
157
  );
163
158
 
@@ -169,6 +164,7 @@ export const Filter = <T extends Data>({
169
164
  onSelect={(value: FilterValue) =>
170
165
  onFilterValueChange(options.property, value?.toString())
171
166
  }
167
+ labelId={labelId}
172
168
  />
173
169
  );
174
170
  case FilterTypes.SearcheableOptions:
@@ -181,6 +177,8 @@ export const Filter = <T extends Data>({
181
177
  onFilterValueChange(options.property, value, stringValue)
182
178
  }
183
179
  maxItems={options.maxItems}
180
+ inputId={inputId}
181
+ labelId={labelId}
184
182
  />
185
183
  );
186
184
 
@@ -194,6 +192,7 @@ export const Filter = <T extends Data>({
194
192
  modifyTime={false}
195
193
  onError={onError}
196
194
  onValidate={onValidate}
195
+ inputId={inputId}
197
196
  />
198
197
  );
199
198
 
@@ -207,6 +206,7 @@ export const Filter = <T extends Data>({
207
206
  modifyTime={true}
208
207
  onError={onError}
209
208
  onValidate={onValidate}
209
+ inputId={inputId}
210
210
  />
211
211
  );
212
212
 
@@ -220,6 +220,7 @@ export const Filter = <T extends Data>({
220
220
  }}
221
221
  onError={onError}
222
222
  onValidate={onValidate}
223
+ labelId={labelId}
223
224
  />
224
225
  );
225
226
 
@@ -229,9 +230,16 @@ export const Filter = <T extends Data>({
229
230
  }
230
231
  };
231
232
 
233
+ const hasValue = value !== undefined && value !== '';
234
+
232
235
  return (
233
236
  <div
234
- className={clsx(classes.container, 'filter-container', className)}
237
+ className={clsx(
238
+ classes.container,
239
+ { [classes.hasValue]: hasValue },
240
+ 'filter-container',
241
+ className,
242
+ )}
235
243
  data-test-id={`filter:${options.property as string}`}
236
244
  data-test-type={FilterTypes[options.type]}
237
245
  onClick={() => onFilterClicked()}
@@ -241,31 +249,36 @@ export const Filter = <T extends Data>({
241
249
  setIsExpanded(!isExpanded);
242
250
  setHasError(false);
243
251
  }}
244
- className={clsx(
245
- classes.title,
246
- { [classes.expanded]: isExpanded && isActive },
247
- hasError && classes.hasError,
248
- !!value && classes.active,
249
- )}
252
+ className={clsx(classes.title, {
253
+ [classes.hasValue]: hasValue,
254
+ [classes.active]: isActive && isExpanded,
255
+ [classes.hasError]: hasError,
256
+ })}
250
257
  data-test-id="filter-button-toggle"
251
258
  >
252
- <span data-test-id="filter-label">{options.label}</span>
259
+ <label
260
+ id={labelId}
261
+ {...(inputBasedFilter.includes(options.type)
262
+ ? {}
263
+ : { htmlFor: inputId })}
264
+ data-test-id="filter-label"
265
+ >
266
+ {options.label}
267
+ </label>
253
268
  <Button
254
- icon={IconName.ChevronDown}
255
- className={clsx(classes.button)}
269
+ icon={IconName.ChevronRight}
270
+ className={clsx(classes.button, {
271
+ [classes.rotated]: isExpanded || hasValue,
272
+ })}
256
273
  buttonContext={ButtonContext.None}
257
274
  />
258
275
  </div>
259
276
  <div
260
- className={clsx(classes.content)}
261
- style={{
262
- maxHeight: contentHeight,
263
- }}
277
+ className={clsx(classes.content, { [classes.expanded]: isExpanded })}
264
278
  >
265
- {value !== undefined && !isExpanded && (
279
+ {hasValue && !isExpanded && (
266
280
  <div
267
281
  className={clsx(classes.selectedValue)}
268
- ref={valueRef}
269
282
  onClick={() => {
270
283
  setIsExpanded(!isExpanded);
271
284
  }}
@@ -285,7 +298,6 @@ export const Filter = <T extends Data>({
285
298
  )}
286
299
  {isExpanded && (
287
300
  <div
288
- ref={contentRef}
289
301
  data-test-id="filter-content"
290
302
  className={clsx({ [classes.active]: isActive })}
291
303
  >
@@ -78,6 +78,12 @@ export interface CustomFilterProps {
78
78
  /** Wether or not the filter is active (default: false) */
79
79
  active?: boolean;
80
80
 
81
+ /**
82
+ * Optional id for the label element.
83
+ * Assign to the label's `id` and reference from the input or group via `aria-labelledby`.
84
+ */
85
+ labelId?: string;
86
+
81
87
  /** Callback triggered when a new filter value is selected */
82
88
  onSelect: (
83
89
  value: FilterValue,
@@ -31,6 +31,9 @@ export interface DateTimeFilterProps {
31
31
 
32
32
  /** CSS Class name for additional styles */
33
33
  className?: string;
34
+
35
+ /** Optional id for the input field */
36
+ inputId?: string;
34
37
  }
35
38
 
36
39
  export const DateTimeFilter: React.FC<DateTimeFilterProps> = ({
@@ -40,6 +43,7 @@ export const DateTimeFilter: React.FC<DateTimeFilterProps> = ({
40
43
  onError = noop,
41
44
  onValidate: customValidate,
42
45
  className = '',
46
+ inputId,
43
47
  }) => {
44
48
  const container = useRef<HTMLDivElement>(null);
45
49
  const [showPicker, setShowPicker] = useState(false);
@@ -111,6 +115,7 @@ export const DateTimeFilter: React.FC<DateTimeFilterProps> = ({
111
115
  ref={container}
112
116
  >
113
117
  <input
118
+ id={inputId}
114
119
  autoFocus
115
120
  className={clsx(classes.inputValue)}
116
121
  onKeyDown={handleKeyDown}
@@ -163,7 +168,7 @@ export const DateTimeFilter: React.FC<DateTimeFilterProps> = ({
163
168
  onClick={() => {
164
169
  setShowPicker(false);
165
170
 
166
- if (modifyTime) {
171
+ if (modifyTime && value?.length > 0) {
167
172
  const parsedValue = DateTime.fromFormat(value, 'f');
168
173
 
169
174
  if (parsedValue.isValid) {
@@ -19,6 +19,8 @@ export interface FreeTextFilterProps {
19
19
  className?: string;
20
20
  /** Select text on focus if true (default: true) */
21
21
  selectOnFocus?: boolean;
22
+ /** Optional id for the input field */
23
+ inputId?: string;
22
24
  }
23
25
 
24
26
  export const FreeTextFilter: React.FC<FreeTextFilterProps> = ({
@@ -28,6 +30,7 @@ export const FreeTextFilter: React.FC<FreeTextFilterProps> = ({
28
30
  onValidate: customValidate,
29
31
  className = '',
30
32
  selectOnFocus = true,
33
+ inputId,
31
34
  }) => {
32
35
  const [errorMsg, setErrorMsg] = useState<string>();
33
36
  const ENTER_KEY = 'Enter';
@@ -68,6 +71,7 @@ export const FreeTextFilter: React.FC<FreeTextFilterProps> = ({
68
71
  )}
69
72
  >
70
73
  <input
74
+ id={inputId}
71
75
  ref={inputRef}
72
76
  autoFocus
73
77
  className={clsx(classes.inputValue, errorMsg && classes.hasError)}
@@ -17,19 +17,27 @@ export interface MultiOptionFilterProps {
17
17
 
18
18
  /** CSS Class name for additional styles */
19
19
  className?: string;
20
+
21
+ /** ID for the label */
22
+ labelId?: string;
20
23
  }
21
24
 
22
25
  export const MultiOptionsFilter: React.FC<MultiOptionFilterProps> = ({
23
26
  value,
24
27
  options,
25
28
  onSelect,
29
+ labelId,
26
30
  }) => {
27
31
  const [selectedOptionList, setSelectedOptionList] = useState<string[]>(
28
32
  value ? value.split(',') : [],
29
33
  );
30
34
 
31
35
  return (
32
- <div className={clsx(classes.multiFilterContainer)}>
36
+ <div
37
+ className={classes.multiFilterContainer}
38
+ role="group"
39
+ aria-labelledby={labelId}
40
+ >
33
41
  {options?.map((option: Option) => (
34
42
  <Checkbox
35
43
  className={clsx(
@@ -18,6 +18,9 @@ export interface NumericTextFilterProps {
18
18
 
19
19
  /** CSS Class name for additional styles */
20
20
  className?: string;
21
+
22
+ /** Optional id for the input field */
23
+ inputId?: string;
21
24
  }
22
25
 
23
26
  export const NumericTextFilter: React.FC<NumericTextFilterProps> = ({
@@ -26,6 +29,7 @@ export const NumericTextFilter: React.FC<NumericTextFilterProps> = ({
26
29
  onError = noop,
27
30
  onValidate: customValidate,
28
31
  className = '',
32
+ inputId,
29
33
  }) => {
30
34
  const [errorMsg, setErrorMsg] = useState<string>();
31
35
  const ENTER_KEY = 'Enter';
@@ -64,6 +68,7 @@ export const NumericTextFilter: React.FC<NumericTextFilterProps> = ({
64
68
  )}
65
69
  >
66
70
  <input
71
+ id={inputId}
67
72
  autoFocus
68
73
  value={valueLocal}
69
74
  className={clsx(classes.inputValue, errorMsg && classes.hasError)}