@dbcdk/react-components 0.0.8 → 0.0.10
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/README.md +167 -0
- package/dist/components/__stories__/_data/tabs.d.ts +9 -0
- package/dist/components/__stories__/_data/tabs.js +31 -0
- 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/circle/Circle.d.ts +4 -1
- package/dist/components/circle/Circle.js +2 -2
- package/dist/components/circle/Circle.module.css +54 -2
- package/dist/components/code-block/CodeBlock.module.css +1 -1
- package/dist/components/datetime-picker/DateTimePicker.d.ts +4 -7
- package/dist/components/datetime-picker/DateTimePicker.js +117 -64
- package/dist/components/datetime-picker/dateTimeHelpers.d.ts +14 -2
- package/dist/components/datetime-picker/dateTimeHelpers.js +32 -17
- package/dist/components/forms/checkbox/Checkbox.d.ts +2 -8
- package/dist/components/forms/checkbox/Checkbox.js +3 -5
- 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 +9 -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 +1 -0
- package/dist/components/hyperlink/Hyperlink.js +5 -1
- package/dist/components/icon/Icon.module.css +1 -0
- package/dist/components/interval-select/IntervalSelect.js +1 -1
- package/dist/components/nav-bar/NavBar.d.ts +24 -6
- package/dist/components/overlay/modal/provider/ModalProvider.d.ts +2 -2
- package/dist/components/overlay/modal/provider/ModalProvider.js +24 -25
- package/dist/components/overlay/side-panel/SidePanel.d.ts +12 -4
- package/dist/components/overlay/side-panel/SidePanel.js +60 -4
- package/dist/components/overlay/side-panel/SidePanel.module.css +151 -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/page-layout/PageLayout.js +0 -2
- package/dist/components/popover/Popover.js +33 -14
- package/dist/components/popover/Popover.module.css +0 -4
- package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.d.ts +5 -5
- package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +16 -8
- 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.js +25 -46
- package/dist/components/skeleton-loader/SkeletonLoader.d.ts +1 -1
- package/dist/components/skeleton-loader/SkeletonLoader.js +15 -12
- 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.js +4 -4
- package/dist/components/table/Table.module.css +168 -60
- 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/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/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 +22 -3
- package/dist/styles/styles.css +22 -3
- package/dist/styles/themes/dbc/dark.css +1 -1
- package/dist/styles/themes/dbc/light.css +2 -1
- package/package.json +1 -1
|
@@ -5,9 +5,9 @@ import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'r
|
|
|
5
5
|
import { Button } from '../../components/button/Button';
|
|
6
6
|
import { Input } from '../../components/forms/input/Input';
|
|
7
7
|
import { Popover } from '../../components/popover/Popover';
|
|
8
|
+
import { isoFromLocalDate, isoFromLocalParts, localDateFromIso, maskRange, maskSingle, parseLooseDateOrDateTime, parseLooseRange, utcMillisFromIso, toMaskedFromDate, } from './dateTimeHelpers';
|
|
8
9
|
import styles from './DateTimePicker.module.css';
|
|
9
|
-
|
|
10
|
-
/* ---------- Date grid helpers (UTC) ---------- */
|
|
10
|
+
/* ---------- Date grid helpers (UTC date-only cells) ---------- */
|
|
11
11
|
const dUTC = (y, m, day) => new Date(Date.UTC(y, m, day));
|
|
12
12
|
const addDaysUTC = (utcDate, n) => dUTC(utcDate.getUTCFullYear(), utcDate.getUTCMonth(), utcDate.getUTCDate() + n);
|
|
13
13
|
const startOfMonthUTC = (utcDate) => dUTC(utcDate.getUTCFullYear(), utcDate.getUTCMonth(), 1);
|
|
@@ -15,14 +15,16 @@ const endOfMonthUTC = (utcDate) => dUTC(utcDate.getUTCFullYear(), utcDate.getUTC
|
|
|
15
15
|
const sameDayUTC = (a, b) => a.getUTCFullYear() === b.getUTCFullYear() &&
|
|
16
16
|
a.getUTCMonth() === b.getUTCMonth() &&
|
|
17
17
|
a.getUTCDate() === b.getUTCDate();
|
|
18
|
-
|
|
18
|
+
// IMPORTANT: treat local calendar date as the source of truth for the grid.
|
|
19
|
+
// Build an equivalent "UTC date-only" for comparisons.
|
|
20
|
+
const toUTCDateOnlyFromLocal = (local) => dUTC(local.getFullYear(), local.getMonth(), local.getDate());
|
|
19
21
|
const startOfWeekUTC = (utcDate, weekStartsOn) => {
|
|
20
22
|
const dow = utcDate.getUTCDay();
|
|
21
23
|
const diff = (dow - weekStartsOn + 7) % 7;
|
|
22
24
|
return addDaysUTC(utcDate, -diff);
|
|
23
25
|
};
|
|
24
26
|
function buildMonthGrid(anchorLocalDate, weekStartsOn) {
|
|
25
|
-
const anchorUTC =
|
|
27
|
+
const anchorUTC = toUTCDateOnlyFromLocal(anchorLocalDate);
|
|
26
28
|
const firstUTC = startOfWeekUTC(startOfMonthUTC(anchorUTC), weekStartsOn);
|
|
27
29
|
const cells = [];
|
|
28
30
|
for (let i = 0; i < 42; i++)
|
|
@@ -33,13 +35,6 @@ const isBetweenUTC = (d, a, b) => {
|
|
|
33
35
|
const t = +d, s = +a, e = +b;
|
|
34
36
|
return t >= Math.min(s, e) && t <= Math.max(s, e);
|
|
35
37
|
};
|
|
36
|
-
function composeLocalDateTimeISO(utcDateOnly, hh, mm) {
|
|
37
|
-
const y = utcDateOnly.getUTCFullYear();
|
|
38
|
-
const m = utcDateOnly.getUTCMonth();
|
|
39
|
-
const d = utcDateOnly.getUTCDate();
|
|
40
|
-
const local = new Date(y, m, d, hh, mm, 0, 0);
|
|
41
|
-
return local.toISOString();
|
|
42
|
-
}
|
|
43
38
|
/* ---------- Formatting (exposed but input uses mask) ---------- */
|
|
44
39
|
function defaultFormatDate(d, { locale, enableTime }) {
|
|
45
40
|
const opts = enableTime
|
|
@@ -57,40 +52,77 @@ function defaultFormatRange(s, e, opts) {
|
|
|
57
52
|
return '';
|
|
58
53
|
}
|
|
59
54
|
const cx = (...classes) => classes.filter(Boolean).join(' ');
|
|
60
|
-
export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'single', value, onChange, enableTime = false, timeStep = 15, min, max, locale = typeof navigator !== 'undefined' ? navigator.language : 'da-DK', weekStartsOn = 1, presets, inputProps, formatDate = defaultFormatDate,
|
|
61
|
-
formatRange = defaultFormatRange, // still exposed, not used for input text
|
|
62
|
-
}, _ref) {
|
|
55
|
+
export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'single', value, onChange, enableTime = false, timeStep = 15, min, max, locale = typeof navigator !== 'undefined' ? navigator.language : 'da-DK', weekStartsOn = 1, presets, inputProps, formatDate = defaultFormatDate, formatRange = defaultFormatRange, }, _ref) {
|
|
63
56
|
void formatDate;
|
|
64
57
|
void formatRange;
|
|
65
58
|
const popRef = useRef(null);
|
|
66
59
|
const todayLocal = useMemo(() => new Date(), []);
|
|
60
|
+
// ---- local anchor from controlled value ----
|
|
67
61
|
const initialAnchor = useMemo(() => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
var _a, _b;
|
|
63
|
+
if (mode === 'single') {
|
|
64
|
+
if (typeof value === 'string')
|
|
65
|
+
return (_a = localDateFromIso(value)) !== null && _a !== void 0 ? _a : todayLocal;
|
|
66
|
+
return todayLocal;
|
|
67
|
+
}
|
|
68
|
+
if (mode === 'range' && value && typeof value === 'object' && 'start' in value && value.start) {
|
|
69
|
+
return (_b = localDateFromIso(value.start)) !== null && _b !== void 0 ? _b : todayLocal;
|
|
70
|
+
}
|
|
72
71
|
return todayLocal;
|
|
73
72
|
}, [mode, value, todayLocal]);
|
|
74
73
|
const [monthAnchor, setMonthAnchor] = useState(initialAnchor);
|
|
74
|
+
useEffect(() => setMonthAnchor(initialAnchor), [initialAnchor]);
|
|
75
|
+
// time defaults (local)
|
|
75
76
|
const [timeHH, setTimeHH] = useState(todayLocal.getHours());
|
|
76
77
|
const [timeMM, setTimeMM] = useState(Math.floor(todayLocal.getMinutes() / timeStep) * timeStep);
|
|
78
|
+
// If datetime value changes externally, keep HH/MM in sync
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (mode === 'single' && enableTime && typeof value === 'string') {
|
|
81
|
+
const d = localDateFromIso(value);
|
|
82
|
+
if (!d)
|
|
83
|
+
return;
|
|
84
|
+
setTimeHH(d.getHours());
|
|
85
|
+
setTimeMM(Math.floor(d.getMinutes() / timeStep) * timeStep);
|
|
86
|
+
}
|
|
87
|
+
}, [mode, enableTime, value, timeStep]);
|
|
77
88
|
const [hoverUTC, setHoverUTC] = useState(null);
|
|
78
89
|
const cellsUTC = useMemo(() => buildMonthGrid(monthAnchor, weekStartsOn), [monthAnchor, weekStartsOn]);
|
|
79
|
-
const monthStartUTC = useMemo(() => startOfMonthUTC(
|
|
80
|
-
const monthEndUTC = useMemo(() => endOfMonthUTC(
|
|
90
|
+
const monthStartUTC = useMemo(() => startOfMonthUTC(toUTCDateOnlyFromLocal(monthAnchor)), [monthAnchor]);
|
|
91
|
+
const monthEndUTC = useMemo(() => endOfMonthUTC(toUTCDateOnlyFromLocal(monthAnchor)), [monthAnchor]);
|
|
81
92
|
const weekdayFmt = useMemo(() => new Intl.DateTimeFormat(locale, { weekday: 'short' }), [locale]);
|
|
82
93
|
const monthFmt = useMemo(() => new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' }), [locale]);
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
// ---- selection state for grid (compare in UTC date-only) ----
|
|
95
|
+
const selectedUTC_single = useMemo(() => {
|
|
96
|
+
if (mode !== 'single')
|
|
97
|
+
return null;
|
|
98
|
+
if (typeof value !== 'string')
|
|
99
|
+
return null;
|
|
100
|
+
const d = localDateFromIso(value);
|
|
101
|
+
if (!d)
|
|
102
|
+
return null;
|
|
103
|
+
return toUTCDateOnlyFromLocal(d);
|
|
104
|
+
}, [mode, value]);
|
|
105
|
+
const selectedUTC_start = useMemo(() => {
|
|
106
|
+
if (mode !== 'range' || !value || typeof value !== 'object' || !('start' in value))
|
|
107
|
+
return null;
|
|
108
|
+
if (!value.start)
|
|
109
|
+
return null;
|
|
110
|
+
const d = localDateFromIso(value.start);
|
|
111
|
+
return d ? toUTCDateOnlyFromLocal(d) : null;
|
|
112
|
+
}, [mode, value]);
|
|
113
|
+
const selectedUTC_end = useMemo(() => {
|
|
114
|
+
if (mode !== 'range' || !value || typeof value !== 'object' || !('end' in value))
|
|
115
|
+
return null;
|
|
116
|
+
if (!value.end)
|
|
117
|
+
return null;
|
|
118
|
+
const d = localDateFromIso(value.end);
|
|
119
|
+
return d ? toUTCDateOnlyFromLocal(d) : null;
|
|
120
|
+
}, [mode, value]);
|
|
90
121
|
const isDisabledUTC = useCallback((utcDay) => {
|
|
91
|
-
|
|
122
|
+
// Interpret min/max as local-day constraints for UI
|
|
123
|
+
if (min && utcDay < toUTCDateOnlyFromLocal(min))
|
|
92
124
|
return true;
|
|
93
|
-
if (max && utcDay >
|
|
125
|
+
if (max && utcDay > toUTCDateOnlyFromLocal(max))
|
|
94
126
|
return true;
|
|
95
127
|
return false;
|
|
96
128
|
}, [min, max]);
|
|
@@ -98,29 +130,33 @@ formatRange = defaultFormatRange, // still exposed, not used for input text
|
|
|
98
130
|
var _a, _b;
|
|
99
131
|
if (isDisabledUTC(utcDay))
|
|
100
132
|
return;
|
|
133
|
+
// utcDay's UTC Y/M/D corresponds to the *local calendar day* shown in UI.
|
|
134
|
+
const y = utcDay.getUTCFullYear();
|
|
135
|
+
const m0 = utcDay.getUTCMonth();
|
|
136
|
+
const d = utcDay.getUTCDate();
|
|
101
137
|
if (mode === 'single') {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
else {
|
|
107
|
-
onChange(new Date(utcDay.getTime()));
|
|
108
|
-
}
|
|
138
|
+
const iso = enableTime
|
|
139
|
+
? isoFromLocalParts(y, m0, d, timeHH, timeMM)
|
|
140
|
+
: isoFromLocalParts(y, m0, d, 0, 0);
|
|
141
|
+
onChange(iso);
|
|
109
142
|
(_a = popRef.current) === null || _a === void 0 ? void 0 : _a.close();
|
|
110
143
|
return;
|
|
111
144
|
}
|
|
112
|
-
|
|
145
|
+
// range (date-only in UI, but emitted as ISO instants at local midnight)
|
|
146
|
+
const curr = value && typeof value === 'object' && 'start' in value
|
|
147
|
+
? value
|
|
148
|
+
: { start: null, end: null };
|
|
149
|
+
const picked = isoFromLocalParts(y, m0, d, 0, 0);
|
|
113
150
|
if (!curr.start || (curr.start && curr.end)) {
|
|
114
|
-
onChange({ start:
|
|
115
|
-
|
|
116
|
-
else {
|
|
117
|
-
const startUTC = toUTCDateOnly(curr.start);
|
|
118
|
-
const endUTC = utcDay;
|
|
119
|
-
const s = new Date(Math.min(+startUTC, +endUTC));
|
|
120
|
-
const e = new Date(Math.max(+startUTC, +endUTC));
|
|
121
|
-
onChange({ start: s, end: e });
|
|
122
|
-
(_b = popRef.current) === null || _b === void 0 ? void 0 : _b.close();
|
|
151
|
+
onChange({ start: picked, end: null });
|
|
152
|
+
return;
|
|
123
153
|
}
|
|
154
|
+
const a = utcMillisFromIso(curr.start);
|
|
155
|
+
const b = utcMillisFromIso(picked);
|
|
156
|
+
const start = a <= b ? curr.start : picked;
|
|
157
|
+
const end = a <= b ? picked : curr.start;
|
|
158
|
+
onChange({ start, end });
|
|
159
|
+
(_b = popRef.current) === null || _b === void 0 ? void 0 : _b.close();
|
|
124
160
|
};
|
|
125
161
|
const gridRef = useRef(null);
|
|
126
162
|
useEffect(() => {
|
|
@@ -143,7 +179,7 @@ formatRange = defaultFormatRange, // still exposed, not used for input text
|
|
|
143
179
|
].includes(e.key)) {
|
|
144
180
|
e.preventDefault();
|
|
145
181
|
}
|
|
146
|
-
const anchor =
|
|
182
|
+
const anchor = toUTCDateOnlyFromLocal(monthAnchor);
|
|
147
183
|
const move = (days) => setMonthAnchor(prev => addDaysLocal(prev, days));
|
|
148
184
|
switch (e.key) {
|
|
149
185
|
case 'ArrowLeft':
|
|
@@ -177,20 +213,32 @@ formatRange = defaultFormatRange, // still exposed, not used for input text
|
|
|
177
213
|
}, [monthAnchor]);
|
|
178
214
|
const hours = useMemo(() => Array.from({ length: 24 }, (_, i) => i), []);
|
|
179
215
|
const minutes = useMemo(() => Array.from({ length: Math.floor(60 / (timeStep || 1)) }, (_, i) => i * (timeStep || 1)), [timeStep]);
|
|
180
|
-
// ---- Input display:
|
|
216
|
+
// ---- Input display: always local ----
|
|
181
217
|
const formatted = useMemo(() => {
|
|
182
|
-
var _a, _b;
|
|
183
218
|
if (mode === 'single') {
|
|
184
|
-
|
|
219
|
+
if (typeof value !== 'string')
|
|
220
|
+
return '';
|
|
221
|
+
const d = localDateFromIso(value);
|
|
222
|
+
return d ? toMaskedFromDate(d, enableTime) : '';
|
|
185
223
|
}
|
|
186
224
|
const v = value;
|
|
187
|
-
|
|
225
|
+
const s = typeof (v === null || v === void 0 ? void 0 : v.start) === 'string' ? localDateFromIso(v.start) : null;
|
|
226
|
+
const e = typeof (v === null || v === void 0 ? void 0 : v.end) === 'string' ? localDateFromIso(v.end) : null;
|
|
227
|
+
const ss = s ? toMaskedFromDate(s, false) : '';
|
|
228
|
+
const ee = e ? toMaskedFromDate(e, false) : '';
|
|
229
|
+
if (ss && ee)
|
|
230
|
+
return `${ss} – ${ee}`;
|
|
231
|
+
if (ss)
|
|
232
|
+
return `${ss} –`;
|
|
233
|
+
if (ee)
|
|
234
|
+
return `– ${ee}`;
|
|
235
|
+
return '';
|
|
188
236
|
}, [mode, value, enableTime]);
|
|
189
237
|
const [text, setText] = useState(formatted);
|
|
190
|
-
const [dirty, setDirty] = useState(false);
|
|
238
|
+
const [dirty, setDirty] = useState(false);
|
|
191
239
|
useEffect(() => {
|
|
192
240
|
if (!dirty)
|
|
193
|
-
setText(formatted);
|
|
241
|
+
setText(formatted);
|
|
194
242
|
}, [formatted, dirty]);
|
|
195
243
|
const commitTypedValue = useCallback(() => {
|
|
196
244
|
if (!text.trim()) {
|
|
@@ -203,16 +251,19 @@ formatRange = defaultFormatRange, // still exposed, not used for input text
|
|
|
203
251
|
}
|
|
204
252
|
if (mode === 'single') {
|
|
205
253
|
const dLocal = parseLooseDateOrDateTime(text);
|
|
206
|
-
if (dLocal)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
254
|
+
if (!dLocal)
|
|
255
|
+
return;
|
|
256
|
+
// If enableTime=false, parseLooseDateOrDateTime returns 00:00 local -> still OK.
|
|
257
|
+
onChange(isoFromLocalDate(dLocal));
|
|
258
|
+
setMonthAnchor(dLocal);
|
|
259
|
+
setDirty(false);
|
|
211
260
|
return;
|
|
212
261
|
}
|
|
213
262
|
const r = parseLooseRange(text);
|
|
214
263
|
if (r) {
|
|
215
|
-
|
|
264
|
+
const startIso = isoFromLocalParts(r.start.getFullYear(), r.start.getMonth(), r.start.getDate(), 0, 0);
|
|
265
|
+
const endIso = isoFromLocalParts(r.end.getFullYear(), r.end.getMonth(), r.end.getDate(), 0, 0);
|
|
266
|
+
onChange({ start: startIso, end: endIso });
|
|
216
267
|
setMonthAnchor(r.start);
|
|
217
268
|
setDirty(false);
|
|
218
269
|
}
|
|
@@ -238,10 +289,10 @@ formatRange = defaultFormatRange, // still exposed, not used for input text
|
|
|
238
289
|
const fallbackPlaceholder = mode === 'single' ? 'Vælg dato' : 'Vælg interval';
|
|
239
290
|
return (_jsx(Popover, { ref: popRef, trigger: toggle => {
|
|
240
291
|
var _a, _b;
|
|
241
|
-
return (_jsx("div", { onClick: toggle, className: styles.triggerWrap, children: _jsx(Input, { ...inputProps, placeholder: (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.placeholder) !== null && _a !== void 0 ? _a : fallbackPlaceholder, value: dirty ? text : formatted, onInput: e => {
|
|
292
|
+
return (_jsx("div", { onClick: toggle, className: styles.triggerWrap, children: _jsx(Input, { ...inputProps, autoComplete: "off", autoCorrect: "off", autoCapitalize: "off", spellCheck: "false", placeholder: (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.placeholder) !== null && _a !== void 0 ? _a : fallbackPlaceholder, value: dirty ? text : formatted, onInput: e => {
|
|
242
293
|
setDirty(true);
|
|
243
294
|
const raw = e.target.value;
|
|
244
|
-
const masked = mode === 'single' ? maskSingle(raw, enableTime) : maskRange(raw,
|
|
295
|
+
const masked = mode === 'single' ? maskSingle(raw, enableTime) : maskRange(raw, false);
|
|
245
296
|
setText(masked);
|
|
246
297
|
}, onBlur: commitTypedValue, onKeyDown: e => {
|
|
247
298
|
var _a;
|
|
@@ -256,14 +307,16 @@ formatRange = defaultFormatRange, // still exposed, not used for input text
|
|
|
256
307
|
}, viewportPadding: 8, children: _jsxs("div", { className: cx(styles.panel, !!(presets === null || presets === void 0 ? void 0 : presets.length) && styles.panelWithPresets), children: [(presets === null || presets === void 0 ? void 0 : presets.length) ? (_jsxs("div", { className: styles.presetsCol, children: [_jsx("div", { className: styles.presetsLabel, children: "Forvalg" }), _jsxs("div", { className: styles.presetsList, children: [presets.map(p => (_jsx(Button, { variant: "outlined", size: "sm", onClick: () => {
|
|
257
308
|
var _a;
|
|
258
309
|
const r = p.getRange();
|
|
259
|
-
|
|
310
|
+
const startIso = isoFromLocalParts(r.start.getFullYear(), r.start.getMonth(), r.start.getDate(), 0, 0);
|
|
311
|
+
const endIso = isoFromLocalParts(r.end.getFullYear(), r.end.getMonth(), r.end.getDate(), 0, 0);
|
|
312
|
+
onChange({ start: startIso, end: endIso });
|
|
260
313
|
setDirty(false);
|
|
261
|
-
setText(
|
|
314
|
+
setText(`${toMaskedFromDate(r.start, false)} – ${toMaskedFromDate(r.end, false)}`);
|
|
262
315
|
setMonthAnchor(r.start);
|
|
263
316
|
(_a = popRef.current) === null || _a === void 0 ? void 0 : _a.close();
|
|
264
317
|
}, children: p.label }, p.label))), mode === 'range' && (_jsx(Button, { variant: "danger", size: "sm", onClick: clear, icon: _jsx(X, { size: 14 }), children: "Ryd" }))] })] })) : null, _jsxs("div", { className: styles.calendarArea, children: [_jsxs("div", { className: styles.header, children: [_jsx(Button, { variant: "outlined", size: "sm", "aria-label": "Forrige m\u00E5ned", icon: _jsx(ChevronLeft, { size: 16 }), onClick: () => setMonthAnchor(addMonthsLocal(monthAnchor, -1)) }), _jsx("div", { "aria-live": "polite", className: styles.headerTitle, children: monthFmt.format(monthAnchor) }), _jsx(Button, { variant: "outlined", size: "sm", "aria-label": "N\u00E6ste m\u00E5ned", icon: _jsx(ChevronRight, { size: 16 }), onClick: () => setMonthAnchor(addMonthsLocal(monthAnchor, 1)) })] }), _jsx("div", { className: styles.weekRow, "aria-hidden": true, children: Array.from({ length: 7 }, (_, i) => (i + weekStartsOn) % 7).map(dow => (_jsx("div", { className: styles.weekCell, children: weekdayFmt.format(dUTC(2024, 8, dow + 1)).slice(0, 2) }, dow))) }), _jsx("div", { ref: gridRef, role: "grid", "aria-label": "Kalender", tabIndex: 0, className: styles.grid, onMouseLeave: () => setHoverUTC(null), children: cellsUTC.map((utcDay, idx) => {
|
|
265
318
|
const inThisMonth = utcDay >= monthStartUTC && utcDay <= monthEndUTC;
|
|
266
|
-
const isToday = sameDayUTC(utcDay,
|
|
319
|
+
const isToday = sameDayUTC(utcDay, toUTCDateOnlyFromLocal(todayLocal));
|
|
267
320
|
const disabledDay = isDisabledUTC(utcDay);
|
|
268
321
|
let selected = false;
|
|
269
322
|
let inRange = false;
|
|
@@ -2,10 +2,22 @@ export declare const digits: (s: string) => string;
|
|
|
2
2
|
export declare function maskDateEU(text: string): string;
|
|
3
3
|
export declare function maskTimeHM(text: string): string;
|
|
4
4
|
export declare function maskSingle(text: string, enableTime: boolean): string;
|
|
5
|
-
export declare function maskRange(text: string,
|
|
5
|
+
export declare function maskRange(text: string, _enableTime: boolean): string;
|
|
6
6
|
export declare const pad2: (n: number) => string;
|
|
7
|
+
export type UtcIsoString = string;
|
|
8
|
+
export declare function isUtcIsoString(v: unknown): v is UtcIsoString;
|
|
9
|
+
export declare function utcMillisFromIso(iso: UtcIsoString): number;
|
|
10
|
+
/**
|
|
11
|
+
* Build a *local* Date from y/m/d/hh/mm and return UTC ISO string (Z).
|
|
12
|
+
* This keeps the UI meaning "local wall time", while emitting a stable UTC instant.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isoFromLocalParts(y: number, m0: number, // 0-based
|
|
15
|
+
d: number, hh?: number, mm?: number): UtcIsoString;
|
|
16
|
+
/** Convert a local Date to a UTC ISO string (Z). */
|
|
17
|
+
export declare function isoFromLocalDate(dLocal: Date): UtcIsoString;
|
|
18
|
+
/** For anchoring the calendar safely from a UTC ISO string. */
|
|
19
|
+
export declare function localDateFromIso(iso: UtcIsoString): Date | null;
|
|
7
20
|
export declare function toMaskedFromDate(d: Date, enableTime: boolean): string;
|
|
8
|
-
export declare function toMaskedRange(start: Date | null, end: Date | null, enableTime: boolean): string;
|
|
9
21
|
export declare function parseLooseDateOrDateTime(input: string): Date | null;
|
|
10
22
|
export declare function parseLooseRange(input: string): {
|
|
11
23
|
start: Date;
|
|
@@ -35,19 +35,44 @@ export function maskSingle(text, enableTime) {
|
|
|
35
35
|
return timePart ? `${datePart} ${timePart}` : datePart;
|
|
36
36
|
}
|
|
37
37
|
// Range: mask both sides around common separators (–, -, to, til)
|
|
38
|
-
export function maskRange(text,
|
|
38
|
+
export function maskRange(text, _enableTime) {
|
|
39
|
+
// NOTE: range is date-only in the UI
|
|
39
40
|
const sepRe = /\s*(?:–|-|to|til)\s*/i;
|
|
40
41
|
const parts = text.split(sepRe);
|
|
41
|
-
if (parts.length === 1)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const a = maskSingle(parts[0], enableTime);
|
|
46
|
-
const b = maskSingle(parts.slice(1).join(' '), enableTime); // everything after first sep
|
|
42
|
+
if (parts.length === 1)
|
|
43
|
+
return maskSingle(parts[0], false);
|
|
44
|
+
const a = maskSingle(parts[0], false);
|
|
45
|
+
const b = maskSingle(parts.slice(1).join(' '), false);
|
|
47
46
|
return `${a} – ${b}`.trim();
|
|
48
47
|
}
|
|
49
48
|
// Pad helper
|
|
50
49
|
export const pad2 = (n) => String(n).padStart(2, '0');
|
|
50
|
+
export function isUtcIsoString(v) {
|
|
51
|
+
return typeof v === 'string' && /Z$/.test(v) && !Number.isNaN(Date.parse(v));
|
|
52
|
+
}
|
|
53
|
+
export function utcMillisFromIso(iso) {
|
|
54
|
+
return Date.parse(iso);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Build a *local* Date from y/m/d/hh/mm and return UTC ISO string (Z).
|
|
58
|
+
* This keeps the UI meaning "local wall time", while emitting a stable UTC instant.
|
|
59
|
+
*/
|
|
60
|
+
export function isoFromLocalParts(y, m0, // 0-based
|
|
61
|
+
d, hh = 0, mm = 0) {
|
|
62
|
+
return new Date(y, m0, d, hh, mm, 0, 0).toISOString();
|
|
63
|
+
}
|
|
64
|
+
/** Convert a local Date to a UTC ISO string (Z). */
|
|
65
|
+
export function isoFromLocalDate(dLocal) {
|
|
66
|
+
return dLocal.toISOString();
|
|
67
|
+
}
|
|
68
|
+
/** For anchoring the calendar safely from a UTC ISO string. */
|
|
69
|
+
export function localDateFromIso(iso) {
|
|
70
|
+
const ms = Date.parse(iso);
|
|
71
|
+
if (Number.isNaN(ms))
|
|
72
|
+
return null;
|
|
73
|
+
return new Date(ms); // local rendering
|
|
74
|
+
}
|
|
75
|
+
/* ---------- Formatting (UI shows local) ---------- */
|
|
51
76
|
// From Date → "DD-MM-YYYY" or "DD-MM-YYYY HH:mm" (local time)
|
|
52
77
|
export function toMaskedFromDate(d, enableTime) {
|
|
53
78
|
const dd = pad2(d.getDate());
|
|
@@ -58,16 +83,6 @@ export function toMaskedFromDate(d, enableTime) {
|
|
|
58
83
|
out += ` ${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
59
84
|
return out;
|
|
60
85
|
}
|
|
61
|
-
// From start/end → "DD-MM-YYYY – DD-MM-YYYY" (+ optional time)
|
|
62
|
-
export function toMaskedRange(start, end, enableTime) {
|
|
63
|
-
if (start && end)
|
|
64
|
-
return `${toMaskedFromDate(start, enableTime)} – ${toMaskedFromDate(end, enableTime)}`;
|
|
65
|
-
if (start)
|
|
66
|
-
return `${toMaskedFromDate(start, enableTime)} –`;
|
|
67
|
-
if (end)
|
|
68
|
-
return `– ${toMaskedFromDate(end, enableTime)}`;
|
|
69
|
-
return '';
|
|
70
|
-
}
|
|
71
86
|
/* ---------- Parsing helpers (no deps) ---------- */
|
|
72
87
|
// Accepts: YYYY-MM-DD, DD-MM-YYYY, DD/MM/YYYY, DD.MM.YYYY (+ optional HH:mm)
|
|
73
88
|
export function parseLooseDateOrDateTime(input) {
|
|
@@ -7,7 +7,7 @@ interface CheckboxProps {
|
|
|
7
7
|
onChange?: (checked: boolean, event: React.MouseEvent<HTMLButtonElement>) => void;
|
|
8
8
|
variant?: Variant;
|
|
9
9
|
disabled?: boolean;
|
|
10
|
-
|
|
10
|
+
modified?: boolean;
|
|
11
11
|
label?: string;
|
|
12
12
|
size?: Size;
|
|
13
13
|
containerLabel?: 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
|
}
|
|
@@ -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 = '
|
|
27
|
-
// ✅ Input-level tooltip props
|
|
28
|
-
tooltip, tooltipPlacement = 'right',
|
|
26
|
+
label, error, helpText, orientation = 'horizontal', 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 : '',
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
display: inline-flex;
|
|
5
5
|
align-items: stretch;
|
|
6
6
|
gap: 0;
|
|
7
|
-
|
|
8
7
|
/* width control */
|
|
9
8
|
inline-size: var(--input-width, auto);
|
|
10
9
|
min-inline-size: var(--input-min-width, 0);
|
|
@@ -33,14 +32,13 @@
|
|
|
33
32
|
flex: 1 1 auto;
|
|
34
33
|
min-inline-size: 0; /* critical */
|
|
35
34
|
inline-size: 100%;
|
|
36
|
-
|
|
35
|
+
background: var(--color-bg-surface);
|
|
37
36
|
font-family: var(--font-family);
|
|
38
37
|
font-size: var(--font-size-sm);
|
|
39
38
|
line-height: var(--line-height-normal);
|
|
40
39
|
box-sizing: border-box;
|
|
41
40
|
text-overflow: ellipsis;
|
|
42
41
|
|
|
43
|
-
background-color: var(--color-bg-surface);
|
|
44
42
|
border: var(--border-width-thin) solid var(--color-border-default);
|
|
45
43
|
border-radius: var(--border-radius-default);
|
|
46
44
|
|
|
@@ -53,6 +51,13 @@
|
|
|
53
51
|
box-shadow var(--transition-fast) var(--ease-standard);
|
|
54
52
|
}
|
|
55
53
|
|
|
54
|
+
.input:disabled {
|
|
55
|
+
background-color: var(--color-disabled-bg);
|
|
56
|
+
border: 0;
|
|
57
|
+
color: var(--color-disabled-fg);
|
|
58
|
+
cursor: not-allowed;
|
|
59
|
+
opacity: 0.5;
|
|
60
|
+
}
|
|
56
61
|
/* Button group styling */
|
|
57
62
|
.withButton .input {
|
|
58
63
|
border-top-right-radius: 0;
|
|
@@ -75,26 +80,19 @@
|
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
.input:focus-visible {
|
|
78
|
-
outline: none;
|
|
79
83
|
border-color: var(--color-border-selected);
|
|
80
|
-
box-shadow: var(--focus-ring);
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
/* Variants */
|
|
84
87
|
.filled {
|
|
85
88
|
background-color: var(--color-bg-surface);
|
|
86
|
-
border: 0;
|
|
87
89
|
}
|
|
88
90
|
.standalone {
|
|
89
91
|
border-radius: var(--border-radius-rounded);
|
|
90
92
|
background-color: var(--color-bg-surface);
|
|
91
93
|
box-shadow: var(--shadow-xs), var(--shadow-md);
|
|
92
94
|
}
|
|
93
|
-
|
|
94
|
-
outline: none;
|
|
95
|
-
border-color: var(--color-border-selected);
|
|
96
|
-
box-shadow: var(--focus-ring);
|
|
97
|
-
}
|
|
95
|
+
|
|
98
96
|
.outlined {
|
|
99
97
|
background-color: transparent;
|
|
100
98
|
}
|
|
@@ -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-md);
|
|
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
|
+
}
|