@dbcdk/react-components 0.0.9 → 0.0.12

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 (113) hide show
  1. package/dist/components/accordion/Accordion.d.ts +27 -0
  2. package/dist/components/accordion/Accordion.js +66 -0
  3. package/dist/components/accordion/Accordion.module.css +87 -0
  4. package/dist/components/button/Button.module.css +1 -0
  5. package/dist/components/card/Card.d.ts +21 -3
  6. package/dist/components/card/Card.js +17 -2
  7. package/dist/components/card/Card.module.css +59 -0
  8. package/dist/components/circle/Circle.d.ts +5 -1
  9. package/dist/components/circle/Circle.js +2 -2
  10. package/dist/components/circle/Circle.module.css +60 -4
  11. package/dist/components/code-block/CodeBlock.js +1 -1
  12. package/dist/components/code-block/CodeBlock.module.css +30 -17
  13. package/dist/components/copy-button/CopyButton.d.ts +1 -0
  14. package/dist/components/copy-button/CopyButton.js +10 -2
  15. package/dist/components/datetime-picker/DateTimePicker.d.ts +4 -8
  16. package/dist/components/datetime-picker/DateTimePicker.js +72 -92
  17. package/dist/components/datetime-picker/dateTimeHelpers.d.ts +14 -12
  18. package/dist/components/datetime-picker/dateTimeHelpers.js +25 -45
  19. package/dist/components/filter-field/FilterField.js +16 -11
  20. package/dist/components/filter-field/FilterField.module.css +133 -12
  21. package/dist/components/forms/checkbox/Checkbox.d.ts +4 -10
  22. package/dist/components/forms/checkbox/Checkbox.js +3 -5
  23. package/dist/components/forms/checkbox-group/CheckboxGroup.js +1 -1
  24. package/dist/components/forms/checkbox-group/CheckboxGroup.module.css +1 -1
  25. package/dist/components/forms/input/Input.d.ts +1 -0
  26. package/dist/components/forms/input/Input.js +2 -4
  27. package/dist/components/forms/input/Input.module.css +10 -11
  28. package/dist/components/forms/input-container/InputContainer.d.ts +2 -1
  29. package/dist/components/forms/input-container/InputContainer.js +3 -3
  30. package/dist/components/forms/input-container/InputContainer.module.css +65 -0
  31. package/dist/components/forms/radio-buttons/RadioButton.d.ts +36 -0
  32. package/dist/components/forms/radio-buttons/RadioButton.js +26 -0
  33. package/dist/components/forms/radio-buttons/RadioButtonGroup.d.ts +25 -0
  34. package/dist/components/forms/radio-buttons/RadioButtonGroup.js +19 -0
  35. package/dist/components/forms/radio-buttons/RadioButtons.module.css +117 -0
  36. package/dist/components/forms/select/Select.d.ts +1 -1
  37. package/dist/components/forms/select/Select.js +3 -3
  38. package/dist/components/forms/text-area/Textarea.js +3 -3
  39. package/dist/components/forms/text-area/Textarea.module.css +8 -1
  40. package/dist/components/headline/Headline.d.ts +2 -7
  41. package/dist/components/headline/Headline.js +5 -2
  42. package/dist/components/headline/Headline.module.css +61 -2
  43. package/dist/components/hyperlink/Hyperlink.d.ts +19 -6
  44. package/dist/components/hyperlink/Hyperlink.js +35 -7
  45. package/dist/components/hyperlink/Hyperlink.module.css +50 -2
  46. package/dist/components/icon/Icon.module.css +1 -0
  47. package/dist/components/interval-select/IntervalSelect.js +1 -1
  48. package/dist/components/menu/Menu.d.ts +32 -0
  49. package/dist/components/menu/Menu.js +73 -13
  50. package/dist/components/menu/Menu.module.css +72 -4
  51. package/dist/components/nav-bar/NavBar.d.ts +24 -6
  52. package/dist/components/overlay/modal/Modal.module.css +2 -2
  53. package/dist/components/overlay/side-panel/SidePanel.d.ts +12 -4
  54. package/dist/components/overlay/side-panel/SidePanel.js +77 -4
  55. package/dist/components/overlay/side-panel/SidePanel.module.css +149 -28
  56. package/dist/components/overlay/side-panel/useSidePanel.d.ts +1 -1
  57. package/dist/components/overlay/side-panel/useSidePanel.js +2 -2
  58. package/dist/components/overlay/tooltip/useTooltipTrigger.js +4 -2
  59. package/dist/components/page-layout/PageLayout.js +0 -2
  60. package/dist/components/popover/Popover.js +1 -1
  61. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.d.ts +5 -5
  62. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +36 -24
  63. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.module.css +0 -3
  64. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.d.ts +3 -1
  65. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.js +4 -3
  66. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.module.css +109 -79
  67. package/dist/components/sidebar/components/sidebar-items/SidebarItems.js +16 -3
  68. package/dist/components/sidebar/components/sidebar-items/SidebarItems.module.css +20 -0
  69. package/dist/components/sidebar/providers/SidebarProvider.d.ts +4 -1
  70. package/dist/components/sidebar/providers/SidebarProvider.js +85 -58
  71. package/dist/components/skeleton-loader/SkeletonLoader.d.ts +1 -1
  72. package/dist/components/skeleton-loader/SkeletonLoader.js +15 -12
  73. package/dist/components/split-button/SplitButton.d.ts +1 -1
  74. package/dist/components/split-button/SplitButton.js +3 -1
  75. package/dist/components/split-button/SplitButton.module.css +4 -4
  76. package/dist/components/state-page/StatePage.d.ts +9 -0
  77. package/dist/components/state-page/StatePage.js +20 -0
  78. package/dist/components/state-page/StatePage.module.css +9 -0
  79. package/dist/components/state-page/empty.d.ts +2 -0
  80. package/dist/components/state-page/empty.js +2 -0
  81. package/dist/components/state-page/error.d.ts +2 -0
  82. package/dist/components/state-page/error.js +2 -0
  83. package/dist/components/state-page/notFound.d.ts +2 -0
  84. package/dist/components/state-page/notFound.js +2 -0
  85. package/dist/components/sticky-footer-layout/StickyFooterLayout.d.ts +19 -0
  86. package/dist/components/sticky-footer-layout/StickyFooterLayout.js +27 -0
  87. package/dist/components/table/Table.d.ts +9 -4
  88. package/dist/components/table/Table.js +6 -9
  89. package/dist/components/table/Table.module.css +180 -59
  90. package/dist/components/table/components/empty-state/EmptyState.d.ts +1 -1
  91. package/dist/components/table/components/empty-state/EmptyState.js +6 -7
  92. package/dist/components/table/components/table-settings/TableSettings.d.ts +13 -3
  93. package/dist/components/table/components/table-settings/TableSettings.js +55 -4
  94. package/dist/components/table/tanstack.d.ts +12 -1
  95. package/dist/components/table/tanstack.js +75 -23
  96. package/dist/components/toast/Toast.js +5 -1
  97. package/dist/components/toast/Toast.module.css +40 -15
  98. package/dist/components/toast/provider/ToastProvider.js +1 -0
  99. package/dist/hooks/useTableSettings.d.ts +23 -4
  100. package/dist/hooks/useTableSettings.js +64 -17
  101. package/dist/hooks/useTimeDuration.js +9 -3
  102. package/dist/hooks/useViewportFill.js +1 -0
  103. package/dist/index.d.ts +6 -0
  104. package/dist/index.js +6 -1
  105. package/dist/src/styles/styles.css +60 -25
  106. package/dist/styles/animation.d.ts +5 -0
  107. package/dist/styles/animation.js +5 -0
  108. package/dist/styles/styles.css +60 -25
  109. package/dist/styles/themes/dbc/dark.css +1 -1
  110. package/dist/styles/themes/dbc/light.css +2 -1
  111. package/dist/utils/localStorage.utils.d.ts +19 -0
  112. package/dist/utils/localStorage.utils.js +78 -0
  113. package/package.json +1 -1
@@ -0,0 +1,117 @@
1
+ .container {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: 8px;
5
+ }
6
+
7
+ .controlWrap {
8
+ position: relative;
9
+ display: inline-flex;
10
+ align-items: center;
11
+ }
12
+
13
+ /* Real input stays for a11y, but visually hidden */
14
+ .input {
15
+ position: absolute;
16
+ inset: 0;
17
+ width: var(--component-size-xs);
18
+ height: var(--component-size-xs);
19
+ margin: 0;
20
+ opacity: 0;
21
+ cursor: pointer;
22
+ }
23
+
24
+ /* The visible radio */
25
+ .radio {
26
+ width: var(--component-size-xs);
27
+ height: var(--component-size-xs);
28
+ border-radius: 999px;
29
+ display: inline-flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+
33
+ background: var(--color-bg-surface);
34
+ border: 2px solid rgba(0, 0, 0, 0.25);
35
+
36
+ transition:
37
+ border-color 120ms ease,
38
+ box-shadow 120ms ease,
39
+ background-color 120ms ease;
40
+ }
41
+
42
+ .dot {
43
+ width: 10px;
44
+ height: 10px;
45
+ border-radius: 999px;
46
+ transform: scale(0);
47
+ transition: transform 120ms ease;
48
+ background: var(--color-brand);
49
+ }
50
+
51
+ /* checked state */
52
+ .checked {
53
+ border-color: var(--color-brand);
54
+ }
55
+ .checked .dot {
56
+ transform: scale(1);
57
+ }
58
+
59
+ /* hover (only when not disabled) */
60
+ .input:not(:disabled):hover + .radio {
61
+ border-color: var(--color-brand-hover);
62
+ }
63
+
64
+ /* focus ring */
65
+ .input:focus-visible + .radio {
66
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-brand) 30%, transparent);
67
+ border-color: var(--color-brand-strong);
68
+ }
69
+
70
+ /* disabled */
71
+ .disabled {
72
+ opacity: 0.5;
73
+ }
74
+ .input:disabled {
75
+ cursor: not-allowed;
76
+ }
77
+
78
+ .label {
79
+ cursor: pointer;
80
+ user-select: none;
81
+ }
82
+
83
+ .primary.checked {
84
+ background: color-mix(in srgb, var(--color-brand) 10%, var(--color-bg-surface));
85
+ }
86
+
87
+ .default {
88
+ border-color: rgba(0, 0, 0, 0.2);
89
+ }
90
+
91
+ /* Sizes */
92
+ .sm {
93
+ width: 16px;
94
+ height: 16px;
95
+ }
96
+ .sm .dot {
97
+ width: 8px;
98
+ height: 8px;
99
+ }
100
+
101
+ .md {
102
+ width: 20px;
103
+ height: 20px;
104
+ }
105
+ .md .dot {
106
+ width: 10px;
107
+ height: 10px;
108
+ }
109
+
110
+ .lg {
111
+ width: 24px;
112
+ height: 24px;
113
+ }
114
+ .lg .dot {
115
+ width: 12px;
116
+ height: 12px;
117
+ }
@@ -18,5 +18,5 @@ export type SelectProps<T> = Omit<InputContainerProps, 'children' | 'htmlFor' |
18
18
  tooltip?: React.ReactNode;
19
19
  tooltipPlacement?: 'top' | 'right' | 'bottom' | 'left';
20
20
  };
21
- export declare function Select<T extends string | number | Record<string, any>>({ label, error, helpText, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, id, options, selectedValue, onChange, placeholder, size, variant, onClear, datakey, dataCy, disabled, }: SelectProps<T>): React.ReactNode;
21
+ export declare function Select<T extends string | number | Record<string, any>>({ label, error, helpText, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, modified, id, options, selectedValue, onChange, placeholder, size, variant, onClear, datakey, dataCy, disabled, }: SelectProps<T>): React.ReactNode;
22
22
  export {};
@@ -10,7 +10,7 @@ import { Popover } from '../../popover/Popover';
10
10
  import { InputContainer } from '../input-container/InputContainer';
11
11
  export function Select({
12
12
  // InputContainer props
13
- label, error, helpText, orientation = 'vertical', labelWidth = '120px', fullWidth = true, required, tooltip, tooltipPlacement = 'right',
13
+ label, error, helpText, orientation = 'vertical', labelWidth = '160px', fullWidth = true, required, tooltip, tooltipPlacement = 'right', modified = false,
14
14
  // Select props
15
15
  id, options, selectedValue, onChange, placeholder = 'Vælg', size, variant = 'outlined', onClear, datakey, dataCy, disabled, }) {
16
16
  const generatedId = useId();
@@ -72,10 +72,10 @@ id, options, selectedValue, onChange, placeholder = 'Vælg', size, variant = 'ou
72
72
  ids.push(tooltipId);
73
73
  return ids.length ? ids.join(' ') : undefined;
74
74
  })();
75
- return (_jsxs(InputContainer, { label: label, htmlFor: controlId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, children: [_jsx(Popover, { ref: popoverRef, trigger: (onClick, icon) => (_jsx(Button, { disabled: disabled, ...(tooltipEnabled ? triggerProps : {}), id: controlId, "data-cy": dataCy !== null && dataCy !== void 0 ? dataCy : 'select-button', onKeyDown: handleKeyDown, fullWidth: fullWidth, variant: variant, onClick: e => {
75
+ return (_jsxs(InputContainer, { label: label, htmlFor: controlId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, modified: modified, children: [_jsx(Popover, { ref: popoverRef, trigger: (onClick, icon) => (_jsx(Button, { disabled: disabled, ...(tooltipEnabled ? triggerProps : {}), id: controlId, "data-cy": dataCy !== null && dataCy !== void 0 ? dataCy : 'select-button', onKeyDown: handleKeyDown, fullWidth: fullWidth, variant: variant, onClick: e => {
76
76
  setActiveIndex(selectedIndex >= 0 ? selectedIndex : 0);
77
77
  onClick(e);
78
- }, size: size, type: "button", "aria-haspopup": "listbox", "aria-invalid": Boolean(error) || undefined, "aria-describedby": describedBy, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsx("span", { children: selected ? selected.label : placeholder }), onClear && selected && _jsx(ClearButton, { onClick: onClear }), icon] }) })), children: _jsx(Menu, { onKeyDown: handleKeyDown, role: "listbox", children: options.map((opt, index) => {
78
+ }, size: size, type: "button", "data-forminput": true, "aria-haspopup": "listbox", "aria-invalid": Boolean(error) || undefined, "aria-describedby": describedBy, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsx("span", { children: selected ? selected.label : placeholder }), onClear && selected && _jsx(ClearButton, { onClick: onClear }), icon] }) })), children: _jsx(Menu, { onKeyDown: handleKeyDown, role: "listbox", children: options.map((opt, index) => {
79
79
  const isSelected = typeof opt.value === 'object' && typeof selectedValue === 'object' && datakey
80
80
  ? (selectedValue === null || selectedValue === void 0 ? void 0 : selectedValue[datakey]) === opt.value[datakey]
81
81
  : opt.value === selectedValue;
@@ -4,9 +4,9 @@ import { useCallback, useId, useMemo } from 'react';
4
4
  import { useTooltipTrigger } from '../../../components/overlay/tooltip/useTooltipTrigger';
5
5
  import styles from './Textarea.module.css';
6
6
  import { InputContainer } from '../input-container/InputContainer';
7
- export const Textarea = function Textarea({ value, inputChanged, disabled, rows = 3, showCount, tooltip, tooltipPlacement = 'right', showTooltip, placeholder, adjustHeight, id,
7
+ export const Textarea = function Textarea({ value, inputChanged, disabled, rows = 3, showCount, tooltip, tooltipPlacement = 'right', showTooltip, placeholder, adjustHeight, id, modified = false,
8
8
  // InputContainer props
9
- label, error, helpText, orientation = 'horizontal', labelWidth = '120px', fullWidth = false, required,
9
+ label, error, helpText, orientation = 'horizontal', labelWidth = '160px', fullWidth = false, required,
10
10
  // Native textarea props
11
11
  className, ...rest }) {
12
12
  const generatedId = useId();
@@ -28,6 +28,6 @@ className, ...rest }) {
28
28
  placement: tooltipPlacement,
29
29
  offset: 8,
30
30
  });
31
- return (_jsx(InputContainer, { label: label, htmlFor: textareaId, error: error, helpText: helpText, helpTextAddition: showCount ? `${value === null || value === void 0 ? void 0 : value.length} tegn i denne boks` : undefined, orientation: orientation, labelWidth: labelWidth, fullWidth: fullWidth, required: required, children: _jsx("div", { className: styles.container, children: _jsx("div", { ...(tooltipEnabled ? triggerProps : {}), children: inputField }) }) }));
31
+ return (_jsx(InputContainer, { modified: modified, label: label, htmlFor: textareaId, error: error, helpText: helpText, helpTextAddition: showCount ? `${value === null || value === void 0 ? void 0 : value.length} tegn i denne boks` : undefined, orientation: orientation, labelWidth: labelWidth, fullWidth: fullWidth, required: required, children: _jsx("div", { className: styles.container, children: _jsx("div", { ...(tooltipEnabled ? triggerProps : {}), children: inputField }) }) }));
32
32
  };
33
33
  Textarea.displayName = 'Textarea';
@@ -9,6 +9,14 @@
9
9
  font-family: var(--font-family);
10
10
  }
11
11
 
12
+ .container textarea:disabled {
13
+ background-color: var(--color-disabled-bg);
14
+ border: 0;
15
+ color: var(--color-disabled-fg);
16
+ cursor: not-allowed;
17
+ opacity: 0.5;
18
+ }
19
+
12
20
  .internalCount {
13
21
  text-align: right;
14
22
  font-size: var(--font-size-sm);
@@ -22,5 +30,4 @@
22
30
  .container textarea:focus-visible {
23
31
  outline: none;
24
32
  border-color: var(--color-border-selected);
25
- box-shadow: var(--focus-ring);
26
33
  }
@@ -10,13 +10,8 @@ interface HeadlineProps extends React.AriaAttributes {
10
10
  subHeadline?: string | JSX.Element;
11
11
  addition?: React.ReactNode;
12
12
  icon?: React.ReactNode;
13
- /**
14
- * Optional visual tone override:
15
- * - dark: force normal foreground colours
16
- * - light: force on-strong/light text
17
- * If omitted, the headline simply inherits its colour from its parent.
18
- */
13
+ allowWrap?: boolean;
19
14
  tone?: HeadlineTone;
20
15
  }
21
- export declare function Headline({ size, marker, disableMargin, children, severity, weight, subHeadline, addition, icon, tone, }: PropsWithChildren<HeadlineProps>): React.ReactNode;
16
+ export declare function Headline({ size, marker, disableMargin, children, severity, weight, subHeadline, addition, icon, tone, allowWrap, }: PropsWithChildren<HeadlineProps>): React.ReactNode;
22
17
  export {};
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import styles from './Headline.module.css';
4
4
  import { SeverityBgColor } from '../../constants/severity';
5
5
  import { Icon } from '../icon/Icon';
6
- export function Headline({ size = 2, marker, disableMargin, children, severity, weight = 600, subHeadline, addition, icon, tone, }) {
6
+ export function Headline({ size = 2, marker, disableMargin, children, severity, weight = 600, subHeadline, addition, icon, tone, allowWrap = true, }) {
7
7
  const Tag = `h${size}`;
8
8
  const containerClassName = [styles.headlineContainer, tone ? styles[`tone-${tone}`] : '']
9
9
  .filter(Boolean)
@@ -15,8 +15,11 @@ export function Headline({ size = 2, marker, disableMargin, children, severity,
15
15
  ]
16
16
  .filter(Boolean)
17
17
  .join(' ');
18
+ const textClassName = [styles.text, allowWrap ? styles.wrap : styles.truncate]
19
+ .filter(Boolean)
20
+ .join(' ');
18
21
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: containerClassName, children: [_jsxs(Tag, { style: {
19
22
  '--font-weight': weight,
20
23
  '--marker-color': severity ? SeverityBgColor[severity] : undefined,
21
- }, className: headlineClassName, children: [icon || (severity && !marker) ? _jsx(Icon, { customIcon: icon, severity: severity }) : null, children] }), addition] }), subHeadline && _jsx("div", { className: styles.subHeadline, children: subHeadline })] }));
24
+ }, className: headlineClassName, children: [icon || (severity && !marker) ? (_jsx("span", { className: styles.icon, children: _jsx(Icon, { customIcon: icon, severity: severity }) })) : null, _jsx("span", { className: textClassName, children: children })] }), addition] }), subHeadline && _jsx("div", { className: styles.subHeadline, children: subHeadline })] }));
22
25
  }
@@ -3,6 +3,7 @@
3
3
  align-items: center;
4
4
  gap: var(--spacing-lg);
5
5
  flex-wrap: wrap;
6
+ max-width: 100%;
6
7
  }
7
8
 
8
9
  /* Base headline: inherit colour from parent surface */
@@ -18,8 +19,6 @@
18
19
  transition: color var(--transition-fast) var(--ease-standard);
19
20
  }
20
21
 
21
- /* Optional tone overrides for special cases (hero sections, etc.) */
22
-
23
22
  .tone-dark .headline {
24
23
  color: var(--color-fg-default);
25
24
  }
@@ -58,3 +57,63 @@
58
57
  margin-block-start: calc(var(--spacing-2xs) * -1);
59
58
  line-height: var(--line-height-normal);
60
59
  }
60
+
61
+ .headline {
62
+ position: relative;
63
+ display: inline-flex;
64
+ align-items: center;
65
+ gap: var(--spacing-xs);
66
+ font-weight: var(--font-weight, var(--font-weight-bold));
67
+ letter-spacing: var(--letter-spacing-tight);
68
+ color: inherit;
69
+ line-height: var(--line-height-tight);
70
+ transition: color var(--transition-fast) var(--ease-standard);
71
+
72
+ /* Needed so truncation works inside flex parents */
73
+ min-width: 0;
74
+ }
75
+
76
+ /* Truncated variant (single line with ellipsis) */
77
+ .truncate {
78
+ white-space: nowrap;
79
+ overflow: hidden;
80
+ text-overflow: ellipsis;
81
+ }
82
+
83
+ .headline {
84
+ position: relative;
85
+ display: inline-flex;
86
+ align-items: center;
87
+ gap: var(--spacing-xs);
88
+ font-weight: var(--font-weight, var(--font-weight-bold));
89
+ letter-spacing: var(--letter-spacing-tight);
90
+ color: inherit;
91
+ line-height: var(--line-height-tight);
92
+ transition: color var(--transition-fast) var(--ease-standard);
93
+
94
+ /* helps inside flex parents */
95
+ min-width: 0;
96
+ }
97
+
98
+ .icon {
99
+ flex: 0 0 auto;
100
+ display: inline-flex;
101
+ align-items: center;
102
+ }
103
+
104
+ /* text wrapper must be the shrinkable element */
105
+ .text {
106
+ min-width: 0;
107
+ flex: 1 1 auto;
108
+ }
109
+
110
+ .truncate {
111
+ overflow: hidden;
112
+ white-space: nowrap;
113
+ text-overflow: ellipsis;
114
+ }
115
+
116
+ .wrap {
117
+ white-space: normal;
118
+ overflow: visible;
119
+ }
@@ -1,9 +1,22 @@
1
1
  import * as React from 'react';
2
- interface HyperlinkProps {
3
- component: React.ReactElement;
4
- className?: string;
2
+ type BaseProps = {
3
+ children: React.ReactNode;
5
4
  icon?: React.ReactNode;
6
- disableIcon?: boolean;
7
- }
8
- export declare function Hyperlink({ component, icon }: HyperlinkProps): React.ReactElement;
5
+ className?: string;
6
+ /**
7
+ * If true, Hyperlink will NOT render <a>.
8
+ * Instead, it will clone its only child (e.g. Next <Link>) and apply styling/props to it.
9
+ */
10
+ asChild?: boolean;
11
+ variant?: 'primary' | 'secondary';
12
+ inline?: boolean;
13
+ };
14
+ type AnchorProps = BaseProps & React.AnchorHTMLAttributes<HTMLAnchorElement> & {
15
+ as?: 'a';
16
+ };
17
+ type ButtonProps = BaseProps & React.ButtonHTMLAttributes<HTMLButtonElement> & {
18
+ as: 'button';
19
+ };
20
+ type HyperlinkProps = AnchorProps | ButtonProps;
21
+ export declare function Hyperlink(props: HyperlinkProps): React.ReactElement;
9
22
  export {};
@@ -1,11 +1,39 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import * as React from 'react';
3
3
  import styles from './Hyperlink.module.css';
4
- export function Hyperlink({ component, icon }) {
5
- const originalProps = component.props;
6
- return React.cloneElement(component, {
7
- ...originalProps,
8
- className: styles.link,
9
- onClick: (e) => e.stopPropagation(),
10
- }, _jsxs(_Fragment, { children: [_jsx("span", { className: styles.content, children: originalProps.children }), icon && _jsx("span", { className: styles.icon, children: icon })] }));
4
+ function cx(...parts) {
5
+ return parts.filter(Boolean).join(' ');
6
+ }
7
+ function renderInner(children, icon) {
8
+ return (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.content, children: children }), icon && _jsx("span", { className: styles.icon, children: icon })] }));
9
+ }
10
+ export function Hyperlink(props) {
11
+ var _a;
12
+ const { children, icon, className, asChild, as = 'a', variant = 'primary', inline = true, ...rest } = props;
13
+ const linkClassName = cx(styles.link, className, variant === 'secondary' ? styles.secondary : styles.primary, inline ? '' : styles.block);
14
+ if (asChild) {
15
+ const child = React.Children.only(children);
16
+ if (!React.isValidElement(child)) {
17
+ throw new Error('Hyperlink with asChild expects a single valid React element as its child.');
18
+ }
19
+ const childProps = (_a = child.props) !== null && _a !== void 0 ? _a : {};
20
+ return React.cloneElement(child, {
21
+ ...childProps,
22
+ ...rest,
23
+ className: cx(childProps.className, linkClassName),
24
+ children: renderInner(childProps.children, icon),
25
+ onClick: (e) => {
26
+ e.stopPropagation();
27
+ if (childProps.onClick) {
28
+ childProps.onClick(e);
29
+ }
30
+ },
31
+ });
32
+ }
33
+ if (as === 'button') {
34
+ // (Optional) guardrail: avoid accidentally passing href to a button
35
+ // const { href, ...buttonRest } = rest as any
36
+ return (_jsx("button", { type: "button", className: linkClassName, ...rest, children: renderInner(children, icon) }));
37
+ }
38
+ return (_jsx("a", { onClick: e => e.stopPropagation(), className: linkClassName, ...rest, children: renderInner(children, icon) }));
11
39
  }
@@ -2,13 +2,61 @@
2
2
  display: inline-flex;
3
3
  gap: var(--spacing-xs);
4
4
  position: relative;
5
+ font-weight: normal;
6
+ background: none;
7
+ border: none;
8
+ padding: 0;
5
9
  text-decoration: none;
6
- color: var(--color-brand);
7
10
  font-size: inherit;
11
+ font-family: inherit;
12
+ cursor: pointer;
13
+ color: var(--color-brand);
14
+ line-height: inherit;
15
+ }
16
+
17
+ .link.secondary {
18
+ color: var(--color-fg-default);
8
19
  }
9
20
 
10
21
  .link:hover {
11
- text-decoration: underline;
22
+ color: var(--color-brand);
23
+ }
24
+
25
+ .link.primary {
26
+ position: relative;
27
+ color: var(--color-brand);
28
+ text-decoration: none;
29
+ }
30
+
31
+ .link.block {
32
+ background: var(--color-bg-contextual-subtle);
33
+ display: inline-block;
34
+ padding: var(--spacing-xs);
35
+ &:hover {
36
+ background-color: var(--color-bg-contextual);
37
+ }
38
+ }
39
+
40
+ .link::after {
41
+ content: '';
42
+ position: absolute;
43
+ left: 0;
44
+ bottom: -2px;
45
+ width: 100%;
46
+ height: 1px;
47
+ background-color: currentColor;
48
+ transform: scaleX(0);
49
+ transform-origin: left;
50
+ transition: transform 100ms ease;
51
+ }
52
+
53
+ .link:hover::after {
54
+ transform: scaleX(1);
55
+ }
56
+
57
+ .link:focus-visible {
58
+ outline: 2px solid var(--color-brand);
59
+ outline-offset: 2px;
12
60
  }
13
61
 
14
62
  .icon {
@@ -4,6 +4,7 @@
4
4
  vertical-align: middle;
5
5
  gap: var(--spacing-xxs);
6
6
  color: var(--color-fg-subtle);
7
+ font-size: var(--font-size-sm);
7
8
  }
8
9
 
9
10
  .icon {
@@ -10,7 +10,7 @@ import { useTooltipTrigger } from '../../components/overlay/tooltip/useTooltipTr
10
10
  import { Popover } from '../../components/popover/Popover';
11
11
  export function IntervalSelect({
12
12
  // InputContainer props
13
- label, error, helpText, orientation = 'vertical', labelWidth = '120px', fullWidth = true, required,
13
+ label, error, helpText, orientation = 'vertical', labelWidth = '160px', fullWidth = true, required,
14
14
  // tooltip
15
15
  tooltip, tooltipPlacement = 'right',
16
16
  // IntervalSelect props
@@ -2,11 +2,43 @@ import * as React from 'react';
2
2
  export interface MenuProps extends React.HTMLAttributes<HTMLUListElement> {
3
3
  children: React.ReactNode;
4
4
  }
5
+ /**
6
+ * Use when you need a visual divider inside a Menu.
7
+ * Renders a non-interactive <li role="separator" />
8
+ */
9
+ export type MenuSeparatorProps = React.LiHTMLAttributes<HTMLLIElement>;
5
10
  export interface MenuItemProps extends React.LiHTMLAttributes<HTMLLIElement> {
6
11
  children: React.ReactNode;
7
12
  active?: boolean;
8
13
  disabled?: boolean;
9
14
  }
15
+ export interface MenuCheckItemProps extends Omit<React.LiHTMLAttributes<HTMLLIElement>, 'onChange'> {
16
+ label: React.ReactNode;
17
+ checked: boolean;
18
+ disabled?: boolean;
19
+ onCheckedChange?: (checked: boolean) => void;
20
+ }
21
+ export interface MenuRadioItemProps extends Omit<React.LiHTMLAttributes<HTMLLIElement>, 'onChange'> {
22
+ /**
23
+ * Shared group name for the radio items in this menu section.
24
+ * (Required by your RadioButton component)
25
+ */
26
+ name: string;
27
+ /**
28
+ * This option's value.
29
+ */
30
+ value: string;
31
+ /**
32
+ * Whether this radio option is selected.
33
+ */
34
+ checked: boolean;
35
+ disabled?: boolean;
36
+ label: string;
37
+ onValueChange?: (value: string) => void;
38
+ }
10
39
  export declare const Menu: React.FC<MenuProps> & {
11
40
  Item: React.FC<MenuItemProps>;
41
+ CheckItem: React.FC<MenuCheckItemProps>;
42
+ RadioItem: React.FC<MenuRadioItemProps>;
43
+ Separator: React.FC<MenuSeparatorProps>;
12
44
  };
@@ -2,28 +2,88 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import * as React from 'react';
4
4
  import styles from './Menu.module.css';
5
+ import { Checkbox } from '../forms/checkbox/Checkbox';
6
+ import { RadioButton } from '../forms/radio-buttons/RadioButton';
5
7
  const MenuBase = React.forwardRef(({ children, className, ...props }, ref) => (_jsx("ul", { ref: ref, role: "menu", className: [styles.container, className].filter(Boolean).join(' '), ...props, children: children })));
6
8
  MenuBase.displayName = 'Menu';
7
9
  const isInteractiveEl = (el) => React.isValidElement(el) &&
8
- (typeof el.type === 'string' ? el.type === 'a' || el.type === 'button' : true); // allow custom/Next Link components
10
+ (typeof el.type === 'string' ? el.type === 'a' || el.type === 'button' : true);
11
+ /**
12
+ * Apply menu styles not only to the interactive element itself,
13
+ * but also to immediate children (covers cases where the direct child is a <div>
14
+ * wrapping a button-like thing, or components that render a wrapper).
15
+ */
16
+ function applyMenuItemPropsToElement(child, opts) {
17
+ const { active, disabled, role = 'menuitem', tabIndex = -1, className } = opts;
18
+ const childClass = [styles.item, active ? styles.active : ''].filter(Boolean).join(' ');
19
+ // Always apply styling to the immediate child
20
+ const nextImmediate = React.cloneElement(child, {
21
+ className: [child.props.className, styles.interactiveChild, className]
22
+ .filter(Boolean)
23
+ .join(' '),
24
+ });
25
+ // If the immediate child is already interactive, we can apply full a11y+styles there
26
+ if (typeof child.type === 'string' && (child.type === 'a' || child.type === 'button')) {
27
+ return React.cloneElement(child, {
28
+ role,
29
+ tabIndex,
30
+ 'aria-selected': active || undefined,
31
+ 'aria-disabled': disabled || undefined,
32
+ className: [child.props.className, styles.interactive, childClass, className]
33
+ .filter(Boolean)
34
+ .join(' '),
35
+ ...(child.type === 'button' ? { disabled } : {}),
36
+ });
37
+ }
38
+ // For custom components, we *assume* they forward props.
39
+ return React.cloneElement(nextImmediate, {
40
+ role,
41
+ tabIndex,
42
+ 'aria-selected': active || undefined,
43
+ 'aria-disabled': disabled || undefined,
44
+ className: [nextImmediate.props.className, styles.interactive, childClass]
45
+ .filter(Boolean)
46
+ .join(' '),
47
+ disabled,
48
+ });
49
+ }
9
50
  const MenuItem = React.forwardRef(({ children, active, disabled, className, ...liProps }, ref) => {
10
- // If child is interactive (a/button/NextLink), clone it and style it.
11
51
  if (isInteractiveEl(children)) {
12
52
  const child = children;
13
- const childClass = [styles.item, active ? styles.active : ''].filter(Boolean).join(' ');
14
- return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: React.cloneElement(child, {
15
- role: 'menuitem',
16
- tabIndex: -1,
17
- 'aria-selected': active || undefined,
18
- 'aria-disabled': disabled || undefined,
19
- className: [child.props.className, styles.interactive, childClass]
20
- .filter(Boolean)
21
- .join(' '),
22
- }) }));
53
+ return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: applyMenuItemPropsToElement(child, { active, disabled }) }));
23
54
  }
55
+ // Fallback: we wrap non-interactive children in a <button>
24
56
  return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: _jsx("button", { role: "menuitem", tabIndex: -1, "aria-selected": active || undefined, "aria-disabled": disabled || undefined, className: [styles.interactive, styles.item, active ? styles.active : '']
25
57
  .filter(Boolean)
26
58
  .join(' '), type: "button", disabled: disabled, children: children }) }));
27
59
  });
28
60
  MenuItem.displayName = 'Menu.Item';
29
- export const Menu = Object.assign(MenuBase, { Item: MenuItem });
61
+ /**
62
+ * Menu checkbox row that uses your Checkbox component.
63
+ * Note: this is a *control inside a menu*, so we lean on your Checkbox a11y (`role="checkbox"`)
64
+ * rather than forcing `menuitemcheckbox` roles.
65
+ */
66
+ const MenuCheckItem = React.forwardRef(({ label, checked, disabled, onCheckedChange, className, ...liProps }, ref) => {
67
+ return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: _jsx("div", { className: styles.interactiveChild, children: _jsx(Checkbox, { noContainer: true, checked: checked, disabled: disabled, label: label,
68
+ // Your Checkbox emits (checked, mouseEvent)
69
+ onChange: (next, _e) => onCheckedChange === null || onCheckedChange === void 0 ? void 0 : onCheckedChange(next) }) }) }));
70
+ });
71
+ MenuCheckItem.displayName = 'Menu.CheckItem';
72
+ /**
73
+ * Menu radio row that uses your RadioButton component.
74
+ * Same note as above: we keep your native radio semantics.
75
+ */
76
+ const MenuRadioItem = React.forwardRef(({ name, value, checked, disabled, label, onValueChange, className, ...liProps }, ref) => {
77
+ return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: _jsx("div", { className: styles.interactiveChild, children: _jsx(RadioButton, { noContainer: true, name: name, value: value, checked: checked, disabled: disabled, label: label,
78
+ // Your RadioButton emits (value, changeEvent)
79
+ onChange: (v, _e) => onValueChange === null || onValueChange === void 0 ? void 0 : onValueChange(v) }) }) }));
80
+ });
81
+ MenuRadioItem.displayName = 'Menu.RadioItem';
82
+ const MenuSeparator = React.forwardRef(({ className, ...props }, ref) => (_jsx("li", { ref: ref, role: "separator", className: [styles.separator, className].filter(Boolean).join(' '), ...props })));
83
+ MenuSeparator.displayName = 'Menu.Separator';
84
+ export const Menu = Object.assign(MenuBase, {
85
+ Item: MenuItem,
86
+ CheckItem: MenuCheckItem,
87
+ RadioItem: MenuRadioItem,
88
+ Separator: MenuSeparator,
89
+ });