@dbcdk/react-components 0.0.200 → 0.0.201

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.
@@ -2,7 +2,9 @@
2
2
  'use strict';
3
3
 
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
+ var lucideReact = require('lucide-react');
5
6
  var styles = require('./Alert.module.css');
7
+ var Button = require('../button/Button');
6
8
  var Headline = require('../headline/Headline');
7
9
  var Icon = require('../icon/Icon');
8
10
 
@@ -14,12 +16,16 @@ function Alert({
14
16
  children,
15
17
  title,
16
18
  headlineSize = 3,
19
+ headlineWeight = 600,
20
+ headlineVariant,
17
21
  severity = "neutral",
18
22
  customIcon,
19
23
  disableIcon = false,
20
24
  fullWidth = false,
21
25
  maxWidth = "72ch",
22
- role = "status"
26
+ role = "status",
27
+ onClose,
28
+ closeLabel = "Luk"
23
29
  }) {
24
30
  const hasLeading = Boolean(customIcon) || !disableIcon;
25
31
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -40,10 +46,32 @@ function Alert({
40
46
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.content, children: [
41
47
  title ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.titleRow, children: [
42
48
  hasLeading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.leading, children: customIcon != null ? customIcon : /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { severity }) }) : null,
43
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.title, children: /* @__PURE__ */ jsxRuntime.jsx(Headline.Headline, { size: headlineSize, weight: 600, disableMargin: true, children: title }) })
49
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.title, children: /* @__PURE__ */ jsxRuntime.jsx(
50
+ Headline.Headline,
51
+ {
52
+ size: headlineSize,
53
+ weight: headlineWeight,
54
+ variant: headlineVariant,
55
+ disableMargin: true,
56
+ children: title
57
+ }
58
+ ) })
44
59
  ] }) : null,
45
60
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.body, children })
46
- ] })
61
+ ] }),
62
+ onClose ? /* @__PURE__ */ jsxRuntime.jsx(
63
+ Button.Button,
64
+ {
65
+ type: "button",
66
+ variant: "inline",
67
+ shape: "round",
68
+ size: "sm",
69
+ className: styles__default.default.closeButton,
70
+ "aria-label": closeLabel,
71
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, {}),
72
+ onClick: onClose
73
+ }
74
+ ) : null
47
75
  ]
48
76
  }
49
77
  );
@@ -1,14 +1,24 @@
1
1
  import type { JSX, ReactNode } from 'react';
2
2
  import type { Severity } from '../../constants/severity.types';
3
+ import { type HeadlineProps } from '../headline/Headline';
3
4
  export interface AlertProps {
4
5
  children: ReactNode;
5
6
  title?: ReactNode;
6
7
  headlineSize?: 1 | 2 | 3 | 4 | 5 | 6;
8
+ headlineWeight?: HeadlineProps['weight'];
9
+ headlineVariant?: HeadlineProps['variant'];
7
10
  severity?: Severity;
8
11
  customIcon?: ReactNode;
9
12
  disableIcon?: boolean;
10
13
  fullWidth?: boolean;
11
14
  maxWidth?: string | number;
12
15
  role?: 'status' | 'alert';
16
+ /**
17
+ * Shows a close button that calls this. The alert has no open state of its
18
+ * own: the parent decides whether to keep rendering it.
19
+ */
20
+ onClose?: () => void;
21
+ /** Accessible name for the close button. */
22
+ closeLabel?: string;
13
23
  }
14
- export declare function Alert({ children, title, headlineSize, severity, customIcon, disableIcon, fullWidth, maxWidth, role, }: AlertProps): JSX.Element;
24
+ export declare function Alert({ children, title, headlineSize, headlineWeight, headlineVariant, severity, customIcon, disableIcon, fullWidth, maxWidth, role, onClose, closeLabel, }: AlertProps): JSX.Element;
@@ -1,6 +1,8 @@
1
1
  'use client';
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { X } from 'lucide-react';
3
4
  import styles from './Alert.module.css';
5
+ import { Button } from '../button/Button';
4
6
  import { Headline } from '../headline/Headline';
5
7
  import { Icon } from '../icon/Icon';
6
8
 
@@ -8,12 +10,16 @@ function Alert({
8
10
  children,
9
11
  title,
10
12
  headlineSize = 3,
13
+ headlineWeight = 600,
14
+ headlineVariant,
11
15
  severity = "neutral",
12
16
  customIcon,
13
17
  disableIcon = false,
14
18
  fullWidth = false,
15
19
  maxWidth = "72ch",
16
- role = "status"
20
+ role = "status",
21
+ onClose,
22
+ closeLabel = "Luk"
17
23
  }) {
18
24
  const hasLeading = Boolean(customIcon) || !disableIcon;
19
25
  return /* @__PURE__ */ jsxs(
@@ -34,10 +40,32 @@ function Alert({
34
40
  /* @__PURE__ */ jsxs("div", { className: styles.content, children: [
35
41
  title ? /* @__PURE__ */ jsxs("div", { className: styles.titleRow, children: [
36
42
  hasLeading ? /* @__PURE__ */ jsx("div", { className: styles.leading, children: customIcon != null ? customIcon : /* @__PURE__ */ jsx(Icon, { severity }) }) : null,
37
- /* @__PURE__ */ jsx("div", { className: styles.title, children: /* @__PURE__ */ jsx(Headline, { size: headlineSize, weight: 600, disableMargin: true, children: title }) })
43
+ /* @__PURE__ */ jsx("div", { className: styles.title, children: /* @__PURE__ */ jsx(
44
+ Headline,
45
+ {
46
+ size: headlineSize,
47
+ weight: headlineWeight,
48
+ variant: headlineVariant,
49
+ disableMargin: true,
50
+ children: title
51
+ }
52
+ ) })
38
53
  ] }) : null,
39
54
  /* @__PURE__ */ jsx("div", { className: styles.body, children })
40
- ] })
55
+ ] }),
56
+ onClose ? /* @__PURE__ */ jsx(
57
+ Button,
58
+ {
59
+ type: "button",
60
+ variant: "inline",
61
+ shape: "round",
62
+ size: "sm",
63
+ className: styles.closeButton,
64
+ "aria-label": closeLabel,
65
+ icon: /* @__PURE__ */ jsx(X, {}),
66
+ onClick: onClose
67
+ }
68
+ ) : null
41
69
  ]
42
70
  }
43
71
  );
@@ -1,7 +1,6 @@
1
1
  .container {
2
2
  --alert-bg: var(--color-bg-toolbar);
3
3
  --alert-fg: var(--color-fg-default);
4
- --alert-border: var(--color-border-subtle);
5
4
 
6
5
  display: flex;
7
6
  align-items: flex-start;
@@ -9,10 +8,11 @@
9
8
  inline-size: min(100%, var(--alert-max-width, 72ch));
10
9
  max-inline-size: 100%;
11
10
  padding: var(--spacing-sm) var(--spacing-md);
12
- border: 1px solid var(--alert-border);
13
11
  border-radius: var(--border-radius-default);
14
12
  background: var(--alert-bg);
15
13
  color: var(--alert-fg);
14
+ /* Severity stripe on the left edge. An inset shadow keeps size and padding unchanged. */
15
+ box-shadow: inset var(--border-width-thick) 0 0 var(--alert-accent, transparent);
16
16
  box-sizing: border-box;
17
17
  }
18
18
 
@@ -42,7 +42,7 @@
42
42
  .content {
43
43
  display: flex;
44
44
  flex-direction: column;
45
- gap: var(--spacing-md);
45
+ gap: var(--spacing-sm);
46
46
  min-width: 0;
47
47
  flex: 1 1 auto;
48
48
  }
@@ -59,44 +59,60 @@
59
59
  font-size: var(--font-size-sm);
60
60
  }
61
61
 
62
+ .closeButton {
63
+ flex: 0 0 auto;
64
+ margin-inline-start: auto;
65
+ align-self: flex-start;
66
+ }
67
+
62
68
  .neutral {
63
69
  --alert-bg: var(--color-bg-toolbar);
64
70
  --alert-fg: var(--color-fg-default);
65
- --alert-border: var(--color-border-subtle);
66
71
  }
67
72
 
68
73
  .success {
69
74
  --alert-bg: var(--color-status-success-bg);
70
75
  --alert-fg: var(--color-status-success-fg);
71
- --alert-border: var(--color-status-success-border);
76
+ --alert-accent: var(--color-status-success);
72
77
  }
73
78
 
74
79
  .warning {
75
80
  --alert-bg: var(--color-status-warning-bg);
76
81
  --alert-fg: var(--color-status-warning-fg);
77
- --alert-border: var(--color-status-warning-border);
82
+ --alert-accent: var(--color-status-warning);
78
83
  }
79
84
 
80
85
  .error {
81
86
  --alert-bg: var(--color-status-error-bg);
82
87
  --alert-fg: var(--color-status-error-fg);
83
- --alert-border: var(--color-status-error-border);
88
+ --alert-accent: var(--color-status-error);
84
89
  }
85
90
 
86
91
  .info {
87
92
  --alert-bg: var(--color-status-info-bg);
88
93
  --alert-fg: var(--color-status-info-fg);
89
- --alert-border: var(--color-status-info-border);
94
+ --alert-accent: var(--color-status-info);
90
95
  }
91
96
 
92
97
  .brand {
93
98
  --alert-bg: color-mix(in srgb, var(--color-brand) 10%, var(--color-bg-surface));
94
99
  --alert-fg: var(--color-brand);
95
- --alert-border: color-mix(in srgb, var(--color-brand) 35%, transparent);
100
+ --alert-accent: var(--color-brand);
96
101
  }
97
102
 
98
103
  .accent {
99
104
  --alert-bg: var(--color-accent-bg);
100
105
  --alert-fg: var(--color-accent-fg);
101
- --alert-border: var(--color-accent-border);
106
+ --alert-accent: var(--color-accent);
107
+ }
108
+
109
+ /* Square corners on the striped edge, so the stripe stays straight. */
110
+ .success,
111
+ .warning,
112
+ .error,
113
+ .info,
114
+ .brand,
115
+ .accent {
116
+ border-top-left-radius: 0;
117
+ border-bottom-left-radius: 0;
102
118
  }
@@ -2,9 +2,10 @@
2
2
  'use strict';
3
3
 
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
- var lucideReact = require('lucide-react');
6
5
  var React = require('react');
6
+ var lucideReact = require('lucide-react');
7
7
  var useTooltipTrigger = require('../../components/overlay/tooltip/useTooltipTrigger');
8
+ var refs_utils = require('../../utils/refs.utils');
8
9
  var styles = require('./Button.module.css');
9
10
 
10
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -33,15 +34,6 @@ var styles__default = /*#__PURE__*/_interopDefault(styles);
33
34
  function cx(...parts) {
34
35
  return parts.filter(Boolean).join(" ");
35
36
  }
36
- function mergeRefs(...refs) {
37
- return (node) => {
38
- for (const ref of refs) {
39
- if (!ref) continue;
40
- if (typeof ref === "function") ref(node);
41
- else ref.current = node;
42
- }
43
- };
44
- }
45
37
  const Button = React__namespace.forwardRef(function Button2({
46
38
  variant = "outlined",
47
39
  shape = "default",
@@ -58,7 +50,6 @@ const Button = React__namespace.forwardRef(function Button2({
58
50
  type = "button",
59
51
  ...rest
60
52
  }, ref) {
61
- var _a;
62
53
  const { className: userClassName, ...buttonProps } = rest;
63
54
  const computedClassName = cx(
64
55
  styles__default.default.button,
@@ -71,17 +62,26 @@ const Button = React__namespace.forwardRef(function Button2({
71
62
  userClassName
72
63
  );
73
64
  const tooltipEnabled = Boolean(tooltip);
74
- const childRef = isLink && React__namespace.isValidElement(children) ? (_a = children.ref) != null ? _a : null : null;
75
- const mergedRef = React__namespace.useMemo(() => mergeRefs(childRef, ref), [childRef, ref]);
65
+ const linkChild = isLink && React__namespace.isValidElement(children) ? children : null;
66
+ const childRef = linkChild == null ? void 0 : linkChild.props.ref;
76
67
  const { triggerProps, id: tooltipId } = useTooltipTrigger.useTooltipTrigger({
77
68
  content: tooltipEnabled ? tooltip : null,
78
69
  placement: tooltipPlacement,
79
70
  offset: 8
80
71
  });
81
- const { ref: hoverRef, onPointerEnter: hoverEnter, onPointerLeave: hoverLeave } = triggerProps;
72
+ const { ref: hoverRef, ...tooltipTriggerProps } = triggerProps;
73
+ const { onPointerEnter: hoverEnter, onPointerLeave: hoverLeave } = tooltipTriggerProps;
74
+ const isDisabled = Boolean(buttonProps.disabled);
75
+ const tooltipOnControl = tooltipEnabled && (Boolean(linkChild) || !isDisabled);
76
+ const tooltipRef = tooltipOnControl ? hoverRef : void 0;
77
+ const mergedRef = React__namespace.useMemo(
78
+ () => refs_utils.mergeRefs(childRef, ref, tooltipRef),
79
+ [childRef, ref, tooltipRef]
80
+ );
82
81
  const describedBy = (() => {
83
82
  const existing = typeof buttonProps["aria-describedby"] === "string" ? buttonProps["aria-describedby"] : "";
84
83
  const ids = [existing, tooltipEnabled ? tooltipId : ""].filter(Boolean);
84
+ if (linkChild == null ? void 0 : linkChild.props["aria-describedby"]) ids.unshift(linkChild.props["aria-describedby"]);
85
85
  return ids.length ? ids.join(" ") : void 0;
86
86
  })();
87
87
  const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -90,39 +90,48 @@ const Button = React__namespace.forwardRef(function Button2({
90
90
  loading && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { display: "flex", opacity: 0.5 }, className: "spin", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LoaderCircle, {}) })
91
91
  ] });
92
92
  let buttonEl;
93
- if (isLink && React__namespace.isValidElement(children)) {
94
- const childClassName = typeof children.props.className === "string" ? children.props.className : "";
93
+ if (linkChild) {
94
+ const Link = linkChild.type;
95
+ const childClassName = typeof linkChild.props.className === "string" ? linkChild.props.className : "";
95
96
  const { disabled, onClick, ...linkButtonProps } = buttonProps;
96
97
  const handleClick = (e) => {
98
+ var _a, _b;
97
99
  if (disabled) {
98
100
  e.preventDefault();
99
101
  return;
100
102
  }
103
+ (_b = (_a = linkChild.props).onClick) == null ? void 0 : _b.call(_a, e);
104
+ if (e.defaultPrevented) return;
101
105
  onClick == null ? void 0 : onClick(e);
102
106
  };
103
- buttonEl = React__namespace.cloneElement(children, {
104
- ...linkButtonProps,
105
- ref: mergedRef,
106
- className: cx(childClassName, computedClassName, styles__default.default.buttonLink),
107
- ...tooltipEnabled ? triggerProps : {},
108
- "aria-describedby": describedBy,
109
- "aria-disabled": disabled ? "true" : void 0,
110
- onClick: handleClick,
111
- children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
107
+ buttonEl = /* @__PURE__ */ React.createElement(
108
+ Link,
109
+ {
110
+ ...linkChild.props,
111
+ ...linkButtonProps,
112
+ ...tooltipEnabled ? tooltipTriggerProps : {},
113
+ key: linkChild.key,
114
+ ref: mergedRef,
115
+ className: cx(childClassName, computedClassName, styles__default.default.buttonLink),
116
+ "aria-describedby": describedBy,
117
+ "aria-disabled": disabled ? "true" : void 0,
118
+ onClick: handleClick
119
+ },
120
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
112
121
  icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx(styles__default.default.icon, spinIcon ? "spin" : ""), children: icon }),
113
- children.props.children,
122
+ linkChild.props.children,
114
123
  loading && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { display: "flex", opacity: 0.5 }, className: "spin", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LoaderCircle, {}) })
115
124
  ] })
116
- });
125
+ );
117
126
  } else {
118
- const isDisabled = Boolean(buttonProps.disabled);
119
127
  buttonEl = /* @__PURE__ */ jsxRuntime.jsx(
120
128
  "button",
121
129
  {
130
+ ref: mergedRef,
122
131
  className: computedClassName,
123
132
  type,
124
133
  ...buttonProps,
125
- ...tooltipEnabled && !isDisabled ? triggerProps : {},
134
+ ...tooltipOnControl ? tooltipTriggerProps : {},
126
135
  "aria-describedby": describedBy,
127
136
  children: content
128
137
  }
@@ -1,22 +1,15 @@
1
1
  'use client';
2
2
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
- import { LoaderCircle } from 'lucide-react';
4
3
  import * as React from 'react';
4
+ import { createElement } from 'react';
5
+ import { LoaderCircle } from 'lucide-react';
5
6
  import { useTooltipTrigger } from '../../components/overlay/tooltip/useTooltipTrigger';
7
+ import { mergeRefs } from '../../utils/refs.utils';
6
8
  import styles from './Button.module.css';
7
9
 
8
10
  function cx(...parts) {
9
11
  return parts.filter(Boolean).join(" ");
10
12
  }
11
- function mergeRefs(...refs) {
12
- return (node) => {
13
- for (const ref of refs) {
14
- if (!ref) continue;
15
- if (typeof ref === "function") ref(node);
16
- else ref.current = node;
17
- }
18
- };
19
- }
20
13
  const Button = React.forwardRef(function Button2({
21
14
  variant = "outlined",
22
15
  shape = "default",
@@ -33,7 +26,6 @@ const Button = React.forwardRef(function Button2({
33
26
  type = "button",
34
27
  ...rest
35
28
  }, ref) {
36
- var _a;
37
29
  const { className: userClassName, ...buttonProps } = rest;
38
30
  const computedClassName = cx(
39
31
  styles.button,
@@ -46,17 +38,26 @@ const Button = React.forwardRef(function Button2({
46
38
  userClassName
47
39
  );
48
40
  const tooltipEnabled = Boolean(tooltip);
49
- const childRef = isLink && React.isValidElement(children) ? (_a = children.ref) != null ? _a : null : null;
50
- const mergedRef = React.useMemo(() => mergeRefs(childRef, ref), [childRef, ref]);
41
+ const linkChild = isLink && React.isValidElement(children) ? children : null;
42
+ const childRef = linkChild == null ? void 0 : linkChild.props.ref;
51
43
  const { triggerProps, id: tooltipId } = useTooltipTrigger({
52
44
  content: tooltipEnabled ? tooltip : null,
53
45
  placement: tooltipPlacement,
54
46
  offset: 8
55
47
  });
56
- const { ref: hoverRef, onPointerEnter: hoverEnter, onPointerLeave: hoverLeave } = triggerProps;
48
+ const { ref: hoverRef, ...tooltipTriggerProps } = triggerProps;
49
+ const { onPointerEnter: hoverEnter, onPointerLeave: hoverLeave } = tooltipTriggerProps;
50
+ const isDisabled = Boolean(buttonProps.disabled);
51
+ const tooltipOnControl = tooltipEnabled && (Boolean(linkChild) || !isDisabled);
52
+ const tooltipRef = tooltipOnControl ? hoverRef : void 0;
53
+ const mergedRef = React.useMemo(
54
+ () => mergeRefs(childRef, ref, tooltipRef),
55
+ [childRef, ref, tooltipRef]
56
+ );
57
57
  const describedBy = (() => {
58
58
  const existing = typeof buttonProps["aria-describedby"] === "string" ? buttonProps["aria-describedby"] : "";
59
59
  const ids = [existing, tooltipEnabled ? tooltipId : ""].filter(Boolean);
60
+ if (linkChild == null ? void 0 : linkChild.props["aria-describedby"]) ids.unshift(linkChild.props["aria-describedby"]);
60
61
  return ids.length ? ids.join(" ") : void 0;
61
62
  })();
62
63
  const content = /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -65,39 +66,48 @@ const Button = React.forwardRef(function Button2({
65
66
  loading && /* @__PURE__ */ jsx("span", { style: { display: "flex", opacity: 0.5 }, className: "spin", children: /* @__PURE__ */ jsx(LoaderCircle, {}) })
66
67
  ] });
67
68
  let buttonEl;
68
- if (isLink && React.isValidElement(children)) {
69
- const childClassName = typeof children.props.className === "string" ? children.props.className : "";
69
+ if (linkChild) {
70
+ const Link = linkChild.type;
71
+ const childClassName = typeof linkChild.props.className === "string" ? linkChild.props.className : "";
70
72
  const { disabled, onClick, ...linkButtonProps } = buttonProps;
71
73
  const handleClick = (e) => {
74
+ var _a, _b;
72
75
  if (disabled) {
73
76
  e.preventDefault();
74
77
  return;
75
78
  }
79
+ (_b = (_a = linkChild.props).onClick) == null ? void 0 : _b.call(_a, e);
80
+ if (e.defaultPrevented) return;
76
81
  onClick == null ? void 0 : onClick(e);
77
82
  };
78
- buttonEl = React.cloneElement(children, {
79
- ...linkButtonProps,
80
- ref: mergedRef,
81
- className: cx(childClassName, computedClassName, styles.buttonLink),
82
- ...tooltipEnabled ? triggerProps : {},
83
- "aria-describedby": describedBy,
84
- "aria-disabled": disabled ? "true" : void 0,
85
- onClick: handleClick,
86
- children: /* @__PURE__ */ jsxs(Fragment, { children: [
83
+ buttonEl = /* @__PURE__ */ createElement(
84
+ Link,
85
+ {
86
+ ...linkChild.props,
87
+ ...linkButtonProps,
88
+ ...tooltipEnabled ? tooltipTriggerProps : {},
89
+ key: linkChild.key,
90
+ ref: mergedRef,
91
+ className: cx(childClassName, computedClassName, styles.buttonLink),
92
+ "aria-describedby": describedBy,
93
+ "aria-disabled": disabled ? "true" : void 0,
94
+ onClick: handleClick
95
+ },
96
+ /* @__PURE__ */ jsxs(Fragment, { children: [
87
97
  icon && /* @__PURE__ */ jsx("span", { className: cx(styles.icon, spinIcon ? "spin" : ""), children: icon }),
88
- children.props.children,
98
+ linkChild.props.children,
89
99
  loading && /* @__PURE__ */ jsx("span", { style: { display: "flex", opacity: 0.5 }, className: "spin", children: /* @__PURE__ */ jsx(LoaderCircle, {}) })
90
100
  ] })
91
- });
101
+ );
92
102
  } else {
93
- const isDisabled = Boolean(buttonProps.disabled);
94
103
  buttonEl = /* @__PURE__ */ jsx(
95
104
  "button",
96
105
  {
106
+ ref: mergedRef,
97
107
  className: computedClassName,
98
108
  type,
99
109
  ...buttonProps,
100
- ...tooltipEnabled && !isDisabled ? triggerProps : {},
110
+ ...tooltipOnControl ? tooltipTriggerProps : {},
101
111
  "aria-describedby": describedBy,
102
112
  children: content
103
113
  }
@@ -21,6 +21,7 @@ function Checkbox({
21
21
  labelAs = "label",
22
22
  endAdornment,
23
23
  endAdornmentAriaHidden = true,
24
+ labelAction,
24
25
  size = "md",
25
26
  modified,
26
27
  containerLabel,
@@ -78,6 +79,7 @@ function Checkbox({
78
79
  }
79
80
  ) }),
80
81
  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 })),
82
+ labelAction && !containerLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.labelAction, "data-checkbox-label-action": "true", children: labelAction }),
81
83
  endAdornment && /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.endAdornment, "aria-hidden": endAdornmentAriaHidden, children: endAdornment })
82
84
  ]
83
85
  }
@@ -88,6 +90,7 @@ function Checkbox({
88
90
  {
89
91
  modified,
90
92
  label: containerLabel,
93
+ labelAction: containerLabel ? labelAction : void 0,
91
94
  htmlFor: controlId,
92
95
  error,
93
96
  helpText,
@@ -21,6 +21,13 @@ interface CheckboxProps {
21
21
  * Defaults to true since endAdornment is typically decorative.
22
22
  */
23
23
  endAdornmentAriaHidden?: boolean;
24
+ /**
25
+ * Content next to the label, e.g. a help button or other additional content.
26
+ * Rendered next to `containerLabel` when set, otherwise after the per-item
27
+ * `label`.
28
+ * Unlike `endAdornment`, it is never hidden from assistive tech.
29
+ */
30
+ labelAction?: ReactNode;
24
31
  size?: Size;
25
32
  containerLabel?: string;
26
33
  error?: string;
@@ -35,5 +42,5 @@ interface CheckboxProps {
35
42
  tooltip?: ReactNode;
36
43
  tooltipPlacement?: TooltipPlacement;
37
44
  }
38
- export declare function Checkbox({ checked: controlled, onChange, variant, disabled, label, labelAs, endAdornment, endAdornmentAriaHidden, size, modified, containerLabel, error, helpText, orientation, labelWidth, fullWidth, required, noContainer, id, 'data-cy': dataCy, tooltip, tooltipPlacement, }: CheckboxProps): JSX.Element;
45
+ export declare function Checkbox({ checked: controlled, onChange, variant, disabled, label, labelAs, endAdornment, endAdornmentAriaHidden, labelAction, size, modified, containerLabel, error, helpText, orientation, labelWidth, fullWidth, required, noContainer, id, 'data-cy': dataCy, tooltip, tooltipPlacement, }: CheckboxProps): JSX.Element;
39
46
  export {};
@@ -15,6 +15,7 @@ function Checkbox({
15
15
  labelAs = "label",
16
16
  endAdornment,
17
17
  endAdornmentAriaHidden = true,
18
+ labelAction,
18
19
  size = "md",
19
20
  modified,
20
21
  containerLabel,
@@ -72,6 +73,7 @@ function Checkbox({
72
73
  }
73
74
  ) }),
74
75
  label && (labelAs === "label" ? /* @__PURE__ */ jsx("label", { className: styles.label, htmlFor: controlId, children: label }) : /* @__PURE__ */ jsx("span", { className: styles.label, children: label })),
76
+ labelAction && !containerLabel && /* @__PURE__ */ jsx("span", { className: styles.labelAction, "data-checkbox-label-action": "true", children: labelAction }),
75
77
  endAdornment && /* @__PURE__ */ jsx("span", { className: styles.endAdornment, "aria-hidden": endAdornmentAriaHidden, children: endAdornment })
76
78
  ]
77
79
  }
@@ -82,6 +84,7 @@ function Checkbox({
82
84
  {
83
85
  modified,
84
86
  label: containerLabel,
87
+ labelAction: containerLabel ? labelAction : void 0,
85
88
  htmlFor: controlId,
86
89
  error,
87
90
  helpText,
@@ -57,7 +57,8 @@
57
57
  word-break: break-word;
58
58
  }
59
59
 
60
- .endAdornment {
60
+ .endAdornment,
61
+ .labelAction {
61
62
  display: inline-flex;
62
63
  align-items: center;
63
64
  flex: 0 0 auto;
@@ -55,6 +55,7 @@ function findSelectedOption(options, selectedValue, datakey) {
55
55
  function FormSelect({
56
56
  // InputContainer props
57
57
  label,
58
+ labelAction,
58
59
  error,
59
60
  helpText,
60
61
  orientation = "vertical",
@@ -119,6 +120,7 @@ function FormSelect({
119
120
  InputContainer.InputContainer,
120
121
  {
121
122
  label,
123
+ labelAction,
122
124
  htmlFor: controlId,
123
125
  fullWidth,
124
126
  error,
@@ -31,5 +31,5 @@ export type FormSelectProps<T> = Omit<InputContainerProps, 'children' | 'htmlFor
31
31
  */
32
32
  allowClearWhenRequired?: boolean;
33
33
  };
34
- export declare function FormSelect<T extends string | number | Record<string, any>>({ label, error, helpText, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, modified, id, name, options, selectedValue, onChange, placeholder, size, variant, onClear, datakey, dataCy, disabled, includePlaceholderOption, allowClearWhenRequired, }: FormSelectProps<T>): React.ReactNode;
34
+ export declare function FormSelect<T extends string | number | Record<string, any>>({ label, labelAction, error, helpText, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, modified, id, name, options, selectedValue, onChange, placeholder, size, variant, onClear, datakey, dataCy, disabled, includePlaceholderOption, allowClearWhenRequired, }: FormSelectProps<T>): React.ReactNode;
35
35
  export {};
@@ -30,6 +30,7 @@ function findSelectedOption(options, selectedValue, datakey) {
30
30
  function FormSelect({
31
31
  // InputContainer props
32
32
  label,
33
+ labelAction,
33
34
  error,
34
35
  helpText,
35
36
  orientation = "vertical",
@@ -94,6 +95,7 @@ function FormSelect({
94
95
  InputContainer,
95
96
  {
96
97
  label,
98
+ labelAction,
97
99
  htmlFor: controlId,
98
100
  fullWidth,
99
101
  error,
@@ -24,7 +24,7 @@ function InputContainer({
24
24
  labelAlignment = "center",
25
25
  modified = false
26
26
  }) {
27
- const renderLabelRow = (label || labelAction) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${styles__default.default.labelRow} dbc-flex dbc-items-center dbc-gap-md`, children: [
27
+ const renderLabelRow = (label || labelAction) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${styles__default.default.labelRow} dbc-flex dbc-items-center dbc-gap-xs`, children: [
28
28
  label && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: styles__default.default.label, htmlFor, children: [
29
29
  label,
30
30
  required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.required, children: " *" })
@@ -18,7 +18,7 @@ function InputContainer({
18
18
  labelAlignment = "center",
19
19
  modified = false
20
20
  }) {
21
- const renderLabelRow = (label || labelAction) && /* @__PURE__ */ jsxs("div", { className: `${styles.labelRow} dbc-flex dbc-items-center dbc-gap-md`, children: [
21
+ const renderLabelRow = (label || labelAction) && /* @__PURE__ */ jsxs("div", { className: `${styles.labelRow} dbc-flex dbc-items-center dbc-gap-xs`, children: [
22
22
  label && /* @__PURE__ */ jsxs("label", { className: styles.label, htmlFor, children: [
23
23
  label,
24
24
  required && /* @__PURE__ */ jsx("span", { className: styles.required, children: " *" })
@@ -2,7 +2,6 @@
2
2
  'use strict';
3
3
 
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
- var lucideReact = require('lucide-react');
6
5
  var react = require('react');
7
6
  var useTooltipTrigger = require('../../../components/overlay/tooltip/useTooltipTrigger');
8
7
  var styles = require('./Select.module.css');
@@ -18,6 +17,7 @@ var styles__default = /*#__PURE__*/_interopDefault(styles);
18
17
 
19
18
  function Select({
20
19
  label,
20
+ labelAction,
21
21
  error,
22
22
  helpText,
23
23
  orientation = "vertical",
@@ -46,6 +46,7 @@ function Select({
46
46
  const describedById = `${controlId}-desc`;
47
47
  const listboxId = `${controlId}-listbox`;
48
48
  const popoverRef = react.useRef(null);
49
+ const triggerRef = react.useRef(null);
49
50
  const optionRefs = react.useRef([]);
50
51
  const typeaheadRef = react.useRef("");
51
52
  const typeaheadTimeoutRef = react.useRef(null);
@@ -61,9 +62,10 @@ function Select({
61
62
  };
62
63
  }, []);
63
64
  react.useEffect(() => {
64
- var _a;
65
+ var _a, _b, _c;
65
66
  if (!open) return;
66
67
  (_a = optionRefs.current[activeIndex]) == null ? void 0 : _a.focus();
68
+ (_c = (_b = optionRefs.current[activeIndex]) == null ? void 0 : _b.scrollIntoView) == null ? void 0 : _c.call(_b, { block: "nearest" });
67
69
  }, [activeIndex, open]);
68
70
  const resetActiveToSelected = () => setActiveIndex(selectedIndex >= 0 ? selectedIndex : 0);
69
71
  const findTypeaheadMatch = (query, startIndex) => {
@@ -99,25 +101,30 @@ function Select({
99
101
  if (!open) setOpen(true);
100
102
  };
101
103
  const handleKeyDown = (e) => {
102
- var _a, _b;
104
+ var _a, _b, _c, _d, _e;
103
105
  if (e.key.length === 1 && !e.altKey && !e.ctrlKey && !e.metaKey && !/\s/.test(e.key)) {
104
106
  e.preventDefault();
105
107
  handleTypeahead(e.key);
106
108
  return;
107
109
  }
108
110
  switch (e.key) {
109
- case "ArrowDown": {
110
- e.preventDefault();
111
- if (!open) {
112
- setOpen(true);
113
- }
114
- break;
115
- }
111
+ case "ArrowDown":
116
112
  case "ArrowUp": {
117
113
  e.preventDefault();
114
+ e.stopPropagation();
118
115
  if (!open) {
116
+ resetActiveToSelected();
119
117
  setOpen(true);
118
+ break;
120
119
  }
120
+ if (options.length === 0) break;
121
+ const nextIndex = Math.max(
122
+ 0,
123
+ Math.min(activeIndex + (e.key === "ArrowDown" ? 1 : -1), options.length - 1)
124
+ );
125
+ setActiveIndex(nextIndex);
126
+ (_a = optionRefs.current[nextIndex]) == null ? void 0 : _a.focus();
127
+ (_c = (_b = optionRefs.current[nextIndex]) == null ? void 0 : _b.scrollIntoView) == null ? void 0 : _c.call(_b, { block: "nearest" });
121
128
  break;
122
129
  }
123
130
  case "Enter":
@@ -129,20 +136,21 @@ function Select({
129
136
  typeaheadTimeoutRef.current = null;
130
137
  }
131
138
  if (!open) {
139
+ resetActiveToSelected();
132
140
  setOpen(true);
133
141
  return;
134
142
  }
135
143
  const opt = options[activeIndex];
136
144
  if (opt) {
137
145
  onChange(opt.value);
138
- (_a = popoverRef.current) == null ? void 0 : _a.close();
146
+ (_d = popoverRef.current) == null ? void 0 : _d.close();
139
147
  }
140
148
  break;
141
149
  }
142
150
  case "Escape": {
143
151
  if (!open) return;
144
152
  e.preventDefault();
145
- (_b = popoverRef.current) == null ? void 0 : _b.close();
153
+ (_e = popoverRef.current) == null ? void 0 : _e.close();
146
154
  break;
147
155
  }
148
156
  case "Home": {
@@ -176,6 +184,7 @@ function Select({
176
184
  InputContainer.InputContainer,
177
185
  {
178
186
  label,
187
+ labelAction,
179
188
  htmlFor: controlId,
180
189
  fullWidth,
181
190
  error,
@@ -189,6 +198,7 @@ function Select({
189
198
  Popover.Popover,
190
199
  {
191
200
  ref: popoverRef,
201
+ anchorRef: triggerRef,
192
202
  open,
193
203
  fullWidth,
194
204
  onOpenChange: (next) => {
@@ -203,6 +213,10 @@ function Select({
203
213
  {
204
214
  disabled,
205
215
  ...tooltipEnabled ? triggerProps : {},
216
+ ref: (node) => {
217
+ triggerRef.current = node;
218
+ if (tooltipEnabled) triggerProps.ref(node);
219
+ },
206
220
  id: controlId,
207
221
  "data-cy": dataCy != null ? dataCy : "select-button",
208
222
  onKeyDown: handleKeyDown,
@@ -246,7 +260,7 @@ function Select({
246
260
  )
247
261
  }
248
262
  ),
249
- children: /* @__PURE__ */ jsxRuntime.jsxs(Menu.Menu, { onKeyDown: handleKeyDown, role: "listbox", className: styles__default.default.menu, children: [
263
+ children: /* @__PURE__ */ jsxRuntime.jsxs(Menu.Menu, { onKeyDownCapture: handleKeyDown, role: "listbox", className: styles__default.default.menu, children: [
250
264
  menuHeader ? /* @__PURE__ */ jsxRuntime.jsx(Menu.Menu.Header, { children: menuHeader }) : null,
251
265
  options.map((opt, index) => {
252
266
  const isSelected = typeof opt.value === "object" && typeof selectedValue === "object" && datakey ? (selectedValue == null ? void 0 : selectedValue[datakey]) === opt.value[datakey] : opt.value === selectedValue;
@@ -258,7 +272,7 @@ function Select({
258
272
  selected: isSelected,
259
273
  itemRole: "option",
260
274
  className: isSelected ? styles__default.default.selectedOption : void 0,
261
- children: /* @__PURE__ */ jsxRuntime.jsxs(
275
+ children: /* @__PURE__ */ jsxRuntime.jsx(
262
276
  "button",
263
277
  {
264
278
  ref: (el) => {
@@ -273,10 +287,7 @@ function Select({
273
287
  },
274
288
  onFocus: () => setActiveIndex(index),
275
289
  style: { display: "flex", alignItems: "center", width: "100%" },
276
- children: [
277
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { width: 16, display: "inline-flex", justifyContent: "center" }, children: isSelected ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, {}) : null }),
278
- opt.label
279
- ]
290
+ children: opt.label
280
291
  }
281
292
  )
282
293
  },
@@ -20,5 +20,5 @@ export type SelectProps<T> = Omit<InputContainerProps, 'children' | 'htmlFor' |
20
20
  tooltip?: React.ReactNode;
21
21
  tooltipPlacement?: 'top' | 'right' | 'bottom' | 'left';
22
22
  };
23
- export declare function Select<T extends string | number | Record<string, any>>({ label, error, helpText, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, modified, id, options, selectedValue, onChange, placeholder, size, variant, onClear, datakey, dataCy, disabled, minWidth, menuHeader, }: SelectProps<T>): React.ReactNode;
23
+ export declare function Select<T extends string | number | Record<string, any>>({ label, labelAction, error, helpText, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, modified, id, options, selectedValue, onChange, placeholder, size, variant, onClear, datakey, dataCy, disabled, minWidth, menuHeader, }: SelectProps<T>): React.ReactNode;
24
24
  export {};
@@ -1,6 +1,5 @@
1
1
  'use client';
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
- import { Check } from 'lucide-react';
4
3
  import { useId, useRef, useMemo, useState, useEffect } from 'react';
5
4
  import { useTooltipTrigger } from '../../../components/overlay/tooltip/useTooltipTrigger';
6
5
  import styles from './Select.module.css';
@@ -12,6 +11,7 @@ import { InputContainer } from '../input-container/InputContainer';
12
11
 
13
12
  function Select({
14
13
  label,
14
+ labelAction,
15
15
  error,
16
16
  helpText,
17
17
  orientation = "vertical",
@@ -40,6 +40,7 @@ function Select({
40
40
  const describedById = `${controlId}-desc`;
41
41
  const listboxId = `${controlId}-listbox`;
42
42
  const popoverRef = useRef(null);
43
+ const triggerRef = useRef(null);
43
44
  const optionRefs = useRef([]);
44
45
  const typeaheadRef = useRef("");
45
46
  const typeaheadTimeoutRef = useRef(null);
@@ -55,9 +56,10 @@ function Select({
55
56
  };
56
57
  }, []);
57
58
  useEffect(() => {
58
- var _a;
59
+ var _a, _b, _c;
59
60
  if (!open) return;
60
61
  (_a = optionRefs.current[activeIndex]) == null ? void 0 : _a.focus();
62
+ (_c = (_b = optionRefs.current[activeIndex]) == null ? void 0 : _b.scrollIntoView) == null ? void 0 : _c.call(_b, { block: "nearest" });
61
63
  }, [activeIndex, open]);
62
64
  const resetActiveToSelected = () => setActiveIndex(selectedIndex >= 0 ? selectedIndex : 0);
63
65
  const findTypeaheadMatch = (query, startIndex) => {
@@ -93,25 +95,30 @@ function Select({
93
95
  if (!open) setOpen(true);
94
96
  };
95
97
  const handleKeyDown = (e) => {
96
- var _a, _b;
98
+ var _a, _b, _c, _d, _e;
97
99
  if (e.key.length === 1 && !e.altKey && !e.ctrlKey && !e.metaKey && !/\s/.test(e.key)) {
98
100
  e.preventDefault();
99
101
  handleTypeahead(e.key);
100
102
  return;
101
103
  }
102
104
  switch (e.key) {
103
- case "ArrowDown": {
104
- e.preventDefault();
105
- if (!open) {
106
- setOpen(true);
107
- }
108
- break;
109
- }
105
+ case "ArrowDown":
110
106
  case "ArrowUp": {
111
107
  e.preventDefault();
108
+ e.stopPropagation();
112
109
  if (!open) {
110
+ resetActiveToSelected();
113
111
  setOpen(true);
112
+ break;
114
113
  }
114
+ if (options.length === 0) break;
115
+ const nextIndex = Math.max(
116
+ 0,
117
+ Math.min(activeIndex + (e.key === "ArrowDown" ? 1 : -1), options.length - 1)
118
+ );
119
+ setActiveIndex(nextIndex);
120
+ (_a = optionRefs.current[nextIndex]) == null ? void 0 : _a.focus();
121
+ (_c = (_b = optionRefs.current[nextIndex]) == null ? void 0 : _b.scrollIntoView) == null ? void 0 : _c.call(_b, { block: "nearest" });
115
122
  break;
116
123
  }
117
124
  case "Enter":
@@ -123,20 +130,21 @@ function Select({
123
130
  typeaheadTimeoutRef.current = null;
124
131
  }
125
132
  if (!open) {
133
+ resetActiveToSelected();
126
134
  setOpen(true);
127
135
  return;
128
136
  }
129
137
  const opt = options[activeIndex];
130
138
  if (opt) {
131
139
  onChange(opt.value);
132
- (_a = popoverRef.current) == null ? void 0 : _a.close();
140
+ (_d = popoverRef.current) == null ? void 0 : _d.close();
133
141
  }
134
142
  break;
135
143
  }
136
144
  case "Escape": {
137
145
  if (!open) return;
138
146
  e.preventDefault();
139
- (_b = popoverRef.current) == null ? void 0 : _b.close();
147
+ (_e = popoverRef.current) == null ? void 0 : _e.close();
140
148
  break;
141
149
  }
142
150
  case "Home": {
@@ -170,6 +178,7 @@ function Select({
170
178
  InputContainer,
171
179
  {
172
180
  label,
181
+ labelAction,
173
182
  htmlFor: controlId,
174
183
  fullWidth,
175
184
  error,
@@ -183,6 +192,7 @@ function Select({
183
192
  Popover,
184
193
  {
185
194
  ref: popoverRef,
195
+ anchorRef: triggerRef,
186
196
  open,
187
197
  fullWidth,
188
198
  onOpenChange: (next) => {
@@ -197,6 +207,10 @@ function Select({
197
207
  {
198
208
  disabled,
199
209
  ...tooltipEnabled ? triggerProps : {},
210
+ ref: (node) => {
211
+ triggerRef.current = node;
212
+ if (tooltipEnabled) triggerProps.ref(node);
213
+ },
200
214
  id: controlId,
201
215
  "data-cy": dataCy != null ? dataCy : "select-button",
202
216
  onKeyDown: handleKeyDown,
@@ -240,7 +254,7 @@ function Select({
240
254
  )
241
255
  }
242
256
  ),
243
- children: /* @__PURE__ */ jsxs(Menu, { onKeyDown: handleKeyDown, role: "listbox", className: styles.menu, children: [
257
+ children: /* @__PURE__ */ jsxs(Menu, { onKeyDownCapture: handleKeyDown, role: "listbox", className: styles.menu, children: [
244
258
  menuHeader ? /* @__PURE__ */ jsx(Menu.Header, { children: menuHeader }) : null,
245
259
  options.map((opt, index) => {
246
260
  const isSelected = typeof opt.value === "object" && typeof selectedValue === "object" && datakey ? (selectedValue == null ? void 0 : selectedValue[datakey]) === opt.value[datakey] : opt.value === selectedValue;
@@ -252,7 +266,7 @@ function Select({
252
266
  selected: isSelected,
253
267
  itemRole: "option",
254
268
  className: isSelected ? styles.selectedOption : void 0,
255
- children: /* @__PURE__ */ jsxs(
269
+ children: /* @__PURE__ */ jsx(
256
270
  "button",
257
271
  {
258
272
  ref: (el) => {
@@ -267,10 +281,7 @@ function Select({
267
281
  },
268
282
  onFocus: () => setActiveIndex(index),
269
283
  style: { display: "flex", alignItems: "center", width: "100%" },
270
- children: [
271
- /* @__PURE__ */ jsx("span", { style: { width: 16, display: "inline-flex", justifyContent: "center" }, children: isSelected ? /* @__PURE__ */ jsx(Check, {}) : null }),
272
- opt.label
273
- ]
284
+ children: opt.label
274
285
  }
275
286
  )
276
287
  },
@@ -225,6 +225,7 @@ const Popover = React.forwardRef(function Popover2({
225
225
  if (!isOpen) return;
226
226
  const content = contentRef.current;
227
227
  if (!content) return;
228
+ if (anchorRef == null ? void 0 : anchorRef.current) triggerElRef.current = anchorRef.current;
228
229
  const triggerEl = (_b = (_a = anchorRef == null ? void 0 : anchorRef.current) != null ? _a : triggerElRef.current) != null ? _b : containerRef.current;
229
230
  if (autoFocusContent) {
230
231
  const focusables = getFocusable(content);
@@ -201,6 +201,7 @@ const Popover = forwardRef(function Popover2({
201
201
  if (!isOpen) return;
202
202
  const content = contentRef.current;
203
203
  if (!content) return;
204
+ if (anchorRef == null ? void 0 : anchorRef.current) triggerElRef.current = anchorRef.current;
204
205
  const triggerEl = (_b = (_a = anchorRef == null ? void 0 : anchorRef.current) != null ? _a : triggerElRef.current) != null ? _b : containerRef.current;
205
206
  if (autoFocusContent) {
206
207
  const focusables = getFocusable(content);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbcdk/react-components",
3
- "version": "0.0.200",
3
+ "version": "0.0.201",
4
4
  "description": "Reusable React components for DBC projects",
5
5
  "license": "ISC",
6
6
  "author": "",