@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
@@ -10,16 +10,22 @@
10
10
  border: var(--border-width-thin) solid var(--color-border-default);
11
11
  border-radius: var(--border-radius-default);
12
12
 
13
+ position: relative;
14
+
13
15
  transition:
14
16
  border-color var(--transition-fast) var(--ease-standard),
15
17
  box-shadow var(--transition-fast) var(--ease-standard),
16
18
  background-color var(--transition-fast) var(--ease-standard);
17
19
  }
18
20
 
19
- /* .filterField.active {
20
- border-color: var(--color-border-selected);
21
- background: var(--color-bg-selected);
22
- } */
21
+ /* More comfortable active state:
22
+ - less "blue outline" noise
23
+ - slightly warmer surface hint
24
+ */
25
+ .filterField.active {
26
+ border-color: color-mix(in srgb, var(--color-border-default) 75%, var(--color-border-selected));
27
+ background: color-mix(in srgb, var(--color-bg-surface) 96%, var(--color-bg-selected));
28
+ }
23
29
 
24
30
  .filterField.sm {
25
31
  block-size: calc(var(--component-size-sm) + var(--density));
@@ -39,6 +45,7 @@
39
45
  user-select: none;
40
46
  }
41
47
 
48
+ /* Operator trigger */
42
49
  .filterField .operatorTrigger {
43
50
  display: inline-flex;
44
51
  align-items: center;
@@ -68,30 +75,139 @@
68
75
  color: var(--color-disabled-fg);
69
76
  background: var(--color-disabled-bg);
70
77
  }
71
-
72
78
  .filterField .operatorText {
73
79
  white-space: nowrap;
74
80
  }
75
81
 
82
+ /* When active, operator is less dominant (calmer + more "token"-like) */
83
+ .filterField.active .operatorTrigger {
84
+ background: transparent;
85
+ color: var(--color-fg-muted);
86
+ }
87
+ .filterField.active .operatorTrigger:hover {
88
+ background: var(--opac-bg-dark);
89
+ color: var(--color-fg-default);
90
+ }
91
+
92
+ /* Wrapper for the select / multiselect control */
76
93
  .filterField .valueWrapper {
77
94
  display: inline-flex;
78
95
  align-items: center;
79
96
  padding: 0;
80
97
  height: 100%;
98
+
99
+ flex: 1;
100
+ min-width: 0;
81
101
  }
82
102
 
83
- .valueWrapper > * {
103
+ /* Ensure the control inside can stretch/shrink */
104
+ .filterField .valueWrapper > * {
84
105
  height: 100%;
85
- & > * {
86
- height: 100% !important;
87
- }
106
+ width: 100%;
107
+ min-width: 0;
88
108
  }
89
-
90
- .valueWrapper button {
109
+ .filterField .valueWrapper > * > * {
91
110
  height: 100% !important;
111
+ }
112
+
113
+ /* Active: emphasize VALUE area (but keep it soft) */
114
+ .filterField.active .valueWrapper {
115
+ background: color-mix(in srgb, var(--color-bg-surface) 88%, var(--color-bg-selected));
116
+ border-left: var(--border-width-thin) solid
117
+ color-mix(in srgb, var(--color-border-default) 80%, var(--color-border-selected));
118
+ border-top-right-radius: calc(var(--border-radius-default) - 1px);
119
+ border-bottom-right-radius: calc(var(--border-radius-default) - 1px);
120
+ }
121
+
122
+ /* =========================
123
+ TRIGGER BUTTON TARGETING
124
+ ========================= */
125
+
126
+ /* Select trigger button */
127
+ .filterField .valueWrapper :global(button[data-forminput]) {
128
+ width: 100%;
129
+ height: 100%;
130
+ border: 0 !important;
131
+
132
+ /* slightly more breathing room than before */
133
+ padding-inline: calc(var(--spacing-sm) + var(--spacing-2xs)) !important;
134
+
135
+ text-align: left;
136
+ justify-content: flex-start;
137
+ }
138
+
139
+ /* MultiSelect trigger button (Popover container is a div; trigger is its direct child button) */
140
+ .filterField .valueWrapper > div > button {
141
+ width: 100%;
142
+ height: 100%;
92
143
  border: 0 !important;
93
- padding-inline: var(--spacing-sm) !important;
144
+ padding-inline: calc(var(--spacing-sm) + var(--spacing-2xs)) !important;
145
+
146
+ text-align: left;
147
+ justify-content: flex-start;
148
+ }
149
+
150
+ /* Slight spacing between clear (×) and chevron for Select + MultiSelect
151
+ (feels less cramped / more intentional) */
152
+ .filterField .valueWrapper :global(button[data-forminput]) :global(.dbc-flex),
153
+ .filterField .valueWrapper > div > button {
154
+ column-gap: var(--spacing-xs);
155
+ }
156
+
157
+ /* Make the internal Select layout behave */
158
+ .filterField .valueWrapper :global(.dbc-flex) {
159
+ width: 100% !important;
160
+ min-width: 0;
161
+ }
162
+
163
+ .filterField .valueWrapper :global(.dbc-flex > span:first-child) {
164
+ flex: 1;
165
+ min-width: 0;
166
+ overflow: hidden;
167
+ text-overflow: ellipsis;
168
+ white-space: nowrap;
169
+ }
170
+
171
+ /* Keep ClearButton + chevron from stretching */
172
+ .filterField .valueWrapper :global(.dbc-flex) > *:not(span:first-child) {
173
+ flex: 0 0 auto;
174
+ }
175
+
176
+ /* For MultiSelect: label + chip should truncate nicely */
177
+ .filterField .valueWrapper > div > button > span:first-child {
178
+ display: inline-flex;
179
+ align-items: center;
180
+ gap: var(--spacing-xxs);
181
+
182
+ flex: 1;
183
+ min-width: 0;
184
+ overflow: hidden;
185
+ text-overflow: ellipsis;
186
+ white-space: nowrap;
187
+ }
188
+
189
+ /* Emphasize chosen value text in active state (but slightly less "boldy") */
190
+ .filterField.active .valueWrapper input {
191
+ font-weight: 550;
94
192
  }
193
+ .filterField.active .valueWrapper :global(button[data-forminput]),
194
+ .filterField.active .valueWrapper > div > button {
195
+ font-weight: 550 !important;
196
+ }
197
+
198
+ /* Icons calmer by default; crisp on hover/focus */
199
+ .filterField.active .valueWrapper svg {
200
+ opacity: 0.72;
201
+ }
202
+ .filterField.active .valueWrapper:hover svg,
203
+ .filterField.active .valueWrapper :global(button[data-forminput]):focus-visible svg,
204
+ .filterField.active .valueWrapper > div > button:focus-visible svg {
205
+ opacity: 1;
206
+ }
207
+
208
+ /* =========================
209
+ INPUT styling
210
+ ========================= */
95
211
 
96
212
  .filterField input {
97
213
  appearance: none;
@@ -103,6 +219,11 @@
103
219
  inline-size: auto;
104
220
  min-inline-size: 10ch;
105
221
  block-size: 100%;
222
+ border-top-left-radius: 0;
223
+ border-bottom-left-radius: 0;
224
+
225
+ /* a tiny bit more comfort */
226
+ padding-block: calc(var(--spacing-3xs) + var(--density));
106
227
  }
107
228
 
108
229
  .filterField button {
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import type { JSX } from 'react';
2
+ import type { JSX, ReactNode } from 'react';
3
3
  type Variant = 'default' | 'primary' | 'outlined';
4
4
  type Size = 'sm' | 'md' | 'lg';
5
5
  interface CheckboxProps {
@@ -7,8 +7,8 @@ interface CheckboxProps {
7
7
  onChange?: (checked: boolean, event: React.MouseEvent<HTMLButtonElement>) => void;
8
8
  variant?: Variant;
9
9
  disabled?: boolean;
10
- /** Text shown next to the box (per-item label) */
11
- label?: string;
10
+ modified?: boolean;
11
+ label?: ReactNode;
12
12
  size?: Size;
13
13
  containerLabel?: string;
14
14
  error?: string;
@@ -17,15 +17,9 @@ interface CheckboxProps {
17
17
  labelWidth?: string;
18
18
  fullWidth?: boolean;
19
19
  required?: boolean;
20
- /**
21
- * If true, do NOT wrap with InputContainer.
22
- * Use this inside CheckboxGroup (so you don't get group-form layout per item).
23
- */
24
20
  noContainer?: boolean;
25
- /** Optional id for accessibility (label htmlFor) */
26
21
  id?: string;
27
- /** Data attributes pass-through */
28
22
  'data-cy'?: string;
29
23
  }
30
- export declare function Checkbox({ checked: controlled, onChange, variant, disabled, label, size, containerLabel, error, helpText, orientation, labelWidth, fullWidth, required, noContainer, id, 'data-cy': dataCy, }: CheckboxProps): JSX.Element;
24
+ export declare function Checkbox({ checked: controlled, onChange, variant, disabled, label, size, modified, containerLabel, error, helpText, orientation, labelWidth, fullWidth, required, noContainer, id, 'data-cy': dataCy, }: CheckboxProps): JSX.Element;
31
25
  export {};
@@ -4,7 +4,7 @@ import { Check } from 'lucide-react';
4
4
  import { useId, useState } from 'react';
5
5
  import styles from './Checkbox.module.css';
6
6
  import { InputContainer } from '../input-container/InputContainer';
7
- export function Checkbox({ checked: controlled, onChange, variant = 'outlined', disabled, label, size = 'md', containerLabel, error, helpText, orientation = 'horizontal', labelWidth = '120px', fullWidth = false, required = false, noContainer = false, id, 'data-cy': dataCy, }) {
7
+ export function Checkbox({ checked: controlled, onChange, variant = 'outlined', disabled, label, size = 'md', modified, containerLabel, error, helpText, orientation = 'horizontal', labelWidth = '160px', fullWidth = false, required = false, noContainer = false, id, 'data-cy': dataCy, }) {
8
8
  const [internal, setInternal] = useState(false);
9
9
  const isChecked = controlled !== null && controlled !== void 0 ? controlled : internal;
10
10
  const generatedId = useId();
@@ -19,9 +19,7 @@ export function Checkbox({ checked: controlled, onChange, variant = 'outlined',
19
19
  const content = (_jsxs("span", { className: styles.container, "data-cy": dataCy, children: [_jsx("button", { id: controlId, disabled: disabled, type: "button", role: "checkbox", "aria-checked": isChecked, "aria-disabled": disabled || undefined, "aria-invalid": Boolean(error) || undefined, onClick: toggle, className: [styles.checkbox, isChecked ? styles.checked : '', styles[variant], styles[size]]
20
20
  .filter(Boolean)
21
21
  .join(' '), children: isChecked && _jsx(Check, { className: styles.icon }) }), label && (_jsx("label", { className: styles.label, htmlFor: controlId, children: label }))] }));
22
- // For CheckboxGroup use-case
23
- if (noContainer || (!containerLabel && !error))
22
+ if (noContainer)
24
23
  return content;
25
- // Standalone form field use-case
26
- return (_jsx(InputContainer, { label: containerLabel, htmlFor: controlId, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, fullWidth: fullWidth, required: required, children: content }));
24
+ return (_jsx(InputContainer, { modified: modified, label: containerLabel, htmlFor: controlId, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, fullWidth: fullWidth, required: required, children: content }));
27
25
  }
@@ -66,7 +66,7 @@ export function CheckboxGroup({ label, options, selectedValues, onChange, onTogg
66
66
  return (_jsxs("div", { className: wrapperClassName, "data-cy": dataCy, style: { ['--checkboxgroup-action-min-width']: actionMinWidth }, children: [label && _jsx("span", { className: styles.groupLabel, children: label }), _jsx("div", { className: itemsClassName, role: "group", "aria-label": label, children: options.map(opt => {
67
67
  const isChecked = selectedSet.has(opt.value);
68
68
  const isDisabled = disabled || !!opt.disabled;
69
- return (_jsx(Checkbox, { label: opt.label, checked: isChecked, disabled: isDisabled, variant: variant, size: size, onChange: () => {
69
+ return (_jsx(Checkbox, { label: opt.label, checked: isChecked, disabled: isDisabled, variant: variant, size: "sm", onChange: () => {
70
70
  if (isDisabled)
71
71
  return;
72
72
  toggleValue(opt.value);
@@ -30,7 +30,7 @@
30
30
  .contained {
31
31
  padding: 0 var(--spacing-xs);
32
32
  border: var(--border-width-thin) solid var(--color-border-subtle);
33
- border-radius: var(--border-radius-md);
33
+ border-radius: var(--border-radius-default);
34
34
  background: var(--color-bg-surface);
35
35
  }
36
36
 
@@ -15,6 +15,7 @@ export type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size
15
15
  buttonIcon?: React.ReactNode;
16
16
  tooltip?: React.ReactNode;
17
17
  tooltipPlacement?: 'top' | 'right' | 'bottom' | 'left';
18
+ modified?: boolean;
18
19
  };
19
20
  /**
20
21
  * Explicit exported type annotation is required with --isolatedDeclarations.
@@ -23,9 +23,7 @@ function mergeRefs(...refs) {
23
23
  */
24
24
  export const Input = forwardRef(function Input({
25
25
  // InputContainer props
26
- label, error, helpText, orientation = 'horizontal', labelWidth = '120px', fullWidth = false, required,
27
- // ✅ Input-level tooltip props
28
- tooltip, tooltipPlacement = 'right',
26
+ label, error, helpText, orientation = 'vertical', labelWidth = '160px', fullWidth = false, required, tooltip, tooltipPlacement = 'right', modified,
29
27
  // Input-only props
30
28
  icon, autoFocus, minWidth, width, inputSize = 'md', variant = 'outlined', onClear, onButtonClick, buttonLabel, buttonIcon,
31
29
  // Native input props
@@ -50,7 +48,7 @@ id, style, className, ...inputProps }, ref) {
50
48
  placement: tooltipPlacement,
51
49
  offset: 8,
52
50
  });
53
- return (_jsx(InputContainer, { label: label, htmlFor: inputId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, children: _jsxs("div", { style: rootStyle, className: [
51
+ return (_jsx(InputContainer, { label: label, htmlFor: inputId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, modified: modified, children: _jsxs("div", { style: rootStyle, className: [
54
52
  styles.container,
55
53
  fullWidth ? styles.fullWidth : '',
56
54
  onClear ? styles.withClear : '',
@@ -3,8 +3,8 @@
3
3
  /* inline-flex makes width behave weird in parents */
4
4
  display: inline-flex;
5
5
  align-items: stretch;
6
+ flex-grow: 1;
6
7
  gap: 0;
7
-
8
8
  /* width control */
9
9
  inline-size: var(--input-width, auto);
10
10
  min-inline-size: var(--input-min-width, 0);
@@ -33,14 +33,13 @@
33
33
  flex: 1 1 auto;
34
34
  min-inline-size: 0; /* critical */
35
35
  inline-size: 100%;
36
-
36
+ background: var(--color-bg-surface);
37
37
  font-family: var(--font-family);
38
38
  font-size: var(--font-size-sm);
39
39
  line-height: var(--line-height-normal);
40
40
  box-sizing: border-box;
41
41
  text-overflow: ellipsis;
42
42
 
43
- background-color: var(--color-bg-surface);
44
43
  border: var(--border-width-thin) solid var(--color-border-default);
45
44
  border-radius: var(--border-radius-default);
46
45
 
@@ -53,6 +52,13 @@
53
52
  box-shadow var(--transition-fast) var(--ease-standard);
54
53
  }
55
54
 
55
+ .input:disabled {
56
+ background-color: var(--color-disabled-bg);
57
+ border: 0;
58
+ color: var(--color-disabled-fg);
59
+ cursor: not-allowed;
60
+ opacity: 0.5;
61
+ }
56
62
  /* Button group styling */
57
63
  .withButton .input {
58
64
  border-top-right-radius: 0;
@@ -75,26 +81,19 @@
75
81
  }
76
82
 
77
83
  .input:focus-visible {
78
- outline: none;
79
84
  border-color: var(--color-border-selected);
80
- box-shadow: var(--focus-ring);
81
85
  }
82
86
 
83
87
  /* Variants */
84
88
  .filled {
85
89
  background-color: var(--color-bg-surface);
86
- border: 0;
87
90
  }
88
91
  .standalone {
89
92
  border-radius: var(--border-radius-rounded);
90
93
  background-color: var(--color-bg-surface);
91
94
  box-shadow: var(--shadow-xs), var(--shadow-md);
92
95
  }
93
- .standalone:focus-visible {
94
- outline: none;
95
- border-color: var(--color-border-selected);
96
- box-shadow: var(--focus-ring);
97
- }
96
+
98
97
  .outlined {
99
98
  background-color: transparent;
100
99
  }
@@ -11,5 +11,6 @@ export interface InputContainerProps {
11
11
  orientation?: 'vertical' | 'horizontal';
12
12
  labelWidth?: string;
13
13
  labelAlignment?: 'top' | 'center';
14
+ modified?: boolean;
14
15
  }
15
- export declare function InputContainer({ label, htmlFor, error, helpText, helpTextAddition, fullWidth, required, children, orientation, labelWidth, }: InputContainerProps): React.ReactElement;
16
+ export declare function InputContainer({ label, htmlFor, error, helpText, helpTextAddition, fullWidth, required, children, orientation, labelWidth, modified, }: InputContainerProps): React.ReactElement;
@@ -1,14 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import styles from './InputContainer.module.css';
3
- export function InputContainer({ label, htmlFor, error, helpText, helpTextAddition, fullWidth = false, required = false, children, orientation = 'horizontal', labelWidth = '120px', }) {
3
+ export function InputContainer({ label, htmlFor, error, helpText, helpTextAddition, fullWidth = false, required = false, children, orientation = 'horizontal', labelWidth = '160px', modified = false, }) {
4
4
  const message = error !== null && error !== void 0 ? error : helpText;
5
5
  const messageClass = error ? styles.errorText : styles.helpText;
6
6
  const renderLabel = label && (_jsxs("label", { className: styles.label, htmlFor: htmlFor, children: [label, required && _jsx("span", { className: styles.required, children: " *" })] }));
7
7
  const renderMessageRow = (message || helpTextAddition) && (_jsxs("div", { className: `${messageClass} ${styles.messageRow}`, children: [_jsx("span", { children: message }), helpTextAddition && _jsx("span", { className: styles.helpTextAddition, children: helpTextAddition })] }));
8
8
  if (orientation === 'vertical') {
9
- return (_jsxs("div", { className: "dbc-flex dbc-flex-column dbc-gap-xs", style: { width: fullWidth ? '100%' : undefined }, children: [renderLabel, children, renderMessageRow] }));
9
+ return (_jsxs("div", { "data-modified": modified ? true : undefined, className: `dbc-flex dbc-flex-column dbc-gap-xs ${styles.inputContainer}`, style: { width: fullWidth ? '100%' : undefined }, children: [renderLabel, children, renderMessageRow] }));
10
10
  }
11
- return (_jsx("div", { className: styles.inputContainer, style: {
11
+ return (_jsx("div", { "data-modified": modified ? true : undefined, className: styles.inputContainer, style: {
12
12
  '--label-width': labelWidth,
13
13
  width: fullWidth ? '100%' : undefined,
14
14
  }, children: _jsxs("div", { className: `${styles.horizontal} dbc-flex dbc-flex-column dbc-gap-xs`, children: [_jsxs("div", { className: `${styles.labelContainer} dbc-flex dbc-items-center dbc-gap-xs`, children: [renderLabel, children] }), renderMessageRow] }) }));
@@ -6,6 +6,12 @@
6
6
  gap: var(--gap);
7
7
  }
8
8
 
9
+ .label {
10
+ color: var(--color-fg-default);
11
+ font-size: var(--font-size-sm);
12
+ font-weight: var(--font-weight-medium);
13
+ }
14
+
9
15
  .horizontal .errorText,
10
16
  .horizontal .helpText {
11
17
  margin-left: calc(var(--label-width) + var(--gap));
@@ -32,3 +38,62 @@
32
38
  color: var(--color-status-error);
33
39
  font-weight: bold;
34
40
  }
41
+
42
+ /* ---------------- MODIFIED FIELD (DIRECT CONTROL TINT) ---------------- */
43
+
44
+ /* Optional scan cue: left bar only (no box around control) */
45
+ .inputContainer[data-modified] {
46
+ border-left: var(--border-width-thick) solid var(--color-status-warning-border);
47
+ padding-left: var(--spacing-xs);
48
+ }
49
+
50
+ /**
51
+ * Tint "real controls" directly.
52
+ * This covers:
53
+ * - native input/textarea
54
+ * - button-based components (Select trigger, Checkbox button, etc.)
55
+ * - combobox triggers
56
+ */
57
+ .inputContainer[data-modified]
58
+ :is(input, textarea, button[data-forminput], [role='combobox'][data-forminput]) {
59
+ background-color: color-mix(in srgb, var(--color-status-warning-bg) 45%, var(--color-bg-surface));
60
+ }
61
+
62
+ /**
63
+ * If your controls also have borders, nudge them slightly warmer.
64
+ * (Keep subtle so it doesn't look like validation.)
65
+ */
66
+ .inputContainer[data-modified]
67
+ :is(input, textarea, button[data-forminput], [role='combobox'][data-forminput]) {
68
+ border-color: color-mix(
69
+ in srgb,
70
+ var(--color-status-warning-border) 35%,
71
+ var(--color-border-default)
72
+ );
73
+ }
74
+
75
+ /**
76
+ * Checkbox/Radio special case:
77
+ * - Their "label" is inside children.
78
+ * - InputContainer’s own label uses .label class.
79
+ * We want to tint ONLY the child labels, not the container label column.
80
+ */
81
+ .inputContainer[data-modified] label:not(.label) {
82
+ background-color: color-mix(in srgb, var(--color-status-warning-bg) 35%, transparent);
83
+ border-radius: var(--border-radius-default);
84
+ padding: 2px 6px;
85
+ }
86
+
87
+ /* Correct disabled selector (no accidental “actionable” tint) */
88
+ .inputContainer[data-modified]
89
+ :is(input, textarea, button[data-forminput], [role='combobox'][data-forminput]):disabled,
90
+ .inputContainer[data-modified]
91
+ :is(
92
+ input,
93
+ textarea,
94
+ button[data-forminput],
95
+ [role='combobox'][data-forminput]
96
+ )[aria-disabled='true'] {
97
+ background-color: var(--color-disabled-bg);
98
+ border-color: var(--color-disabled-border);
99
+ }
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import type { JSX } from 'react';
3
+ type Variant = 'default' | 'primary' | 'outlined';
4
+ type Size = 'sm' | 'md' | 'lg';
5
+ export interface RadioButtonProps {
6
+ /** group name (required for radio behavior) */
7
+ name: string;
8
+ /** this option's value */
9
+ value: string;
10
+ /** controlled selected value for the group */
11
+ selectedValue?: string;
12
+ /** uncontrolled checked (rare; prefer selectedValue on group) */
13
+ checked?: boolean;
14
+ /** called with (value, event) */
15
+ onChange?: (value: string, event: React.ChangeEvent<HTMLInputElement>) => void;
16
+ disabled?: boolean;
17
+ label?: string;
18
+ variant?: Variant;
19
+ size?: Size;
20
+ containerLabel?: string;
21
+ error?: string;
22
+ helpText?: string;
23
+ orientation?: 'vertical' | 'horizontal';
24
+ labelWidth?: string;
25
+ fullWidth?: boolean;
26
+ required?: boolean;
27
+ /**
28
+ * If true, do NOT wrap with InputContainer.
29
+ * Use inside RadioGroup.
30
+ */
31
+ noContainer?: boolean;
32
+ id?: string;
33
+ 'data-cy'?: string;
34
+ }
35
+ export declare function RadioButton({ name, value, selectedValue, checked, onChange, disabled, label, variant, size, containerLabel, error, helpText, orientation, labelWidth, fullWidth, required, noContainer, id, 'data-cy': dataCy, }: RadioButtonProps): JSX.Element;
36
+ export {};
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useId } from 'react';
4
+ import styles from './RadioButtons.module.css';
5
+ import { InputContainer } from '../input-container/InputContainer';
6
+ export function RadioButton({ name, value, selectedValue, checked, onChange, disabled, label, variant = 'outlined', size = 'md', containerLabel, error, helpText, orientation = 'horizontal', labelWidth = '160px', fullWidth = false, required = false, noContainer = false, id, 'data-cy': dataCy, }) {
7
+ const generatedId = useId();
8
+ const controlId = id !== null && id !== void 0 ? id : `radio-${generatedId}`;
9
+ const isChecked = selectedValue !== undefined ? selectedValue === value : Boolean(checked);
10
+ const content = (_jsxs("span", { className: styles.container, "data-cy": dataCy, children: [_jsxs("span", { className: styles.controlWrap, children: [_jsx("input", { id: controlId, className: styles.input, type: "radio", name: name, value: value, checked: isChecked, disabled: disabled, required: required, "aria-invalid": Boolean(error) || undefined, onChange: e => {
11
+ if (disabled)
12
+ return;
13
+ onChange === null || onChange === void 0 ? void 0 : onChange(e.target.value, e);
14
+ } }), _jsx("span", { className: [
15
+ styles.radio,
16
+ isChecked ? styles.checked : '',
17
+ disabled ? styles.disabled : '',
18
+ styles[variant],
19
+ styles[size],
20
+ ]
21
+ .filter(Boolean)
22
+ .join(' '), "aria-hidden": "true", children: _jsx("span", { className: styles.dot }) })] }), label && (_jsx("label", { className: styles.label, htmlFor: controlId, children: label }))] }));
23
+ if (noContainer || (!containerLabel && !error))
24
+ return content;
25
+ return (_jsx(InputContainer, { label: containerLabel, htmlFor: controlId, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, fullWidth: fullWidth, required: required, children: content }));
26
+ }
@@ -0,0 +1,25 @@
1
+ import type { JSX } from 'react';
2
+ export type RadioOption<V extends string = string> = {
3
+ label: string;
4
+ value: V;
5
+ disabled?: boolean;
6
+ };
7
+ export interface RadioGroupProps<V extends string = string> {
8
+ name?: string;
9
+ value: V | null;
10
+ onChange: (value: V) => void;
11
+ options: RadioOption<V>[];
12
+ disabled?: boolean;
13
+ label?: string;
14
+ error?: string;
15
+ helpText?: string;
16
+ required?: boolean;
17
+ orientation?: 'vertical' | 'horizontal';
18
+ labelWidth?: string;
19
+ fullWidth?: boolean;
20
+ modified?: boolean;
21
+ /** layout of radios themselves */
22
+ direction?: 'row' | 'column';
23
+ 'data-cy'?: string;
24
+ }
25
+ export declare function RadioButtonGroup<V extends string = string>({ name, value, onChange, options, disabled, label, error, helpText, required, orientation, labelWidth, fullWidth, modified, direction, 'data-cy': dataCy, }: RadioGroupProps<V>): JSX.Element;
@@ -0,0 +1,19 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useId } from 'react';
4
+ import { RadioButton } from './RadioButton';
5
+ import { InputContainer } from '../input-container/InputContainer';
6
+ export function RadioButtonGroup({ name, value, onChange, options, disabled = false, label, error, helpText, required = false, orientation = 'horizontal', labelWidth = '160px', fullWidth = false, modified = false, direction = 'column', 'data-cy': dataCy, }) {
7
+ const generated = useId();
8
+ const groupName = name !== null && name !== void 0 ? name : `radio-group-${generated}`;
9
+ const content = (_jsx("div", { role: "radiogroup", "aria-invalid": Boolean(error) || undefined, "data-cy": dataCy, style: {
10
+ display: 'flex',
11
+ flexDirection: direction,
12
+ gap: '8px',
13
+ }, children: options.map(opt => (_jsx(RadioButton, { noContainer: true, name: groupName, value: String(opt.value), selectedValue: value !== null && value !== void 0 ? value : undefined, disabled: disabled || opt.disabled, label: opt.label, required: required, onChange: v => onChange(v), "data-cy": dataCy ? `${dataCy}-option-${String(opt.value)}` : undefined }, String(opt.value)))) }));
14
+ // If no InputContainer props were provided, just return group content
15
+ if (!label && !error)
16
+ return content;
17
+ // Wrap once as a proper field
18
+ return (_jsx(InputContainer, { label: label, htmlFor: undefined, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, fullWidth: fullWidth, required: required, modified: modified, children: content }));
19
+ }