@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.
- package/dist/components/__stories__/story-components/ComponentSizes.d.ts +2 -0
- package/dist/components/__stories__/story-components/ComponentSizes.js +26 -0
- package/dist/components/__stories__/story-components/Elevation.d.ts +2 -0
- package/dist/components/__stories__/story-components/Elevation.js +49 -0
- package/dist/components/__stories__/story-components/Spacing.d.ts +5 -1
- package/dist/components/__stories__/story-components/Spacing.js +3 -3
- package/dist/components/card/Card.d.ts +1 -1
- package/dist/components/card/Card.js +0 -2
- package/dist/components/card/Card.module.css +0 -5
- package/dist/components/code-block/CodeBlock.module.css +5 -3
- package/dist/components/copy-button/CopyButton.d.ts +3 -1
- package/dist/components/copy-button/CopyButton.js +34 -11
- package/dist/components/copy-button/CopyButton.module.css +20 -4
- package/dist/components/filter-field/FilterField.d.ts +2 -1
- package/dist/components/filter-field/FilterField.js +3 -3
- package/dist/components/forms/input/Input.d.ts +1 -1
- package/dist/components/forms/input/Input.js +1 -1
- package/dist/components/forms/input/Input.module.css +1 -1
- package/dist/components/forms/typeahead/Typeahead.d.ts +2 -1
- package/dist/components/forms/typeahead/Typeahead.js +2 -2
- package/dist/components/hyperlink/Hyperlink.module.css +28 -2
- package/dist/components/menu/Menu.module.css +15 -8
- package/dist/components/popover/Popover.js +18 -4
- package/dist/components/popover/Popover.module.css +4 -2
- package/dist/components/search-box/SearchBox.d.ts +1 -1
- package/dist/components/split-pane/provider/SplitPaneContext.js +9 -1
- package/dist/components/tabs/Tabs.d.ts +1 -1
- package/dist/components/tabs/Tabs.js +1 -1
- package/dist/components/toast/Toast.js +11 -3
- package/dist/components/toast/Toast.module.css +34 -0
- package/dist/components/toast/provider/ToastProvider.d.ts +1 -0
- package/dist/components/toast/provider/ToastProvider.js +9 -4
- package/dist/styles/themes/dbc/dark.css +2 -0
- package/dist/styles/themes/dbc/light.css +2 -0
- package/package.json +1 -1
|
@@ -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,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
|
+
}
|
|
@@ -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: "
|
|
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
|
|
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'
|
|
4
|
+
type CardVariant = 'default' | 'subtle';
|
|
5
5
|
type CardSize = 'sm' | 'md' | 'lg';
|
|
6
6
|
type CardImagePlacement = 'left' | 'right' | 'top';
|
|
7
7
|
export interface CardProps {
|
|
@@ -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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
44
|
-
return (_jsx(Hyperlink, { asChild: true, variant: "
|
|
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:
|
|
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 ? '' : ((
|
|
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:
|
|
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
|
-
.
|
|
2
|
-
|
|
3
|
-
|
|
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:
|
|
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: "
|
|
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';
|
|
@@ -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-
|
|
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-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
(
|
|
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:
|
|
19
|
+
padding: 0;
|
|
20
20
|
z-index: var(--z-popover);
|
|
21
21
|
overflow: auto;
|
|
22
|
-
box-shadow:
|
|
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
|
-
}), [
|
|
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
|
}
|
|
@@ -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
|
|
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
|
|
11
|
-
|
|
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
|
-
|
|
41
|
+
timeouts.current.forEach(timeoutId => window.clearTimeout(timeoutId));
|
|
42
|
+
timeouts.current.clear();
|
|
42
43
|
setToasts([]);
|
|
43
|
-
}, [
|
|
44
|
+
}, []);
|
|
44
45
|
useEffect(() => () => {
|
|
45
|
-
|
|
46
|
-
|
|
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;
|