@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.
- package/dist/components/DynamicDataList/DynamicDataList.d.ts +3 -1
- package/dist/components/DynamicDataList/DynamicDataList.d.ts.map +1 -1
- package/dist/components/DynamicDataList/DynamicListRow/DynamicListRow.d.ts +3 -1
- package/dist/components/DynamicDataList/DynamicListRow/DynamicListRow.d.ts.map +1 -1
- package/dist/components/Explorer/Explorer.d.ts +2 -0
- package/dist/components/Explorer/Explorer.d.ts.map +1 -1
- package/dist/components/Filters/Filter/Filter.d.ts +2 -1
- package/dist/components/Filters/Filter/Filter.d.ts.map +1 -1
- package/dist/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.d.ts +2 -0
- package/dist/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.d.ts.map +1 -1
- package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts.map +1 -1
- package/dist/components/List/List.d.ts +2 -0
- package/dist/components/List/List.d.ts.map +1 -1
- package/dist/components/List/ListRow/ListRow.d.ts +4 -2
- package/dist/components/List/ListRow/ListRow.d.ts.map +1 -1
- package/dist/components/List/ListRow/ListRowLoader.d.ts +1 -1
- package/dist/components/List/ListRow/ListRowLoader.d.ts.map +1 -1
- package/dist/components/PageHeader/PageHeader.d.ts +9 -2
- package/dist/components/PageHeader/PageHeader.d.ts.map +1 -1
- package/dist/components/PageHeader/PageHeader.model.d.ts +11 -12
- package/dist/components/PageHeader/PageHeader.model.d.ts.map +1 -1
- package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts +35 -0
- package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +1 -0
- package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts +7 -0
- package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts.map +1 -0
- package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts +3 -0
- package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts.map +1 -0
- package/dist/components/PageHeader/index.d.ts +1 -1
- package/dist/components/PageHeader/index.d.ts.map +1 -1
- package/dist/index.es.js +5 -4
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/{components/DynamicDataList/helpers/generateId.d.ts → utils/GenerateId.d.ts} +1 -1
- package/dist/utils/GenerateId.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/components/DynamicDataList/DynamicDataList.spec.tsx +2 -1
- package/src/components/DynamicDataList/DynamicDataList.stories.tsx +2 -1
- package/src/components/DynamicDataList/DynamicDataList.tsx +5 -1
- package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.scss +11 -5
- package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.spec.tsx +37 -0
- package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.tsx +23 -14
- package/src/components/Explorer/Explorer.spec.tsx +26 -16
- package/src/components/Explorer/Explorer.stories.tsx +1 -0
- package/src/components/Explorer/Explorer.tsx +52 -28
- package/src/components/Explorer/NavigationExplorer/NavigationExplorer.spec.tsx +2 -2
- package/src/components/Explorer/SelectionExplorer/SelectionExplorer.spec.tsx +8 -32
- package/src/components/Filters/Filter/Filter.tsx +3 -0
- package/src/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.spec.tsx +16 -1
- package/src/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.tsx +13 -1
- package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +34 -30
- package/src/components/List/List.stories.tsx +2 -1
- package/src/components/List/List.tsx +5 -1
- package/src/components/List/ListRow/ListRow.scss +11 -4
- package/src/components/List/ListRow/ListRow.spec.tsx +35 -0
- package/src/components/List/ListRow/ListRow.tsx +44 -17
- package/src/components/List/ListRow/ListRowLoader.tsx +2 -2
- package/src/components/PageHeader/PageHeader.model.ts +10 -12
- package/src/components/PageHeader/PageHeader.scss +7 -3
- package/src/components/PageHeader/PageHeader.spec.tsx +28 -86
- package/src/components/PageHeader/PageHeader.stories.tsx +32 -7
- package/src/components/PageHeader/PageHeader.tsx +50 -77
- package/src/components/PageHeader/{PageHeaderBulkActions/PageHeaderBulkActions.scss → PageHeaderActionsGroup/PageHeaderActionsGroup.scss} +21 -21
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.spec.tsx +105 -0
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.tsx +224 -0
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.ts +13 -0
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.tsx +30 -0
- package/src/components/PageHeader/index.ts +1 -1
- package/dist/components/DynamicDataList/helpers/generateId.d.ts.map +0 -1
- package/dist/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.d.ts +0 -22
- package/dist/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.d.ts.map +0 -1
- package/src/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.spec.tsx +0 -369
- package/src/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.tsx +0 -188
- /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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
122
|
+
const columnData = dateFormatter.format(value);
|
|
123
|
+
return {
|
|
124
|
+
columnData: <span>{columnData}</span>,
|
|
125
|
+
tooltip: getTooltip(columnData),
|
|
126
|
+
};
|
|
106
127
|
}
|
|
107
128
|
|
|
108
|
-
return
|
|
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
|
|
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={{
|
|
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
|
|
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?:
|
|
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(
|
|
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
|
-
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.spacer {
|
|
61
|
+
display: grid;
|
|
62
|
+
width: 8px;
|
|
59
63
|
}
|
|
60
64
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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(
|
|
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(`
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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(
|
|
117
|
-
const
|
|
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
|
|
120
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
117
|
-
|
|
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,
|
|
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:
|
|
135
|
-
|
|
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
|
};
|