@axinom/mosaic-ui 0.49.0-rc.5 → 0.49.0-rc.6

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 (46) hide show
  1. package/dist/components/DynamicDataList/DynamicDataList.d.ts.map +1 -1
  2. package/dist/components/Explorer/Explorer.d.ts.map +1 -1
  3. package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts.map +1 -1
  4. package/dist/components/PageHeader/PageHeader.d.ts +9 -2
  5. package/dist/components/PageHeader/PageHeader.d.ts.map +1 -1
  6. package/dist/components/PageHeader/PageHeader.model.d.ts +11 -12
  7. package/dist/components/PageHeader/PageHeader.model.d.ts.map +1 -1
  8. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts +35 -0
  9. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +1 -0
  10. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts +7 -0
  11. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts.map +1 -0
  12. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts +3 -0
  13. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts.map +1 -0
  14. package/dist/components/PageHeader/index.d.ts +1 -1
  15. package/dist/components/PageHeader/index.d.ts.map +1 -1
  16. package/dist/index.es.js +4 -4
  17. package/dist/index.es.js.map +1 -1
  18. package/dist/index.js +4 -4
  19. package/dist/index.js.map +1 -1
  20. package/dist/{components/DynamicDataList/helpers/generateId.d.ts → utils/GenerateId.d.ts} +1 -1
  21. package/dist/utils/GenerateId.d.ts.map +1 -0
  22. package/package.json +3 -3
  23. package/src/components/DynamicDataList/DynamicDataList.spec.tsx +2 -1
  24. package/src/components/DynamicDataList/DynamicDataList.tsx +1 -1
  25. package/src/components/Explorer/Explorer.spec.tsx +26 -16
  26. package/src/components/Explorer/Explorer.tsx +47 -28
  27. package/src/components/Explorer/NavigationExplorer/NavigationExplorer.spec.tsx +2 -2
  28. package/src/components/Explorer/SelectionExplorer/SelectionExplorer.spec.tsx +8 -32
  29. package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +34 -30
  30. package/src/components/PageHeader/PageHeader.model.ts +10 -12
  31. package/src/components/PageHeader/PageHeader.scss +7 -3
  32. package/src/components/PageHeader/PageHeader.spec.tsx +28 -86
  33. package/src/components/PageHeader/PageHeader.stories.tsx +32 -7
  34. package/src/components/PageHeader/PageHeader.tsx +50 -77
  35. package/src/components/PageHeader/{PageHeaderBulkActions/PageHeaderBulkActions.scss → PageHeaderActionsGroup/PageHeaderActionsGroup.scss} +21 -21
  36. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.spec.tsx +105 -0
  37. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.tsx +224 -0
  38. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.ts +13 -0
  39. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.tsx +30 -0
  40. package/src/components/PageHeader/index.ts +1 -1
  41. package/dist/components/DynamicDataList/helpers/generateId.d.ts.map +0 -1
  42. package/dist/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.d.ts +0 -22
  43. package/dist/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.d.ts.map +0 -1
  44. package/src/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.spec.tsx +0 -369
  45. package/src/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.tsx +0 -188
  46. /package/src/{components/DynamicDataList/helpers/generateId.ts → utils/GenerateId.ts} +0 -0
@@ -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
  };
@@ -1,90 +1,57 @@
1
1
  import clsx from 'clsx';
2
- import React, { useEffect, useState } from '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 { PageHeaderBulkActions } from './PageHeaderBulkActions/PageHeaderBulkActions';
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 bulk actions.
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
- useEffect(() => {
47
- // only perform calculation if there are bulkActions
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
- const bulkActionSpace = 1;
54
- const moreActionSpace = 1;
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
- // calculates the number of bulk action spaces available
57
- let currentAvailableActionSpace: number = Math.floor(
58
- availableWidth / ActionsWidth - bulkActionSpace,
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
- {bulkActions.length > 0 && (
102
- <PageHeaderBulkActions
103
- availableActionSpace={availableActionSpace}
104
- actions={bulkActions}
105
- openBulkActionsOnStart={openBulkActionsOnStart}
106
- onBulkActionsToggled={onBulkActionsToggled}
107
- bulkActionsDisabled={bulkActionsDisabled}
108
- />
109
- )}
110
- <div
111
- className={clsx(classes.actions)}
112
- style={{ marginLeft: actions.length > 0 ? '10px' : undefined }}
113
- ref={actionsRef}
114
- data-test-id="page-header-actions"
115
- >
116
- {actions.map((action, index) => {
117
- return <PageHeaderAction key={index} {...action} />;
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-auto-flow: column;
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
- .hasMore {
11
- width: 120px;
11
+ .actions {
12
12
  display: grid;
13
- grid-template-columns: 1fr;
14
- grid-template-rows: 1fr;
15
-
16
- position: relative;
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
- .dropDownList {
19
- width: 360px;
20
- display: grid;
21
- grid-template-columns: 1fr;
22
- grid-auto-rows: 60px;
23
- grid-gap: 1px;
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
- border-top: 2px solid white;
27
+ border-top: 2px solid white;
26
28
 
27
- position: absolute;
28
- top: $page-header-height;
29
- right: 0px;
29
+ grid-column: 1 / span 2;
30
+ place-self: end;
30
31
 
31
- background-color: white;
32
+ background-color: white;
32
33
 
33
- z-index: 2;
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
+ });
@@ -0,0 +1,224 @@
1
+ import clsx from 'clsx';
2
+ import React, { useCallback, useEffect, useState } from 'react';
3
+ import { noop } from '../../../helpers/utils';
4
+ import { useExpand } from '../../../hooks/useExpand/useExpand';
5
+ import { uuid } from '../../../utils/GenerateId';
6
+ import { ConfirmationMode } from '../../ConfirmDialog';
7
+ import { IconName } from '../../Icons';
8
+ import {
9
+ PageHeaderAction,
10
+ isPageHeaderJsAction,
11
+ } from '../PageHeaderAction/PageHeaderAction';
12
+ import {
13
+ PageHeaderActionProps,
14
+ PageHeaderActionType,
15
+ } from '../PageHeaderAction/PageHeaderAction.model';
16
+ import classes from './PageHeaderActionsGroup.scss';
17
+ import { PageHeaderActionsGroupContext } from './PageHeaderActionsGroupsContext';
18
+
19
+ export interface PageHeaderActionsGroupProps {
20
+ /** Array of Actions to be rendered (default: []) */
21
+ actions: PageHeaderActionProps[];
22
+ /** Whether group is disabled (default: false) */
23
+ disabled?: boolean;
24
+ /** Whether actions in the group are disabled or not (default: false)*/
25
+ groupActionsDisabled?: boolean;
26
+ /** CSS Class name for additional styles */
27
+ className?: string;
28
+ /** The label of the action. */
29
+ label: string;
30
+ /** Optional built in icon. This prop also accepts an img src. */
31
+ icon?: IconName | string;
32
+ /** Whether group actions are shown by default. (default: false) */
33
+ openActionsGroupOnStart?: boolean;
34
+ /** The number of actions that can be displayed in the group. (default: actions.length) */
35
+ availableActionSpace?: number;
36
+ /**
37
+ * Callback to emit when Group Actions is toggled
38
+ * The expanded state is supplied as an argument
39
+ */
40
+ onActionsGroupToggled?: (expanded: boolean) => void;
41
+ }
42
+
43
+ /**
44
+ * Used to perform group operations from the PageHeader component.
45
+ * @example
46
+ * <PageHeaderActionsGroup>
47
+ * <PageHeaderAction action={action} onActionsGroupToggled={onActionsGroupToggledHandler} />
48
+ * </PageHeaderActionsGroup>
49
+ */
50
+ export const PageHeaderActionsGroup: React.FC<PageHeaderActionsGroupProps> = ({
51
+ actions = [],
52
+ openActionsGroupOnStart = false,
53
+ label,
54
+ icon,
55
+ onActionsGroupToggled = noop,
56
+ groupActionsDisabled = false,
57
+ disabled = false,
58
+ availableActionSpace = actions.length,
59
+ className = '',
60
+ }) => {
61
+ const [uniqueId] = useState<string>(uuid());
62
+ const { isExpanded, toggleExpanded, collapse } = useExpand(
63
+ openActionsGroupOnStart,
64
+ );
65
+
66
+ const { registerCollapse, collapseAll } = React.useContext(
67
+ PageHeaderActionsGroupContext,
68
+ );
69
+
70
+ const customCollapse = useCallback(() => {
71
+ collapse();
72
+ onActionsGroupToggled(false);
73
+ }, [collapse, onActionsGroupToggled]);
74
+
75
+ useEffect(() => {
76
+ registerCollapse(customCollapse, uniqueId);
77
+ }, [customCollapse, registerCollapse, uniqueId]);
78
+
79
+ const {
80
+ isExpanded: isMoreExpanded,
81
+ expand: moreExpanded,
82
+ collapse: moreCollapse,
83
+ toggleExpanded: toggleMoreExpanded,
84
+ } = useExpand();
85
+
86
+ const [isMouseOverMore, setIsMouseOverMore] = useState<boolean>(false);
87
+ const [isConfirmOpen, setIsConfirmOpen] = useState<Record<string, boolean>>(
88
+ {},
89
+ );
90
+
91
+ const onConfirmToggled = (
92
+ isOpen: boolean,
93
+ data: { id: string; mode: ConfirmationMode },
94
+ ): void => {
95
+ setIsConfirmOpen((prevState) => {
96
+ return { ...prevState, [data.id]: isOpen };
97
+ });
98
+ };
99
+
100
+ useEffect(() => {
101
+ const hasConfirm: boolean = Object.values(isConfirmOpen).includes(true);
102
+
103
+ // open drop down
104
+ if (isMouseOverMore) {
105
+ moreExpanded();
106
+ // close drop down if no confirmation is active
107
+ } else if (!isMouseOverMore && !hasConfirm) {
108
+ moreCollapse();
109
+ // keep the drop down open if confirmation is active
110
+ } else if (!isMouseOverMore && hasConfirm) {
111
+ return;
112
+ }
113
+ }, [isConfirmOpen, isMouseOverMore, moreCollapse, moreExpanded]);
114
+
115
+ const actionsSlice =
116
+ availableActionSpace < actions.length
117
+ ? availableActionSpace - 1
118
+ : availableActionSpace;
119
+
120
+ return (
121
+ <div
122
+ className={clsx(
123
+ classes.container,
124
+ 'page-header-group-action-container',
125
+ className,
126
+ )}
127
+ onMouseLeave={() => setIsMouseOverMore(false)}
128
+ >
129
+ <PageHeaderAction
130
+ label={label}
131
+ actionType={
132
+ isExpanded
133
+ ? PageHeaderActionType.Active
134
+ : PageHeaderActionType.Context
135
+ }
136
+ icon={icon}
137
+ onClick={() => {
138
+ if (!isExpanded) {
139
+ collapseAll();
140
+ }
141
+ toggleExpanded();
142
+ onActionsGroupToggled(!isExpanded);
143
+ }}
144
+ disabled={disabled}
145
+ />
146
+ <div
147
+ style={{
148
+ maxWidth: isExpanded
149
+ ? `${Math.min(availableActionSpace, actions.length) * 121}px`
150
+ : `0px`,
151
+ opacity: isExpanded ? 1 : 0,
152
+ }}
153
+ className={classes.actions}
154
+ >
155
+ {actions.slice(0, actionsSlice).map((action, idx) =>
156
+ isPageHeaderJsAction(action) ? (
157
+ <PageHeaderAction
158
+ key={idx}
159
+ {...action}
160
+ disabled={groupActionsDisabled}
161
+ confirmationConfig={{
162
+ ...action.confirmationConfig,
163
+ onConfirmOpen: (isOpen: boolean, args: unknown) => {
164
+ onConfirmToggled(
165
+ isOpen,
166
+ args as { id: string; mode: ConfirmationMode },
167
+ );
168
+ action.confirmationConfig?.onConfirmOpen?.(isOpen, args);
169
+ },
170
+ }}
171
+ />
172
+ ) : (
173
+ <PageHeaderAction key={idx} {...action} />
174
+ ),
175
+ )}
176
+ {actions.length > availableActionSpace && (
177
+ <div onMouseEnter={() => setIsMouseOverMore(true)}>
178
+ <PageHeaderAction
179
+ label="More"
180
+ icon={IconName.Ellipsis}
181
+ actionType={
182
+ isMoreExpanded
183
+ ? PageHeaderActionType.Active
184
+ : PageHeaderActionType.Context
185
+ }
186
+ onClick={() => toggleMoreExpanded()}
187
+ />
188
+ </div>
189
+ )}
190
+ </div>
191
+ {isMoreExpanded && (
192
+ <div className={clsx(classes.dropDownList)}>
193
+ {actions.slice(actionsSlice).map((action, idx) =>
194
+ isPageHeaderJsAction(action) ? (
195
+ <PageHeaderAction
196
+ key={idx}
197
+ {...action}
198
+ disabled={groupActionsDisabled}
199
+ onClick={() => {
200
+ action.onClick();
201
+ moreCollapse();
202
+ setIsMouseOverMore(false);
203
+ }}
204
+ className={clsx(classes.dropDown)}
205
+ confirmationConfig={{
206
+ ...action.confirmationConfig,
207
+ onConfirmOpen: (isOpen, args) => {
208
+ onConfirmToggled(
209
+ isOpen,
210
+ args as { id: string; mode: ConfirmationMode },
211
+ );
212
+ action.confirmationConfig?.onConfirmOpen?.(isOpen, args);
213
+ },
214
+ }}
215
+ />
216
+ ) : (
217
+ <PageHeaderAction key={idx} {...action} />
218
+ ),
219
+ )}
220
+ </div>
221
+ )}
222
+ </div>
223
+ );
224
+ };
@@ -0,0 +1,13 @@
1
+ import { createContext } from 'react';
2
+ import { noop } from '../../../helpers/utils';
3
+
4
+ export interface PageHeaderActionsGroupContextType {
5
+ registerCollapse: (readonly: () => void, uuid: string) => void;
6
+ collapseAll: () => void;
7
+ }
8
+
9
+ export const PageHeaderActionsGroupContext =
10
+ createContext<PageHeaderActionsGroupContextType>({
11
+ registerCollapse: noop,
12
+ collapseAll: noop,
13
+ });
@@ -0,0 +1,30 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import { PageHeaderActionsGroupContext } from './PageHeaderActionsGroupsContext';
3
+
4
+ type CollapseAction = Record<string, () => void>;
5
+
6
+ export const PageHeaderActionsGroupContextProvider: React.FC = ({
7
+ children,
8
+ }) => {
9
+ const [registeredCollapses, setRegisteredCollapses] =
10
+ useState<CollapseAction>({});
11
+
12
+ const registerCollapse = useCallback(
13
+ (collapse: () => void, uuid: string): void => {
14
+ setRegisteredCollapses((prev) => ({ ...prev, [uuid]: collapse }));
15
+ },
16
+ [],
17
+ );
18
+
19
+ const collapseAll = useCallback(() => {
20
+ Object.values(registeredCollapses).forEach((collapse) => collapse());
21
+ }, [registeredCollapses]);
22
+
23
+ return (
24
+ <PageHeaderActionsGroupContext.Provider
25
+ value={{ registerCollapse, collapseAll }}
26
+ >
27
+ {children}
28
+ </PageHeaderActionsGroupContext.Provider>
29
+ );
30
+ };
@@ -1,5 +1,5 @@
1
1
  export { PageHeader } from './PageHeader';
2
- export { PageHeaderProps } from './PageHeader.model';
2
+ export { PageHeaderActionItemProps, PageHeaderProps } from './PageHeader.model';
3
3
  export {
4
4
  PageHeaderAction,
5
5
  PageHeaderActionProps,