@dbcdk/react-components 0.0.6 → 0.0.8
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/datetime-picker/DateTimePicker.js +1 -119
- package/dist/components/datetime-picker/dateTimeHelpers.d.ts +13 -0
- package/dist/components/datetime-picker/dateTimeHelpers.js +119 -0
- package/dist/components/forms/select/Select.d.ts +2 -1
- package/dist/components/forms/select/Select.js +3 -8
- package/dist/components/interval-select/IntervalSelect.d.ts +30 -0
- package/dist/components/interval-select/IntervalSelect.js +82 -0
- package/dist/components/table/tanstack.js +1 -1
- package/package.json +1 -1
|
@@ -6,125 +6,7 @@ import { Button } from '../../components/button/Button';
|
|
|
6
6
|
import { Input } from '../../components/forms/input/Input';
|
|
7
7
|
import { Popover } from '../../components/popover/Popover';
|
|
8
8
|
import styles from './DateTimePicker.module.css';
|
|
9
|
-
|
|
10
|
-
// Pull only 0-9
|
|
11
|
-
const digits = (s) => (s.match(/\d/g) || []).join('');
|
|
12
|
-
// DD-MM-YYYY
|
|
13
|
-
function maskDateEU(text) {
|
|
14
|
-
const d = digits(text).slice(0, 8);
|
|
15
|
-
const dd = d.slice(0, 2);
|
|
16
|
-
const mm = d.slice(2, 4);
|
|
17
|
-
const yyyy = d.slice(4, 8);
|
|
18
|
-
let out = dd;
|
|
19
|
-
if (mm.length)
|
|
20
|
-
out += (out ? '-' : '') + mm;
|
|
21
|
-
if (yyyy.length)
|
|
22
|
-
out += (out ? '-' : '') + yyyy;
|
|
23
|
-
return out;
|
|
24
|
-
}
|
|
25
|
-
// HH:mm (24h)
|
|
26
|
-
function maskTimeHM(text) {
|
|
27
|
-
const d = digits(text).slice(0, 4);
|
|
28
|
-
const hh = d.slice(0, 2);
|
|
29
|
-
const mm = d.slice(2, 4);
|
|
30
|
-
return mm.length ? `${hh}:${mm}` : hh;
|
|
31
|
-
}
|
|
32
|
-
// Single: "DD-MM-YYYY" or "DD-MM-YYYY HH:mm"
|
|
33
|
-
function maskSingle(text, enableTime) {
|
|
34
|
-
let t = text.trim().replace(/\s+/g, ' ');
|
|
35
|
-
if (!enableTime)
|
|
36
|
-
return maskDateEU(t);
|
|
37
|
-
// split date + time by first space or 'T'
|
|
38
|
-
const m = /^(.*?)[ T](.*)$/.exec(t);
|
|
39
|
-
if (!m)
|
|
40
|
-
return maskDateEU(t);
|
|
41
|
-
const datePart = maskDateEU(m[1]);
|
|
42
|
-
const timePart = maskTimeHM(m[2]);
|
|
43
|
-
return timePart ? `${datePart} ${timePart}` : datePart;
|
|
44
|
-
}
|
|
45
|
-
// Range: mask both sides around common separators (–, -, to, til)
|
|
46
|
-
function maskRange(text, enableTime) {
|
|
47
|
-
const sepRe = /\s*(?:–|-|to|til)\s*/i;
|
|
48
|
-
const parts = text.split(sepRe);
|
|
49
|
-
if (parts.length === 1) {
|
|
50
|
-
// user typing first side
|
|
51
|
-
return maskSingle(parts[0], enableTime);
|
|
52
|
-
}
|
|
53
|
-
const a = maskSingle(parts[0], enableTime);
|
|
54
|
-
const b = maskSingle(parts.slice(1).join(' '), enableTime); // everything after first sep
|
|
55
|
-
return `${a} – ${b}`.trim();
|
|
56
|
-
}
|
|
57
|
-
// Pad helper
|
|
58
|
-
const pad2 = (n) => String(n).padStart(2, '0');
|
|
59
|
-
// From Date → "DD-MM-YYYY" or "DD-MM-YYYY HH:mm" (local time)
|
|
60
|
-
function toMaskedFromDate(d, enableTime) {
|
|
61
|
-
const dd = pad2(d.getDate());
|
|
62
|
-
const mm = pad2(d.getMonth() + 1);
|
|
63
|
-
const yyyy = String(d.getFullYear());
|
|
64
|
-
let out = `${dd}-${mm}-${yyyy}`;
|
|
65
|
-
if (enableTime)
|
|
66
|
-
out += ` ${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
67
|
-
return out;
|
|
68
|
-
}
|
|
69
|
-
// From start/end → "DD-MM-YYYY – DD-MM-YYYY" (+ optional time)
|
|
70
|
-
function toMaskedRange(start, end, enableTime) {
|
|
71
|
-
if (start && end)
|
|
72
|
-
return `${toMaskedFromDate(start, enableTime)} – ${toMaskedFromDate(end, enableTime)}`;
|
|
73
|
-
if (start)
|
|
74
|
-
return `${toMaskedFromDate(start, enableTime)} –`;
|
|
75
|
-
if (end)
|
|
76
|
-
return `– ${toMaskedFromDate(end, enableTime)}`;
|
|
77
|
-
return '';
|
|
78
|
-
}
|
|
79
|
-
/* ---------- Parsing helpers (no deps) ---------- */
|
|
80
|
-
// Accepts: YYYY-MM-DD, DD-MM-YYYY, DD/MM/YYYY, DD.MM.YYYY (+ optional HH:mm)
|
|
81
|
-
function parseLooseDateOrDateTime(input) {
|
|
82
|
-
const txt = input.trim().replace(/\s+/g, ' ');
|
|
83
|
-
const dateTimeMatch = /^(?<date>[\d./-]{8,10})(?:[ T](?<hh>\d{1,2}):(?<mm>\d{2}))?$/i.exec(txt);
|
|
84
|
-
if (!(dateTimeMatch === null || dateTimeMatch === void 0 ? void 0 : dateTimeMatch.groups))
|
|
85
|
-
return null;
|
|
86
|
-
const raw = dateTimeMatch.groups.date;
|
|
87
|
-
const hh = dateTimeMatch.groups.hh ? parseInt(dateTimeMatch.groups.hh, 10) : 0;
|
|
88
|
-
const mm = dateTimeMatch.groups.mm ? parseInt(dateTimeMatch.groups.mm, 10) : 0;
|
|
89
|
-
if (hh < 0 || hh > 23 || mm < 0 || mm > 59)
|
|
90
|
-
return null;
|
|
91
|
-
// Try YYYY-MM-DD first
|
|
92
|
-
let y, m, d;
|
|
93
|
-
const mIso = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(raw);
|
|
94
|
-
if (mIso) {
|
|
95
|
-
y = +mIso[1];
|
|
96
|
-
m = +mIso[2] - 1;
|
|
97
|
-
d = +mIso[3];
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
// Try DD-MM-YYYY or DD/MM/YYYY or DD.MM.YYYY
|
|
101
|
-
const mEu = /^(\d{1,2})[./-](\d{1,2})[./-](\d{4})$/.exec(raw);
|
|
102
|
-
if (!mEu)
|
|
103
|
-
return null;
|
|
104
|
-
d = +mEu[1];
|
|
105
|
-
m = +mEu[2] - 1;
|
|
106
|
-
y = +mEu[3];
|
|
107
|
-
}
|
|
108
|
-
const local = new Date(y, m, d, hh, mm, 0, 0);
|
|
109
|
-
if (Number.isNaN(local.getTime()))
|
|
110
|
-
return null;
|
|
111
|
-
// Guard: JS autocorrects invalid dates; re-validate exact Y/M/D
|
|
112
|
-
if (local.getFullYear() !== y || local.getMonth() !== m || local.getDate() !== d)
|
|
113
|
-
return null;
|
|
114
|
-
return local;
|
|
115
|
-
}
|
|
116
|
-
// Parse a range string with separators: "–", "-", "to", "til"
|
|
117
|
-
function parseLooseRange(input) {
|
|
118
|
-
const sep = /\s*(?:–|-|to|til)\s*/i;
|
|
119
|
-
const [a, b] = input.split(sep);
|
|
120
|
-
if (!a || !b)
|
|
121
|
-
return null;
|
|
122
|
-
const s = parseLooseDateOrDateTime(a);
|
|
123
|
-
const e = parseLooseDateOrDateTime(b);
|
|
124
|
-
if (!s || !e)
|
|
125
|
-
return null;
|
|
126
|
-
return s <= e ? { start: s, end: e } : { start: e, end: s };
|
|
127
|
-
}
|
|
9
|
+
import { maskRange, maskSingle, parseLooseDateOrDateTime, parseLooseRange, toMaskedFromDate, toMaskedRange, } from './dateTimeHelpers';
|
|
128
10
|
/* ---------- Date grid helpers (UTC) ---------- */
|
|
129
11
|
const dUTC = (y, m, day) => new Date(Date.UTC(y, m, day));
|
|
130
12
|
const addDaysUTC = (utcDate, n) => dUTC(utcDate.getUTCFullYear(), utcDate.getUTCMonth(), utcDate.getUTCDate() + n);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const digits: (s: string) => string;
|
|
2
|
+
export declare function maskDateEU(text: string): string;
|
|
3
|
+
export declare function maskTimeHM(text: string): string;
|
|
4
|
+
export declare function maskSingle(text: string, enableTime: boolean): string;
|
|
5
|
+
export declare function maskRange(text: string, enableTime: boolean): string;
|
|
6
|
+
export declare const pad2: (n: number) => string;
|
|
7
|
+
export declare function toMaskedFromDate(d: Date, enableTime: boolean): string;
|
|
8
|
+
export declare function toMaskedRange(start: Date | null, end: Date | null, enableTime: boolean): string;
|
|
9
|
+
export declare function parseLooseDateOrDateTime(input: string): Date | null;
|
|
10
|
+
export declare function parseLooseRange(input: string): {
|
|
11
|
+
start: Date;
|
|
12
|
+
end: Date;
|
|
13
|
+
} | null;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* ---------- Mask helpers (no deps) ---------- */
|
|
2
|
+
// Pull only 0-9
|
|
3
|
+
export const digits = (s) => (s.match(/\d/g) || []).join('');
|
|
4
|
+
// DD-MM-YYYY
|
|
5
|
+
export function maskDateEU(text) {
|
|
6
|
+
const d = digits(text).slice(0, 8);
|
|
7
|
+
const dd = d.slice(0, 2);
|
|
8
|
+
const mm = d.slice(2, 4);
|
|
9
|
+
const yyyy = d.slice(4, 8);
|
|
10
|
+
let out = dd;
|
|
11
|
+
if (mm.length)
|
|
12
|
+
out += (out ? '-' : '') + mm;
|
|
13
|
+
if (yyyy.length)
|
|
14
|
+
out += (out ? '-' : '') + yyyy;
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
// HH:mm (24h)
|
|
18
|
+
export function maskTimeHM(text) {
|
|
19
|
+
const d = digits(text).slice(0, 4);
|
|
20
|
+
const hh = d.slice(0, 2);
|
|
21
|
+
const mm = d.slice(2, 4);
|
|
22
|
+
return mm.length ? `${hh}:${mm}` : hh;
|
|
23
|
+
}
|
|
24
|
+
// Single: "DD-MM-YYYY" or "DD-MM-YYYY HH:mm"
|
|
25
|
+
export function maskSingle(text, enableTime) {
|
|
26
|
+
let t = text.trim().replace(/\s+/g, ' ');
|
|
27
|
+
if (!enableTime)
|
|
28
|
+
return maskDateEU(t);
|
|
29
|
+
// split date + time by first space or 'T'
|
|
30
|
+
const m = /^(.*?)[ T](.*)$/.exec(t);
|
|
31
|
+
if (!m)
|
|
32
|
+
return maskDateEU(t);
|
|
33
|
+
const datePart = maskDateEU(m[1]);
|
|
34
|
+
const timePart = maskTimeHM(m[2]);
|
|
35
|
+
return timePart ? `${datePart} ${timePart}` : datePart;
|
|
36
|
+
}
|
|
37
|
+
// Range: mask both sides around common separators (–, -, to, til)
|
|
38
|
+
export function maskRange(text, enableTime) {
|
|
39
|
+
const sepRe = /\s*(?:–|-|to|til)\s*/i;
|
|
40
|
+
const parts = text.split(sepRe);
|
|
41
|
+
if (parts.length === 1) {
|
|
42
|
+
// user typing first side
|
|
43
|
+
return maskSingle(parts[0], enableTime);
|
|
44
|
+
}
|
|
45
|
+
const a = maskSingle(parts[0], enableTime);
|
|
46
|
+
const b = maskSingle(parts.slice(1).join(' '), enableTime); // everything after first sep
|
|
47
|
+
return `${a} – ${b}`.trim();
|
|
48
|
+
}
|
|
49
|
+
// Pad helper
|
|
50
|
+
export const pad2 = (n) => String(n).padStart(2, '0');
|
|
51
|
+
// From Date → "DD-MM-YYYY" or "DD-MM-YYYY HH:mm" (local time)
|
|
52
|
+
export function toMaskedFromDate(d, enableTime) {
|
|
53
|
+
const dd = pad2(d.getDate());
|
|
54
|
+
const mm = pad2(d.getMonth() + 1);
|
|
55
|
+
const yyyy = String(d.getFullYear());
|
|
56
|
+
let out = `${dd}-${mm}-${yyyy}`;
|
|
57
|
+
if (enableTime)
|
|
58
|
+
out += ` ${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
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
|
+
/* ---------- Parsing helpers (no deps) ---------- */
|
|
72
|
+
// Accepts: YYYY-MM-DD, DD-MM-YYYY, DD/MM/YYYY, DD.MM.YYYY (+ optional HH:mm)
|
|
73
|
+
export function parseLooseDateOrDateTime(input) {
|
|
74
|
+
const txt = input.trim().replace(/\s+/g, ' ');
|
|
75
|
+
const dateTimeMatch = /^(?<date>[\d./-]{8,10})(?:[ T](?<hh>\d{1,2}):(?<mm>\d{2}))?$/i.exec(txt);
|
|
76
|
+
if (!(dateTimeMatch === null || dateTimeMatch === void 0 ? void 0 : dateTimeMatch.groups))
|
|
77
|
+
return null;
|
|
78
|
+
const raw = dateTimeMatch.groups.date;
|
|
79
|
+
const hh = dateTimeMatch.groups.hh ? parseInt(dateTimeMatch.groups.hh, 10) : 0;
|
|
80
|
+
const mm = dateTimeMatch.groups.mm ? parseInt(dateTimeMatch.groups.mm, 10) : 0;
|
|
81
|
+
if (hh < 0 || hh > 23 || mm < 0 || mm > 59)
|
|
82
|
+
return null;
|
|
83
|
+
// Try YYYY-MM-DD first
|
|
84
|
+
let y, m, d;
|
|
85
|
+
const mIso = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(raw);
|
|
86
|
+
if (mIso) {
|
|
87
|
+
y = +mIso[1];
|
|
88
|
+
m = +mIso[2] - 1;
|
|
89
|
+
d = +mIso[3];
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
// Try DD-MM-YYYY or DD/MM/YYYY or DD.MM.YYYY
|
|
93
|
+
const mEu = /^(\d{1,2})[./-](\d{1,2})[./-](\d{4})$/.exec(raw);
|
|
94
|
+
if (!mEu)
|
|
95
|
+
return null;
|
|
96
|
+
d = +mEu[1];
|
|
97
|
+
m = +mEu[2] - 1;
|
|
98
|
+
y = +mEu[3];
|
|
99
|
+
}
|
|
100
|
+
const local = new Date(y, m, d, hh, mm, 0, 0);
|
|
101
|
+
if (Number.isNaN(local.getTime()))
|
|
102
|
+
return null;
|
|
103
|
+
// Guard: JS autocorrects invalid dates; re-validate exact Y/M/D
|
|
104
|
+
if (local.getFullYear() !== y || local.getMonth() !== m || local.getDate() !== d)
|
|
105
|
+
return null;
|
|
106
|
+
return local;
|
|
107
|
+
}
|
|
108
|
+
// Parse a range string with separators: "–", "-", "to", "til"
|
|
109
|
+
export function parseLooseRange(input) {
|
|
110
|
+
const sep = /\s*(?:–|-|to|til)\s*/i;
|
|
111
|
+
const [a, b] = input.split(sep);
|
|
112
|
+
if (!a || !b)
|
|
113
|
+
return null;
|
|
114
|
+
const s = parseLooseDateOrDateTime(a);
|
|
115
|
+
const e = parseLooseDateOrDateTime(b);
|
|
116
|
+
if (!s || !e)
|
|
117
|
+
return null;
|
|
118
|
+
return s <= e ? { start: s, end: e } : { start: e, end: s };
|
|
119
|
+
}
|
|
@@ -14,8 +14,9 @@ export type SelectProps<T> = Omit<InputContainerProps, 'children' | 'htmlFor' |
|
|
|
14
14
|
onClear?: () => void;
|
|
15
15
|
datakey?: string;
|
|
16
16
|
dataCy?: string;
|
|
17
|
+
disabled?: boolean;
|
|
17
18
|
tooltip?: React.ReactNode;
|
|
18
19
|
tooltipPlacement?: 'top' | 'right' | 'bottom' | 'left';
|
|
19
20
|
};
|
|
20
|
-
export declare function Select<T extends string | number | Record<string, any>>({ label, error, helpText, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, id, options, selectedValue, onChange, placeholder, size, variant, onClear, datakey, dataCy, }: SelectProps<T>): React.ReactNode;
|
|
21
|
+
export declare function Select<T extends string | number | Record<string, any>>({ label, error, helpText, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, id, options, selectedValue, onChange, placeholder, size, variant, onClear, datakey, dataCy, disabled, }: SelectProps<T>): React.ReactNode;
|
|
21
22
|
export {};
|
|
@@ -10,11 +10,9 @@ import { Popover } from '../../popover/Popover';
|
|
|
10
10
|
import { InputContainer } from '../input-container/InputContainer';
|
|
11
11
|
export function Select({
|
|
12
12
|
// InputContainer props
|
|
13
|
-
label, error, helpText, orientation = 'vertical', labelWidth = '120px', fullWidth = true, required,
|
|
14
|
-
// ✅ tooltip props
|
|
15
|
-
tooltip, tooltipPlacement = 'right',
|
|
13
|
+
label, error, helpText, orientation = 'vertical', labelWidth = '120px', fullWidth = true, required, tooltip, tooltipPlacement = 'right',
|
|
16
14
|
// Select props
|
|
17
|
-
id, options, selectedValue, onChange, placeholder = 'Vælg', size, variant = 'outlined', onClear, datakey, dataCy, }) {
|
|
15
|
+
id, options, selectedValue, onChange, placeholder = 'Vælg', size, variant = 'outlined', onClear, datakey, dataCy, disabled, }) {
|
|
18
16
|
const generatedId = useId();
|
|
19
17
|
const controlId = id !== null && id !== void 0 ? id : `select-${generatedId}`;
|
|
20
18
|
const describedById = `${controlId}-desc`;
|
|
@@ -74,10 +72,7 @@ id, options, selectedValue, onChange, placeholder = 'Vælg', size, variant = 'ou
|
|
|
74
72
|
ids.push(tooltipId);
|
|
75
73
|
return ids.length ? ids.join(' ') : undefined;
|
|
76
74
|
})();
|
|
77
|
-
return (_jsxs(InputContainer, { label: label, htmlFor: controlId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, children: [_jsx(Popover, { ref: popoverRef, trigger: (onClick, icon) => (_jsx(Button
|
|
78
|
-
// IMPORTANT: keep triggerProps last for events, but let our aria-describedby win
|
|
79
|
-
// We'll spread triggerProps and then override aria-describedby to include both.
|
|
80
|
-
, { ...(tooltipEnabled ? triggerProps : {}), id: controlId, "data-cy": dataCy !== null && dataCy !== void 0 ? dataCy : 'select-button', onKeyDown: handleKeyDown, fullWidth: fullWidth, variant: variant, onClick: e => {
|
|
75
|
+
return (_jsxs(InputContainer, { label: label, htmlFor: controlId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, children: [_jsx(Popover, { ref: popoverRef, trigger: (onClick, icon) => (_jsx(Button, { disabled: disabled, ...(tooltipEnabled ? triggerProps : {}), id: controlId, "data-cy": dataCy !== null && dataCy !== void 0 ? dataCy : 'select-button', onKeyDown: handleKeyDown, fullWidth: fullWidth, variant: variant, onClick: e => {
|
|
81
76
|
setActiveIndex(selectedIndex >= 0 ? selectedIndex : 0);
|
|
82
77
|
onClick(e);
|
|
83
78
|
}, size: size, type: "button", "aria-haspopup": "listbox", "aria-invalid": Boolean(error) || undefined, "aria-describedby": describedBy, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsx("span", { children: selected ? selected.label : placeholder }), onClear && selected && _jsx(ClearButton, { onClick: onClear }), icon] }) })), children: _jsx(Menu, { onKeyDown: handleKeyDown, role: "listbox", children: options.map((opt, index) => {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ButtonVariant } from '../../components/button/Button';
|
|
3
|
+
import { InputContainer } from '../../components/forms/input-container/InputContainer';
|
|
4
|
+
type InputContainerProps = React.ComponentProps<typeof InputContainer>;
|
|
5
|
+
export type IntervalOption = {
|
|
6
|
+
label: string;
|
|
7
|
+
/** How many minutes back from baseDate (default: now). */
|
|
8
|
+
minutesAgo: number;
|
|
9
|
+
};
|
|
10
|
+
export type IntervalSelectValue = number | null;
|
|
11
|
+
export type IntervalSelectProps = Omit<InputContainerProps, 'children' | 'htmlFor' | 'tooltip' | 'tooltipPlacement'> & {
|
|
12
|
+
id?: string;
|
|
13
|
+
options: IntervalOption[];
|
|
14
|
+
selectedValue: IntervalSelectValue;
|
|
15
|
+
onChange: (date: Date, meta: {
|
|
16
|
+
minutesAgo: number;
|
|
17
|
+
option: IntervalOption;
|
|
18
|
+
}) => void;
|
|
19
|
+
/** Base date for the calculation; defaults to "now". */
|
|
20
|
+
baseDate?: Date;
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
size?: 'sm' | 'md' | 'lg';
|
|
23
|
+
variant?: ButtonVariant;
|
|
24
|
+
onClear?: () => void;
|
|
25
|
+
dataCy?: string;
|
|
26
|
+
tooltip?: React.ReactNode;
|
|
27
|
+
tooltipPlacement?: 'top' | 'right' | 'bottom' | 'left';
|
|
28
|
+
};
|
|
29
|
+
export declare function IntervalSelect({ label, error, helpText, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, id, options, selectedValue, onChange, baseDate, placeholder, size, variant, onClear, dataCy, }: IntervalSelectProps): React.ReactNode;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Check, Clock } from 'lucide-react';
|
|
4
|
+
import { useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { Button } from '../../components/button/Button';
|
|
6
|
+
import { ClearButton } from '../../components/clear-button/ClearButton';
|
|
7
|
+
import { InputContainer } from '../../components/forms/input-container/InputContainer';
|
|
8
|
+
import { Menu } from '../../components/menu/Menu';
|
|
9
|
+
import { useTooltipTrigger } from '../../components/overlay/tooltip/useTooltipTrigger';
|
|
10
|
+
import { Popover } from '../../components/popover/Popover';
|
|
11
|
+
export function IntervalSelect({
|
|
12
|
+
// InputContainer props
|
|
13
|
+
label, error, helpText, orientation = 'vertical', labelWidth = '120px', fullWidth = true, required,
|
|
14
|
+
// tooltip
|
|
15
|
+
tooltip, tooltipPlacement = 'right',
|
|
16
|
+
// IntervalSelect props
|
|
17
|
+
id, options, selectedValue, onChange, baseDate, placeholder = 'Vælg interval', size, variant = 'outlined', onClear, dataCy, }) {
|
|
18
|
+
const generatedId = useId();
|
|
19
|
+
const controlId = id !== null && id !== void 0 ? id : `interval-select-${generatedId}`;
|
|
20
|
+
const describedById = `${controlId}-desc`;
|
|
21
|
+
const popoverRef = useRef(null);
|
|
22
|
+
const optionRefs = useRef([]);
|
|
23
|
+
const selectedIndex = useMemo(() => options.findIndex(o => o.minutesAgo === selectedValue), [options, selectedValue]);
|
|
24
|
+
const [activeIndex, setActiveIndex] = useState(selectedIndex >= 0 ? selectedIndex : 0);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
var _a;
|
|
27
|
+
(_a = optionRefs.current[activeIndex]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
28
|
+
}, [activeIndex]);
|
|
29
|
+
const selected = useMemo(() => { var _a; return (_a = options.find(o => o.minutesAgo === selectedValue)) !== null && _a !== void 0 ? _a : null; }, [options, selectedValue]);
|
|
30
|
+
const tooltipEnabled = Boolean(tooltip);
|
|
31
|
+
const { triggerProps, id: tooltipId } = useTooltipTrigger({
|
|
32
|
+
content: tooltipEnabled ? tooltip : null,
|
|
33
|
+
placement: tooltipPlacement,
|
|
34
|
+
offset: 8,
|
|
35
|
+
});
|
|
36
|
+
const describedBy = (() => {
|
|
37
|
+
const ids = [];
|
|
38
|
+
if (error || helpText)
|
|
39
|
+
ids.push(describedById);
|
|
40
|
+
if (tooltipEnabled)
|
|
41
|
+
ids.push(tooltipId);
|
|
42
|
+
return ids.length ? ids.join(' ') : undefined;
|
|
43
|
+
})();
|
|
44
|
+
const handleCommit = (opt) => {
|
|
45
|
+
var _a;
|
|
46
|
+
const base = baseDate !== null && baseDate !== void 0 ? baseDate : new Date();
|
|
47
|
+
const dt = new Date(base.getTime() - opt.minutesAgo * 60000);
|
|
48
|
+
onChange(dt, { minutesAgo: opt.minutesAgo, option: opt });
|
|
49
|
+
(_a = popoverRef.current) === null || _a === void 0 ? void 0 : _a.close();
|
|
50
|
+
};
|
|
51
|
+
const handleKeyDown = (e) => {
|
|
52
|
+
var _a;
|
|
53
|
+
switch (e.key) {
|
|
54
|
+
case 'ArrowDown':
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
setActiveIndex(i => Math.min(i + 1, options.length - 1));
|
|
57
|
+
break;
|
|
58
|
+
case 'ArrowUp':
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
setActiveIndex(i => Math.max(i - 1, 0));
|
|
61
|
+
break;
|
|
62
|
+
case 'Enter':
|
|
63
|
+
case ' ':
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
if (options[activeIndex])
|
|
66
|
+
handleCommit(options[activeIndex]);
|
|
67
|
+
break;
|
|
68
|
+
case 'Escape':
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
(_a = popoverRef.current) === null || _a === void 0 ? void 0 : _a.close();
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
return (_jsxs(InputContainer, { label: label, htmlFor: controlId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, children: [_jsx(Popover, { ref: popoverRef, trigger: (onClick, icon) => (_jsx(Button, { ...(tooltipEnabled ? triggerProps : {}), id: controlId, "data-cy": dataCy !== null && dataCy !== void 0 ? dataCy : 'interval-select-button', onKeyDown: handleKeyDown, fullWidth: fullWidth, variant: variant, onClick: e => {
|
|
75
|
+
setActiveIndex(selectedIndex >= 0 ? selectedIndex : 0);
|
|
76
|
+
onClick(e);
|
|
77
|
+
}, size: size, type: "button", "aria-haspopup": "listbox", "aria-invalid": Boolean(error) || undefined, "aria-describedby": describedBy, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsxs("span", { className: "dbc-flex dbc-items-center dbc-gap-xxs", children: [_jsx(Clock, { size: 14 }), selected ? selected.label : placeholder] }), onClear && selected && _jsx(ClearButton, { onClick: onClear }), icon] }) })), children: _jsx(Menu, { onKeyDown: handleKeyDown, role: "listbox", children: options.map((opt, index) => {
|
|
78
|
+
const isSelected = opt.minutesAgo === selectedValue;
|
|
79
|
+
const isActive = index === activeIndex;
|
|
80
|
+
return (_jsx(Menu.Item, { active: isActive, "aria-selected": isSelected, children: _jsxs("button", { ref: el => (optionRefs.current[index] = el), type: "button", tabIndex: isActive ? 0 : -1, onClick: () => handleCommit(opt), onFocus: () => setActiveIndex(index), style: { display: 'flex', alignItems: 'center', width: '100%' }, children: [_jsx("span", { style: { width: 16, display: 'inline-flex', justifyContent: 'center' }, children: isSelected ? _jsx(Check, {}) : null }), opt.label] }) }, opt.minutesAgo));
|
|
81
|
+
}) }) }), (error || helpText) && (_jsx("span", { id: describedById, style: { display: 'none' }, children: error !== null && error !== void 0 ? error : helpText }))] }));
|
|
82
|
+
}
|
|
@@ -82,7 +82,7 @@ export function TanstackTable(props) {
|
|
|
82
82
|
},
|
|
83
83
|
});
|
|
84
84
|
const columnItems = React.useMemo(() => mapDefsToColumnItems(columns), [columns]);
|
|
85
|
-
const visibleData =
|
|
85
|
+
const visibleData = table.getRowModel().rows.map(r => r.original);
|
|
86
86
|
const s = (_a = table.getState().sorting) === null || _a === void 0 ? void 0 : _a[0];
|
|
87
87
|
const sortById = (_b = s === null || s === void 0 ? void 0 : s.id) !== null && _b !== void 0 ? _b : undefined;
|
|
88
88
|
const sortDirection = s ? (s.desc ? 'desc' : 'asc') : null;
|