@camunda/camunda-composite-components 0.23.2 → 0.23.4
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/api/endpoints.const.js +2 -2
- 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-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 +6 -0
- package/lib/esm/src/components/c3-navigation-v2/c3-navigation-v2.types.d.ts +4 -0
- package/lib/esm/src/components/c3-navigation-v2/c3-sidebar.js +25 -42
- package/lib/esm/src/components/c3-navigation-v2/c3-tools-area.js +18 -5
- 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/use-camunda-tools.d.ts +21 -4
- package/lib/esm/src/components/c3-navigation-v2/use-camunda-tools.js +39 -20
- package/lib/esm/src/components/c3-navigation-v2/use-cluster-sidebar-entries.js +10 -8
- package/package.json +27 -27
package/lib/esm/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camunda/camunda-composite-components",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.4",
|
|
4
4
|
"description": "Camunda Composite Components",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/camunda/camunda-cloud-management-apps/issues"
|
|
@@ -52,41 +52,41 @@
|
|
|
52
52
|
"jwt-decode": "4.0.0",
|
|
53
53
|
"react-error-boundary": "6.1.1",
|
|
54
54
|
"react-markdown": "10.1.0",
|
|
55
|
-
"semver": "7.
|
|
55
|
+
"semver": "7.8.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@auth0/auth0-spa-js": "2.19.
|
|
58
|
+
"@auth0/auth0-spa-js": "2.19.3",
|
|
59
59
|
"@camunda/ccma-shared-types": "workspace:*",
|
|
60
|
-
"@carbon/react": "1.
|
|
61
|
-
"@chromatic-com/storybook": "5.
|
|
60
|
+
"@carbon/react": "1.107.1",
|
|
61
|
+
"@chromatic-com/storybook": "5.2.1",
|
|
62
62
|
"@mdx-js/react": "3.1.1",
|
|
63
|
-
"@playwright/test": "1.
|
|
64
|
-
"@storybook/addon-a11y": "10.
|
|
65
|
-
"@storybook/addon-docs": "10.
|
|
66
|
-
"@storybook/addon-links": "10.
|
|
67
|
-
"@storybook/addon-vitest": "10.
|
|
68
|
-
"@storybook/react": "10.
|
|
69
|
-
"@storybook/react-vite": "10.
|
|
70
|
-
"@vitest/browser": "4.1.
|
|
71
|
-
"@vitest/browser-playwright": "4.1.
|
|
72
|
-
"vitest": "4.1.
|
|
63
|
+
"@playwright/test": "1.60.0",
|
|
64
|
+
"@storybook/addon-a11y": "10.4.0",
|
|
65
|
+
"@storybook/addon-docs": "10.4.0",
|
|
66
|
+
"@storybook/addon-links": "10.4.0",
|
|
67
|
+
"@storybook/addon-vitest": "10.4.0",
|
|
68
|
+
"@storybook/react": "10.4.0",
|
|
69
|
+
"@storybook/react-vite": "10.4.0",
|
|
70
|
+
"@vitest/browser": "4.1.7",
|
|
71
|
+
"@vitest/browser-playwright": "4.1.7",
|
|
72
|
+
"vitest": "4.1.7",
|
|
73
73
|
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
74
74
|
"eslint-import-resolver-typescript": "4.4.4",
|
|
75
75
|
"eslint-plugin-react": "7.37.5",
|
|
76
|
-
"eslint-plugin-react-hooks": "7.
|
|
77
|
-
"eslint-plugin-storybook": "10.
|
|
76
|
+
"eslint-plugin-react-hooks": "7.1.1",
|
|
77
|
+
"eslint-plugin-storybook": "10.4.0",
|
|
78
78
|
"event-source-polyfill": "1.0.31",
|
|
79
|
-
"mixpanel-browser": "2.
|
|
80
|
-
"playwright": "1.
|
|
81
|
-
"react": "19.2.
|
|
82
|
-
"react-dom": "19.2.
|
|
83
|
-
"react-is": "19.2.
|
|
79
|
+
"mixpanel-browser": "2.79.0",
|
|
80
|
+
"playwright": "1.60.0",
|
|
81
|
+
"react": "19.2.6",
|
|
82
|
+
"react-dom": "19.2.6",
|
|
83
|
+
"react-is": "19.2.6",
|
|
84
84
|
"rimraf": "6.1.3",
|
|
85
85
|
"serve": "14.2.6",
|
|
86
|
-
"storybook": "10.
|
|
87
|
-
"styled-components": "6.4.
|
|
88
|
-
"typescript-eslint": "8.
|
|
89
|
-
"wait-on": "9.0.
|
|
86
|
+
"storybook": "10.4.0",
|
|
87
|
+
"styled-components": "6.4.2",
|
|
88
|
+
"typescript-eslint": "8.59.4",
|
|
89
|
+
"wait-on": "9.0.10"
|
|
90
90
|
},
|
|
91
91
|
"peerDependencies": {
|
|
92
92
|
"@carbon/react": "1.x",
|
|
@@ -23,8 +23,8 @@ export const CONSOLE = {
|
|
|
23
23
|
};
|
|
24
24
|
export const STATUS = {
|
|
25
25
|
id: 'status',
|
|
26
|
-
dev: 'https://camundaultrawombat.statuspage.io
|
|
27
|
-
int: 'https://camundaultrawombat.statuspage.io
|
|
26
|
+
dev: 'https://camundaultrawombat.statuspage.io',
|
|
27
|
+
int: 'https://camundaultrawombat.statuspage.io',
|
|
28
28
|
prod: 'https://status.camunda.io',
|
|
29
29
|
};
|
|
30
30
|
export function getEndpoint(stage, endpoint) {
|
|
@@ -371,7 +371,7 @@ export const C3DataTable = ({ data, headers, options, toolbar: singleToolbar, to
|
|
|
371
371
|
}
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
|
-
if (toolbarComponents.length > 0 ||
|
|
374
|
+
if (toolbarComponents.length > 0 || searchComponent) {
|
|
375
375
|
toolbarComponent = (_jsx(TableToolbar, { "aria-label": 'data table toolbar', children: _jsxs(ToolbarWrapper, { style: {
|
|
376
376
|
justifyContent: toolbarComponents.length > 5 ? 'normal' : 'end',
|
|
377
377
|
flexWrap: toolbarComponents.length > 5 ? 'wrap' : undefined,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { type FC, type PropsWithChildren } from 'react';
|
|
1
|
+
import React, { type FC, type PropsWithChildren, type RefObject } from 'react';
|
|
2
2
|
export declare enum HelpCenterHintType {
|
|
3
3
|
HelpCenter = "help-center",
|
|
4
4
|
Onboarding = "onboarding"
|
|
@@ -13,6 +13,7 @@ export type C3HelpCenterContextValue = {
|
|
|
13
13
|
setShowHintOnClose: (showHintOnClose: boolean) => void;
|
|
14
14
|
hintType: HelpCenterHintType;
|
|
15
15
|
setHintType: (hintType: HelpCenterHintType) => void;
|
|
16
|
+
launcherButtonRef: RefObject<HTMLButtonElement | null>;
|
|
16
17
|
};
|
|
17
18
|
export declare const C3HelpCenterContext: React.Context<C3HelpCenterContextValue>;
|
|
18
19
|
export declare const C3HelpCenterProvider: FC<PropsWithChildren>;
|
|
@@ -4,7 +4,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
4
4
|
* under one or more contributor license agreements. Licensed under a commercial license.
|
|
5
5
|
* You may not use this file except in compliance with the commercial license.
|
|
6
6
|
*/
|
|
7
|
-
import React, { useEffect, useState, } from 'react';
|
|
7
|
+
import React, { useEffect, useRef, useState, } from 'react';
|
|
8
8
|
export var HelpCenterHintType;
|
|
9
9
|
(function (HelpCenterHintType) {
|
|
10
10
|
HelpCenterHintType["HelpCenter"] = "help-center";
|
|
@@ -19,6 +19,7 @@ export const C3HelpCenterContext = React.createContext({
|
|
|
19
19
|
setShowHintOnClose: () => undefined,
|
|
20
20
|
hintType: HelpCenterHintType.Onboarding,
|
|
21
21
|
setHintType: () => undefined,
|
|
22
|
+
launcherButtonRef: { current: null },
|
|
22
23
|
});
|
|
23
24
|
export const C3HelpCenterProvider = ({ children }) => {
|
|
24
25
|
const [isHelpCenterOpen, setIsHelpCenterOpen] = useState(false);
|
|
@@ -26,6 +27,7 @@ export const C3HelpCenterProvider = ({ children }) => {
|
|
|
26
27
|
const [showHint, setShowHint] = useState(false);
|
|
27
28
|
const [showHintOnClose, setShowHintOnClose] = useState(false);
|
|
28
29
|
const [hintType, setHintType] = useState(HelpCenterHintType.Onboarding);
|
|
30
|
+
const launcherButtonRef = useRef(null);
|
|
29
31
|
const openHelpCenter = (showTabId) => {
|
|
30
32
|
if (!isHelpCenterOpen) {
|
|
31
33
|
setIsHelpCenterOpen(true);
|
|
@@ -53,6 +55,7 @@ export const C3HelpCenterProvider = ({ children }) => {
|
|
|
53
55
|
setShowHintOnClose,
|
|
54
56
|
hintType,
|
|
55
57
|
setHintType,
|
|
58
|
+
launcherButtonRef,
|
|
56
59
|
}, children: children }));
|
|
57
60
|
};
|
|
58
61
|
export const useC3HelpCenter = () => React.useContext(C3HelpCenterContext);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Dict } from 'mixpanel-browser';
|
|
2
|
-
import { type FC } from 'react';
|
|
2
|
+
import React, { type FC } from 'react';
|
|
3
3
|
import { type Theme } from '../c3-user-configuration/c3-profile-provider/c3-profile-provider';
|
|
4
4
|
import type { Persona } from './c3-help-center.types';
|
|
5
5
|
export interface C3HelpCenterProps {
|
|
@@ -12,5 +12,6 @@ export interface C3HelpCenterProps {
|
|
|
12
12
|
onRequestClose?: () => void;
|
|
13
13
|
onRequestOpen?: () => void;
|
|
14
14
|
mixpanelTrack?: (event: string, data: Dict | undefined) => void;
|
|
15
|
+
launcherButtonRef?: React.RefObject<HTMLButtonElement | null>;
|
|
15
16
|
}
|
|
16
17
|
export declare const C3HelpCenter: FC<C3HelpCenterProps>;
|
|
@@ -21,8 +21,9 @@ const StyledComposedModal = styled(ComposedModal) `
|
|
|
21
21
|
mask-image: none;
|
|
22
22
|
}
|
|
23
23
|
`;
|
|
24
|
-
export const C3HelpCenter = ({ autoStartSurvey, origin, flags, onRequestClose, mixpanelTrack: customMixpanelTrack, onRequestOpen, theme, onPersonaChange, activeTab, }) => {
|
|
25
|
-
const { isHelpCenterOpen: isOpen, setIsHelpCenterOpen, setShowHintOnClose, } = useC3HelpCenter();
|
|
24
|
+
export const C3HelpCenter = ({ autoStartSurvey, origin, flags, onRequestClose, mixpanelTrack: customMixpanelTrack, onRequestOpen, theme, onPersonaChange, activeTab, launcherButtonRef, }) => {
|
|
25
|
+
const { isHelpCenterOpen: isOpen, setIsHelpCenterOpen, setShowHintOnClose, launcherButtonRef: contextLauncherButtonRef, } = useC3HelpCenter();
|
|
26
|
+
const effectiveLauncherButtonRef = launcherButtonRef ?? contextLauncherButtonRef;
|
|
26
27
|
const { userToken, decodedToken, activeOrganizationId, handleTheme, decodedAudience, analyticsTrack, currentApp, } = useC3UserConfiguration();
|
|
27
28
|
const { theme: themeConfig, isEnabled, reloadClusters } = useC3Profile();
|
|
28
29
|
const themeHandlingEnabled = isEnabled && !!handleTheme && !!themeConfig;
|
|
@@ -212,7 +213,7 @@ export const C3HelpCenter = ({ autoStartSurvey, origin, flags, onRequestClose, m
|
|
|
212
213
|
setShowSurvey(false);
|
|
213
214
|
}, 400);
|
|
214
215
|
};
|
|
215
|
-
return (_jsx(Layer, { children: _jsx(StyledComposedModal, { open: isOpen, size: 'lg', onClose: closeFn, className: 'help-center', "aria-label": 'HelpCenter', preventCloseOnClickOutside: true, style: showSurvey || !persona?.wasShown
|
|
216
|
+
return (_jsx(Layer, { children: _jsx(StyledComposedModal, { open: isOpen, size: 'lg', onClose: closeFn, launcherButtonRef: effectiveLauncherButtonRef, className: 'help-center', "aria-label": 'HelpCenter', preventCloseOnClickOutside: true, style: showSurvey || !persona?.wasShown
|
|
216
217
|
? { backgroundColor: 'rgba(22,22,22, 0.8)' }
|
|
217
218
|
: {}, children: _jsx(ErrorBoundary, { fallbackRender: () => (_jsxs(_Fragment, { children: [_jsx(ModalHeader, { title: 'Help Center', closeModal: closeFn }), _jsx(ModalBody, { children: _jsx(ActionableNotification, { inline: true, hideCloseButton: true, lowContrast: true, kind: 'error', title: 'Something went wrong.', subtitle: 'Try reloading the page.', actionButtonLabel: 'Reload', onActionButtonClick: () => window.location.reload() }) })] })), children: showSurvey || !persona?.wasShown ? (_jsx(C3OnboardingSurvey, { personaCallback: personaCallback, persona: persona, mixpanelTrack: mixpanelTrack, onRequestClose: closeFn, onRequestSkip: onRequestSkipSurvey, theme: resolvedTheme, origin: hostApp, modal: true })) : (_jsx(HelpCenter, { configuration: helpCenterConfig, persona: persona, audience: decodedAudience || '', flags: flags, onRequestResumeSurvey: onRequestResumeSurvey, onRequestRetakeSurvey: onRequestRetakeSurvey, onRequestClose: closeFn, mixpanelTrack: mixpanelTrack, email: email, theme: resolvedTheme, origin: hostApp, initialTab: activeTab })) }) }) }));
|
|
218
219
|
};
|
|
@@ -138,18 +138,18 @@ export const C3NavigationAppBar = ({ app: appProps, appBar, forwardRef, navbar,
|
|
|
138
138
|
const clusterDto = data.payload;
|
|
139
139
|
const status = clusterDto.status;
|
|
140
140
|
const endpoints = {
|
|
141
|
-
tasklist: status?.tasklistUrl,
|
|
142
|
-
operate: status?.operateUrl,
|
|
143
|
-
optimize: status?.optimizeUrl,
|
|
141
|
+
tasklist: status?.tasklistUrl ?? '',
|
|
142
|
+
operate: status?.operateUrl ?? '',
|
|
143
|
+
optimize: status?.optimizeUrl ?? '',
|
|
144
144
|
console: '',
|
|
145
145
|
modeler: '',
|
|
146
146
|
identity: status?.identityUrl ?? '',
|
|
147
147
|
admin: status?.identityUrl ?? '',
|
|
148
148
|
};
|
|
149
149
|
const expectedStatus = {
|
|
150
|
-
tasklist: status?.tasklistStatus,
|
|
151
|
-
operate: status?.operateStatus,
|
|
152
|
-
optimize: status?.optimizeStatus,
|
|
150
|
+
tasklist: status?.tasklistStatus ?? '',
|
|
151
|
+
operate: status?.operateStatus ?? '',
|
|
152
|
+
optimize: status?.optimizeStatus ?? '',
|
|
153
153
|
console: '',
|
|
154
154
|
modeler: '',
|
|
155
155
|
identity: '',
|
package/lib/esm/src/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar-element.js
CHANGED
|
@@ -39,6 +39,9 @@ const C3NavigationSidebarElement = (props) => {
|
|
|
39
39
|
setIsOverflown(element ? element.offsetWidth < element.scrollWidth : false);
|
|
40
40
|
};
|
|
41
41
|
useEffect(() => {
|
|
42
|
+
if (typeof window === 'undefined') {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
42
45
|
window.addEventListener('resize', handleSetIsOverflown);
|
|
43
46
|
handleSetIsOverflown();
|
|
44
47
|
return () => window.removeEventListener('resize', handleSetIsOverflown);
|
|
@@ -60,16 +60,25 @@ const C3NavigationSideBar = (props) => {
|
|
|
60
60
|
setScrollBarWidth(scrollbarWidth);
|
|
61
61
|
};
|
|
62
62
|
useEffect(() => {
|
|
63
|
+
if (typeof window === 'undefined') {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
63
66
|
window.addEventListener('resize', handleResize);
|
|
64
67
|
handleResize();
|
|
65
68
|
return () => window.removeEventListener('resize', handleResize);
|
|
66
69
|
}, []);
|
|
67
70
|
useEffect(() => {
|
|
71
|
+
let timeoutId;
|
|
68
72
|
if (isOpen) {
|
|
69
|
-
setTimeout(() => {
|
|
73
|
+
timeoutId = setTimeout(() => {
|
|
70
74
|
handleResize();
|
|
71
75
|
}, 120);
|
|
72
76
|
}
|
|
77
|
+
return () => {
|
|
78
|
+
if (timeoutId !== undefined) {
|
|
79
|
+
clearTimeout(timeoutId);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
73
82
|
}, [isOpen]);
|
|
74
83
|
return (_jsxs(Wrapper, { children: [_jsx(HeaderGlobalAction, { ref: setIconRef, "aria-label": sideBar.tooltip ?? `Open ${sideBar.ariaLabel}`, "aria-expanded": isOpen, "aria-controls": id, onClick: () => {
|
|
75
84
|
setIsOpen(!isOpen);
|
|
@@ -40,6 +40,18 @@ export function useOnClickOutside(handler) {
|
|
|
40
40
|
return { panelRef, setPanelRef, iconRef, setIconRef };
|
|
41
41
|
}
|
|
42
42
|
export function executeMediaQuery(mediaQuery) {
|
|
43
|
+
if (typeof window === 'undefined') {
|
|
44
|
+
return {
|
|
45
|
+
matches: false,
|
|
46
|
+
media: mediaQuery,
|
|
47
|
+
onchange: null,
|
|
48
|
+
addEventListener: () => { },
|
|
49
|
+
removeEventListener: () => { },
|
|
50
|
+
addListener: () => { },
|
|
51
|
+
removeListener: () => { },
|
|
52
|
+
dispatchEvent: () => false,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
43
55
|
return window.matchMedia(mediaQuery);
|
|
44
56
|
}
|
|
45
57
|
export function useMediaQuery(mediaQuery) {
|
|
@@ -9,16 +9,16 @@ import { Checkmark, ChevronDown, OverflowMenuVertical, } from '@carbon/react/ico
|
|
|
9
9
|
import { useCallback, useEffect, useRef, useState, } from 'react';
|
|
10
10
|
import { createPortal } from 'react-dom';
|
|
11
11
|
import styled from 'styled-components';
|
|
12
|
+
// `display: contents` so nested flex doesn't trap min-content; label
|
|
13
|
+
// shrinkage propagates to the breadcrumb row directly.
|
|
12
14
|
const SegmentWrapper = styled.div `
|
|
13
|
-
display:
|
|
14
|
-
align-items: center;
|
|
15
|
-
position: relative;
|
|
15
|
+
display: contents;
|
|
16
16
|
`;
|
|
17
17
|
const SegmentButton = styled.button `
|
|
18
18
|
display: flex;
|
|
19
19
|
align-items: center;
|
|
20
20
|
gap: var(--cds-spacing-03);
|
|
21
|
-
padding: var(--cds-spacing-02) var(--cds-spacing-
|
|
21
|
+
padding: var(--cds-spacing-02) var(--cds-spacing-04);
|
|
22
22
|
background: transparent;
|
|
23
23
|
border: none;
|
|
24
24
|
border-radius: 4px;
|
|
@@ -29,6 +29,8 @@ const SegmentButton = styled.button `
|
|
|
29
29
|
text-decoration: none;
|
|
30
30
|
white-space: nowrap;
|
|
31
31
|
transition: background 0.15s, color 0.15s;
|
|
32
|
+
min-width: 0;
|
|
33
|
+
flex-shrink: ${(p) => (p.$isLast ? 0 : 1)};
|
|
32
34
|
|
|
33
35
|
&:hover {
|
|
34
36
|
background: ${(p) => (p.$isInteractive ? 'var(--cds-layer-hover)' : 'transparent')};
|
|
@@ -41,7 +43,13 @@ const SegmentButton = styled.button `
|
|
|
41
43
|
outline-offset: -2px;
|
|
42
44
|
}
|
|
43
45
|
`;
|
|
44
|
-
const
|
|
46
|
+
const SegmentLabel = styled.span `
|
|
47
|
+
overflow: hidden;
|
|
48
|
+
text-overflow: ellipsis;
|
|
49
|
+
white-space: nowrap;
|
|
50
|
+
min-width: 0;
|
|
51
|
+
`;
|
|
52
|
+
const ControlButton = styled.button `
|
|
45
53
|
display: flex;
|
|
46
54
|
align-items: center;
|
|
47
55
|
justify-content: center;
|
|
@@ -52,6 +60,7 @@ const ChevronButton = styled.button `
|
|
|
52
60
|
cursor: pointer;
|
|
53
61
|
color: var(--cds-icon-secondary);
|
|
54
62
|
transition: background 0.15s;
|
|
63
|
+
flex-shrink: 0;
|
|
55
64
|
|
|
56
65
|
&:hover {
|
|
57
66
|
background: var(--cds-layer-hover);
|
|
@@ -62,32 +71,20 @@ const ChevronButton = styled.button `
|
|
|
62
71
|
outline-offset: -2px;
|
|
63
72
|
}
|
|
64
73
|
|
|
74
|
+
${SegmentButton} + & {
|
|
75
|
+
margin-left: -0.125rem;
|
|
76
|
+
}
|
|
77
|
+
& + & {
|
|
78
|
+
margin-left: 0.375rem;
|
|
79
|
+
}
|
|
80
|
+
`;
|
|
81
|
+
const ChevronButton = styled(ControlButton) `
|
|
65
82
|
svg {
|
|
66
83
|
transform: ${(p) => (p.$isOpen ? 'rotate(180deg)' : 'rotate(0deg)')};
|
|
67
84
|
transition: transform 0.15s;
|
|
68
85
|
}
|
|
69
86
|
`;
|
|
70
|
-
const ActionMenuButton = styled
|
|
71
|
-
display: flex;
|
|
72
|
-
align-items: center;
|
|
73
|
-
justify-content: center;
|
|
74
|
-
padding: var(--cds-spacing-02);
|
|
75
|
-
background: ${(p) => (p.$isOpen ? 'var(--cds-layer-01)' : 'transparent')};
|
|
76
|
-
border: none;
|
|
77
|
-
border-radius: 4px;
|
|
78
|
-
cursor: pointer;
|
|
79
|
-
color: var(--cds-icon-secondary);
|
|
80
|
-
transition: background 0.15s;
|
|
81
|
-
|
|
82
|
-
&:hover {
|
|
83
|
-
background: var(--cds-layer-hover);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
&:focus-visible {
|
|
87
|
-
outline: 2px solid var(--cds-focus);
|
|
88
|
-
outline-offset: -2px;
|
|
89
|
-
}
|
|
90
|
-
`;
|
|
87
|
+
const ActionMenuButton = styled(ControlButton) ``;
|
|
91
88
|
const ActionMenuItem = styled.button `
|
|
92
89
|
width: 100%;
|
|
93
90
|
display: flex;
|
|
@@ -167,6 +164,7 @@ const Separator = styled.span `
|
|
|
167
164
|
color: var(--cds-text-secondary);
|
|
168
165
|
font-size: 0.875rem;
|
|
169
166
|
user-select: none;
|
|
167
|
+
flex-shrink: 0;
|
|
170
168
|
|
|
171
169
|
&::after {
|
|
172
170
|
content: '/';
|
|
@@ -312,6 +310,7 @@ const BreadcrumbSegmentComponent = ({ segment, isLast, linkComponent, }) => {
|
|
|
312
310
|
const dropdownId = useRef(`dropdown-${Math.random().toString(36).slice(2, 8)}`).current;
|
|
313
311
|
const { isOpen, close, toggle } = useBreadcrumbDropdown(dropdownId);
|
|
314
312
|
const wrapperRef = useRef(null);
|
|
313
|
+
const segmentButtonRef = useRef(null);
|
|
315
314
|
const chevronRef = useRef(null);
|
|
316
315
|
const dropdownPanelRef = useRef(null);
|
|
317
316
|
const hasDropdown = segment.dropdownItems && segment.dropdownItems.length > 0;
|
|
@@ -319,8 +318,9 @@ const BreadcrumbSegmentComponent = ({ segment, isLast, linkComponent, }) => {
|
|
|
319
318
|
const dropdownAriaLabel = segment.dropdownAriaLabel ?? `Switch ${segment.label}`;
|
|
320
319
|
const [dropdownPos, setDropdownPos] = useState(undefined);
|
|
321
320
|
const updateDropdownPosition = useCallback(() => {
|
|
322
|
-
|
|
323
|
-
|
|
321
|
+
// SegmentWrapper has no box (display: contents); anchor on the button.
|
|
322
|
+
if (segmentButtonRef.current) {
|
|
323
|
+
const rect = segmentButtonRef.current.getBoundingClientRect();
|
|
324
324
|
setDropdownPos({ top: rect.bottom + 4, left: rect.left });
|
|
325
325
|
}
|
|
326
326
|
}, []);
|
|
@@ -344,11 +344,11 @@ const BreadcrumbSegmentComponent = ({ segment, isLast, linkComponent, }) => {
|
|
|
344
344
|
close();
|
|
345
345
|
}, [close]);
|
|
346
346
|
const handleDropdownKeyDown = usePanelKeyboard(dropdownPanelRef, close, chevronRef, isOpen, '[role="option"]');
|
|
347
|
-
return (_jsxs(_Fragment, { children: [_jsxs(SegmentWrapper, { ref: wrapperRef, children: [_jsxs(SegmentButton, { as: segment.linkProps?.href !== undefined
|
|
347
|
+
return (_jsxs(_Fragment, { children: [_jsxs(SegmentWrapper, { ref: wrapperRef, children: [_jsxs(SegmentButton, { ref: segmentButtonRef, as: segment.linkProps?.href !== undefined
|
|
348
348
|
? (linkComponent ?? 'a')
|
|
349
349
|
: segment.linkProps
|
|
350
350
|
? linkComponent
|
|
351
|
-
: undefined, ...(segment.linkProps ?? {}), "$isInteractive": !!(segment.onClick || segment.linkProps), "$isLast": isLast, onClick: segment.onClick, "aria-label": segment.label, children: [Icon && _jsx(Icon, { size: 16, style: { flexShrink: 0 } }), _jsx(
|
|
351
|
+
: undefined, ...(segment.linkProps ?? {}), "$isInteractive": !!(segment.onClick || segment.linkProps), "$isLast": isLast, onClick: segment.onClick, "aria-label": segment.label, title: segment.label, children: [Icon && _jsx(Icon, { size: 16, style: { flexShrink: 0 } }), _jsx(SegmentLabel, { children: segment.label }), segment.trailingElement] }), segment.menuElement, segment.actions && segment.actions.length > 0 && (_jsx(ActionsMenu, { actions: segment.actions, ariaLabel: `${segment.label} actions` })), hasDropdown && (_jsx(ChevronButton, { ref: chevronRef, "$isOpen": isOpen, onClick: toggle, onKeyDown: (e) => {
|
|
352
352
|
if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') {
|
|
353
353
|
if (!isOpen) {
|
|
354
354
|
e.preventDefault();
|
|
@@ -365,7 +365,9 @@ const BreadcrumbBarWrapper = styled.div `
|
|
|
365
365
|
align-items: center;
|
|
366
366
|
flex: 1;
|
|
367
367
|
min-width: 0;
|
|
368
|
+
height: 100%;
|
|
368
369
|
overflow-x: auto;
|
|
369
|
-
|
|
370
|
+
margin-left: var(--cds-spacing-03);
|
|
371
|
+
margin-right: var(--cds-spacing-03);
|
|
370
372
|
`;
|
|
371
373
|
export const C3BreadcrumbBar = ({ segments, linkComponent, }) => (_jsx(BreadcrumbBarWrapper, { role: 'navigation', "aria-label": 'Breadcrumb', children: segments.map((segment, index) => (_jsx(BreadcrumbSegmentComponent, { segment: segment, isLast: index === segments.length - 1, linkComponent: linkComponent }, segment.key))) }));
|
|
@@ -16,6 +16,12 @@ const StyledHeader = styled(Header) `
|
|
|
16
16
|
border-bottom: 1px solid var(--cds-border-subtle) !important;
|
|
17
17
|
box-shadow: none !important;
|
|
18
18
|
z-index: 8001 !important;
|
|
19
|
+
|
|
20
|
+
// Carbon defaults the tools area to flex: 1, which splits the row
|
|
21
|
+
// 50/50 with breadcrumbs. Make it intrinsic instead.
|
|
22
|
+
.cds--header__global {
|
|
23
|
+
flex: 0 0 auto;
|
|
24
|
+
}
|
|
19
25
|
`;
|
|
20
26
|
const LogoSection = styled.a `
|
|
21
27
|
display: flex;
|
|
@@ -2,6 +2,10 @@ import type { ReactNode } from 'react';
|
|
|
2
2
|
/**
|
|
3
3
|
* Describes a tool button in the header. If `panel` is provided, clicking the
|
|
4
4
|
* button toggles a slide-over panel. Without `panel`, the button is standalone.
|
|
5
|
+
*
|
|
6
|
+
* `renderButton` is rendered via JSX (`<tool.renderButton ... />`), so it
|
|
7
|
+
* may call hooks internally — e.g. reading `C3NotificationContext` to drive
|
|
8
|
+
* an unread-state badge.
|
|
5
9
|
*/
|
|
6
10
|
export interface ToolDescriptor {
|
|
7
11
|
key: string;
|
|
@@ -7,7 +7,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
import { Popover, PopoverContent } from '@carbon/react';
|
|
8
8
|
import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp, } from '@carbon/react/icons/index.esm.js';
|
|
9
9
|
import { useEffect, useRef, useState } from 'react';
|
|
10
|
-
import styled from 'styled-components';
|
|
10
|
+
import styled, { css } from 'styled-components';
|
|
11
11
|
const SidebarNav = styled.nav `
|
|
12
12
|
position: fixed;
|
|
13
13
|
top: 3rem;
|
|
@@ -76,20 +76,33 @@ const NavLabel = styled.span `
|
|
|
76
76
|
text-overflow: ellipsis;
|
|
77
77
|
white-space: nowrap;
|
|
78
78
|
`;
|
|
79
|
+
/**
|
|
80
|
+
* Container row for `group` (pure section) and `group-item` (clickable +
|
|
81
|
+
* expandable) variants. `$clickable` adds the navigable affordance:
|
|
82
|
+
* hover background + transition. `$isActive` flips the selected styling.
|
|
83
|
+
*/
|
|
79
84
|
const GroupHeader = styled.div `
|
|
80
85
|
display: flex;
|
|
81
86
|
align-items: center;
|
|
82
87
|
flex-wrap: nowrap;
|
|
83
88
|
width: 100%;
|
|
84
89
|
min-height: 2.5rem;
|
|
90
|
+
padding-right: var(--cds-spacing-04);
|
|
91
|
+
gap: var(--cds-spacing-03);
|
|
85
92
|
overflow: hidden;
|
|
86
93
|
background: ${(p) => (p.$isActive ? 'var(--cds-layer-selected)' : 'transparent')};
|
|
87
94
|
border-left: ${(p) => (p.$isActive ? '3px solid var(--cds-border-interactive)' : '3px solid transparent')};
|
|
88
|
-
transition: background 0.15s, color 0.15s;
|
|
89
95
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
${(p) => p.$clickable &&
|
|
97
|
+
css `
|
|
98
|
+
transition: background 0.15s, color 0.15s;
|
|
99
|
+
|
|
100
|
+
&:hover:not(:has(button[data-expand]:hover)) {
|
|
101
|
+
background: ${p.$isActive
|
|
102
|
+
? 'var(--cds-layer-selected)'
|
|
103
|
+
: 'var(--cds-layer-hover)'};
|
|
104
|
+
}
|
|
105
|
+
`}
|
|
93
106
|
`;
|
|
94
107
|
const GroupLabelButton = styled.button `
|
|
95
108
|
display: flex;
|
|
@@ -127,7 +140,6 @@ const ExpandButton = styled.button `
|
|
|
127
140
|
align-items: center;
|
|
128
141
|
justify-content: center;
|
|
129
142
|
padding: var(--cds-spacing-02);
|
|
130
|
-
margin-right: var(--cds-spacing-04);
|
|
131
143
|
background: transparent;
|
|
132
144
|
border: none;
|
|
133
145
|
border-radius: 4px;
|
|
@@ -144,15 +156,6 @@ const ExpandButton = styled.button `
|
|
|
144
156
|
outline-offset: -2px;
|
|
145
157
|
}
|
|
146
158
|
`;
|
|
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
159
|
const PlainGroupLabel = styled.span `
|
|
157
160
|
display: flex;
|
|
158
161
|
align-items: center;
|
|
@@ -168,28 +171,6 @@ const PlainGroupLabel = styled.span `
|
|
|
168
171
|
font-size: 0.875rem;
|
|
169
172
|
font-weight: 400;
|
|
170
173
|
`;
|
|
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
174
|
const StyledPopover = styled(Popover) `
|
|
194
175
|
display: block;
|
|
195
176
|
|
|
@@ -200,7 +181,9 @@ const StyledPopover = styled(Popover) `
|
|
|
200
181
|
white-space: nowrap;
|
|
201
182
|
}
|
|
202
183
|
`;
|
|
203
|
-
|
|
184
|
+
// `enabled={false}` keeps the wrapper but skips the popover. Lets a parent
|
|
185
|
+
// button keep its focus across state toggles (see the collapse button below).
|
|
186
|
+
const CollapsedItemTooltip = ({ label, enabled = true, children, }) => {
|
|
204
187
|
const [open, setOpen] = useState(false);
|
|
205
188
|
const timerRef = useRef(null);
|
|
206
189
|
const handleMouseEnter = () => {
|
|
@@ -217,7 +200,7 @@ const CollapsedItemTooltip = ({ label, children, }) => {
|
|
|
217
200
|
if (timerRef.current)
|
|
218
201
|
clearTimeout(timerRef.current);
|
|
219
202
|
}, []);
|
|
220
|
-
return (_jsxs(StyledPopover, { open: open, align: 'right', highContrast: true, dropShadow: false, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: [children, _jsx(PopoverContent, { children: label })] }));
|
|
203
|
+
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
204
|
};
|
|
222
205
|
const SectionDivider = styled.div `
|
|
223
206
|
border-top: ${(p) => (p.$hideTopDivider ? 'none' : '1px solid var(--cds-border-subtle-01)')};
|
|
@@ -323,7 +306,7 @@ const GroupItemNode = ({ node, sidebarExpanded, depth, linkComponent, }) => {
|
|
|
323
306
|
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
307
|
return (_jsx(CollapsedItemTooltip, { label: node.label, children: collapsed }));
|
|
325
308
|
}
|
|
326
|
-
return (_jsxs("div", { children: [_jsxs(GroupHeader, { "$isActive": isActive, "$
|
|
309
|
+
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
310
|
e.stopPropagation();
|
|
328
311
|
node.onToggleExpand?.();
|
|
329
312
|
}, "aria-label": node.isExpanded
|
|
@@ -337,7 +320,7 @@ const GroupNode = ({ node, sidebarExpanded, depth, linkComponent, }) => {
|
|
|
337
320
|
const collapsed = (_jsx(NavButton, { "$isActive": false, "$isExpanded": false, "$depth": 0, onClick: node.onToggleExpand, children: _jsx(Icon, { size: 20, style: { flexShrink: 0 } }) }));
|
|
338
321
|
return (_jsx(CollapsedItemTooltip, { label: node.label, children: collapsed }));
|
|
339
322
|
}
|
|
340
|
-
return (_jsxs("div", { children: [_jsxs(
|
|
323
|
+
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": node.isExpanded
|
|
341
324
|
? `Collapse ${node.label}`
|
|
342
325
|
: `Expand ${node.label}`, "aria-expanded": !!node.isExpanded, children: node.isExpanded ? (_jsx(ChevronUp, { size: 16 })) : (_jsx(ChevronDown, { size: 16 })) }))] }), node.isExpanded &&
|
|
343
326
|
node.children.map((child) => (_jsx(SidebarNodeComponent, { node: child, sidebarExpanded: sidebarExpanded, depth: depth + 1, linkComponent: linkComponent }, child.key)))] }));
|
|
@@ -390,5 +373,5 @@ export const C3Sidebar = ({ ariaLabel, children: nodes, isExpanded = true, onTog
|
|
|
390
373
|
}
|
|
391
374
|
return (_jsx(SidebarNodeComponent, { node: node, sidebarExpanded: isExpanded, depth: 0, linkComponent: linkComponent, hideTopDivider: hideTopDivider, eatScrollPadding: eatScrollPadding, tight: tight }, node.key));
|
|
392
375
|
});
|
|
393
|
-
})() }), onToggleExpanded && (_jsx(CollapseToggleArea, { children: isExpanded
|
|
376
|
+
})() }), onToggleExpanded && (_jsx(CollapseToggleArea, { children: _jsx(CollapsedItemTooltip, { label: 'Expand', enabled: !isExpanded, children: _jsxs(CollapseButton, { "$isExpanded": isExpanded, onClick: onToggleExpanded, "aria-label": isExpanded ? 'Collapse sidebar' : 'Expand sidebar', "aria-expanded": isExpanded, children: [isExpanded ? (_jsx(ChevronLeft, { size: 20, style: { flexShrink: 0 } })) : (_jsx(ChevronRight, { size: 20, style: { flexShrink: 0 } })), isExpanded && _jsx(NavLabel, { children: "Collapse" })] }) }) }))] }));
|
|
394
377
|
};
|
|
@@ -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
|
};
|
|
@@ -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
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type FC, type ReactNode } from 'react';
|
|
1
2
|
import type { Notification } from '../../api/notifications';
|
|
2
3
|
import type { ToolDescriptor } from './c3-navigation-v2.types';
|
|
3
4
|
import { type InfoPanelElement } from './tools/c3-info-panel';
|
|
@@ -39,11 +40,27 @@ export interface UseCamundaToolsOptions {
|
|
|
39
40
|
*/
|
|
40
41
|
help?: HelpToolOptions;
|
|
41
42
|
}
|
|
43
|
+
export interface UseCamundaToolsReturn {
|
|
44
|
+
tools: ToolDescriptor[];
|
|
45
|
+
/**
|
|
46
|
+
* Wraps the consumer's nav subtree with the providers the requested tools
|
|
47
|
+
* depend on. Currently only notifications needs a provider; when omitted,
|
|
48
|
+
* `ToolsProvider` passes children through unchanged. Wrap the
|
|
49
|
+
* `<C3NavigationV2>` render with this so the tool components can read their
|
|
50
|
+
* context inside the JSX they're rendered into.
|
|
51
|
+
*/
|
|
52
|
+
ToolsProvider: FC<{
|
|
53
|
+
children: ReactNode;
|
|
54
|
+
}>;
|
|
55
|
+
}
|
|
42
56
|
/**
|
|
43
57
|
* Single helper entry point for the standard Camunda tool set.
|
|
44
|
-
* Returns a ToolDescriptor[]
|
|
45
|
-
*
|
|
58
|
+
* Returns a `ToolDescriptor[]` for `C3NavigationV2`'s `tools` prop plus a
|
|
59
|
+
* `ToolsProvider` that conditionally mounts the providers each requested tool
|
|
60
|
+
* depends on. Custom tools can be appended to the returned array; if they
|
|
61
|
+
* have their own provider needs, layer a separate wrapper outside
|
|
62
|
+
* `ToolsProvider`.
|
|
46
63
|
*
|
|
47
|
-
* Must be called within a C3UserConfigurationProvider tree (SaaS).
|
|
64
|
+
* Must be called within a `C3UserConfigurationProvider` tree (SaaS).
|
|
48
65
|
*/
|
|
49
|
-
export declare const useCamundaTools: (options: UseCamundaToolsOptions) =>
|
|
66
|
+
export declare const useCamundaTools: (options: UseCamundaToolsOptions) => UseCamundaToolsReturn;
|
|
@@ -1,75 +1,94 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/*
|
|
3
3
|
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
|
|
4
4
|
* under one or more contributor license agreements. Licensed under a commercial license.
|
|
5
5
|
* You may not use this file except in compliance with the commercial license.
|
|
6
6
|
*/
|
|
7
|
-
import { HeaderGlobalAction } from '@carbon/react';
|
|
7
|
+
import { HeaderGlobalAction as HeaderGlobalActionBase } from '@carbon/react';
|
|
8
8
|
import { Help as HelpIcon, Information, Notification as NotificationIcon, UserAvatar, } from '@carbon/react/icons/index.esm.js';
|
|
9
9
|
import { useContext, useMemo } from 'react';
|
|
10
10
|
import { C3NotificationsUnreadIcon } from '../../assets/c3-icons.js';
|
|
11
|
-
import {
|
|
11
|
+
import { useC3HelpCenter } from '../c3-help-center/c3-help-center-provider.js';
|
|
12
|
+
import C3NotificationProvider, { C3NotificationContext, } from '../c3-navigation/c3-notification-provider/c3-notification-provider.js';
|
|
12
13
|
import { C3InfoPanel } from './tools/c3-info-panel.js';
|
|
13
14
|
import { C3NotificationsPanel } from './tools/c3-notifications-panel.js';
|
|
14
15
|
import { C3UserPanel } from './tools/c3-user-panel.js';
|
|
16
|
+
/**
|
|
17
|
+
* Carbon's `HeaderGlobalAction` is typed as `React.FC` and omits both `ref`
|
|
18
|
+
* (the component uses `forwardRef` internally) and `leaveDelayMs`. Both work
|
|
19
|
+
* at runtime, so we widen the type at the import site — same pattern as
|
|
20
|
+
* `c3-navigation-appbar` / `-sidebar`.
|
|
21
|
+
*/
|
|
22
|
+
const HeaderGlobalAction = HeaderGlobalActionBase;
|
|
15
23
|
/**
|
|
16
24
|
* Single helper entry point for the standard Camunda tool set.
|
|
17
|
-
* Returns a ToolDescriptor[]
|
|
18
|
-
*
|
|
25
|
+
* Returns a `ToolDescriptor[]` for `C3NavigationV2`'s `tools` prop plus a
|
|
26
|
+
* `ToolsProvider` that conditionally mounts the providers each requested tool
|
|
27
|
+
* depends on. Custom tools can be appended to the returned array; if they
|
|
28
|
+
* have their own provider needs, layer a separate wrapper outside
|
|
29
|
+
* `ToolsProvider`.
|
|
19
30
|
*
|
|
20
|
-
* Must be called within a C3UserConfigurationProvider tree (SaaS).
|
|
31
|
+
* Must be called within a `C3UserConfigurationProvider` tree (SaaS).
|
|
21
32
|
*/
|
|
22
33
|
export const useCamundaTools = (options) => {
|
|
23
|
-
const {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const tools = [];
|
|
34
|
+
const { launcherButtonRef: helpButtonRef } = useC3HelpCenter();
|
|
35
|
+
const tools = useMemo(() => {
|
|
36
|
+
const result = [];
|
|
27
37
|
if (options.notifications !== undefined) {
|
|
28
38
|
const notifOptions = options.notifications;
|
|
29
39
|
const label = notifOptions.ariaLabel ?? 'Notifications';
|
|
30
|
-
|
|
40
|
+
result.push({
|
|
31
41
|
key: 'notifications',
|
|
32
42
|
label,
|
|
33
|
-
renderButton: ({ onClick, isActive }) =>
|
|
43
|
+
renderButton: ({ onClick, isActive }) => {
|
|
44
|
+
const { notifications, isFetching } = useContext(C3NotificationContext);
|
|
45
|
+
const hasUnread = !isFetching && notifications.some((n) => n.state === 'new');
|
|
46
|
+
return (_jsx(HeaderGlobalAction, { "aria-label": label, onClick: onClick, isActive: isActive, tooltipAlignment: 'center', leaveDelayMs: 100, children: hasUnread ? (_jsx(C3NotificationsUnreadIcon, { size: 20 })) : (_jsx(NotificationIcon, { size: 20 })) }));
|
|
47
|
+
},
|
|
34
48
|
panel: (_jsx(C3NotificationsPanel, { onLinkClick: notifOptions.onLinkClick })),
|
|
35
49
|
});
|
|
36
50
|
}
|
|
37
51
|
if (options.info !== undefined) {
|
|
38
52
|
const { elements, title, ariaLabel } = options.info;
|
|
39
53
|
const label = ariaLabel ?? 'Info';
|
|
40
|
-
|
|
54
|
+
result.push({
|
|
41
55
|
key: 'info',
|
|
42
56
|
label,
|
|
43
|
-
renderButton: ({ onClick, isActive }) => (_jsx(HeaderGlobalAction, { "aria-label": label, onClick: onClick, isActive: isActive, tooltipAlignment: 'center',
|
|
57
|
+
renderButton: ({ onClick, isActive }) => (_jsx(HeaderGlobalAction, { "aria-label": label, onClick: onClick, isActive: isActive, tooltipAlignment: 'center', leaveDelayMs: 100, children: _jsx(Information, { size: 20 }) })),
|
|
44
58
|
panel: (_jsx(C3InfoPanel, { elements: elements, title: title })),
|
|
45
59
|
});
|
|
46
60
|
}
|
|
47
61
|
if (options.help !== undefined) {
|
|
48
62
|
const { onClick, ariaLabel } = options.help;
|
|
49
63
|
const label = ariaLabel ?? 'Help';
|
|
50
|
-
|
|
64
|
+
result.push({
|
|
51
65
|
key: 'help',
|
|
52
66
|
label,
|
|
53
|
-
renderButton: ({ isActive: _isActive }) => (_jsx(HeaderGlobalAction, { "aria-label": label, onClick: onClick, tooltipAlignment: 'center',
|
|
67
|
+
renderButton: ({ isActive: _isActive }) => (_jsx(HeaderGlobalAction, { ref: helpButtonRef, "aria-label": label, onClick: onClick, tooltipAlignment: 'center', leaveDelayMs: 100, children: _jsx(HelpIcon, { size: 20 }) })),
|
|
54
68
|
// no panel, help is a plain button
|
|
55
69
|
});
|
|
56
70
|
}
|
|
57
71
|
if (options.user !== undefined) {
|
|
58
72
|
const { ariaLabel, ...panelProps } = options.user;
|
|
59
73
|
const label = ariaLabel ?? 'Account';
|
|
60
|
-
|
|
74
|
+
result.push({
|
|
61
75
|
key: 'user',
|
|
62
76
|
label,
|
|
63
|
-
renderButton: ({ onClick, isActive }) => (_jsx(HeaderGlobalAction, { "aria-label": label, onClick: onClick, isActive: isActive, tooltipAlignment: 'end',
|
|
77
|
+
renderButton: ({ onClick, isActive }) => (_jsx(HeaderGlobalAction, { "aria-label": label, onClick: onClick, isActive: isActive, tooltipAlignment: 'end', leaveDelayMs: 100, children: _jsx(UserAvatar, { size: 20 }) })),
|
|
64
78
|
panel: (_jsx(C3UserPanel, { ...panelProps })),
|
|
65
79
|
});
|
|
66
80
|
}
|
|
67
|
-
return
|
|
81
|
+
return result;
|
|
68
82
|
}, [
|
|
69
|
-
hasUnread,
|
|
70
83
|
options.notifications,
|
|
71
84
|
options.info,
|
|
72
85
|
options.help,
|
|
73
86
|
options.user,
|
|
87
|
+
helpButtonRef,
|
|
74
88
|
]);
|
|
89
|
+
const ToolsProvider = useMemo(() => {
|
|
90
|
+
const needsNotifications = options.notifications !== undefined;
|
|
91
|
+
return ({ children }) => needsNotifications ? (_jsx(C3NotificationProvider, { children: children })) : (_jsx(_Fragment, { children: children }));
|
|
92
|
+
}, [options.notifications !== undefined]);
|
|
93
|
+
return { tools, ToolsProvider };
|
|
75
94
|
};
|
|
@@ -24,15 +24,17 @@ function buildClusterSidebarEntries(clusters, { isAppVisible, resolveClusterLink
|
|
|
24
24
|
if (!endpoint)
|
|
25
25
|
return [];
|
|
26
26
|
const teaser = appTeaserRoutes?.[resolvedApp] ?? appTeaserRoutes?.[app];
|
|
27
|
-
// Admin has no dedicated status on the cluster DTO
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
// `appTeaserRoutes.admin` / `.identity`
|
|
31
|
-
|
|
27
|
+
// Admin/identity has no dedicated status on the cluster DTO; it rides
|
|
28
|
+
// the same ingress as zeebe, so we gate its link on zeebe's health —
|
|
29
|
+
// matching V1's "zeebe as admin health proxy" contract. Consumers can
|
|
30
|
+
// still surface a teaser via `appTeaserRoutes.admin` / `.identity`
|
|
31
|
+
// when the link is unavailable.
|
|
32
|
+
const healthKey = isAdminApp(app)
|
|
33
|
+
? 'zeebe'
|
|
34
|
+
: resolvedApp;
|
|
35
|
+
const linkProps = cluster.status?.[healthKey] === 'Healthy'
|
|
32
36
|
? (teaser ?? { href: endpoint })
|
|
33
|
-
:
|
|
34
|
-
? { href: endpoint }
|
|
35
|
-
: teaser;
|
|
37
|
+
: teaser;
|
|
36
38
|
if (!linkProps)
|
|
37
39
|
return [];
|
|
38
40
|
return [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camunda/camunda-composite-components",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.4",
|
|
4
4
|
"description": "Camunda Composite Components",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/camunda/camunda-cloud-management-apps/issues"
|
|
@@ -26,41 +26,41 @@
|
|
|
26
26
|
"jwt-decode": "4.0.0",
|
|
27
27
|
"react-error-boundary": "6.1.1",
|
|
28
28
|
"react-markdown": "10.1.0",
|
|
29
|
-
"semver": "7.
|
|
29
|
+
"semver": "7.8.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@auth0/auth0-spa-js": "2.19.
|
|
33
|
-
"@carbon/react": "1.
|
|
34
|
-
"@chromatic-com/storybook": "5.
|
|
32
|
+
"@auth0/auth0-spa-js": "2.19.3",
|
|
33
|
+
"@carbon/react": "1.107.1",
|
|
34
|
+
"@chromatic-com/storybook": "5.2.1",
|
|
35
35
|
"@mdx-js/react": "3.1.1",
|
|
36
|
-
"@playwright/test": "1.
|
|
37
|
-
"@storybook/addon-a11y": "10.
|
|
38
|
-
"@storybook/addon-docs": "10.
|
|
39
|
-
"@storybook/addon-links": "10.
|
|
40
|
-
"@storybook/addon-vitest": "10.
|
|
41
|
-
"@storybook/react": "10.
|
|
42
|
-
"@storybook/react-vite": "10.
|
|
43
|
-
"@vitest/browser": "4.1.
|
|
44
|
-
"@vitest/browser-playwright": "4.1.
|
|
45
|
-
"vitest": "4.1.
|
|
36
|
+
"@playwright/test": "1.60.0",
|
|
37
|
+
"@storybook/addon-a11y": "10.4.0",
|
|
38
|
+
"@storybook/addon-docs": "10.4.0",
|
|
39
|
+
"@storybook/addon-links": "10.4.0",
|
|
40
|
+
"@storybook/addon-vitest": "10.4.0",
|
|
41
|
+
"@storybook/react": "10.4.0",
|
|
42
|
+
"@storybook/react-vite": "10.4.0",
|
|
43
|
+
"@vitest/browser": "4.1.7",
|
|
44
|
+
"@vitest/browser-playwright": "4.1.7",
|
|
45
|
+
"vitest": "4.1.7",
|
|
46
46
|
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
47
47
|
"eslint-import-resolver-typescript": "4.4.4",
|
|
48
48
|
"eslint-plugin-react": "7.37.5",
|
|
49
|
-
"eslint-plugin-react-hooks": "7.
|
|
50
|
-
"eslint-plugin-storybook": "10.
|
|
49
|
+
"eslint-plugin-react-hooks": "7.1.1",
|
|
50
|
+
"eslint-plugin-storybook": "10.4.0",
|
|
51
51
|
"event-source-polyfill": "1.0.31",
|
|
52
|
-
"mixpanel-browser": "2.
|
|
53
|
-
"playwright": "1.
|
|
54
|
-
"react": "19.2.
|
|
55
|
-
"react-dom": "19.2.
|
|
56
|
-
"react-is": "19.2.
|
|
52
|
+
"mixpanel-browser": "2.79.0",
|
|
53
|
+
"playwright": "1.60.0",
|
|
54
|
+
"react": "19.2.6",
|
|
55
|
+
"react-dom": "19.2.6",
|
|
56
|
+
"react-is": "19.2.6",
|
|
57
57
|
"rimraf": "6.1.3",
|
|
58
58
|
"serve": "14.2.6",
|
|
59
|
-
"storybook": "10.
|
|
60
|
-
"styled-components": "6.4.
|
|
61
|
-
"typescript-eslint": "8.
|
|
62
|
-
"wait-on": "9.0.
|
|
63
|
-
"@camunda/ccma-shared-types": "0.0
|
|
59
|
+
"storybook": "10.4.0",
|
|
60
|
+
"styled-components": "6.4.2",
|
|
61
|
+
"typescript-eslint": "8.59.4",
|
|
62
|
+
"wait-on": "9.0.10",
|
|
63
|
+
"@camunda/ccma-shared-types": "0.1.0"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
66
|
"@carbon/react": "1.x",
|