@axinom/mosaic-ui 0.49.0-rc.1 → 0.49.0-rc.11

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 (74) hide show
  1. package/dist/components/DynamicDataList/DynamicDataList.d.ts +3 -1
  2. package/dist/components/DynamicDataList/DynamicDataList.d.ts.map +1 -1
  3. package/dist/components/DynamicDataList/DynamicListRow/DynamicListRow.d.ts +3 -1
  4. package/dist/components/DynamicDataList/DynamicListRow/DynamicListRow.d.ts.map +1 -1
  5. package/dist/components/Explorer/Explorer.d.ts +2 -0
  6. package/dist/components/Explorer/Explorer.d.ts.map +1 -1
  7. package/dist/components/Filters/Filter/Filter.d.ts +2 -1
  8. package/dist/components/Filters/Filter/Filter.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/FormStation/FormStationHeader/FormStationHeader.d.ts.map +1 -1
  12. package/dist/components/List/List.d.ts +2 -0
  13. package/dist/components/List/List.d.ts.map +1 -1
  14. package/dist/components/List/ListRow/ListRow.d.ts +4 -2
  15. package/dist/components/List/ListRow/ListRow.d.ts.map +1 -1
  16. package/dist/components/List/ListRow/ListRowLoader.d.ts +1 -1
  17. package/dist/components/List/ListRow/ListRowLoader.d.ts.map +1 -1
  18. package/dist/components/PageHeader/PageHeader.d.ts +9 -2
  19. package/dist/components/PageHeader/PageHeader.d.ts.map +1 -1
  20. package/dist/components/PageHeader/PageHeader.model.d.ts +11 -12
  21. package/dist/components/PageHeader/PageHeader.model.d.ts.map +1 -1
  22. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts +35 -0
  23. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +1 -0
  24. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts +7 -0
  25. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts.map +1 -0
  26. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts +3 -0
  27. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts.map +1 -0
  28. package/dist/components/PageHeader/index.d.ts +1 -1
  29. package/dist/components/PageHeader/index.d.ts.map +1 -1
  30. package/dist/index.es.js +5 -4
  31. package/dist/index.es.js.map +1 -1
  32. package/dist/index.js +5 -4
  33. package/dist/index.js.map +1 -1
  34. package/dist/{components/DynamicDataList/helpers/generateId.d.ts → utils/GenerateId.d.ts} +1 -1
  35. package/dist/utils/GenerateId.d.ts.map +1 -0
  36. package/package.json +3 -3
  37. package/src/components/DynamicDataList/DynamicDataList.spec.tsx +2 -1
  38. package/src/components/DynamicDataList/DynamicDataList.stories.tsx +2 -1
  39. package/src/components/DynamicDataList/DynamicDataList.tsx +5 -1
  40. package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.scss +11 -5
  41. package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.spec.tsx +37 -0
  42. package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.tsx +23 -14
  43. package/src/components/Explorer/Explorer.spec.tsx +26 -16
  44. package/src/components/Explorer/Explorer.stories.tsx +1 -0
  45. package/src/components/Explorer/Explorer.tsx +52 -28
  46. package/src/components/Explorer/NavigationExplorer/NavigationExplorer.spec.tsx +2 -2
  47. package/src/components/Explorer/SelectionExplorer/SelectionExplorer.spec.tsx +8 -32
  48. package/src/components/Filters/Filter/Filter.tsx +3 -0
  49. package/src/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.spec.tsx +16 -1
  50. package/src/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.tsx +13 -1
  51. package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +34 -30
  52. package/src/components/List/List.stories.tsx +2 -1
  53. package/src/components/List/List.tsx +5 -1
  54. package/src/components/List/ListRow/ListRow.scss +11 -4
  55. package/src/components/List/ListRow/ListRow.spec.tsx +35 -0
  56. package/src/components/List/ListRow/ListRow.tsx +44 -17
  57. package/src/components/List/ListRow/ListRowLoader.tsx +2 -2
  58. package/src/components/PageHeader/PageHeader.model.ts +10 -12
  59. package/src/components/PageHeader/PageHeader.scss +7 -3
  60. package/src/components/PageHeader/PageHeader.spec.tsx +28 -86
  61. package/src/components/PageHeader/PageHeader.stories.tsx +32 -7
  62. package/src/components/PageHeader/PageHeader.tsx +50 -77
  63. package/src/components/PageHeader/{PageHeaderBulkActions/PageHeaderBulkActions.scss → PageHeaderActionsGroup/PageHeaderActionsGroup.scss} +21 -21
  64. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.spec.tsx +105 -0
  65. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.tsx +224 -0
  66. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.ts +13 -0
  67. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.tsx +30 -0
  68. package/src/components/PageHeader/index.ts +1 -1
  69. package/dist/components/DynamicDataList/helpers/generateId.d.ts.map +0 -1
  70. package/dist/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.d.ts +0 -22
  71. package/dist/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.d.ts.map +0 -1
  72. package/src/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.spec.tsx +0 -369
  73. package/src/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.tsx +0 -188
  74. /package/src/{components/DynamicDataList/helpers/generateId.ts → utils/GenerateId.ts} +0 -0
@@ -1,5 +1,5 @@
1
1
  import clsx from 'clsx';
2
- import React, { KeyboardEventHandler, useState } from 'react';
2
+ import React, { KeyboardEventHandler, useRef, useState } from 'react';
3
3
  import { noop } from '../../../../helpers/utils';
4
4
  import { FilterValidationResult, FilterValue } from '../../Filters.model';
5
5
  import classes from './FreeTextFilter.scss';
@@ -17,6 +17,8 @@ export interface FreeTextFilterProps {
17
17
 
18
18
  /** CSS Class name for additional styles */
19
19
  className?: string;
20
+ /** Select text on focus if true (default: true) */
21
+ selectOnFocus?: boolean;
20
22
  }
21
23
 
22
24
  export const FreeTextFilter: React.FC<FreeTextFilterProps> = ({
@@ -25,11 +27,19 @@ export const FreeTextFilter: React.FC<FreeTextFilterProps> = ({
25
27
  onError = noop,
26
28
  onValidate: customValidate,
27
29
  className = '',
30
+ selectOnFocus = true,
28
31
  }) => {
29
32
  const [errorMsg, setErrorMsg] = useState<string>();
30
33
  const ENTER_KEY = 'Enter';
31
34
 
32
35
  const [valueLocal, setValue] = useState(value || '');
36
+ const inputRef = useRef<HTMLInputElement>(null);
37
+
38
+ const onFocusWrapper = (): void => {
39
+ if (selectOnFocus && valueLocal) {
40
+ inputRef.current?.select();
41
+ }
42
+ };
33
43
 
34
44
  const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (e) => {
35
45
  if (e.key === ENTER_KEY) {
@@ -55,11 +65,13 @@ export const FreeTextFilter: React.FC<FreeTextFilterProps> = ({
55
65
  )}
56
66
  >
57
67
  <input
68
+ ref={inputRef}
58
69
  autoFocus
59
70
  className={clsx(classes.inputValue, errorMsg && classes.hasError)}
60
71
  onKeyDown={handleKeyDown}
61
72
  value={valueLocal as string}
62
73
  onChange={(e) => setValue(e.target.value)}
74
+ onFocus={onFocusWrapper}
63
75
  />
64
76
  {errorMsg !== undefined && <small>{errorMsg}</small>}
65
77
  </div>
@@ -1,10 +1,11 @@
1
1
  import { FormikValues, useFormikContext } from 'formik';
2
- import React, { useEffect } from 'react';
2
+ import React, { useEffect, useMemo } from 'react';
3
3
  import { useHistory } from 'react-router-dom';
4
4
  import { SaveIndicatorType, setSaveIndicator } from '../../../initialize';
5
5
  import { IconName } from '../../Icons';
6
6
  import {
7
7
  PageHeader,
8
+ PageHeaderActionItemProps,
8
9
  PageHeaderActionType,
9
10
  PageHeaderProps,
10
11
  } from '../../PageHeader';
@@ -49,41 +50,44 @@ export const FormStationHeader: React.FC<
49
50
 
50
51
  const title = useTitle(titleProperty, defaultTitle);
51
52
 
53
+ const actions: PageHeaderActionItemProps[] = useMemo(() => {
54
+ const actionItems: PageHeaderActionItemProps[] = [];
55
+
56
+ if (dirty) {
57
+ actionItems.push({
58
+ label: 'Undo Changes',
59
+ icon: IconName.Undo,
60
+ kind: 'action',
61
+ actionType: PageHeaderActionType.Context,
62
+ onClick: () => {
63
+ resetForm();
64
+ },
65
+ });
66
+ }
67
+
68
+ if (cancelNavigationUrl) {
69
+ actionItems.push({
70
+ label: 'Cancel',
71
+ icon: IconName.X,
72
+ kind: 'action',
73
+ onClick: () => {
74
+ resetForm();
75
+ // If the form has errors, Navigation needs to be wrapped in a promise or timeout.
76
+ Promise.resolve().then(() => history.push(cancelNavigationUrl));
77
+ },
78
+ });
79
+ }
80
+
81
+ return actionItems;
82
+ }, [cancelNavigationUrl, dirty, history, resetForm]);
83
+
52
84
  return (
53
85
  <PageHeader
54
86
  title={title}
55
87
  subtitle={subtitle}
56
88
  className={className}
57
89
  setTabTitle={setTabTitle}
58
- actions={[
59
- ...(dirty === true // add undo action if form as been altered
60
- ? [
61
- {
62
- label: 'Undo Changes',
63
- icon: IconName.Undo,
64
- actionType: PageHeaderActionType.Context,
65
- onClick: () => {
66
- resetForm();
67
- },
68
- },
69
- ]
70
- : []),
71
- ...(cancelNavigationUrl // add cancel action if applicable
72
- ? [
73
- {
74
- label: 'Cancel',
75
- icon: IconName.X,
76
- onClick: () => {
77
- resetForm();
78
- // If the form has errors, Navigation needs to be wrapped in a promise or timeout.
79
- Promise.resolve().then(() =>
80
- history.push(cancelNavigationUrl),
81
- );
82
- },
83
- },
84
- ]
85
- : []),
86
- ]}
90
+ actions={actions}
87
91
  />
88
92
  );
89
93
  };
@@ -54,7 +54,7 @@ const generateData = (amount: number): ListStoryData[] =>
54
54
  generateItemArray(amount, (index) => ({
55
55
  id: index + 1,
56
56
  desc: `Description ${index + 1}: ${faker.lorem.words(
57
- faker.datatype.number({ min: 10, max: 20 }),
57
+ faker.datatype.number({ min: 10, max: 50 }),
58
58
  )}`,
59
59
  title: `Item ${index + 1}: ${faker.random.words(
60
60
  faker.datatype.number({ min: 1, max: 3 }),
@@ -92,6 +92,7 @@ const groups = createGroups({
92
92
  Styling: [
93
93
  'horizontalTextAlign',
94
94
  'verticalTextAlign',
95
+ 'textWrap',
95
96
  'minimumWidth',
96
97
  'columnGap',
97
98
  'rowGap',
@@ -65,6 +65,8 @@ export interface ListProps<T extends Data> {
65
65
  horizontalTextAlign?: 'left' | 'right' | 'center';
66
66
  /** Vertical alignment of text */
67
67
  verticalTextAlign?: 'start' | 'center' | 'end';
68
+ /** If set to true, column text overflow will be wrapped to a new line. Otherwise, it will be truncated with an ellipsis (default: false) */
69
+ textWrap?: boolean;
68
70
  /**
69
71
  * Determines which select mode the list is in. (default: ListSelectMode.None)
70
72
  * If 'Single' is selected, a check mark will be rendered for each row of data
@@ -122,7 +124,7 @@ const ListRenderer = <T extends Data>(
122
124
  columnGap = '5px',
123
125
  rowGap = '0px',
124
126
  headerRowHeight = '44px',
125
- listRowHeight = '50px',
127
+ listRowHeight,
126
128
  listRowActionSize = '50px',
127
129
  headerRowActionSize = '28px',
128
130
  horizontalTextAlign = 'left',
@@ -134,6 +136,7 @@ const ListRenderer = <T extends Data>(
134
136
  selectionMode = ListSelectMode.None,
135
137
  enableSelectAll = true,
136
138
  enableSelectAllDeselect = false,
139
+ textWrap,
137
140
  onItemClicked = noop,
138
141
  onItemSelected = noop,
139
142
  onRequestMoreData = noop,
@@ -300,6 +303,7 @@ const ListRenderer = <T extends Data>(
300
303
  actionSize={listRowActionSize}
301
304
  horizontalTextAlign={horizontalTextAlign}
302
305
  verticalTextAlign={verticalTextAlign}
306
+ textWrap={textWrap}
303
307
  selectionMode={selectionMode}
304
308
  showActionButton={
305
309
  selectionMode === ListSelectMode.None &&
@@ -29,17 +29,24 @@
29
29
 
30
30
  .cellWrapper {
31
31
  display: grid;
32
- align-content: center;
33
32
  width: 100%;
34
33
  height: 100%;
35
34
  }
36
35
 
37
36
  .cell {
38
- white-space: nowrap;
39
- text-overflow: ellipsis;
40
- overflow: hidden;
41
37
  max-width: 100%;
42
38
  max-height: 100%;
39
+
40
+ span {
41
+ padding: 5px 0;
42
+ display: grid;
43
+ }
44
+
45
+ &.nowrap {
46
+ white-space: nowrap;
47
+ text-overflow: ellipsis;
48
+ overflow: hidden;
49
+ }
43
50
  }
44
51
 
45
52
  .actions {
@@ -566,4 +566,39 @@ describe('ListRow', () => {
566
566
  expect(cell.prop('title')).toBe(mockValue);
567
567
  });
568
568
  });
569
+
570
+ describe('column text alignments', () => {
571
+ const alignments: {
572
+ horizontal: 'left' | 'right' | 'center';
573
+ vertical: 'center' | 'start' | 'end';
574
+ }[] = [
575
+ { horizontal: 'center', vertical: 'center' },
576
+ { horizontal: 'left', vertical: 'start' },
577
+ { horizontal: 'right', vertical: 'end' },
578
+ ];
579
+
580
+ alignments.forEach(({ horizontal, vertical }) => {
581
+ it(`should apply the correct styles for textAlign: ${horizontal}, alignSelf: ${vertical}`, () => {
582
+ const wrapper = mount(
583
+ <ListRow
584
+ {...mockProps}
585
+ columns={[{ propertyName: 'id', size: '1fr', label: 'id' }]}
586
+ horizontalTextAlign={horizontal}
587
+ verticalTextAlign={vertical}
588
+ />,
589
+ );
590
+
591
+ const wrapperDivs = wrapper.findWhere((node) =>
592
+ node.prop('data-test-id')?.startsWith('list-entry-property'),
593
+ );
594
+
595
+ wrapperDivs.forEach((node) => {
596
+ const style = node.prop('style');
597
+
598
+ expect(style).toHaveProperty('textAlign', horizontal);
599
+ expect(style).toHaveProperty('alignSelf', vertical);
600
+ });
601
+ });
602
+ });
603
+ });
569
604
  });
@@ -19,13 +19,15 @@ export interface ListRowProps<T extends Data> {
19
19
  /** Header row height */
20
20
  columnGap: string;
21
21
  /** List row height */
22
- rowHeight: string;
22
+ rowHeight?: string;
23
23
  /** Size of action button and checkbox */
24
24
  actionSize: string;
25
25
  /** Horizontal alignment of text */
26
26
  horizontalTextAlign: 'left' | 'right' | 'center';
27
27
  /** Vertical alignment of text */
28
28
  verticalTextAlign: 'start' | 'center' | 'end';
29
+ /** If set to true, column text overflow will be wrapped to a new line. Otherwise, it will be truncated with an ellipsis (default: false) */
30
+ textWrap?: boolean;
29
31
  /** List data */
30
32
  data: T;
31
33
  /** The column definition */
@@ -81,32 +83,54 @@ export const setLocale = (locale: string): void => {
81
83
 
82
84
  setLocale(navigator.language);
83
85
 
84
- function renderData<T extends Data>(
86
+ const renderData = <T extends Data>(
85
87
  column: Column<T>,
86
88
  rowData: T,
87
- ): React.ReactNode {
89
+ ): { columnData: React.ReactNode; tooltip: string | undefined } => {
90
+ const getTooltip = (value: unknown): string | undefined =>
91
+ column.tooltip !== false ? getTooltipText(value) : undefined;
92
+
88
93
  if (!column.propertyName) {
89
- return column.render!(undefined, rowData);
94
+ const columnData = column.render?.(undefined, rowData);
95
+ return {
96
+ columnData,
97
+ tooltip: getTooltip(columnData),
98
+ };
90
99
  }
91
100
  const value: unknown = rowData[column.propertyName];
92
101
  if (column.render) {
93
- return column.render(value, rowData);
102
+ const columnData = column.render(value, rowData);
103
+ return {
104
+ columnData,
105
+ tooltip: getTooltip(columnData),
106
+ };
94
107
  }
95
108
 
96
109
  if (value === null || value === undefined) {
97
- return '';
110
+ return { columnData: <span />, tooltip: undefined };
98
111
  }
99
112
 
100
113
  if (typeof value === 'number') {
101
- return numberFormatter.format(value);
114
+ const columnData = numberFormatter.format(value);
115
+ return {
116
+ columnData: <span>{columnData}</span>,
117
+ tooltip: getTooltip(columnData),
118
+ };
102
119
  }
103
120
 
104
121
  if (value instanceof Date) {
105
- return dateFormatter.format(value);
122
+ const columnData = dateFormatter.format(value);
123
+ return {
124
+ columnData: <span>{columnData}</span>,
125
+ tooltip: getTooltip(columnData),
126
+ };
106
127
  }
107
128
 
108
- return String(value);
109
- }
129
+ return {
130
+ columnData: <span>{String(value)}</span>,
131
+ tooltip: getTooltip(String(value)),
132
+ };
133
+ };
110
134
 
111
135
  /**
112
136
  * Renders the rows for the list component
@@ -128,6 +152,7 @@ export const ListRow = <T extends Data>({
128
152
  actionSize,
129
153
  horizontalTextAlign,
130
154
  verticalTextAlign,
155
+ textWrap = false,
131
156
  data,
132
157
  itemSelected = false,
133
158
  isTrigger = false,
@@ -143,7 +168,7 @@ export const ListRow = <T extends Data>({
143
168
  inlineMenuActions,
144
169
  }: PropsWithChildren<ListRowProps<T>>): JSX.Element => {
145
170
  const customRootStyles = {
146
- gridAutoRows: rowHeight,
171
+ gridAutoRows: `minmax(50px, ${rowHeight})`,
147
172
  gridColumnGap: columnGap,
148
173
  justifyItems: horizontalTextAlign,
149
174
  alignItems: verticalTextAlign,
@@ -199,7 +224,7 @@ export const ListRow = <T extends Data>({
199
224
  typeof onItemClicked !== 'function';
200
225
 
201
226
  const generateCells: JSX.Element[] = columns.map((column: Column<T>) => {
202
- const columnData: React.ReactNode = renderData<T>(column, data);
227
+ const { columnData, tooltip } = renderData<T>(column, data);
203
228
 
204
229
  return (
205
230
  <div
@@ -207,12 +232,14 @@ export const ListRow = <T extends Data>({
207
232
  className={classes.cellWrapper}
208
233
  >
209
234
  <div
210
- className={classes.cell}
211
- title={
212
- column.tooltip !== false ? getTooltipText(columnData) : undefined
213
- }
235
+ className={clsx(classes.cell, { [classes.nowrap]: !textWrap })}
236
+ title={tooltip}
214
237
  data-test-id={`list-entry-property:${column.propertyName as string}`}
215
- style={{ justifySelf: column.horizontalColumnAlign }}
238
+ style={{
239
+ justifySelf: column.horizontalColumnAlign, // Horizontal alignment based on column config
240
+ alignSelf: verticalTextAlign, // Vertical alignment based on props
241
+ textAlign: horizontalTextAlign, // Additional text alignment inside the cell
242
+ }}
216
243
  >
217
244
  {columnData}
218
245
  </div>
@@ -10,7 +10,7 @@ export interface ListRowLoaderProps<T extends Data> {
10
10
  /** Space between columns */
11
11
  columnGap: string;
12
12
  /** List row height */
13
- rowHeight: string;
13
+ rowHeight?: string;
14
14
  /** The column definition */
15
15
  columns: Column<T>[];
16
16
  }
@@ -25,7 +25,7 @@ export const ListRowLoader = <T extends Data>({
25
25
  const rows = 5;
26
26
 
27
27
  const customRowStyles = {
28
- gridAutoRows: rowHeight,
28
+ gridAutoRows: `minmax(50px, ${rowHeight})`,
29
29
  gridTemplateColumns: columnSizes,
30
30
  gridColumnGap: columnGap,
31
31
  } as React.CSSProperties;
@@ -1,4 +1,5 @@
1
1
  import { PageHeaderActionProps } from './PageHeaderAction/PageHeaderAction.model';
2
+ import { PageHeaderActionsGroupProps } from './PageHeaderActionsGroup/PageHeaderActionsGroup';
2
3
 
3
4
  export interface PageHeaderProps {
4
5
  /** Title shown in page header */
@@ -6,20 +7,17 @@ export interface PageHeaderProps {
6
7
  /** Subtitle shown in page header */
7
8
  subtitle?: string;
8
9
  /** Array of actions to be rendered. (default: []) */
9
- actions?: PageHeaderActionProps[];
10
- /** Array of Bulk Actions to be rendered. If populated, Bulk Actions will become available. (default: []) */
11
- bulkActions?: PageHeaderActionProps[];
12
- /** Whether or bulk actions are shown by default. (default: false) */
13
- openBulkActionsOnStart?: boolean;
14
- /** Whether or not bulk actions are disabled (default: false)*/
15
- bulkActionsDisabled?: boolean;
16
- /**
17
- * Callback to emit when Bulk Actions is toggled
18
- * The expanded state is supplied as an argument
19
- */
20
- onBulkActionsToggled?: (expanded: boolean) => void;
10
+ actions?: PageHeaderActionItemProps[];
21
11
  /** CSS Class name for additional styles */
22
12
  className?: string;
23
13
  /** Update the tab title using the 'title' field. (default: true) */
24
14
  setTabTitle?: boolean;
25
15
  }
16
+
17
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
18
+ export interface PageHeaderSpacerProps {}
19
+
20
+ export type PageHeaderActionItemProps =
21
+ | (PageHeaderSpacerProps & { kind: 'spacer' })
22
+ | (PageHeaderActionProps & { kind: 'action' })
23
+ | (PageHeaderActionsGroupProps & { kind: 'group' });
@@ -5,7 +5,7 @@
5
5
 
6
6
  height: $page-header-height;
7
7
  display: grid;
8
- grid-template-columns: minmax(min-content, 1fr) max-content max-content;
8
+ grid-template-columns: minmax(30%, 1fr) auto;
9
9
  grid-template-rows: 1fr;
10
10
 
11
11
  @media only screen and (min-width: $XL-min-size) {
@@ -52,9 +52,13 @@
52
52
 
53
53
  .actions {
54
54
  display: grid;
55
- grid-template-rows: 1fr;
56
55
  grid-auto-flow: column;
56
+ grid-auto-columns: minmax(0, min-content);
57
57
  grid-gap: 1px;
58
- justify-items: end;
58
+ }
59
+
60
+ .spacer {
61
+ display: grid;
62
+ width: 8px;
59
63
  }
60
64
  }
@@ -1,10 +1,11 @@
1
- import { mount, shallow } from 'enzyme';
1
+ import { shallow } from 'enzyme';
2
2
  import React from 'react';
3
3
  import { noop } from '../../helpers/utils';
4
4
  import { PageHeader } from './PageHeader';
5
+ import { PageHeaderActionItemProps } from './PageHeader.model';
6
+ import { PageHeaderActionProps } from './PageHeaderAction';
5
7
  import { PageHeaderAction } from './PageHeaderAction/PageHeaderAction';
6
- import { PageHeaderActionProps } from './PageHeaderAction/PageHeaderAction.model';
7
- import { PageHeaderBulkActions } from './PageHeaderBulkActions/PageHeaderBulkActions';
8
+ import { PageHeaderActionsGroup } from './PageHeaderActionsGroup/PageHeaderActionsGroup';
8
9
 
9
10
  jest.mock('../../hooks/useTabTitle/useTabTitle');
10
11
 
@@ -40,96 +41,37 @@ describe('PageHeader', () => {
40
41
  });
41
42
 
42
43
  it(`renders actions`, () => {
43
- const wrapper = shallow(<PageHeader actions={defaultbulkActions} />);
44
+ const wrapper = shallow(
45
+ <PageHeader actions={[{ ...defaultbulkActions[0], kind: 'action' }]} />,
46
+ );
44
47
 
45
48
  const action = wrapper.find(PageHeaderAction);
46
49
  expect(action.exists()).toBe(true);
47
50
  });
48
51
 
49
- it(`adds a 10px left margin to the actions container as a spacer when actions are present`, () => {
50
- const wrapper = shallow(<PageHeader actions={defaultbulkActions} />);
51
-
52
- const actionsContainer = wrapper
53
- .find('.actions')
54
- .prop('style') as React.CSSProperties;
55
-
56
- expect(actionsContainer.marginLeft).toBe('10px');
57
- });
58
-
59
- it(`does not add a 10px left margin to the actions container when no actions are present`, () => {
60
- const wrapper = shallow(<PageHeader />);
61
-
62
- const actionsContainer = wrapper
63
- .find('.actions')
64
- .prop('style') as React.CSSProperties;
65
-
66
- expect(actionsContainer.marginLeft).toBeUndefined();
67
- });
68
-
69
- it(`does not render 'PageHeaderBulkActions' if bulkActions prop is empty`, () => {
70
- const wrapper = mount(<PageHeader />);
71
-
72
- const bulkActions = wrapper.find(PageHeaderBulkActions);
73
-
74
- expect(bulkActions.exists()).toBe(false);
75
- });
76
-
77
- it(`renders 'PageHeaderBulkActions' if bulkActions prop is populated`, () => {
78
- const wrapper = mount(<PageHeader bulkActions={defaultbulkActions} />);
79
-
80
- const bulkActions = wrapper.find(PageHeaderBulkActions);
81
-
82
- expect(bulkActions.exists()).toBe(true);
83
- });
84
-
85
- it('raises onBulkActionsToggled', () => {
86
- const spy = jest.fn();
87
-
88
- const wrapper = mount(
89
- <PageHeader
90
- bulkActions={defaultbulkActions}
91
- onBulkActionsToggled={spy}
92
- />,
93
- );
94
-
95
- const bulkActionsToggle = wrapper.find(PageHeaderAction).first();
96
- bulkActionsToggle.simulate('click');
97
- expect(spy).toHaveBeenCalledTimes(1);
98
- expect(spy).toHaveBeenCalledWith(true);
99
- });
100
-
101
- it(`bulk actions should not be disabled by default`, () => {
102
- const spy = jest.fn();
103
-
104
- const wrapper = shallow(
105
- <PageHeader
106
- bulkActions={defaultbulkActions}
107
- onBulkActionsToggled={spy}
108
- />,
109
- );
110
-
111
- const bulkActions = wrapper.find(PageHeaderBulkActions);
112
-
113
- expect(bulkActions.prop('bulkActionsDisabled')).toBe(false);
52
+ it(`renders actions groups`, () => {
53
+ const actions: PageHeaderActionItemProps[] = [
54
+ {
55
+ label: 'Bulk Actions',
56
+ kind: 'group',
57
+ actions: defaultbulkActions,
58
+ },
59
+ ];
60
+ const wrapper = shallow(<PageHeader actions={actions} />);
61
+
62
+ const action = wrapper.find(PageHeaderActionsGroup);
63
+ expect(action.exists()).toBe(true);
114
64
  });
115
65
 
116
- it(`disables bulk actions if 'bulkActionsDisabled' prop is set to true`, () => {
117
- const spy = jest.fn();
66
+ it('renders spacers', () => {
67
+ const actions: PageHeaderActionItemProps[] = [
68
+ {
69
+ kind: 'spacer',
70
+ },
71
+ ];
72
+ const wrapper = shallow(<PageHeader actions={actions} />);
118
73
 
119
- const wrapper = shallow(
120
- <PageHeader
121
- bulkActions={defaultbulkActions}
122
- onBulkActionsToggled={spy}
123
- bulkActionsDisabled={true}
124
- />,
125
- );
126
-
127
- const bulkActions = wrapper.find(PageHeaderBulkActions);
128
-
129
- expect(bulkActions.prop('bulkActionsDisabled')).toBe(true);
74
+ const spacer = wrapper.find('.spacer');
75
+ expect(spacer.exists()).toBe(true);
130
76
  });
131
-
132
- it.todo('calculates the available space needed for bulk actions');
133
-
134
- it.todo('should not calcuate if there are no bulk actions');
135
77
  });
@@ -2,21 +2,24 @@ import { action } from '@storybook/addon-actions';
2
2
  import { Meta, StoryObj } from '@storybook/react';
3
3
  import { IconName } from '../Icons';
4
4
  import { PageHeader } from './PageHeader';
5
+ import { PageHeaderActionItemProps } from './PageHeader.model';
5
6
  import {
6
7
  PageHeaderActionProps,
7
8
  PageHeaderActionType,
8
9
  } from './PageHeaderAction';
9
10
  import {} from './PageHeaderAction/PageHeaderAction';
10
11
 
11
- const headerActions: PageHeaderActionProps[] = [
12
+ const headerActions: PageHeaderActionItemProps[] = [
12
13
  {
13
14
  label: 'Undo',
14
15
  icon: IconName.Undo,
16
+ kind: 'action',
15
17
  onClick: action('onActionSelected'),
16
18
  },
17
19
  {
18
20
  label: 'Cancel',
19
21
  icon: IconName.X,
22
+ kind: 'action',
20
23
  onClick: action('onActionSelected'),
21
24
  },
22
25
  ];
@@ -101,7 +104,7 @@ export const Actions: StoryObj<typeof PageHeader> = {
101
104
  },
102
105
  };
103
106
 
104
- export const BulkActions: StoryObj<typeof PageHeader> = {
107
+ export const ActionsGroup: StoryObj<typeof PageHeader> = {
105
108
  parameters: {
106
109
  docs: {
107
110
  description: {
@@ -113,13 +116,19 @@ export const BulkActions: StoryObj<typeof PageHeader> = {
113
116
  args: {
114
117
  title: 'Title',
115
118
  subtitle: 'Subtitle',
116
- bulkActions: bulkHeaderActions,
117
- openBulkActionsOnStart: false,
119
+ actions: [
120
+ {
121
+ label: 'Bulk Actions',
122
+ icon: IconName.Bulk,
123
+ kind: 'group',
124
+ actions: bulkHeaderActions,
125
+ },
126
+ ],
118
127
  },
119
128
  };
120
129
 
121
130
  export const All: StoryObj<typeof PageHeader> = {
122
- name: 'Titles, Bulk Actions, & Actions',
131
+ name: 'Titles, Actions, & Actions Groups',
123
132
  parameters: {
124
133
  docs: {
125
134
  description: {
@@ -131,7 +140,23 @@ export const All: StoryObj<typeof PageHeader> = {
131
140
  args: {
132
141
  title: 'Title',
133
142
  subtitle: 'Subtitle',
134
- actions: headerActions,
135
- bulkActions: bulkHeaderActions,
143
+ actions: [
144
+ ...headerActions,
145
+ { kind: 'spacer' },
146
+ {
147
+ label: 'Bulk Actions',
148
+ icon: IconName.Bulk,
149
+ kind: 'group',
150
+ actions: bulkHeaderActions,
151
+ onActionsGroupToggled: action('onBulkActions1Toggled'),
152
+ },
153
+ {
154
+ label: 'Bulk Actions 2',
155
+ icon: IconName.Bulk,
156
+ kind: 'group',
157
+ actions: [bulkHeaderActions[0]],
158
+ onActionsGroupToggled: action('onBulkActions2Toggled'),
159
+ },
160
+ ],
136
161
  },
137
162
  };