@dbcdk/react-components 0.0.150 → 0.0.152

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.
@@ -95,3 +95,9 @@
95
95
  --alert-fg: var(--color-brand);
96
96
  --alert-border: color-mix(in srgb, var(--color-brand) 35%, transparent);
97
97
  }
98
+
99
+ .accent {
100
+ --alert-bg: var(--color-accent-bg);
101
+ --alert-fg: var(--color-accent-fg);
102
+ --alert-border: var(--color-accent-border);
103
+ }
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
+ var severity = require('../../constants/severity');
4
5
  var styles = require('./AttributeChip.module.css');
5
6
 
6
7
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -12,15 +13,23 @@ function AttributeChip({
12
13
  value,
13
14
  size = "md",
14
15
  loading,
15
- severity = "neutral"
16
+ severity: severity$1
16
17
  }) {
17
- const valueClassName = [styles__default.default.value, styles__default.default[severity], !label && styles__default.default.valueStandalone].filter(Boolean).join(" ");
18
+ const markerSeverity = severity$1 && severity$1 !== "neutral" ? severity$1 : void 0;
18
19
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${styles__default.default.container} ${styles__default.default[size]}`, "aria-busy": loading, children: [
19
20
  label ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20
21
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.label, children: label }),
21
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.separator, "aria-hidden": "true", children: "|" })
22
+ !markerSeverity ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.separator, "aria-hidden": "true", children: "|" }) : null
22
23
  ] }) : null,
23
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: valueClassName, children: loading ? "\u2014" : value == null ? void 0 : value.toString() })
24
+ markerSeverity ? /* @__PURE__ */ jsxRuntime.jsx(
25
+ "span",
26
+ {
27
+ className: styles__default.default.marker,
28
+ "aria-hidden": "true",
29
+ style: { "--attribute-chip-marker-color": severity.SeverityBgColor[markerSeverity] }
30
+ }
31
+ ) : null,
32
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.value, children: loading ? "\u2014" : value == null ? void 0 : value.toString() })
24
33
  ] });
25
34
  }
26
35
 
@@ -1,4 +1,5 @@
1
1
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
+ import { SeverityBgColor } from '../../constants/severity';
2
3
  import styles from './AttributeChip.module.css';
3
4
 
4
5
  function AttributeChip({
@@ -6,15 +7,23 @@ function AttributeChip({
6
7
  value,
7
8
  size = "md",
8
9
  loading,
9
- severity = "neutral"
10
+ severity
10
11
  }) {
11
- const valueClassName = [styles.value, styles[severity], !label && styles.valueStandalone].filter(Boolean).join(" ");
12
+ const markerSeverity = severity && severity !== "neutral" ? severity : void 0;
12
13
  return /* @__PURE__ */ jsxs("div", { className: `${styles.container} ${styles[size]}`, "aria-busy": loading, children: [
13
14
  label ? /* @__PURE__ */ jsxs(Fragment, { children: [
14
15
  /* @__PURE__ */ jsx("span", { className: styles.label, children: label }),
15
- /* @__PURE__ */ jsx("span", { className: styles.separator, "aria-hidden": "true", children: "|" })
16
+ !markerSeverity ? /* @__PURE__ */ jsx("span", { className: styles.separator, "aria-hidden": "true", children: "|" }) : null
16
17
  ] }) : null,
17
- /* @__PURE__ */ jsx("span", { className: valueClassName, children: loading ? "\u2014" : value == null ? void 0 : value.toString() })
18
+ markerSeverity ? /* @__PURE__ */ jsx(
19
+ "span",
20
+ {
21
+ className: styles.marker,
22
+ "aria-hidden": "true",
23
+ style: { "--attribute-chip-marker-color": SeverityBgColor[markerSeverity] }
24
+ }
25
+ ) : null,
26
+ /* @__PURE__ */ jsx("span", { className: styles.value, children: loading ? "\u2014" : value == null ? void 0 : value.toString() })
18
27
  ] });
19
28
  }
20
29
 
@@ -1,6 +1,7 @@
1
1
  .container {
2
2
  display: inline-flex;
3
3
  align-items: center;
4
+ gap: var(--spacing-2xs);
4
5
  white-space: nowrap;
5
6
  border-radius: var(--border-radius-default);
6
7
  border: 1px solid var(--color-border-subtle);
@@ -13,14 +14,12 @@
13
14
  avoids uneven leading around the glyphs at different font-sizes. */
14
15
  line-height: 1;
15
16
  --attribute-chip-padding-inline: var(--spacing-xxs);
16
- --attribute-chip-value-bleed: calc(
17
- (var(--attribute-chip-padding-inline) - var(--spacing-2xs)) * -1
18
- );
19
17
  padding-inline: var(--attribute-chip-padding-inline);
20
18
  }
21
19
 
22
20
  .label,
23
- .value {
21
+ .value,
22
+ .marker {
24
23
  display: inline-flex;
25
24
  align-items: center;
26
25
  height: 100%;
@@ -32,28 +31,20 @@
32
31
  }
33
32
 
34
33
  .value {
35
- height: calc(100% - 2 * var(--spacing-2xs));
36
- margin-block: var(--spacing-2xs);
37
34
  font-weight: var(--font-weight-medium);
38
- padding-inline: var(--attribute-chip-padding-inline);
39
- margin-inline-end: var(--attribute-chip-value-bleed);
40
- border-radius: 0 var(--border-radius-default) var(--border-radius-default) 0;
41
- background: var(--value-bg, transparent);
42
- color: var(--value-fg, inherit);
43
35
  }
44
36
 
45
- /* When there's no label/separator before it, the value is the only content,
46
- so it bleeds into the left padding too and becomes a full pill. A thin
47
- sliver of the container's own background stays visible around it. */
48
- .valueStandalone {
49
- margin-inline-start: var(--attribute-chip-value-bleed);
50
- border-radius: var(--border-radius-default);
37
+ .marker {
38
+ inline-size: var(--border-width-medium);
39
+ block-size: 0.9em;
40
+ flex: 0 0 auto;
41
+ border-radius: var(--border-radius-sm);
42
+ background: var(--attribute-chip-marker-color, var(--color-brand));
43
+ margin-inline: var(--spacing-2xs) var(--spacing-2xs);
51
44
  }
52
45
 
53
46
  .separator {
54
47
  color: var(--color-border-default);
55
- margin-inline-start: var(--spacing-xxs);
56
- margin-inline-end: var(--spacing-2xs);
57
48
  }
58
49
 
59
50
  /* Sizes */
@@ -71,35 +62,3 @@
71
62
  height: var(--component-size-md);
72
63
  font-size: var(--font-size-md);
73
64
  }
74
-
75
- /* Severity colors for the value */
76
-
77
- .neutral {
78
- --value-bg: transparent;
79
- --value-fg: inherit;
80
- }
81
-
82
- .success {
83
- --value-bg: var(--color-status-success-bg);
84
- --value-fg: var(--color-status-success-fg);
85
- }
86
-
87
- .warning {
88
- --value-bg: var(--color-status-warning-bg);
89
- --value-fg: var(--color-status-warning-fg);
90
- }
91
-
92
- .error {
93
- --value-bg: var(--color-status-error-bg);
94
- --value-fg: var(--color-status-error-fg);
95
- }
96
-
97
- .info {
98
- --value-bg: var(--color-status-info-bg);
99
- --value-fg: var(--color-status-info-fg);
100
- }
101
-
102
- .brand {
103
- --value-bg: var(--color-brand);
104
- --value-fg: var(--color-fg-on-brand);
105
- }
@@ -172,6 +172,17 @@
172
172
  box-shadow: inset 0 0 0 1px transparent;
173
173
  }
174
174
 
175
+ .accent {
176
+ --chip-bg: var(--color-accent-bg);
177
+ --chip-fg: var(--color-accent-fg);
178
+ --chip-border: transparent;
179
+ --chip-outlined-border: var(--color-accent-border);
180
+ --chip-outlined-border-hover: var(--color-accent-border);
181
+ --chip-outlined-fg: var(--color-accent-fg);
182
+ --chip-outlined-fg-hover: var(--color-accent-fg);
183
+ box-shadow: inset 0 0 0 1px var(--color-accent-border);
184
+ }
185
+
175
186
  /* Selected */
176
187
 
177
188
  .selected {
@@ -40,6 +40,9 @@
40
40
  .brand {
41
41
  background-color: var(--color-brand);
42
42
  }
43
+ .accent {
44
+ background-color: var(--color-accent);
45
+ }
43
46
  .success {
44
47
  background-color: var(--color-status-success);
45
48
  }
@@ -57,6 +60,10 @@
57
60
  box-shadow: 0 0 0 2px var(--color-bg-selected);
58
61
  }
59
62
 
63
+ .accent[data-glow='true'] {
64
+ box-shadow: 0 0 0 2px var(--color-accent-bg);
65
+ }
66
+
60
67
  .success[data-glow='true'] {
61
68
  box-shadow: 0 0 0 2px var(--color-status-success-bg);
62
69
  }
@@ -43,6 +43,11 @@
43
43
  --env-banner-fg: var(--color-fg-on-brand);
44
44
  }
45
45
 
46
+ .banner[data-severity='accent'] {
47
+ --env-banner-bg: var(--color-accent);
48
+ --env-banner-fg: var(--color-fg-on-accent);
49
+ }
50
+
46
51
  .banner[data-severity='neutral'] {
47
52
  --env-banner-bg: var(--color-neutral);
48
53
  --env-banner-fg: var(--color-fg-default);
@@ -18,6 +18,8 @@ function Switch({
18
18
  modified,
19
19
  label,
20
20
  labelAs = "label",
21
+ endAdornment,
22
+ endAdornmentAriaHidden = true,
21
23
  size = "md",
22
24
  containerLabel,
23
25
  error,
@@ -64,7 +66,8 @@ function Switch({
64
66
  children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.thumb, "aria-hidden": "true" })
65
67
  }
66
68
  ) }),
67
- label && (labelAs === "label" ? /* @__PURE__ */ jsxRuntime.jsx("label", { className: styles__default.default.label, htmlFor: controlId, children: label }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.label, children: label }))
69
+ label && (labelAs === "label" ? /* @__PURE__ */ jsxRuntime.jsx("label", { className: styles__default.default.label, htmlFor: controlId, children: label }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.label, children: label })),
70
+ endAdornment && /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.endAdornment, "aria-hidden": endAdornmentAriaHidden, children: endAdornment })
68
71
  ]
69
72
  }
70
73
  );
@@ -9,6 +9,16 @@ interface SwitchProps {
9
9
  modified?: boolean;
10
10
  label?: ReactNode;
11
11
  labelAs?: 'label' | 'span';
12
+ /**
13
+ * Extra content rendered after the label (e.g. a status dot or badge).
14
+ * The component owns the layout/spacing for this slot.
15
+ */
16
+ endAdornment?: ReactNode;
17
+ /**
18
+ * Whether the endAdornment wrapper is hidden from assistive tech.
19
+ * Defaults to true since endAdornment is typically decorative.
20
+ */
21
+ endAdornmentAriaHidden?: boolean;
12
22
  size?: Size;
13
23
  containerLabel?: string;
14
24
  error?: string;
@@ -23,5 +33,5 @@ interface SwitchProps {
23
33
  tooltip?: ReactNode;
24
34
  tooltipPlacement?: TooltipPlacement;
25
35
  }
26
- export declare function Switch({ checked: controlled, onChange, disabled, modified, label, labelAs, size, containerLabel, error, helpText, orientation, labelWidth, fullWidth, required, noContainer, id, 'data-cy': dataCy, tooltip, tooltipPlacement, }: SwitchProps): JSX.Element;
36
+ export declare function Switch({ checked: controlled, onChange, disabled, modified, label, labelAs, endAdornment, endAdornmentAriaHidden, size, containerLabel, error, helpText, orientation, labelWidth, fullWidth, required, noContainer, id, 'data-cy': dataCy, tooltip, tooltipPlacement, }: SwitchProps): JSX.Element;
27
37
  export {};
@@ -12,6 +12,8 @@ function Switch({
12
12
  modified,
13
13
  label,
14
14
  labelAs = "label",
15
+ endAdornment,
16
+ endAdornmentAriaHidden = true,
15
17
  size = "md",
16
18
  containerLabel,
17
19
  error,
@@ -58,7 +60,8 @@ function Switch({
58
60
  children: /* @__PURE__ */ jsx("span", { className: styles.thumb, "aria-hidden": "true" })
59
61
  }
60
62
  ) }),
61
- label && (labelAs === "label" ? /* @__PURE__ */ jsx("label", { className: styles.label, htmlFor: controlId, children: label }) : /* @__PURE__ */ jsx("span", { className: styles.label, children: label }))
63
+ label && (labelAs === "label" ? /* @__PURE__ */ jsx("label", { className: styles.label, htmlFor: controlId, children: label }) : /* @__PURE__ */ jsx("span", { className: styles.label, children: label })),
64
+ endAdornment && /* @__PURE__ */ jsx("span", { className: styles.endAdornment, "aria-hidden": endAdornmentAriaHidden, children: endAdornment })
62
65
  ]
63
66
  }
64
67
  );
@@ -81,3 +81,9 @@
81
81
  overflow-wrap: anywhere;
82
82
  word-break: break-word;
83
83
  }
84
+
85
+ .endAdornment {
86
+ display: inline-flex;
87
+ align-items: center;
88
+ flex: 0 0 auto;
89
+ }
@@ -14,7 +14,8 @@ const SeverityIcon = {
14
14
  error: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CircleX, {}),
15
15
  info: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Info, {}),
16
16
  warning: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CircleAlert, {}),
17
- brand: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Building2, {})
17
+ brand: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Building2, {}),
18
+ accent: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, {})
18
19
  };
19
20
  function Icon({ severity, label, customIcon }) {
20
21
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${styles__default.default.container}`, children: [
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
- import { Building2, CircleAlert, Info, CircleX, CircleCheck } from 'lucide-react';
2
+ import { Sparkles, Building2, CircleAlert, Info, CircleX, CircleCheck } from 'lucide-react';
3
3
  import styles from './Icon.module.css';
4
4
 
5
5
  const SeverityIcon = {
@@ -8,7 +8,8 @@ const SeverityIcon = {
8
8
  error: /* @__PURE__ */ jsx(CircleX, {}),
9
9
  info: /* @__PURE__ */ jsx(Info, {}),
10
10
  warning: /* @__PURE__ */ jsx(CircleAlert, {}),
11
- brand: /* @__PURE__ */ jsx(Building2, {})
11
+ brand: /* @__PURE__ */ jsx(Building2, {}),
12
+ accent: /* @__PURE__ */ jsx(Sparkles, {})
12
13
  };
13
14
  function Icon({ severity, label, customIcon }) {
14
15
  return /* @__PURE__ */ jsxs("div", { className: `${styles.container}`, children: [
@@ -35,3 +35,7 @@
35
35
  .brand {
36
36
  color: var(--color-fg-on-brand);
37
37
  }
38
+
39
+ .accent {
40
+ color: var(--color-accent);
41
+ }
@@ -90,3 +90,9 @@
90
90
  --inline-status-fg: var(--color-brand);
91
91
  --inline-status-border: color-mix(in srgb, var(--color-brand) 35%, transparent);
92
92
  }
93
+
94
+ .accent {
95
+ --inline-status-bg: var(--color-accent-bg);
96
+ --inline-status-fg: var(--color-accent-fg);
97
+ --inline-status-border: var(--color-accent-border);
98
+ }
@@ -6,6 +6,7 @@ var React = require('react');
6
6
  var styles = require('./Menu.module.css');
7
7
  var Checkbox = require('../forms/checkbox/Checkbox');
8
8
  var RadioButton = require('../forms/radio-buttons/RadioButton');
9
+ var Switch = require('../forms/switch/Switch');
9
10
 
10
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
12
 
@@ -82,8 +83,23 @@ MenuBase.displayName = "Menu";
82
83
  const isInteractiveEl = (el) => React__namespace.isValidElement(el) && (typeof el.type === "string" ? el.type === "a" || el.type === "button" : true);
83
84
  function applyMenuItemPropsToElement(child, opts) {
84
85
  var _a, _b, _c, _d;
85
- const { active, selected, disabled, role, tabIndex = 0, className } = opts;
86
+ const {
87
+ active,
88
+ selected,
89
+ disabled,
90
+ role,
91
+ tabIndex = 0,
92
+ className,
93
+ endAdornment,
94
+ endAdornmentAriaHidden = true
95
+ } = opts;
86
96
  const childClass = [styles__default.default.item, active ? styles__default.default.active : "", selected ? styles__default.default.selected : ""].filter(Boolean).join(" ");
97
+ const endAdornmentProps = endAdornment ? {
98
+ children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
99
+ child.props.children,
100
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.endAdornment, "aria-hidden": endAdornmentAriaHidden, children: endAdornment })
101
+ ] })
102
+ } : {};
87
103
  const nextImmediate = React__namespace.cloneElement(child, {
88
104
  className: [child.props.className, styles__default.default.interactiveChild, className].filter(Boolean).join(" ")
89
105
  });
@@ -94,7 +110,8 @@ function applyMenuItemPropsToElement(child, opts) {
94
110
  "aria-selected": selected || void 0,
95
111
  "aria-disabled": disabled || void 0,
96
112
  className: [child.props.className, styles__default.default.interactive, childClass, className].filter(Boolean).join(" "),
97
- ...child.type === "button" ? { disabled } : {}
113
+ ...child.type === "button" ? { disabled } : {},
114
+ ...endAdornmentProps
98
115
  });
99
116
  }
100
117
  return React__namespace.cloneElement(nextImmediate, {
@@ -103,11 +120,23 @@ function applyMenuItemPropsToElement(child, opts) {
103
120
  "aria-selected": selected || void 0,
104
121
  "aria-disabled": disabled || void 0,
105
122
  className: [nextImmediate.props.className, styles__default.default.interactive, childClass].filter(Boolean).join(" "),
106
- disabled
123
+ disabled,
124
+ ...endAdornmentProps
107
125
  });
108
126
  }
109
127
  const MenuItem = React__namespace.forwardRef(
110
- ({ children, active, selected, disabled, className, itemRole, variant, ...liProps }, ref) => {
128
+ ({
129
+ children,
130
+ active,
131
+ selected,
132
+ disabled,
133
+ className,
134
+ itemRole,
135
+ variant,
136
+ endAdornment,
137
+ endAdornmentAriaHidden = true,
138
+ ...liProps
139
+ }, ref) => {
111
140
  const resolvedRole = itemRole != null ? itemRole : "menuitem";
112
141
  const isBordered = variant === "bordered";
113
142
  const itemClassName = variant === "danger" ? styles__default.default.itemDanger : void 0;
@@ -119,10 +148,12 @@ const MenuItem = React__namespace.forwardRef(
119
148
  selected,
120
149
  disabled,
121
150
  role: resolvedRole,
122
- className: itemClassName
151
+ className: itemClassName,
152
+ endAdornment,
153
+ endAdornmentAriaHidden
123
154
  }) });
124
155
  }
125
- return /* @__PURE__ */ jsxRuntime.jsx("li", { ref, role: "none", className: rowClass, ...liProps, children: /* @__PURE__ */ jsxRuntime.jsx(
156
+ return /* @__PURE__ */ jsxRuntime.jsx("li", { ref, role: "none", className: rowClass, ...liProps, children: /* @__PURE__ */ jsxRuntime.jsxs(
126
157
  "button",
127
158
  {
128
159
  role: resolvedRole,
@@ -138,7 +169,10 @@ const MenuItem = React__namespace.forwardRef(
138
169
  ].filter(Boolean).join(" "),
139
170
  type: "button",
140
171
  disabled,
141
- children
172
+ children: [
173
+ children,
174
+ endAdornment && /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.endAdornment, "aria-hidden": endAdornmentAriaHidden, children: endAdornment })
175
+ ]
142
176
  }
143
177
  ) });
144
178
  }
@@ -295,6 +329,79 @@ const MenuRadioItem = React__namespace.forwardRef(
295
329
  }
296
330
  );
297
331
  MenuRadioItem.displayName = "Menu.RadioItem";
332
+ const MenuSwitchItem = React__namespace.forwardRef(
333
+ ({
334
+ label,
335
+ checked,
336
+ active,
337
+ disabled,
338
+ interactiveRef,
339
+ interactiveProps,
340
+ onCheckedChange,
341
+ endAdornment,
342
+ endAdornmentAriaHidden,
343
+ className,
344
+ ...liProps
345
+ }, ref) => {
346
+ const isSelected = checked;
347
+ const interactiveClass = [
348
+ styles__default.default.interactiveChild,
349
+ styles__default.default.item,
350
+ active ? styles__default.default.active : "",
351
+ isSelected ? styles__default.default.selected : ""
352
+ ].filter(Boolean).join(" ");
353
+ return /* @__PURE__ */ jsxRuntime.jsx(
354
+ "li",
355
+ {
356
+ ref,
357
+ role: "none",
358
+ className: [styles__default.default.row, className].filter(Boolean).join(" "),
359
+ ...liProps,
360
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
361
+ "div",
362
+ {
363
+ ref: interactiveRef,
364
+ role: interactiveProps == null ? void 0 : interactiveProps.role,
365
+ tabIndex: -1,
366
+ "aria-selected": isSelected || void 0,
367
+ "aria-disabled": disabled || void 0,
368
+ className: interactiveClass,
369
+ ...interactiveProps,
370
+ onClick: (event) => {
371
+ var _a;
372
+ (_a = interactiveProps == null ? void 0 : interactiveProps.onClick) == null ? void 0 : _a.call(interactiveProps, event);
373
+ if (event.defaultPrevented || disabled) return;
374
+ const target = event.target;
375
+ if (target instanceof Element && target.closest("button") !== null) {
376
+ return;
377
+ }
378
+ onCheckedChange == null ? void 0 : onCheckedChange(!checked);
379
+ },
380
+ children: [
381
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.label, children: label }),
382
+ endAdornment && /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.endAdornment, "aria-hidden": endAdornmentAriaHidden != null ? endAdornmentAriaHidden : true, children: endAdornment }),
383
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.switchControl, children: /* @__PURE__ */ jsxRuntime.jsx(
384
+ Switch.Switch,
385
+ {
386
+ size: "sm",
387
+ noContainer: true,
388
+ checked,
389
+ disabled,
390
+ onChange: (next, _e) => onCheckedChange == null ? void 0 : onCheckedChange(next)
391
+ }
392
+ ) })
393
+ ]
394
+ }
395
+ )
396
+ }
397
+ );
398
+ }
399
+ );
400
+ MenuSwitchItem.displayName = "Menu.SwitchItem";
401
+ const MenuLabel = React__namespace.forwardRef(
402
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx("span", { ref, className: [styles__default.default.label, className].filter(Boolean).join(" "), ...props })
403
+ );
404
+ MenuLabel.displayName = "Menu.Label";
298
405
  const MenuSeparator = React__namespace.forwardRef(
299
406
  ({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
300
407
  "li",
@@ -325,6 +432,8 @@ const Menu = Object.assign(MenuBase, {
325
432
  Item: MenuItem,
326
433
  CheckItem: MenuCheckItem,
327
434
  RadioItem: MenuRadioItem,
435
+ SwitchItem: MenuSwitchItem,
436
+ Label: MenuLabel,
328
437
  Separator: MenuSeparator,
329
438
  Header: MenuHeader
330
439
  });
@@ -10,6 +10,7 @@ export interface MenuProps extends React.HTMLAttributes<HTMLUListElement> {
10
10
  /** Adds a gap of --spacing-xs between items */
11
11
  gap?: boolean;
12
12
  }
13
+ export type MenuLabelProps = React.HTMLAttributes<HTMLSpanElement>;
13
14
  export type MenuSeparatorProps = React.LiHTMLAttributes<HTMLLIElement>;
14
15
  export interface MenuHeaderProps extends React.LiHTMLAttributes<HTMLLIElement> {
15
16
  children: React.ReactNode;
@@ -26,6 +27,15 @@ export interface MenuItemProps extends React.LiHTMLAttributes<HTMLLIElement> {
26
27
  itemRole?: 'menuitem' | 'option';
27
28
  /** Adds a rounded border around the item */
28
29
  variant?: 'default' | 'bordered' | 'danger';
30
+ /**
31
+ * Extra content rendered after `children` (e.g. a status dot or badge).
32
+ * Appended as a trailing, vertically-centered slot inside the interactive
33
+ * element, so `children` stays exactly the icon + label content it was
34
+ * before — no need to know about `Menu.Label`.
35
+ */
36
+ endAdornment?: React.ReactNode;
37
+ /** Forwarded to the endAdornment wrapper. Defaults to true. */
38
+ endAdornmentAriaHidden?: boolean;
29
39
  }
30
40
  export interface MenuCheckItemProps extends Omit<React.LiHTMLAttributes<HTMLLIElement>, 'onChange'> {
31
41
  label: React.ReactNode;
@@ -61,10 +71,28 @@ export interface MenuRadioItemProps extends Omit<React.LiHTMLAttributes<HTMLLIEl
61
71
  /** Forwarded to the underlying RadioButton. Defaults to true. */
62
72
  endAdornmentAriaHidden?: boolean;
63
73
  }
74
+ export interface MenuSwitchItemProps extends Omit<React.LiHTMLAttributes<HTMLLIElement>, 'onChange'> {
75
+ label: React.ReactNode;
76
+ checked: boolean;
77
+ active?: boolean;
78
+ disabled?: boolean;
79
+ interactiveRef?: React.Ref<HTMLDivElement>;
80
+ interactiveProps?: React.HTMLAttributes<HTMLDivElement>;
81
+ onCheckedChange?: (checked: boolean) => void;
82
+ /**
83
+ * Extra content rendered after the label (e.g. a status dot or badge).
84
+ * Forwarded to the underlying Switch, which owns the layout/spacing.
85
+ */
86
+ endAdornment?: React.ReactNode;
87
+ /** Forwarded to the underlying Switch. Defaults to true. */
88
+ endAdornmentAriaHidden?: boolean;
89
+ }
64
90
  export declare const Menu: React.ForwardRefExoticComponent<MenuProps & React.RefAttributes<HTMLUListElement>> & {
65
91
  Item: React.ForwardRefExoticComponent<MenuItemProps & React.RefAttributes<HTMLLIElement>>;
66
92
  CheckItem: React.ForwardRefExoticComponent<MenuCheckItemProps & React.RefAttributes<HTMLLIElement>>;
67
93
  RadioItem: React.ForwardRefExoticComponent<MenuRadioItemProps & React.RefAttributes<HTMLLIElement>>;
94
+ SwitchItem: React.ForwardRefExoticComponent<MenuSwitchItemProps & React.RefAttributes<HTMLLIElement>>;
68
95
  Separator: React.ForwardRefExoticComponent<MenuSeparatorProps & React.RefAttributes<HTMLLIElement>>;
69
96
  Header: React.ForwardRefExoticComponent<MenuHeaderProps & React.RefAttributes<HTMLLIElement>>;
97
+ Label: React.ForwardRefExoticComponent<MenuLabelProps & React.RefAttributes<HTMLSpanElement>>;
70
98
  };
@@ -1,9 +1,10 @@
1
1
  'use client';
2
- import { jsx } from 'react/jsx-runtime';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import * as React from 'react';
4
4
  import styles from './Menu.module.css';
5
5
  import { Checkbox } from '../forms/checkbox/Checkbox';
6
6
  import { RadioButton } from '../forms/radio-buttons/RadioButton';
7
+ import { Switch } from '../forms/switch/Switch';
7
8
 
8
9
  const INTERACTIVE_SELECTOR = 'a:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex="-1"]):not([aria-disabled="true"]), [role="menuitem"]:not([aria-disabled="true"]), [role="menuitemradio"]:not([aria-disabled="true"]), [role="menuitemcheckbox"]:not([aria-disabled="true"]), [role="option"]:not([aria-disabled="true"])';
9
10
  function getMenuItems(el) {
@@ -57,8 +58,23 @@ MenuBase.displayName = "Menu";
57
58
  const isInteractiveEl = (el) => React.isValidElement(el) && (typeof el.type === "string" ? el.type === "a" || el.type === "button" : true);
58
59
  function applyMenuItemPropsToElement(child, opts) {
59
60
  var _a, _b, _c, _d;
60
- const { active, selected, disabled, role, tabIndex = 0, className } = opts;
61
+ const {
62
+ active,
63
+ selected,
64
+ disabled,
65
+ role,
66
+ tabIndex = 0,
67
+ className,
68
+ endAdornment,
69
+ endAdornmentAriaHidden = true
70
+ } = opts;
61
71
  const childClass = [styles.item, active ? styles.active : "", selected ? styles.selected : ""].filter(Boolean).join(" ");
72
+ const endAdornmentProps = endAdornment ? {
73
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [
74
+ child.props.children,
75
+ /* @__PURE__ */ jsx("span", { className: styles.endAdornment, "aria-hidden": endAdornmentAriaHidden, children: endAdornment })
76
+ ] })
77
+ } : {};
62
78
  const nextImmediate = React.cloneElement(child, {
63
79
  className: [child.props.className, styles.interactiveChild, className].filter(Boolean).join(" ")
64
80
  });
@@ -69,7 +85,8 @@ function applyMenuItemPropsToElement(child, opts) {
69
85
  "aria-selected": selected || void 0,
70
86
  "aria-disabled": disabled || void 0,
71
87
  className: [child.props.className, styles.interactive, childClass, className].filter(Boolean).join(" "),
72
- ...child.type === "button" ? { disabled } : {}
88
+ ...child.type === "button" ? { disabled } : {},
89
+ ...endAdornmentProps
73
90
  });
74
91
  }
75
92
  return React.cloneElement(nextImmediate, {
@@ -78,11 +95,23 @@ function applyMenuItemPropsToElement(child, opts) {
78
95
  "aria-selected": selected || void 0,
79
96
  "aria-disabled": disabled || void 0,
80
97
  className: [nextImmediate.props.className, styles.interactive, childClass].filter(Boolean).join(" "),
81
- disabled
98
+ disabled,
99
+ ...endAdornmentProps
82
100
  });
83
101
  }
84
102
  const MenuItem = React.forwardRef(
85
- ({ children, active, selected, disabled, className, itemRole, variant, ...liProps }, ref) => {
103
+ ({
104
+ children,
105
+ active,
106
+ selected,
107
+ disabled,
108
+ className,
109
+ itemRole,
110
+ variant,
111
+ endAdornment,
112
+ endAdornmentAriaHidden = true,
113
+ ...liProps
114
+ }, ref) => {
86
115
  const resolvedRole = itemRole != null ? itemRole : "menuitem";
87
116
  const isBordered = variant === "bordered";
88
117
  const itemClassName = variant === "danger" ? styles.itemDanger : void 0;
@@ -94,10 +123,12 @@ const MenuItem = React.forwardRef(
94
123
  selected,
95
124
  disabled,
96
125
  role: resolvedRole,
97
- className: itemClassName
126
+ className: itemClassName,
127
+ endAdornment,
128
+ endAdornmentAriaHidden
98
129
  }) });
99
130
  }
100
- return /* @__PURE__ */ jsx("li", { ref, role: "none", className: rowClass, ...liProps, children: /* @__PURE__ */ jsx(
131
+ return /* @__PURE__ */ jsx("li", { ref, role: "none", className: rowClass, ...liProps, children: /* @__PURE__ */ jsxs(
101
132
  "button",
102
133
  {
103
134
  role: resolvedRole,
@@ -113,7 +144,10 @@ const MenuItem = React.forwardRef(
113
144
  ].filter(Boolean).join(" "),
114
145
  type: "button",
115
146
  disabled,
116
- children
147
+ children: [
148
+ children,
149
+ endAdornment && /* @__PURE__ */ jsx("span", { className: styles.endAdornment, "aria-hidden": endAdornmentAriaHidden, children: endAdornment })
150
+ ]
117
151
  }
118
152
  ) });
119
153
  }
@@ -270,6 +304,79 @@ const MenuRadioItem = React.forwardRef(
270
304
  }
271
305
  );
272
306
  MenuRadioItem.displayName = "Menu.RadioItem";
307
+ const MenuSwitchItem = React.forwardRef(
308
+ ({
309
+ label,
310
+ checked,
311
+ active,
312
+ disabled,
313
+ interactiveRef,
314
+ interactiveProps,
315
+ onCheckedChange,
316
+ endAdornment,
317
+ endAdornmentAriaHidden,
318
+ className,
319
+ ...liProps
320
+ }, ref) => {
321
+ const isSelected = checked;
322
+ const interactiveClass = [
323
+ styles.interactiveChild,
324
+ styles.item,
325
+ active ? styles.active : "",
326
+ isSelected ? styles.selected : ""
327
+ ].filter(Boolean).join(" ");
328
+ return /* @__PURE__ */ jsx(
329
+ "li",
330
+ {
331
+ ref,
332
+ role: "none",
333
+ className: [styles.row, className].filter(Boolean).join(" "),
334
+ ...liProps,
335
+ children: /* @__PURE__ */ jsxs(
336
+ "div",
337
+ {
338
+ ref: interactiveRef,
339
+ role: interactiveProps == null ? void 0 : interactiveProps.role,
340
+ tabIndex: -1,
341
+ "aria-selected": isSelected || void 0,
342
+ "aria-disabled": disabled || void 0,
343
+ className: interactiveClass,
344
+ ...interactiveProps,
345
+ onClick: (event) => {
346
+ var _a;
347
+ (_a = interactiveProps == null ? void 0 : interactiveProps.onClick) == null ? void 0 : _a.call(interactiveProps, event);
348
+ if (event.defaultPrevented || disabled) return;
349
+ const target = event.target;
350
+ if (target instanceof Element && target.closest("button") !== null) {
351
+ return;
352
+ }
353
+ onCheckedChange == null ? void 0 : onCheckedChange(!checked);
354
+ },
355
+ children: [
356
+ /* @__PURE__ */ jsx("span", { className: styles.label, children: label }),
357
+ endAdornment && /* @__PURE__ */ jsx("span", { className: styles.endAdornment, "aria-hidden": endAdornmentAriaHidden != null ? endAdornmentAriaHidden : true, children: endAdornment }),
358
+ /* @__PURE__ */ jsx("span", { className: styles.switchControl, children: /* @__PURE__ */ jsx(
359
+ Switch,
360
+ {
361
+ size: "sm",
362
+ noContainer: true,
363
+ checked,
364
+ disabled,
365
+ onChange: (next, _e) => onCheckedChange == null ? void 0 : onCheckedChange(next)
366
+ }
367
+ ) })
368
+ ]
369
+ }
370
+ )
371
+ }
372
+ );
373
+ }
374
+ );
375
+ MenuSwitchItem.displayName = "Menu.SwitchItem";
376
+ const MenuLabel = React.forwardRef(
377
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx("span", { ref, className: [styles.label, className].filter(Boolean).join(" "), ...props })
378
+ );
379
+ MenuLabel.displayName = "Menu.Label";
273
380
  const MenuSeparator = React.forwardRef(
274
381
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
275
382
  "li",
@@ -300,6 +407,8 @@ const Menu = Object.assign(MenuBase, {
300
407
  Item: MenuItem,
301
408
  CheckItem: MenuCheckItem,
302
409
  RadioItem: MenuRadioItem,
410
+ SwitchItem: MenuSwitchItem,
411
+ Label: MenuLabel,
303
412
  Separator: MenuSeparator,
304
413
  Header: MenuHeader
305
414
  });
@@ -39,13 +39,51 @@
39
39
  color var(--transition-fast) var(--ease-standard);
40
40
  }
41
41
 
42
- .interactive > :last-child {
42
+ /* Default case: applies wrap-safety to whichever element ends up last,
43
+ e.g. a plain label with no `endAdornment` prop involved — covers most
44
+ Menu.Item usage as-is. Excluded once .endAdornment is present (it's then
45
+ always the true last child) — wrap the label in <Menu.Label> in that case,
46
+ since it's no longer last and needs the wrap styling explicitly. */
47
+ .interactive > :last-child:not(.endAdornment) {
43
48
  min-width: 0;
44
49
  white-space: normal;
45
50
  overflow-wrap: anywhere;
46
51
  word-break: break-word;
47
52
  }
48
53
 
54
+ /* Explicit label slot (use <Menu.Label>) so wrap styling doesn't depend on
55
+ the label being the last child — needed once trailing content (the
56
+ `endAdornment` prop) is rendered after it. */
57
+ .label {
58
+ min-width: 0;
59
+ flex: 1 1 auto;
60
+ white-space: normal;
61
+ overflow-wrap: anywhere;
62
+ word-break: break-word;
63
+ }
64
+
65
+ /* Trailing content rendered via the `endAdornment` prop (e.g. a status
66
+ dot/badge). Centered on its own regardless of the row's flex-start
67
+ alignment, since it isn't tied to the first text line the way a leading
68
+ icon is. */
69
+ .endAdornment {
70
+ display: inline-flex;
71
+ align-items: center;
72
+ align-self: center;
73
+ flex: 0 0 auto;
74
+ }
75
+
76
+ /* Wraps a bare <Switch noContainer> in Menu.SwitchItem so it docks at the
77
+ end of the row (label/endAdornment are rendered by Menu, not Switch).
78
+ Shrink-proof: Switch's own `.container` still matches the generic
79
+ `.row :global(.container)` full-width rule below, but that now resolves
80
+ against this wrapper's own content width, not the whole row. */
81
+ .switchControl {
82
+ display: inline-flex;
83
+ align-items: center;
84
+ flex: 0 0 auto;
85
+ }
86
+
49
87
  /*
50
88
  Applied to the immediate child of <li> even if it's NOT an interactive element (e.g. a <div>)
51
89
  so that menu row styling still works for components that render a wrapper.
@@ -57,6 +57,11 @@
57
57
  color: var(--color-fg-on-brand);
58
58
  }
59
59
 
60
+ .tile[data-severity='accent'] {
61
+ background-color: var(--color-accent-bg-soft);
62
+ color: var(--color-accent-fg-soft);
63
+ }
64
+
60
65
  .label {
61
66
  font-size: var(--font-size-xs);
62
67
  font-weight: var(--font-weight-semibold, 600);
@@ -16,6 +16,7 @@
16
16
  --progress-fill-info: color-mix(in srgb, var(--color-status-info) 70%, transparent);
17
17
  --progress-fill-warning: color-mix(in srgb, var(--color-status-warning) 70%, transparent);
18
18
  --progress-fill-error: color-mix(in srgb, var(--color-status-error) 75%, transparent);
19
+ --progress-fill-accent: color-mix(in srgb, var(--color-accent) 75%, transparent);
19
20
 
20
21
  position: relative;
21
22
  width: 100%;
@@ -106,6 +107,10 @@
106
107
  background: var(--progress-fill-error);
107
108
  }
108
109
 
110
+ .progressSegment[data-severity='accent'] {
111
+ background: var(--progress-fill-accent);
112
+ }
113
+
109
114
  /* Label overlay */
110
115
  .progressCenter {
111
116
  position: absolute;
@@ -41,6 +41,10 @@
41
41
  border-left-color: var(--color-brand);
42
42
  }
43
43
 
44
+ .accent {
45
+ border-left-color: var(--color-accent);
46
+ }
47
+
44
48
  .success {
45
49
  border-left-color: var(--color-status-success);
46
50
  }
@@ -3,6 +3,7 @@
3
3
  const SeverityBgColor = {
4
4
  neutral: "var(--color-neutral-strong)",
5
5
  brand: "var(--color-brand)",
6
+ accent: "var(--color-accent)",
6
7
  success: "var(--color-status-success)",
7
8
  error: "var(--color-status-error)",
8
9
  info: "var(--color-status-info)",
@@ -11,6 +12,7 @@ const SeverityBgColor = {
11
12
  const SeverityBorderColor = {
12
13
  neutral: "var(--color-neutral-strong)",
13
14
  brand: "var(--color-brand)",
15
+ accent: "var(--color-accent-border)",
14
16
  success: "var(--color-status-success-border)",
15
17
  error: "var(--color-status-error-border)",
16
18
  info: "var(--color-status-info-border)",
@@ -19,6 +21,7 @@ const SeverityBorderColor = {
19
21
  const SeverityTextColor = {
20
22
  neutral: "var(--color-neutral-strong-fg)",
21
23
  brand: "var(--color-fg-on-brand)",
24
+ accent: "var(--color-accent-fg)",
22
25
  success: "var(--color-status-success-fg)",
23
26
  error: "var(--color-status-error-fg)",
24
27
  info: "var(--color-status-info-fg)",
@@ -1,6 +1,7 @@
1
1
  const SeverityBgColor = {
2
2
  neutral: "var(--color-neutral-strong)",
3
3
  brand: "var(--color-brand)",
4
+ accent: "var(--color-accent)",
4
5
  success: "var(--color-status-success)",
5
6
  error: "var(--color-status-error)",
6
7
  info: "var(--color-status-info)",
@@ -9,6 +10,7 @@ const SeverityBgColor = {
9
10
  const SeverityBorderColor = {
10
11
  neutral: "var(--color-neutral-strong)",
11
12
  brand: "var(--color-brand)",
13
+ accent: "var(--color-accent-border)",
12
14
  success: "var(--color-status-success-border)",
13
15
  error: "var(--color-status-error-border)",
14
16
  info: "var(--color-status-info-border)",
@@ -17,6 +19,7 @@ const SeverityBorderColor = {
17
19
  const SeverityTextColor = {
18
20
  neutral: "var(--color-neutral-strong-fg)",
19
21
  brand: "var(--color-fg-on-brand)",
22
+ accent: "var(--color-accent-fg)",
20
23
  success: "var(--color-status-success-fg)",
21
24
  error: "var(--color-status-error-fg)",
22
25
  info: "var(--color-status-info-fg)",
@@ -1 +1 @@
1
- export type Severity = 'neutral' | 'brand' | 'success' | 'error' | 'info' | 'warning';
1
+ export type Severity = 'neutral' | 'brand' | 'accent' | 'success' | 'error' | 'info' | 'warning';
@@ -43,6 +43,9 @@ html {
43
43
  /* Accents */
44
44
  --dbc-pink-500: light-dark(#ec4899, #f472b6);
45
45
  --dbc-pink-600: light-dark(#be185d, #db2777);
46
+ --dbc-magenta-500: light-dark(#d946ef, #e879f9);
47
+ --dbc-magenta-100: light-dark(#fae8ff, #3b0a45);
48
+ --dbc-magenta-700: light-dark(#a21caf, #f5d0fe);
46
49
 
47
50
  --dbc-gray-500: light-dark(#6b7280, #9ca3af);
48
51
  --dbc-gray-600: light-dark(#4b5563, #6b7280);
@@ -77,6 +80,11 @@ html {
77
80
  --color-brand: var(--dbc-blue-500);
78
81
  --color-brand-hover: var(--dbc-blue-600);
79
82
  --color-brand-strong: var(--dbc-blue-600);
83
+ --color-accent: var(--dbc-magenta-500);
84
+ --color-accent-bg: var(--dbc-magenta-100);
85
+ --color-accent-border: color-mix(in srgb, var(--dbc-magenta-500) 24%, transparent);
86
+ --color-accent-fg: var(--dbc-magenta-700);
87
+ --color-fg-on-accent: light-dark(#ffffff, #111827);
80
88
 
81
89
  /* Backgrounds & surfaces */
82
90
  --color-bg-page: light-dark(#ffffff, #111827);
@@ -176,6 +184,8 @@ html {
176
184
  --color-status-warning-fg-soft: var(--color-status-warning-fg);
177
185
  --color-status-error-fg-soft: var(--color-status-error-fg);
178
186
  --color-status-info-fg-soft: var(--color-status-info-fg);
187
+ --color-accent-bg-soft: color-mix(in srgb, var(--color-accent) 10%, var(--color-bg-surface));
188
+ --color-accent-fg-soft: var(--color-accent-fg);
179
189
 
180
190
  /* Chart colors */
181
191
  --color-chart-empty: color-mix(in srgb, var(--color-fg-muted) 10%, rgb(255 255 255 / 0));
@@ -184,6 +194,7 @@ html {
184
194
  --color-chart-success: color-mix(in srgb, var(--dbc-green-500) 75%, rgb(255 255 255 / 0));
185
195
  --color-chart-warning: color-mix(in srgb, var(--dbc-amber-400) 75%, rgb(255 255 255 / 0));
186
196
  --color-chart-brand: color-mix(in srgb, var(--dbc-pink-500) 75%, rgb(255 255 255 / 0));
197
+ --color-chart-accent: color-mix(in srgb, var(--dbc-magenta-500) 75%, rgb(255 255 255 / 0));
187
198
  --color-chart-info: color-mix(in srgb, var(--dbc-info-500) 75%, rgb(255 255 255 / 0));
188
199
  --color-chart-error: color-mix(in srgb, var(--dbc-red-600) 75%, rgb(255 255 255 / 0));
189
200
 
@@ -192,6 +203,7 @@ html {
192
203
  --color-chart-green: var(--color-chart-success);
193
204
  --color-chart-amber: var(--color-chart-warning);
194
205
  --color-chart-pink: var(--color-chart-brand);
206
+ --color-chart-magenta: var(--color-chart-accent);
195
207
  --color-chart-sky: var(--color-chart-info);
196
208
  --color-chart-red: var(--color-chart-error);
197
209
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbcdk/react-components",
3
- "version": "0.0.150",
3
+ "version": "0.0.152",
4
4
  "description": "Reusable React components for DBC projects",
5
5
  "license": "ISC",
6
6
  "author": "",