@camunda/camunda-composite-components 0.23.4 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/lib/esm/package.json +22 -22
  2. package/lib/esm/src/assets/c3-icons.d.ts +4 -4
  3. package/lib/esm/src/components/c3-app-teaser/app-teaser-cards.d.ts +1 -1
  4. package/lib/esm/src/components/c3-help-center/help-center-hint.d.ts +1 -1
  5. package/lib/esm/src/components/c3-license-tag/c3-license-tag.d.ts +4 -5
  6. package/lib/esm/src/components/c3-license-tag/c3-license-tag.js +57 -46
  7. package/lib/esm/src/components/c3-navigation-v2/c3-breadcrumb-bar.js +2 -2
  8. package/lib/esm/src/components/c3-navigation-v2/c3-navigation-v2.js +6 -2
  9. package/lib/esm/src/components/c3-navigation-v2/c3-navigation-v2.types.d.ts +14 -0
  10. package/lib/esm/src/components/c3-navigation-v2/c3-sidebar.d.ts +1 -1
  11. package/lib/esm/src/components/c3-navigation-v2/c3-sidebar.js +62 -47
  12. package/lib/esm/src/components/c3-navigation-v2/index.d.ts +5 -3
  13. package/lib/esm/src/components/c3-navigation-v2/index.js +1 -0
  14. package/lib/esm/src/components/c3-navigation-v2/tools/c3-info-panel.d.ts +2 -1
  15. package/lib/esm/src/components/c3-navigation-v2/tools/c3-info-panel.js +1 -1
  16. package/lib/esm/src/components/c3-navigation-v2/tools/c3-notifications-panel.d.ts +11 -0
  17. package/lib/esm/src/components/c3-navigation-v2/tools/c3-notifications-panel.js +9 -5
  18. package/lib/esm/src/components/c3-navigation-v2/tools/c3-theme-selector.d.ts +25 -0
  19. package/lib/esm/src/components/c3-navigation-v2/tools/c3-theme-selector.js +15 -0
  20. package/lib/esm/src/components/c3-navigation-v2/tools/c3-user-panel.d.ts +15 -0
  21. package/lib/esm/src/components/c3-navigation-v2/tools/c3-user-panel.js +10 -17
  22. package/lib/esm/src/components/c3-navigation-v2/use-c3-navigation-v2.d.ts +3 -1
  23. package/lib/esm/src/components/c3-navigation-v2/use-c3-navigation-v2.js +2 -1
  24. package/lib/esm/src/components/c3-navigation-v2/use-camunda-tools.d.ts +7 -1
  25. package/lib/esm/src/components/c3-navigation-v2/use-camunda-tools.js +4 -4
  26. package/lib/esm/src/components/c3-navigation-v2/use-cluster-webapp-breadcrumbs.d.ts +20 -16
  27. package/lib/esm/src/components/c3-navigation-v2/use-cluster-webapp-breadcrumbs.js +156 -36
  28. package/lib/esm/src/components/c3-user-configuration/c3-profile-provider/c3-profile-provider.d.ts +1 -1
  29. package/lib/esm/src/contexts/c3-cluster-update-manager.d.ts +1 -1
  30. package/lib/esm/src/index.d.ts +2 -2
  31. package/lib/esm/src/index.js +1 -1
  32. package/lib/esm/src/utils/camunda.d.ts +1 -1
  33. package/package.json +23 -23
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/camunda-composite-components",
3
- "version": "0.23.4",
3
+ "version": "0.25.0",
4
4
  "description": "Camunda Composite Components",
5
5
  "bugs": {
6
6
  "url": "https://github.com/camunda/camunda-cloud-management-apps/issues"
@@ -50,42 +50,42 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "jwt-decode": "4.0.0",
53
- "react-error-boundary": "6.1.1",
53
+ "react-error-boundary": "6.1.2",
54
54
  "react-markdown": "10.1.0",
55
- "semver": "7.8.0"
55
+ "semver": "7.8.4"
56
56
  },
57
57
  "devDependencies": {
58
- "@auth0/auth0-spa-js": "2.19.3",
58
+ "@auth0/auth0-spa-js": "2.21.1",
59
59
  "@camunda/ccma-shared-types": "workspace:*",
60
- "@carbon/react": "1.107.1",
60
+ "@carbon/react": "1.109.0",
61
61
  "@chromatic-com/storybook": "5.2.1",
62
62
  "@mdx-js/react": "3.1.1",
63
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",
64
+ "@storybook/addon-a11y": "10.4.3",
65
+ "@storybook/addon-docs": "10.4.3",
66
+ "@storybook/addon-links": "10.4.3",
67
+ "@storybook/addon-vitest": "10.4.3",
68
+ "@storybook/react": "10.4.3",
69
+ "@storybook/react-vite": "10.4.3",
70
+ "@vitest/browser": "4.1.8",
71
+ "@vitest/browser-playwright": "4.1.8",
72
+ "vitest": "4.1.8",
73
73
  "conventional-changelog-conventionalcommits": "9.3.1",
74
- "eslint-import-resolver-typescript": "4.4.4",
74
+ "eslint-import-resolver-typescript": "4.4.5",
75
75
  "eslint-plugin-react": "7.37.5",
76
76
  "eslint-plugin-react-hooks": "7.1.1",
77
- "eslint-plugin-storybook": "10.4.0",
77
+ "eslint-plugin-storybook": "10.4.3",
78
78
  "event-source-polyfill": "1.0.31",
79
- "mixpanel-browser": "2.79.0",
79
+ "mixpanel-browser": "2.80.0",
80
80
  "playwright": "1.60.0",
81
- "react": "19.2.6",
82
- "react-dom": "19.2.6",
83
- "react-is": "19.2.6",
81
+ "react": "19.2.7",
82
+ "react-dom": "19.2.7",
83
+ "react-is": "19.2.7",
84
84
  "rimraf": "6.1.3",
85
85
  "serve": "14.2.6",
86
- "storybook": "10.4.0",
86
+ "storybook": "10.4.3",
87
87
  "styled-components": "6.4.2",
88
- "typescript-eslint": "8.59.4",
88
+ "typescript-eslint": "8.61.0",
89
89
  "wait-on": "9.0.10"
90
90
  },
91
91
  "peerDependencies": {
@@ -1,5 +1,5 @@
1
1
  import type { C3IconProps } from './c3-icons.types';
2
- export declare const C3AppMenuIcon: ({ size }: C3IconProps) => import("react/jsx-runtime").JSX.Element;
3
- export declare const C3NotificationsUnreadIcon: ({ size }: C3IconProps) => import("react/jsx-runtime").JSX.Element;
4
- export declare const C3BellIcon: ({ size }: C3IconProps) => import("react/jsx-runtime").JSX.Element;
5
- export declare const CamundaLogo: () => import("react/jsx-runtime").JSX.Element;
2
+ export declare const C3AppMenuIcon: ({ size }: C3IconProps) => import("react").JSX.Element;
3
+ export declare const C3NotificationsUnreadIcon: ({ size }: C3IconProps) => import("react").JSX.Element;
4
+ export declare const C3BellIcon: ({ size }: C3IconProps) => import("react").JSX.Element;
5
+ export declare const CamundaLogo: () => import("react").JSX.Element;
@@ -13,5 +13,5 @@ type AppTeaserCardsProps = {
13
13
  };
14
14
  subtext: string;
15
15
  };
16
- export declare const AppTeaserCards: ({ title, subtitle, cards, cta, subtext, }: AppTeaserCardsProps) => import("react/jsx-runtime").JSX.Element;
16
+ export declare const AppTeaserCards: ({ title, subtitle, cards, cta, subtext, }: AppTeaserCardsProps) => JSX.Element;
17
17
  export {};
@@ -1,4 +1,4 @@
1
1
  import type { JSX } from 'react';
2
2
  export declare const HelpCenterHint: ({ children }: {
3
3
  children: JSX.Element;
4
- }) => import("react/jsx-runtime").JSX.Element;
4
+ }) => JSX.Element;
@@ -8,10 +8,9 @@ export interface C3LicenseTagProps {
8
8
  expiresAt?: number | string;
9
9
  }
10
10
  /**
11
- * Renders a license status tag for the Camunda header. Handles all license
12
- * states: production, non-production (with tooltip), non-commercial,
13
- * non-commercial expiring (with countdown), and non-commercial expired.
14
- *
15
- * Drop into `headerTrailingContent` of C3NavigationV2.
11
+ * Renders license status tags for the Camunda header. Mirrors V1 c3-navigation:
12
+ * always renders a production-status tag, plus a non-commercial tag (or its
13
+ * expiring/expired variant) when isCommercial === false. Drop the element into
14
+ * `headerTrailingContent` of C3NavigationV2.
16
15
  */
17
16
  export declare const C3LicenseTag: (props: C3LicenseTagProps) => JSX.Element | null;
@@ -17,63 +17,74 @@ function resolveExpiresAt(value) {
17
17
  return Date.parse(value);
18
18
  return value;
19
19
  }
20
- function getTagVariant(props) {
20
+ // Matches V1 c3-navigation: production-status tag is always shown, and a
21
+ // non-commercial tag is shown additionally when isCommercial === false. They
22
+ // stack rather than override each other.
23
+ function getTagVariants(props) {
21
24
  const { isProductionLicense, isCommercial, expiresAt: rawExpiresAt } = props;
22
25
  const expiresAt = resolveExpiresAt(rawExpiresAt);
23
26
  const now = Date.now();
24
- // Non-commercial expired
25
- if (isCommercial === false && expiresAt !== undefined && expiresAt < now) {
26
- return {
27
- label: 'Non-commercial license - expired',
28
- color: 'red',
29
- icon: Warning,
30
- tooltip: (_jsx("p", { children: "To continue using all features, please renew your license." })),
31
- };
32
- }
33
- // Non-commercial about to expire (within 30 days)
34
- if (isCommercial === false &&
35
- expiresAt !== undefined &&
36
- expiresAt - LICENSE_EXPIRY_THRESHOLD < now &&
37
- expiresAt > now) {
38
- const daysLeft = Math.floor((expiresAt - now) / DAY);
39
- return {
40
- label: `Non-commercial license - ${daysLeft} ${daysLeft > 1 ? 'days' : 'day'} left`,
41
- color: 'blue',
42
- icon: Time,
43
- tooltip: (_jsx("p", { children: "Please renew and provide new license keys to continue production use of Camunda." })),
44
- };
45
- }
46
- // Non-commercial (not expiring soon or no expiry)
47
- if (isCommercial === false &&
48
- (expiresAt === undefined || expiresAt - LICENSE_EXPIRY_THRESHOLD >= now)) {
49
- return {
50
- label: 'Non-commercial license',
51
- color: 'gray',
52
- };
53
- }
54
- // Production or non-production license
55
- return {
27
+ const variants = [];
28
+ variants.push({
29
+ key: 'license-tag',
56
30
  label: isProductionLicense
57
31
  ? 'Production license'
58
32
  : 'Non-production license',
59
33
  color: 'gray',
60
34
  tooltip: isProductionLicense ? undefined : (_jsxs("p", { children: ["Non-production license. For production usage details, visit our", ' ', _jsx(Link, { href: NON_PRODUCTION_TERMS_LINK, target: '_blank', rel: 'noopener noreferrer', style: { display: 'inline' }, children: "terms & conditions page" }), ' ', "or", ' ', _jsx(Link, { href: SALES_CONTACT_LINK, target: '_blank', rel: 'noopener noreferrer', style: { display: 'inline' }, children: "contact our sales team" }), "."] })),
61
- };
35
+ });
36
+ if (isCommercial === false) {
37
+ if (expiresAt !== undefined && expiresAt < now) {
38
+ variants.push({
39
+ key: 'non-commercial-license-tag-is-expired',
40
+ label: 'Non-commercial license - expired',
41
+ color: 'red',
42
+ icon: Warning,
43
+ tooltip: (_jsx("p", { children: "To continue using all features, please renew your license." })),
44
+ });
45
+ }
46
+ else if (expiresAt !== undefined &&
47
+ expiresAt - LICENSE_EXPIRY_THRESHOLD < now &&
48
+ expiresAt > now) {
49
+ const daysLeft = Math.floor((expiresAt - now) / DAY);
50
+ variants.push({
51
+ key: 'non-commercial-license-tag-about-to-expire',
52
+ label: `Non-commercial license - ${daysLeft} ${daysLeft > 1 ? 'days' : 'day'} left`,
53
+ color: 'blue',
54
+ icon: Time,
55
+ tooltip: (_jsx("p", { children: "Please renew and provide new license keys to continue production use of Camunda." })),
56
+ });
57
+ }
58
+ else {
59
+ variants.push({
60
+ key: 'non-commercial-license-tag',
61
+ label: 'Non-commercial license',
62
+ color: 'gray',
63
+ });
64
+ }
65
+ }
66
+ return variants;
67
+ }
68
+ function renderVariant(variant) {
69
+ const { key, label, color, icon, tooltip } = variant;
70
+ if (tooltip) {
71
+ return (_jsxs(Toggletip, { align: 'bottom', children: [_jsx(ToggletipButton, { as: 'span', label: label, children: _jsx(Tag, { type: color, renderIcon: icon, style: { margin: 0, cursor: 'pointer' }, children: label }) }), _jsx(ToggletipContent, { children: tooltip })] }, key));
72
+ }
73
+ return (_jsx(Tag, { type: color, renderIcon: icon, style: { margin: 0 }, children: label }, key));
62
74
  }
63
75
  /**
64
- * Renders a license status tag for the Camunda header. Handles all license
65
- * states: production, non-production (with tooltip), non-commercial,
66
- * non-commercial expiring (with countdown), and non-commercial expired.
67
- *
68
- * Drop into `headerTrailingContent` of C3NavigationV2.
76
+ * Renders license status tags for the Camunda header. Mirrors V1 c3-navigation:
77
+ * always renders a production-status tag, plus a non-commercial tag (or its
78
+ * expiring/expired variant) when isCommercial === false. Drop the element into
79
+ * `headerTrailingContent` of C3NavigationV2.
69
80
  */
70
81
  export const C3LicenseTag = (props) => {
71
- const variant = getTagVariant(props);
72
- if (!variant)
82
+ const variants = getTagVariants(props);
83
+ if (variants.length === 0)
73
84
  return null;
74
- const { label, color, icon, tooltip } = variant;
75
- if (tooltip) {
76
- return (_jsxs(Toggletip, { align: 'bottom', children: [_jsx(ToggletipButton, { as: 'span', label: label, children: _jsx(Tag, { type: color, renderIcon: icon, style: { margin: 0, cursor: 'pointer' }, children: label }) }), _jsx(ToggletipContent, { children: tooltip })] }));
77
- }
78
- return (_jsx(Tag, { type: color, renderIcon: icon, style: { margin: 0 }, children: label }));
85
+ return (_jsx("span", { style: {
86
+ display: 'inline-flex',
87
+ alignItems: 'center',
88
+ gap: 'var(--cds-spacing-02)',
89
+ }, children: variants.map(renderVariant) }));
79
90
  };
@@ -173,7 +173,7 @@ const Separator = styled.span `
173
173
  const DropdownEntry = ({ item, onSelect, linkComponent, }) => {
174
174
  const Icon = item.icon;
175
175
  return (_jsxs(DropdownItem, { as: item.linkProps?.href !== undefined
176
- ? (linkComponent ?? 'a')
176
+ ? 'a'
177
177
  : item.linkProps
178
178
  ? linkComponent
179
179
  : undefined, ...(item.linkProps ?? {}), "$isSelected": !!item.isSelected, onClick: () => onSelect(item), role: 'option', tabIndex: -1, "aria-selected": !!item.isSelected, children: [Icon && (_jsx(Icon, { size: 16, style: { color: 'var(--cds-icon-secondary)', flexShrink: 0 } })), _jsx(DropdownItemLabel, { children: item.label }), item.trailingElement, item.isSelected && (_jsx(Checkmark, { size: 16, style: { color: 'var(--cds-icon-primary)', flexShrink: 0 } }))] }));
@@ -345,7 +345,7 @@ const BreadcrumbSegmentComponent = ({ segment, isLast, linkComponent, }) => {
345
345
  }, [close]);
346
346
  const handleDropdownKeyDown = usePanelKeyboard(dropdownPanelRef, close, chevronRef, isOpen, '[role="option"]');
347
347
  return (_jsxs(_Fragment, { children: [_jsxs(SegmentWrapper, { ref: wrapperRef, children: [_jsxs(SegmentButton, { ref: segmentButtonRef, as: segment.linkProps?.href !== undefined
348
- ? (linkComponent ?? 'a')
348
+ ? 'a'
349
349
  : segment.linkProps
350
350
  ? linkComponent
351
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) => {
@@ -18,9 +18,13 @@ const StyledHeader = styled(Header) `
18
18
  z-index: 8001 !important;
19
19
 
20
20
  // Carbon defaults the tools area to flex: 1, which splits the row
21
- // 50/50 with breadcrumbs. Make it intrinsic instead.
21
+ // 50/50 with breadcrumbs. Make it intrinsic and pin it to the right
22
+ // (margin-inline-start: auto) so the tools stay flush right even
23
+ // when the breadcrumb bar is absent — loading state, or routes that
24
+ // don't publish breadcrumbs.
22
25
  .cds--header__global {
23
26
  flex: 0 0 auto;
27
+ margin-inline-start: auto;
24
28
  }
25
29
  `;
26
30
  const LogoSection = styled.a `
@@ -63,7 +67,7 @@ export const C3NavigationV2 = ({ app, skipToContentTargetId, skipToContentLabel
63
67
  root.style.removeProperty('--c3-sidebar-width');
64
68
  };
65
69
  }, [sidebarWidth]);
66
- return (_jsxs(_Fragment, { children: [_jsxs(StyledHeader, { "aria-label": ariaLabel, children: [_jsx(SkipToContent, { href: `#${skipToContentTargetId}`, children: skipToContentLabel }), _jsxs(LogoSection, { as: app.linkProps ? LinkEl : 'div', ...(app.linkProps ?? {}), children: [_jsx(CamundaLogo, { "aria-label": 'Camunda' }), app.name && _jsx("span", { children: app.name })] }), breadcrumbs && breadcrumbs.length > 0 && (_jsx(C3BreadcrumbBar, { segments: breadcrumbs, linkComponent: LinkEl })), _jsxs(HeaderGlobalBar, { children: [headerTrailingContent && (_jsx("span", { style: {
70
+ return (_jsxs(_Fragment, { children: [_jsxs(StyledHeader, { "aria-label": ariaLabel, children: [_jsx(SkipToContent, { href: `#${skipToContentTargetId}`, children: skipToContentLabel }), _jsx(LogoSection, { as: app.linkProps ? LinkEl : 'div', "aria-label": app.name ? `Camunda ${app.name}` : 'Camunda', ...(app.linkProps ?? {}), children: _jsx(CamundaLogo, {}) }), breadcrumbs && breadcrumbs.length > 0 && (_jsx(C3BreadcrumbBar, { segments: breadcrumbs, linkComponent: LinkEl })), _jsxs(HeaderGlobalBar, { children: [headerTrailingContent && (_jsx("span", { style: {
67
71
  display: 'flex',
68
72
  alignItems: 'center',
69
73
  padding: '0 var(--cds-spacing-03)',
@@ -147,6 +147,19 @@ export interface SidebarSection {
147
147
  compact?: boolean;
148
148
  }
149
149
  export type SidebarNode = SidebarItem | SidebarGroupItem | SidebarGroup | SidebarSection;
150
+ export interface SidebarLabels {
151
+ /** Toggle button text when expanded. Defaults to `'Collapse'`. */
152
+ collapse?: string;
153
+ /** Tooltip on the collapsed toggle button. Defaults to `'Expand'`. */
154
+ expand?: string;
155
+ /** Aria-label on the toggle button. Receives `isExpanded`. Defaults to `'Collapse sidebar'` / `'Expand sidebar'`. */
156
+ toggleAriaLabel?: (isExpanded: boolean) => string;
157
+ /** Aria-label on a group's chevron. Receives `{label, isExpanded}`. Defaults to `'Collapse {label}'` / `'Expand {label}'`. */
158
+ groupToggleAriaLabel?: (args: {
159
+ label: string;
160
+ isExpanded: boolean;
161
+ }) => string;
162
+ }
150
163
  export interface SidebarProps {
151
164
  ariaLabel: string;
152
165
  children: SidebarNode[];
@@ -155,6 +168,7 @@ export interface SidebarProps {
155
168
  expandedWidth?: string;
156
169
  collapsedWidth?: string;
157
170
  linkComponent?: LinkComponent;
171
+ labels?: SidebarLabels;
158
172
  }
159
173
  export interface AppProps {
160
174
  name?: string;
@@ -5,4 +5,4 @@ import type { SidebarProps } from './c3-navigation-v2.types';
5
5
  * Loose items (not wrapped in a section) are valid and behave as an implicit
6
6
  * untitled group at the top.
7
7
  */
8
- export declare const C3Sidebar: ({ ariaLabel, children: nodes, isExpanded, onToggleExpanded, expandedWidth, collapsedWidth, linkComponent, }: SidebarProps) => JSX.Element;
8
+ export declare const C3Sidebar: ({ ariaLabel, children: nodes, isExpanded, onToggleExpanded, expandedWidth, collapsedWidth, linkComponent, labels, }: SidebarProps) => JSX.Element;
@@ -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';
9
+ import { createContext, useContext, useEffect, useRef, useState, } from 'react';
10
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
- min-height: 2.5rem;
43
+ height: var(--cds-spacing-09);
44
+ box-sizing: border-box;
40
45
  padding: ${(p) => {
41
46
  if (p.$depth > 0)
42
- return `0.625rem var(--cds-spacing-05) 0.625rem ${0.75 + p.$depth * 1.75}rem`;
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')};
@@ -86,7 +89,8 @@ const GroupHeader = styled.div `
86
89
  align-items: center;
87
90
  flex-wrap: nowrap;
88
91
  width: 100%;
89
- min-height: 2.5rem;
92
+ height: var(--cds-spacing-09);
93
+ box-sizing: border-box;
90
94
  padding-right: var(--cds-spacing-04);
91
95
  gap: var(--cds-spacing-03);
92
96
  overflow: hidden;
@@ -98,21 +102,20 @@ const GroupHeader = styled.div `
98
102
  transition: background 0.15s, color 0.15s;
99
103
 
100
104
  &:hover:not(:has(button[data-expand]:hover)) {
101
- background: ${p.$isActive
102
- ? 'var(--cds-layer-selected)'
103
- : 'var(--cds-layer-hover)'};
105
+ background: ${p.$isActive ? 'var(--cds-layer-selected)' : 'var(--cds-layer-hover)'};
104
106
  }
105
107
  `}
106
108
  `;
107
109
  const GroupLabelButton = styled.button `
108
110
  display: flex;
109
111
  align-items: center;
112
+ align-self: stretch;
110
113
  flex: 1;
111
114
  min-width: 0;
112
115
  padding: ${(p) => {
113
116
  if (p.$depth > 0)
114
- return `var(--cds-spacing-04) 0 var(--cds-spacing-04) ${0.75 + p.$depth * 1.75}rem`;
115
- return 'var(--cds-spacing-04) 0 var(--cds-spacing-04) var(--cds-spacing-05)';
117
+ return `0 0 0 ${0.75 + p.$depth * 1.75}rem`;
118
+ return '0 0 0 var(--cds-spacing-05)';
116
119
  }};
117
120
  background: transparent;
118
121
  border: none;
@@ -139,7 +142,11 @@ const ExpandButton = styled.button `
139
142
  display: flex;
140
143
  align-items: center;
141
144
  justify-content: center;
142
- padding: var(--cds-spacing-02);
145
+ flex-shrink: 0;
146
+ align-self: center;
147
+ width: var(--cds-spacing-07);
148
+ height: var(--cds-spacing-07);
149
+ padding: 0;
143
150
  background: transparent;
144
151
  border: none;
145
152
  border-radius: 4px;
@@ -159,12 +166,13 @@ const ExpandButton = styled.button `
159
166
  const PlainGroupLabel = styled.span `
160
167
  display: flex;
161
168
  align-items: center;
169
+ align-self: stretch;
162
170
  flex: 1;
163
171
  min-width: 0;
164
172
  padding: ${(p) => {
165
173
  if (p.$depth > 0)
166
- return `var(--cds-spacing-04) 0 var(--cds-spacing-04) ${0.75 + p.$depth * 1.75}rem`;
167
- return 'var(--cds-spacing-04) 0 var(--cds-spacing-04) var(--cds-spacing-05)';
174
+ return `0 0 0 ${0.75 + p.$depth * 1.75}rem`;
175
+ return '0 0 0 var(--cds-spacing-05)';
168
176
  }};
169
177
  gap: var(--cds-spacing-04);
170
178
  color: var(--cds-text-secondary);
@@ -231,10 +239,9 @@ const CollapseButton = styled.button `
231
239
  flex-wrap: nowrap;
232
240
  gap: var(--cds-spacing-04);
233
241
  width: 100%;
234
- min-height: 2.5rem;
235
- padding: ${(p) => p.$isExpanded
236
- ? 'var(--cds-spacing-04) var(--cds-spacing-05)'
237
- : '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')};
238
245
  justify-content: ${(p) => (p.$isExpanded ? 'flex-start' : 'center')};
239
246
  background: transparent;
240
247
  border: none;
@@ -316,13 +323,15 @@ const GroupItemNode = ({ node, sidebarExpanded, depth, linkComponent, }) => {
316
323
  };
317
324
  const GroupNode = ({ node, sidebarExpanded, depth, linkComponent, }) => {
318
325
  const Icon = node.icon;
326
+ const { groupToggleAria } = useSidebarLabels();
319
327
  if (!sidebarExpanded) {
320
328
  const collapsed = (_jsx(NavButton, { "$isActive": false, "$isExpanded": false, "$depth": 0, onClick: node.onToggleExpand, children: _jsx(Icon, { size: 20, style: { flexShrink: 0 } }) }));
321
329
  return (_jsx(CollapsedItemTooltip, { label: node.label, children: collapsed }));
322
330
  }
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
324
- ? `Collapse ${node.label}`
325
- : `Expand ${node.label}`, "aria-expanded": !!node.isExpanded, children: node.isExpanded ? (_jsx(ChevronUp, { size: 16 })) : (_jsx(ChevronDown, { size: 16 })) }))] }), node.isExpanded &&
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 &&
326
335
  node.children.map((child) => (_jsx(SidebarNodeComponent, { node: child, sidebarExpanded: sidebarExpanded, depth: depth + 1, linkComponent: linkComponent }, child.key)))] }));
327
336
  };
328
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)))] }));
@@ -347,31 +356,37 @@ const SidebarNodeComponent = ({ node, sidebarExpanded, depth, linkComponent, hid
347
356
  * Loose items (not wrapped in a section) are valid and behave as an implicit
348
357
  * untitled group at the top.
349
358
  */
350
- 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, }) => {
351
360
  const width = isExpanded ? expandedWidth : collapsedWidth;
352
361
  const prunedNodes = pruneChildren(nodes);
353
- return (_jsxs(SidebarNav, { "$width": width, "aria-label": ariaLabel, children: [_jsx(ScrollArea, { "$sidebarExpanded": isExpanded, children: (() => {
354
- let sectionSeen = false;
355
- let prevSectionCompact = false;
356
- let hasNonSectionNodes = false;
357
- return prunedNodes.map((node) => {
358
- let hideTopDivider = false;
359
- let eatScrollPadding = false;
360
- let tight = false;
361
- if (node.type === 'section') {
362
- const isFirst = !sectionSeen;
363
- hideTopDivider =
364
- (isFirst && !hasNonSectionNodes) || !!node.compact;
365
- eatScrollPadding =
366
- isFirst && !hasNonSectionNodes && !!node.compact;
367
- tight = prevSectionCompact;
368
- sectionSeen = true;
369
- prevSectionCompact = !!node.compact;
370
- }
371
- else {
372
- hasNonSectionNodes = true;
373
- }
374
- return (_jsx(SidebarNodeComponent, { node: node, sidebarExpanded: isExpanded, depth: 0, linkComponent: linkComponent, hideTopDivider: hideTopDivider, eatScrollPadding: eatScrollPadding, tight: tight }, node.key));
375
- });
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" })] }) }) }))] }));
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 })] }) }) }))] }) }));
377
392
  };
@@ -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 { C3UserPanelProps, UserPanelElement, } from './tools/c3-user-panel';
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';
@@ -6,6 +6,7 @@ export interface InfoPanelElement {
6
6
  }
7
7
  export interface C3InfoPanelProps {
8
8
  elements: InfoPanelElement[];
9
- title?: string;
9
+ /** Defaults to `'Info'`. */
10
+ title?: string | null;
10
11
  }
11
12
  export declare const C3InfoPanel: FC<C3InfoPanelProps>;
@@ -30,4 +30,4 @@ const LinkButton = styled.button `
30
30
  outline-offset: -2px;
31
31
  }
32
32
  `;
33
- export const C3InfoPanel = ({ elements, title = 'Help & Resources', }) => (_jsxs(_Fragment, { children: [_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))) })] }));
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
- justifyContent: 'space-between',
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: "Notifications" }), notifications.length > 0 && (_jsx(DismissAllButton, { kind: 'ghost', size: 'sm', onClick: () => dismissAll(notifications), children: "Dismiss all" }))] }), notifications.length > 0 ? ([...notifications].sort(sortDescending).map((notification) => (_jsx(C3NotificationContainer, { onRead: () => undefined, onDismiss: () => dismiss(notification), originalOnLinkClick: onLinkClick, onLinkClick: () => {
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: "No notifications" }), _jsx(EmptyStateDescription, { children: "New updates regarding your processes, clusters and more will appear here." })] }))] }));
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
  };