@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
|
@@ -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 {
|
|
8
|
+
import { isoFromLocalDate, isoFromLocalParts, localDateFromIso, maskRange, maskSingle, parseLooseDateOrDateTime, parseLooseRange, utcMillisFromIso, toMaskedFromDate, } from './dateTimeHelpers';
|
|
9
9
|
import styles from './DateTimePicker.module.css';
|
|
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++)
|
|
@@ -55,77 +57,72 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
|
|
|
55
57
|
void formatRange;
|
|
56
58
|
const popRef = useRef(null);
|
|
57
59
|
const todayLocal = useMemo(() => new Date(), []);
|
|
58
|
-
// ----
|
|
60
|
+
// ---- local anchor from controlled value ----
|
|
59
61
|
const initialAnchor = useMemo(() => {
|
|
60
62
|
var _a, _b;
|
|
61
63
|
if (mode === 'single') {
|
|
62
|
-
if (
|
|
63
|
-
return
|
|
64
|
-
if (!enableTime && typeof value === 'string')
|
|
65
|
-
return (_a = localDateFromYMD(value)) !== null && _a !== void 0 ? _a : todayLocal;
|
|
64
|
+
if (typeof value === 'string')
|
|
65
|
+
return (_a = localDateFromIso(value)) !== null && _a !== void 0 ? _a : todayLocal;
|
|
66
66
|
return todayLocal;
|
|
67
67
|
}
|
|
68
68
|
if (mode === 'range' && value && typeof value === 'object' && 'start' in value && value.start) {
|
|
69
|
-
return (_b =
|
|
69
|
+
return (_b = localDateFromIso(value.start)) !== null && _b !== void 0 ? _b : todayLocal;
|
|
70
70
|
}
|
|
71
71
|
return todayLocal;
|
|
72
|
-
}, [mode, value,
|
|
72
|
+
}, [mode, value, todayLocal]);
|
|
73
73
|
const [monthAnchor, setMonthAnchor] = useState(initialAnchor);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
setMonthAnchor(initialAnchor);
|
|
77
|
-
}, [initialAnchor]);
|
|
74
|
+
useEffect(() => setMonthAnchor(initialAnchor), [initialAnchor]);
|
|
75
|
+
// time defaults (local)
|
|
78
76
|
const [timeHH, setTimeHH] = useState(todayLocal.getHours());
|
|
79
77
|
const [timeMM, setTimeMM] = useState(Math.floor(todayLocal.getMinutes() / timeStep) * timeStep);
|
|
80
|
-
// If
|
|
78
|
+
// If datetime value changes externally, keep HH/MM in sync
|
|
81
79
|
useEffect(() => {
|
|
82
|
-
if (mode === 'single' && enableTime && typeof value === '
|
|
83
|
-
const d =
|
|
80
|
+
if (mode === 'single' && enableTime && typeof value === 'string') {
|
|
81
|
+
const d = localDateFromIso(value);
|
|
82
|
+
if (!d)
|
|
83
|
+
return;
|
|
84
84
|
setTimeHH(d.getHours());
|
|
85
85
|
setTimeMM(Math.floor(d.getMinutes() / timeStep) * timeStep);
|
|
86
86
|
}
|
|
87
87
|
}, [mode, enableTime, value, timeStep]);
|
|
88
88
|
const [hoverUTC, setHoverUTC] = useState(null);
|
|
89
89
|
const cellsUTC = useMemo(() => buildMonthGrid(monthAnchor, weekStartsOn), [monthAnchor, weekStartsOn]);
|
|
90
|
-
const monthStartUTC = useMemo(() => startOfMonthUTC(
|
|
91
|
-
const monthEndUTC = useMemo(() => endOfMonthUTC(
|
|
90
|
+
const monthStartUTC = useMemo(() => startOfMonthUTC(toUTCDateOnlyFromLocal(monthAnchor)), [monthAnchor]);
|
|
91
|
+
const monthEndUTC = useMemo(() => endOfMonthUTC(toUTCDateOnlyFromLocal(monthAnchor)), [monthAnchor]);
|
|
92
92
|
const weekdayFmt = useMemo(() => new Intl.DateTimeFormat(locale, { weekday: 'short' }), [locale]);
|
|
93
93
|
const monthFmt = useMemo(() => new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' }), [locale]);
|
|
94
|
-
// ----
|
|
94
|
+
// ---- selection state for grid (compare in UTC date-only) ----
|
|
95
95
|
const selectedUTC_single = useMemo(() => {
|
|
96
|
-
if (mode !== 'single'
|
|
96
|
+
if (mode !== 'single')
|
|
97
97
|
return null;
|
|
98
|
-
if (enableTime) {
|
|
99
|
-
if (typeof value !== 'number')
|
|
100
|
-
return null;
|
|
101
|
-
return toUTCDateOnly(new Date(value));
|
|
102
|
-
}
|
|
103
98
|
if (typeof value !== 'string')
|
|
104
99
|
return null;
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
100
|
+
const d = localDateFromIso(value);
|
|
101
|
+
if (!d)
|
|
102
|
+
return null;
|
|
103
|
+
return toUTCDateOnlyFromLocal(d);
|
|
104
|
+
}, [mode, value]);
|
|
108
105
|
const selectedUTC_start = useMemo(() => {
|
|
109
106
|
if (mode !== 'range' || !value || typeof value !== 'object' || !('start' in value))
|
|
110
107
|
return null;
|
|
111
108
|
if (!value.start)
|
|
112
109
|
return null;
|
|
113
|
-
const
|
|
114
|
-
return
|
|
110
|
+
const d = localDateFromIso(value.start);
|
|
111
|
+
return d ? toUTCDateOnlyFromLocal(d) : null;
|
|
115
112
|
}, [mode, value]);
|
|
116
113
|
const selectedUTC_end = useMemo(() => {
|
|
117
114
|
if (mode !== 'range' || !value || typeof value !== 'object' || !('end' in value))
|
|
118
115
|
return null;
|
|
119
116
|
if (!value.end)
|
|
120
117
|
return null;
|
|
121
|
-
const
|
|
122
|
-
return
|
|
118
|
+
const d = localDateFromIso(value.end);
|
|
119
|
+
return d ? toUTCDateOnlyFromLocal(d) : null;
|
|
123
120
|
}, [mode, value]);
|
|
124
121
|
const isDisabledUTC = useCallback((utcDay) => {
|
|
125
|
-
// min/max
|
|
126
|
-
if (min && utcDay <
|
|
122
|
+
// Interpret min/max as local-day constraints for UI
|
|
123
|
+
if (min && utcDay < toUTCDateOnlyFromLocal(min))
|
|
127
124
|
return true;
|
|
128
|
-
if (max && utcDay >
|
|
125
|
+
if (max && utcDay > toUTCDateOnlyFromLocal(max))
|
|
129
126
|
return true;
|
|
130
127
|
return false;
|
|
131
128
|
}, [min, max]);
|
|
@@ -133,33 +130,29 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
|
|
|
133
130
|
var _a, _b;
|
|
134
131
|
if (isDisabledUTC(utcDay))
|
|
135
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();
|
|
136
137
|
if (mode === 'single') {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const d = utcDay.getUTCDate();
|
|
142
|
-
const local = new Date(y, m, d, timeHH, timeMM, 0, 0);
|
|
143
|
-
onChange(local.getTime());
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
// Date-only: emit timezone-free day label
|
|
147
|
-
onChange(ymdFromUTCDateOnly(utcDay));
|
|
148
|
-
}
|
|
138
|
+
const iso = enableTime
|
|
139
|
+
? isoFromLocalParts(y, m0, d, timeHH, timeMM)
|
|
140
|
+
: isoFromLocalParts(y, m0, d, 0, 0);
|
|
141
|
+
onChange(iso);
|
|
149
142
|
(_a = popRef.current) === null || _a === void 0 ? void 0 : _a.close();
|
|
150
143
|
return;
|
|
151
144
|
}
|
|
152
|
-
//
|
|
145
|
+
// range (date-only in UI, but emitted as ISO instants at local midnight)
|
|
153
146
|
const curr = value && typeof value === 'object' && 'start' in value
|
|
154
147
|
? value
|
|
155
148
|
: { start: null, end: null };
|
|
156
|
-
const picked =
|
|
149
|
+
const picked = isoFromLocalParts(y, m0, d, 0, 0);
|
|
157
150
|
if (!curr.start || (curr.start && curr.end)) {
|
|
158
151
|
onChange({ start: picked, end: null });
|
|
159
152
|
return;
|
|
160
153
|
}
|
|
161
|
-
const a =
|
|
162
|
-
const b =
|
|
154
|
+
const a = utcMillisFromIso(curr.start);
|
|
155
|
+
const b = utcMillisFromIso(picked);
|
|
163
156
|
const start = a <= b ? curr.start : picked;
|
|
164
157
|
const end = a <= b ? picked : curr.start;
|
|
165
158
|
onChange({ start, end });
|
|
@@ -186,7 +179,7 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
|
|
|
186
179
|
].includes(e.key)) {
|
|
187
180
|
e.preventDefault();
|
|
188
181
|
}
|
|
189
|
-
const anchor =
|
|
182
|
+
const anchor = toUTCDateOnlyFromLocal(monthAnchor);
|
|
190
183
|
const move = (days) => setMonthAnchor(prev => addDaysLocal(prev, days));
|
|
191
184
|
switch (e.key) {
|
|
192
185
|
case 'ArrowLeft':
|
|
@@ -223,31 +216,26 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
|
|
|
223
216
|
// ---- Input display: always local ----
|
|
224
217
|
const formatted = useMemo(() => {
|
|
225
218
|
if (mode === 'single') {
|
|
226
|
-
if (!value)
|
|
227
|
-
return '';
|
|
228
|
-
if (enableTime) {
|
|
229
|
-
if (typeof value !== 'number')
|
|
230
|
-
return '';
|
|
231
|
-
return toMaskedFromDate(new Date(value), true);
|
|
232
|
-
}
|
|
233
219
|
if (typeof value !== 'string')
|
|
234
220
|
return '';
|
|
235
|
-
|
|
221
|
+
const d = localDateFromIso(value);
|
|
222
|
+
return d ? toMaskedFromDate(d, enableTime) : '';
|
|
236
223
|
}
|
|
237
|
-
// range (date-only)
|
|
238
224
|
const v = value;
|
|
239
|
-
const s = typeof (v === null || v === void 0 ? void 0 : v.start) === 'string' ?
|
|
240
|
-
const e = typeof (v === null || v === void 0 ? void 0 : v.end) === 'string' ?
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (
|
|
244
|
-
return `${
|
|
245
|
-
if (
|
|
246
|
-
return
|
|
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}`;
|
|
247
235
|
return '';
|
|
248
236
|
}, [mode, value, enableTime]);
|
|
249
237
|
const [text, setText] = useState(formatted);
|
|
250
|
-
const [dirty, setDirty] = useState(false);
|
|
238
|
+
const [dirty, setDirty] = useState(false);
|
|
251
239
|
useEffect(() => {
|
|
252
240
|
if (!dirty)
|
|
253
241
|
setText(formatted);
|
|
@@ -265,28 +253,21 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
|
|
|
265
253
|
const dLocal = parseLooseDateOrDateTime(text);
|
|
266
254
|
if (!dLocal)
|
|
267
255
|
return;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
onChange(dLocal.getTime());
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
// Emit date-only string (local calendar day)
|
|
274
|
-
onChange(ymdFromLocalDate(dLocal));
|
|
275
|
-
}
|
|
256
|
+
// If enableTime=false, parseLooseDateOrDateTime returns 00:00 local -> still OK.
|
|
257
|
+
onChange(isoFromLocalDate(dLocal));
|
|
276
258
|
setMonthAnchor(dLocal);
|
|
277
259
|
setDirty(false);
|
|
278
260
|
return;
|
|
279
261
|
}
|
|
280
262
|
const r = parseLooseRange(text);
|
|
281
263
|
if (r) {
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
onChange({ start, end });
|
|
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 });
|
|
286
267
|
setMonthAnchor(r.start);
|
|
287
268
|
setDirty(false);
|
|
288
269
|
}
|
|
289
|
-
}, [text, mode, onChange
|
|
270
|
+
}, [text, mode, onChange]);
|
|
290
271
|
const clear = useCallback(() => {
|
|
291
272
|
if (mode === 'single')
|
|
292
273
|
onChange(null);
|
|
@@ -308,7 +289,7 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
|
|
|
308
289
|
const fallbackPlaceholder = mode === 'single' ? 'Vælg dato' : 'Vælg interval';
|
|
309
290
|
return (_jsx(Popover, { ref: popRef, trigger: toggle => {
|
|
310
291
|
var _a, _b;
|
|
311
|
-
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 => {
|
|
312
293
|
setDirty(true);
|
|
313
294
|
const raw = e.target.value;
|
|
314
295
|
const masked = mode === 'single' ? maskSingle(raw, enableTime) : maskRange(raw, false);
|
|
@@ -326,17 +307,16 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
|
|
|
326
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: () => {
|
|
327
308
|
var _a;
|
|
328
309
|
const r = p.getRange();
|
|
329
|
-
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
onChange({ start, end });
|
|
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 });
|
|
333
313
|
setDirty(false);
|
|
334
|
-
setText(`${
|
|
314
|
+
setText(`${toMaskedFromDate(r.start, false)} – ${toMaskedFromDate(r.end, false)}`);
|
|
335
315
|
setMonthAnchor(r.start);
|
|
336
316
|
(_a = popRef.current) === null || _a === void 0 ? void 0 : _a.close();
|
|
337
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) => {
|
|
338
318
|
const inThisMonth = utcDay >= monthStartUTC && utcDay <= monthEndUTC;
|
|
339
|
-
const isToday = sameDayUTC(utcDay,
|
|
319
|
+
const isToday = sameDayUTC(utcDay, toUTCDateOnlyFromLocal(todayLocal));
|
|
340
320
|
const disabledDay = isDisabledUTC(utcDay);
|
|
341
321
|
let selected = false;
|
|
342
322
|
let inRange = false;
|
|
@@ -2,20 +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
|
|
8
|
-
export declare function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export declare function
|
|
15
|
-
|
|
16
|
-
|
|
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;
|
|
17
20
|
export declare function toMaskedFromDate(d: Date, enableTime: boolean): string;
|
|
18
|
-
export declare function toMaskedFromYMD(ymd: DateOnly): string;
|
|
19
21
|
export declare function parseLooseDateOrDateTime(input: string): Date | null;
|
|
20
22
|
export declare function parseLooseRange(input: string): {
|
|
21
23
|
start: Date;
|
|
@@ -35,56 +35,42 @@ 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');
|
|
51
|
-
export function
|
|
52
|
-
|
|
53
|
-
if (!m)
|
|
54
|
-
return null;
|
|
55
|
-
const y = +m[1];
|
|
56
|
-
const mo = +m[2];
|
|
57
|
-
const d = +m[3];
|
|
58
|
-
if (mo < 1 || mo > 12 || d < 1 || d > 31)
|
|
59
|
-
return null;
|
|
60
|
-
return { y, m: mo, d };
|
|
50
|
+
export function isUtcIsoString(v) {
|
|
51
|
+
return typeof v === 'string' && /Z$/.test(v) && !Number.isNaN(Date.parse(v));
|
|
61
52
|
}
|
|
62
|
-
export function
|
|
63
|
-
|
|
64
|
-
const m = dLocal.getMonth() + 1;
|
|
65
|
-
const d = dLocal.getDate();
|
|
66
|
-
return `${y}-${pad2(m)}-${pad2(d)}`;
|
|
53
|
+
export function utcMillisFromIso(iso) {
|
|
54
|
+
return Date.parse(iso);
|
|
67
55
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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();
|
|
73
63
|
}
|
|
74
|
-
|
|
75
|
-
export function
|
|
76
|
-
|
|
77
|
-
if (!p)
|
|
78
|
-
return NaN;
|
|
79
|
-
return Date.UTC(p.y, p.m - 1, p.d);
|
|
64
|
+
/** Convert a local Date to a UTC ISO string (Z). */
|
|
65
|
+
export function isoFromLocalDate(dLocal) {
|
|
66
|
+
return dLocal.toISOString();
|
|
80
67
|
}
|
|
81
|
-
|
|
82
|
-
export function
|
|
83
|
-
const
|
|
84
|
-
if (
|
|
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))
|
|
85
72
|
return null;
|
|
86
|
-
|
|
87
|
-
return new Date(p.y, p.m - 1, p.d, 12, 0, 0, 0);
|
|
73
|
+
return new Date(ms); // local rendering
|
|
88
74
|
}
|
|
89
75
|
/* ---------- Formatting (UI shows local) ---------- */
|
|
90
76
|
// From Date → "DD-MM-YYYY" or "DD-MM-YYYY HH:mm" (local time)
|
|
@@ -97,12 +83,6 @@ export function toMaskedFromDate(d, enableTime) {
|
|
|
97
83
|
out += ` ${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
98
84
|
return out;
|
|
99
85
|
}
|
|
100
|
-
export function toMaskedFromYMD(ymd) {
|
|
101
|
-
const p = parseYMD(ymd);
|
|
102
|
-
if (!p)
|
|
103
|
-
return '';
|
|
104
|
-
return `${pad2(p.d)}-${pad2(p.m)}-${p.y}`;
|
|
105
|
-
}
|
|
106
86
|
/* ---------- Parsing helpers (no deps) ---------- */
|
|
107
87
|
// Accepts: YYYY-MM-DD, DD-MM-YYYY, DD/MM/YYYY, DD.MM.YYYY (+ optional HH:mm)
|
|
108
88
|
export function parseLooseDateOrDateTime(input) {
|
|
@@ -61,10 +61,18 @@ function OperatorDropdown({ value, onChange, operators, size = 'sm', disabled, }
|
|
|
61
61
|
return (_jsx(Menu.Item, { active: selected, children: _jsxs("button", { type: "button", onClick: () => handleSelect(op), disabled: disabled, children: [_jsx("span", { style: { width: 16, display: 'inline-flex', justifyContent: 'center' }, children: selected ? _jsx(Check, { size: 16 }) : null }), LABELS[op]] }) }, op));
|
|
62
62
|
}) }) }));
|
|
63
63
|
}
|
|
64
|
+
function isFilterActive(value) {
|
|
65
|
+
if (Array.isArray(value))
|
|
66
|
+
return value.length > 0;
|
|
67
|
+
if (typeof value === 'string')
|
|
68
|
+
return value.trim().length > 0;
|
|
69
|
+
return value != null;
|
|
70
|
+
}
|
|
64
71
|
export function FilterField({ field, control, operator, value, onChange, operators, options = [], single = true, size = 'md', label, placeholder = 'Type value…', disabled, 'data-cy': dataCy, ...inputProps }) {
|
|
65
72
|
var _a, _b;
|
|
66
73
|
const [selectedOperator, setSelectedOperator] = useState(operator);
|
|
67
74
|
const ops = useMemo(() => operators !== null && operators !== void 0 ? operators : DEFAULT_TEXT_OPERATORS, [operators]);
|
|
75
|
+
const active = isFilterActive(value);
|
|
68
76
|
// Local state ONLY for input control (to avoid URL->props lag)
|
|
69
77
|
const [localValue, setLocalValue] = useState((_a = value) !== null && _a !== void 0 ? _a : '');
|
|
70
78
|
const debounceRef = useRef(null);
|
|
@@ -92,7 +100,7 @@ export function FilterField({ field, control, operator, value, onChange, operato
|
|
|
92
100
|
debounceRef.current = setTimeout(() => {
|
|
93
101
|
isTypingRef.current = false;
|
|
94
102
|
emit({ value: nextVal });
|
|
95
|
-
}, 250);
|
|
103
|
+
}, 250);
|
|
96
104
|
};
|
|
97
105
|
// Sync internal value when parent value changes (e.g. URL updates)
|
|
98
106
|
useEffect(() => {
|
|
@@ -100,7 +108,6 @@ export function FilterField({ field, control, operator, value, onChange, operato
|
|
|
100
108
|
if (control !== 'input')
|
|
101
109
|
return;
|
|
102
110
|
const incoming = (_a = value) !== null && _a !== void 0 ? _a : '';
|
|
103
|
-
// don't fight the user mid-typing; once parent catches up, we allow sync again
|
|
104
111
|
if (!isTypingRef.current && incoming !== localValue) {
|
|
105
112
|
setLocalValue(incoming);
|
|
106
113
|
}
|
|
@@ -115,25 +122,23 @@ export function FilterField({ field, control, operator, value, onChange, operato
|
|
|
115
122
|
clearTimeout(debounceRef.current);
|
|
116
123
|
};
|
|
117
124
|
}, []);
|
|
118
|
-
return (_jsxs("div", { ...(dataCy ? { 'data-cy': dataCy } : {}), className:
|
|
125
|
+
return (_jsxs("div", { ...(dataCy ? { 'data-cy': dataCy } : {}), className: `${styles.filterField} ${styles[size]} ${active ? styles.active : ''}`, children: [label ? _jsx("span", { className: `${styles.label} ${styles[size]}`, children: label }) : null, _jsx(OperatorDropdown, { value: selectedOperator, onChange: op => emit({ operator: op }), operators: ops, size: size, disabled: disabled }), _jsx("div", { className: `${control === 'input' ? 'dbc-flex dbc-flex-grow' : styles.valueWrapper}`, children: control === 'input' ? (_jsx(Input, { ...inputProps, value: localValue, onChange: e => {
|
|
119
126
|
const next = e.currentTarget.value;
|
|
120
127
|
isTypingRef.current = true;
|
|
121
|
-
setLocalValue(next);
|
|
122
|
-
scheduleEmitValue(next);
|
|
123
|
-
}, inputSize: size, placeholder: placeholder, width: "160px", minWidth: "120px", disabled: disabled, onClear: () => {
|
|
128
|
+
setLocalValue(next);
|
|
129
|
+
scheduleEmitValue(next);
|
|
130
|
+
}, fullWidth: true, inputSize: size, placeholder: placeholder, width: "160px", minWidth: "120px", disabled: disabled, onClear: () => {
|
|
124
131
|
isTypingRef.current = false;
|
|
125
132
|
if (debounceRef.current)
|
|
126
133
|
clearTimeout(debounceRef.current);
|
|
127
134
|
setLocalValue('');
|
|
128
|
-
emit({ value: '' });
|
|
135
|
+
emit({ value: '' });
|
|
129
136
|
} })) : single ? (_jsx(Select, { options: options, selectedValue: (_b = value) !== null && _b !== void 0 ? _b : null, onChange: v => emit({ value: v }), placeholder: placeholder, size: size, variant: "inline", onClear: () => emit({ value: '' }) })) : (_jsx(MultiSelect, { options: options, size: size, variant: "inline", selectedValues: (Array.isArray(value) ? value : []), onChange: v => {
|
|
130
137
|
const current = new Set((Array.isArray(value) ? value : []));
|
|
131
|
-
if (current.has(v))
|
|
138
|
+
if (current.has(v))
|
|
132
139
|
current.delete(v);
|
|
133
|
-
|
|
134
|
-
else {
|
|
140
|
+
else
|
|
135
141
|
current.add(v);
|
|
136
|
-
}
|
|
137
142
|
emit({ value: Array.from(current) });
|
|
138
143
|
}, onClear: () => emit({ value: [] }), children: placeholder })) })] }));
|
|
139
144
|
}
|