@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
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { faker } from '@faker-js/faker';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import React, { useMemo, useState } from 'react';
|
|
4
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
5
|
+
import { action } from 'storybook/actions';
|
|
6
|
+
import { EmptyStation } from '../EmptyStation';
|
|
7
|
+
import { IconName } from '../Icons';
|
|
8
|
+
import { List, ListSelectMode } from '../List';
|
|
9
|
+
import { ActionBar } from './ActionBar';
|
|
10
|
+
import type { ActionBarAction } from './ActionBar.model';
|
|
11
|
+
|
|
12
|
+
type ActionBarStoryType = React.ComponentProps<typeof ActionBar> & {
|
|
13
|
+
containerWidth: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const actions: ActionBarAction[] = [
|
|
17
|
+
{
|
|
18
|
+
label: 'Edit',
|
|
19
|
+
icon: IconName.BulkEdit,
|
|
20
|
+
tag: 'BETA',
|
|
21
|
+
toggled: false,
|
|
22
|
+
onClick: action('Edit.onClick'),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
label: 'Publish',
|
|
26
|
+
icon: IconName.Publish,
|
|
27
|
+
onClick: action('Publish.onClick'),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
label: 'Unpublish',
|
|
31
|
+
icon: IconName.Unpublish,
|
|
32
|
+
confirmationMode: 'Simple',
|
|
33
|
+
onClick: action('Unpublish.onClick'),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
label: 'Delete',
|
|
37
|
+
icon: IconName.Delete,
|
|
38
|
+
confirmationMode: 'Advanced',
|
|
39
|
+
confirmationConfig: {
|
|
40
|
+
title: 'Delete the selected items?',
|
|
41
|
+
body: 'The action is irreversible.',
|
|
42
|
+
},
|
|
43
|
+
onClick: action('Delete.onClick'),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
label: 'Archive',
|
|
47
|
+
icon: IconName.Archive,
|
|
48
|
+
disabled: true,
|
|
49
|
+
onClick: action('Archive.onClick'),
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
label: 'Export Settings',
|
|
53
|
+
icon: IconName.External,
|
|
54
|
+
path: '/export-settings',
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const meta: Meta<ActionBarStoryType> = {
|
|
59
|
+
title: 'Other Components/ActionBar',
|
|
60
|
+
component: ActionBar,
|
|
61
|
+
argTypes: {
|
|
62
|
+
actions: { control: false },
|
|
63
|
+
containerWidth: {
|
|
64
|
+
description: `<b>[Storybook only]</b> Width of the element the bar is rendered in.`,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
args: {
|
|
68
|
+
label: 'Selected 3',
|
|
69
|
+
actions,
|
|
70
|
+
containerWidth: '100%',
|
|
71
|
+
},
|
|
72
|
+
decorators: [
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
74
|
+
(Story) => (
|
|
75
|
+
<MemoryRouter>
|
|
76
|
+
<Story />
|
|
77
|
+
</MemoryRouter>
|
|
78
|
+
),
|
|
79
|
+
],
|
|
80
|
+
render: ({ containerWidth, ...args }) => (
|
|
81
|
+
<div style={{ width: containerWidth }}>
|
|
82
|
+
<ActionBar {...args} />
|
|
83
|
+
</div>
|
|
84
|
+
),
|
|
85
|
+
};
|
|
86
|
+
export default meta;
|
|
87
|
+
|
|
88
|
+
export const Default: StoryObj<ActionBarStoryType> = {
|
|
89
|
+
parameters: {
|
|
90
|
+
docs: {
|
|
91
|
+
description: {
|
|
92
|
+
story: `The bar renders a label at its start and the actions at its end. Actions run a handler, optionally behind a 'Simple' or 'Advanced' confirmation, or navigate when they have a 'path'.
|
|
93
|
+
They can be toggled, disabled, and carry a tag.`,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const Overflow: StoryObj<ActionBarStoryType> = {
|
|
100
|
+
parameters: {
|
|
101
|
+
docs: {
|
|
102
|
+
description: {
|
|
103
|
+
story: `Actions that do not fit the available width move into an overflow menu at the end of the bar. The menu shows the tag of an action as part of its label.`,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
args: {
|
|
108
|
+
containerWidth: '520px',
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
interface CompositionStoryData {
|
|
113
|
+
id: number;
|
|
114
|
+
title: string;
|
|
115
|
+
desc: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const ExplorerLikeComposition: StoryObj<ActionBarStoryType> = {
|
|
119
|
+
name: 'Explorer-like composition',
|
|
120
|
+
parameters: {
|
|
121
|
+
docs: {
|
|
122
|
+
description: {
|
|
123
|
+
story: `An Explorer-like screen composed from its parts: an action on the page header toggles the bulk actions, which the List renders on its action bar through its 'actionBar' property.
|
|
124
|
+
The List keeps the bar in place above its column header. Render the ActionBar directly for content that is not a List.`,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
controls: { disable: true },
|
|
128
|
+
},
|
|
129
|
+
render: () =>
|
|
130
|
+
React.createElement(() => {
|
|
131
|
+
const data = useMemo<CompositionStoryData[]>(
|
|
132
|
+
() =>
|
|
133
|
+
Array.from({ length: 30 }, (_, index) => ({
|
|
134
|
+
id: index + 1,
|
|
135
|
+
title: `Item ${index + 1}`,
|
|
136
|
+
desc: faker.lorem.sentence(),
|
|
137
|
+
})),
|
|
138
|
+
[],
|
|
139
|
+
);
|
|
140
|
+
const [isBulkOpen, setIsBulkOpen] = useState<boolean>(false);
|
|
141
|
+
const [selectedCount, setSelectedCount] = useState<number>(0);
|
|
142
|
+
|
|
143
|
+
const noSelection = selectedCount === 0;
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<EmptyStation
|
|
147
|
+
title="Custom Explorer"
|
|
148
|
+
actions={[
|
|
149
|
+
{
|
|
150
|
+
kind: 'action',
|
|
151
|
+
label: 'Bulk Actions',
|
|
152
|
+
icon: IconName.Bulk,
|
|
153
|
+
toggled: isBulkOpen,
|
|
154
|
+
onClick: () => {
|
|
155
|
+
setIsBulkOpen((open) => !open);
|
|
156
|
+
setSelectedCount(0);
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
]}
|
|
160
|
+
>
|
|
161
|
+
<List<CompositionStoryData>
|
|
162
|
+
columns={[
|
|
163
|
+
{ propertyName: 'id', label: 'Id', size: '60px' },
|
|
164
|
+
{ propertyName: 'title', label: 'Title' },
|
|
165
|
+
{ propertyName: 'desc', label: 'Description', size: '3fr' },
|
|
166
|
+
]}
|
|
167
|
+
data={data}
|
|
168
|
+
selectionMode={
|
|
169
|
+
isBulkOpen ? ListSelectMode.Multi : ListSelectMode.None
|
|
170
|
+
}
|
|
171
|
+
onItemSelected={(event) =>
|
|
172
|
+
setSelectedCount(
|
|
173
|
+
event.mode === 'SELECT_ALL'
|
|
174
|
+
? data.length
|
|
175
|
+
: event.items?.length ?? 0,
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
actionBar={{
|
|
179
|
+
label:
|
|
180
|
+
isBulkOpen && !noSelection
|
|
181
|
+
? `Selected ${selectedCount}`
|
|
182
|
+
: `Total ${data.length}`,
|
|
183
|
+
actions: isBulkOpen
|
|
184
|
+
? [
|
|
185
|
+
{
|
|
186
|
+
label: 'Publish',
|
|
187
|
+
icon: IconName.Publish,
|
|
188
|
+
disabled: noSelection,
|
|
189
|
+
onClick: action('Publish.onClick'),
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
label: 'Delete',
|
|
193
|
+
icon: IconName.Delete,
|
|
194
|
+
confirmationMode: 'Simple',
|
|
195
|
+
disabled: noSelection,
|
|
196
|
+
onClick: action('Delete.onClick'),
|
|
197
|
+
},
|
|
198
|
+
]
|
|
199
|
+
: [],
|
|
200
|
+
}}
|
|
201
|
+
/>
|
|
202
|
+
</EmptyStation>
|
|
203
|
+
);
|
|
204
|
+
}),
|
|
205
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import React, { useLayoutEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { useElementWidthObserver } from '../../hooks/useElementWidthObserver/useElementWidthObserver';
|
|
4
|
+
import type { ActionData } from '../Actions';
|
|
5
|
+
import { InlineMenu } from '../InlineMenu';
|
|
6
|
+
import type { ActionBarAction, ActionBarData } from './ActionBar.model';
|
|
7
|
+
import classes from './ActionBar.module.scss';
|
|
8
|
+
import { ActionBarButton } from './ActionBarButton/ActionBarButton';
|
|
9
|
+
import { getVisibleActionCount, isActionBarNavigationAction } from './helpers';
|
|
10
|
+
|
|
11
|
+
/** Width of the overflow menu trigger. Has to match `.overflowMenuButton`. */
|
|
12
|
+
const OVERFLOW_MENU_WIDTH_PX = 50;
|
|
13
|
+
|
|
14
|
+
export interface ActionBarProps extends ActionBarData {
|
|
15
|
+
/** Height of the bar, not including its bottom border. (default: '44px') */
|
|
16
|
+
height?: string;
|
|
17
|
+
/** CSS Class name for additional styles */
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Renders a bar with a label at its start and actions that run to its end, e.g.
|
|
23
|
+
* the bulk actions that apply to the items selected in a list.
|
|
24
|
+
*
|
|
25
|
+
* Actions that do not fit the available width are moved into an overflow menu.
|
|
26
|
+
*
|
|
27
|
+
* The List renders the bar on top of its column header through its `actionBar`
|
|
28
|
+
* property, and the PageHeader renders it below itself while an actions group
|
|
29
|
+
* is open. Render it directly to place it anywhere else.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* <ActionBar
|
|
33
|
+
* label={'Selected 3'}
|
|
34
|
+
* actions={[{ label: 'Delete', icon: IconName.Delete, onClick: deleteHandler }]}
|
|
35
|
+
* />
|
|
36
|
+
*/
|
|
37
|
+
export const ActionBar: React.FC<ActionBarProps> = ({
|
|
38
|
+
label,
|
|
39
|
+
actions,
|
|
40
|
+
height = '44px',
|
|
41
|
+
className = '',
|
|
42
|
+
}) => {
|
|
43
|
+
const { width: availableWidth, ref: actionsRef } =
|
|
44
|
+
useElementWidthObserver<HTMLDivElement>();
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Natural width of every action, measured while all of them are rendered.
|
|
48
|
+
* Tagged with the set it was taken for, so that a new set of actions is
|
|
49
|
+
* measured again instead of being collapsed by the widths of the old one.
|
|
50
|
+
*/
|
|
51
|
+
const [measurement, setMeasurement] = useState<{
|
|
52
|
+
key: string;
|
|
53
|
+
widths: number[];
|
|
54
|
+
}>();
|
|
55
|
+
|
|
56
|
+
const actionsKey = actions
|
|
57
|
+
.map((action) =>
|
|
58
|
+
[action.label, action.tag ?? '', action.icon === undefined].join(':'),
|
|
59
|
+
)
|
|
60
|
+
.join('|');
|
|
61
|
+
const actionWidths =
|
|
62
|
+
measurement?.key === actionsKey ? measurement.widths : undefined;
|
|
63
|
+
|
|
64
|
+
useLayoutEffect(() => {
|
|
65
|
+
if (actionWidths !== undefined || actionsRef.current === null) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const widths = Array.from(actionsRef.current.children).map(
|
|
70
|
+
(child) => (child as HTMLElement).offsetWidth,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// Every action is rendered while the widths are unknown, so anything else
|
|
74
|
+
// means the measurement ran before the actions were laid out. Widths of 0
|
|
75
|
+
// are reported by environments without a layout engine, in which case the
|
|
76
|
+
// actions are left uncollapsed.
|
|
77
|
+
if (
|
|
78
|
+
widths.length !== actions.length ||
|
|
79
|
+
widths.some((width) => width <= 0)
|
|
80
|
+
) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
setMeasurement({ key: actionsKey, widths });
|
|
85
|
+
}, [actionWidths, actionsKey, actions.length, actionsRef]);
|
|
86
|
+
|
|
87
|
+
const visibleActionCount = useMemo(
|
|
88
|
+
() =>
|
|
89
|
+
actionWidths === undefined || availableWidth <= 0
|
|
90
|
+
? actions.length
|
|
91
|
+
: getVisibleActionCount(
|
|
92
|
+
actionWidths,
|
|
93
|
+
availableWidth,
|
|
94
|
+
OVERFLOW_MENU_WIDTH_PX,
|
|
95
|
+
),
|
|
96
|
+
[actionWidths, availableWidth, actions.length],
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const overflowActions = actions.slice(visibleActionCount);
|
|
100
|
+
|
|
101
|
+
const customStyles = {
|
|
102
|
+
gridAutoRows: height,
|
|
103
|
+
} as React.CSSProperties;
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div
|
|
107
|
+
className={clsx(classes.container, 'action-bar-container', className)}
|
|
108
|
+
style={customStyles}
|
|
109
|
+
data-test-id="action-bar"
|
|
110
|
+
>
|
|
111
|
+
<div className={classes.content}>
|
|
112
|
+
{label !== undefined && (
|
|
113
|
+
<span className={classes.label} data-test-id="action-bar-label">
|
|
114
|
+
{label}
|
|
115
|
+
</span>
|
|
116
|
+
)}
|
|
117
|
+
<div
|
|
118
|
+
className={clsx(classes.actions, {
|
|
119
|
+
[classes.measuring]: actionWidths === undefined,
|
|
120
|
+
})}
|
|
121
|
+
ref={actionsRef}
|
|
122
|
+
>
|
|
123
|
+
{actions.slice(0, visibleActionCount).map((action, index) => (
|
|
124
|
+
// Keyed by the set of actions, so that another set starts afresh
|
|
125
|
+
// rather than taking over e.g. a pending confirmation.
|
|
126
|
+
<ActionBarButton key={`${actionsKey}#${index}`} action={action} />
|
|
127
|
+
))}
|
|
128
|
+
{overflowActions.length > 0 && (
|
|
129
|
+
<InlineMenu
|
|
130
|
+
actions={overflowActions.map(toActionData)}
|
|
131
|
+
placement="bottom-end"
|
|
132
|
+
showArrow={false}
|
|
133
|
+
width="250px"
|
|
134
|
+
buttonClassName={classes.overflowMenuButton}
|
|
135
|
+
className={classes.overflowMenu}
|
|
136
|
+
/>
|
|
137
|
+
)}
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Converts an action of the bar into the shape the overflow menu renders, which
|
|
146
|
+
* keeps the confirmation behavior or the link of the action intact.
|
|
147
|
+
*/
|
|
148
|
+
const toActionData = (action: ActionBarAction): ActionData => {
|
|
149
|
+
// The menu has no room for a tag of its own, so it becomes part of the label.
|
|
150
|
+
const label = action.tag ? `${action.label} (${action.tag})` : action.label;
|
|
151
|
+
// The menu renders icon components only, so an image source is dropped.
|
|
152
|
+
const icon = typeof action.icon === 'string' ? undefined : action.icon;
|
|
153
|
+
|
|
154
|
+
return isActionBarNavigationAction(action)
|
|
155
|
+
? {
|
|
156
|
+
label,
|
|
157
|
+
icon,
|
|
158
|
+
isDisabled: action.disabled,
|
|
159
|
+
path: action.path,
|
|
160
|
+
openInNewTab: action.openInNewTab,
|
|
161
|
+
}
|
|
162
|
+
: {
|
|
163
|
+
label,
|
|
164
|
+
icon,
|
|
165
|
+
isDisabled: action.disabled,
|
|
166
|
+
confirmationMode: action.confirmationMode,
|
|
167
|
+
confirmationConfig: action.confirmationConfig,
|
|
168
|
+
onActionSelected: action.onClick,
|
|
169
|
+
};
|
|
170
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
@import '../../../styles/common.scss';
|
|
2
|
+
|
|
3
|
+
.container {
|
|
4
|
+
all: unset; // remove all button and link styles
|
|
5
|
+
|
|
6
|
+
@include boxSizing;
|
|
7
|
+
|
|
8
|
+
height: 100%;
|
|
9
|
+
// Keeps the action from resizing while the 'Simple' confirmation replaces the
|
|
10
|
+
// label, which would otherwise shift the actions next to it.
|
|
11
|
+
min-width: var(--action-bar-action-min-width, $action-bar-action-min-width);
|
|
12
|
+
padding: 0 12px;
|
|
13
|
+
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: 8px;
|
|
17
|
+
|
|
18
|
+
color: var(--action-bar-action-color, $action-bar-action-color);
|
|
19
|
+
font-size: 14px;
|
|
20
|
+
white-space: nowrap;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
user-select: none;
|
|
23
|
+
|
|
24
|
+
.icon {
|
|
25
|
+
display: grid;
|
|
26
|
+
place-items: center;
|
|
27
|
+
|
|
28
|
+
> * {
|
|
29
|
+
height: var(--action-bar-action-icon-size, $action-bar-action-icon-size);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.label {
|
|
34
|
+
display: grid;
|
|
35
|
+
place-items: center;
|
|
36
|
+
overflow: hidden;
|
|
37
|
+
text-overflow: ellipsis;
|
|
38
|
+
|
|
39
|
+
> span {
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
text-overflow: ellipsis;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Follows the color of the action, so that it matches its state.
|
|
46
|
+
.tag {
|
|
47
|
+
padding: 1px 4px;
|
|
48
|
+
|
|
49
|
+
border: 1px solid currentColor;
|
|
50
|
+
border-radius: 3px;
|
|
51
|
+
|
|
52
|
+
font-size: var(
|
|
53
|
+
--action-bar-action-tag-font-size,
|
|
54
|
+
$action-bar-action-tag-font-size
|
|
55
|
+
);
|
|
56
|
+
font-weight: bold;
|
|
57
|
+
line-height: 1.2;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.actionIcon {
|
|
61
|
+
rect,
|
|
62
|
+
polygon,
|
|
63
|
+
polyline,
|
|
64
|
+
path,
|
|
65
|
+
circle {
|
|
66
|
+
stroke: var(--action-bar-action-color, $action-bar-action-color);
|
|
67
|
+
stroke-width: var(
|
|
68
|
+
--action-bar-action-icon-stroke-width,
|
|
69
|
+
$action-bar-action-icon-stroke-width
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
&:hover:not(:disabled) {
|
|
75
|
+
background-color: var(
|
|
76
|
+
--action-bar-action-hover-background-color,
|
|
77
|
+
$action-bar-action-hover-background-color
|
|
78
|
+
);
|
|
79
|
+
transition: background-color $confirmation-transition linear;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&.toggled {
|
|
83
|
+
color: var(
|
|
84
|
+
--action-bar-action-toggled-color,
|
|
85
|
+
$action-bar-action-toggled-color
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Keeps its background while hovered, like the selected option of a Select.
|
|
89
|
+
&,
|
|
90
|
+
&:hover:not(:disabled) {
|
|
91
|
+
background-color: var(
|
|
92
|
+
--action-bar-action-toggled-background-color,
|
|
93
|
+
$action-bar-action-toggled-background-color
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.actionIcon {
|
|
98
|
+
rect,
|
|
99
|
+
polygon,
|
|
100
|
+
polyline,
|
|
101
|
+
path,
|
|
102
|
+
circle {
|
|
103
|
+
stroke: var(
|
|
104
|
+
--action-bar-action-toggled-color,
|
|
105
|
+
$action-bar-action-toggled-color
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
&.hasConfirm,
|
|
112
|
+
&.hasConfirm:hover:not(:disabled) {
|
|
113
|
+
background-color: var(
|
|
114
|
+
--action-bar-action-confirmation-background-color,
|
|
115
|
+
$action-bar-action-confirmation-background-color
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
&:disabled {
|
|
120
|
+
color: var(
|
|
121
|
+
--action-bar-action-disabled-color,
|
|
122
|
+
$action-bar-action-disabled-color
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
cursor: default;
|
|
126
|
+
|
|
127
|
+
.actionIcon {
|
|
128
|
+
rect,
|
|
129
|
+
polygon,
|
|
130
|
+
polyline,
|
|
131
|
+
path,
|
|
132
|
+
circle {
|
|
133
|
+
stroke: var(
|
|
134
|
+
--action-bar-action-disabled-color,
|
|
135
|
+
$action-bar-action-disabled-color
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import React, { type PropsWithChildren } from 'react';
|
|
3
|
+
import { Link } from 'react-router-dom';
|
|
4
|
+
import { noop } from '../../../helpers/utils';
|
|
5
|
+
import { useConfirmableAction } from '../../ConfirmDialog';
|
|
6
|
+
import { IconName, Icons } from '../../Icons';
|
|
7
|
+
import { ImgElement } from '../../VisualElements';
|
|
8
|
+
import type {
|
|
9
|
+
ActionBarAction,
|
|
10
|
+
ActionBarJsAction,
|
|
11
|
+
ActionBarNavigationAction,
|
|
12
|
+
} from '../ActionBar.model';
|
|
13
|
+
import { isActionBarNavigationAction } from '../helpers';
|
|
14
|
+
import classes from './ActionBarButton.module.scss';
|
|
15
|
+
|
|
16
|
+
export interface ActionBarButtonProps {
|
|
17
|
+
/** The action to render */
|
|
18
|
+
action: ActionBarAction;
|
|
19
|
+
/** CSS Class name for additional styles */
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Renders a single action of the ActionBar: a button running the handler of the
|
|
25
|
+
* action, or a link if the action has a `path`.
|
|
26
|
+
*
|
|
27
|
+
* Supports the same confirmation modes as the other action components: in
|
|
28
|
+
* 'Simple' mode the label is replaced by a confirmation message until the
|
|
29
|
+
* action is clicked a second time, in 'Advanced' mode a confirmation dialog is
|
|
30
|
+
* opened above the action.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* <ActionBarButton action={{ label: 'Delete', icon: IconName.Delete, onClick: deleteHandler }} />
|
|
34
|
+
*/
|
|
35
|
+
export const ActionBarButton: React.FC<ActionBarButtonProps> = ({
|
|
36
|
+
action,
|
|
37
|
+
className,
|
|
38
|
+
}) =>
|
|
39
|
+
isActionBarNavigationAction(action) ? (
|
|
40
|
+
<NavigationActionButton action={action} className={className} />
|
|
41
|
+
) : (
|
|
42
|
+
<JsActionButton action={action} className={className} />
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const JsActionButton: React.FC<{
|
|
46
|
+
action: ActionBarJsAction;
|
|
47
|
+
className?: string;
|
|
48
|
+
}> = ({ action, className }) => {
|
|
49
|
+
const {
|
|
50
|
+
label,
|
|
51
|
+
icon,
|
|
52
|
+
imgAlt,
|
|
53
|
+
tag,
|
|
54
|
+
disabled = false,
|
|
55
|
+
toggled,
|
|
56
|
+
confirmationMode = 'None',
|
|
57
|
+
confirmationConfig = {},
|
|
58
|
+
onClick = noop,
|
|
59
|
+
} = action;
|
|
60
|
+
|
|
61
|
+
const {
|
|
62
|
+
isConfirming,
|
|
63
|
+
isSimpleConfirmation,
|
|
64
|
+
onTriggerClicked,
|
|
65
|
+
triggerProps,
|
|
66
|
+
simpleConfirmation,
|
|
67
|
+
advancedConfirmation,
|
|
68
|
+
} = useConfirmableAction<HTMLButtonElement>({
|
|
69
|
+
confirmationMode,
|
|
70
|
+
confirmationConfig,
|
|
71
|
+
onConfirmed: onClick,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<>
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
className={clsx(
|
|
79
|
+
classes.container,
|
|
80
|
+
{
|
|
81
|
+
[classes.toggled]: toggled,
|
|
82
|
+
[classes.hasConfirm]: isConfirming,
|
|
83
|
+
},
|
|
84
|
+
'action-bar-action-container',
|
|
85
|
+
className,
|
|
86
|
+
)}
|
|
87
|
+
{...triggerProps}
|
|
88
|
+
onClick={(event) => {
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
onTriggerClicked();
|
|
91
|
+
}}
|
|
92
|
+
disabled={disabled}
|
|
93
|
+
aria-pressed={toggled}
|
|
94
|
+
data-test-id="action"
|
|
95
|
+
data-test-toggled={toggled}
|
|
96
|
+
>
|
|
97
|
+
{/* The confirmation message takes up the room of the icon and the tag */}
|
|
98
|
+
<ActionContent
|
|
99
|
+
icon={isConfirming ? undefined : icon}
|
|
100
|
+
imgAlt={imgAlt}
|
|
101
|
+
tag={isConfirming ? undefined : tag}
|
|
102
|
+
>
|
|
103
|
+
{isSimpleConfirmation ? (
|
|
104
|
+
simpleConfirmation
|
|
105
|
+
) : (
|
|
106
|
+
<span data-test-id="label">{label}</span>
|
|
107
|
+
)}
|
|
108
|
+
</ActionContent>
|
|
109
|
+
</button>
|
|
110
|
+
{advancedConfirmation}
|
|
111
|
+
</>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Renders a link to the path of the action. A disabled action renders as a
|
|
117
|
+
* disabled button instead, as a link cannot be disabled.
|
|
118
|
+
*/
|
|
119
|
+
const NavigationActionButton: React.FC<{
|
|
120
|
+
action: ActionBarNavigationAction;
|
|
121
|
+
className?: string;
|
|
122
|
+
}> = ({ action, className }) => {
|
|
123
|
+
const {
|
|
124
|
+
label,
|
|
125
|
+
icon,
|
|
126
|
+
imgAlt,
|
|
127
|
+
tag,
|
|
128
|
+
disabled = false,
|
|
129
|
+
path,
|
|
130
|
+
openInNewTab = false,
|
|
131
|
+
} = action;
|
|
132
|
+
|
|
133
|
+
const classNames = clsx(
|
|
134
|
+
classes.container,
|
|
135
|
+
'action-bar-action-container',
|
|
136
|
+
className,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const content = (
|
|
140
|
+
<ActionContent
|
|
141
|
+
icon={icon ?? (openInNewTab ? IconName.External : undefined)}
|
|
142
|
+
imgAlt={imgAlt}
|
|
143
|
+
tag={tag}
|
|
144
|
+
>
|
|
145
|
+
<span data-test-id="label">{label}</span>
|
|
146
|
+
</ActionContent>
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return disabled ? (
|
|
150
|
+
<button type="button" className={classNames} disabled data-test-id="action">
|
|
151
|
+
{content}
|
|
152
|
+
</button>
|
|
153
|
+
) : (
|
|
154
|
+
<Link
|
|
155
|
+
to={path}
|
|
156
|
+
target={openInNewTab ? '_blank' : undefined}
|
|
157
|
+
className={classNames}
|
|
158
|
+
data-test-id="action"
|
|
159
|
+
>
|
|
160
|
+
{content}
|
|
161
|
+
</Link>
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/** The icon, label and tag of an action. */
|
|
166
|
+
const ActionContent: React.FC<
|
|
167
|
+
PropsWithChildren<{
|
|
168
|
+
icon?: IconName | string;
|
|
169
|
+
imgAlt?: string;
|
|
170
|
+
tag?: string;
|
|
171
|
+
}>
|
|
172
|
+
> = ({ icon, imgAlt, tag, children }) => (
|
|
173
|
+
<>
|
|
174
|
+
{icon !== undefined && (
|
|
175
|
+
<span className={classes.icon}>
|
|
176
|
+
{typeof icon === 'string' ? (
|
|
177
|
+
<ImgElement
|
|
178
|
+
src={icon}
|
|
179
|
+
{...(imgAlt ? { alt: imgAlt } : { decorative: true as const })}
|
|
180
|
+
/>
|
|
181
|
+
) : (
|
|
182
|
+
<Icons icon={icon} className={classes.actionIcon} />
|
|
183
|
+
)}
|
|
184
|
+
</span>
|
|
185
|
+
)}
|
|
186
|
+
<span className={classes.label}>{children}</span>
|
|
187
|
+
{tag && (
|
|
188
|
+
<span className={classes.tag} data-test-id="tag">
|
|
189
|
+
{tag}
|
|
190
|
+
</span>
|
|
191
|
+
)}
|
|
192
|
+
</>
|
|
193
|
+
);
|