@dbcdk/react-components 0.0.51 → 0.0.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/components/__stories__/story-components/ComponentSizes.d.ts +2 -0
  2. package/dist/components/__stories__/story-components/ComponentSizes.js +26 -0
  3. package/dist/components/__stories__/story-components/Elevation.d.ts +2 -0
  4. package/dist/components/__stories__/story-components/Elevation.js +49 -0
  5. package/dist/components/__stories__/story-components/Spacing.d.ts +5 -1
  6. package/dist/components/__stories__/story-components/Spacing.js +3 -3
  7. package/dist/components/code-block/CodeBlock.module.css +5 -3
  8. package/dist/components/copy-button/CopyButton.d.ts +3 -1
  9. package/dist/components/copy-button/CopyButton.js +34 -11
  10. package/dist/components/copy-button/CopyButton.module.css +20 -4
  11. package/dist/components/filter-field/FilterField.d.ts +2 -1
  12. package/dist/components/filter-field/FilterField.js +3 -3
  13. package/dist/components/forms/input/Input.d.ts +1 -1
  14. package/dist/components/forms/input/Input.js +1 -1
  15. package/dist/components/forms/input/Input.module.css +2 -2
  16. package/dist/components/forms/typeahead/Typeahead.d.ts +2 -1
  17. package/dist/components/forms/typeahead/Typeahead.js +13 -6
  18. package/dist/components/forms/typeahead/Typeahead.module.css +3 -0
  19. package/dist/components/hyperlink/Hyperlink.module.css +28 -2
  20. package/dist/components/menu/Menu.module.css +1 -2
  21. package/dist/components/popover/Popover.module.css +1 -1
  22. package/dist/components/search-box/SearchBox.d.ts +1 -1
  23. package/dist/components/split-pane/provider/SplitPaneContext.js +9 -1
  24. package/dist/components/tabs/Tabs.d.ts +1 -1
  25. package/dist/components/tabs/Tabs.js +1 -1
  26. package/dist/components/toast/Toast.js +11 -3
  27. package/dist/components/toast/Toast.module.css +30 -0
  28. package/dist/components/toast/provider/ToastProvider.d.ts +1 -0
  29. package/dist/components/toast/provider/ToastProvider.js +9 -4
  30. package/dist/styles/themes/dbc/dark.css +2 -0
  31. package/dist/styles/themes/dbc/light.css +2 -0
  32. package/package.json +1 -1
@@ -0,0 +1,2 @@
1
+ import type { JSX } from 'react';
2
+ export declare function ComponentSizes(): JSX.Element;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Headline } from '../../../components/headline/Headline';
3
+ const TOKENS = [
4
+ { name: '2xs', token: '--component-size-2xs' },
5
+ { name: 'xxs', token: '--component-size-xxs' },
6
+ { name: 'xs', token: '--component-size-xs' },
7
+ { name: 'sm', token: '--component-size-sm' },
8
+ { name: 'md', token: '--component-size-md' },
9
+ { name: 'lg', token: '--component-size-lg' },
10
+ ];
11
+ function Swatch({ token, name }) {
12
+ return (_jsxs("div", { style: {
13
+ display: 'grid',
14
+ gap: 8,
15
+ justifyItems: 'start',
16
+ minWidth: 120,
17
+ }, children: [_jsx("div", { style: { fontSize: 'var(--font-size-xs)', color: 'var(--color-fg-muted)' }, children: name }), _jsx("div", { style: {
18
+ width: `var(${token})`,
19
+ height: `var(${token})`,
20
+ borderRadius: 'var(--border-radius-default)',
21
+ background: 'var(--color-brand)',
22
+ } }), _jsx("code", { style: { fontSize: 'var(--font-size-xs)' }, children: token })] }));
23
+ }
24
+ export function ComponentSizes() {
25
+ return (_jsxs("div", { style: { display: 'grid', gap: 16 }, children: [_jsx(Headline, { disableMargin: true, children: "Komponentst\u00F8rrelser" }), _jsx("p", { style: { margin: 0, color: 'var(--color-fg-muted)' }, children: "Tokens til faste komponenth\u00F8jder og st\u00F8rrelser, som bruges p\u00E5 tv\u00E6rs af inputfelter, chips, indikatorer og andre UI-elementer." }), _jsx("div", { style: { display: 'flex', gap: 20, flexWrap: 'wrap', alignItems: 'end' }, children: TOKENS.map(item => (_jsx(Swatch, { token: item.token, name: item.name }, item.token))) })] }));
26
+ }
@@ -0,0 +1,2 @@
1
+ import type { JSX } from 'react';
2
+ export declare function Elevation(): JSX.Element;
@@ -0,0 +1,49 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Headline } from '../../../components/headline/Headline';
3
+ const SHADOWS = [
4
+ { name: 'xs', token: '--shadow-xs' },
5
+ { name: 'sm', token: '--shadow-sm' },
6
+ { name: 'md', token: '--shadow-md' },
7
+ { name: 'lg', token: '--shadow-lg' },
8
+ { name: 'xl', token: '--shadow-xl' },
9
+ ];
10
+ const RADII = [
11
+ { name: 'sm', token: '--border-radius-sm' },
12
+ { name: 'default', token: '--border-radius-default' },
13
+ { name: 'md', token: '--border-radius-md' },
14
+ { name: 'lg', token: '--border-radius-lg' },
15
+ { name: 'xl', token: '--border-radius-xl' },
16
+ { name: 'rounded', token: '--border-radius-rounded' },
17
+ ];
18
+ export function Elevation() {
19
+ return (_jsxs("div", { style: { display: 'grid', gap: 24 }, children: [_jsxs("section", { style: { display: 'grid', gap: 16 }, children: [_jsx(Headline, { disableMargin: true, children: "Skygger" }), _jsx("div", { style: { display: 'flex', gap: 20, flexWrap: 'wrap' }, children: SHADOWS.map(item => (_jsxs("div", { style: {
20
+ display: 'grid',
21
+ gap: 8,
22
+ width: 140,
23
+ }, children: [_jsx("div", { style: {
24
+ height: 72,
25
+ borderRadius: 'var(--border-radius-md)',
26
+ background: 'var(--color-bg-surface)',
27
+ boxShadow: `var(${item.token})`,
28
+ border: '1px solid var(--color-border-subtle)',
29
+ } }), _jsx("div", { style: { fontSize: 'var(--font-size-xs)' }, children: item.name }), _jsx("code", { style: { fontSize: 'var(--font-size-xs)' }, children: item.token })] }, item.token))) })] }), _jsxs("section", { style: { display: 'grid', gap: 16 }, children: [_jsx(Headline, { disableMargin: true, children: "Border radius" }), _jsx("div", { style: { display: 'flex', gap: 20, flexWrap: 'wrap', alignItems: 'end' }, children: RADII.map(item => (_jsxs("div", { style: {
30
+ display: 'grid',
31
+ gap: 8,
32
+ width: 120,
33
+ }, children: [_jsx("div", { style: {
34
+ height: 64,
35
+ background: 'var(--color-bg-contextual)',
36
+ border: '1px solid var(--color-border-default)',
37
+ borderRadius: `var(${item.token})`,
38
+ } }), _jsx("div", { style: { fontSize: 'var(--font-size-xs)' }, children: item.name }), _jsx("code", { style: { fontSize: 'var(--font-size-xs)' }, children: item.token })] }, item.token))) })] }), _jsxs("section", { style: { display: 'grid', gap: 16 }, children: [_jsx(Headline, { disableMargin: true, children: "Fokusmarkering" }), _jsx("div", { style: {
39
+ display: 'inline-flex',
40
+ width: 180,
41
+ height: 44,
42
+ alignItems: 'center',
43
+ justifyContent: 'center',
44
+ borderRadius: 'var(--border-radius-default)',
45
+ background: 'var(--color-bg-surface)',
46
+ border: '1px solid var(--color-border-default)',
47
+ boxShadow: 'var(--focus-ring)',
48
+ }, children: _jsx("code", { style: { fontSize: 'var(--font-size-xs)' }, children: "--focus-ring" }) })] })] }));
49
+ }
@@ -1,2 +1,6 @@
1
1
  import type { JSX } from 'react';
2
- export declare function Spacing(): JSX.Element;
2
+ type SpacingMode = 'padding' | 'margin' | 'both';
3
+ export declare function Spacing({ mode }: {
4
+ mode?: SpacingMode;
5
+ }): JSX.Element;
6
+ export {};
@@ -62,9 +62,9 @@ function Cell({ kind, axis, size }) {
62
62
  return (_jsx("div", { className: styles.cellInner, children: _jsx("div", { className: styles.cellPreview, children: kind === 'padding' ? _jsx(PaddingPreview, { cls: cls }) : _jsx(MarginPreview, { cls: cls }) }) }));
63
63
  }
64
64
  function Grid({ kind, title, severity }) {
65
- return (_jsxs("section", { className: styles.section, children: [_jsx(Headline, { marker: true, severity: severity, disableMargin: true, size: 2, children: title }), _jsx("div", { className: styles.tableContainer, children: _jsxs("table", { className: styles.table, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { className: styles.axisCol, children: "Axis" }), SIZES.map(size => (_jsxs("th", { className: styles.sizeHead, children: [size, " ", _jsxs("span", { className: styles.sizePx, children: ["(", SIZE_PX[size], ")"] })] }, size)))] }) }), _jsx("tbody", { children: AXES.map(axis => (_jsxs("tr", { children: [_jsx("th", { className: styles.axisHead, children: axis }), SIZES.map(size => (_jsx("td", { className: styles.cell, children: _jsx(Cell, { kind: kind, axis: axis, size: size }) }, size)))] }, axis))) })] }) })] }));
65
+ return (_jsxs("section", { className: styles.section, children: [_jsx(Headline, { marker: true, severity: severity, disableMargin: true, size: 2, children: title }), _jsx("div", { className: styles.tableContainer, children: _jsxs("table", { className: styles.table, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { className: styles.axisCol, children: "Akse" }), SIZES.map(size => (_jsxs("th", { className: styles.sizeHead, children: [size, " ", _jsxs("span", { className: styles.sizePx, children: ["(", SIZE_PX[size], ")"] })] }, size)))] }) }), _jsx("tbody", { children: AXES.map(axis => (_jsxs("tr", { children: [_jsx("th", { className: styles.axisHead, children: axis }), SIZES.map(size => (_jsx("td", { className: styles.cell, children: _jsx(Cell, { kind: kind, axis: axis, size: size }) }, size)))] }, axis))) })] }) })] }));
66
66
  }
67
- export function Spacing() {
67
+ export function Spacing({ mode = 'both' }) {
68
68
  return (_jsxs("div", { className: styles.container, style: {
69
69
  ['--pad-indicator']: 'var(--color-bg-info)',
70
70
  ['--pad-border']: 'var(--color-info)',
@@ -72,5 +72,5 @@ export function Spacing() {
72
72
  ['--margin-border']: 'var(--color-warning)',
73
73
  ['--dot-pad']: 'var(--color-bg-selected)',
74
74
  ['--dot-mar']: 'var(--opac-bg-default)',
75
- }, children: [_jsx("header", { className: styles.header, children: _jsx(Headline, { disableMargin: true, children: "Spacing helpers" }) }), _jsxs("section", { className: styles.intro, children: [_jsx("h2", { className: styles.h2, children: "Naming Convention" }), _jsxs("p", { className: styles.paragraph, children: ["Spacing helpers follow a simple ", _jsx("strong", { children: "prefix\u2013axis\u2013size" }), " pattern:"] }), _jsx("div", { className: styles.example, children: _jsx(Code, { children: "dbc-[prefix][axis?]-[size]" }) }), _jsxs("ul", { className: styles.list, children: [_jsxs("li", { children: [_jsx("strong", { children: "Prefix" }), ": ", _jsx(Code, { children: "p" }), " for ", _jsx("em", { children: "padding" }), ", ", _jsx(Code, { children: "m" }), " for", ' ', _jsx("em", { children: "margin" }), "."] }), _jsxs("li", { children: [_jsx("strong", { children: "Axis" }), " (optional): ", _jsx(Code, { children: "x" }), " = inline (left + right), ", _jsx(Code, { children: "y" }), ' ', "= block (top + bottom), or single sides ", _jsx(Code, { children: "t" }), " / ", _jsx(Code, { children: "b" }), " / ", _jsx(Code, { children: "s" }), ' ', "/ ", _jsx(Code, { children: "e" }), "."] }), _jsxs("li", { children: [_jsx("strong", { children: "Size" }), ": one of ", _jsx(Code, { children: "xxs" }), ", ", _jsx(Code, { children: "xs" }), ", ", _jsx(Code, { children: "sm" }), ",", ' ', _jsx(Code, { children: "md" }), ", ", _jsx(Code, { children: "lg" }), ", ", _jsx(Code, { children: "xl" }), ", ", _jsx(Code, { children: "2xl" }), "."] })] }), _jsxs("p", { className: styles.paragraph, children: ["For example: ", _jsx(Code, { children: "dbc-px-md" }), " adds medium horizontal padding, while", ' ', _jsx(Code, { children: "dbc-mt-lg" }), " adds a large top margin."] })] }), _jsx(Grid, { kind: "padding", title: "Padding", severity: "info" }), _jsx(Grid, { kind: "margin", title: "Margin", severity: "warning" })] }));
75
+ }, children: [_jsx("header", { className: styles.header, children: _jsx(Headline, { disableMargin: true, children: "Spacing-hj\u00E6lpere" }) }), _jsxs("section", { className: styles.intro, children: [_jsx("h2", { className: styles.h2, children: "Navngivningskonvention" }), _jsxs("p", { className: styles.paragraph, children: ["Spacing-hj\u00E6lpere f\u00F8lger et enkelt m\u00F8nster med ", _jsx("strong", { children: "prefix\u2013akse\u2013st\u00F8rrelse" }), ":"] }), _jsx("div", { className: styles.example, children: _jsx(Code, { children: "dbc-[prefix][axis?]-[size]" }) }), _jsxs("ul", { className: styles.list, children: [_jsxs("li", { children: [_jsx("strong", { children: "Prefix" }), ": ", _jsx(Code, { children: "p" }), " for ", _jsx("em", { children: "padding" }), ", ", _jsx(Code, { children: "m" }), " for", ' ', _jsx("em", { children: "margin" }), "."] }), _jsxs("li", { children: [_jsx("strong", { children: "Akse" }), " (valgfri): ", _jsx(Code, { children: "x" }), " = inline (venstre + h\u00F8jre),", ' ', _jsx(Code, { children: "y" }), " = blok (top + bund), eller enkeltsider ", _jsx(Code, { children: "t" }), " / ", _jsx(Code, { children: "b" }), " /", ' ', _jsx(Code, { children: "s" }), " / ", _jsx(Code, { children: "e" }), "."] }), _jsxs("li", { children: [_jsx("strong", { children: "St\u00F8rrelse" }), ": \u00E9n af ", _jsx(Code, { children: "xxs" }), ", ", _jsx(Code, { children: "xs" }), ", ", _jsx(Code, { children: "sm" }), ",", ' ', _jsx(Code, { children: "md" }), ", ", _jsx(Code, { children: "lg" }), ", ", _jsx(Code, { children: "xl" }), ", ", _jsx(Code, { children: "2xl" }), "."] })] }), _jsxs("p", { className: styles.paragraph, children: ["Eksempel: ", _jsx(Code, { children: "dbc-px-md" }), " giver medium horizontal padding, mens", ' ', _jsx(Code, { children: "dbc-mt-lg" }), " giver stor top-margin."] })] }), (mode === 'both' || mode === 'padding') && (_jsx(Grid, { kind: "padding", title: "Padding", severity: "info" })), (mode === 'both' || mode === 'margin') && (_jsx(Grid, { kind: "margin", title: "Margin", severity: "warning" }))] }));
76
76
  }
@@ -1,5 +1,7 @@
1
1
  .wrapper {
2
2
  position: relative;
3
+ display: inline-block;
4
+ max-inline-size: 100%;
3
5
  --code-actions-h: var(--component-size-sm);
4
6
  --code-actions-inset: var(--spacing-xs);
5
7
  }
@@ -17,9 +19,9 @@
17
19
  line-height: 1.35;
18
20
  overflow: auto;
19
21
  scrollbar-gutter: stable;
20
-
21
- display: flex;
22
- align-items: center;
22
+ display: inline-flex;
23
+ align-items: flex-start;
24
+ max-inline-size: 100%;
23
25
  }
24
26
 
25
27
  /* Sizes */
@@ -5,7 +5,9 @@ interface CopyButtonProps {
5
5
  variant?: ButtonVariant;
6
6
  size?: ButtonSize;
7
7
  children?: ReactNode;
8
- style?: 'button' | 'link';
8
+ displayStyle?: 'button' | 'link' | 'field';
9
+ disableIcon?: boolean;
10
+ disableToast?: boolean;
9
11
  }
10
12
  export declare function CopyButton(props: CopyButtonProps & ComponentProps<typeof Button>): ReactNode;
11
13
  export {};
@@ -4,19 +4,36 @@ import { Check, Copy } from 'lucide-react';
4
4
  import { useState } from 'react';
5
5
  import styles from './CopyButton.module.css';
6
6
  import { Button } from '../button/Button';
7
+ import { Input } from '../forms/input/Input';
7
8
  import { Hyperlink } from '../hyperlink/Hyperlink';
9
+ import { useOptionalToast } from '../toast/provider/ToastProvider';
8
10
  export function CopyButton(props) {
9
- var _a, _b;
10
- const { text, variant = 'outlined', size = 'sm', children, ...rest } = props;
11
+ var _a, _b, _c;
12
+ const { text, variant = 'outlined', size = 'sm', children, displayStyle, disableIcon = false, disableToast = false, className, style, fullWidth, disabled, ...rest } = props;
11
13
  const [copied, setCopied] = useState(false);
14
+ const toast = useOptionalToast();
15
+ const buttonLabel = children !== null && children !== void 0 ? children : 'Kopiér';
16
+ const icon = disableIcon ? null : copied ? _jsx(Check, {}) : _jsx(Copy, {});
17
+ const handleCopySuccess = () => {
18
+ if (!disableIcon) {
19
+ setCopied(true);
20
+ window.setTimeout(() => setCopied(false), 1000);
21
+ return;
22
+ }
23
+ if (!disableToast) {
24
+ toast === null || toast === void 0 ? void 0 : toast.showToast({
25
+ severity: 'neutral',
26
+ message: 'Kopieret',
27
+ });
28
+ }
29
+ };
12
30
  const handleCopy = async () => {
13
31
  try {
14
32
  if (!window.isSecureContext || !navigator.clipboard) {
15
33
  throw new Error('Clipboard API unavailable');
16
34
  }
17
35
  await navigator.clipboard.writeText(text);
18
- setCopied(true);
19
- setTimeout(() => setCopied(false), 1000);
36
+ handleCopySuccess();
20
37
  }
21
38
  catch (err) {
22
39
  console.error('Failed to copy:', err);
@@ -31,8 +48,7 @@ export function CopyButton(props) {
31
48
  const success = document.execCommand('copy');
32
49
  document.body.removeChild(textarea);
33
50
  if (success) {
34
- setCopied(true);
35
- setTimeout(() => setCopied(false), 1000);
51
+ handleCopySuccess();
36
52
  }
37
53
  }
38
54
  catch (fallbackErr) {
@@ -40,16 +56,23 @@ export function CopyButton(props) {
40
56
  }
41
57
  }
42
58
  };
43
- if (props.style === 'link') {
44
- return (_jsx(Hyperlink, { asChild: true, variant: "secondary", inline: props.variant === 'inline', children: _jsxs("button", { "aria-label": children ? '' : ((_a = rest['aria-label']) !== null && _a !== void 0 ? _a : 'Kopier til udklipsholder'), onClick: e => {
59
+ if (displayStyle === 'link') {
60
+ return (_jsx(Hyperlink, { asChild: true, variant: "primary", icon: icon, children: _jsx("button", { type: "button", "aria-label": children ? '' : ((_a = rest['aria-label']) !== null && _a !== void 0 ? _a : 'Kopier til udklipsholder'), onClick: e => {
45
61
  e.preventDefault();
46
62
  e.stopPropagation();
47
63
  handleCopy();
48
- }, className: `${styles.link} ${copied ? styles.copied : ''}`, children: [children, copied ? _jsx(Check, {}) : _jsx(Copy, {})] }) }));
64
+ }, className: [styles.link, copied ? styles.copied : '', className !== null && className !== void 0 ? className : '']
65
+ .filter(Boolean)
66
+ .join(' '), style: style, disabled: disabled, children: buttonLabel }) }));
67
+ }
68
+ if (displayStyle === 'field') {
69
+ return (_jsx(Input, { value: text, readOnly: true, disabled: disabled, fullWidth: fullWidth, className: className, style: style, inputSize: size, variant: "outlined", buttonLabel: String(buttonLabel), buttonIcon: icon, onButtonClick: () => {
70
+ void handleCopy();
71
+ }, "aria-label": (_b = rest['aria-label']) !== null && _b !== void 0 ? _b : 'Kopier til udklipsholder' }));
49
72
  }
50
- return (_jsxs(Button, { ...rest, "aria-label": children ? '' : ((_b = rest['aria-label']) !== null && _b !== void 0 ? _b : 'Kopier til udklipsholder'), onClick: e => {
73
+ return (_jsxs(Button, { ...rest, "aria-label": children ? '' : ((_c = rest['aria-label']) !== null && _c !== void 0 ? _c : 'Kopier til udklipsholder'), onClick: e => {
51
74
  e.preventDefault();
52
75
  e.stopPropagation();
53
76
  handleCopy();
54
- }, variant: variant, size: size, children: [_jsx("span", { className: `${styles.container} ${copied ? styles.copied : ''}`, children: copied ? _jsx(Check, {}) : _jsx(Copy, {}) }), children] }));
77
+ }, variant: variant, size: size, className: className, style: style, disabled: disabled, children: [_jsx("span", { className: `${styles.container} ${copied ? styles.copied : ''}`, children: icon }), buttonLabel] }));
55
78
  }
@@ -1,6 +1,22 @@
1
- .copied > button {
2
- background-color: var(--color-status-success-bg);
3
- border-color: var(--color-status-success-border);
1
+ .container,
2
+ .link {
3
+ display: inline-flex;
4
+ align-items: center;
5
+ gap: var(--spacing-xxs);
6
+ background-color: unset;
7
+ }
8
+
9
+ .link {
10
+ appearance: none;
11
+ border: none;
12
+ padding: 0;
13
+ font: inherit;
14
+ color: inherit;
15
+ cursor: pointer;
16
+ vertical-align: middle;
17
+ }
18
+
19
+ .copied {
4
20
  color: var(--color-status-success);
5
- transition: all ease-in 0.1s;
21
+ transition: color ease-in 0.1s;
6
22
  }
@@ -24,12 +24,13 @@ export interface FilterFieldProps extends Omit<React.InputHTMLAttributes<HTMLInp
24
24
  label?: string;
25
25
  placeholder?: string;
26
26
  disabled?: boolean;
27
+ minWidth?: string;
27
28
  width?: string;
28
29
  maxWidth?: string;
29
30
  debounceTime?: number;
30
31
  }
31
32
  export declare const NUMBER_OPERATORS: Operator[];
32
- export declare function FilterField({ field, control, operator, value, onChange, operators, options, single, size, variant, label, placeholder, disabled, 'data-cy': dataCy, width, maxWidth, debounceTime, ...inputProps }: FilterFieldProps & {
33
+ export declare function FilterField({ field, control, operator, value, onChange, operators, options, single, size, variant, label, placeholder, disabled, 'data-cy': dataCy, minWidth, width, maxWidth, debounceTime, ...inputProps }: FilterFieldProps & {
33
34
  'data-cy'?: string;
34
35
  }): React.ReactElement;
35
36
  export {};
@@ -57,7 +57,7 @@ function OperatorDropdown({ value, onChange, operators, size = 'sm', disabled, }
57
57
  setActiveIndex(operators.indexOf(op));
58
58
  (_a = popRef.current) === null || _a === void 0 ? void 0 : _a.close();
59
59
  };
60
- return (_jsx(Popover, { ref: popRef, minWidth: "220px", trigger: (toggle, icon) => (_jsxs("button", { type: "button", onClick: toggle, disabled: disabled, "aria-label": "Skift operator", className: `${styles.operatorTrigger} ${styles[size]}`, children: [_jsx("span", { className: styles.operatorText, children: LABELS[value] }), icon] })), children: _jsx(Menu, { children: operators.map(op => {
60
+ return (_jsx(Popover, { ref: popRef, minWidth: "160px", trigger: (toggle, icon) => (_jsxs("button", { type: "button", onClick: toggle, disabled: disabled, "aria-label": "Skift operator", className: `${styles.operatorTrigger} ${styles[size]}`, children: [_jsx("span", { className: styles.operatorText, children: LABELS[value] }), icon] })), children: _jsx(Menu, { children: operators.map(op => {
61
61
  const selected = op === value;
62
62
  return (_jsx(Menu.Item, { active: selected, children: _jsxs("button", { type: "button", onClick: () => handleSelect(op), disabled: disabled, children: [_jsx("span", { style: { width: 16, display: 'inline-flex', justifyContent: 'center' }, children: selected ? _jsx(Check, { size: 16 }) : null }), LABELS[op]] }) }, op));
63
63
  }) }) }));
@@ -69,7 +69,7 @@ function isFilterActive(value) {
69
69
  return value.trim().length > 0;
70
70
  return value != null;
71
71
  }
72
- export function FilterField({ field, control, operator, value, onChange, operators, options = [], single = true, size = 'md', variant = 'surface', label, placeholder = 'Type value…', disabled, 'data-cy': dataCy, width, maxWidth, debounceTime = INPUT_DEBOUNCE_MS, ...inputProps }) {
72
+ export function FilterField({ field, control, operator, value, onChange, operators, options = [], single = true, size = 'md', variant = 'surface', label, placeholder = 'Type value…', disabled, 'data-cy': dataCy, minWidth, width, maxWidth, debounceTime = INPUT_DEBOUNCE_MS, ...inputProps }) {
73
73
  var _a, _b, _c, _d, _e, _f;
74
74
  const ops = useMemo(() => operators !== null && operators !== void 0 ? operators : DEFAULT_TEXT_OPERATORS, [operators]);
75
75
  const [selectedOperator, setSelectedOperator] = useState(operator);
@@ -171,7 +171,7 @@ export function FilterField({ field, control, operator, value, onChange, operato
171
171
  pendingValueRef.current = '';
172
172
  setLocalValue('');
173
173
  emit({ value: '' });
174
- } })) : (_jsx(Typeahead, { options: options, mode: single ? 'single' : 'multi', selectedValue: single ? ((_f = value) !== null && _f !== void 0 ? _f : null) : Array.isArray(value) ? value : [], onChange: v => emit({ value: v }), placeholder: placeholder, variant: "embedded", inputProps: {
174
+ } })) : (_jsx(Typeahead, { options: options, mode: single ? 'single' : 'multi', selectedValue: single ? ((_f = value) !== null && _f !== void 0 ? _f : null) : Array.isArray(value) ? value : [], onChange: v => emit({ value: v }), minWidth: minWidth, placeholder: placeholder, variant: "embedded", inputProps: {
175
175
  inputSize: size,
176
176
  fieldClassName: styles.embeddedInputField,
177
177
  inputClassName: styles.embeddedInputElement,
@@ -8,7 +8,7 @@ export type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size
8
8
  minWidth?: string | number;
9
9
  width?: string | number;
10
10
  maxWidth?: string | number;
11
- inputSize?: Size;
11
+ inputSize?: Exclude<Size, 'xl'>;
12
12
  variant?: InputVariant;
13
13
  onClear?: () => void;
14
14
  onButtonClick?: () => void;
@@ -59,6 +59,6 @@ export const Input = forwardRef(function Input({ label, error, helpText, orienta
59
59
  .filter(Boolean)
60
60
  .join(' '), "data-forminput": "field", "data-modified": modified ? 'true' : undefined, "aria-disabled": inputProps.disabled ? 'true' : undefined, ...(tooltip ? triggerProps : {}), children: [icon && _jsx("span", { className: styles.icon, children: icon }), startAdornment && _jsx("span", { className: styles.startAdornment, children: startAdornment }), _jsx("input", { ...inputProps, id: inputId, ref: mergeRefs(inputRef, ref), className: [styles.input, inputSize ? styles[inputSize] : '', inputClassName !== null && inputClassName !== void 0 ? inputClassName : '']
61
61
  .filter(Boolean)
62
- .join(' ') }), endAdornment && _jsx("span", { className: styles.endAdornment, children: endAdornment }), onClear && inputProps.value && _jsx(ClearButton, { onClick: onClear, absolute: true })] }), hasButton && (_jsxs(Button, { onClick: onButtonClick, className: styles.trailingButton, type: "button", variant: trailingButtonVariant, children: [buttonIcon !== null && buttonIcon !== void 0 ? buttonIcon : null, buttonLabel !== null && buttonLabel !== void 0 ? buttonLabel : null] }))] }) }));
62
+ .join(' ') }), endAdornment && _jsx("span", { className: styles.endAdornment, children: endAdornment }), onClear && inputProps.value && _jsx(ClearButton, { onClick: onClear, absolute: true })] }), hasButton && (_jsxs(Button, { onClick: onButtonClick, className: styles.trailingButton, type: "button", variant: trailingButtonVariant, size: inputSize, children: [buttonIcon !== null && buttonIcon !== void 0 ? buttonIcon : null, buttonLabel !== null && buttonLabel !== void 0 ? buttonLabel : null] }))] }) }));
63
63
  });
64
64
  Input.displayName = 'Input';
@@ -74,7 +74,7 @@
74
74
  }
75
75
 
76
76
  .withClear .input {
77
- padding-inline-end: calc(var(--spacing-xs) + 16px + var(--spacing-xs));
77
+ padding-inline-end: calc(var(--spacing-xxs) + 16px + var(--spacing-xxs));
78
78
  }
79
79
 
80
80
  /* Global focus reset - variants own visible focus treatment */
@@ -389,7 +389,7 @@
389
389
  display: flex;
390
390
  align-items: center;
391
391
  gap: 4px;
392
- margin-left: 4px;
392
+ margin-left: var(--spacing-xs);
393
393
  }
394
394
 
395
395
  .endAdornment {
@@ -25,10 +25,11 @@ interface TypeaheadProps<T> {
25
25
  inputProps?: Omit<InputProps, 'onChange' | 'value'>;
26
26
  inputSize?: InputProps['inputSize'];
27
27
  width?: InputProps['width'];
28
+ minWidth?: string;
28
29
  autoComplete?: InputProps['autoComplete'];
29
30
  autoCorrect?: InputProps['autoCorrect'];
30
31
  autoCapitalize?: InputProps['autoCapitalize'];
31
32
  spellCheck?: InputProps['spellCheck'];
32
33
  }
33
- export declare function Typeahead<T extends string | number>({ options, mode, multiValueDisplayMode, multiSelectedValuesDisplayMode, multiSelectedValueChipContent, selectedValue, onChange, placeholder, variant, disabled, fullWidth, onClear, emptyMessage, filterOptions, inputProps, inputSize, width, autoComplete, autoCorrect, autoCapitalize, spellCheck, }: TypeaheadProps<T>): React.ReactElement;
34
+ export declare function Typeahead<T extends string | number>({ options, mode, multiValueDisplayMode, multiSelectedValuesDisplayMode, multiSelectedValueChipContent, selectedValue, onChange, placeholder, variant, disabled, fullWidth, onClear, emptyMessage, filterOptions, inputProps, inputSize, width, minWidth, autoComplete, autoCorrect, autoCapitalize, spellCheck, }: TypeaheadProps<T>): React.ReactElement;
34
35
  export {};
@@ -6,7 +6,8 @@ import { Chip } from '../../../components/chip/Chip';
6
6
  import { Input } from '../../../components/forms/input/Input';
7
7
  import { Menu } from '../../../components/menu/Menu';
8
8
  import { Popover } from '../../../components/popover/Popover';
9
- export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'chips', multiSelectedValuesDisplayMode = 'hidden', multiSelectedValueChipContent = 'label', selectedValue = null, onChange, placeholder, variant = 'outlined', disabled = false, fullWidth = false, onClear, emptyMessage = 'Ingen resultater', filterOptions, inputProps, inputSize, width, autoComplete, autoCorrect, autoCapitalize, spellCheck, }) {
9
+ import styles from './Typeahead.module.css';
10
+ export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'chips', multiSelectedValuesDisplayMode = 'hidden', multiSelectedValueChipContent = 'label', selectedValue = null, onChange, placeholder, variant = 'outlined', disabled = false, fullWidth = false, onClear, emptyMessage = 'Ingen resultater', filterOptions, inputProps, inputSize, width, minWidth, autoComplete, autoCorrect, autoCapitalize, spellCheck, }) {
10
11
  var _a;
11
12
  const inputRef = useRef(null);
12
13
  const listboxRef = useRef(null);
@@ -43,7 +44,7 @@ export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'c
43
44
  const multiSelectionAdornment = mode === 'multi' && selectedOptions.length > 0 ? (multiValueDisplayMode === 'count' ? (_jsxs("span", { className: "dbc-muted-text dbc-sm-text", style: {
44
45
  whiteSpace: 'nowrap',
45
46
  flexShrink: 0,
46
- marginRight: 6,
47
+ marginRight: 'var(--spacing-xxs)',
47
48
  }, children: ["(", selectedOptions.length, ")"] })) : ((() => {
48
49
  const MAX_CHIPS = 2;
49
50
  const chipsToShow = selectedOptions.slice(0, MAX_CHIPS);
@@ -56,6 +57,7 @@ export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'c
56
57
  overflow: 'hidden',
57
58
  }, children: [chipsToShow.map(option => (_jsx(Chip, { size: "sm", type: "rounded", onClose: () => commitSelection(option), children: option.label }, option.value))), extraCount > 0 && (_jsxs("span", { className: "dbc-muted-text dbc-sm-text dbc-px-xxs", children: ["+", extraCount] }))] }));
58
59
  })())) : undefined;
60
+ const usesCountAdornment = mode === 'multi' && multiValueDisplayMode === 'count' && selectedOptions.length > 0;
59
61
  useEffect(() => {
60
62
  var _a;
61
63
  if (mode === 'multi') {
@@ -268,7 +270,7 @@ export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'c
268
270
  ? 8
269
271
  : 0,
270
272
  width: fullWidth ? '100%' : undefined,
271
- }, children: [_jsx(Popover, { open: open, onOpenChange: nextOpen => {
273
+ }, children: [_jsx(Popover, { open: open, minWidth: minWidth, onOpenChange: nextOpen => {
272
274
  setOpen(nextOpen);
273
275
  if (nextOpen) {
274
276
  if (mode === 'single' && selectedOption) {
@@ -329,7 +331,12 @@ export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'c
329
331
  if (e.defaultPrevented)
330
332
  return;
331
333
  handleKeyDown(e);
332
- }, placeholder: placeholder, variant: variant, inputSize: (_a = inputSize !== null && inputSize !== void 0 ? inputSize : inputProps === null || inputProps === void 0 ? void 0 : inputProps.inputSize) !== null && _a !== void 0 ? _a : 'md', width: width !== null && width !== void 0 ? width : inputProps === null || inputProps === void 0 ? void 0 : inputProps.width, autoComplete: (_b = autoComplete !== null && autoComplete !== void 0 ? autoComplete : inputProps === null || inputProps === void 0 ? void 0 : inputProps.autoComplete) !== null && _b !== void 0 ? _b : 'off', autoCorrect: (_c = autoCorrect !== null && autoCorrect !== void 0 ? autoCorrect : inputProps === null || inputProps === void 0 ? void 0 : inputProps.autoCorrect) !== null && _c !== void 0 ? _c : 'off', autoCapitalize: (_d = autoCapitalize !== null && autoCapitalize !== void 0 ? autoCapitalize : inputProps === null || inputProps === void 0 ? void 0 : inputProps.autoCapitalize) !== null && _d !== void 0 ? _d : 'none', spellCheck: (_e = spellCheck !== null && spellCheck !== void 0 ? spellCheck : inputProps === null || inputProps === void 0 ? void 0 : inputProps.spellCheck) !== null && _e !== void 0 ? _e : false, disabled: disabled, fullWidth: fullWidth, onClear: () => {
334
+ }, placeholder: placeholder, variant: variant, inputSize: (_a = inputSize !== null && inputSize !== void 0 ? inputSize : inputProps === null || inputProps === void 0 ? void 0 : inputProps.inputSize) !== null && _a !== void 0 ? _a : 'md', width: width !== null && width !== void 0 ? width : inputProps === null || inputProps === void 0 ? void 0 : inputProps.width, autoComplete: (_b = autoComplete !== null && autoComplete !== void 0 ? autoComplete : inputProps === null || inputProps === void 0 ? void 0 : inputProps.autoComplete) !== null && _b !== void 0 ? _b : 'off', autoCorrect: (_c = autoCorrect !== null && autoCorrect !== void 0 ? autoCorrect : inputProps === null || inputProps === void 0 ? void 0 : inputProps.autoCorrect) !== null && _c !== void 0 ? _c : 'off', autoCapitalize: (_d = autoCapitalize !== null && autoCapitalize !== void 0 ? autoCapitalize : inputProps === null || inputProps === void 0 ? void 0 : inputProps.autoCapitalize) !== null && _d !== void 0 ? _d : 'none', spellCheck: (_e = spellCheck !== null && spellCheck !== void 0 ? spellCheck : inputProps === null || inputProps === void 0 ? void 0 : inputProps.spellCheck) !== null && _e !== void 0 ? _e : false, disabled: disabled, fullWidth: fullWidth, inputClassName: [
335
+ inputProps === null || inputProps === void 0 ? void 0 : inputProps.inputClassName,
336
+ usesCountAdornment ? styles.inputWithoutStartPadding : '',
337
+ ]
338
+ .filter(Boolean)
339
+ .join(' '), onClear: () => {
333
340
  setInputValue('');
334
341
  setQuery('');
335
342
  setActiveIndex(-1);
@@ -346,7 +353,7 @@ export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'c
346
353
  ? Array.isArray(selectedValue) && selectedValue.includes(option.value)
347
354
  : option.value === selectedValue;
348
355
  const optionId = `${listboxId}-option-${index}`;
349
- return (mode === 'multi' ? (_jsx(Menu.CheckItem, { checked: isSelected, active: isActive, interactiveRef: node => {
356
+ return mode === 'multi' ? (_jsx(Menu.CheckItem, { checked: isSelected, active: isActive, interactiveRef: node => {
350
357
  optionRefs.current[index] = node;
351
358
  }, interactiveProps: {
352
359
  id: optionId,
@@ -360,7 +367,7 @@ export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'c
360
367
  optionRefs.current[index] = node;
361
368
  }, id: optionId, type: "button", role: "option", "aria-selected": isSelected, onMouseEnter: () => setActiveIndex(index), onMouseDown: e => {
362
369
  e.preventDefault();
363
- }, onClick: () => commitSelection(option), children: _jsx("span", { children: option.label }) }) }, option.value)));
370
+ }, onClick: () => commitSelection(option), children: _jsx("span", { children: option.label }) }) }, option.value));
364
371
  })) : (_jsx(Menu.Item, { disabled: true, children: emptyMessage })) }) }), mode === 'multi' &&
365
372
  multiSelectedValuesDisplayMode === 'below-input' &&
366
373
  selectedOptions.length > 0 && (_jsx("div", { style: {
@@ -0,0 +1,3 @@
1
+ .inputWithoutStartPadding {
2
+ padding-inline-start: 0;
3
+ }
@@ -1,5 +1,6 @@
1
1
  .link {
2
2
  display: inline-flex;
3
+ align-items: center;
3
4
  gap: var(--spacing-xs);
4
5
  position: relative;
5
6
  background: none;
@@ -11,6 +12,11 @@
11
12
  cursor: pointer;
12
13
  color: var(--color-brand);
13
14
  line-height: inherit;
15
+ transition:
16
+ color var(--transition-fast) var(--ease-standard),
17
+ transform 60ms ease,
18
+ opacity 60ms ease,
19
+ background-color var(--transition-fast) var(--ease-standard);
14
20
  }
15
21
 
16
22
  .link.secondary {
@@ -38,16 +44,23 @@
38
44
 
39
45
  .link {
40
46
  position: relative;
41
- display: inline-block;
47
+ display: inline-flex;
48
+ align-items: center;
42
49
  max-width: 100%;
43
50
  }
44
51
 
45
52
  .link {
46
53
  position: relative;
47
- display: inline-block;
54
+ display: inline-flex;
55
+ align-items: center;
48
56
  max-width: 100%;
49
57
  }
50
58
 
59
+ .content {
60
+ display: inline-flex;
61
+ align-items: center;
62
+ }
63
+
51
64
  .link::after {
52
65
  content: '';
53
66
  position: absolute;
@@ -64,6 +77,19 @@
64
77
  .link:hover::after {
65
78
  transform: scaleX(1);
66
79
  }
80
+
81
+ .link:active {
82
+ color: var(--color-link-hover);
83
+ }
84
+
85
+ .link:active::after {
86
+ transform: scaleX(1);
87
+ }
88
+
89
+ .link.block:active {
90
+ background-color: var(--color-bg-contextual);
91
+ }
92
+
67
93
  .link:focus-visible {
68
94
  outline: 2px solid var(--color-brand);
69
95
  outline-offset: 2px;
@@ -23,7 +23,6 @@
23
23
  text-align: start;
24
24
  text-decoration: none;
25
25
 
26
- /* choose your density */
27
26
  padding-block: var(--spacing-xs);
28
27
  padding-inline: var(--spacing-md);
29
28
 
@@ -123,7 +122,7 @@
123
122
  .active,
124
123
  .interactive.active,
125
124
  .row > .interactiveChild.active {
126
- background-color: var(--color-bg-hover-subtle);
125
+ background-color: var(--color-bg-toolbar-hover);
127
126
  }
128
127
 
129
128
  /* Selected item */
@@ -16,7 +16,7 @@
16
16
  border: 1px solid var(--color-border-default);
17
17
  background-color: var(--color-bg-surface);
18
18
  border-radius: var(--border-radius-default);
19
- padding: var(--spacing-sm) 0;
19
+ padding: 0;
20
20
  z-index: var(--z-popover);
21
21
  overflow: auto;
22
22
  box-shadow: var(--shadow-md);
@@ -3,7 +3,7 @@ import { InputVariant } from '../../components/forms/input/Input';
3
3
  import { Size } from '../../types/sizes.types';
4
4
  type SearchBoxProps<T extends Record<string, unknown>> = {
5
5
  inputWidth?: string | number;
6
- inputSize?: Size;
6
+ inputSize?: Exclude<Size, 'xl'>;
7
7
  variant?: InputVariant;
8
8
  result?: T[];
9
9
  resultKeys?: Array<Extract<keyof T, string>>;
@@ -111,6 +111,14 @@ export function SplitPaneProvider({ children, direction, initialPrimarySize, min
111
111
  minSecondarySize,
112
112
  containerRef,
113
113
  storageKey,
114
- }), [direction, primarySize, handleSizeChange, resetDefault, minPrimarySize, minSecondarySize, storageKey]);
114
+ }), [
115
+ direction,
116
+ primarySize,
117
+ handleSizeChange,
118
+ resetDefault,
119
+ minPrimarySize,
120
+ minSecondarySize,
121
+ storageKey,
122
+ ]);
115
123
  return _jsx(SplitPaneContext.Provider, { value: value, children: children });
116
124
  }
@@ -7,7 +7,7 @@ export type TabItem = {
7
7
  content: ReactNode;
8
8
  disabled?: boolean;
9
9
  hidden?: boolean;
10
- badge?: number;
10
+ badge?: number | string;
11
11
  };
12
12
  type TabsVariant = 'filled' | 'outlined';
13
13
  export interface TabsProps {
@@ -117,7 +117,7 @@ export function Tabs({ header, variant, panelStyle = false, tabs, value, default
117
117
  const selected = index === activeIndex;
118
118
  const tabDomId = `${uid}-tab-${String(tab.id)}`;
119
119
  const panelDomId = `${uid}-panel-${String(tab.id)}`;
120
- return (_jsx("div", { className: `${styles.tab} ${selected ? styles.active : ''}`, children: _jsxs("button", { id: tabDomId, type: "button", className: styles.tabButton, role: "tab", "aria-selected": selected, "aria-controls": panelDomId, tabIndex: selected ? 0 : -1, disabled: tab.disabled, onClick: () => setValue(tab.id), onKeyDown: e => onKeyDownTab(e, index), children: [tab.headerIcon ? _jsx("span", { className: styles.icon, children: tab.headerIcon }) : null, _jsx("span", { className: styles.label, children: tab.header }), tab.badge !== undefined && tab.badge > 0 ? (_jsx("span", { className: styles.badge, children: _jsx(Chip, { size: "sm", children: tab.badge.toLocaleString('da-DK') }) })) : null] }) }, tab.id));
120
+ return (_jsx("div", { className: `${styles.tab} ${selected ? styles.active : ''}`, children: _jsxs("button", { id: tabDomId, type: "button", className: styles.tabButton, role: "tab", "aria-selected": selected, "aria-controls": panelDomId, tabIndex: selected ? 0 : -1, disabled: tab.disabled, onClick: () => setValue(tab.id), onKeyDown: e => onKeyDownTab(e, index), children: [tab.headerIcon ? _jsx("span", { className: styles.icon, children: tab.headerIcon }) : null, _jsx("span", { className: styles.label, children: tab.header }), tab.badge ? (_jsx("span", { className: styles.badge, children: _jsx(Chip, { size: "sm", children: tab.badge.toLocaleString('da-DK') }) })) : null] }) }, tab.id));
121
121
  }) }), _jsx("div", { id: activeTab ? `${uid}-panel-${String(activeTab.id)}` : undefined, role: "tabpanel", "aria-labelledby": activeTab ? `${uid}-tab-${String(activeTab.id)}` : undefined, className: styles.tabContent, children: activeTab === null || activeTab === void 0 ? void 0 : activeTab.content })] })] }));
122
122
  }
123
123
  Tabs.Item = TabsItem;
@@ -1,5 +1,4 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // Toast.tsx
3
2
  import { X } from 'lucide-react';
4
3
  import styles from './Toast.module.css';
5
4
  import { Button } from '../button/Button';
@@ -7,6 +6,15 @@ import { Headline } from '../headline/Headline';
7
6
  export function Toast({ title, message, severity = 'info', action, onClose, }) {
8
7
  const showHeader = Boolean(title);
9
8
  const showMessage = Boolean(message);
10
- const CloseButton = onClose ? (_jsx(Button, { type: "button", variant: "inline", shape: "round", className: styles.closeButton, "aria-label": "Dismiss notification", onClick: onClose, children: _jsx(X, { className: styles.closeIcon, "aria-hidden": "true" }) })) : null;
11
- return (_jsxs("div", { className: `${styles.toast} ${styles[severity]}`, role: "status", children: [_jsxs("div", { className: styles.content, children: [showHeader && (_jsxs("div", { className: styles.row, children: [_jsx(Headline, { size: 4, severity: severity, disableMargin: true, children: title }), CloseButton] })), showMessage && (_jsxs("div", { className: styles.row, children: [_jsx("div", { className: styles.message, children: message }), !showHeader && CloseButton] }))] }), action && (_jsx("div", { className: styles.actions, children: _jsx(Button, { type: "button", variant: "primary", onClick: action.onClick, children: action.label }) }))] }));
9
+ const canClose = severity !== 'neutral';
10
+ const isDismissibleNeutral = severity === 'neutral' && Boolean(onClose);
11
+ const CloseButton = onClose && canClose ? (_jsx(Button, { type: "button", variant: "inline", shape: "round", className: styles.closeButton, "aria-label": "Dismiss notification", onClick: onClose, children: _jsx(X, { className: styles.closeIcon, "aria-hidden": "true" }) })) : null;
12
+ return (_jsxs("div", { className: `${styles.toast} ${styles[severity]} ${isDismissibleNeutral ? styles.dismissibleNeutral : ''}`, role: "status", onClick: isDismissibleNeutral ? onClose : undefined, onKeyDown: isDismissibleNeutral
13
+ ? e => {
14
+ if (e.key === 'Enter' || e.key === ' ') {
15
+ e.preventDefault();
16
+ onClose === null || onClose === void 0 ? void 0 : onClose();
17
+ }
18
+ }
19
+ : undefined, tabIndex: isDismissibleNeutral ? 0 : undefined, "aria-label": isDismissibleNeutral ? 'Luk notifikation' : undefined, children: [_jsxs("div", { className: styles.content, children: [showHeader && (_jsxs("div", { className: styles.row, children: [_jsx(Headline, { size: 4, severity: severity, disableMargin: true, children: title }), CloseButton] })), showMessage && (_jsxs("div", { className: styles.row, children: [_jsx("div", { className: styles.message, children: message }), !showHeader && CloseButton] }))] }), action && (_jsx("div", { className: styles.actions, children: _jsx(Button, { type: "button", variant: "primary", onClick: action.onClick, children: action.label }) }))] }));
12
20
  }
@@ -49,6 +49,36 @@
49
49
  border-left-color: var(--color-status-error-border);
50
50
  }
51
51
 
52
+ .neutral {
53
+ background-color: var(--color-bg-inverse);
54
+ color: var(--color-fg-inverse);
55
+ border-left-width: 0;
56
+ min-width: 50px;
57
+ max-width: 200px;
58
+ width: fit-content;
59
+ align-self: flex-end;
60
+ }
61
+
62
+ .dismissibleNeutral {
63
+ cursor: pointer;
64
+ transition:
65
+ background-color var(--transition-fast) var(--ease-standard),
66
+ transform 60ms ease;
67
+ }
68
+
69
+ .dismissibleNeutral:hover {
70
+ background-color: color-mix(in srgb, var(--color-bg-inverse) 92%, white);
71
+ }
72
+
73
+ .dismissibleNeutral:active {
74
+ transform: translateY(1px);
75
+ }
76
+
77
+ .dismissibleNeutral:focus-visible {
78
+ outline: 2px solid var(--color-fg-inverse);
79
+ outline-offset: 2px;
80
+ }
81
+
52
82
  /* Layout */
53
83
  .content {
54
84
  flex: 1;
@@ -20,4 +20,5 @@ type ToastProviderProps = {
20
20
  };
21
21
  export declare function ToastProvider({ children, defaultDuration, }: ToastProviderProps): React.ReactNode;
22
22
  export declare function useToast(): ToastContextValue;
23
+ export declare function useOptionalToast(): ToastContextValue | undefined;
23
24
  export {};
@@ -38,12 +38,14 @@ export function ToastProvider({ children, defaultDuration = 4000, }) {
38
38
  return id;
39
39
  }, [scheduleAutoDismiss]);
40
40
  const clearToasts = useCallback(() => {
41
- toasts.forEach(t => clearTimeoutForId(t.id));
41
+ timeouts.current.forEach(timeoutId => window.clearTimeout(timeoutId));
42
+ timeouts.current.clear();
42
43
  setToasts([]);
43
- }, [toasts]);
44
+ }, []);
44
45
  useEffect(() => () => {
45
- toasts.forEach(t => clearTimeoutForId(t.id));
46
- }, [toasts]);
46
+ timeouts.current.forEach(timeoutId => window.clearTimeout(timeoutId));
47
+ timeouts.current.clear();
48
+ }, []);
47
49
  return (_jsxs(ToastContext.Provider, { value: { showToast, hideToast, clearToasts }, children: [children, toasts.length > 0 && (_jsx("div", { className: styles.container, "aria-live": "polite", "aria-atomic": "false", children: toasts.map(toast => {
48
50
  var _a;
49
51
  return (_jsx(Toast, { title: toast.title, message: toast.message, severity: (_a = toast.severity) !== null && _a !== void 0 ? _a : 'info', action: toast.action && {
@@ -63,3 +65,6 @@ export function useToast() {
63
65
  }
64
66
  return ctx;
65
67
  }
68
+ export function useOptionalToast() {
69
+ return useContext(ToastContext);
70
+ }
@@ -67,6 +67,7 @@ html[data-theme='dark'] {
67
67
  --color-bg-surface: #111827;
68
68
  --color-bg-surface-subtle: var(--dbc-surface-muted);
69
69
  --color-bg-surface-strong: var(--dbc-surface-strong);
70
+ --color-bg-inverse: #2b3444;
70
71
 
71
72
  --color-bg-toolbar: #1f2937;
72
73
  --color-bg-toolbar-hover: #263244;
@@ -95,6 +96,7 @@ html[data-theme='dark'] {
95
96
  --color-fg-default: var(--dbc-neutral-900);
96
97
  --color-fg-muted: var(--dbc-neutral-700);
97
98
  --color-fg-subtle: var(--dbc-neutral-600);
99
+ --color-fg-inverse: #f9fafb;
98
100
 
99
101
  --color-fg-on-strong: #111827;
100
102
  --color-fg-on-brand: #111827;
@@ -67,6 +67,7 @@ html[data-theme='light'] {
67
67
  --color-bg-surface: #ffffff;
68
68
  --color-bg-surface-subtle: var(--dbc-surface-muted);
69
69
  --color-bg-surface-strong: var(--dbc-surface-strong);
70
+ --color-bg-inverse: #1f2937;
70
71
 
71
72
  --color-bg-toolbar: #f3f4f6;
72
73
  --color-bg-toolbar-hover: #eef0f3;
@@ -95,6 +96,7 @@ html[data-theme='light'] {
95
96
  --color-fg-default: var(--dbc-neutral-900);
96
97
  --color-fg-muted: var(--dbc-neutral-700);
97
98
  --color-fg-subtle: var(--dbc-neutral-600);
99
+ --color-fg-inverse: #ffffff;
98
100
 
99
101
  --color-fg-on-strong: #ffffff;
100
102
  --color-fg-on-brand: #ffffff;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbcdk/react-components",
3
- "version": "0.0.51",
3
+ "version": "0.0.54",
4
4
  "description": "Reusable React components for DBC projects",
5
5
  "license": "ISC",
6
6
  "author": "",