@axinom/mosaic-ui 0.49.0-rc.1 → 0.49.0-rc.10
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.map +1 -1
- package/dist/components/DynamicDataList/DynamicListRow/DynamicListRow.d.ts.map +1 -1
- 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/ListRow/ListRow.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 +4 -4
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +4 -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.tsx +1 -1
- package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.spec.tsx +37 -0
- package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.tsx +4 -0
- package/src/components/Explorer/Explorer.spec.tsx +26 -16
- package/src/components/Explorer/Explorer.tsx +47 -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/ListRow/ListRow.scss +0 -1
- package/src/components/List/ListRow/ListRow.spec.tsx +35 -0
- package/src/components/List/ListRow/ListRow.tsx +5 -1
- 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
|
@@ -212,7 +212,11 @@ export const ListRow = <T extends Data>({
|
|
|
212
212
|
column.tooltip !== false ? getTooltipText(columnData) : undefined
|
|
213
213
|
}
|
|
214
214
|
data-test-id={`list-entry-property:${column.propertyName as string}`}
|
|
215
|
-
style={{
|
|
215
|
+
style={{
|
|
216
|
+
justifySelf: column.horizontalColumnAlign, // Horizontal alignment based on column config
|
|
217
|
+
alignSelf: verticalTextAlign, // Vertical alignment based on props
|
|
218
|
+
textAlign: horizontalTextAlign, // Additional text alignment inside the cell
|
|
219
|
+
}}
|
|
216
220
|
>
|
|
217
221
|
{columnData}
|
|
218
222
|
</div>
|
|
@@ -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
|
};
|
|
@@ -1,90 +1,57 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
|
-
import React
|
|
3
|
-
import { noop } from '../../helpers/utils';
|
|
2
|
+
import React from 'react';
|
|
4
3
|
import { useTabTitle } from '../../hooks/useTabTitle/useTabTitle';
|
|
5
4
|
import { useWindowSize } from '../../hooks/useWindowSize/useWindowSize';
|
|
6
5
|
import { PageHeaderProps } from './PageHeader.model';
|
|
7
6
|
import classes from './PageHeader.scss';
|
|
7
|
+
import { PageHeaderActionProps } from './PageHeaderAction';
|
|
8
8
|
import { PageHeaderAction } from './PageHeaderAction/PageHeaderAction';
|
|
9
|
-
import {
|
|
9
|
+
import { PageHeaderActionsGroup } from './PageHeaderActionsGroup/PageHeaderActionsGroup';
|
|
10
|
+
import { PageHeaderActionsGroupContextProvider } from './PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
|
-
* Primary header for stations. Accepts a title, subtitle, actions, and
|
|
13
|
+
* Primary header for stations. Accepts a title, subtitle, actions, and actions groups.
|
|
13
14
|
* @example
|
|
14
|
-
* <PageHeader title="title" subtitle="subtitle"
|
|
15
|
+
* <PageHeader title="title" subtitle="subtitle" actions={[
|
|
16
|
+
* { label: 'Undo', icon: IconName.Undo, kind: 'action', onClick: onActionSelected },
|
|
17
|
+
* { label: 'Cancel', icon: IconName.X, kind: 'action' onClick: onActionSelected },
|
|
18
|
+
* { kind: 'spacer' },
|
|
19
|
+
* { label: 'Actions Group', kind: 'group', actions: [
|
|
20
|
+
* { label: 'Action 1', actionType: PageHeaderActionType.Context, icon: IconName.Delete, confirmationMode: 'Advanced', onClick: onActionSelected },
|
|
21
|
+
* ]}
|
|
22
|
+
* ]} />
|
|
15
23
|
*/
|
|
16
24
|
export const PageHeader: React.FC<PageHeaderProps> = ({
|
|
17
25
|
title = '',
|
|
18
26
|
subtitle = '',
|
|
19
27
|
actions = [],
|
|
20
|
-
bulkActions = [],
|
|
21
|
-
bulkActionsDisabled = false,
|
|
22
|
-
openBulkActionsOnStart = false,
|
|
23
|
-
onBulkActionsToggled = noop,
|
|
24
28
|
className = '',
|
|
25
29
|
setTabTitle = true,
|
|
26
30
|
}) => {
|
|
27
|
-
const [containerScrollWidth, setContainerScrollWidth] = useState<number>(0);
|
|
28
|
-
const [childrenScrollWidth, setChildrenScrollWidth] = useState<number>(0);
|
|
29
|
-
const [availableActionSpace, setAvailableActionSpace] = useState<number>(0);
|
|
30
|
-
const ActionsWidth = 120;
|
|
31
|
-
const { width } = useWindowSize();
|
|
32
|
-
|
|
33
|
-
// Order of this useEffect hook is important
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
setContainerScrollWidth(width);
|
|
36
|
-
}, [width]);
|
|
37
|
-
|
|
38
|
-
const actionsRef = (node: HTMLDivElement): void => {
|
|
39
|
-
if (node !== null) {
|
|
40
|
-
setChildrenScrollWidth(node.scrollWidth);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
31
|
useTabTitle(title, setTabTitle);
|
|
32
|
+
const { width } = useWindowSize();
|
|
33
|
+
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
45
34
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (bulkActions.length > 0) {
|
|
49
|
-
const minimumTitleWidth = width >= 1920 ? 510 : 120;
|
|
50
|
-
const availableWidth: number =
|
|
51
|
-
containerScrollWidth - (minimumTitleWidth + childrenScrollWidth);
|
|
35
|
+
const [availableActionSpace, setAvailableActionSpace] =
|
|
36
|
+
React.useState<number>(0);
|
|
52
37
|
|
|
53
|
-
|
|
54
|
-
|
|
38
|
+
React.useEffect(() => {
|
|
39
|
+
if (containerRef.current) {
|
|
40
|
+
// Use up to 75% of the container width for actions
|
|
41
|
+
const maxActionsWidth = containerRef.current.clientWidth * 0.75;
|
|
55
42
|
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
43
|
+
// Each action is 120px wide
|
|
44
|
+
setAvailableActionSpace(
|
|
45
|
+
Math.floor(maxActionsWidth / 120 - actions.length),
|
|
59
46
|
);
|
|
60
|
-
|
|
61
|
-
// determines if the 'more' action will take up an action slot
|
|
62
|
-
if (bulkActions.length > currentAvailableActionSpace) {
|
|
63
|
-
currentAvailableActionSpace -= moreActionSpace;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// prevents current available space from going negative
|
|
67
|
-
if (currentAvailableActionSpace < 0) {
|
|
68
|
-
currentAvailableActionSpace = 0;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// update available action space if the currentAvailableActionSpace has changed
|
|
72
|
-
if (currentAvailableActionSpace !== availableActionSpace) {
|
|
73
|
-
setAvailableActionSpace(currentAvailableActionSpace);
|
|
74
|
-
}
|
|
75
47
|
}
|
|
76
|
-
}, [
|
|
77
|
-
bulkActions,
|
|
78
|
-
availableActionSpace,
|
|
79
|
-
childrenScrollWidth,
|
|
80
|
-
containerScrollWidth,
|
|
81
|
-
width,
|
|
82
|
-
]);
|
|
48
|
+
}, [width, actions.length]);
|
|
83
49
|
|
|
84
50
|
return (
|
|
85
51
|
<div
|
|
86
52
|
className={clsx(classes.container, 'page-header-container', className)}
|
|
87
53
|
data-test-id="page-header"
|
|
54
|
+
ref={containerRef}
|
|
88
55
|
>
|
|
89
56
|
<div className={clsx(classes.titles)}>
|
|
90
57
|
<div className={clsx(classes.title)} data-test-id="page-header-title">
|
|
@@ -98,24 +65,30 @@ export const PageHeader: React.FC<PageHeaderProps> = ({
|
|
|
98
65
|
{subtitle}
|
|
99
66
|
</div>
|
|
100
67
|
</div>
|
|
101
|
-
{
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
68
|
+
<div className={classes.actions} data-test-id="page-header-actions">
|
|
69
|
+
<PageHeaderActionsGroupContextProvider>
|
|
70
|
+
{actions.map((action, index) => {
|
|
71
|
+
switch (action.kind) {
|
|
72
|
+
case 'group':
|
|
73
|
+
return (
|
|
74
|
+
<PageHeaderActionsGroup
|
|
75
|
+
key={index}
|
|
76
|
+
{...action}
|
|
77
|
+
availableActionSpace={availableActionSpace}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
case 'spacer':
|
|
81
|
+
return <div key={index} className={classes.spacer} />;
|
|
82
|
+
case 'action':
|
|
83
|
+
return (
|
|
84
|
+
<PageHeaderAction
|
|
85
|
+
key={index}
|
|
86
|
+
{...(action as PageHeaderActionProps)}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
})}
|
|
91
|
+
</PageHeaderActionsGroupContextProvider>
|
|
119
92
|
</div>
|
|
120
93
|
</div>
|
|
121
94
|
);
|
|
@@ -2,36 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
.container {
|
|
4
4
|
display: grid;
|
|
5
|
-
grid-template-rows: 1fr;
|
|
6
|
-
grid-
|
|
5
|
+
grid-template-rows: $page-header-height 1fr;
|
|
6
|
+
grid-template-columns: max-content 1fr;
|
|
7
7
|
grid-gap: 1px;
|
|
8
8
|
justify-content: end;
|
|
9
|
+
width: max-content;
|
|
9
10
|
|
|
10
|
-
.
|
|
11
|
-
width: 120px;
|
|
11
|
+
.actions {
|
|
12
12
|
display: grid;
|
|
13
|
-
grid-
|
|
14
|
-
grid-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
grid-auto-flow: column;
|
|
14
|
+
grid-gap: 1px;
|
|
15
|
+
transition: max-width 200ms linear, opacity 150ms linear;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
}
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
.dropDownList {
|
|
20
|
+
width: 100%;
|
|
21
|
+
max-width: 360px;
|
|
22
|
+
display: grid;
|
|
23
|
+
grid-template-columns: 1fr;
|
|
24
|
+
grid-auto-rows: 60px;
|
|
25
|
+
grid-gap: 1px;
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
border-top: 2px solid white;
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
right: 0px;
|
|
29
|
+
grid-column: 1 / span 2;
|
|
30
|
+
place-self: end;
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
background-color: white;
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
}
|
|
34
|
+
z-index: 2;
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { mount, shallow } from 'enzyme';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { noop } from '../../../helpers/utils';
|
|
4
|
+
import { PageHeaderAction } from '../PageHeaderAction/PageHeaderAction';
|
|
5
|
+
import {
|
|
6
|
+
PageHeaderActionProps,
|
|
7
|
+
PageHeaderActionType,
|
|
8
|
+
} from '../PageHeaderAction/PageHeaderAction.model';
|
|
9
|
+
import {
|
|
10
|
+
PageHeaderActionsGroup,
|
|
11
|
+
PageHeaderActionsGroupProps,
|
|
12
|
+
} from './PageHeaderActionsGroup';
|
|
13
|
+
|
|
14
|
+
jest.mock('../../../utils/GenerateId', () => ({
|
|
15
|
+
uuid: jest.fn().mockReturnValue('test-uuid'),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const defaultActions: PageHeaderActionProps[] = [
|
|
19
|
+
{
|
|
20
|
+
label: 'Group Action 1',
|
|
21
|
+
onClick: noop,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
label: 'Group Action 2',
|
|
25
|
+
onClick: noop,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
label: 'Group Action 3',
|
|
29
|
+
onClick: noop,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
label: 'Group Action 4',
|
|
33
|
+
onClick: noop,
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const defaultProps: PageHeaderActionsGroupProps = {
|
|
38
|
+
label: 'Group Actions',
|
|
39
|
+
actions: defaultActions,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
describe('PageHeaderActionsGroup', () => {
|
|
43
|
+
it('renders the component without crashing', () => {
|
|
44
|
+
const wrapper = shallow(<PageHeaderActionsGroup {...defaultProps} />);
|
|
45
|
+
expect(wrapper).toBeTruthy();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it(`'Group Actions' has the 'Context' actionType when closed and 'Active' actionType when open`, () => {
|
|
49
|
+
const wrapper = mount(<PageHeaderActionsGroup {...defaultProps} />);
|
|
50
|
+
let groupActionsToggle = wrapper.find(PageHeaderAction).first();
|
|
51
|
+
// let action = wrapper.find(PageHeaderAction);
|
|
52
|
+
|
|
53
|
+
expect(groupActionsToggle.prop('actionType')).toBe(
|
|
54
|
+
PageHeaderActionType.Context,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
groupActionsToggle.simulate('click');
|
|
58
|
+
|
|
59
|
+
groupActionsToggle = wrapper.find(PageHeaderAction).first();
|
|
60
|
+
|
|
61
|
+
expect(groupActionsToggle.prop('actionType')).toBe(
|
|
62
|
+
PageHeaderActionType.Active,
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it(`renders all actions when 'Group Actions' is selected and there is enough available action slots`, () => {
|
|
67
|
+
const wrapper = mount(
|
|
68
|
+
<PageHeaderActionsGroup {...defaultProps} availableActionSpace={5} />,
|
|
69
|
+
);
|
|
70
|
+
const groupActionsToggle = wrapper.find(PageHeaderAction).first();
|
|
71
|
+
let actions = wrapper.find(PageHeaderAction);
|
|
72
|
+
|
|
73
|
+
groupActionsToggle.simulate('click');
|
|
74
|
+
actions = wrapper.find(PageHeaderAction);
|
|
75
|
+
expect(actions).toHaveLength(5);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it(`raises onActionsGroupToggled`, () => {
|
|
79
|
+
const groupActionSpy = jest.fn();
|
|
80
|
+
const wrapper = mount(
|
|
81
|
+
<PageHeaderActionsGroup
|
|
82
|
+
{...defaultProps}
|
|
83
|
+
onActionsGroupToggled={groupActionSpy}
|
|
84
|
+
/>,
|
|
85
|
+
);
|
|
86
|
+
const groupActionsToggle = wrapper.find(PageHeaderAction).first();
|
|
87
|
+
groupActionsToggle.simulate('click');
|
|
88
|
+
expect(groupActionSpy).toHaveBeenCalledTimes(1);
|
|
89
|
+
expect(groupActionSpy).toHaveBeenCalledWith(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it(`renders 'Group Actions' and 'More Actions' if openActionsGroupOnStart is set to true and there isn't enough available space`, () => {
|
|
93
|
+
const wrapper = mount(
|
|
94
|
+
<PageHeaderActionsGroup
|
|
95
|
+
{...defaultProps}
|
|
96
|
+
openActionsGroupOnStart={true}
|
|
97
|
+
availableActionSpace={3}
|
|
98
|
+
/>,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const actions = wrapper.find(PageHeaderAction);
|
|
102
|
+
|
|
103
|
+
expect(actions).toHaveLength(4);
|
|
104
|
+
});
|
|
105
|
+
});
|