@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.
- package/dist/THIRD-PARTY-LICENSES.txt +1296 -0
- package/dist/cjs/components/ActionBar/ActionBar.d.ts +26 -0
- package/dist/cjs/components/ActionBar/ActionBar.d.ts.map +1 -0
- package/dist/cjs/components/ActionBar/ActionBar.model.d.ts +49 -0
- package/dist/cjs/components/ActionBar/ActionBar.model.d.ts.map +1 -0
- package/dist/cjs/components/ActionBar/ActionBarButton/ActionBarButton.d.ts +22 -0
- package/dist/cjs/components/ActionBar/ActionBarButton/ActionBarButton.d.ts.map +1 -0
- package/dist/cjs/components/ActionBar/helpers.d.ts +22 -0
- package/dist/cjs/components/ActionBar/helpers.d.ts.map +1 -0
- package/dist/cjs/components/ActionBar/index.d.ts +3 -0
- package/dist/cjs/components/ActionBar/index.d.ts.map +1 -0
- package/dist/cjs/components/Actions/Action/Action.d.ts.map +1 -1
- package/dist/cjs/components/ConfirmDialog/index.d.ts +1 -0
- package/dist/cjs/components/ConfirmDialog/index.d.ts.map +1 -1
- package/dist/cjs/components/ConfirmDialog/useConfirmableAction.d.ts +55 -0
- package/dist/cjs/components/ConfirmDialog/useConfirmableAction.d.ts.map +1 -0
- package/dist/cjs/components/Explorer/BulkEdit/useBulkEdit.d.ts.map +1 -1
- package/dist/cjs/components/Explorer/Explorer.d.ts.map +1 -1
- package/dist/cjs/components/Explorer/QuickEdit/useQuickEdit.d.ts +14 -3
- package/dist/cjs/components/Explorer/QuickEdit/useQuickEdit.d.ts.map +1 -1
- package/dist/cjs/components/Explorer/helpers/useActions.d.ts +15 -4
- package/dist/cjs/components/Explorer/helpers/useActions.d.ts.map +1 -1
- package/dist/cjs/components/Explorer/helpers/useSubtitle.d.ts.map +1 -1
- package/dist/cjs/components/List/List.d.ts +17 -3
- package/dist/cjs/components/List/List.d.ts.map +1 -1
- package/dist/cjs/components/List/List.model.d.ts +5 -1
- package/dist/cjs/components/List/List.model.d.ts.map +1 -1
- package/dist/cjs/components/List/ListHeader/ListHeader.d.ts +10 -1
- package/dist/cjs/components/List/ListHeader/ListHeader.d.ts.map +1 -1
- package/dist/cjs/components/List/ListRow/ListRow.d.ts +16 -5
- package/dist/cjs/components/List/ListRow/ListRow.d.ts.map +1 -1
- package/dist/cjs/components/List/ListRowRenderer/ListRowRenderer.d.ts.map +1 -1
- package/dist/cjs/components/List/helpers.d.ts +11 -3
- package/dist/cjs/components/List/helpers.d.ts.map +1 -1
- package/dist/cjs/components/List/useColumnsSize.d.ts +4 -2
- package/dist/cjs/components/List/useColumnsSize.d.ts.map +1 -1
- package/dist/cjs/components/PageHeader/PageHeader.d.ts +4 -1
- package/dist/cjs/components/PageHeader/PageHeader.d.ts.map +1 -1
- package/dist/cjs/components/PageHeader/PageHeader.model.d.ts +37 -1
- package/dist/cjs/components/PageHeader/PageHeader.model.d.ts.map +1 -1
- package/dist/cjs/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts +3 -0
- package/dist/cjs/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts.map +1 -1
- package/dist/cjs/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts +8 -0
- package/dist/cjs/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts.map +1 -1
- package/dist/cjs/components/PageHeader/helpers.d.ts +9 -0
- package/dist/cjs/components/PageHeader/helpers.d.ts.map +1 -0
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/components/index.d.ts.map +1 -1
- package/dist/cjs/hooks/index.d.ts +1 -0
- package/dist/cjs/hooks/index.d.ts.map +1 -1
- package/dist/cjs/hooks/useElementWidthObserver/useElementWidthObserver.d.ts.map +1 -0
- package/dist/cjs/index.js +4 -4
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/components/ActionBar/ActionBar.d.ts +26 -0
- package/dist/esm/components/ActionBar/ActionBar.d.ts.map +1 -0
- package/dist/esm/components/ActionBar/ActionBar.model.d.ts +49 -0
- package/dist/esm/components/ActionBar/ActionBar.model.d.ts.map +1 -0
- package/dist/esm/components/ActionBar/ActionBarButton/ActionBarButton.d.ts +22 -0
- package/dist/esm/components/ActionBar/ActionBarButton/ActionBarButton.d.ts.map +1 -0
- package/dist/esm/components/ActionBar/helpers.d.ts +22 -0
- package/dist/esm/components/ActionBar/helpers.d.ts.map +1 -0
- package/dist/esm/components/ActionBar/index.d.ts +3 -0
- package/dist/esm/components/ActionBar/index.d.ts.map +1 -0
- package/dist/esm/components/Actions/Action/Action.d.ts.map +1 -1
- package/dist/esm/components/ConfirmDialog/index.d.ts +1 -0
- package/dist/esm/components/ConfirmDialog/index.d.ts.map +1 -1
- package/dist/esm/components/ConfirmDialog/useConfirmableAction.d.ts +55 -0
- package/dist/esm/components/ConfirmDialog/useConfirmableAction.d.ts.map +1 -0
- package/dist/esm/components/Explorer/BulkEdit/useBulkEdit.d.ts.map +1 -1
- package/dist/esm/components/Explorer/Explorer.d.ts.map +1 -1
- package/dist/esm/components/Explorer/QuickEdit/useQuickEdit.d.ts +14 -3
- package/dist/esm/components/Explorer/QuickEdit/useQuickEdit.d.ts.map +1 -1
- package/dist/esm/components/Explorer/helpers/useActions.d.ts +15 -4
- package/dist/esm/components/Explorer/helpers/useActions.d.ts.map +1 -1
- package/dist/esm/components/Explorer/helpers/useSubtitle.d.ts.map +1 -1
- package/dist/esm/components/List/List.d.ts +17 -3
- package/dist/esm/components/List/List.d.ts.map +1 -1
- package/dist/esm/components/List/List.model.d.ts +5 -1
- package/dist/esm/components/List/List.model.d.ts.map +1 -1
- package/dist/esm/components/List/ListHeader/ListHeader.d.ts +10 -1
- package/dist/esm/components/List/ListHeader/ListHeader.d.ts.map +1 -1
- package/dist/esm/components/List/ListRow/ListRow.d.ts +16 -5
- package/dist/esm/components/List/ListRow/ListRow.d.ts.map +1 -1
- package/dist/esm/components/List/ListRowRenderer/ListRowRenderer.d.ts.map +1 -1
- package/dist/esm/components/List/helpers.d.ts +11 -3
- package/dist/esm/components/List/helpers.d.ts.map +1 -1
- package/dist/esm/components/List/useColumnsSize.d.ts +4 -2
- package/dist/esm/components/List/useColumnsSize.d.ts.map +1 -1
- package/dist/esm/components/PageHeader/PageHeader.d.ts +4 -1
- package/dist/esm/components/PageHeader/PageHeader.d.ts.map +1 -1
- package/dist/esm/components/PageHeader/PageHeader.model.d.ts +37 -1
- package/dist/esm/components/PageHeader/PageHeader.model.d.ts.map +1 -1
- package/dist/esm/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts +3 -0
- package/dist/esm/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts.map +1 -1
- package/dist/esm/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts +8 -0
- package/dist/esm/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts.map +1 -1
- package/dist/esm/components/PageHeader/helpers.d.ts +9 -0
- package/dist/esm/components/PageHeader/helpers.d.ts.map +1 -0
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.d.ts.map +1 -1
- package/dist/esm/hooks/index.d.ts +1 -0
- package/dist/esm/hooks/index.d.ts.map +1 -1
- package/dist/esm/hooks/useElementWidthObserver/useElementWidthObserver.d.ts.map +1 -0
- package/dist/esm/index.js +4 -4
- package/dist/esm/index.js.map +1 -1
- package/package.json +7 -3
- package/src/components/ActionBar/ActionBar.model.ts +61 -0
- package/src/components/ActionBar/ActionBar.module.scss +64 -0
- package/src/components/ActionBar/ActionBar.spec.tsx +398 -0
- package/src/components/ActionBar/ActionBar.stories.tsx +205 -0
- package/src/components/ActionBar/ActionBar.tsx +170 -0
- package/src/components/ActionBar/ActionBarButton/ActionBarButton.module.scss +140 -0
- package/src/components/ActionBar/ActionBarButton/ActionBarButton.tsx +193 -0
- package/src/components/ActionBar/helpers.spec.ts +41 -0
- package/src/components/ActionBar/helpers.ts +54 -0
- package/src/components/ActionBar/index.ts +2 -0
- package/src/components/Actions/Action/Action.spec.tsx +64 -2
- package/src/components/Actions/Action/Action.tsx +6 -1
- package/src/components/ConfirmDialog/index.ts +1 -0
- package/src/components/ConfirmDialog/useConfirmableAction.tsx +159 -0
- package/src/components/EmptyStation/EmptyStation.stories.tsx +97 -0
- package/src/components/Explorer/BulkEdit/useBulkEdit.tsx +2 -0
- package/src/components/Explorer/Explorer.spec.tsx +97 -8
- package/src/components/Explorer/Explorer.tsx +26 -13
- package/src/components/Explorer/NavigationExplorer/NavigationExplorer.stories.tsx +124 -1
- package/src/components/Explorer/QuickEdit/useQuickEdit.spec.tsx +94 -50
- package/src/components/Explorer/QuickEdit/useQuickEdit.tsx +101 -42
- package/src/components/Explorer/SelectionExplorer/SelectionExplorer.spec.tsx +46 -13
- package/src/components/Explorer/helpers/useActions.ts +125 -73
- package/src/components/Explorer/helpers/useSubtitle.spec.tsx +33 -23
- package/src/components/Explorer/helpers/useSubtitle.tsx +18 -26
- package/src/components/List/List.model.ts +5 -1
- package/src/components/List/List.module.scss +25 -6
- package/src/components/List/List.spec.tsx +498 -1
- package/src/components/List/List.stories.tsx +137 -8
- package/src/components/List/List.tsx +305 -194
- package/src/components/List/ListHeader/ListHeader.module.scss +14 -0
- package/src/components/List/ListHeader/ListHeader.spec.tsx +1 -0
- package/src/components/List/ListHeader/ListHeader.tsx +25 -8
- package/src/components/List/ListRow/ListRow.module.scss +1 -1
- package/src/components/List/ListRow/ListRow.spec.tsx +109 -22
- package/src/components/List/ListRow/ListRow.tsx +52 -13
- package/src/components/List/ListRowRenderer/ListRowRenderer.tsx +28 -22
- package/src/components/List/helpers.spec.ts +29 -0
- package/src/components/List/helpers.ts +17 -9
- package/src/components/List/useColumnsSize.ts +17 -7
- package/src/components/PageHeader/PageHeader.model.ts +38 -1
- package/src/components/PageHeader/PageHeader.module.scss +39 -18
- package/src/components/PageHeader/PageHeader.spec.tsx +407 -18
- package/src/components/PageHeader/PageHeader.stories.tsx +95 -12
- package/src/components/PageHeader/PageHeader.tsx +89 -38
- package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.model.ts +8 -0
- package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.module.scss +50 -42
- package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.spec.tsx +193 -1
- package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.stories.tsx +36 -0
- package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.tsx +81 -130
- package/src/components/PageHeader/helpers.spec.ts +60 -0
- package/src/components/PageHeader/helpers.ts +36 -0
- package/src/components/index.ts +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/styles/variables.scss +41 -7
- package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts +0 -35
- package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +0 -1
- package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts +0 -6
- package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts.map +0 -1
- package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts +0 -3
- package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts.map +0 -1
- package/dist/cjs/components/PageHeader/helpers/useElementWidthObserver.d.ts.map +0 -1
- package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts +0 -35
- package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +0 -1
- package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts +0 -6
- package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts.map +0 -1
- package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts +0 -3
- package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts.map +0 -1
- package/dist/esm/components/PageHeader/helpers/useElementWidthObserver.d.ts.map +0 -1
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.module.scss +0 -77
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.spec.tsx +0 -122
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.tsx +0 -235
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.ts +0 -15
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.tsx +0 -37
- /package/dist/cjs/{components/PageHeader/helpers → hooks/useElementWidthObserver}/useElementWidthObserver.d.ts +0 -0
- /package/dist/esm/{components/PageHeader/helpers → hooks/useElementWidthObserver}/useElementWidthObserver.d.ts +0 -0
- /package/src/{components/PageHeader/helpers → hooks/useElementWidthObserver}/useElementWidthObserver.tsx +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axinom/mosaic-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.77.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=20.19.0"
|
|
6
6
|
},
|
|
@@ -101,19 +101,23 @@
|
|
|
101
101
|
"react-router-dom": "^5.3.4",
|
|
102
102
|
"rimraf": "^6.1.3",
|
|
103
103
|
"rollup": "^2.80.0",
|
|
104
|
+
"rollup-plugin-license": "^3.7.1",
|
|
104
105
|
"rollup-plugin-peer-deps-external": "^2.2.3",
|
|
105
106
|
"rollup-plugin-postcss": "^4.0.2",
|
|
106
107
|
"rollup-plugin-typescript2": "^0.29.0",
|
|
107
108
|
"rollup-plugin-visualizer": "^5.8.3",
|
|
108
109
|
"sass": "^1.57.1",
|
|
110
|
+
"spdx-license-list": "^6.12.0",
|
|
111
|
+
"spdx-satisfies": "^5.0.1",
|
|
109
112
|
"storybook": "^10.5.5",
|
|
110
113
|
"typescript": "^5.9.3",
|
|
111
114
|
"typescript-plugin-css-modules": "^5.0.2",
|
|
112
115
|
"vite": "6.4.3",
|
|
113
|
-
"vitest": "^4.1.10"
|
|
116
|
+
"vitest": "^4.1.10",
|
|
117
|
+
"yaml": "^1.10.2"
|
|
114
118
|
},
|
|
115
119
|
"publishConfig": {
|
|
116
120
|
"access": "public"
|
|
117
121
|
},
|
|
118
|
-
"gitHead": "
|
|
122
|
+
"gitHead": "334221c125bafad4bdb862699e05cdff515cc4d7"
|
|
119
123
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Not } from '../../types';
|
|
2
|
+
import type { IconName } from '../Icons';
|
|
3
|
+
import type { ConfirmAction, DefaultHandler, LinkAction } from '../models';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* An action that is rendered on the ActionBar. Actions with an `onClick`
|
|
7
|
+
* handler run it, optionally behind a confirmation, while actions with a `path`
|
|
8
|
+
* navigate to it.
|
|
9
|
+
*/
|
|
10
|
+
export type ActionBarAction = ActionBarJsAction | ActionBarNavigationAction;
|
|
11
|
+
|
|
12
|
+
/** An action of the ActionBar that runs a handler when clicked. */
|
|
13
|
+
export interface ActionBarJsAction
|
|
14
|
+
extends ActionBarBaseAction,
|
|
15
|
+
HandledAction,
|
|
16
|
+
ConfirmAction,
|
|
17
|
+
Not<LinkAction> {
|
|
18
|
+
/**
|
|
19
|
+
* Whether the action is toggled on, e.g. while the panel it has opened is the
|
|
20
|
+
* one on display. Set this to make the action a toggle button rather than a
|
|
21
|
+
* one-shot action. (default: undefined)
|
|
22
|
+
*/
|
|
23
|
+
toggled?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** An action of the ActionBar that navigates when clicked. */
|
|
27
|
+
export interface ActionBarNavigationAction
|
|
28
|
+
extends ActionBarBaseAction,
|
|
29
|
+
LinkAction,
|
|
30
|
+
Not<ConfirmAction>,
|
|
31
|
+
Not<HandledAction>,
|
|
32
|
+
Not<Pick<ActionBarJsAction, 'toggled'>> {}
|
|
33
|
+
|
|
34
|
+
interface HandledAction {
|
|
35
|
+
/** Callback to emit when a user clicks on the action. */
|
|
36
|
+
onClick: DefaultHandler;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface ActionBarBaseAction {
|
|
40
|
+
/** The label of the action. */
|
|
41
|
+
label: string;
|
|
42
|
+
/** Optional built in icon. This prop also accepts an img src. */
|
|
43
|
+
icon?: IconName | string;
|
|
44
|
+
/** Optional image alt attribute. */
|
|
45
|
+
imgAlt?: string;
|
|
46
|
+
/** Whether the action is disabled. If set to true, disallows interactions. (default: false) */
|
|
47
|
+
disabled?: boolean;
|
|
48
|
+
/** Optional tag rendered next to the label, e.g. 'BETA'. */
|
|
49
|
+
tag?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The content of an ActionBar: a label at its start and the actions that run to
|
|
54
|
+
* its end.
|
|
55
|
+
*/
|
|
56
|
+
export interface ActionBarData {
|
|
57
|
+
/** Optional text rendered at the start of the bar, e.g. 'Selected 3'. */
|
|
58
|
+
label?: string;
|
|
59
|
+
/** The actions to render. Actions that do not fit move into an overflow menu. */
|
|
60
|
+
actions: ActionBarAction[];
|
|
61
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
@import '../../styles/common.scss';
|
|
2
|
+
|
|
3
|
+
.container {
|
|
4
|
+
@include boxSizing;
|
|
5
|
+
|
|
6
|
+
padding-left: 5px;
|
|
7
|
+
display: grid;
|
|
8
|
+
|
|
9
|
+
background-color: var(
|
|
10
|
+
--action-bar-background-color,
|
|
11
|
+
$action-bar-background-color
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
border-bottom: solid 1px
|
|
15
|
+
var(--action-bar-border-color, $action-bar-border-color);
|
|
16
|
+
|
|
17
|
+
.content {
|
|
18
|
+
min-width: 0;
|
|
19
|
+
height: 100%;
|
|
20
|
+
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
column-gap: 10px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.label {
|
|
27
|
+
padding-left: 10px;
|
|
28
|
+
|
|
29
|
+
color: var(--action-bar-label-color, $action-bar-label-color);
|
|
30
|
+
font-size: 14px;
|
|
31
|
+
white-space: nowrap;
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
text-overflow: ellipsis;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Takes up the space that is left next to the label, so that its width is the
|
|
37
|
+
// space the actions can occupy. Actions are collapsed into the overflow menu
|
|
38
|
+
// until they fit, `overflow: hidden` only covers the frame they are measured in.
|
|
39
|
+
.actions {
|
|
40
|
+
flex: 1 1 auto;
|
|
41
|
+
min-width: 0;
|
|
42
|
+
height: 100%;
|
|
43
|
+
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: flex-end;
|
|
47
|
+
overflow: hidden;
|
|
48
|
+
|
|
49
|
+
// Actions would shrink to fit while they are measured, which hides that
|
|
50
|
+
// they do not fit at their natural width.
|
|
51
|
+
&.measuring > * {
|
|
52
|
+
flex-shrink: 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.overflowMenuButton {
|
|
57
|
+
height: 100%;
|
|
58
|
+
width: 50px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.overflowMenu {
|
|
62
|
+
z-index: 3;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { fireEvent, render, screen, within } from '@testing-library/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { MemoryRouter, Route } from 'react-router-dom';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { noop } from '../../helpers/utils';
|
|
6
|
+
import { IconName } from '../Icons';
|
|
7
|
+
import { ActionBar, type ActionBarProps } from './ActionBar';
|
|
8
|
+
import type { ActionBarAction } from './ActionBar.model';
|
|
9
|
+
|
|
10
|
+
const defaultProps: ActionBarProps = {
|
|
11
|
+
actions: [],
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const mockActions: ActionBarAction[] = [
|
|
15
|
+
{ label: 'Publish', icon: IconName.Publish, onClick: vi.fn() },
|
|
16
|
+
{ label: 'Delete', icon: IconName.Delete, onClick: vi.fn() },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const renderBar = (
|
|
20
|
+
props: Partial<ActionBarProps> = {},
|
|
21
|
+
): ReturnType<typeof render> =>
|
|
22
|
+
render(
|
|
23
|
+
<MemoryRouter>
|
|
24
|
+
<ActionBar {...defaultProps} {...props} />
|
|
25
|
+
</MemoryRouter>,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
describe('ActionBar', () => {
|
|
29
|
+
it('renders the given actions', () => {
|
|
30
|
+
renderBar({ actions: mockActions });
|
|
31
|
+
|
|
32
|
+
const labels = screen
|
|
33
|
+
.getAllByTestId('label')
|
|
34
|
+
.map((label) => label.textContent);
|
|
35
|
+
|
|
36
|
+
expect(labels).toEqual(['Publish', 'Delete']);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders the label', () => {
|
|
40
|
+
renderBar({ actions: mockActions, label: '3 Selected' });
|
|
41
|
+
|
|
42
|
+
expect(screen.getByTestId('action-bar-label')).toHaveTextContent(
|
|
43
|
+
'3 Selected',
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('renders no label element when no label is given', () => {
|
|
48
|
+
renderBar({ actions: mockActions });
|
|
49
|
+
|
|
50
|
+
expect(screen.queryByTestId('action-bar-label')).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('applies the height to the bar', () => {
|
|
54
|
+
renderBar({ height: '60px' });
|
|
55
|
+
|
|
56
|
+
expect(screen.getByTestId('action-bar').style.gridAutoRows).toBe('60px');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it(`defaults to a height of '44px'`, () => {
|
|
60
|
+
renderBar();
|
|
61
|
+
|
|
62
|
+
expect(screen.getByTestId('action-bar').style.gridAutoRows).toBe('44px');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('raises onClick when an action is clicked', () => {
|
|
66
|
+
const onClick = vi.fn();
|
|
67
|
+
renderBar({ actions: [{ label: 'Publish', onClick }] });
|
|
68
|
+
|
|
69
|
+
fireEvent.click(screen.getByTestId('action'));
|
|
70
|
+
|
|
71
|
+
expect(onClick).toHaveBeenCalled();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('does not raise onClick of a disabled action', () => {
|
|
75
|
+
const onClick = vi.fn();
|
|
76
|
+
renderBar({ actions: [{ label: 'Publish', disabled: true, onClick }] });
|
|
77
|
+
|
|
78
|
+
fireEvent.click(screen.getByTestId('action'));
|
|
79
|
+
|
|
80
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('marks a toggled action as pressed', () => {
|
|
84
|
+
renderBar({
|
|
85
|
+
actions: [
|
|
86
|
+
{ label: 'Details', toggled: true, onClick: vi.fn() },
|
|
87
|
+
{ label: 'Focal Point', toggled: false, onClick: vi.fn() },
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const actions = screen.getAllByTestId('action');
|
|
92
|
+
|
|
93
|
+
expect(actions[0]).toHaveAttribute('aria-pressed', 'true');
|
|
94
|
+
expect(actions[1]).toHaveAttribute('aria-pressed', 'false');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it(`doesn't mark a one-shot action as a toggle`, () => {
|
|
98
|
+
renderBar({ actions: [{ label: 'Publish', onClick: vi.fn() }] });
|
|
99
|
+
|
|
100
|
+
expect(screen.getByTestId('action')).not.toHaveAttribute('aria-pressed');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('tags', () => {
|
|
104
|
+
it('renders the tag next to the label', () => {
|
|
105
|
+
renderBar({
|
|
106
|
+
actions: [{ label: 'Edit', tag: 'BETA', onClick: vi.fn() }],
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(screen.getByTestId('tag')).toHaveTextContent('BETA');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('renders no tag element when no tag is given', () => {
|
|
113
|
+
renderBar({ actions: [{ label: 'Edit', onClick: vi.fn() }] });
|
|
114
|
+
|
|
115
|
+
expect(screen.queryByTestId('tag')).toBeNull();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('hides the tag while a confirmation is shown', () => {
|
|
119
|
+
renderBar({
|
|
120
|
+
actions: [
|
|
121
|
+
{
|
|
122
|
+
label: 'Delete',
|
|
123
|
+
tag: 'BETA',
|
|
124
|
+
confirmationMode: 'Simple',
|
|
125
|
+
onClick: vi.fn(),
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
fireEvent.click(screen.getByTestId('action'));
|
|
131
|
+
|
|
132
|
+
expect(screen.queryByTestId('tag')).toBeNull();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('links', () => {
|
|
137
|
+
it('renders a link to the path of the action', () => {
|
|
138
|
+
renderBar({ actions: [{ label: 'Export', path: '/export' }] });
|
|
139
|
+
|
|
140
|
+
const action = screen.getByTestId('action');
|
|
141
|
+
|
|
142
|
+
expect(action.tagName).toBe('A');
|
|
143
|
+
expect(action).toHaveAttribute('href', '/export');
|
|
144
|
+
expect(action).not.toHaveAttribute('target');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('opens the link in a new tab when openInNewTab is set', () => {
|
|
148
|
+
renderBar({
|
|
149
|
+
actions: [{ label: 'Export', path: '/export', openInNewTab: true }],
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(screen.getByTestId('action')).toHaveAttribute('target', '_blank');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('navigates to the path when clicked', () => {
|
|
156
|
+
render(
|
|
157
|
+
<MemoryRouter initialEntries={['/']}>
|
|
158
|
+
<ActionBar actions={[{ label: 'Export', path: '/export' }]} />
|
|
159
|
+
<Route path="/export">
|
|
160
|
+
<span data-test-id="export-page" />
|
|
161
|
+
</Route>
|
|
162
|
+
</MemoryRouter>,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
fireEvent.click(screen.getByTestId('action'));
|
|
166
|
+
|
|
167
|
+
expect(screen.getByTestId('export-page')).toBeInTheDocument();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('renders a disabled link as a disabled button', () => {
|
|
171
|
+
render(
|
|
172
|
+
<MemoryRouter initialEntries={['/']}>
|
|
173
|
+
<ActionBar
|
|
174
|
+
actions={[{ label: 'Export', path: '/export', disabled: true }]}
|
|
175
|
+
/>
|
|
176
|
+
<Route path="/export">
|
|
177
|
+
<span data-test-id="export-page" />
|
|
178
|
+
</Route>
|
|
179
|
+
</MemoryRouter>,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const action = screen.getByTestId('action');
|
|
183
|
+
|
|
184
|
+
expect(action.tagName).toBe('BUTTON');
|
|
185
|
+
expect(action).toBeDisabled();
|
|
186
|
+
|
|
187
|
+
fireEvent.click(action);
|
|
188
|
+
|
|
189
|
+
expect(screen.queryByTestId('export-page')).toBeNull();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('confirmations', () => {
|
|
194
|
+
it('requires a second click in Simple mode', () => {
|
|
195
|
+
const onClick = vi.fn();
|
|
196
|
+
renderBar({
|
|
197
|
+
actions: [{ label: 'Delete', confirmationMode: 'Simple', onClick }],
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
fireEvent.click(screen.getByTestId('action'));
|
|
201
|
+
|
|
202
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
203
|
+
expect(screen.getByTestId('confirm')).toHaveTextContent(
|
|
204
|
+
'Click again to confirm',
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
fireEvent.click(screen.getByTestId('action'));
|
|
208
|
+
|
|
209
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('opens a dialog in Advanced mode and runs the action once confirmed', () => {
|
|
213
|
+
const onClick = vi.fn();
|
|
214
|
+
renderBar({
|
|
215
|
+
actions: [
|
|
216
|
+
{
|
|
217
|
+
label: 'Delete',
|
|
218
|
+
confirmationMode: 'Advanced',
|
|
219
|
+
confirmationConfig: { title: 'Delete the items?' },
|
|
220
|
+
onClick,
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
fireEvent.click(screen.getByTestId('action'));
|
|
226
|
+
|
|
227
|
+
expect(screen.getByText('Delete the items?')).toBeInTheDocument();
|
|
228
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
229
|
+
|
|
230
|
+
fireEvent.click(screen.getByTestId('confirm'));
|
|
231
|
+
|
|
232
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('does not run the action when the dialog is cancelled', () => {
|
|
236
|
+
const onClick = vi.fn();
|
|
237
|
+
renderBar({
|
|
238
|
+
actions: [{ label: 'Delete', confirmationMode: 'Advanced', onClick }],
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
fireEvent.click(screen.getByTestId('action'));
|
|
242
|
+
fireEvent.click(screen.getByTestId('cancel'));
|
|
243
|
+
|
|
244
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('does not carry a pending confirmation over to other actions', () => {
|
|
248
|
+
const onArchive = vi.fn();
|
|
249
|
+
const { rerender } = renderBar({
|
|
250
|
+
actions: [
|
|
251
|
+
{ label: 'Delete', confirmationMode: 'Simple', onClick: noop },
|
|
252
|
+
],
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
fireEvent.click(screen.getByTestId('action'));
|
|
256
|
+
|
|
257
|
+
expect(screen.getByTestId('confirm')).toBeInTheDocument();
|
|
258
|
+
|
|
259
|
+
rerender(
|
|
260
|
+
<MemoryRouter>
|
|
261
|
+
<ActionBar
|
|
262
|
+
actions={[
|
|
263
|
+
{
|
|
264
|
+
label: 'Archive',
|
|
265
|
+
confirmationMode: 'Simple',
|
|
266
|
+
onClick: onArchive,
|
|
267
|
+
},
|
|
268
|
+
]}
|
|
269
|
+
/>
|
|
270
|
+
</MemoryRouter>,
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
expect(screen.queryByTestId('confirm')).toBeNull();
|
|
274
|
+
|
|
275
|
+
// The first click only asks for the confirmation.
|
|
276
|
+
fireEvent.click(screen.getByTestId('action'));
|
|
277
|
+
|
|
278
|
+
expect(onArchive).not.toHaveBeenCalled();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('keeps a pending confirmation while the same actions render again', () => {
|
|
282
|
+
const actions = (): ActionBarAction[] => [
|
|
283
|
+
{ label: 'Delete', confirmationMode: 'Simple', onClick: noop },
|
|
284
|
+
];
|
|
285
|
+
const { rerender } = renderBar({ actions: actions() });
|
|
286
|
+
|
|
287
|
+
fireEvent.click(screen.getByTestId('action'));
|
|
288
|
+
rerender(
|
|
289
|
+
<MemoryRouter>
|
|
290
|
+
<ActionBar actions={actions()} />
|
|
291
|
+
</MemoryRouter>,
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
expect(screen.getByTestId('confirm')).toBeInTheDocument();
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe('overflow', () => {
|
|
299
|
+
// jsdom has no layout, so every action is given a width of 100px and the
|
|
300
|
+
// actions a width of 220px, which leaves room for a single action next to
|
|
301
|
+
// the 50px wide overflow menu.
|
|
302
|
+
beforeEach(() => {
|
|
303
|
+
vi.spyOn(HTMLElement.prototype, 'offsetWidth', 'get').mockReturnValue(
|
|
304
|
+
100,
|
|
305
|
+
);
|
|
306
|
+
vi.stubGlobal(
|
|
307
|
+
'ResizeObserver',
|
|
308
|
+
class {
|
|
309
|
+
constructor(
|
|
310
|
+
private readonly callback: (
|
|
311
|
+
entries: { contentRect: { width: number } }[],
|
|
312
|
+
) => void,
|
|
313
|
+
) {}
|
|
314
|
+
observe(): void {
|
|
315
|
+
this.callback([{ contentRect: { width: 220 } }]);
|
|
316
|
+
}
|
|
317
|
+
unobserve(): void {
|
|
318
|
+
// nothing to clean up
|
|
319
|
+
}
|
|
320
|
+
disconnect(): void {
|
|
321
|
+
// nothing to clean up
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
afterEach(() => {
|
|
328
|
+
vi.restoreAllMocks();
|
|
329
|
+
vi.unstubAllGlobals();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const overflowActions: ActionBarAction[] = [
|
|
333
|
+
{ label: 'Publish', onClick: vi.fn() },
|
|
334
|
+
{ label: 'Edit', tag: 'BETA', onClick: vi.fn() },
|
|
335
|
+
{ label: 'Export', path: '/export' },
|
|
336
|
+
];
|
|
337
|
+
|
|
338
|
+
it('moves the actions that do not fit into the overflow menu', () => {
|
|
339
|
+
renderBar({ actions: overflowActions });
|
|
340
|
+
|
|
341
|
+
const bar = screen.getByTestId('action-bar');
|
|
342
|
+
|
|
343
|
+
expect(
|
|
344
|
+
within(bar)
|
|
345
|
+
.getAllByTestId('action')
|
|
346
|
+
.map((action) => action.textContent),
|
|
347
|
+
).toEqual(['Publish']);
|
|
348
|
+
expect(within(bar).getByTestId('inline-menu-button')).toBeInTheDocument();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('keeps the tag and the link of an action in the overflow menu', () => {
|
|
352
|
+
renderBar({ actions: overflowActions });
|
|
353
|
+
|
|
354
|
+
fireEvent.click(screen.getByTestId('inline-menu-button'));
|
|
355
|
+
|
|
356
|
+
const menuActions = within(
|
|
357
|
+
screen.getByTestId('inline-menu'),
|
|
358
|
+
).getAllByTestId('action');
|
|
359
|
+
|
|
360
|
+
expect(menuActions.map((action) => action.textContent)).toEqual([
|
|
361
|
+
'Edit (BETA)',
|
|
362
|
+
'Export',
|
|
363
|
+
]);
|
|
364
|
+
expect(menuActions[1]).toHaveAttribute('href', '/export');
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('keeps a disabled link in the overflow menu from navigating', () => {
|
|
368
|
+
render(
|
|
369
|
+
<MemoryRouter initialEntries={['/']}>
|
|
370
|
+
<ActionBar
|
|
371
|
+
actions={[
|
|
372
|
+
{ label: 'Publish', onClick: vi.fn() },
|
|
373
|
+
{ label: 'Edit', onClick: vi.fn() },
|
|
374
|
+
{ label: 'Export', path: '/export', disabled: true },
|
|
375
|
+
]}
|
|
376
|
+
/>
|
|
377
|
+
<Route path="/export">
|
|
378
|
+
<span data-test-id="export-page" />
|
|
379
|
+
</Route>
|
|
380
|
+
</MemoryRouter>,
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
fireEvent.click(screen.getByTestId('inline-menu-button'));
|
|
384
|
+
|
|
385
|
+
const link = within(screen.getByTestId('inline-menu'))
|
|
386
|
+
.getAllByTestId('action')
|
|
387
|
+
.find((action) => action.textContent === 'Export') as HTMLElement;
|
|
388
|
+
|
|
389
|
+
// Out of reach of the keyboard, and inert if it is activated anyway.
|
|
390
|
+
expect(link).toHaveAttribute('aria-disabled', 'true');
|
|
391
|
+
expect(link).toHaveAttribute('tabindex', '-1');
|
|
392
|
+
|
|
393
|
+
fireEvent.click(link);
|
|
394
|
+
|
|
395
|
+
expect(screen.queryByTestId('export-page')).toBeNull();
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
});
|