@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.
- package/dist/components/accordion/Accordion.d.ts +27 -0
- package/dist/components/accordion/Accordion.js +66 -0
- package/dist/components/accordion/Accordion.module.css +87 -0
- package/dist/components/button/Button.module.css +1 -0
- package/dist/components/card/Card.d.ts +21 -3
- package/dist/components/card/Card.js +17 -2
- package/dist/components/card/Card.module.css +59 -0
- package/dist/components/circle/Circle.d.ts +5 -1
- package/dist/components/circle/Circle.js +2 -2
- package/dist/components/circle/Circle.module.css +60 -4
- package/dist/components/code-block/CodeBlock.js +1 -1
- package/dist/components/code-block/CodeBlock.module.css +30 -17
- package/dist/components/copy-button/CopyButton.d.ts +1 -0
- package/dist/components/copy-button/CopyButton.js +10 -2
- package/dist/components/datetime-picker/DateTimePicker.d.ts +4 -8
- package/dist/components/datetime-picker/DateTimePicker.js +72 -92
- package/dist/components/datetime-picker/dateTimeHelpers.d.ts +14 -12
- package/dist/components/datetime-picker/dateTimeHelpers.js +25 -45
- package/dist/components/filter-field/FilterField.js +16 -11
- package/dist/components/filter-field/FilterField.module.css +133 -12
- package/dist/components/forms/checkbox/Checkbox.d.ts +4 -10
- package/dist/components/forms/checkbox/Checkbox.js +3 -5
- package/dist/components/forms/checkbox-group/CheckboxGroup.js +1 -1
- package/dist/components/forms/checkbox-group/CheckboxGroup.module.css +1 -1
- package/dist/components/forms/input/Input.d.ts +1 -0
- package/dist/components/forms/input/Input.js +2 -4
- package/dist/components/forms/input/Input.module.css +10 -11
- package/dist/components/forms/input-container/InputContainer.d.ts +2 -1
- package/dist/components/forms/input-container/InputContainer.js +3 -3
- package/dist/components/forms/input-container/InputContainer.module.css +65 -0
- package/dist/components/forms/radio-buttons/RadioButton.d.ts +36 -0
- package/dist/components/forms/radio-buttons/RadioButton.js +26 -0
- package/dist/components/forms/radio-buttons/RadioButtonGroup.d.ts +25 -0
- package/dist/components/forms/radio-buttons/RadioButtonGroup.js +19 -0
- package/dist/components/forms/radio-buttons/RadioButtons.module.css +117 -0
- package/dist/components/forms/select/Select.d.ts +1 -1
- package/dist/components/forms/select/Select.js +3 -3
- package/dist/components/forms/text-area/Textarea.js +3 -3
- package/dist/components/forms/text-area/Textarea.module.css +8 -1
- package/dist/components/headline/Headline.d.ts +2 -7
- package/dist/components/headline/Headline.js +5 -2
- package/dist/components/headline/Headline.module.css +61 -2
- package/dist/components/hyperlink/Hyperlink.d.ts +19 -6
- package/dist/components/hyperlink/Hyperlink.js +35 -7
- package/dist/components/hyperlink/Hyperlink.module.css +50 -2
- package/dist/components/icon/Icon.module.css +1 -0
- package/dist/components/interval-select/IntervalSelect.js +1 -1
- package/dist/components/menu/Menu.d.ts +32 -0
- package/dist/components/menu/Menu.js +73 -13
- package/dist/components/menu/Menu.module.css +72 -4
- package/dist/components/nav-bar/NavBar.d.ts +24 -6
- package/dist/components/overlay/modal/Modal.module.css +2 -2
- package/dist/components/overlay/side-panel/SidePanel.d.ts +12 -4
- package/dist/components/overlay/side-panel/SidePanel.js +77 -4
- package/dist/components/overlay/side-panel/SidePanel.module.css +149 -28
- package/dist/components/overlay/side-panel/useSidePanel.d.ts +1 -1
- package/dist/components/overlay/side-panel/useSidePanel.js +2 -2
- package/dist/components/overlay/tooltip/useTooltipTrigger.js +4 -2
- package/dist/components/page-layout/PageLayout.js +0 -2
- package/dist/components/popover/Popover.js +1 -1
- package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.d.ts +5 -5
- package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +36 -24
- package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.module.css +0 -3
- package/dist/components/sidebar/components/sidebar-container/SidebarContainer.d.ts +3 -1
- package/dist/components/sidebar/components/sidebar-container/SidebarContainer.js +4 -3
- package/dist/components/sidebar/components/sidebar-container/SidebarContainer.module.css +109 -79
- package/dist/components/sidebar/components/sidebar-items/SidebarItems.js +16 -3
- package/dist/components/sidebar/components/sidebar-items/SidebarItems.module.css +20 -0
- package/dist/components/sidebar/providers/SidebarProvider.d.ts +4 -1
- package/dist/components/sidebar/providers/SidebarProvider.js +85 -58
- package/dist/components/skeleton-loader/SkeletonLoader.d.ts +1 -1
- package/dist/components/skeleton-loader/SkeletonLoader.js +15 -12
- package/dist/components/split-button/SplitButton.d.ts +1 -1
- package/dist/components/split-button/SplitButton.js +3 -1
- package/dist/components/split-button/SplitButton.module.css +4 -4
- package/dist/components/state-page/StatePage.d.ts +9 -0
- package/dist/components/state-page/StatePage.js +20 -0
- package/dist/components/state-page/StatePage.module.css +9 -0
- package/dist/components/state-page/empty.d.ts +2 -0
- package/dist/components/state-page/empty.js +2 -0
- package/dist/components/state-page/error.d.ts +2 -0
- package/dist/components/state-page/error.js +2 -0
- package/dist/components/state-page/notFound.d.ts +2 -0
- package/dist/components/state-page/notFound.js +2 -0
- package/dist/components/sticky-footer-layout/StickyFooterLayout.d.ts +19 -0
- package/dist/components/sticky-footer-layout/StickyFooterLayout.js +27 -0
- package/dist/components/table/Table.d.ts +9 -4
- package/dist/components/table/Table.js +6 -9
- package/dist/components/table/Table.module.css +180 -59
- package/dist/components/table/components/empty-state/EmptyState.d.ts +1 -1
- package/dist/components/table/components/empty-state/EmptyState.js +6 -7
- package/dist/components/table/components/table-settings/TableSettings.d.ts +13 -3
- package/dist/components/table/components/table-settings/TableSettings.js +55 -4
- package/dist/components/table/tanstack.d.ts +12 -1
- package/dist/components/table/tanstack.js +75 -23
- package/dist/components/toast/Toast.js +5 -1
- package/dist/components/toast/Toast.module.css +40 -15
- package/dist/components/toast/provider/ToastProvider.js +1 -0
- package/dist/hooks/useTableSettings.d.ts +23 -4
- package/dist/hooks/useTableSettings.js +64 -17
- package/dist/hooks/useTimeDuration.js +9 -3
- package/dist/hooks/useViewportFill.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -1
- package/dist/src/styles/styles.css +60 -25
- package/dist/styles/animation.d.ts +5 -0
- package/dist/styles/animation.js +5 -0
- package/dist/styles/styles.css +60 -25
- package/dist/styles/themes/dbc/dark.css +1 -1
- package/dist/styles/themes/dbc/light.css +2 -1
- package/dist/utils/localStorage.utils.d.ts +19 -0
- package/dist/utils/localStorage.utils.js +78 -0
- 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
|
-
/*
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
103
|
+
/* Ensure the control inside can stretch/shrink */
|
|
104
|
+
.filterField .valueWrapper > * {
|
|
84
105
|
height: 100%;
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
11
|
-
label?:
|
|
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 = '
|
|
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
|
-
|
|
23
|
-
if (noContainer || (!containerLabel && !error))
|
|
22
|
+
if (noContainer)
|
|
24
23
|
return content;
|
|
25
|
-
|
|
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:
|
|
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);
|
|
@@ -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 = '
|
|
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
|
-
|
|
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 = '
|
|
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:
|
|
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
|
+
}
|