@axinom/mosaic-ui 0.75.0-rc.5 → 0.77.0

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 (183) hide show
  1. package/dist/THIRD-PARTY-LICENSES.txt +1296 -0
  2. package/dist/cjs/components/ActionBar/ActionBar.d.ts +26 -0
  3. package/dist/cjs/components/ActionBar/ActionBar.d.ts.map +1 -0
  4. package/dist/cjs/components/ActionBar/ActionBar.model.d.ts +49 -0
  5. package/dist/cjs/components/ActionBar/ActionBar.model.d.ts.map +1 -0
  6. package/dist/cjs/components/ActionBar/ActionBarButton/ActionBarButton.d.ts +22 -0
  7. package/dist/cjs/components/ActionBar/ActionBarButton/ActionBarButton.d.ts.map +1 -0
  8. package/dist/cjs/components/ActionBar/helpers.d.ts +22 -0
  9. package/dist/cjs/components/ActionBar/helpers.d.ts.map +1 -0
  10. package/dist/cjs/components/ActionBar/index.d.ts +3 -0
  11. package/dist/cjs/components/ActionBar/index.d.ts.map +1 -0
  12. package/dist/cjs/components/Actions/Action/Action.d.ts.map +1 -1
  13. package/dist/cjs/components/ConfirmDialog/index.d.ts +1 -0
  14. package/dist/cjs/components/ConfirmDialog/index.d.ts.map +1 -1
  15. package/dist/cjs/components/ConfirmDialog/useConfirmableAction.d.ts +55 -0
  16. package/dist/cjs/components/ConfirmDialog/useConfirmableAction.d.ts.map +1 -0
  17. package/dist/cjs/components/Explorer/BulkEdit/useBulkEdit.d.ts.map +1 -1
  18. package/dist/cjs/components/Explorer/Explorer.d.ts.map +1 -1
  19. package/dist/cjs/components/Explorer/QuickEdit/useQuickEdit.d.ts +14 -3
  20. package/dist/cjs/components/Explorer/QuickEdit/useQuickEdit.d.ts.map +1 -1
  21. package/dist/cjs/components/Explorer/helpers/useActions.d.ts +15 -4
  22. package/dist/cjs/components/Explorer/helpers/useActions.d.ts.map +1 -1
  23. package/dist/cjs/components/Explorer/helpers/useSubtitle.d.ts.map +1 -1
  24. package/dist/cjs/components/List/List.d.ts +17 -3
  25. package/dist/cjs/components/List/List.d.ts.map +1 -1
  26. package/dist/cjs/components/List/List.model.d.ts +5 -1
  27. package/dist/cjs/components/List/List.model.d.ts.map +1 -1
  28. package/dist/cjs/components/List/ListHeader/ListHeader.d.ts +10 -1
  29. package/dist/cjs/components/List/ListHeader/ListHeader.d.ts.map +1 -1
  30. package/dist/cjs/components/List/ListRow/ListRow.d.ts +16 -5
  31. package/dist/cjs/components/List/ListRow/ListRow.d.ts.map +1 -1
  32. package/dist/cjs/components/List/ListRowRenderer/ListRowRenderer.d.ts.map +1 -1
  33. package/dist/cjs/components/List/helpers.d.ts +11 -3
  34. package/dist/cjs/components/List/helpers.d.ts.map +1 -1
  35. package/dist/cjs/components/List/useColumnsSize.d.ts +4 -2
  36. package/dist/cjs/components/List/useColumnsSize.d.ts.map +1 -1
  37. package/dist/cjs/components/PageHeader/PageHeader.d.ts +4 -1
  38. package/dist/cjs/components/PageHeader/PageHeader.d.ts.map +1 -1
  39. package/dist/cjs/components/PageHeader/PageHeader.model.d.ts +37 -1
  40. package/dist/cjs/components/PageHeader/PageHeader.model.d.ts.map +1 -1
  41. package/dist/cjs/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts +3 -0
  42. package/dist/cjs/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts.map +1 -1
  43. package/dist/cjs/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts +8 -0
  44. package/dist/cjs/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts.map +1 -1
  45. package/dist/cjs/components/PageHeader/helpers.d.ts +9 -0
  46. package/dist/cjs/components/PageHeader/helpers.d.ts.map +1 -0
  47. package/dist/cjs/components/index.d.ts +1 -0
  48. package/dist/cjs/components/index.d.ts.map +1 -1
  49. package/dist/cjs/hooks/index.d.ts +1 -0
  50. package/dist/cjs/hooks/index.d.ts.map +1 -1
  51. package/dist/cjs/hooks/useElementWidthObserver/useElementWidthObserver.d.ts.map +1 -0
  52. package/dist/cjs/index.js +4 -4
  53. package/dist/cjs/index.js.map +1 -1
  54. package/dist/esm/components/ActionBar/ActionBar.d.ts +26 -0
  55. package/dist/esm/components/ActionBar/ActionBar.d.ts.map +1 -0
  56. package/dist/esm/components/ActionBar/ActionBar.model.d.ts +49 -0
  57. package/dist/esm/components/ActionBar/ActionBar.model.d.ts.map +1 -0
  58. package/dist/esm/components/ActionBar/ActionBarButton/ActionBarButton.d.ts +22 -0
  59. package/dist/esm/components/ActionBar/ActionBarButton/ActionBarButton.d.ts.map +1 -0
  60. package/dist/esm/components/ActionBar/helpers.d.ts +22 -0
  61. package/dist/esm/components/ActionBar/helpers.d.ts.map +1 -0
  62. package/dist/esm/components/ActionBar/index.d.ts +3 -0
  63. package/dist/esm/components/ActionBar/index.d.ts.map +1 -0
  64. package/dist/esm/components/Actions/Action/Action.d.ts.map +1 -1
  65. package/dist/esm/components/ConfirmDialog/index.d.ts +1 -0
  66. package/dist/esm/components/ConfirmDialog/index.d.ts.map +1 -1
  67. package/dist/esm/components/ConfirmDialog/useConfirmableAction.d.ts +55 -0
  68. package/dist/esm/components/ConfirmDialog/useConfirmableAction.d.ts.map +1 -0
  69. package/dist/esm/components/Explorer/BulkEdit/useBulkEdit.d.ts.map +1 -1
  70. package/dist/esm/components/Explorer/Explorer.d.ts.map +1 -1
  71. package/dist/esm/components/Explorer/QuickEdit/useQuickEdit.d.ts +14 -3
  72. package/dist/esm/components/Explorer/QuickEdit/useQuickEdit.d.ts.map +1 -1
  73. package/dist/esm/components/Explorer/helpers/useActions.d.ts +15 -4
  74. package/dist/esm/components/Explorer/helpers/useActions.d.ts.map +1 -1
  75. package/dist/esm/components/Explorer/helpers/useSubtitle.d.ts.map +1 -1
  76. package/dist/esm/components/List/List.d.ts +17 -3
  77. package/dist/esm/components/List/List.d.ts.map +1 -1
  78. package/dist/esm/components/List/List.model.d.ts +5 -1
  79. package/dist/esm/components/List/List.model.d.ts.map +1 -1
  80. package/dist/esm/components/List/ListHeader/ListHeader.d.ts +10 -1
  81. package/dist/esm/components/List/ListHeader/ListHeader.d.ts.map +1 -1
  82. package/dist/esm/components/List/ListRow/ListRow.d.ts +16 -5
  83. package/dist/esm/components/List/ListRow/ListRow.d.ts.map +1 -1
  84. package/dist/esm/components/List/ListRowRenderer/ListRowRenderer.d.ts.map +1 -1
  85. package/dist/esm/components/List/helpers.d.ts +11 -3
  86. package/dist/esm/components/List/helpers.d.ts.map +1 -1
  87. package/dist/esm/components/List/useColumnsSize.d.ts +4 -2
  88. package/dist/esm/components/List/useColumnsSize.d.ts.map +1 -1
  89. package/dist/esm/components/PageHeader/PageHeader.d.ts +4 -1
  90. package/dist/esm/components/PageHeader/PageHeader.d.ts.map +1 -1
  91. package/dist/esm/components/PageHeader/PageHeader.model.d.ts +37 -1
  92. package/dist/esm/components/PageHeader/PageHeader.model.d.ts.map +1 -1
  93. package/dist/esm/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts +3 -0
  94. package/dist/esm/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts.map +1 -1
  95. package/dist/esm/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts +8 -0
  96. package/dist/esm/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts.map +1 -1
  97. package/dist/esm/components/PageHeader/helpers.d.ts +9 -0
  98. package/dist/esm/components/PageHeader/helpers.d.ts.map +1 -0
  99. package/dist/esm/components/index.d.ts +1 -0
  100. package/dist/esm/components/index.d.ts.map +1 -1
  101. package/dist/esm/hooks/index.d.ts +1 -0
  102. package/dist/esm/hooks/index.d.ts.map +1 -1
  103. package/dist/esm/hooks/useElementWidthObserver/useElementWidthObserver.d.ts.map +1 -0
  104. package/dist/esm/index.js +4 -4
  105. package/dist/esm/index.js.map +1 -1
  106. package/package.json +7 -3
  107. package/src/components/ActionBar/ActionBar.model.ts +61 -0
  108. package/src/components/ActionBar/ActionBar.module.scss +64 -0
  109. package/src/components/ActionBar/ActionBar.spec.tsx +398 -0
  110. package/src/components/ActionBar/ActionBar.stories.tsx +205 -0
  111. package/src/components/ActionBar/ActionBar.tsx +170 -0
  112. package/src/components/ActionBar/ActionBarButton/ActionBarButton.module.scss +140 -0
  113. package/src/components/ActionBar/ActionBarButton/ActionBarButton.tsx +193 -0
  114. package/src/components/ActionBar/helpers.spec.ts +41 -0
  115. package/src/components/ActionBar/helpers.ts +54 -0
  116. package/src/components/ActionBar/index.ts +2 -0
  117. package/src/components/Actions/Action/Action.spec.tsx +64 -2
  118. package/src/components/Actions/Action/Action.tsx +6 -1
  119. package/src/components/ConfirmDialog/index.ts +1 -0
  120. package/src/components/ConfirmDialog/useConfirmableAction.tsx +159 -0
  121. package/src/components/EmptyStation/EmptyStation.stories.tsx +97 -0
  122. package/src/components/Explorer/BulkEdit/useBulkEdit.tsx +2 -0
  123. package/src/components/Explorer/Explorer.spec.tsx +97 -8
  124. package/src/components/Explorer/Explorer.tsx +26 -13
  125. package/src/components/Explorer/NavigationExplorer/NavigationExplorer.stories.tsx +124 -1
  126. package/src/components/Explorer/QuickEdit/useQuickEdit.spec.tsx +94 -50
  127. package/src/components/Explorer/QuickEdit/useQuickEdit.tsx +101 -42
  128. package/src/components/Explorer/SelectionExplorer/SelectionExplorer.spec.tsx +46 -13
  129. package/src/components/Explorer/helpers/useActions.ts +125 -73
  130. package/src/components/Explorer/helpers/useSubtitle.spec.tsx +33 -23
  131. package/src/components/Explorer/helpers/useSubtitle.tsx +18 -26
  132. package/src/components/List/List.model.ts +5 -1
  133. package/src/components/List/List.module.scss +25 -6
  134. package/src/components/List/List.spec.tsx +498 -1
  135. package/src/components/List/List.stories.tsx +137 -8
  136. package/src/components/List/List.tsx +305 -194
  137. package/src/components/List/ListHeader/ListHeader.module.scss +14 -0
  138. package/src/components/List/ListHeader/ListHeader.spec.tsx +1 -0
  139. package/src/components/List/ListHeader/ListHeader.tsx +25 -8
  140. package/src/components/List/ListRow/ListRow.module.scss +1 -1
  141. package/src/components/List/ListRow/ListRow.spec.tsx +109 -22
  142. package/src/components/List/ListRow/ListRow.tsx +52 -13
  143. package/src/components/List/ListRowRenderer/ListRowRenderer.tsx +28 -22
  144. package/src/components/List/helpers.spec.ts +29 -0
  145. package/src/components/List/helpers.ts +17 -9
  146. package/src/components/List/useColumnsSize.ts +17 -7
  147. package/src/components/PageHeader/PageHeader.model.ts +38 -1
  148. package/src/components/PageHeader/PageHeader.module.scss +39 -18
  149. package/src/components/PageHeader/PageHeader.spec.tsx +407 -18
  150. package/src/components/PageHeader/PageHeader.stories.tsx +95 -12
  151. package/src/components/PageHeader/PageHeader.tsx +89 -38
  152. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.model.ts +8 -0
  153. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.module.scss +50 -42
  154. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.spec.tsx +193 -1
  155. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.stories.tsx +36 -0
  156. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.tsx +81 -130
  157. package/src/components/PageHeader/helpers.spec.ts +60 -0
  158. package/src/components/PageHeader/helpers.ts +36 -0
  159. package/src/components/index.ts +1 -0
  160. package/src/hooks/index.ts +1 -0
  161. package/src/styles/variables.scss +41 -7
  162. package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts +0 -35
  163. package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +0 -1
  164. package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts +0 -6
  165. package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts.map +0 -1
  166. package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts +0 -3
  167. package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts.map +0 -1
  168. package/dist/cjs/components/PageHeader/helpers/useElementWidthObserver.d.ts.map +0 -1
  169. package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts +0 -35
  170. package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +0 -1
  171. package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts +0 -6
  172. package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts.map +0 -1
  173. package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts +0 -3
  174. package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts.map +0 -1
  175. package/dist/esm/components/PageHeader/helpers/useElementWidthObserver.d.ts.map +0 -1
  176. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.module.scss +0 -77
  177. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.spec.tsx +0 -122
  178. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.tsx +0 -235
  179. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.ts +0 -15
  180. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.tsx +0 -37
  181. /package/dist/cjs/{components/PageHeader/helpers → hooks/useElementWidthObserver}/useElementWidthObserver.d.ts +0 -0
  182. /package/dist/esm/{components/PageHeader/helpers → hooks/useElementWidthObserver}/useElementWidthObserver.d.ts +0 -0
  183. /package/src/{components/PageHeader/helpers → hooks/useElementWidthObserver}/useElementWidthObserver.tsx +0 -0
@@ -1,4 +1,6 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react';
2
+ import React from 'react';
3
+ import { MemoryRouter } from 'react-router-dom';
2
4
  import { action } from 'storybook/actions';
3
5
  import { IconName } from '../Icons';
4
6
  import { PageHeader } from './PageHeader';
@@ -78,9 +80,30 @@ const bulkHeaderActions: PageHeaderActionProps[] = [
78
80
  },
79
81
  ];
80
82
 
83
+ const exportHeaderActions: PageHeaderActionProps[] = [
84
+ {
85
+ label: 'Export as CSV',
86
+ icon: IconName.File,
87
+ onClick: action('onActionSelected'),
88
+ },
89
+ {
90
+ label: 'Export Settings',
91
+ path: '/export-settings',
92
+ tag: 'NEW',
93
+ },
94
+ ];
95
+
81
96
  const meta: Meta<typeof PageHeader> = {
82
97
  component: PageHeader,
83
98
  title: 'Other Components/PageHeader',
99
+ decorators: [
100
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
101
+ (Story) => (
102
+ <MemoryRouter>
103
+ <Story />
104
+ </MemoryRouter>
105
+ ),
106
+ ],
84
107
  };
85
108
  export default meta;
86
109
 
@@ -97,11 +120,22 @@ export const Titles: StoryObj<typeof PageHeader> = {
97
120
  args: { title: 'Title', subtitle: 'Subtitle' },
98
121
  };
99
122
 
123
+ export const TitleOnly: StoryObj<typeof PageHeader> = {
124
+ parameters: {
125
+ docs: {
126
+ description: {
127
+ story: 'Without a subtitle, the title is centered vertically.',
128
+ },
129
+ },
130
+ },
131
+ args: { title: 'Episodes' },
132
+ };
133
+
100
134
  export const Actions: StoryObj<typeof PageHeader> = {
101
135
  parameters: {
102
136
  docs: {
103
137
  description: {
104
- story: 'An array of standard page header actions can be passed in.',
138
+ story: `An array of standard page header actions can be passed in. An action shows its icon, and its label as a tooltip.`,
105
139
  },
106
140
  },
107
141
  },
@@ -112,12 +146,59 @@ export const Actions: StoryObj<typeof PageHeader> = {
112
146
  },
113
147
  };
114
148
 
149
+ export const ActionsWithoutIcons: StoryObj<typeof PageHeader> = {
150
+ parameters: {
151
+ docs: {
152
+ description: {
153
+ story: `An action without an icon shows its label instead, which is cut after four lines.
154
+ Spacers separate groups of actions.`,
155
+ },
156
+ },
157
+ },
158
+ args: {
159
+ title: 'Episodes',
160
+ actions: [
161
+ {
162
+ label: 'Create Snapshot',
163
+ kind: 'action',
164
+ onClick: action('onActionSelected'),
165
+ },
166
+ {
167
+ label: 'Create Snapshot and Publish all Episodes',
168
+ kind: 'action',
169
+ onClick: action('onActionSelected'),
170
+ },
171
+ { kind: 'spacer' },
172
+ {
173
+ label: 'Bulk Actions',
174
+ icon: IconName.Bulk,
175
+ kind: 'group',
176
+ actions: bulkHeaderActions,
177
+ },
178
+ { kind: 'spacer' },
179
+ {
180
+ label: 'Quick View',
181
+ icon: IconName.QuickEdit,
182
+ kind: 'action',
183
+ onClick: action('onActionSelected'),
184
+ },
185
+ { kind: 'spacer' },
186
+ {
187
+ label: 'New',
188
+ icon: IconName.Plus,
189
+ kind: 'action',
190
+ path: '/new',
191
+ },
192
+ ],
193
+ },
194
+ };
195
+
115
196
  export const ActionsGroup: StoryObj<typeof PageHeader> = {
116
197
  parameters: {
117
198
  docs: {
118
199
  description: {
119
- story: `Bulk actions is available when an array of actions is passed in as a prop. The actions themselves will try to fit into any available space.
120
- Those that cannot fit, will be rendered under the 'More' action as a child of the drop down list.`,
200
+ story: `An actions group renders a single action on the page header. Clicking it opens an action bar below the page header, holding the actions of the group, and clicking it again closes the bar.
201
+ The bar can show a label of its own through 'actionBarLabel'. Actions that do not fit the available width move into an overflow menu.`,
121
202
  },
122
203
  },
123
204
  },
@@ -130,6 +211,8 @@ export const ActionsGroup: StoryObj<typeof PageHeader> = {
130
211
  icon: IconName.Bulk,
131
212
  kind: 'group',
132
213
  actions: bulkHeaderActions,
214
+ actionBarLabel: 'Selected 3',
215
+ openActionsGroupOnStart: true,
133
216
  },
134
217
  ],
135
218
  },
@@ -140,8 +223,8 @@ export const All: StoryObj<typeof PageHeader> = {
140
223
  parameters: {
141
224
  docs: {
142
225
  description: {
143
- story:
144
- 'When actions and bulk actions are present, a spacer will be created in-between to help differentiate them.',
226
+ story: `When actions and bulk actions are present, a spacer will be created in-between to help differentiate them.
227
+ Only one actions group can be open at a time, so opening a group closes the one that is open. Groups can hold navigation actions as well.`,
145
228
  },
146
229
  },
147
230
  },
@@ -156,17 +239,17 @@ export const All: StoryObj<typeof PageHeader> = {
156
239
  icon: IconName.Bulk,
157
240
  kind: 'group',
158
241
  actions: bulkHeaderActions,
159
- onActionsGroupToggled: async (): Promise<void> => {
160
- action('onBulkActions1Toggled');
242
+ onActionsGroupToggled: async (expanded: boolean): Promise<void> => {
243
+ action('onBulkActionsToggled')(expanded);
161
244
  },
162
245
  },
163
246
  {
164
- label: 'Bulk Actions 2',
165
- icon: IconName.Bulk,
247
+ label: 'Export',
248
+ icon: IconName.File,
166
249
  kind: 'group',
167
- actions: [bulkHeaderActions[0]],
168
- onActionsGroupToggled: async (): Promise<void> => {
169
- action('onBulkActions2Toggled');
250
+ actions: exportHeaderActions,
251
+ onActionsGroupToggled: async (expanded: boolean): Promise<void> => {
252
+ action('onExportToggled')(expanded);
170
253
  },
171
254
  },
172
255
  ],
@@ -1,23 +1,37 @@
1
1
  import clsx from 'clsx';
2
- import React, { useEffect, useState } from 'react';
2
+ import React, { useState } from 'react';
3
3
  import { useTabTitle } from '../../hooks/useTabTitle/useTabTitle';
4
- import type { PageHeaderProps } from './PageHeader.model';
4
+ import { ActionBar } from '../ActionBar';
5
+ import type {
6
+ PageHeaderActionItemProps,
7
+ PageHeaderProps,
8
+ } from './PageHeader.model';
5
9
  import classes from './PageHeader.module.scss';
6
10
  import type { PageHeaderActionProps } from './PageHeaderAction';
7
11
  import { PageHeaderAction } from './PageHeaderAction/PageHeaderAction';
8
- import { PageHeaderActionsGroup } from './PageHeaderActionsGroup/PageHeaderActionsGroup';
9
- import { PageHeaderActionsGroupContextProvider } from './PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider';
10
- import { useElementWidthObserver } from './helpers/useElementWidthObserver';
12
+ import { toActionBarAction } from './helpers';
13
+
14
+ type PageHeaderActionsGroupItem = Extract<
15
+ PageHeaderActionItemProps,
16
+ { kind: 'group' }
17
+ >;
18
+
19
+ const isActionsGroup = (
20
+ action: PageHeaderActionItemProps,
21
+ ): action is PageHeaderActionsGroupItem => action.kind === 'group';
11
22
 
12
23
  /**
13
24
  * Primary header for stations. Accepts a title, subtitle, actions, and actions groups.
25
+ *
26
+ * An actions group renders a single action, which opens an action bar holding
27
+ * the actions of the group below the header. Only one group can be open at a time.
14
28
  * @example
15
29
  * <PageHeader title="title" subtitle="subtitle" actions={[
16
30
  * { label: 'Undo', icon: IconName.Undo, kind: 'action', onClick: onActionSelected },
17
31
  * { label: 'Cancel', icon: IconName.X, kind: 'action' onClick: onActionSelected },
18
32
  * { kind: 'spacer' },
19
33
  * { label: 'Actions Group', kind: 'group', actions: [
20
- * { label: 'Action 1', actionType: PageHeaderActionType.Context, icon: IconName.Delete, confirmationMode: 'Advanced', onClick: onActionSelected },
34
+ * { label: 'Action 1', icon: IconName.Delete, confirmationMode: 'Advanced', onClick: onActionSelected },
21
35
  * ]}
22
36
  * ]} />
23
37
  */
@@ -29,23 +43,46 @@ export const PageHeader: React.FC<PageHeaderProps> = ({
29
43
  setTabTitle = true,
30
44
  }) => {
31
45
  useTabTitle(title, setTabTitle);
32
- const { width, ref } = useElementWidthObserver<HTMLDivElement>();
33
46
 
34
- const [availableActionSpace, setAvailableActionSpace] = useState<number>(0);
47
+ /**
48
+ * Label of the open group, or `null` once the groups were closed. Stays
49
+ * `undefined` until a group is toggled, in which case a group asking to be
50
+ * open on start is the open one.
51
+ */
52
+ const [toggledGroupLabel, setToggledGroupLabel] = useState<string | null>();
53
+
54
+ const groups = actions.filter(isActionsGroup);
55
+
56
+ const openGroup =
57
+ toggledGroupLabel === undefined
58
+ ? groups.find((group) => group.openActionsGroupOnStart)
59
+ : groups.find((group) => group.label === toggledGroupLabel);
60
+
61
+ const toggleGroup = async (
62
+ group: PageHeaderActionsGroupItem,
63
+ ): Promise<void> => {
64
+ const isOpening = group !== openGroup;
65
+
66
+ try {
67
+ // Only one group can be open, so the open one is closed first.
68
+ if (isOpening && openGroup !== undefined) {
69
+ await openGroup.onActionsGroupToggled?.(false);
70
+ setToggledGroupLabel(null);
71
+ }
35
72
 
36
- useEffect(() => {
37
- // Use up to 75% of the container width for actions
38
- const maxActionsWidth = width * 0.75;
73
+ await group.onActionsGroupToggled?.(isOpening);
74
+ } catch {
75
+ // A handler rejecting aborts the toggle.
76
+ return;
77
+ }
39
78
 
40
- // Each action is 120px wide
41
- setAvailableActionSpace(Math.floor(maxActionsWidth / 120 - actions.length));
42
- }, [width, actions.length]);
79
+ setToggledGroupLabel(isOpening ? group.label : null);
80
+ };
43
81
 
44
82
  return (
45
83
  <div
46
84
  className={clsx(classes.container, 'page-header-container', className)}
47
85
  data-test-id="page-header"
48
- ref={ref}
49
86
  >
50
87
  <div className={clsx(classes.titles)}>
51
88
  <div
@@ -64,30 +101,44 @@ export const PageHeader: React.FC<PageHeaderProps> = ({
64
101
  </div>
65
102
  </div>
66
103
  <div className={classes.actions} data-test-id="page-header-actions">
67
- <PageHeaderActionsGroupContextProvider>
68
- {actions.map((action, index) => {
69
- switch (action.kind) {
70
- case 'group':
71
- return (
72
- <PageHeaderActionsGroup
73
- key={index}
74
- {...action}
75
- availableActionSpace={availableActionSpace}
76
- />
77
- );
78
- case 'spacer':
79
- return <div key={index} className={classes.spacer} />;
80
- case 'action':
81
- return (
82
- <PageHeaderAction
83
- key={index}
84
- {...(action as PageHeaderActionProps)}
85
- />
86
- );
87
- }
88
- })}
89
- </PageHeaderActionsGroupContextProvider>
104
+ {actions.map((action, index) => {
105
+ switch (action.kind) {
106
+ case 'group':
107
+ return (
108
+ <PageHeaderAction
109
+ key={index}
110
+ label={action.label}
111
+ icon={action.icon}
112
+ disabled={action.disabled}
113
+ className={action.className}
114
+ toggled={action === openGroup}
115
+ onClick={() => toggleGroup(action)}
116
+ />
117
+ );
118
+ case 'spacer':
119
+ return <div key={index} className={classes.spacer} />;
120
+ case 'action':
121
+ return (
122
+ <PageHeaderAction
123
+ key={index}
124
+ {...(action as PageHeaderActionProps)}
125
+ />
126
+ );
127
+ }
128
+ })}
90
129
  </div>
130
+ {openGroup !== undefined && (
131
+ <ActionBar
132
+ // Each group gets a bar of its own, even if they hold the same actions.
133
+ key={openGroup.label}
134
+ className={classes.actionBar}
135
+ label={openGroup.actionBarLabel}
136
+ actions={openGroup.actions.map((action) => ({
137
+ ...toActionBarAction(action),
138
+ disabled: openGroup.groupActionsDisabled || action.disabled,
139
+ }))}
140
+ />
141
+ )}
91
142
  </div>
92
143
  );
93
144
  };
@@ -22,6 +22,14 @@ export interface PageHeaderJsActionProps
22
22
  * Changes the background color to the corresponding action type. (default: 'Context')
23
23
  */
24
24
  actionType?: PageHeaderActionType;
25
+ /**
26
+ * Whether the action is toggled on. When `true`, the action renders in its
27
+ * toggled (pressed) state and `actionType` is ignored. When `false` or
28
+ * omitted, the action renders according to `actionType`.
29
+ * Set this to make the action a toggle button rather than a one-shot action.
30
+ * (default: undefined)
31
+ */
32
+ toggled?: boolean;
25
33
  }
26
34
 
27
35
  export interface PageHeaderNavigationActionProps
@@ -6,11 +6,15 @@
6
6
  @include boxSizing;
7
7
 
8
8
  height: $page-header-height;
9
- width: 120px;
9
+ width: var(--page-header-action-width, $page-header-action-width);
10
+
11
+ // Holds either the icon or the label, centered. The hidden label and the tag
12
+ // are laid over the action.
13
+ position: relative;
14
+ overflow: hidden;
10
15
 
11
16
  display: grid;
12
- grid-template-columns: 1fr;
13
- grid-template-rows: 1fr 1fr;
17
+ place-items: center;
14
18
 
15
19
  color: var(--page-header-action-color, $page-header-action-color);
16
20
  background-color: var(
@@ -20,30 +24,52 @@
20
24
  cursor: pointer;
21
25
  user-select: none;
22
26
 
23
- .label,
24
- .icon {
25
- display: grid;
26
- grid-template: 1fr / 1fr;
27
- place-items: center;
28
- }
29
-
30
27
  .label {
31
- place-items: end;
32
- justify-items: center;
33
- padding-bottom: 10px;
28
+ // Long labels wrap onto further rows and are cut after the fourth one. The
29
+ // wrapping is set explicitly, as `all: unset` would inherit a `nowrap` of
30
+ // whatever the header is rendered in.
31
+ display: -webkit-box;
32
+ -webkit-box-orient: vertical;
33
+ -webkit-line-clamp: 4;
34
+ overflow: hidden;
35
+ white-space: normal;
36
+ overflow-wrap: anywhere;
34
37
 
35
- font-size: 14px;
38
+ // Chrome centers the cut row without its ellipsis, and appends the
39
+ // ellipsis after it, which can then reach up to half its width past the
40
+ // row. The padding leaves room for it, as overflow is only cut at the edge
41
+ // of the padding.
42
+ width: 100%;
43
+ padding: 0 6px;
44
+
45
+ font-size: var(
46
+ --page-header-action-font-size,
47
+ $page-header-action-font-size
48
+ );
36
49
  line-height: 1.1;
37
50
  text-align: center;
38
51
  }
39
52
 
53
+ // Transparent rather than removed, so that it still names the action and
54
+ // takes the clicks on it, e.g. of tests that find an action by its label.
55
+ .hiddenLabel {
56
+ position: absolute;
57
+ inset: 0;
58
+
59
+ opacity: 0;
60
+ }
61
+
40
62
  .icon {
41
- justify-items: center;
42
- padding-top: 10px;
63
+ display: grid;
64
+ place-items: center;
43
65
  }
44
66
 
45
67
  .icon > * {
46
- height: 40px;
68
+ height: var(--page-header-action-icon-size, $page-header-action-icon-size);
69
+ max-width: var(
70
+ --page-header-action-icon-size,
71
+ $page-header-action-icon-size
72
+ );
47
73
  }
48
74
 
49
75
  &:hover {
@@ -118,23 +144,10 @@
118
144
  }
119
145
 
120
146
  &:hover {
121
- color: var(--page-header-action-color, $page-header-action-color);
122
-
123
147
  background-color: var(
124
- --page-header-action-context-hover-background-color,
125
- $page-header-action-context-hover-background-color
148
+ --page-header-action-active-hover-background-color,
149
+ $page-header-action-active-hover-background-color
126
150
  );
127
-
128
- .pageHeaderActionsIcons {
129
- polygon,
130
- path,
131
- circle {
132
- stroke: var(
133
- --page-header-action-icon-color,
134
- $page-header-action-icon-color
135
- );
136
- }
137
- }
138
151
  }
139
152
  }
140
153
 
@@ -164,12 +177,12 @@
164
177
  color: var(--actions-disabled-fg-color, $actions-disabled-fg-color);
165
178
  }
166
179
 
180
+ // A ribbon across the top right corner of the action.
167
181
  .tag {
168
182
  position: absolute;
169
- transform: rotate(45deg) translateX(53px) translateY(-40px);
170
- transform-origin: center;
171
- top: 0;
172
- left: 0;
183
+ top: 6px;
184
+ right: -22px;
185
+ transform: rotate(45deg);
173
186
  display: grid;
174
187
  background-color: var(
175
188
  --page-header-action-tag-background-color,
@@ -180,15 +193,10 @@
180
193
  $page-header-action-tag-font-size
181
194
  );
182
195
  font-weight: bold;
183
- width: 75px;
196
+ width: 70px;
184
197
  height: 14px;
185
198
  align-items: center;
186
199
  text-align: center;
187
200
  z-index: 0;
188
201
  }
189
-
190
- &:has(.tag) {
191
- position: relative;
192
- overflow: hidden;
193
- }
194
202
  }