@camunda/camunda-composite-components 0.23.3 → 0.24.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/lib/esm/package.json +26 -26
- package/lib/esm/src/components/c3-data-table/c3-data-table.js +1 -1
- package/lib/esm/src/components/c3-help-center/c3-help-center-provider.d.ts +2 -1
- package/lib/esm/src/components/c3-help-center/c3-help-center-provider.js +4 -1
- package/lib/esm/src/components/c3-help-center/c3-help-center.d.ts +2 -1
- package/lib/esm/src/components/c3-help-center/c3-help-center.js +4 -3
- package/lib/esm/src/components/c3-license-tag/c3-license-tag.d.ts +4 -5
- package/lib/esm/src/components/c3-license-tag/c3-license-tag.js +54 -47
- package/lib/esm/src/components/c3-navigation/c3-navigation-appbar/c3-navigation-appbar.js +6 -6
- package/lib/esm/src/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar-element.js +3 -0
- package/lib/esm/src/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar.js +10 -1
- package/lib/esm/src/components/c3-navigation/helpers.js +12 -0
- package/lib/esm/src/components/c3-navigation-v2/c3-breadcrumb-bar.js +33 -31
- package/lib/esm/src/components/c3-navigation-v2/c3-navigation-v2.js +7 -1
- package/lib/esm/src/components/c3-navigation-v2/c3-navigation-v2.types.d.ts +18 -0
- package/lib/esm/src/components/c3-navigation-v2/c3-sidebar.d.ts +1 -1
- package/lib/esm/src/components/c3-navigation-v2/c3-sidebar.js +82 -84
- package/lib/esm/src/components/c3-navigation-v2/c3-tools-area.js +18 -5
- package/lib/esm/src/components/c3-navigation-v2/index.d.ts +5 -3
- package/lib/esm/src/components/c3-navigation-v2/index.js +1 -0
- package/lib/esm/src/components/c3-navigation-v2/stories/story-templates.d.ts +1 -0
- package/lib/esm/src/components/c3-navigation-v2/stories/story-templates.js +112 -0
- package/lib/esm/src/components/c3-navigation-v2/tools/c3-info-panel.d.ts +2 -1
- package/lib/esm/src/components/c3-navigation-v2/tools/c3-info-panel.js +1 -1
- package/lib/esm/src/components/c3-navigation-v2/tools/c3-notifications-panel.d.ts +11 -0
- package/lib/esm/src/components/c3-navigation-v2/tools/c3-notifications-panel.js +9 -5
- package/lib/esm/src/components/c3-navigation-v2/tools/c3-theme-selector.d.ts +25 -0
- package/lib/esm/src/components/c3-navigation-v2/tools/c3-theme-selector.js +15 -0
- package/lib/esm/src/components/c3-navigation-v2/tools/c3-user-panel.d.ts +15 -0
- package/lib/esm/src/components/c3-navigation-v2/tools/c3-user-panel.js +10 -17
- package/lib/esm/src/components/c3-navigation-v2/use-c3-navigation-v2.d.ts +3 -1
- package/lib/esm/src/components/c3-navigation-v2/use-c3-navigation-v2.js +2 -1
- package/lib/esm/src/components/c3-navigation-v2/use-camunda-tools.d.ts +28 -5
- package/lib/esm/src/components/c3-navigation-v2/use-camunda-tools.js +42 -23
- package/lib/esm/src/components/c3-navigation-v2/use-cluster-sidebar-entries.js +10 -8
- package/lib/esm/src/components/c3-navigation-v2/use-cluster-webapp-breadcrumbs.d.ts +16 -18
- package/lib/esm/src/components/c3-navigation-v2/use-cluster-webapp-breadcrumbs.js +146 -36
- package/lib/esm/src/index.d.ts +2 -2
- package/lib/esm/src/index.js +1 -1
- package/package.json +27 -27
|
@@ -6,8 +6,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
*/
|
|
7
7
|
import { Popover, PopoverContent } from '@carbon/react';
|
|
8
8
|
import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp, } from '@carbon/react/icons/index.esm.js';
|
|
9
|
-
import { useEffect, useRef, useState } from 'react';
|
|
10
|
-
import styled from 'styled-components';
|
|
9
|
+
import { createContext, useContext, useEffect, useRef, useState, } from 'react';
|
|
10
|
+
import styled, { css } from 'styled-components';
|
|
11
|
+
const SidebarLabelsContext = createContext({
|
|
12
|
+
groupToggleAria: ({ label, isExpanded }) => isExpanded ? `Collapse ${label}` : `Expand ${label}`,
|
|
13
|
+
});
|
|
14
|
+
const useSidebarLabels = () => useContext(SidebarLabelsContext);
|
|
11
15
|
const SidebarNav = styled.nav `
|
|
12
16
|
position: fixed;
|
|
13
17
|
top: 3rem;
|
|
@@ -36,13 +40,12 @@ const NavButton = styled.button `
|
|
|
36
40
|
flex-wrap: nowrap;
|
|
37
41
|
gap: var(--cds-spacing-04);
|
|
38
42
|
width: 100%;
|
|
39
|
-
|
|
43
|
+
height: var(--cds-spacing-09);
|
|
44
|
+
box-sizing: border-box;
|
|
40
45
|
padding: ${(p) => {
|
|
41
46
|
if (p.$depth > 0)
|
|
42
|
-
return `0
|
|
43
|
-
return p.$isExpanded
|
|
44
|
-
? 'var(--cds-spacing-04) var(--cds-spacing-05)'
|
|
45
|
-
: 'var(--cds-spacing-04)';
|
|
47
|
+
return `0 var(--cds-spacing-05) 0 ${0.75 + p.$depth * 1.75}rem`;
|
|
48
|
+
return p.$isExpanded ? '0 var(--cds-spacing-05)' : '0';
|
|
46
49
|
}};
|
|
47
50
|
justify-content: ${(p) => (p.$isExpanded ? 'flex-start' : 'center')};
|
|
48
51
|
background: ${(p) => (p.$isActive ? 'var(--cds-layer-selected)' : 'transparent')};
|
|
@@ -76,30 +79,43 @@ const NavLabel = styled.span `
|
|
|
76
79
|
text-overflow: ellipsis;
|
|
77
80
|
white-space: nowrap;
|
|
78
81
|
`;
|
|
82
|
+
/**
|
|
83
|
+
* Container row for `group` (pure section) and `group-item` (clickable +
|
|
84
|
+
* expandable) variants. `$clickable` adds the navigable affordance:
|
|
85
|
+
* hover background + transition. `$isActive` flips the selected styling.
|
|
86
|
+
*/
|
|
79
87
|
const GroupHeader = styled.div `
|
|
80
88
|
display: flex;
|
|
81
89
|
align-items: center;
|
|
82
90
|
flex-wrap: nowrap;
|
|
83
91
|
width: 100%;
|
|
84
|
-
|
|
92
|
+
height: var(--cds-spacing-09);
|
|
93
|
+
box-sizing: border-box;
|
|
94
|
+
padding-right: var(--cds-spacing-04);
|
|
95
|
+
gap: var(--cds-spacing-03);
|
|
85
96
|
overflow: hidden;
|
|
86
97
|
background: ${(p) => (p.$isActive ? 'var(--cds-layer-selected)' : 'transparent')};
|
|
87
98
|
border-left: ${(p) => (p.$isActive ? '3px solid var(--cds-border-interactive)' : '3px solid transparent')};
|
|
88
|
-
transition: background 0.15s, color 0.15s;
|
|
89
99
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
100
|
+
${(p) => p.$clickable &&
|
|
101
|
+
css `
|
|
102
|
+
transition: background 0.15s, color 0.15s;
|
|
103
|
+
|
|
104
|
+
&:hover:not(:has(button[data-expand]:hover)) {
|
|
105
|
+
background: ${p.$isActive ? 'var(--cds-layer-selected)' : 'var(--cds-layer-hover)'};
|
|
106
|
+
}
|
|
107
|
+
`}
|
|
93
108
|
`;
|
|
94
109
|
const GroupLabelButton = styled.button `
|
|
95
110
|
display: flex;
|
|
96
111
|
align-items: center;
|
|
112
|
+
align-self: stretch;
|
|
97
113
|
flex: 1;
|
|
98
114
|
min-width: 0;
|
|
99
115
|
padding: ${(p) => {
|
|
100
116
|
if (p.$depth > 0)
|
|
101
|
-
return `
|
|
102
|
-
return '
|
|
117
|
+
return `0 0 0 ${0.75 + p.$depth * 1.75}rem`;
|
|
118
|
+
return '0 0 0 var(--cds-spacing-05)';
|
|
103
119
|
}};
|
|
104
120
|
background: transparent;
|
|
105
121
|
border: none;
|
|
@@ -126,8 +142,11 @@ const ExpandButton = styled.button `
|
|
|
126
142
|
display: flex;
|
|
127
143
|
align-items: center;
|
|
128
144
|
justify-content: center;
|
|
129
|
-
|
|
130
|
-
|
|
145
|
+
flex-shrink: 0;
|
|
146
|
+
align-self: center;
|
|
147
|
+
width: var(--cds-spacing-07);
|
|
148
|
+
height: var(--cds-spacing-07);
|
|
149
|
+
padding: 0;
|
|
131
150
|
background: transparent;
|
|
132
151
|
border: none;
|
|
133
152
|
border-radius: 4px;
|
|
@@ -144,52 +163,22 @@ const ExpandButton = styled.button `
|
|
|
144
163
|
outline-offset: -2px;
|
|
145
164
|
}
|
|
146
165
|
`;
|
|
147
|
-
const PlainGroupHeader = styled.div `
|
|
148
|
-
display: flex;
|
|
149
|
-
align-items: center;
|
|
150
|
-
flex-wrap: nowrap;
|
|
151
|
-
width: 100%;
|
|
152
|
-
min-height: 2.5rem;
|
|
153
|
-
overflow: hidden;
|
|
154
|
-
border-left: 3px solid transparent;
|
|
155
|
-
`;
|
|
156
166
|
const PlainGroupLabel = styled.span `
|
|
157
167
|
display: flex;
|
|
158
168
|
align-items: center;
|
|
169
|
+
align-self: stretch;
|
|
159
170
|
flex: 1;
|
|
160
171
|
min-width: 0;
|
|
161
172
|
padding: ${(p) => {
|
|
162
173
|
if (p.$depth > 0)
|
|
163
|
-
return `
|
|
164
|
-
return '
|
|
174
|
+
return `0 0 0 ${0.75 + p.$depth * 1.75}rem`;
|
|
175
|
+
return '0 0 0 var(--cds-spacing-05)';
|
|
165
176
|
}};
|
|
166
177
|
gap: var(--cds-spacing-04);
|
|
167
178
|
color: var(--cds-text-secondary);
|
|
168
179
|
font-size: 0.875rem;
|
|
169
180
|
font-weight: 400;
|
|
170
181
|
`;
|
|
171
|
-
const PlainGroupExpandButton = styled.button `
|
|
172
|
-
display: flex;
|
|
173
|
-
align-items: center;
|
|
174
|
-
justify-content: center;
|
|
175
|
-
padding: var(--cds-spacing-02);
|
|
176
|
-
margin-right: var(--cds-spacing-04);
|
|
177
|
-
background: transparent;
|
|
178
|
-
border: none;
|
|
179
|
-
border-radius: 4px;
|
|
180
|
-
cursor: pointer;
|
|
181
|
-
color: var(--cds-icon-secondary);
|
|
182
|
-
transition: background 0.15s;
|
|
183
|
-
|
|
184
|
-
&:hover {
|
|
185
|
-
background: var(--cds-layer-hover);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
&:focus-visible {
|
|
189
|
-
outline: 2px solid var(--cds-focus);
|
|
190
|
-
outline-offset: -2px;
|
|
191
|
-
}
|
|
192
|
-
`;
|
|
193
182
|
const StyledPopover = styled(Popover) `
|
|
194
183
|
display: block;
|
|
195
184
|
|
|
@@ -200,7 +189,9 @@ const StyledPopover = styled(Popover) `
|
|
|
200
189
|
white-space: nowrap;
|
|
201
190
|
}
|
|
202
191
|
`;
|
|
203
|
-
|
|
192
|
+
// `enabled={false}` keeps the wrapper but skips the popover. Lets a parent
|
|
193
|
+
// button keep its focus across state toggles (see the collapse button below).
|
|
194
|
+
const CollapsedItemTooltip = ({ label, enabled = true, children, }) => {
|
|
204
195
|
const [open, setOpen] = useState(false);
|
|
205
196
|
const timerRef = useRef(null);
|
|
206
197
|
const handleMouseEnter = () => {
|
|
@@ -217,7 +208,7 @@ const CollapsedItemTooltip = ({ label, children, }) => {
|
|
|
217
208
|
if (timerRef.current)
|
|
218
209
|
clearTimeout(timerRef.current);
|
|
219
210
|
}, []);
|
|
220
|
-
return (_jsxs(StyledPopover, { open: open, align: 'right', highContrast: true, dropShadow: false, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: [children, _jsx(PopoverContent, { children: label })] }));
|
|
211
|
+
return (_jsxs(StyledPopover, { open: enabled && open, align: 'right', highContrast: true, dropShadow: false, onMouseEnter: enabled ? handleMouseEnter : undefined, onMouseLeave: enabled ? handleMouseLeave : undefined, children: [children, _jsx(PopoverContent, { children: label })] }));
|
|
221
212
|
};
|
|
222
213
|
const SectionDivider = styled.div `
|
|
223
214
|
border-top: ${(p) => (p.$hideTopDivider ? 'none' : '1px solid var(--cds-border-subtle-01)')};
|
|
@@ -248,10 +239,9 @@ const CollapseButton = styled.button `
|
|
|
248
239
|
flex-wrap: nowrap;
|
|
249
240
|
gap: var(--cds-spacing-04);
|
|
250
241
|
width: 100%;
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
: 'var(--cds-spacing-04)'};
|
|
242
|
+
height: var(--cds-spacing-09);
|
|
243
|
+
box-sizing: border-box;
|
|
244
|
+
padding: ${(p) => (p.$isExpanded ? '0 var(--cds-spacing-05)' : '0')};
|
|
255
245
|
justify-content: ${(p) => (p.$isExpanded ? 'flex-start' : 'center')};
|
|
256
246
|
background: transparent;
|
|
257
247
|
border: none;
|
|
@@ -323,7 +313,7 @@ const GroupItemNode = ({ node, sidebarExpanded, depth, linkComponent, }) => {
|
|
|
323
313
|
const collapsed = (_jsx(NavButton, { as: resolveLinkAs(node.linkProps, linkComponent), ...(node.linkProps ?? {}), "$isActive": isActive, "$isExpanded": false, "$depth": 0, onClick: node.onClick ?? node.onToggleExpand, "aria-current": isActive ? 'page' : undefined, children: _jsx(Icon, { size: 20, style: { flexShrink: 0 } }) }));
|
|
324
314
|
return (_jsx(CollapsedItemTooltip, { label: node.label, children: collapsed }));
|
|
325
315
|
}
|
|
326
|
-
return (_jsxs("div", { children: [_jsxs(GroupHeader, { "$isActive": isActive, "$
|
|
316
|
+
return (_jsxs("div", { children: [_jsxs(GroupHeader, { "$isActive": isActive, "$clickable": true, children: [_jsxs(GroupLabelButton, { as: resolveLinkAs(node.linkProps, linkComponent), ...(node.linkProps ?? {}), "$isActive": isActive, "$isClickable": true, "$depth": depth, onClick: node.onClick, "aria-current": isActive ? 'page' : undefined, children: [_jsx(Icon, { size: 20, style: { flexShrink: 0 } }), _jsx(NavLabel, { children: node.label }), node.trailingElement] }), node.onToggleExpand && node.children.length > 0 && (_jsx(ExpandButton, { "data-expand": true, onClick: (e) => {
|
|
327
317
|
e.stopPropagation();
|
|
328
318
|
node.onToggleExpand?.();
|
|
329
319
|
}, "aria-label": node.isExpanded
|
|
@@ -333,13 +323,15 @@ const GroupItemNode = ({ node, sidebarExpanded, depth, linkComponent, }) => {
|
|
|
333
323
|
};
|
|
334
324
|
const GroupNode = ({ node, sidebarExpanded, depth, linkComponent, }) => {
|
|
335
325
|
const Icon = node.icon;
|
|
326
|
+
const { groupToggleAria } = useSidebarLabels();
|
|
336
327
|
if (!sidebarExpanded) {
|
|
337
328
|
const collapsed = (_jsx(NavButton, { "$isActive": false, "$isExpanded": false, "$depth": 0, onClick: node.onToggleExpand, children: _jsx(Icon, { size: 20, style: { flexShrink: 0 } }) }));
|
|
338
329
|
return (_jsx(CollapsedItemTooltip, { label: node.label, children: collapsed }));
|
|
339
330
|
}
|
|
340
|
-
return (_jsxs("div", { children: [_jsxs(
|
|
341
|
-
|
|
342
|
-
:
|
|
331
|
+
return (_jsxs("div", { children: [_jsxs(GroupHeader, { children: [_jsxs(PlainGroupLabel, { "$depth": depth, children: [_jsx(Icon, { size: 20, style: { flexShrink: 0 } }), _jsx(NavLabel, { children: node.label }), node.trailingElement] }), node.onToggleExpand && (_jsx(ExpandButton, { "data-expand": true, onClick: node.onToggleExpand, "aria-label": groupToggleAria({
|
|
332
|
+
label: node.label,
|
|
333
|
+
isExpanded: !!node.isExpanded,
|
|
334
|
+
}), "aria-expanded": !!node.isExpanded, children: node.isExpanded ? (_jsx(ChevronUp, { size: 16 })) : (_jsx(ChevronDown, { size: 16 })) }))] }), node.isExpanded &&
|
|
343
335
|
node.children.map((child) => (_jsx(SidebarNodeComponent, { node: child, sidebarExpanded: sidebarExpanded, depth: depth + 1, linkComponent: linkComponent }, child.key)))] }));
|
|
344
336
|
};
|
|
345
337
|
const SectionNode = ({ node, sidebarExpanded, linkComponent, hideTopDivider, eatScrollPadding, tight, }) => (_jsxs(SectionDivider, { "$hideTopDivider": hideTopDivider, "$eatScrollPadding": eatScrollPadding, "$tight": tight, children: [sidebarExpanded && node.title && _jsx(SectionTitle, { children: node.title }), node.children.map((child) => (_jsx(SidebarNodeComponent, { node: child, sidebarExpanded: sidebarExpanded, depth: 0, linkComponent: linkComponent }, child.key)))] }));
|
|
@@ -364,31 +356,37 @@ const SidebarNodeComponent = ({ node, sidebarExpanded, depth, linkComponent, hid
|
|
|
364
356
|
* Loose items (not wrapped in a section) are valid and behave as an implicit
|
|
365
357
|
* untitled group at the top.
|
|
366
358
|
*/
|
|
367
|
-
export const C3Sidebar = ({ ariaLabel, children: nodes, isExpanded = true, onToggleExpanded, expandedWidth = '16rem', collapsedWidth = '3rem', linkComponent, }) => {
|
|
359
|
+
export const C3Sidebar = ({ ariaLabel, children: nodes, isExpanded = true, onToggleExpanded, expandedWidth = '16rem', collapsedWidth = '3rem', linkComponent, labels, }) => {
|
|
368
360
|
const width = isExpanded ? expandedWidth : collapsedWidth;
|
|
369
361
|
const prunedNodes = pruneChildren(nodes);
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
let
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
eatScrollPadding =
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
362
|
+
const collapseLabel = labels?.collapse ?? 'Collapse';
|
|
363
|
+
const expandLabel = labels?.expand ?? 'Expand';
|
|
364
|
+
const toggleAria = labels?.toggleAriaLabel ??
|
|
365
|
+
((expanded) => (expanded ? 'Collapse sidebar' : 'Expand sidebar'));
|
|
366
|
+
const groupToggleAria = labels?.groupToggleAriaLabel ??
|
|
367
|
+
(({ label, isExpanded: e }) => e ? `Collapse ${label}` : `Expand ${label}`);
|
|
368
|
+
return (_jsx(SidebarLabelsContext.Provider, { value: { groupToggleAria }, children: _jsxs(SidebarNav, { "$width": width, "aria-label": ariaLabel, children: [_jsx(ScrollArea, { "$sidebarExpanded": isExpanded, children: (() => {
|
|
369
|
+
let sectionSeen = false;
|
|
370
|
+
let prevSectionCompact = false;
|
|
371
|
+
let hasNonSectionNodes = false;
|
|
372
|
+
return prunedNodes.map((node) => {
|
|
373
|
+
let hideTopDivider = false;
|
|
374
|
+
let eatScrollPadding = false;
|
|
375
|
+
let tight = false;
|
|
376
|
+
if (node.type === 'section') {
|
|
377
|
+
const isFirst = !sectionSeen;
|
|
378
|
+
hideTopDivider =
|
|
379
|
+
(isFirst && !hasNonSectionNodes) || !!node.compact;
|
|
380
|
+
eatScrollPadding =
|
|
381
|
+
isFirst && !hasNonSectionNodes && !!node.compact;
|
|
382
|
+
tight = prevSectionCompact;
|
|
383
|
+
sectionSeen = true;
|
|
384
|
+
prevSectionCompact = !!node.compact;
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
hasNonSectionNodes = true;
|
|
388
|
+
}
|
|
389
|
+
return (_jsx(SidebarNodeComponent, { node: node, sidebarExpanded: isExpanded, depth: 0, linkComponent: linkComponent, hideTopDivider: hideTopDivider, eatScrollPadding: eatScrollPadding, tight: tight }, node.key));
|
|
390
|
+
});
|
|
391
|
+
})() }), onToggleExpanded && (_jsx(CollapseToggleArea, { children: _jsx(CollapsedItemTooltip, { label: expandLabel, enabled: !isExpanded, children: _jsxs(CollapseButton, { "$isExpanded": isExpanded, onClick: onToggleExpanded, "aria-label": toggleAria(isExpanded), "aria-expanded": isExpanded, children: [isExpanded ? (_jsx(ChevronLeft, { size: 20, style: { flexShrink: 0 } })) : (_jsx(ChevronRight, { size: 20, style: { flexShrink: 0 } })), isExpanded && _jsx(NavLabel, { children: collapseLabel })] }) }) }))] }) }));
|
|
394
392
|
};
|
|
@@ -97,9 +97,22 @@ export const C3ToolsArea = ({ tools, activeToolKey: controlledKey, onActiveToolC
|
|
|
97
97
|
return;
|
|
98
98
|
setActive(null);
|
|
99
99
|
}, [setActive]);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
// Shift+Tab at the panel's first focusable: close the panel and return
|
|
101
|
+
// focus to the trigger button, instead of letting focus escape to whatever
|
|
102
|
+
// sits before the portaled panel in DOM order.
|
|
103
|
+
const handlePanelKeyDown = useCallback((event) => {
|
|
104
|
+
if (event.key !== 'Tab' || !event.shiftKey)
|
|
105
|
+
return;
|
|
106
|
+
const first = panelRef.current?.querySelector(FOCUSABLE_SELECTOR);
|
|
107
|
+
if (!first || event.target !== first)
|
|
108
|
+
return;
|
|
109
|
+
event.preventDefault();
|
|
110
|
+
setActive(null);
|
|
111
|
+
openerRef.current?.focus();
|
|
112
|
+
}, [setActive]);
|
|
113
|
+
return (_jsxs(_Fragment, { children: [tools.map((tool) => {
|
|
114
|
+
const RenderButton = tool.renderButton;
|
|
115
|
+
return (_jsx(Fragment, { children: _jsx(RenderButton, { onClick: () => handleToolClick(tool.key, Boolean(tool.panel)), isActive: activeKey === tool.key }) }, tool.key));
|
|
116
|
+
}), activeTool?.panel &&
|
|
117
|
+
createPortal(_jsx(ToolsPanel, { ref: panelRef, role: 'complementary', "aria-label": activeTool.label, tabIndex: -1, onBlur: handlePanelBlur, onKeyDown: handlePanelKeyDown, children: activeTool.panel }), document.body)] }));
|
|
105
118
|
};
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
export type { CamundaApp } from '../../utils/camunda.types';
|
|
2
2
|
export { C3BreadcrumbBar } from './c3-breadcrumb-bar';
|
|
3
3
|
export { C3NavigationV2 } from './c3-navigation-v2';
|
|
4
|
-
export type { AppProps, BreadcrumbAction, BreadcrumbDropdownItem, BreadcrumbSegment, C3NavigationV2Props, GlobalActionButton, LinkComponent, LinkProps, SidebarGroup, SidebarGroupItem, SidebarItem, SidebarNode, SidebarProps, SidebarSection, ToolDescriptor, } from './c3-navigation-v2.types';
|
|
4
|
+
export type { AppProps, BreadcrumbAction, BreadcrumbDropdownItem, BreadcrumbSegment, C3NavigationV2Props, GlobalActionButton, LinkComponent, LinkProps, SidebarGroup, SidebarGroupItem, SidebarItem, SidebarLabels, SidebarNode, SidebarProps, SidebarSection, ToolDescriptor, } from './c3-navigation-v2.types';
|
|
5
5
|
export { C3Sidebar } from './c3-sidebar';
|
|
6
6
|
export { C3ToolsArea } from './c3-tools-area';
|
|
7
7
|
export type { C3InfoPanelProps, InfoPanelElement } from './tools/c3-info-panel';
|
|
8
8
|
export { C3InfoPanel } from './tools/c3-info-panel';
|
|
9
|
-
export type { C3NotificationsPanelProps } from './tools/c3-notifications-panel';
|
|
9
|
+
export type { C3NotificationsPanelLabels, C3NotificationsPanelProps, } from './tools/c3-notifications-panel';
|
|
10
10
|
export { C3NotificationsPanel } from './tools/c3-notifications-panel';
|
|
11
|
-
export type {
|
|
11
|
+
export type { C3ThemeSelectorLabels, C3ThemeSelectorProps, } from './tools/c3-theme-selector';
|
|
12
|
+
export { C3ThemeSelector } from './tools/c3-theme-selector';
|
|
13
|
+
export type { C3UserPanelLabels, C3UserPanelProps, UserPanelElement, } from './tools/c3-user-panel';
|
|
12
14
|
export { C3UserPanel } from './tools/c3-user-panel';
|
|
13
15
|
export type { BreadcrumbDescriptor, BreadcrumbDropdownItemDescriptor, GroupDescriptor, GroupItemDescriptor, ItemDescriptor, SectionDescriptor, SidebarNodeDescriptor, UseC3NavigationV2Options, UseC3NavigationV2Return, } from './use-c3-navigation-v2';
|
|
14
16
|
export { useC3NavigationV2 } from './use-c3-navigation-v2';
|
|
@@ -9,6 +9,7 @@ export { C3Sidebar } from './c3-sidebar.js';
|
|
|
9
9
|
export { C3ToolsArea } from './c3-tools-area.js';
|
|
10
10
|
export { C3InfoPanel } from './tools/c3-info-panel.js';
|
|
11
11
|
export { C3NotificationsPanel } from './tools/c3-notifications-panel.js';
|
|
12
|
+
export { C3ThemeSelector } from './tools/c3-theme-selector.js';
|
|
12
13
|
export { C3UserPanel } from './tools/c3-user-panel.js';
|
|
13
14
|
export { useC3NavigationV2 } from './use-c3-navigation-v2.js';
|
|
14
15
|
export { useCamundaTools } from './use-camunda-tools.js';
|
|
@@ -12,3 +12,4 @@ export declare const PruningTemplate: FC;
|
|
|
12
12
|
export declare const BuildClusterSidebarEntriesTemplate: FC;
|
|
13
13
|
export declare const CompactSectionTemplate: FC;
|
|
14
14
|
export declare const GlobalActionWithCustomElementTemplate: FC;
|
|
15
|
+
export declare const LongBreadcrumbsTemplate: FC;
|
|
@@ -1379,3 +1379,115 @@ export const GlobalActionWithCustomElementTemplate = () => {
|
|
|
1379
1379
|
});
|
|
1380
1380
|
return (_jsxs(_Fragment, { children: [_jsx(C3NavigationV2, { ...navProps }), _jsxs(MainContent, { sidebarExpanded: isSidebarExpanded, children: [_jsx("h1", { children: "Global Action with Custom Element" }), _jsxs("p", { style: { marginTop: '1rem', color: 'var(--cds-text-secondary)' }, children: ["When a ", _jsx("code", { children: "globalActions" }), " entry supplies an", ' ', _jsx("code", { children: "element" }), ", the navigation renders it as a direct child of the header bar (no wrapper) so popups and inputs that rely on a stable positioning context (e.g. C4Search) work correctly. Click the search icon to expand the input and verify the popup positions against the search field, not against an unrelated parent."] })] })] }));
|
|
1381
1381
|
};
|
|
1382
|
+
// ─── Long breadcrumbs (header space allocation) ─────────────────────────────
|
|
1383
|
+
export const LongBreadcrumbsTemplate = () => {
|
|
1384
|
+
const { navProps, isSidebarExpanded } = useC3NavigationV2({
|
|
1385
|
+
app: { ariaLabel: 'Camunda Modeler', linkProps: { href: '#' } },
|
|
1386
|
+
skipToContentTargetId: 'main-content',
|
|
1387
|
+
activeItemKey: 'readme',
|
|
1388
|
+
breadcrumbs: [
|
|
1389
|
+
{
|
|
1390
|
+
key: 'org',
|
|
1391
|
+
label: 'Globex Megacorp International Holdings GmbH & Co. KG',
|
|
1392
|
+
icon: Building,
|
|
1393
|
+
dropdownTitle: 'Switch organization',
|
|
1394
|
+
dropdownItems: [
|
|
1395
|
+
{
|
|
1396
|
+
key: 'org-globex',
|
|
1397
|
+
label: 'Globex Megacorp International Holdings GmbH & Co. KG',
|
|
1398
|
+
icon: Building,
|
|
1399
|
+
isSelected: true,
|
|
1400
|
+
},
|
|
1401
|
+
{ key: 'org-acme', label: 'Acme Corp', icon: Building },
|
|
1402
|
+
{ key: 'org-beta', label: 'Beta Inc', icon: Building },
|
|
1403
|
+
],
|
|
1404
|
+
},
|
|
1405
|
+
{
|
|
1406
|
+
key: 'cluster',
|
|
1407
|
+
label: 'eu-west-3 production cluster (long-lived, customer-facing)',
|
|
1408
|
+
icon: CloudApp,
|
|
1409
|
+
dropdownTitle: 'Switch cluster',
|
|
1410
|
+
dropdownItems: [
|
|
1411
|
+
{
|
|
1412
|
+
key: 'cluster-prod-eu',
|
|
1413
|
+
label: 'eu-west-3 production cluster (long-lived, customer-facing)',
|
|
1414
|
+
icon: CloudApp,
|
|
1415
|
+
isSelected: true,
|
|
1416
|
+
},
|
|
1417
|
+
{
|
|
1418
|
+
key: 'cluster-staging',
|
|
1419
|
+
label: 'staging cluster',
|
|
1420
|
+
icon: CloudApp,
|
|
1421
|
+
},
|
|
1422
|
+
],
|
|
1423
|
+
actions: [
|
|
1424
|
+
{
|
|
1425
|
+
key: 'pause',
|
|
1426
|
+
label: 'Pause cluster',
|
|
1427
|
+
onClick: () => console.log('pause cluster'),
|
|
1428
|
+
},
|
|
1429
|
+
{
|
|
1430
|
+
key: 'rename-cluster',
|
|
1431
|
+
label: 'Rename cluster',
|
|
1432
|
+
onClick: () => console.log('rename cluster'),
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
key: 'delete-cluster',
|
|
1436
|
+
label: 'Delete cluster',
|
|
1437
|
+
isDanger: true,
|
|
1438
|
+
hasDivider: true,
|
|
1439
|
+
onClick: () => console.log('delete cluster'),
|
|
1440
|
+
},
|
|
1441
|
+
],
|
|
1442
|
+
},
|
|
1443
|
+
{
|
|
1444
|
+
key: 'project',
|
|
1445
|
+
label: 'Customer Onboarding & KYC Verification Pipeline (rev. 2026)',
|
|
1446
|
+
icon: Folder,
|
|
1447
|
+
actions: [
|
|
1448
|
+
{
|
|
1449
|
+
key: 'rename',
|
|
1450
|
+
label: 'Rename project',
|
|
1451
|
+
onClick: () => console.log('rename project'),
|
|
1452
|
+
},
|
|
1453
|
+
{
|
|
1454
|
+
key: 'duplicate',
|
|
1455
|
+
label: 'Duplicate project',
|
|
1456
|
+
onClick: () => console.log('duplicate project'),
|
|
1457
|
+
},
|
|
1458
|
+
{
|
|
1459
|
+
key: 'delete',
|
|
1460
|
+
label: 'Delete project',
|
|
1461
|
+
isDanger: true,
|
|
1462
|
+
hasDivider: true,
|
|
1463
|
+
onClick: () => console.log('delete project'),
|
|
1464
|
+
},
|
|
1465
|
+
],
|
|
1466
|
+
},
|
|
1467
|
+
{
|
|
1468
|
+
key: 'file',
|
|
1469
|
+
label: 'customer-onboarding-kyc-verification-pipeline.bpmn',
|
|
1470
|
+
icon: Diagram,
|
|
1471
|
+
actions: [
|
|
1472
|
+
{
|
|
1473
|
+
key: 'rename-file',
|
|
1474
|
+
label: 'Rename file',
|
|
1475
|
+
onClick: () => console.log('rename file'),
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
key: 'download',
|
|
1479
|
+
label: 'Download BPMN',
|
|
1480
|
+
onClick: () => console.log('download'),
|
|
1481
|
+
},
|
|
1482
|
+
],
|
|
1483
|
+
},
|
|
1484
|
+
],
|
|
1485
|
+
sidebarChildren: [],
|
|
1486
|
+
globalActions: [
|
|
1487
|
+
{ key: 'notifications', label: 'Notifications', icon: Notification },
|
|
1488
|
+
{ key: 'help', label: 'Help', icon: Help },
|
|
1489
|
+
{ key: 'user', label: 'Account', icon: UserAvatar },
|
|
1490
|
+
],
|
|
1491
|
+
});
|
|
1492
|
+
return (_jsxs(_Fragment, { children: [_jsx(C3NavigationV2, { ...navProps }), _jsxs(MainContent, { sidebarExpanded: isSidebarExpanded, hasSidebar: false, children: [_jsx("h1", { children: "Long Breadcrumbs" }), _jsx("p", { style: { marginTop: '1rem', color: 'var(--cds-text-secondary)' }, children: "Demonstrates header space allocation with long org / cluster / project / file names. The tools area on the right renders its intrinsic content; the breadcrumb row claims the remaining width. Resize the viewport to see how the row reacts. Segment-level truncation (ellipsis + overflow collapse chip) is a separate follow-up; today the row scrolls horizontally when content exceeds the budget." })] })] }));
|
|
1493
|
+
};
|
|
@@ -30,4 +30,4 @@ const LinkButton = styled.button `
|
|
|
30
30
|
outline-offset: -2px;
|
|
31
31
|
}
|
|
32
32
|
`;
|
|
33
|
-
export const C3InfoPanel = ({ elements, title = '
|
|
33
|
+
export const C3InfoPanel = ({ elements, title = 'Info', }) => (_jsxs(_Fragment, { children: [title && (_jsx(PanelHeader, { children: _jsx(PanelTitle, { children: title }) })), _jsx(LinkList, { children: elements.map((el) => (_jsx(LinkItem, { children: _jsx(LinkButton, { onClick: el.onClick, children: el.label }) }, el.key))) })] }));
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { type FC } from 'react';
|
|
2
2
|
import type { Notification } from '../../../api/notifications';
|
|
3
|
+
export interface C3NotificationsPanelLabels {
|
|
4
|
+
/** Defaults to `'Dismiss all'`. */
|
|
5
|
+
dismissAll?: string;
|
|
6
|
+
/** Defaults to `'No notifications'`. */
|
|
7
|
+
emptyTitle?: string;
|
|
8
|
+
/** Defaults to `'New updates regarding your processes, clusters and more will appear here.'`. */
|
|
9
|
+
emptyDescription?: string;
|
|
10
|
+
}
|
|
3
11
|
export interface C3NotificationsPanelProps {
|
|
4
12
|
onLinkClick?: (meta: Notification['meta']) => void;
|
|
13
|
+
/** Defaults to `'Notifications'`. */
|
|
14
|
+
title?: string | null;
|
|
15
|
+
labels?: C3NotificationsPanelLabels;
|
|
5
16
|
}
|
|
6
17
|
export declare const C3NotificationsPanel: FC<C3NotificationsPanelProps>;
|
|
@@ -28,7 +28,7 @@ const EmptyStateDescription = styled(NotificationDescription) `
|
|
|
28
28
|
margin-top: var(--cds-spacing-03);
|
|
29
29
|
`;
|
|
30
30
|
const sortDescending = (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
|
|
31
|
-
export const C3NotificationsPanel = ({ onLinkClick, }) => {
|
|
31
|
+
export const C3NotificationsPanel = ({ onLinkClick, title = 'Notifications', labels, }) => {
|
|
32
32
|
const { enabled, notifications, markAllAsRead, dismiss, dismissAll, analytics, } = useContext(C3NotificationContext);
|
|
33
33
|
// Snapshot uuids that were unread when the panel opened so "new" dots
|
|
34
34
|
// persist until the panel closes, even after mark-as-read runs.
|
|
@@ -41,17 +41,21 @@ export const C3NotificationsPanel = ({ onLinkClick, }) => {
|
|
|
41
41
|
if (enabled)
|
|
42
42
|
analytics('notification-panel-opened');
|
|
43
43
|
}, []);
|
|
44
|
-
return (_jsxs(_Fragment, { children: [_jsxs(PanelHeader, { style: {
|
|
44
|
+
return (_jsxs(_Fragment, { children: [(title || notifications.length > 0) && (_jsxs(PanelHeader, { style: {
|
|
45
45
|
width: '100%',
|
|
46
46
|
height: 60,
|
|
47
47
|
display: 'flex',
|
|
48
48
|
flexDirection: 'row',
|
|
49
|
-
|
|
49
|
+
// Without a title, the dismiss-all button is the sole row item;
|
|
50
|
+
// flex-end keeps it pinned right instead of drifting left under
|
|
51
|
+
// `space-between`.
|
|
52
|
+
justifyContent: title ? 'space-between' : 'flex-end',
|
|
50
53
|
alignItems: 'center',
|
|
51
|
-
}, children: [_jsx(PanelTitle, { children:
|
|
54
|
+
}, children: [title && _jsx(PanelTitle, { children: title }), notifications.length > 0 && (_jsx(DismissAllButton, { kind: 'ghost', size: 'sm', onClick: () => dismissAll(notifications), children: labels?.dismissAll ?? 'Dismiss all' }))] })), notifications.length > 0 ? ([...notifications].sort(sortDescending).map((notification) => (_jsx(C3NotificationContainer, { onRead: () => undefined, onDismiss: () => dismiss(notification), originalOnLinkClick: onLinkClick, onLinkClick: () => {
|
|
52
55
|
if (enabled) {
|
|
53
56
|
analytics('notification-clicked-cta', notification.meta?.identifier);
|
|
54
57
|
}
|
|
55
58
|
onLinkClick?.(notification.meta);
|
|
56
|
-
}, unread: unreadAtOpen.has(notification.uuid), ...notification }, notification.uuid)))) : (_jsxs(EmptyState, { children: [_jsx(C3BellIcon, { size: 56 }), _jsx(EmptyStateTitle, { children:
|
|
59
|
+
}, unread: unreadAtOpen.has(notification.uuid), ...notification }, notification.uuid)))) : (_jsxs(EmptyState, { children: [_jsx(C3BellIcon, { size: 56 }), _jsx(EmptyStateTitle, { children: labels?.emptyTitle ?? 'No notifications' }), _jsx(EmptyStateDescription, { children: labels?.emptyDescription ??
|
|
60
|
+
'New updates regarding your processes, clusters and more will appear here.' })] }))] }));
|
|
57
61
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import type { Theme } from '../../c3-user-configuration/c3-profile-provider/c3-profile-provider';
|
|
3
|
+
export interface C3ThemeSelectorLabels {
|
|
4
|
+
/** Defaults to `'Theme'`. */
|
|
5
|
+
legend?: string;
|
|
6
|
+
/** Defaults to `'Light'`. */
|
|
7
|
+
light?: string;
|
|
8
|
+
/** Defaults to `'System'`. */
|
|
9
|
+
system?: string;
|
|
10
|
+
/** Defaults to `'Dark'`. */
|
|
11
|
+
dark?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface C3ThemeSelectorProps {
|
|
14
|
+
currentTheme: Theme;
|
|
15
|
+
onChange: (theme: Theme) => void;
|
|
16
|
+
labels?: C3ThemeSelectorLabels;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Light / System / Dark radio group, matching the V1 user sidebar's
|
|
20
|
+
* built-in theme switcher. Consumers wire it into V2's
|
|
21
|
+
* `useCamundaTools.user.customSection` (or anywhere else) and pass
|
|
22
|
+
* their own state. SaaS consumers can read `theme` and
|
|
23
|
+
* `onThemeChange` from `useC3Profile()`; SM consumers pass local state.
|
|
24
|
+
*/
|
|
25
|
+
export declare const C3ThemeSelector: FC<C3ThemeSelectorProps>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
|
|
4
|
+
* under one or more contributor license agreements. Licensed under a commercial license.
|
|
5
|
+
* You may not use this file except in compliance with the commercial license.
|
|
6
|
+
*/
|
|
7
|
+
import { Layer, RadioButton, RadioButtonGroup } from '@carbon/react';
|
|
8
|
+
/**
|
|
9
|
+
* Light / System / Dark radio group, matching the V1 user sidebar's
|
|
10
|
+
* built-in theme switcher. Consumers wire it into V2's
|
|
11
|
+
* `useCamundaTools.user.customSection` (or anywhere else) and pass
|
|
12
|
+
* their own state. SaaS consumers can read `theme` and
|
|
13
|
+
* `onThemeChange` from `useC3Profile()`; SM consumers pass local state.
|
|
14
|
+
*/
|
|
15
|
+
export const C3ThemeSelector = ({ currentTheme, onChange, labels, }) => (_jsx(Layer, { children: _jsx("div", { style: { padding: '0.5rem 1rem' }, children: _jsxs(RadioButtonGroup, { name: 'theme-radio-group', legendText: labels?.legend ?? 'Theme', orientation: 'vertical', valueSelected: currentTheme, onChange: (value) => onChange(value), children: [_jsx(RadioButton, { id: 'theme-light', labelText: labels?.light ?? 'Light', value: 'light' }), _jsx(RadioButton, { id: 'theme-system', labelText: labels?.system ?? 'System', value: 'system' }), _jsx(RadioButton, { id: 'theme-dark', labelText: labels?.dark ?? 'Dark', value: 'dark' })] }) }) }));
|
|
@@ -26,5 +26,20 @@ export interface C3UserPanelProps {
|
|
|
26
26
|
* a built-in link (`terms`, `privacy`, `imprint`) replace the default.
|
|
27
27
|
*/
|
|
28
28
|
elements?: UserPanelElement[];
|
|
29
|
+
/** Defaults to `'Account'`. */
|
|
30
|
+
title?: string | null;
|
|
31
|
+
labels?: C3UserPanelLabels;
|
|
32
|
+
}
|
|
33
|
+
export interface C3UserPanelLabels {
|
|
34
|
+
/** Defaults to `'Terms of use'`. */
|
|
35
|
+
termsOfUse?: string;
|
|
36
|
+
/** Defaults to `'Privacy policy'`. */
|
|
37
|
+
privacyPolicy?: string;
|
|
38
|
+
/** Defaults to `'Imprint'`. */
|
|
39
|
+
imprint?: string;
|
|
40
|
+
/** Defaults to `'Log out'`. */
|
|
41
|
+
logOut?: string;
|
|
42
|
+
/** Footer copyright. Defaults to `'© Camunda Services GmbH {year}'`. Receives the current year. */
|
|
43
|
+
copyright?: (year: number) => string;
|
|
29
44
|
}
|
|
30
45
|
export declare const C3UserPanel: FC<C3UserPanelProps>;
|