@dbcdk/react-components 0.0.52 → 0.0.55

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 (35) 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/card/Card.d.ts +1 -1
  8. package/dist/components/card/Card.js +0 -2
  9. package/dist/components/card/Card.module.css +0 -5
  10. package/dist/components/code-block/CodeBlock.module.css +5 -3
  11. package/dist/components/copy-button/CopyButton.d.ts +3 -1
  12. package/dist/components/copy-button/CopyButton.js +34 -11
  13. package/dist/components/copy-button/CopyButton.module.css +20 -4
  14. package/dist/components/filter-field/FilterField.d.ts +2 -1
  15. package/dist/components/filter-field/FilterField.js +3 -3
  16. package/dist/components/forms/input/Input.d.ts +1 -1
  17. package/dist/components/forms/input/Input.js +1 -1
  18. package/dist/components/forms/input/Input.module.css +1 -1
  19. package/dist/components/forms/typeahead/Typeahead.d.ts +2 -1
  20. package/dist/components/forms/typeahead/Typeahead.js +2 -2
  21. package/dist/components/hyperlink/Hyperlink.module.css +28 -2
  22. package/dist/components/menu/Menu.module.css +15 -8
  23. package/dist/components/popover/Popover.js +18 -4
  24. package/dist/components/popover/Popover.module.css +4 -2
  25. package/dist/components/search-box/SearchBox.d.ts +1 -1
  26. package/dist/components/split-pane/provider/SplitPaneContext.js +9 -1
  27. package/dist/components/tabs/Tabs.d.ts +1 -1
  28. package/dist/components/tabs/Tabs.js +1 -1
  29. package/dist/components/toast/Toast.js +11 -3
  30. package/dist/components/toast/Toast.module.css +34 -0
  31. package/dist/components/toast/provider/ToastProvider.d.ts +1 -0
  32. package/dist/components/toast/provider/ToastProvider.js +9 -4
  33. package/dist/styles/themes/dbc/dark.css +2 -0
  34. package/dist/styles/themes/dbc/light.css +2 -0
  35. 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,7 +1,7 @@
1
1
  import type { JSX, ReactNode } from 'react';
2
2
  import type { Severity } from '../../constants/severity.types';
3
3
  import { CardMeta, CardMetaRow } from './components/CardMeta';
4
- type CardVariant = 'default' | 'subtle' | 'strong';
4
+ type CardVariant = 'default' | 'subtle';
5
5
  type CardSize = 'sm' | 'md' | 'lg';
6
6
  type CardImagePlacement = 'left' | 'right' | 'top';
7
7
  export interface CardProps {
@@ -19,8 +19,6 @@ function getVariantClass(variant, s) {
19
19
  switch (variant) {
20
20
  case 'subtle':
21
21
  return s.variantSubtle;
22
- case 'strong':
23
- return s.variantStrong;
24
22
  case 'default':
25
23
  default:
26
24
  return s.variantDefault;
@@ -31,11 +31,6 @@
31
31
  background-color: var(--card-bg-subtle, var(--color-bg-contextual-subtle));
32
32
  }
33
33
 
34
- .variantStrong {
35
- background-color: var(--card-bg-strong, var(--color-bg-surface-strong));
36
- color: var(--card-fg-on-strong, var(--color-fg-on-strong));
37
- }
38
-
39
34
  /* SIZE VARIANTS (define vars once; inner uses them) */
40
35
  .sm {
41
36
  --card-pad: var(--spacing-md);
@@ -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 */
@@ -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 {};
@@ -7,7 +7,7 @@ import { Input } from '../../../components/forms/input/Input';
7
7
  import { Menu } from '../../../components/menu/Menu';
8
8
  import { Popover } from '../../../components/popover/Popover';
9
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, autoComplete, autoCorrect, autoCapitalize, spellCheck, }) {
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, }) {
11
11
  var _a;
12
12
  const inputRef = useRef(null);
13
13
  const listboxRef = useRef(null);
@@ -270,7 +270,7 @@ export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'c
270
270
  ? 8
271
271
  : 0,
272
272
  width: fullWidth ? '100%' : undefined,
273
- }, children: [_jsx(Popover, { open: open, onOpenChange: nextOpen => {
273
+ }, children: [_jsx(Popover, { open: open, minWidth: minWidth, onOpenChange: nextOpen => {
274
274
  setOpen(nextOpen);
275
275
  if (nextOpen) {
276
276
  if (mode === 'single' && selectedOption) {
@@ -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
 
@@ -96,15 +95,10 @@
96
95
  cursor: pointer;
97
96
  }
98
97
 
99
- .row + .row .interactive,
100
- .row + .row > .interactiveChild {
101
- box-shadow: inset 0 1px 0 var(--color-border-subtle);
102
- }
103
-
104
98
  /* Hover: support both cases (interactive element, or wrapper child) */
105
99
  .interactive:hover:not(.selected),
106
100
  .row:hover > .interactiveChild:not(.selected) {
107
- background-color: var(--color-bg-hover-subtle);
101
+ background-color: var(--color-bg-toolbar-hover);
108
102
  }
109
103
 
110
104
  /* Focus ring: support both cases */
@@ -123,7 +117,7 @@
123
117
  .active,
124
118
  .interactive.active,
125
119
  .row > .interactiveChild.active {
126
- background-color: var(--color-bg-hover-subtle);
120
+ background-color: var(--color-bg-toolbar-hover);
127
121
  }
128
122
 
129
123
  /* Selected item */
@@ -135,6 +129,19 @@
135
129
  color: var(--color-fg-default);
136
130
  }
137
131
 
132
+ .selected:hover,
133
+ .interactive.selected:hover,
134
+ .interactive[aria-selected='true']:hover,
135
+ .row:hover > .interactiveChild.selected,
136
+ .row:hover > .interactiveChild[aria-selected='true'],
137
+ .active.selected,
138
+ .interactive.active.selected,
139
+ .interactive.active[aria-selected='true'],
140
+ .row > .interactiveChild.active.selected,
141
+ .row > .interactiveChild.active[aria-selected='true'] {
142
+ background-color: var(--color-bg-selected-hover);
143
+ }
144
+
138
145
  /* Checked (legacy support; kept in case any interactive element still uses aria-checked) */
139
146
  .interactive[aria-checked='true'],
140
147
  .row > .interactiveChild[aria-checked='true'] {
@@ -66,7 +66,8 @@ export const Popover = forwardRef(function Popover({ trigger: Trigger, children,
66
66
  setOpen(false);
67
67
  }, [setOpen]);
68
68
  const togglePopover = useCallback((e) => {
69
- triggerElRef.current = e.currentTarget;
69
+ var _a;
70
+ triggerElRef.current = (_a = containerRef.current) !== null && _a !== void 0 ? _a : e.currentTarget;
70
71
  if (isOpen)
71
72
  closePopover('trigger');
72
73
  else
@@ -86,11 +87,14 @@ export const Popover = forwardRef(function Popover({ trigger: Trigger, children,
86
87
  if (!triggerEl)
87
88
  return;
88
89
  const triggerRect = triggerEl.getBoundingClientRect();
90
+ const overlayWidthBuffer = 8;
89
91
  // Only compute a forced width when requested.
90
92
  let forcedWidthPx = null;
91
93
  if (matchTriggerWidth) {
92
94
  const minWidthPx = parseMinWidthPx(minWidth, triggerEl);
93
- forcedWidthPx = Math.max(triggerRect.width, minWidthPx || 0);
95
+ // Make the overlay slightly wider than the trigger so it reads as a
96
+ // floating layer instead of blending into adjacent form fields.
97
+ forcedWidthPx = Math.max(triggerRect.width + overlayWidthBuffer, minWidthPx || 0);
94
98
  setTriggerWidth(forcedWidthPx);
95
99
  }
96
100
  else {
@@ -154,15 +158,16 @@ export const Popover = forwardRef(function Popover({ trigger: Trigger, children,
154
158
  // eslint-disable-next-line react-hooks/exhaustive-deps
155
159
  }, [isOpen]);
156
160
  useEffect(() => {
157
- var _a;
161
+ var _a, _b;
158
162
  if (!isOpen)
159
163
  return;
160
164
  const content = contentRef.current;
161
165
  if (!content)
162
166
  return;
167
+ const triggerEl = (_a = triggerElRef.current) !== null && _a !== void 0 ? _a : containerRef.current;
163
168
  if (autoFocusContent) {
164
169
  const focusables = getFocusable(content);
165
- (_a = focusables[0]) === null || _a === void 0 ? void 0 : _a.focus();
170
+ (_b = focusables[0]) === null || _b === void 0 ? void 0 : _b.focus();
166
171
  }
167
172
  const handlePointerDownCapture = (e) => {
168
173
  const container = containerRef.current;
@@ -179,15 +184,24 @@ export const Popover = forwardRef(function Popover({ trigger: Trigger, children,
179
184
  closePopover('escape');
180
185
  };
181
186
  const handleReposition = () => computeAndSetPosition();
187
+ const resizeObserver = typeof ResizeObserver !== 'undefined'
188
+ ? new ResizeObserver(() => {
189
+ handleReposition();
190
+ })
191
+ : null;
182
192
  document.addEventListener('pointerdown', handlePointerDownCapture, true);
183
193
  document.addEventListener('keydown', handleEscape, true);
184
194
  window.addEventListener('resize', handleReposition);
185
195
  window.addEventListener('scroll', handleReposition, true);
196
+ resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.observe(content);
197
+ if (triggerEl)
198
+ resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.observe(triggerEl);
186
199
  return () => {
187
200
  document.removeEventListener('pointerdown', handlePointerDownCapture, true);
188
201
  document.removeEventListener('keydown', handleEscape, true);
189
202
  window.removeEventListener('resize', handleReposition);
190
203
  window.removeEventListener('scroll', handleReposition, true);
204
+ resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.disconnect();
191
205
  };
192
206
  }, [isOpen, closePopover, computeAndSetPosition, autoFocusContent]);
193
207
  useEffect(() => {
@@ -16,10 +16,12 @@
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
- box-shadow: var(--shadow-md);
22
+ box-shadow:
23
+ 0 0 0 1px color-mix(in srgb, var(--color-border-subtle) 55%, transparent),
24
+ var(--shadow-lg);
23
25
  }
24
26
 
25
27
  .content[hidden] {
@@ -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
  }
@@ -37,6 +37,10 @@
37
37
  border-left-color: var(--color-status-info-border);
38
38
  }
39
39
 
40
+ .brand {
41
+ border-left-color: var(--color-brand);
42
+ }
43
+
40
44
  .success {
41
45
  border-left-color: var(--color-status-success-border);
42
46
  }
@@ -49,6 +53,36 @@
49
53
  border-left-color: var(--color-status-error-border);
50
54
  }
51
55
 
56
+ .neutral {
57
+ background-color: var(--color-bg-inverse);
58
+ color: var(--color-fg-inverse);
59
+ border-left-width: 0;
60
+ min-width: 50px;
61
+ max-width: 200px;
62
+ width: fit-content;
63
+ align-self: flex-end;
64
+ }
65
+
66
+ .dismissibleNeutral {
67
+ cursor: pointer;
68
+ transition:
69
+ background-color var(--transition-fast) var(--ease-standard),
70
+ transform 60ms ease;
71
+ }
72
+
73
+ .dismissibleNeutral:hover {
74
+ background-color: color-mix(in srgb, var(--color-bg-inverse) 92%, white);
75
+ }
76
+
77
+ .dismissibleNeutral:active {
78
+ transform: translateY(1px);
79
+ }
80
+
81
+ .dismissibleNeutral:focus-visible {
82
+ outline: 2px solid var(--color-fg-inverse);
83
+ outline-offset: 2px;
84
+ }
85
+
52
86
  /* Layout */
53
87
  .content {
54
88
  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.52",
3
+ "version": "0.0.55",
4
4
  "description": "Reusable React components for DBC projects",
5
5
  "license": "ISC",
6
6
  "author": "",