@camunda/camunda-composite-components 0.25.2 → 0.25.3

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/camunda-composite-components",
3
- "version": "0.25.2",
3
+ "version": "0.25.3",
4
4
  "description": "Camunda Composite Components",
5
5
  "bugs": {
6
6
  "url": "https://github.com/camunda/camunda-cloud-management-apps/issues"
@@ -70,7 +70,7 @@
70
70
  "@vitest/browser": "4.1.9",
71
71
  "@vitest/browser-playwright": "4.1.9",
72
72
  "vitest": "4.1.9",
73
- "conventional-changelog-conventionalcommits": "9.3.1",
73
+ "conventional-changelog-conventionalcommits": "10.2.0",
74
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",
@@ -327,7 +327,8 @@ export const C3DataTable = ({ data, headers, options, toolbar: singleToolbar, to
327
327
  multiItemElement.multiOnClick(inputData.filter((row) => selectedItems.includes(row.id)));
328
328
  }, renderIcon: multiItemElement.icon, disabled: (multiItemElement.disabled &&
329
329
  multiItemElement.disabled(inputData.filter((row) => selectedItems.includes(row.id)))) ||
330
- selectedItems.length === 0, kind: multiItemElement.kind, children: multiItemElement.label }, multiItemElement.id));
330
+ (selectedItems.length === 0 &&
331
+ !multiItemElement.allowWithoutSelection), kind: multiItemElement.kind, children: multiItemElement.label }, multiItemElement.id));
331
332
  }
332
333
  else if (toolbarElement.type === 'multiSelect') {
333
334
  const multiSelectElement = toolbarElement;
@@ -54,6 +54,15 @@ export interface C3DataTableToolbarMultiItemButtonProps<T extends RowBaseProps>
54
54
  multiOnClick: (selectedItems: T[]) => void;
55
55
  disabled?: (selectedItems: T[]) => boolean;
56
56
  kind?: any;
57
+ /**
58
+ * By default a `multiItemButton` is disabled whenever no rows are
59
+ * selected. Set this to `true` to keep the button enabled even with an
60
+ * empty selection (e.g. when the action can also be triggered without
61
+ * pre-selecting rows, such as opening a dialog that lets the user type
62
+ * or paste values in directly). The `disabled` callback above still
63
+ * applies on top of this.
64
+ */
65
+ allowWithoutSelection?: boolean;
57
66
  }
58
67
  export interface C3DataTableToolbarMenuButtonItemProps extends C3DataTableCommonToolbarProps {
59
68
  type: 'menuButton';
@@ -9,6 +9,7 @@ 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
+ import { resolveLinkAs } from './resolve-link-as.js';
12
13
  // `display: contents` so nested flex doesn't trap min-content; label
13
14
  // shrinkage propagates to the breadcrumb row directly.
14
15
  const SegmentWrapper = styled.div `
@@ -172,11 +173,7 @@ const Separator = styled.span `
172
173
  `;
173
174
  const DropdownEntry = ({ item, onSelect, linkComponent, }) => {
174
175
  const Icon = item.icon;
175
- return (_jsxs(DropdownItem, { as: item.linkProps?.href !== undefined
176
- ? 'a'
177
- : item.linkProps
178
- ? linkComponent
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 } }))] }));
176
+ return (_jsxs(DropdownItem, { as: resolveLinkAs(item.linkProps, linkComponent), ...(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 } }))] }));
180
177
  };
181
178
  const DropdownTitle = styled.div `
182
179
  padding: var(--cds-spacing-04) var(--cds-spacing-05) var(--cds-spacing-03);
@@ -344,11 +341,7 @@ const BreadcrumbSegmentComponent = ({ segment, isLast, linkComponent, }) => {
344
341
  close();
345
342
  }, [close]);
346
343
  const handleDropdownKeyDown = usePanelKeyboard(dropdownPanelRef, close, chevronRef, isOpen, '[role="option"]');
347
- return (_jsxs(_Fragment, { children: [_jsxs(SegmentWrapper, { ref: wrapperRef, children: [_jsxs(SegmentButton, { ref: segmentButtonRef, as: segment.linkProps?.href !== undefined
348
- ? 'a'
349
- : segment.linkProps
350
- ? linkComponent
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) => {
344
+ return (_jsxs(_Fragment, { children: [_jsxs(SegmentWrapper, { ref: wrapperRef, children: [_jsxs(SegmentButton, { ref: segmentButtonRef, as: resolveLinkAs(segment.linkProps, linkComponent), ...(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
345
  if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') {
353
346
  if (!isOpen) {
354
347
  e.preventDefault();
@@ -15,7 +15,7 @@ const StyledHeader = styled(Header) `
15
15
  background-color: var(--cds-layer-01) !important;
16
16
  border-bottom: 1px solid var(--cds-border-subtle) !important;
17
17
  box-shadow: none !important;
18
- z-index: 8001 !important;
18
+ z-index: calc(var(--c3-nav-z-index, 8000) + 1) !important;
19
19
 
20
20
  // Carbon defaults the tools area to flex: 1, which splits the row
21
21
  // 50/50 with breadcrumbs. Make it intrinsic and pin it to the right
@@ -23,11 +23,15 @@ export interface LinkProps {
23
23
  href?: string;
24
24
  [key: string]: unknown;
25
25
  }
26
- type IconComponent = React.ComponentType<{
26
+ export type IconProps = {
27
27
  size?: number;
28
28
  style?: React.CSSProperties;
29
+ };
30
+ export type IconComponent = React.ComponentType<IconProps> | React.ForwardRefExoticComponent<{
31
+ size?: number | string;
32
+ style?: React.CSSProperties;
29
33
  }>;
30
- export type LinkComponent = React.ComponentType<LinkProps & React.RefAttributes<HTMLAnchorElement>> | string;
34
+ export type LinkComponent = React.ElementType;
31
35
  export interface BreadcrumbDropdownItem {
32
36
  key: string;
33
37
  label: string;
@@ -82,9 +86,7 @@ export interface GlobalActionButton {
82
86
  * Required when `element` is omitted. Ignored when `element` is supplied
83
87
  * (the custom element renders instead of the default icon button).
84
88
  */
85
- icon?: React.ComponentType<{
86
- size?: number;
87
- }>;
89
+ icon?: IconComponent;
88
90
  onClick?: () => void;
89
91
  element?: ReactNode;
90
92
  }
@@ -199,4 +201,3 @@ export interface C3NavigationV2Props {
199
201
  /** Called when the active tool panel changes (including close via outside click). */
200
202
  onActiveToolChange?: (key: string | null) => void;
201
203
  }
202
- export {};
@@ -8,6 +8,7 @@ import { Popover, PopoverContent } from '@carbon/react';
8
8
  import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp, } from '@carbon/react/icons/index.esm.js';
9
9
  import { createContext, useContext, useEffect, useRef, useState, } from 'react';
10
10
  import styled, { css } from 'styled-components';
11
+ import { resolveLinkAs } from './resolve-link-as.js';
11
12
  const SidebarLabelsContext = createContext({
12
13
  groupToggleAria: ({ label, isExpanded }) => isExpanded ? `Collapse ${label}` : `Expand ${label}`,
13
14
  });
@@ -23,7 +24,7 @@ const SidebarNav = styled.nav `
23
24
  display: flex;
24
25
  flex-direction: column;
25
26
  transition: width 0.15s ease-out;
26
- z-index: 8000;
27
+ z-index: var(--c3-nav-z-index, 8000);
27
28
  `;
28
29
  const ScrollArea = styled.div `
29
30
  flex: 1;
@@ -263,13 +264,6 @@ const CollapseButton = styled.button `
263
264
  outline-offset: -2px;
264
265
  }
265
266
  `;
266
- const resolveLinkAs = (linkProps, linkComponent) => {
267
- if (!linkProps)
268
- return undefined;
269
- if ('href' in linkProps)
270
- return 'a';
271
- return linkComponent;
272
- };
273
267
  const pruneNode = (node) => {
274
268
  if (node.type === 'item') {
275
269
  if (!node.onClick && !node.linkProps)
@@ -17,7 +17,7 @@ const ToolsPanel = styled.div `
17
17
  background: var(--cds-layer-01);
18
18
  border-left: 1px solid var(--cds-border-subtle);
19
19
  overflow-y: auto;
20
- z-index: 8000;
20
+ z-index: var(--c3-nav-z-index, 8000);
21
21
  display: flex;
22
22
  flex-direction: column;
23
23
 
@@ -0,0 +1,16 @@
1
+ import type { LinkComponent } from './c3-navigation-v2.types';
2
+ /**
3
+ * Decide which element a clickable nav surface renders as based on the
4
+ * caller's `linkProps`.
5
+ *
6
+ * Preference, in order:
7
+ * 1. If `to` is set and the consumer has wired a `linkComponent`, render
8
+ * through the consumer wrapper so client-side routing stays in effect.
9
+ * 2. If `href` is set, render a plain `<a>` so cross-origin / no-router
10
+ * callers reload as expected and a router-aware Link does not try to
11
+ * SPA-route to another domain.
12
+ * 3. Otherwise (a `to`-only call with no `linkComponent`, or a bare
13
+ * onClick), defer to the consumer's `linkComponent` or fall back to
14
+ * the underlying element default.
15
+ */
16
+ export declare const resolveLinkAs: (linkProps: Record<string, unknown> | undefined, linkComponent?: LinkComponent) => LinkComponent | undefined;
@@ -0,0 +1,28 @@
1
+ /*
2
+ * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
3
+ * under one or more contributor license agreements. Licensed under a commercial license.
4
+ * You may not use this file except in compliance with the commercial license.
5
+ */
6
+ /**
7
+ * Decide which element a clickable nav surface renders as based on the
8
+ * caller's `linkProps`.
9
+ *
10
+ * Preference, in order:
11
+ * 1. If `to` is set and the consumer has wired a `linkComponent`, render
12
+ * through the consumer wrapper so client-side routing stays in effect.
13
+ * 2. If `href` is set, render a plain `<a>` so cross-origin / no-router
14
+ * callers reload as expected and a router-aware Link does not try to
15
+ * SPA-route to another domain.
16
+ * 3. Otherwise (a `to`-only call with no `linkComponent`, or a bare
17
+ * onClick), defer to the consumer's `linkComponent` or fall back to
18
+ * the underlying element default.
19
+ */
20
+ export const resolveLinkAs = (linkProps, linkComponent) => {
21
+ if (!linkProps)
22
+ return undefined;
23
+ if (linkProps.to !== undefined && linkComponent)
24
+ return linkComponent;
25
+ if (linkProps.href !== undefined)
26
+ return 'a';
27
+ return linkComponent;
28
+ };
@@ -1,13 +1,10 @@
1
- import type { BreadcrumbAction, C3NavigationV2Props, GlobalActionButton, LinkComponent, LinkProps, SidebarLabels, ToolDescriptor } from './c3-navigation-v2.types';
1
+ import type { BreadcrumbAction, C3NavigationV2Props, GlobalActionButton, IconComponent, LinkComponent, LinkProps, SidebarLabels, ToolDescriptor } from './c3-navigation-v2.types';
2
2
  type ActiveMatcher = boolean | string | ((activeItemKey: string) => boolean);
3
3
  export interface ItemDescriptor {
4
4
  type: 'item';
5
5
  key: string;
6
6
  label: string;
7
- icon: React.ComponentType<{
8
- size?: number;
9
- style?: React.CSSProperties;
10
- }>;
7
+ icon: IconComponent;
11
8
  linkProps?: LinkProps;
12
9
  onClick?: () => void;
13
10
  isActive?: ActiveMatcher;
@@ -17,10 +14,7 @@ export interface GroupItemDescriptor {
17
14
  type: 'group-item';
18
15
  key: string;
19
16
  label: string;
20
- icon: React.ComponentType<{
21
- size?: number;
22
- style?: React.CSSProperties;
23
- }>;
17
+ icon: IconComponent;
24
18
  linkProps?: LinkProps;
25
19
  onClick?: () => void;
26
20
  trailingElement?: React.ReactNode;
@@ -32,10 +26,7 @@ export interface GroupDescriptor {
32
26
  type: 'group';
33
27
  key: string;
34
28
  label: string;
35
- icon: React.ComponentType<{
36
- size?: number;
37
- style?: React.CSSProperties;
38
- }>;
29
+ icon: IconComponent;
39
30
  trailingElement?: React.ReactNode;
40
31
  defaultExpanded?: boolean;
41
32
  children: SidebarNodeDescriptor[];
@@ -51,10 +42,7 @@ export type SidebarNodeDescriptor = ItemDescriptor | GroupItemDescriptor | Group
51
42
  export interface BreadcrumbDropdownItemDescriptor {
52
43
  key: string;
53
44
  label: string;
54
- icon?: React.ComponentType<{
55
- size?: number;
56
- style?: React.CSSProperties;
57
- }>;
45
+ icon?: IconComponent;
58
46
  isSelected?: ActiveMatcher;
59
47
  onClick?: () => void;
60
48
  linkProps?: LinkProps;
@@ -63,10 +51,7 @@ export interface BreadcrumbDropdownItemDescriptor {
63
51
  export interface BreadcrumbDescriptor {
64
52
  key: string;
65
53
  label: string | ((activeItemKey: string) => string);
66
- icon?: React.ComponentType<{
67
- size?: number;
68
- style?: React.CSSProperties;
69
- }>;
54
+ icon?: IconComponent;
70
55
  onClick?: () => void;
71
56
  linkProps?: LinkProps;
72
57
  trailingElement?: React.ReactNode;
@@ -84,8 +69,12 @@ export interface UseC3NavigationV2Options {
84
69
  sidebarAriaLabel?: string;
85
70
  /** Forwarded to `C3Sidebar` `labels`. */
86
71
  sidebarLabels?: SidebarLabels;
87
- /** Used to resolve `isActive` on sidebar items and `isSelected` on breadcrumb dropdown items. */
88
- activeItemKey: string;
72
+ /**
73
+ * Resolves `isActive` on sidebar items and `isSelected` on breadcrumb
74
+ * dropdown items by key-match. Optional: items that set their own `isActive`
75
+ * (boolean/function) don't need it. Defaults to `''`.
76
+ */
77
+ activeItemKey?: string;
89
78
  sidebarChildren?: SidebarNodeDescriptor[];
90
79
  breadcrumbs?: BreadcrumbDescriptor[];
91
80
  tools?: ToolDescriptor[];
@@ -30,7 +30,7 @@ function collectDefaultExpanded(nodes, out) {
30
30
  * descriptors (isActive, label functions) into concrete props.
31
31
  */
32
32
  export function useC3NavigationV2(options) {
33
- const { app, skipToContentTargetId, skipToContentLabel, headerAriaLabel, sidebarAriaLabel = 'Main navigation', sidebarLabels, activeItemKey, sidebarChildren = [], breadcrumbs = [], tools, globalActions, sidebarExpandedWidth, sidebarCollapsedWidth, defaultSidebarExpanded = true, onSidebarToggle, linkComponent, headerTrailingContent, } = options;
33
+ const { app, skipToContentTargetId, skipToContentLabel, headerAriaLabel, sidebarAriaLabel = 'Main navigation', sidebarLabels, activeItemKey = '', sidebarChildren = [], breadcrumbs = [], tools, globalActions, sidebarExpandedWidth, sidebarCollapsedWidth, defaultSidebarExpanded = true, onSidebarToggle, linkComponent, headerTrailingContent, } = options;
34
34
  const [isSidebarExpanded, setIsSidebarExpanded] = useState(defaultSidebarExpanded);
35
35
  const setSidebarExpanded = useCallback((expanded) => {
36
36
  setIsSidebarExpanded(expanded);
@@ -217,6 +217,7 @@ export function useClusterWebappBreadcrumbs(options) {
217
217
  key: 'app',
218
218
  label: appLabel,
219
219
  icon: appIcon,
220
+ linkProps: { to: '/', href: '/' },
220
221
  ...(dropdownItems
221
222
  ? {
222
223
  dropdownTitle: 'Switch application',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/camunda-composite-components",
3
- "version": "0.25.2",
3
+ "version": "0.25.3",
4
4
  "description": "Camunda Composite Components",
5
5
  "bugs": {
6
6
  "url": "https://github.com/camunda/camunda-cloud-management-apps/issues"
@@ -43,7 +43,7 @@
43
43
  "@vitest/browser": "4.1.9",
44
44
  "@vitest/browser-playwright": "4.1.9",
45
45
  "vitest": "4.1.9",
46
- "conventional-changelog-conventionalcommits": "9.3.1",
46
+ "conventional-changelog-conventionalcommits": "10.2.0",
47
47
  "eslint-import-resolver-typescript": "4.4.5",
48
48
  "eslint-plugin-react": "7.37.5",
49
49
  "eslint-plugin-react-hooks": "7.1.1",
@@ -60,7 +60,7 @@
60
60
  "styled-components": "6.4.2",
61
61
  "typescript-eslint": "8.61.1",
62
62
  "wait-on": "9.0.10",
63
- "@camunda/ccma-shared-types": "0.2.0"
63
+ "@camunda/ccma-shared-types": "0.4.0"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "@carbon/react": "1.x",