@dbcdk/react-components 0.0.10 → 0.0.13

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 (106) hide show
  1. package/dist/components/accordion/Accordion.d.ts +2 -2
  2. package/dist/components/accordion/Accordion.js +34 -41
  3. package/dist/components/accordion/Accordion.module.css +13 -72
  4. package/dist/components/accordion/components/AccordionRow.d.ts +10 -0
  5. package/dist/components/accordion/components/AccordionRow.js +51 -0
  6. package/dist/components/accordion/components/AccordionRow.module.css +82 -0
  7. package/dist/components/breadcrumbs/Breadcrumbs.module.css +0 -1
  8. package/dist/components/button/Button.module.css +7 -7
  9. package/dist/components/card/Card.d.ts +15 -6
  10. package/dist/components/card/Card.js +39 -13
  11. package/dist/components/card/Card.module.css +22 -28
  12. package/dist/components/card/components/CardMeta.d.ts +15 -0
  13. package/dist/components/card/components/CardMeta.js +20 -0
  14. package/dist/components/card/components/CardMeta.module.css +51 -0
  15. package/dist/components/card-container/CardContainer.js +1 -1
  16. package/dist/components/card-container/CardContainer.module.css +3 -1
  17. package/dist/components/chip/Chip.module.css +7 -2
  18. package/dist/components/circle/Circle.d.ts +2 -1
  19. package/dist/components/circle/Circle.js +2 -2
  20. package/dist/components/circle/Circle.module.css +6 -2
  21. package/dist/components/code-block/CodeBlock.js +1 -1
  22. package/dist/components/code-block/CodeBlock.module.css +30 -17
  23. package/dist/components/copy-button/CopyButton.d.ts +1 -0
  24. package/dist/components/copy-button/CopyButton.js +10 -2
  25. package/dist/components/datetime-picker/DateTimePicker.d.ts +33 -8
  26. package/dist/components/datetime-picker/DateTimePicker.js +119 -78
  27. package/dist/components/datetime-picker/DateTimePicker.module.css +2 -0
  28. package/dist/components/datetime-picker/dateTimeHelpers.d.ts +15 -3
  29. package/dist/components/datetime-picker/dateTimeHelpers.js +137 -23
  30. package/dist/components/filter-field/FilterField.js +16 -11
  31. package/dist/components/filter-field/FilterField.module.css +137 -16
  32. package/dist/components/forms/checkbox/Checkbox.d.ts +2 -2
  33. package/dist/components/forms/checkbox-group/CheckboxGroup.js +1 -1
  34. package/dist/components/forms/checkbox-group/CheckboxGroup.module.css +1 -1
  35. package/dist/components/forms/form-select/FormSelect.d.ts +35 -0
  36. package/dist/components/forms/form-select/FormSelect.js +86 -0
  37. package/dist/components/forms/form-select/FormSelect.module.css +236 -0
  38. package/dist/components/forms/input/Input.d.ts +0 -3
  39. package/dist/components/forms/input/Input.js +1 -4
  40. package/dist/components/forms/input/Input.module.css +8 -7
  41. package/dist/components/forms/input-container/InputContainer.module.css +1 -1
  42. package/dist/components/forms/radio-buttons/RadioButtons.module.css +1 -0
  43. package/dist/components/forms/select/Select.js +55 -16
  44. package/dist/components/hyperlink/Hyperlink.d.ts +19 -7
  45. package/dist/components/hyperlink/Hyperlink.js +35 -11
  46. package/dist/components/hyperlink/Hyperlink.module.css +50 -2
  47. package/dist/components/interval-select/IntervalSelect.d.ts +9 -2
  48. package/dist/components/interval-select/IntervalSelect.js +21 -6
  49. package/dist/components/menu/Menu.d.ts +29 -0
  50. package/dist/components/menu/Menu.js +61 -16
  51. package/dist/components/menu/Menu.module.css +73 -5
  52. package/dist/components/overlay/modal/Modal.module.css +4 -3
  53. package/dist/components/overlay/modal/provider/ModalProvider.js +1 -3
  54. package/dist/components/overlay/side-panel/SidePanel.js +18 -1
  55. package/dist/components/overlay/side-panel/SidePanel.module.css +1 -3
  56. package/dist/components/overlay/tooltip/useTooltipTrigger.js +4 -2
  57. package/dist/components/page-layout/PageLayout.d.ts +16 -4
  58. package/dist/components/page-layout/PageLayout.js +57 -28
  59. package/dist/components/page-layout/PageLayout.module.css +153 -33
  60. package/dist/components/popover/Popover.d.ts +17 -4
  61. package/dist/components/popover/Popover.js +147 -65
  62. package/dist/components/popover/Popover.module.css +5 -0
  63. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +22 -18
  64. package/dist/components/sidebar/providers/SidebarProvider.d.ts +4 -1
  65. package/dist/components/sidebar/providers/SidebarProvider.js +66 -18
  66. package/dist/components/split-button/SplitButton.d.ts +1 -1
  67. package/dist/components/split-button/SplitButton.js +3 -1
  68. package/dist/components/split-button/SplitButton.module.css +4 -4
  69. package/dist/components/split-pane/SplitPane.d.ts +10 -24
  70. package/dist/components/split-pane/SplitPane.js +83 -54
  71. package/dist/components/split-pane/SplitPane.module.css +11 -6
  72. package/dist/components/split-pane/provider/SplitPaneContext.js +5 -11
  73. package/dist/components/state-page/StatePage.module.css +1 -1
  74. package/dist/components/sticky-footer-layout/StickyFooterLayout.d.ts +3 -8
  75. package/dist/components/sticky-footer-layout/StickyFooterLayout.js +57 -20
  76. package/dist/components/table/Table.d.ts +8 -8
  77. package/dist/components/table/Table.js +37 -79
  78. package/dist/components/table/Table.module.css +62 -46
  79. package/dist/components/table/{tanstack.d.ts → TanstackTable.d.ts} +7 -3
  80. package/dist/components/table/TanstackTable.js +84 -0
  81. package/dist/components/table/components/column-resizer/ColumnResizer.js +1 -1
  82. package/dist/components/table/components/column-resizer/ColumnResizer.module.css +17 -7
  83. package/dist/components/table/components/table-settings/TableSettings.d.ts +13 -3
  84. package/dist/components/table/components/table-settings/TableSettings.js +55 -4
  85. package/dist/components/table/table.utils.d.ts +17 -0
  86. package/dist/components/table/table.utils.js +61 -0
  87. package/dist/components/table/tanstackTable.utils.d.ts +22 -0
  88. package/dist/components/table/tanstackTable.utils.js +104 -0
  89. package/dist/components/tabs/Tabs.d.ts +35 -12
  90. package/dist/components/tabs/Tabs.js +114 -26
  91. package/dist/components/tabs/Tabs.module.css +158 -71
  92. package/dist/hooks/useTableSettings.d.ts +23 -4
  93. package/dist/hooks/useTableSettings.js +64 -17
  94. package/dist/index.d.ts +1 -1
  95. package/dist/index.js +1 -1
  96. package/dist/src/styles/styles.css +38 -23
  97. package/dist/styles/animation.d.ts +5 -0
  98. package/dist/styles/animation.js +5 -0
  99. package/dist/styles/styles.css +38 -23
  100. package/dist/styles/themes/dbc/base.css +136 -0
  101. package/dist/styles/themes/dbc/dark.css +39 -202
  102. package/dist/styles/themes/dbc/light.css +17 -174
  103. package/dist/utils/localStorage.utils.d.ts +19 -0
  104. package/dist/utils/localStorage.utils.js +78 -0
  105. package/package.json +4 -4
  106. package/dist/components/table/tanstack.js +0 -162
@@ -1,5 +1,5 @@
1
- import type { ReactNode, JSX } from 'react';
2
- import { Severity } from '../../constants/severity.types';
1
+ import type { JSX, ReactNode } from 'react';
2
+ import type { Severity } from '../../constants/severity.types';
3
3
  export interface AccordionItem {
4
4
  header: string;
5
5
  headerIcon?: ReactNode;
@@ -1,42 +1,43 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useId, useMemo, useState } from 'react';
4
4
  import styles from './Accordion.module.css';
5
- import { Headline } from '../headline/Headline';
5
+ import { AccordionRow } from './components/AccordionRow';
6
6
  function uniqSorted(nums) {
7
7
  return Array.from(new Set(nums)).sort((a, b) => a - b);
8
8
  }
9
+ function normalizeMultiple(indexes) {
10
+ return uniqSorted((indexes !== null && indexes !== void 0 ? indexes : []).filter(n => Number.isFinite(n)));
11
+ }
9
12
  export function Accordion({ items, mode = 'single', size = 'md', defaultOpenIndex = null, defaultOpenIndexes = [], openIndex, openIndexes, onOpenIndexChange, onOpenIndexesChange, }) {
10
13
  const uid = useId();
11
- const isControlledSingle = mode === 'single' && openIndex !== undefined;
12
- const isControlledMultiple = mode === 'multiple' && openIndexes !== undefined;
13
- const [internalOpenIndex, setInternalOpenIndex] = useState(mode === 'single' ? defaultOpenIndex : null);
14
- const [internalOpenIndexes, setInternalOpenIndexes] = useState(mode === 'multiple' ? uniqSorted(defaultOpenIndexes) : []);
15
- const currentOpenIndex = mode === 'single' ? (isControlledSingle ? openIndex : internalOpenIndex) : null;
16
- const currentOpenIndexes = useMemo(() => mode === 'multiple'
17
- ? isControlledMultiple
18
- ? uniqSorted(openIndexes)
19
- : internalOpenIndexes
20
- : [], [mode, isControlledMultiple, openIndexes, internalOpenIndexes]);
21
- const openSet = useMemo(() => new Set(mode === 'single'
22
- ? currentOpenIndex !== null
23
- ? [currentOpenIndex]
24
- : []
25
- : currentOpenIndexes), [mode, currentOpenIndex, currentOpenIndexes]);
26
- function setSingle(next) {
27
- if (isControlledSingle)
28
- onOpenIndexChange === null || onOpenIndexChange === void 0 ? void 0 : onOpenIndexChange(next);
29
- else {
30
- setInternalOpenIndex(next);
31
- onOpenIndexChange === null || onOpenIndexChange === void 0 ? void 0 : onOpenIndexChange(next);
14
+ const isControlled = mode === 'single' ? openIndex !== undefined : openIndexes !== undefined;
15
+ const [internalSingle, setInternalSingle] = useState(mode === 'single' ? defaultOpenIndex : null);
16
+ const [internalMultiple, setInternalMultiple] = useState(mode === 'multiple' ? normalizeMultiple(defaultOpenIndexes) : []);
17
+ const currentOpenIndexes = useMemo(() => {
18
+ if (mode === 'single') {
19
+ const current = isControlled ? (openIndex !== null && openIndex !== void 0 ? openIndex : null) : internalSingle;
20
+ return current === null ? [] : [current];
32
21
  }
33
- }
34
- function setMultiple(next) {
35
- const normalized = uniqSorted(next);
36
- if (isControlledMultiple)
22
+ return isControlled ? normalizeMultiple(openIndexes) : internalMultiple;
23
+ }, [mode, isControlled, openIndex, openIndexes, internalSingle, internalMultiple]);
24
+ const openSet = useMemo(() => new Set(currentOpenIndexes), [currentOpenIndexes]);
25
+ function commit(nextIndexes) {
26
+ if (mode === 'single') {
27
+ const next = nextIndexes.length ? nextIndexes[0] : null;
28
+ if (isControlled)
29
+ onOpenIndexChange === null || onOpenIndexChange === void 0 ? void 0 : onOpenIndexChange(next);
30
+ else {
31
+ setInternalSingle(next);
32
+ onOpenIndexChange === null || onOpenIndexChange === void 0 ? void 0 : onOpenIndexChange(next);
33
+ }
34
+ return;
35
+ }
36
+ const normalized = uniqSorted(nextIndexes);
37
+ if (isControlled)
37
38
  onOpenIndexesChange === null || onOpenIndexesChange === void 0 ? void 0 : onOpenIndexesChange(normalized);
38
39
  else {
39
- setInternalOpenIndexes(normalized);
40
+ setInternalMultiple(normalized);
40
41
  onOpenIndexesChange === null || onOpenIndexesChange === void 0 ? void 0 : onOpenIndexesChange(normalized);
41
42
  }
42
43
  }
@@ -44,23 +45,15 @@ export function Accordion({ items, mode = 'single', size = 'md', defaultOpenInde
44
45
  const item = items[index];
45
46
  if (!item || item.disabled)
46
47
  return;
48
+ const isOpen = openSet.has(index);
47
49
  if (mode === 'single') {
48
- const isOpen = openSet.has(index);
49
- setSingle(isOpen ? null : index);
50
+ commit(isOpen ? [] : [index]);
50
51
  return;
51
52
  }
52
- // multiple
53
- const isOpen = openSet.has(index);
54
53
  if (isOpen)
55
- setMultiple(currentOpenIndexes.filter(i => i !== index));
54
+ commit(currentOpenIndexes.filter(i => i !== index));
56
55
  else
57
- setMultiple([...currentOpenIndexes, index]);
56
+ commit([...currentOpenIndexes, index]);
58
57
  }
59
- return (_jsx("div", { className: `${styles.container} ${styles[size]}`, children: items.map((item, i) => {
60
- const isOpen = openSet.has(i);
61
- const isDisabled = !!item.disabled;
62
- const buttonId = `${uid}-acc-btn-${i}`;
63
- const panelId = `${uid}-acc-panel-${i}`;
64
- return (_jsxs("section", { className: `${styles.item} ${isOpen ? styles.open : ''} ${isDisabled ? styles.disabled : ''}`, children: [_jsxs("button", { type: "button", id: buttonId, className: styles.trigger, "aria-expanded": isOpen, "aria-controls": panelId, onClick: () => toggle(i), disabled: isDisabled, children: [_jsx("span", { className: styles.title, children: _jsx(Headline, { disableMargin: true, size: 4, weight: 500, severity: item.severity, allowWrap: isOpen, children: item.header }) }), _jsx("span", { className: styles.chevron, "aria-hidden": "true", children: _jsx("svg", { viewBox: "0 0 20 20", focusable: "false", children: _jsx("path", { d: "M5.5 7.5L10 12l4.5-4.5", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }) })] }), _jsx("div", { id: panelId, role: "region", "aria-labelledby": buttonId, className: styles.panel, "data-open": isOpen ? 'true' : 'false', children: _jsx("div", { className: styles.content, children: item.children }) })] }, i));
65
- }) }));
58
+ return (_jsx("div", { className: `${styles.container} ${styles[size]}`, children: items.map((item, i) => (_jsx(AccordionRow, { uid: uid, index: i, item: item, isOpen: openSet.has(i), onToggle: toggle }, i))) }));
66
59
  }
@@ -8,80 +8,21 @@
8
8
  gap: var(--spacing-xxs);
9
9
  }
10
10
 
11
- .trigger {
12
- all: unset;
13
- box-sizing: border-box;
14
- width: 100%;
15
- display: flex;
16
- align-items: center;
17
- justify-content: space-between;
18
- gap: var(--spacing-sm);
19
- cursor: pointer;
20
- user-select: none;
21
- padding: var(--spacing-xs) var(--spacing-md);
22
- background: var(--color-bg-contextual);
23
-
24
- /* IMPORTANT: allow flex children to actually shrink */
25
- min-width: 0;
26
- }
27
-
28
- .trigger:focus-visible {
29
- outline: none;
30
- box-shadow: var(--focus-ring);
31
- }
32
-
33
- .disabled .trigger {
34
- cursor: not-allowed;
35
- color: var(--color-disabled-fg);
36
- }
37
-
38
- .title {
39
- /* IMPORTANT: this is the shrinking area that contains Headline */
40
- display: flex;
41
- align-items: center;
42
- min-width: 0;
43
- flex: 1 1 auto;
44
-
45
- /* ensures any overflow is clipped so ellipsis can show */
46
- overflow: hidden;
47
- }
48
-
49
- .chevron {
50
- width: var(--icon-size-md);
51
- height: var(--icon-size-md);
52
- flex: 0 0 auto;
53
- transition: transform var(--transition-normal) var(--ease-standard);
54
- }
55
-
56
- .open .chevron {
57
- transform: rotate(180deg);
58
- }
59
-
60
- /* Collapsible panel using max-height */
61
- .panel {
62
- overflow: hidden;
63
- max-height: 0;
64
- transition: max-height var(--transition-slow) var(--ease-decelerate);
65
- }
66
-
67
- .panel[data-open='true'] {
68
- max-height: 999px;
69
- }
70
-
71
- .content {
72
- padding: var(--spacing-md) 0;
73
- }
74
-
75
- /* Sizes */
76
- .sm .trigger,
77
- .sm .content {
78
- padding: var(--spacing-sm) var(--spacing-md);
11
+ /* Size variables (consumed by AccordionRow) */
12
+ .sm {
13
+ --acc-trigger-py: var(--spacing-sm);
14
+ --acc-trigger-px: var(--spacing-md);
15
+ --acc-content-py: var(--spacing-sm);
79
16
  }
80
17
 
81
- .md .trigger {
82
- padding: var(--spacing-sm) var(--spacing-md);
18
+ .md {
19
+ --acc-trigger-py: var(--spacing-sm);
20
+ --acc-trigger-px: var(--spacing-md);
21
+ --acc-content-py: var(--spacing-md);
83
22
  }
84
23
 
85
- .lg .trigger {
86
- padding: var(--spacing-md) var(--spacing-md);
24
+ .lg {
25
+ --acc-trigger-py: var(--spacing-md);
26
+ --acc-trigger-px: var(--spacing-md);
27
+ --acc-content-py: var(--spacing-md);
87
28
  }
@@ -0,0 +1,10 @@
1
+ import type { JSX } from 'react';
2
+ import { AccordionItem } from '../Accordion';
3
+ export type AccordionRowProps = {
4
+ uid: string;
5
+ index: number;
6
+ item: AccordionItem;
7
+ isOpen: boolean;
8
+ onToggle: (index: number) => void;
9
+ };
10
+ export declare function AccordionRow({ uid, index, item, isOpen, onToggle, }: AccordionRowProps): JSX.Element;
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronDown } from 'lucide-react';
3
+ import { useLayoutEffect, useRef, useState } from 'react';
4
+ import { Headline } from '../../../components/headline/Headline';
5
+ import styles from './AccordionRow.module.css';
6
+ function useCollapsibleHeight(isOpen) {
7
+ const innerRef = useRef(null);
8
+ const [height, setHeight] = useState('0px');
9
+ useLayoutEffect(() => {
10
+ var _a, _b;
11
+ const el = innerRef.current;
12
+ if (!el)
13
+ return;
14
+ const prefersReduced = typeof window !== 'undefined' &&
15
+ ((_b = (_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, '(prefers-reduced-motion: reduce)')) === null || _b === void 0 ? void 0 : _b.matches);
16
+ if (prefersReduced) {
17
+ setHeight(isOpen ? 'auto' : '0px');
18
+ return;
19
+ }
20
+ if (isOpen) {
21
+ // 0 -> scrollHeight
22
+ setHeight('0px');
23
+ requestAnimationFrame(() => {
24
+ setHeight(`${el.scrollHeight}px`);
25
+ });
26
+ }
27
+ else {
28
+ // lock -> 0 (important when currently 'auto')
29
+ setHeight(`${el.scrollHeight}px`);
30
+ requestAnimationFrame(() => {
31
+ setHeight('0px');
32
+ });
33
+ }
34
+ }, [isOpen]);
35
+ function onTransitionEnd(e) {
36
+ if (e.propertyName !== 'height')
37
+ return;
38
+ if (!innerRef.current)
39
+ return;
40
+ if (isOpen)
41
+ setHeight('auto');
42
+ }
43
+ return { innerRef, height, onTransitionEnd };
44
+ }
45
+ export function AccordionRow({ uid, index, item, isOpen, onToggle, }) {
46
+ const isDisabled = !!item.disabled;
47
+ const buttonId = `${uid}-acc-btn-${index}`;
48
+ const panelId = `${uid}-acc-panel-${index}`;
49
+ const { innerRef, height, onTransitionEnd } = useCollapsibleHeight(isOpen);
50
+ return (_jsxs("section", { className: `${styles.item} ${isOpen ? styles.open : ''} ${isDisabled ? styles.disabled : ''}`, children: [_jsx("h3", { className: styles.heading, children: _jsxs("button", { type: "button", id: buttonId, className: styles.trigger, "aria-expanded": isOpen, "aria-controls": panelId, onClick: () => onToggle(index), disabled: isDisabled, children: [_jsxs("span", { className: styles.title, children: [item.headerIcon ? _jsx("span", { className: styles.icon, children: item.headerIcon }) : null, _jsx(Headline, { disableMargin: true, size: 4, weight: 500, severity: item.severity, allowWrap: isOpen, children: item.header })] }), _jsx("span", { className: styles.chevron, "aria-hidden": "true", children: _jsx(ChevronDown, {}) })] }) }), _jsx("div", { id: panelId, role: "region", "aria-labelledby": buttonId, className: styles.panel, style: { height }, onTransitionEnd: onTransitionEnd, children: _jsx("div", { ref: innerRef, className: styles.content, children: item.children }) })] }));
51
+ }
@@ -0,0 +1,82 @@
1
+ .heading {
2
+ margin: 0;
3
+ }
4
+
5
+ .trigger {
6
+ all: unset;
7
+ box-sizing: border-box;
8
+
9
+ width: 100%;
10
+ display: flex;
11
+ align-items: center;
12
+ justify-content: space-between;
13
+ gap: var(--spacing-sm);
14
+
15
+ cursor: pointer;
16
+ user-select: none;
17
+
18
+ padding: var(--acc-trigger-py) var(--acc-trigger-px);
19
+ background: var(--color-bg-contextual);
20
+
21
+ min-width: 0;
22
+ }
23
+
24
+ .trigger:focus-visible {
25
+ outline: none;
26
+ box-shadow: var(--focus-ring);
27
+ }
28
+
29
+ .disabled .trigger {
30
+ cursor: not-allowed;
31
+ color: var(--color-disabled-fg);
32
+ }
33
+
34
+ .title {
35
+ display: flex;
36
+ align-items: center;
37
+ gap: var(--spacing-xs);
38
+
39
+ min-width: 0;
40
+ flex: 1 1 auto;
41
+ overflow: hidden;
42
+ }
43
+
44
+ .icon {
45
+ flex: 0 0 auto;
46
+ display: inline-flex;
47
+ align-items: center;
48
+ }
49
+
50
+ .chevron {
51
+ width: var(--icon-size-md);
52
+ height: var(--icon-size-md);
53
+ flex: 0 0 auto;
54
+ transition: transform var(--transition-normal) var(--ease-standard);
55
+ }
56
+
57
+ .chevron svg {
58
+ width: 100%;
59
+ height: 100%;
60
+ display: block;
61
+ }
62
+
63
+ .open .chevron {
64
+ transform: rotate(180deg);
65
+ }
66
+
67
+ .panel {
68
+ overflow: hidden;
69
+ height: 0px;
70
+ transition: height var(--transition-slow) var(--ease-decelerate);
71
+ }
72
+
73
+ /* Padding inside so scrollHeight includes it */
74
+ .content {
75
+ padding: var(--acc-content-py) var(--acc-trigger-px);
76
+ }
77
+
78
+ @media (prefers-reduced-motion: reduce) {
79
+ .panel {
80
+ transition: none;
81
+ }
82
+ }
@@ -1,6 +1,5 @@
1
1
  .breadcrumbs {
2
2
  font-family: var(--font-family);
3
- text-transform: var(--breadcrumb-transform);
4
3
  }
5
4
 
6
5
  .breadcrumbs ul {
@@ -13,10 +13,10 @@
13
13
  line-height: 1;
14
14
 
15
15
  height: var(--component-size-md);
16
- min-block-size: calc(var(--component-size-md) + var(--density));
16
+ min-block-size: var(--component-size-md);
17
17
 
18
- padding-inline: var(--control-padding-x);
19
- padding-block: calc(var(--control-padding-y) + var(--density));
18
+ padding-inline: var(--spacing-sm);
19
+ padding-block: var(--spacing-xs);
20
20
 
21
21
  border-width: var(--border-width-thin, 1px);
22
22
  border-style: solid;
@@ -86,19 +86,19 @@
86
86
 
87
87
  .button.xs {
88
88
  height: var(--component-size-xs);
89
- min-block-size: calc(var(--component-size-sm) + var(--density));
89
+ min-block-size: var(--component-size-sm);
90
90
  padding-inline: var(--spacing-sm);
91
91
  }
92
92
 
93
93
  .button.sm {
94
94
  height: var(--component-size-sm);
95
- min-block-size: calc(var(--component-size-sm) + var(--density));
95
+ min-block-size: var(--component-size-sm);
96
96
  padding-inline: var(--spacing-sm);
97
97
  }
98
98
 
99
99
  .button.xs {
100
100
  height: var(--component-size-xs);
101
- min-block-size: calc(var(--component-size-xs) + var(--density));
101
+ min-block-size: var(--component-size-xs);
102
102
  padding-inline: var(--spacing-xs);
103
103
  font-size: var(--font-size-xs);
104
104
  }
@@ -110,7 +110,7 @@
110
110
 
111
111
  .button.lg {
112
112
  height: var(--component-size-lg);
113
- min-block-size: calc(var(--component-size-lg) + var(--density));
113
+ min-block-size: var(--component-size-lg);
114
114
  padding-inline: var(--spacing-lg);
115
115
  }
116
116
 
@@ -1,10 +1,10 @@
1
- import { JSXElementConstructor, ReactElement } from 'react';
2
- import type { ReactNode, JSX } from 'react';
3
- import { Severity } from '../../constants/severity.types';
1
+ import type { JSX, ReactNode } from 'react';
2
+ import type { Severity } from '../../constants/severity.types';
3
+ import { CardMeta, CardMetaRow } from './components/CardMeta';
4
4
  type CardVariant = 'default' | 'subtle' | 'strong';
5
5
  type CardSize = 'sm' | 'md' | 'lg';
6
6
  type CardImagePlacement = 'left' | 'right' | 'top';
7
- interface CardProps {
7
+ export interface CardProps {
8
8
  title?: string;
9
9
  loading?: boolean;
10
10
  variant?: CardVariant;
@@ -20,8 +20,17 @@ interface CardProps {
20
20
  sectionTitle?: string;
21
21
  showSectionDivider?: boolean;
22
22
  children?: ReactNode;
23
- link?: ReactElement<any, string | JSXElementConstructor<any>>;
23
+ /**
24
+ * Keep current behavior: if provided, Card becomes "linked".
25
+ * NOTE: this assumes your Hyperlink component can render the passed element correctly.
26
+ * If Hyperlink expects an <a>, pass <a href="...">...</a> or whatever your existing pattern is.
27
+ */
28
+ link?: ReactNode;
24
29
  width?: 25 | 33 | 50 | 66 | 75 | 100;
25
30
  }
26
- export declare function Card({ title, loading, variant, size, headerMarker, headerIcon, severity, image, imgPlacement, mediaWidth, actions, headerMeta, sectionTitle, showSectionDivider, children, link, width, }: CardProps): JSX.Element;
31
+ type CardComponent = ((props: CardProps) => JSX.Element) & {
32
+ Meta: typeof CardMeta;
33
+ MetaRow: typeof CardMetaRow;
34
+ };
35
+ export declare const Card: CardComponent;
27
36
  export {};
@@ -1,22 +1,48 @@
1
- 'use client';
2
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
2
  import styles from './Card.module.css';
3
+ import { CardMeta, CardMetaRow } from './components/CardMeta';
4
4
  import { Headline } from '../headline/Headline';
5
5
  import { Hyperlink } from '../hyperlink/Hyperlink';
6
6
  import { SkeletonLoaderItem } from '../skeleton-loader/skeleton-loader-item/SkeletonLoaderItem';
7
- export function Card({ title, loading, variant = 'default', size = 'md', headerMarker = true, headerIcon, severity, image, imgPlacement = 'left', mediaWidth, actions, headerMeta, sectionTitle, showSectionDivider = false, children, link, width, }) {
8
- const outerContainerStyle = width
9
- ? { ['--width']: `${width}%` }
10
- : undefined;
7
+ function getInnerPlacementClass(imgPlacement, s) {
8
+ switch (imgPlacement) {
9
+ case 'top':
10
+ return s.innerImgTop;
11
+ case 'right':
12
+ return s.innerImgRight;
13
+ case 'left':
14
+ default:
15
+ return s.innerImgLeft;
16
+ }
17
+ }
18
+ function getVariantClass(variant, s) {
19
+ switch (variant) {
20
+ case 'subtle':
21
+ return s.variantSubtle;
22
+ case 'strong':
23
+ return s.variantStrong;
24
+ case 'default':
25
+ default:
26
+ return s.variantDefault;
27
+ }
28
+ }
29
+ function CardImpl({ title, loading = false, variant = 'default', size = 'md', headerMarker = true, headerIcon, severity, image, imgPlacement = 'left', mediaWidth, actions, headerMeta, sectionTitle, showSectionDivider = false, children, link, width, }) {
30
+ const outerStyle = width ? { ['--width']: `${width}%` } : undefined;
11
31
  const mediaStyle = mediaWidth
12
32
  ? { ['--card-media-width']: `${mediaWidth}px` }
13
33
  : undefined;
14
- const innerPlacementClass = imgPlacement === 'top'
15
- ? styles.innerImgTop
16
- : imgPlacement === 'right'
17
- ? styles.innerImgRight
18
- : styles.innerImgLeft;
19
- const inner = (_jsxs("div", { className: `${styles.inner} ${innerPlacementClass}`, children: [image && (_jsx("div", { className: styles.media, style: mediaStyle, children: image })), _jsxs("div", { className: styles.content, children: [(title || headerMeta) && (_jsxs("header", { className: styles.header, children: [title && (_jsx(Headline, { severity: severity, marker: headerMarker, icon: headerIcon, size: 4, weight: 500, disableMargin: true, children: title })), headerMeta && _jsx("div", { className: styles.headerMeta, children: headerMeta })] })), loading && (_jsx("div", { className: styles.loadingList, children: Array.from({ length: 4 }).map((_, index) => (_jsxs("div", { className: styles.loadingRow, children: [_jsx(SkeletonLoaderItem, {}), _jsx(SkeletonLoaderItem, { width: "100%" })] }, index))) })), !loading && (showSectionDivider || sectionTitle) && (_jsxs("div", { className: styles.section, children: [showSectionDivider && _jsx("div", { className: styles.sectionDivider }), sectionTitle && _jsx("div", { className: styles.sectionTitle, children: sectionTitle })] })), !loading && children && _jsx("div", { className: styles.body, children: children }), !loading && actions && _jsx("div", { className: styles.actions, children: actions })] })] }));
20
- const cardWithLink = link ? _jsx(Hyperlink, { component: link }) : inner;
21
- return (_jsx("div", { className: `${styles.outerContainer} ${styles[size]}`, style: outerContainerStyle, children: _jsx("div", { className: [styles.container, styles[variant]].filter(Boolean).join(' '), children: cardWithLink }) }));
34
+ const innerPlacementClass = getInnerPlacementClass(imgPlacement, styles);
35
+ const variantClass = getVariantClass(variant, styles);
36
+ const hasHeader = !!title || !!headerMeta;
37
+ const showSection = !loading && (showSectionDivider || !!sectionTitle);
38
+ const showBody = !loading && !!children;
39
+ const showActions = !loading && !!actions;
40
+ const inner = (_jsxs("div", { className: `${styles.inner} ${innerPlacementClass}`, children: [image ? (_jsx("div", { className: styles.media, style: mediaStyle, children: image })) : null, _jsxs("div", { className: styles.content, children: [hasHeader ? (_jsxs("header", { className: styles.header, children: [title ? (_jsx(Headline, { severity: severity, marker: headerMarker, icon: headerIcon, size: 4, weight: 500, disableMargin: true, children: title })) : null, headerMeta ? _jsx("div", { className: styles.headerMeta, children: headerMeta }) : null] })) : null, loading ? (_jsx("div", { className: styles.loadingList, "aria-busy": "true", "aria-live": "polite", children: Array.from({ length: 4 }, (_, index) => (_jsxs("div", { className: styles.loadingRow, children: [_jsx(SkeletonLoaderItem, {}), _jsx(SkeletonLoaderItem, { width: "100%" })] }, index))) })) : null, showSection ? (_jsxs("div", { className: styles.section, children: [showSectionDivider ? _jsx("div", { className: styles.sectionDivider }) : null, sectionTitle ? _jsx("div", { className: styles.sectionTitle, children: sectionTitle }) : null] })) : null, showBody ? _jsx("div", { className: styles.body, children: children }) : null, showActions ? _jsx("div", { className: styles.actions, children: actions }) : null] })] }));
41
+ // keep existing behavior
42
+ const cardContent = link ? _jsx(Hyperlink, { children: link }) : inner;
43
+ return (_jsx("div", { className: `${styles.outerContainer} ${styles[size]}`, style: outerStyle, children: _jsx("div", { className: `${styles.container} ${variantClass}`, children: cardContent }) }));
22
44
  }
45
+ export const Card = Object.assign(CardImpl, {
46
+ Meta: CardMeta,
47
+ MetaRow: CardMetaRow,
48
+ });
@@ -12,7 +12,6 @@
12
12
  border-radius: var(--border-radius-md);
13
13
  border: var(--border-width-thin) solid var(--color-border-subtle);
14
14
  box-shadow: var(--shadow-xs);
15
- padding-block: 0;
16
15
  transition:
17
16
  color var(--transition-fast) var(--ease-standard),
18
17
  box-shadow var(--transition-fast) var(--ease-standard),
@@ -24,33 +23,31 @@
24
23
  }
25
24
 
26
25
  /* BACKGROUND VARIANTS */
27
- .container.default {
26
+ .variantDefault {
28
27
  background-color: var(--card-bg-default, var(--color-bg-surface));
29
28
  }
30
29
 
31
- .container.subtle {
30
+ .variantSubtle {
32
31
  background-color: var(--card-bg-subtle, var(--color-bg-surface-subtle));
33
32
  }
34
33
 
35
- .container.strong {
34
+ .variantStrong {
36
35
  background-color: var(--card-bg-strong, var(--color-bg-surface-strong));
37
36
  color: var(--card-fg-on-strong, var(--color-fg-on-strong));
38
37
  }
39
38
 
40
- /* SIZE VARIANTS */
41
- .sm .inner {
42
- padding: var(--spacing-md);
43
- gap: var(--spacing-md);
39
+ /* SIZE VARIANTS (define vars once; inner uses them) */
40
+ .sm {
41
+ --card-pad: var(--spacing-md);
42
+ --card-gap: var(--spacing-md);
44
43
  }
45
-
46
- .md .inner {
47
- padding: var(--spacing-lg);
48
- gap: var(--spacing-lg);
44
+ .md {
45
+ --card-pad: var(--spacing-lg);
46
+ --card-gap: var(--spacing-lg);
49
47
  }
50
-
51
- .lg .inner {
52
- padding: var(--spacing-xl);
53
- gap: var(--spacing-xl);
48
+ .lg {
49
+ --card-pad: var(--spacing-xl);
50
+ --card-gap: var(--spacing-xl);
54
51
  }
55
52
 
56
53
  /* INNER LAYOUT */
@@ -58,6 +55,8 @@
58
55
  margin-inline: auto;
59
56
  display: flex;
60
57
  align-items: flex-start;
58
+ padding: var(--card-pad);
59
+ gap: var(--card-gap);
61
60
  }
62
61
 
63
62
  /* Media placement */
@@ -103,8 +102,8 @@
103
102
 
104
103
  /* CONTENT */
105
104
  .content {
106
- flex: 1 1 auto; /* important: allow content column to grow */
107
- min-inline-size: 0; /* prevents overflow in flex layouts */
105
+ flex: 1 1 auto;
106
+ min-inline-size: 0;
108
107
  display: flex;
109
108
  flex-direction: column;
110
109
  gap: var(--spacing-md);
@@ -118,11 +117,6 @@
118
117
  gap: var(--spacing-sm);
119
118
  }
120
119
 
121
- .value {
122
- font-weight: 600;
123
- white-space: nowrap;
124
- }
125
-
126
120
  .headerMeta {
127
121
  display: flex;
128
122
  align-items: center;
@@ -156,18 +150,18 @@
156
150
  }
157
151
 
158
152
  .sectionTitle {
159
- font-weight: 600;
153
+ font-weight: var(--font-weight-semibold);
160
154
  }
161
155
 
162
156
  /* BODY */
163
157
  .body {
164
- /* leave this as a neutral container; consumers render their own layout */
158
+ /* neutral container */
165
159
  }
166
160
 
167
- /* ACTIONS (bottom-right) */
161
+ /* ACTIONS */
168
162
  .actions {
169
- margin-top: auto; /* pushes actions to bottom of the content column */
163
+ margin-top: auto;
170
164
  display: flex;
171
- justify-content: flex-end; /* right-align buttons */
165
+ justify-content: flex-end;
172
166
  gap: var(--spacing-sm);
173
167
  }
@@ -0,0 +1,15 @@
1
+ import type { HTMLAttributes, JSX, ReactNode } from 'react';
2
+ export type CardMetaProps = {
3
+ columns?: 1 | 2 | 3;
4
+ className?: string;
5
+ children: ReactNode;
6
+ } & Omit<HTMLAttributes<HTMLDListElement>, 'children'>;
7
+ export type CardMetaRowProps = {
8
+ label: ReactNode;
9
+ value: ReactNode;
10
+ className?: string;
11
+ nowrapValue?: boolean;
12
+ labelWidth?: string;
13
+ };
14
+ export declare function CardMeta({ columns, className, children, ...rest }: CardMetaProps): JSX.Element;
15
+ export declare function CardMetaRow({ label, value, className, nowrapValue, labelWidth, }: CardMetaRowProps): JSX.Element;
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import styles from './CardMeta.module.css';
3
+ function getColsClass(columns) {
4
+ switch (columns) {
5
+ case 1:
6
+ return styles.cols1;
7
+ case 3:
8
+ return styles.cols3;
9
+ case 2:
10
+ default:
11
+ return styles.cols2;
12
+ }
13
+ }
14
+ export function CardMeta({ columns = 2, className, children, ...rest }) {
15
+ const colsClass = getColsClass(columns);
16
+ return (_jsx("dl", { ...rest, className: [styles.grid, colsClass, className].filter(Boolean).join(' '), children: children }));
17
+ }
18
+ export function CardMetaRow({ label, value, className, nowrapValue, labelWidth, }) {
19
+ return (_jsxs("div", { className: [styles.row, className].filter(Boolean).join(' '), style: labelWidth ? { ['--label-width']: labelWidth } : undefined, children: [_jsx("dt", { className: styles.label, children: label }), _jsx("dd", { className: [styles.value, nowrapValue ? styles.nowrap : ''].filter(Boolean).join(' '), children: value })] }));
20
+ }