@dbcdk/react-components 0.0.87 → 0.0.89

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 (66) hide show
  1. package/dist/components/accordion/Accordion.d.ts +1 -0
  2. package/dist/components/accordion/components/AccordionRow.js +1 -1
  3. package/dist/components/button/Button.js +8 -1
  4. package/dist/components/button/Button.module.css +2 -1
  5. package/dist/components/card/Card.d.ts +1 -5
  6. package/dist/components/card/Card.js +29 -4
  7. package/dist/components/card/Card.module.css +85 -98
  8. package/dist/components/card-container/CardContainer.d.ts +2 -1
  9. package/dist/components/card-container/CardContainer.js +2 -2
  10. package/dist/components/card-container/CardContainer.module.css +10 -9
  11. package/dist/components/clear-button/ClearButton.d.ts +2 -1
  12. package/dist/components/clear-button/ClearButton.js +6 -2
  13. package/dist/components/clear-button/ClearButton.module.css +6 -0
  14. package/dist/components/divider/Divider.d.ts +5 -0
  15. package/dist/components/divider/Divider.js +12 -0
  16. package/dist/components/forms/input/Input.d.ts +2 -1
  17. package/dist/components/forms/input/Input.js +6 -2
  18. package/dist/components/forms/input/Input.module.css +32 -0
  19. package/dist/components/forms/select/Select.d.ts +2 -1
  20. package/dist/components/forms/select/Select.js +2 -2
  21. package/dist/components/forms/typeahead/Typeahead.d.ts +2 -1
  22. package/dist/components/forms/typeahead/Typeahead.js +180 -118
  23. package/dist/components/forms/typeahead/Typeahead.module.css +4 -0
  24. package/dist/components/grid/Grid.d.ts +21 -0
  25. package/dist/components/grid/Grid.js +21 -0
  26. package/dist/components/grid/Grid.module.css +20 -0
  27. package/dist/components/headline/Headline.d.ts +3 -3
  28. package/dist/components/headline/Headline.js +6 -6
  29. package/dist/components/headline/Headline.module.css +25 -6
  30. package/dist/components/nav-bar/NavBar.module.css +6 -2
  31. package/dist/components/overlay/modal/Modal.d.ts +2 -1
  32. package/dist/components/overlay/modal/Modal.js +5 -3
  33. package/dist/components/overlay/modal/provider/ModalProvider.js +2 -0
  34. package/dist/components/overlay/side-panel/SidePanel.d.ts +2 -1
  35. package/dist/components/overlay/side-panel/SidePanel.js +2 -2
  36. package/dist/components/page/Page.d.ts +5 -1
  37. package/dist/components/page/Page.js +6 -2
  38. package/dist/components/page/Page.module.css +54 -4
  39. package/dist/components/panel/Panel.d.ts +2 -1
  40. package/dist/components/panel/Panel.js +2 -2
  41. package/dist/components/popover/Popover.js +3 -3
  42. package/dist/components/stack/Stack.d.ts +16 -0
  43. package/dist/components/stack/Stack.js +19 -0
  44. package/dist/components/state-page/StatePage.d.ts +2 -1
  45. package/dist/components/state-page/StatePage.js +2 -2
  46. package/dist/components/table/Table.d.ts +1 -1
  47. package/dist/components/table/Table.js +22 -4
  48. package/dist/components/table/Table.module.css +14 -0
  49. package/dist/components/table/Table.types.d.ts +1 -0
  50. package/dist/components/tabs/Tabs.d.ts +3 -1
  51. package/dist/components/tabs/Tabs.js +4 -2
  52. package/dist/components/tabs/Tabs.module.css +4 -0
  53. package/dist/components/theme-button/ThemeButton.d.ts +1 -0
  54. package/dist/components/theme-button/ThemeButton.js +5 -1
  55. package/dist/components/toast/Toast.d.ts +2 -1
  56. package/dist/components/toast/Toast.js +2 -2
  57. package/dist/hooks/useViewportFill.d.ts +2 -6
  58. package/dist/hooks/useViewportFill.js +29 -24
  59. package/dist/index.d.ts +4 -0
  60. package/dist/index.js +4 -0
  61. package/dist/styles/css-helper-classes/flex.css +12 -0
  62. package/dist/styles/css-helper-classes/spacing.css +5 -0
  63. package/dist/styles/styles.css +154 -66
  64. package/dist/styles/themes/dbc/colors.css +10 -0
  65. package/dist/styles.css +154 -66
  66. package/package.json +1 -1
@@ -1,13 +1,25 @@
1
1
  .headlineContainer {
2
2
  display: flex;
3
- align-items: center;
3
+ align-items: flex-start;
4
4
  gap: var(--spacing-lg);
5
- flex-wrap: wrap;
6
5
  max-width: 100%;
6
+ min-width: 0;
7
+ }
8
+
9
+ .headlineBlock {
10
+ display: flex;
11
+ flex-direction: column;
7
12
  flex: 1 1 auto;
8
13
  min-width: 0;
9
14
  }
10
15
 
16
+ .headlineRow {
17
+ display: flex;
18
+ align-items: center;
19
+ gap: var(--spacing-lg);
20
+ min-width: 0;
21
+ }
22
+
11
23
  /* Base headline */
12
24
  .headline {
13
25
  position: relative;
@@ -58,15 +70,22 @@
58
70
  pointer-events: none;
59
71
  }
60
72
 
61
- /* Subheadline */
62
- .subHeadline {
63
- color: inherit;
64
- opacity: 0.85;
73
+ /* Subheader */
74
+ .subheader {
75
+ color: var(--color-fg-muted);
65
76
  font-size: var(--font-size-md);
66
77
  margin-block-start: calc(var(--spacing-2xs) * -1);
67
78
  line-height: var(--line-height-normal);
68
79
  }
69
80
 
81
+ .addition {
82
+ display: flex;
83
+ flex: 1 1 auto;
84
+ min-width: 0;
85
+ align-items: center;
86
+ justify-content: flex-end;
87
+ }
88
+
70
89
  /* Icon */
71
90
  .icon {
72
91
  flex: 0 0 auto;
@@ -156,7 +156,6 @@
156
156
 
157
157
  /* Hover + focus */
158
158
  .link:hover {
159
- background-color: var(--color-bg-hover-subtle);
160
159
  color: var(--color-fg-default);
161
160
  }
162
161
  .link:focus-visible {
@@ -178,13 +177,18 @@
178
177
  inset-block-end: 0;
179
178
  inline-size: 0;
180
179
  block-size: 2px;
181
- background-color: var(--color-brand);
180
+ background-color: var(--color-border-strong);
182
181
  transition: inline-size var(--transition-fast) var(--ease-standard);
183
182
  }
184
183
 
184
+ .link:hover::after {
185
+ inline-size: 100%;
186
+ }
187
+
185
188
  .link.active::after,
186
189
  .link[aria-current='page']::after {
187
190
  inline-size: 100%;
191
+ background-color: var(--color-brand);
188
192
  }
189
193
 
190
194
  /* Icons/images inside links */
@@ -13,6 +13,7 @@ export type ModalProps = {
13
13
  isOpen: boolean;
14
14
  onRequestClose: () => void;
15
15
  header?: ReactNode;
16
+ subheader?: ReactNode;
16
17
  content?: ReactNode;
17
18
  children?: ReactNode;
18
19
  primaryAction?: ModalActionConfig;
@@ -23,4 +24,4 @@ export type ModalProps = {
23
24
  dataCy?: string;
24
25
  width?: number | string;
25
26
  };
26
- export declare function Modal({ isOpen, onRequestClose, header, content, children, primaryAction, secondaryAction, closeOnOverlayClick, severity, disableContentSpacing, isLoading, dataCy, width, }: ModalProps): React.ReactNode;
27
+ export declare function Modal({ isOpen, onRequestClose, header, subheader, content, children, primaryAction, secondaryAction, closeOnOverlayClick, severity, disableContentSpacing, isLoading, dataCy, width, }: ModalProps): React.ReactNode;
@@ -6,7 +6,7 @@ import { createPortal } from 'react-dom';
6
6
  import { Button } from '../../../components/button/Button';
7
7
  import { Headline } from '../../../components/headline/Headline';
8
8
  import styles from './Modal.module.css';
9
- export function Modal({ isOpen, onRequestClose, header, content, children, primaryAction, secondaryAction, closeOnOverlayClick = true, severity, disableContentSpacing = false, isLoading, dataCy, width, }) {
9
+ export function Modal({ isOpen, onRequestClose, header, subheader, content, children, primaryAction, secondaryAction, closeOnOverlayClick = true, severity, disableContentSpacing = false, isLoading, dataCy, width, }) {
10
10
  const titleId = useId();
11
11
  const dialogRef = useRef(null);
12
12
  const lastActiveElementRef = useRef(null);
@@ -103,11 +103,13 @@ export function Modal({ isOpen, onRequestClose, header, content, children, prima
103
103
  const stopPropagation = e => {
104
104
  e.stopPropagation();
105
105
  };
106
- const resolvedSecondaryAction = secondaryAction !== null && secondaryAction !== void 0 ? secondaryAction : (primaryAction ? { label: 'Luk', onClick: onRequestClose } : undefined);
106
+ const resolvedSecondaryAction = primaryAction || secondaryAction
107
+ ? { label: 'Luk', onClick: onRequestClose, ...secondaryAction }
108
+ : undefined;
107
109
  const shouldRenderFooter = Boolean(primaryAction || resolvedSecondaryAction);
108
110
  const body = children !== null && children !== void 0 ? children : content;
109
111
  const resolvedWidth = typeof width === 'number' ? `${width}px` : typeof width === 'string' ? width : undefined;
110
112
  return createPortal(_jsx("div", { className: styles.overlay, onClick: handleOverlayClick, children: _jsxs("div", { "data-cy": dataCy, ref: dialogRef, className: `${styles.modal} ${disableContentSpacing ? '' : styles.contentSpacing}`, style: resolvedWidth
111
113
  ? { ['--modal-width']: resolvedWidth }
112
- : undefined, onClick: stopPropagation, role: "dialog", "aria-modal": "true", "aria-labelledby": header ? titleId : undefined, tabIndex: -1, children: [_jsxs("div", { className: styles.header, children: [header && (_jsx(Headline, { severity: severity, size: 3, disableMargin: true, children: header })), _jsx(Button, { type: "button", variant: "inline", onClick: () => onRequestCloseRef.current(), "aria-label": "Luk", shape: "round", icon: _jsx(X, {}) })] }), _jsx("div", { className: styles.body, children: body }), shouldRenderFooter && (_jsxs("div", { className: styles.footer, children: [resolvedSecondaryAction && (_jsxs(Button, { type: "button", onClick: resolvedSecondaryAction.onClick, disabled: isLoading, children: [resolvedSecondaryAction.icon && (_jsx("span", { className: styles.icon, children: resolvedSecondaryAction.icon })), _jsx("span", { children: resolvedSecondaryAction.label })] })), primaryAction && (_jsxs(Button, { type: "button", variant: primaryAction.severity || 'primary', onClick: primaryAction.onClick, disabled: primaryAction.disabled || isLoading, loading: isLoading, children: [primaryAction.icon && _jsx("span", { className: styles.icon, children: primaryAction.icon }), _jsx("span", { children: primaryAction.label })] }))] }))] }) }), document.body);
114
+ : undefined, onClick: stopPropagation, role: "dialog", "aria-modal": "true", "aria-labelledby": header ? titleId : undefined, tabIndex: -1, children: [_jsxs("div", { className: styles.header, children: [header && (_jsx(Headline, { severity: severity, size: 3, disableMargin: true, subheader: subheader, children: header })), _jsx(Button, { type: "button", variant: "inline", onClick: () => onRequestCloseRef.current(), "aria-label": "Luk", shape: "round", icon: _jsx(X, {}) })] }), _jsx("div", { className: styles.body, children: body }), shouldRenderFooter && (_jsxs("div", { className: styles.footer, children: [resolvedSecondaryAction && (_jsxs(Button, { type: "button", onClick: resolvedSecondaryAction.onClick, disabled: isLoading, children: [resolvedSecondaryAction.icon && (_jsx("span", { className: styles.icon, children: resolvedSecondaryAction.icon })), _jsx("span", { children: resolvedSecondaryAction.label })] })), primaryAction && (_jsxs(Button, { type: "button", variant: primaryAction.severity || 'primary', onClick: primaryAction.onClick, disabled: primaryAction.disabled || isLoading, loading: isLoading, children: [primaryAction.icon && _jsx("span", { className: styles.icon, children: primaryAction.icon }), _jsx("span", { children: primaryAction.label })] }))] }))] }) }), document.body);
113
115
  }
@@ -47,12 +47,14 @@ export function ModalProvider({ children }) {
47
47
  severity: confirmConfig.confirmButtonSeverity || 'primary',
48
48
  onClick: () => {
49
49
  resolvePending(true);
50
+ closeModal();
50
51
  },
51
52
  },
52
53
  secondaryAction: {
53
54
  label: cancelLabel,
54
55
  onClick: () => {
55
56
  resolvePending(false);
57
+ closeModal();
56
58
  },
57
59
  },
58
60
  });
@@ -3,6 +3,7 @@ import { Severity } from '../../../constants/severity.types';
3
3
  interface SidePanelProps {
4
4
  children?: ReactNode;
5
5
  header: ReactNode;
6
+ subheader?: ReactNode;
6
7
  headerAddition?: ReactNode;
7
8
  actions?: ReactNode;
8
9
  onClose: (event?: React.MouseEvent<HTMLButtonElement> | React.MouseEvent<HTMLDivElement>) => void;
@@ -20,5 +21,5 @@ interface SidePanelProps {
20
21
  onCloseDetails?: () => void;
21
22
  detailsHeaderAddition?: ReactNode;
22
23
  }
23
- export declare function SidePanel({ isOpen, onClose, children, header, headerAddition, actions, showBackdrop, severity, showHeaderMarker, width, details, detailsHeader, detailsWidth, onCloseDetails, detailsHeaderAddition, ...props }: SidePanelProps & HTMLAttributes<HTMLElement>): ReactNode;
24
+ export declare function SidePanel({ isOpen, onClose, children, header, subheader, headerAddition, actions, showBackdrop, severity, showHeaderMarker, width, details, detailsHeader, detailsWidth, onCloseDetails, detailsHeaderAddition, ...props }: SidePanelProps & HTMLAttributes<HTMLElement>): ReactNode;
24
25
  export {};
@@ -6,7 +6,7 @@ import { Button } from '../../../components/button/Button';
6
6
  import { Headline } from '../../../components/headline/Headline';
7
7
  import { MOTION_MS } from '../../../styles/animation';
8
8
  import styles from './SidePanel.module.css';
9
- export function SidePanel({ isOpen, onClose, children, header, headerAddition, actions, showBackdrop = true, severity, showHeaderMarker = true, width = '400px', details, detailsHeader = 'Output', detailsWidth = '420px', onCloseDetails, detailsHeaderAddition, ...props }) {
9
+ export function SidePanel({ isOpen, onClose, children, header, subheader, headerAddition, actions, showBackdrop = true, severity, showHeaderMarker = true, width = '400px', details, detailsHeader = 'Output', detailsWidth = '420px', onCloseDetails, detailsHeaderAddition, ...props }) {
10
10
  const [mounted, setMounted] = useState(false);
11
11
  const [shouldRender, setShouldRender] = useState(isOpen);
12
12
  const [isActive, setIsActive] = useState(false);
@@ -76,7 +76,7 @@ export function SidePanel({ isOpen, onClose, children, header, headerAddition, a
76
76
  }, "data-cy": "details-panel", role: "dialog", "aria-modal": "true", children: [hasDetails ? (_jsxs("aside", { className: styles.detailsCol, "data-cy": "details-panel-details", children: [_jsxs("div", { className: styles.detailsHeader, children: [_jsx("div", { className: styles.detailsTitle, children: detailsHeader }), _jsxs("div", { className: styles.detailsHeaderActions, children: [detailsHeaderAddition, onCloseDetails ? (_jsx(Button, { type: "button", size: "sm", variant: "outlined", onClick: e => {
77
77
  e.stopPropagation();
78
78
  onCloseDetails();
79
- }, children: "Luk" })) : null] })] }), _jsx("div", { className: styles.detailsContent, children: details })] })) : null, _jsxs("section", { className: styles.mainCol, "data-cy": "details-panel-main", children: [_jsx("div", { className: styles.header, children: _jsxs("div", { className: "dbc-flex dbc-justify-between", children: [_jsx(Headline, { size: 3, disableMargin: true, severity: severity, marker: showHeaderMarker, children: header }), _jsxs("span", { className: "dbc-flex dbc-items-center dbc-gap-xs", children: [headerAddition, _jsx(Button, { type: "button", size: "md", shape: "round", variant: "inline", onClick: e => {
79
+ }, children: "Luk" })) : null] })] }), _jsx("div", { className: styles.detailsContent, children: details })] })) : null, _jsxs("section", { className: styles.mainCol, "data-cy": "details-panel-main", children: [_jsx("div", { className: styles.header, children: _jsxs("div", { className: "dbc-flex dbc-justify-between", children: [_jsx(Headline, { size: 3, disableMargin: true, severity: severity, marker: showHeaderMarker, subheader: subheader, children: header }), _jsxs("span", { className: "dbc-flex dbc-items-center dbc-gap-xs", children: [headerAddition, _jsx(Button, { type: "button", size: "md", shape: "round", variant: "inline", onClick: e => {
80
80
  e.stopPropagation();
81
81
  onClose(e);
82
82
  }, "aria-label": "Luk panel", "data-cy": "side-panel-close-button", children: _jsx(X, {}) })] })] }) }), _jsx("div", { className: styles.content, "data-cy": "details-panel-content", children: children }), actions && _jsx("div", { className: styles.actions, children: actions })] })] })] }), document.body);
@@ -3,12 +3,16 @@ import { Severity } from '../../constants/severity.types';
3
3
  import { BreadcrumbItem } from '../breadcrumbs/Breadcrumbs';
4
4
  interface PageProps {
5
5
  header?: string;
6
+ subheader?: ReactNode;
6
7
  severity?: Severity;
7
8
  headerIcon?: ReactNode;
8
9
  children?: ReactNode;
9
10
  headerAddition?: ReactNode;
10
11
  disableContentBox?: boolean;
12
+ disableTopPadding?: boolean;
13
+ maxWidth?: 'sm' | 'md' | 'lg';
11
14
  breadcrumbs?: BreadcrumbItem[];
15
+ containScrolling?: boolean;
12
16
  }
13
- export declare function Page({ header, severity, headerIcon, headerAddition, breadcrumbs, disableContentBox, children, }: PageProps): ReactNode;
17
+ export declare function Page({ header, subheader, severity, headerIcon, headerAddition, breadcrumbs, disableContentBox, disableTopPadding, maxWidth, containScrolling, children, }: PageProps): ReactNode;
14
18
  export {};
@@ -2,6 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import styles from './Page.module.css';
3
3
  import { Breadcrumbs } from '../breadcrumbs/Breadcrumbs';
4
4
  import { Headline } from '../headline/Headline';
5
- export function Page({ header, severity, headerIcon, headerAddition, breadcrumbs, disableContentBox, children, }) {
6
- return (_jsxs("div", { className: "grow-container", children: [_jsxs("div", { className: styles.headerContainer, children: [breadcrumbs ? (_jsx(Breadcrumbs, { items: breadcrumbs })) : (_jsx(Headline, { disableMargin: true, size: 2, severity: severity, icon: headerIcon, children: header })), headerAddition] }), _jsx("div", { className: `${styles.content} ${disableContentBox ? styles.disableContentBox : ''}`, children: children })] }));
5
+ export function Page({ header, subheader, severity, headerIcon, headerAddition, breadcrumbs, disableContentBox, disableTopPadding = true, maxWidth, containScrolling = false, children, }) {
6
+ const maxWidthClass = maxWidth
7
+ ? styles[`maxWidth${maxWidth.charAt(0).toUpperCase()}${maxWidth.slice(1)}`]
8
+ : '';
9
+ const hasHeadline = Boolean(header || subheader || headerAddition);
10
+ return (_jsxs("div", { className: `${styles.container} ${containScrolling ? styles.containScrolling : styles.documentScrolling} ${disableTopPadding ? styles.disableTopPadding : ''} ${maxWidthClass}`, children: [_jsx("div", { className: styles.headerContainer, children: _jsxs("div", { className: styles.headerMain, children: [breadcrumbs ? _jsx(Breadcrumbs, { items: breadcrumbs }) : null, hasHeadline ? (_jsx(Headline, { disableMargin: true, size: 2, severity: severity, icon: headerIcon, subheader: subheader, addition: headerAddition, children: header })) : null] }) }), _jsx("div", { className: `${styles.content} ${disableContentBox ? styles.disableContentBox : ''}`, children: children })] }));
7
11
  }
@@ -1,24 +1,74 @@
1
1
  .container {
2
- padding: var(--spacing-md);
3
2
  box-sizing: border-box;
4
3
  display: flex;
5
4
  flex-direction: column;
6
- gap: var(--spacing-md);
5
+ min-width: 0;
6
+ min-height: 0;
7
+ }
8
+
9
+ .maxWidthSm,
10
+ .maxWidthMd,
11
+ .maxWidthLg {
12
+ margin-inline: auto;
13
+ width: 100%;
14
+ }
15
+
16
+ .maxWidthSm {
17
+ max-width: 560px;
18
+ }
19
+ .maxWidthMd {
20
+ max-width: 768px;
21
+ }
22
+ .maxWidthLg {
23
+ max-width: 1024px;
24
+ }
25
+
26
+ .containScrolling {
7
27
  height: 100%;
28
+ overflow: hidden;
29
+ }
30
+
31
+ .documentScrolling {
32
+ height: auto;
33
+ overflow: visible;
8
34
  }
9
35
 
10
36
  .headerContainer {
11
37
  display: flex;
12
- justify-content: space-between;
13
- align-items: center;
38
+ align-items: flex-start;
14
39
  padding: var(--spacing-lg) 0;
40
+ gap: var(--spacing-md);
41
+ min-width: 0;
42
+ flex: 0 0 auto;
43
+ }
44
+
45
+ .disableTopPadding {
46
+ padding-block-start: 0;
47
+ }
48
+
49
+ .disableTopPadding .headerContainer {
50
+ padding-block-start: 0;
51
+ }
52
+
53
+ .headerMain {
54
+ display: flex;
55
+ flex-direction: column;
56
+ gap: var(--spacing-md);
57
+ flex: 1 1 auto;
58
+ min-width: 0;
15
59
  }
16
60
 
17
61
  .content {
18
62
  flex: 1 1 auto;
63
+ min-height: 0;
64
+ min-width: 0;
19
65
  overflow: auto;
20
66
  }
21
67
 
68
+ .documentScrolling .content {
69
+ overflow: visible;
70
+ }
71
+
22
72
  .content:not(.disableContentBox) {
23
73
  padding: var(--spacing-lg);
24
74
  border: 1px solid var(--color-border-default);
@@ -1,6 +1,7 @@
1
1
  import type { ReactNode, JSX } from 'react';
2
2
  interface PanelProps {
3
3
  header: ReactNode;
4
+ subheader?: ReactNode;
4
5
  headerIcon?: ReactNode;
5
6
  headerAddition?: ReactNode;
6
7
  severity?: Severity;
@@ -8,5 +9,5 @@ interface PanelProps {
8
9
  size?: 'sm' | 'md' | 'lg';
9
10
  }
10
11
  import { Severity } from '../../constants/severity.types';
11
- export declare function Panel({ header, headerIcon, headerAddition, children, severity, size, }: PanelProps): JSX.Element;
12
+ export declare function Panel({ header, subheader, headerIcon, headerAddition, children, severity, size, }: PanelProps): JSX.Element;
12
13
  export {};
@@ -2,6 +2,6 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import styles from './Panel.module.css';
4
4
  import { Headline } from '../headline/Headline';
5
- export function Panel({ header, headerIcon, headerAddition, children, severity, size, }) {
6
- return (_jsxs("section", { className: `${styles.container} ${size ? styles[size] : ''}`, children: [_jsx("div", { className: styles.header, children: _jsx(Headline, { disableMargin: true, size: size === 'sm' ? 4 : 3, icon: headerIcon, addition: headerAddition, severity: severity, children: header }) }), _jsx("div", { className: styles.content, children: children })] }));
5
+ export function Panel({ header, subheader, headerIcon, headerAddition, children, severity, size, }) {
6
+ return (_jsxs("section", { className: `${styles.container} ${size ? styles[size] : ''}`, children: [_jsx("div", { className: styles.header, children: _jsx(Headline, { disableMargin: true, size: size === 'sm' ? 4 : 3, icon: headerIcon, addition: headerAddition, subheader: subheader, severity: severity, children: header }) }), _jsx("div", { className: styles.content, children: children })] }));
7
7
  }
@@ -79,7 +79,7 @@ export const Popover = forwardRef(function Popover({ trigger: Trigger, children,
79
79
  const togglePopover = useCallback((e) => {
80
80
  var _a, _b;
81
81
  triggerElRef.current =
82
- (_b = (_a = anchorRef === null || anchorRef === void 0 ? void 0 : anchorRef.current) !== null && _a !== void 0 ? _a : containerRef.current) !== null && _b !== void 0 ? _b : e.currentTarget;
82
+ (_b = (_a = anchorRef === null || anchorRef === void 0 ? void 0 : anchorRef.current) !== null && _a !== void 0 ? _a : e.currentTarget) !== null && _b !== void 0 ? _b : containerRef.current;
83
83
  if (isOpen)
84
84
  closePopover('trigger');
85
85
  else
@@ -238,8 +238,8 @@ export const Popover = forwardRef(function Popover({ trigger: Trigger, children,
238
238
  .join(' '), children: Trigger(togglePopover, icon, isOpen) }), mounted &&
239
239
  isOpen &&
240
240
  createPortal(_jsx("div", { id: resolvedContentId, ref: node => {
241
- const mutableContentRef = contentRef;
242
- mutableContentRef.current = node;
241
+ ;
242
+ contentRef.current = node;
243
243
  setOverlayRef(node);
244
244
  }, className: styles.content, style: {
245
245
  top: pos.top,
@@ -0,0 +1,16 @@
1
+ import React, { ElementType } from 'react';
2
+ type GapSize = 'xxs' | 'xs' | 'sm' | 'md' | 'lg';
3
+ type StackOwnProps = {
4
+ direction?: 'row' | 'column';
5
+ gap?: GapSize;
6
+ align?: 'start' | 'center' | 'end';
7
+ justify?: 'start' | 'center' | 'end' | 'between';
8
+ grow?: boolean;
9
+ wrap?: boolean;
10
+ };
11
+ export type StackProps<C extends ElementType = 'div'> = StackOwnProps & {
12
+ as?: C;
13
+ } & Omit<React.ComponentPropsWithRef<C>, keyof StackOwnProps | 'as'>;
14
+ type StackComponent = <C extends ElementType = 'div'>(props: StackProps<C>) => React.ReactElement | null;
15
+ export declare const Stack: StackComponent;
16
+ export {};
@@ -0,0 +1,19 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { forwardRef } from 'react';
4
+ export const Stack = forwardRef(function Stack({ as, direction, gap, align, justify, grow = false, wrap = false, className, ...rest }, ref) {
5
+ const Tag = (as !== null && as !== void 0 ? as : 'div');
6
+ const classes = [
7
+ 'dbc-flex',
8
+ direction === 'column' ? 'dbc-flex-column' : undefined,
9
+ gap ? `dbc-gap-${gap}` : undefined,
10
+ align ? `dbc-items-${align}` : undefined,
11
+ justify ? `dbc-justify-${justify}` : undefined,
12
+ grow ? 'dbc-flex-grow' : undefined,
13
+ wrap ? 'dbc-flex-wrap' : undefined,
14
+ className,
15
+ ]
16
+ .filter(Boolean)
17
+ .join(' ');
18
+ return _jsx(Tag, { ref: ref, className: classes, ...rest });
19
+ });
@@ -1,9 +1,10 @@
1
1
  import { ReactNode } from 'react';
2
2
  interface StatePageProps {
3
3
  header?: string;
4
+ subheader?: ReactNode;
4
5
  type: 'error' | 'empty' | 'info' | 'notfound';
5
6
  children?: ReactNode;
6
7
  actions?: ReactNode;
7
8
  }
8
- export declare function StatePage({ header, type, children, actions }: StatePageProps): ReactNode;
9
+ export declare function StatePage({ header, subheader, type, children, actions, }: StatePageProps): ReactNode;
9
10
  export {};
@@ -5,7 +5,7 @@ import { ErrorIllustration } from './error';
5
5
  import { NotFoundIllustration } from './notFound';
6
6
  import styles from './StatePage.module.css';
7
7
  import { Headline } from '../headline/Headline';
8
- export function StatePage({ header, type, children, actions }) {
8
+ export function StatePage({ header, subheader, type, children, actions, }) {
9
9
  const illustration = useMemo(() => {
10
10
  switch (type) {
11
11
  case 'error':
@@ -16,5 +16,5 @@ export function StatePage({ header, type, children, actions }) {
16
16
  return _jsx(NotFoundIllustration, {});
17
17
  }
18
18
  }, [type]);
19
- return (_jsxs("div", { className: `dbc-flex dbc-flex-column dbc-gap-lg dbc-items-center dbc-justify-center ${styles.container}`, children: [_jsx("div", { className: styles.illustration, children: illustration }), _jsxs("div", { className: "dbc-flex dbc-flex-column dbc-gap-md dbc-items-center", children: [_jsx(Headline, { disableMargin: true, size: 1, children: header }), _jsx("div", { children: children }), _jsx("div", { className: "dbc-flex dbc-gap-sm dbc-justify-center", children: actions })] })] }));
19
+ return (_jsxs("div", { className: `dbc-flex dbc-flex-column dbc-gap-lg dbc-items-center dbc-justify-center ${styles.container}`, children: [_jsx("div", { className: styles.illustration, children: illustration }), _jsxs("div", { className: "dbc-flex dbc-flex-column dbc-gap-md dbc-items-center", children: [_jsx(Headline, { disableMargin: true, size: 1, subheader: subheader, children: header }), _jsx("div", { children: children }), _jsx("div", { className: "dbc-flex dbc-gap-sm dbc-justify-center", children: actions })] })] }));
20
20
  }
@@ -1,4 +1,4 @@
1
1
  import type { JSX } from 'react';
2
2
  import type { TableProps } from './Table.types';
3
- export declare function Table<T extends Record<string, any>>({ data, dataKey, columns, selectedRows, selectionMode, allRowsSelected, sortById, sortDirection, loading, emptyConfig, variant, size, viewMode, striped, fillViewport, tableWidth, tableRootRef, measuringLayout, toolbar, headerExtras, take, skip, totalItemsCount, paginationPlacement, showFirstLast, pageSizeOptions, getRowSeverity, onRowClick, onRowMouseEnter, onRowSelect, onSelectAllRows, onSortChange, onPageChange, ...rest }: TableProps<T>): JSX.Element;
3
+ export declare function Table<T extends Record<string, any>>({ data, dataKey, columns, selectedRows, selectionMode, allRowsSelected, sortById, sortDirection, loading, emptyConfig, variant, size, viewMode, striped, fillViewport, fillViewportBottomOffset, tableWidth, tableRootRef, measuringLayout, toolbar, headerExtras, take, skip, totalItemsCount, paginationPlacement, showFirstLast, pageSizeOptions, getRowSeverity, onRowClick, onRowMouseEnter, onRowSelect, onSelectAllRows, onSortChange, onPageChange, ...rest }: TableProps<T>): JSX.Element;
4
4
  export type { ColumnItem } from './Table.types';
@@ -1,7 +1,8 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useCallback, useId, useMemo } from 'react';
3
+ import { useCallback, useId, useMemo, useRef } from 'react';
4
4
  import { Pagination } from '../../components/pagination/Pagination';
5
+ import { useViewportFill } from '../../hooks/useViewportFill';
5
6
  import { TableEmptyState } from './components/empty-state/EmptyState';
6
7
  import { TableBody } from './components/TableBody';
7
8
  import { TableHeader } from './components/TableHeader';
@@ -9,17 +10,34 @@ import { TableLoadingBody } from './components/TableLoadingBody';
9
10
  import { cx } from './table.classes';
10
11
  import styles from './Table.module.css';
11
12
  import { getVisibleColumns, SELECTION_COLUMN_PX } from './table.utils';
12
- export function Table({ data, dataKey, columns, selectedRows, selectionMode = 'single', allRowsSelected, sortById, sortDirection, loading, emptyConfig, variant = 'primary', size = 'md', viewMode, striped, fillViewport = false, tableWidth, tableRootRef, measuringLayout = false, toolbar, headerExtras, take, skip, totalItemsCount, paginationPlacement = 'bottom', showFirstLast = false, pageSizeOptions, getRowSeverity, onRowClick, onRowMouseEnter, onRowSelect, onSelectAllRows, onSortChange, onPageChange, ...rest }) {
13
+ export function Table({ data, dataKey, columns, selectedRows, selectionMode = 'single', allRowsSelected, sortById, sortDirection, loading, emptyConfig, variant = 'primary', size = 'md', viewMode, striped, fillViewport = false, fillViewportBottomOffset = 16, tableWidth, tableRootRef, measuringLayout = false, toolbar, headerExtras, take, skip, totalItemsCount, paginationPlacement = 'bottom', showFirstLast = false, pageSizeOptions, getRowSeverity, onRowClick, onRowMouseEnter, onRowSelect, onSelectAllRows, onSortChange, onPageChange, ...rest }) {
13
14
  const visibleColumns = useMemo(() => getVisibleColumns(columns), [columns]);
14
15
  const selectionInputName = useId();
16
+ const internalTableRootRef = useRef(null);
17
+ const tableRootRefWrapper = useRef(tableRootRef);
15
18
  const hasSelection = Boolean(selectedRows && onRowSelect);
19
+ const hasPagination = Boolean(onPageChange && (loading || data.length > 0));
20
+ const paginationOffset = hasPagination ? 72 : 0;
21
+ const { style: viewportFillStyle } = useViewportFill(internalTableRootRef, {
22
+ bottomOffset: fillViewportBottomOffset + paginationOffset,
23
+ min: 120,
24
+ });
25
+ const setTableRootRef = useCallback((node) => {
26
+ internalTableRootRef.current = node;
27
+ if (typeof tableRootRefWrapper.current === 'function') {
28
+ tableRootRefWrapper.current(node);
29
+ }
30
+ else if (tableRootRefWrapper.current) {
31
+ tableRootRefWrapper.current.current = node;
32
+ }
33
+ }, []);
16
34
  const handlePageChange = useCallback((e) => {
17
35
  onPageChange === null || onPageChange === void 0 ? void 0 : onPageChange(e);
18
36
  }, [onPageChange]);
19
37
  const bodyContent = loading && !data.length ? (_jsx(TableLoadingBody, { rows: take !== null && take !== void 0 ? take : 5, columns: visibleColumns, hasSelection: hasSelection })) : (_jsx(TableBody, { data: data, dataKey: dataKey, columns: visibleColumns, striped: striped, selectedRows: selectedRows, hasSelection: hasSelection, selectionMode: selectionMode, selectionInputName: selectionInputName, viewMode: viewMode, getRowSeverity: getRowSeverity, onRowClick: onRowClick, onRowMouseEnter: onRowMouseEnter, onRowSelect: onRowSelect }));
20
- const paginationEl = onPageChange && (loading || data.length > 0) ? (_jsx("div", { className: cx(styles.paginationSlot, paginationPlacement === 'top' && styles.paginationSlotTop), children: _jsx(Pagination, { itemsCount: totalItemsCount, take: take, skip: skip, onPageChange: handlePageChange, showFirstLast: showFirstLast, pageSizeOptions: pageSizeOptions }) })) : null;
38
+ const paginationEl = hasPagination ? (_jsx("div", { className: cx(styles.paginationSlot, paginationPlacement === 'top' && styles.paginationSlotTop), children: _jsx(Pagination, { itemsCount: totalItemsCount, take: take, skip: skip, onPageChange: handlePageChange, showFirstLast: showFirstLast, pageSizeOptions: pageSizeOptions }) })) : null;
21
39
  const tableClassName = cx(styles.tableRoot, styles[variant], styles[size], measuringLayout && styles.measuringLayout, getRowSeverity && styles.severityTable);
22
- const tableShell = (_jsx("div", { ...rest, className: tableClassName, children: _jsx("div", { ref: tableRootRef, className: styles.tableScroll, children: !data.length && !loading ? (_jsx("div", { className: styles.emptyStateSlot, children: _jsx(TableEmptyState, { config: emptyConfig }) })) : (_jsxs("table", { className: styles.tableElement, "aria-rowcount": data.length, style: tableWidth != null ? { width: tableWidth } : undefined, children: [_jsxs("colgroup", { children: [hasSelection ? _jsx("col", { style: { width: SELECTION_COLUMN_PX } }) : null, visibleColumns.map(column => (_jsx("col", { style: column.width != null ? { width: column.width } : undefined }, column.id)))] }), _jsx(TableHeader, { columns: visibleColumns, hasSelection: hasSelection, selectionMode: selectionMode, allRowsSelected: allRowsSelected, onSelectAllRows: onSelectAllRows, sortById: sortById, sortDirection: sortDirection, onSortChange: onSortChange, headerExtras: headerExtras }), bodyContent] })) }) }));
40
+ const tableShell = (_jsx("div", { ...rest, className: tableClassName, children: _jsx("div", { ref: setTableRootRef, className: styles.tableScroll, style: fillViewport ? viewportFillStyle : undefined, children: !data.length && !loading ? (_jsx("div", { className: styles.emptyStateSlot, children: _jsx(TableEmptyState, { config: emptyConfig }) })) : (_jsxs("table", { className: styles.tableElement, "aria-rowcount": data.length, style: tableWidth != null ? { width: tableWidth } : undefined, children: [_jsxs("colgroup", { children: [hasSelection ? _jsx("col", { style: { width: SELECTION_COLUMN_PX } }) : null, visibleColumns.map(column => (_jsx("col", { style: column.width != null ? { width: column.width } : undefined }, column.id)))] }), _jsx(TableHeader, { columns: visibleColumns, hasSelection: hasSelection, selectionMode: selectionMode, allRowsSelected: allRowsSelected, onSelectAllRows: onSelectAllRows, sortById: sortById, sortDirection: sortDirection, onSortChange: onSortChange, headerExtras: headerExtras }), bodyContent] })) }) }));
23
41
  if (fillViewport) {
24
42
  return (_jsxs("div", { className: styles.fillViewportRoot, style: {
25
43
  flexDirection: paginationPlacement === 'top' ? 'column-reverse' : 'column',
@@ -101,6 +101,10 @@
101
101
  min-block-size: 0;
102
102
  }
103
103
 
104
+ .body .row:first-child .cell {
105
+ border-block-start: var(--spacing-xs) solid var(--table-row-bg);
106
+ }
107
+
104
108
  .headerCell,
105
109
  .cell {
106
110
  position: relative;
@@ -136,6 +140,16 @@
136
140
  line-height: 20px;
137
141
  }
138
142
 
143
+ .md .headerCell {
144
+ padding-block: var(--spacing-xs);
145
+ padding-inline: var(--spacing-sm);
146
+ }
147
+
148
+ .md .cell {
149
+ padding-block: var(--spacing-xs);
150
+ padding-inline: var(--spacing-sm);
151
+ }
152
+
139
153
  .sm .headerCell {
140
154
  padding-block: var(--spacing-xxs);
141
155
  padding-inline: var(--spacing-sm);
@@ -42,6 +42,7 @@ export type TableProps<T extends Record<string, any>> = Omit<HTMLAttributes<HTML
42
42
  viewMode?: ViewMode;
43
43
  striped?: boolean;
44
44
  fillViewport?: boolean;
45
+ fillViewportBottomOffset?: number;
45
46
  tableWidth?: number;
46
47
  tableRootRef?: Ref<HTMLDivElement>;
47
48
  measuringLayout?: boolean;
@@ -12,6 +12,7 @@ export type TabItem = {
12
12
  type TabsVariant = 'filled' | 'outlined';
13
13
  export interface TabsProps {
14
14
  header?: string;
15
+ subheader?: ReactNode;
15
16
  variant: TabsVariant;
16
17
  panelStyle?: boolean;
17
18
  /** Data-driven API */
@@ -22,6 +23,7 @@ export interface TabsProps {
22
23
  defaultValue?: TabId;
23
24
  onValueChange?: (id: TabId, tab: TabItem, index: number) => void;
24
25
  addition?: ReactNode;
26
+ disableTopPadding?: boolean;
25
27
  /** Composition API */
26
28
  children?: ReactNode;
27
29
  }
@@ -38,7 +40,7 @@ type SlotName = 'Item';
38
40
  type TabsItemComponent = ((props: TabsItemProps) => JSX.Element) & {
39
41
  __TABS_SLOT__?: SlotName;
40
42
  };
41
- export declare function Tabs({ header, variant, panelStyle, tabs, value, defaultValue, onValueChange, addition, children, }: TabsProps): JSX.Element;
43
+ export declare function Tabs({ header, subheader, variant, panelStyle, tabs, value, defaultValue, onValueChange, addition, disableTopPadding, children, }: TabsProps): JSX.Element;
42
44
  export declare namespace Tabs {
43
45
  var Item: TabsItemComponent;
44
46
  }
@@ -33,7 +33,7 @@ function normalizeFromChildren(children) {
33
33
  });
34
34
  return items;
35
35
  }
36
- export function Tabs({ header, variant, panelStyle = false, tabs, value, defaultValue, onValueChange, addition, children, }) {
36
+ export function Tabs({ header, subheader, variant, panelStyle = false, tabs, value, defaultValue, onValueChange, addition, disableTopPadding, children, }) {
37
37
  const uid = useId();
38
38
  // Data API wins if provided; otherwise parse <Tabs.Item>
39
39
  const sourceTabs = useMemo(() => {
@@ -113,7 +113,9 @@ export function Tabs({ header, variant, panelStyle = false, tabs, value, default
113
113
  focusAndSelect(enabled.length - 1);
114
114
  }
115
115
  }, [uid, visibleTabs, setValue]);
116
- return (_jsxs("div", { className: styles.root, children: [header ? (_jsxs("div", { className: styles.headerContainer, children: [_jsx(Headline, { disableMargin: true, size: 2, children: header }), addition] })) : null, _jsxs("div", { className: `${styles.tabs} ${styles[variant]} ${panelStyle ? styles.panelStyle : ''}`, children: [_jsx("div", { className: styles.tabList, role: "tablist", "aria-label": header !== null && header !== void 0 ? header : 'Tabs', children: visibleTabs.map((tab, index) => {
116
+ return (_jsxs("div", { className: styles.root, children: [header ? (_jsx("div", { className: [styles.headerContainer, disableTopPadding ? styles.disableTopPadding : '']
117
+ .filter(Boolean)
118
+ .join(' '), children: _jsx(Headline, { disableMargin: true, size: 2, subheader: subheader, addition: addition, children: header }) })) : null, _jsxs("div", { className: `${styles.tabs} ${styles[variant]} ${panelStyle ? styles.panelStyle : ''}`, children: [_jsx("div", { className: styles.tabList, role: "tablist", "aria-label": header !== null && header !== void 0 ? header : 'Tabs', children: visibleTabs.map((tab, index) => {
117
119
  const selected = index === activeIndex;
118
120
  const tabDomId = `${uid}-tab-${String(tab.id)}`;
119
121
  const panelDomId = `${uid}-panel-${String(tab.id)}`;
@@ -13,6 +13,10 @@
13
13
  gap: var(--spacing-md);
14
14
  }
15
15
 
16
+ .headerContainer.disableTopPadding {
17
+ padding-block-start: 0;
18
+ }
19
+
16
20
  /* Outer wrapper for tablist + content */
17
21
  .tabs {
18
22
  display: flex;
@@ -4,4 +4,5 @@ export interface ThemeButtonProps {
4
4
  size?: ButtonSize;
5
5
  variant?: ButtonVariant;
6
6
  }
7
+ export declare function ThemeMenuSection(): JSX.Element;
7
8
  export declare function ThemeButton({ size, variant }: ThemeButtonProps): JSX.Element;
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Monitor, Moon, Palette, Sun } from 'lucide-react';
4
4
  import { Button } from '../../components/button/Button';
5
5
  import { Menu } from '../../components/menu/Menu';
@@ -10,6 +10,10 @@ const THEME_OPTIONS = [
10
10
  { value: 'light', label: 'Lyst', icon: _jsx(Sun, { size: 16 }) },
11
11
  { value: 'dark', label: 'Mørkt', icon: _jsx(Moon, { size: 16 }) },
12
12
  ];
13
+ export function ThemeMenuSection() {
14
+ const { theme, switchTheme } = useTheme();
15
+ return (_jsxs(_Fragment, { children: [_jsx(Menu.Header, { children: "Udseende" }), THEME_OPTIONS.map(option => (_jsx(Menu.RadioItem, { name: "theme", value: option.value, checked: theme === option.value, label: option.label, onValueChange: value => switchTheme(value) }, option.value)))] }));
16
+ }
13
17
  export function ThemeButton({ size, variant = 'outlined' }) {
14
18
  const { theme, switchTheme } = useTheme();
15
19
  return (_jsx(Popover, { matchTriggerWidth: false, minWidth: "140px", trigger: (handleClick, chevron) => (_jsxs(Button, { variant: variant, size: size, onClick: handleClick, "aria-label": "Skift tema", "aria-haspopup": "menu", children: [_jsx(Palette, {}), chevron] })), children: close => (_jsxs(Menu, { children: [_jsx(Menu.Header, { children: "Udseende" }), THEME_OPTIONS.map(option => (_jsx(Menu.Item, { active: theme === option.value, children: _jsxs("button", { onClick: () => {
@@ -6,9 +6,10 @@ export type ToastActionConfig = {
6
6
  };
7
7
  export type ToastProps = {
8
8
  title?: ReactNode;
9
+ subheader?: ReactNode;
9
10
  message?: ReactNode;
10
11
  severity: Severity;
11
12
  action?: ToastActionConfig;
12
13
  onClose?: () => void;
13
14
  };
14
- export declare function Toast({ title, message, severity, action, onClose, }: ToastProps): React.ReactNode;
15
+ export declare function Toast({ title, subheader, message, severity, action, onClose, }: ToastProps): React.ReactNode;
@@ -3,7 +3,7 @@ import { X } from 'lucide-react';
3
3
  import styles from './Toast.module.css';
4
4
  import { Button } from '../button/Button';
5
5
  import { Headline } from '../headline/Headline';
6
- export function Toast({ title, message, severity = 'info', action, onClose, }) {
6
+ export function Toast({ title, subheader, message, severity = 'info', action, onClose, }) {
7
7
  const showHeader = Boolean(title);
8
8
  const showMessage = Boolean(message);
9
9
  const canClose = severity !== 'neutral';
@@ -16,5 +16,5 @@ export function Toast({ title, message, severity = 'info', action, onClose, }) {
16
16
  onClose === null || onClose === void 0 ? void 0 : onClose();
17
17
  }
18
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 }) }))] }));
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, subheader: subheader, 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 }) }))] }));
20
20
  }