@bikdotai/bik-component-library 0.0.867 → 0.0.868-beta.0
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/cjs/components/chip-selector/ChipSelector.js +2 -0
- package/dist/cjs/components/chip-selector/ChipSelector.js.map +1 -0
- package/dist/cjs/components/chip-selector/ChipSelector.styled.js +36 -0
- package/dist/cjs/components/chip-selector/ChipSelector.styled.js.map +1 -0
- package/dist/cjs/components/chip-selector/ChipSelectorGroup.js +2 -0
- package/dist/cjs/components/chip-selector/ChipSelectorGroup.js.map +1 -0
- package/dist/cjs/components/combo-box/ComboBox.js +2 -0
- package/dist/cjs/components/combo-box/ComboBox.js.map +1 -0
- package/dist/cjs/components/combo-box/ComboBox.styled.js +33 -0
- package/dist/cjs/components/combo-box/ComboBox.styled.js.map +1 -0
- package/dist/cjs/components/datePicker/DatePickerWrapper.js +1 -1
- package/dist/cjs/components/datePicker/DatePickerWrapper.js.map +1 -1
- package/dist/cjs/components/schedule-builder/RefreshSegmentToggle.js +2 -0
- package/dist/cjs/components/schedule-builder/RefreshSegmentToggle.js.map +1 -0
- package/dist/cjs/components/schedule-builder/ScheduleBuilder.js +2 -0
- package/dist/cjs/components/schedule-builder/ScheduleBuilder.js.map +1 -0
- package/dist/cjs/components/schedule-builder/ScheduleBuilder.styled.js +138 -0
- package/dist/cjs/components/schedule-builder/ScheduleBuilder.styled.js.map +1 -0
- package/dist/cjs/components/schedule-builder/ScheduleEndPicker.js +2 -0
- package/dist/cjs/components/schedule-builder/ScheduleEndPicker.js.map +1 -0
- package/dist/cjs/components/schedule-builder/ScheduleFrequencyPicker.js +2 -0
- package/dist/cjs/components/schedule-builder/ScheduleFrequencyPicker.js.map +1 -0
- package/dist/cjs/components/schedule-builder/ScheduleHelper.js +2 -0
- package/dist/cjs/components/schedule-builder/ScheduleHelper.js.map +1 -0
- package/dist/cjs/components/schedule-builder/SendModeSelector.js +2 -0
- package/dist/cjs/components/schedule-builder/SendModeSelector.js.map +1 -0
- package/dist/cjs/components/schedule-builder/StartDateTimePicker.js +2 -0
- package/dist/cjs/components/schedule-builder/StartDateTimePicker.js.map +1 -0
- package/dist/cjs/components/schedule-builder/UpcomingRunsPreview.js +2 -0
- package/dist/cjs/components/schedule-builder/UpcomingRunsPreview.js.map +1 -0
- package/dist/cjs/components/schedule-builder/model.js +2 -0
- package/dist/cjs/components/schedule-builder/model.js.map +1 -0
- package/dist/cjs/index.js +1 -1
- package/dist/esm/components/chip-selector/ChipSelector.d.ts +9 -0
- package/dist/esm/components/chip-selector/ChipSelector.js +27 -0
- package/dist/esm/components/chip-selector/ChipSelector.js.map +1 -0
- package/dist/esm/components/chip-selector/ChipSelector.styled.d.ts +10 -0
- package/dist/esm/components/chip-selector/ChipSelector.styled.js +48 -0
- package/dist/esm/components/chip-selector/ChipSelector.styled.js.map +1 -0
- package/dist/esm/components/chip-selector/ChipSelectorGroup.d.ts +10 -0
- package/dist/esm/components/chip-selector/ChipSelectorGroup.js +48 -0
- package/dist/esm/components/chip-selector/ChipSelectorGroup.js.map +1 -0
- package/dist/esm/components/chip-selector/index.d.ts +4 -0
- package/dist/esm/components/chip-selector/model.d.ts +106 -0
- package/dist/esm/components/combo-box/ComboBox.d.ts +15 -0
- package/dist/esm/components/combo-box/ComboBox.js +118 -0
- package/dist/esm/components/combo-box/ComboBox.js.map +1 -0
- package/dist/esm/components/combo-box/ComboBox.styled.d.ts +12 -0
- package/dist/esm/components/combo-box/ComboBox.styled.js +40 -0
- package/dist/esm/components/combo-box/ComboBox.styled.js.map +1 -0
- package/dist/esm/components/combo-box/index.d.ts +3 -0
- package/dist/esm/components/combo-box/model.d.ts +55 -0
- package/dist/esm/components/datePicker/DatePickerWrapper.d.ts +7 -0
- package/dist/esm/components/datePicker/DatePickerWrapper.js +66 -64
- package/dist/esm/components/datePicker/DatePickerWrapper.js.map +1 -1
- package/dist/esm/components/datePicker/index.d.ts +1 -0
- package/dist/esm/components/schedule-builder/RefreshSegmentToggle.d.ts +22 -0
- package/dist/esm/components/schedule-builder/RefreshSegmentToggle.js +31 -0
- package/dist/esm/components/schedule-builder/RefreshSegmentToggle.js.map +1 -0
- package/dist/esm/components/schedule-builder/ScheduleBuilder.d.ts +88 -0
- package/dist/esm/components/schedule-builder/ScheduleBuilder.js +105 -0
- package/dist/esm/components/schedule-builder/ScheduleBuilder.js.map +1 -0
- package/dist/esm/components/schedule-builder/ScheduleBuilder.styled.d.ts +81 -0
- package/dist/esm/components/schedule-builder/ScheduleBuilder.styled.js +168 -0
- package/dist/esm/components/schedule-builder/ScheduleBuilder.styled.js.map +1 -0
- package/dist/esm/components/schedule-builder/ScheduleEndPicker.d.ts +17 -0
- package/dist/esm/components/schedule-builder/ScheduleEndPicker.js +105 -0
- package/dist/esm/components/schedule-builder/ScheduleEndPicker.js.map +1 -0
- package/dist/esm/components/schedule-builder/ScheduleFrequencyPicker.d.ts +21 -0
- package/dist/esm/components/schedule-builder/ScheduleFrequencyPicker.js +136 -0
- package/dist/esm/components/schedule-builder/ScheduleFrequencyPicker.js.map +1 -0
- package/dist/esm/components/schedule-builder/ScheduleHelper.d.ts +110 -0
- package/dist/esm/components/schedule-builder/ScheduleHelper.js +196 -0
- package/dist/esm/components/schedule-builder/ScheduleHelper.js.map +1 -0
- package/dist/esm/components/schedule-builder/SendModeSelector.d.ts +29 -0
- package/dist/esm/components/schedule-builder/SendModeSelector.js +41 -0
- package/dist/esm/components/schedule-builder/SendModeSelector.js.map +1 -0
- package/dist/esm/components/schedule-builder/StartDateTimePicker.d.ts +44 -0
- package/dist/esm/components/schedule-builder/StartDateTimePicker.js +119 -0
- package/dist/esm/components/schedule-builder/StartDateTimePicker.js.map +1 -0
- package/dist/esm/components/schedule-builder/UpcomingRunsPreview.d.ts +19 -0
- package/dist/esm/components/schedule-builder/UpcomingRunsPreview.js +44 -0
- package/dist/esm/components/schedule-builder/UpcomingRunsPreview.js.map +1 -0
- package/dist/esm/components/schedule-builder/index.d.ts +17 -0
- package/dist/esm/components/schedule-builder/model.d.ts +69 -0
- package/dist/esm/components/schedule-builder/model.js +8 -0
- package/dist/esm/components/schedule-builder/model.js.map +1 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +716 -675
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/** Identifier for a chip. Numbers suit day/date indices, strings suit slugs. */
|
|
3
|
+
export type ChipValue = string | number;
|
|
4
|
+
export type ChipSize = 'small' | 'medium';
|
|
5
|
+
export interface ChipOption {
|
|
6
|
+
/**
|
|
7
|
+
* Unique value for this chip, reported back through the group's `onChange`.
|
|
8
|
+
*/
|
|
9
|
+
value: ChipValue;
|
|
10
|
+
/**
|
|
11
|
+
* Text rendered inside the chip. Keep it short — chips are compact by design.
|
|
12
|
+
*/
|
|
13
|
+
label: string;
|
|
14
|
+
/**
|
|
15
|
+
* Accessible name, used when `label` is abbreviated and would not read well
|
|
16
|
+
* on its own — e.g. "M" for Monday, where several days share a letter.
|
|
17
|
+
* @default the `label`
|
|
18
|
+
*/
|
|
19
|
+
ariaLabel?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Blocks selection and dims the chip.
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Sizes this chip to its label, ignoring the group's `chipWidth`. For the odd
|
|
27
|
+
* long option in an otherwise uniform grid — "Last day of the month" beside
|
|
28
|
+
* dates 1–31, which would overflow a fixed date-sized chip.
|
|
29
|
+
* @default false
|
|
30
|
+
*/
|
|
31
|
+
fitContent?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface ChipSelectorProps extends ChipOption {
|
|
34
|
+
/**
|
|
35
|
+
* Whether the chip is currently selected.
|
|
36
|
+
*/
|
|
37
|
+
selected: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Size of the chip. `medium` matches the 48px input height so chips can sit
|
|
40
|
+
* in a row beside inputs and dropdowns.
|
|
41
|
+
* @default 'medium'
|
|
42
|
+
*/
|
|
43
|
+
size?: ChipSize;
|
|
44
|
+
/**
|
|
45
|
+
* Fixed width in px. Omit to let the chip size to its content, which is what
|
|
46
|
+
* variable-length labels need.
|
|
47
|
+
* @default undefined
|
|
48
|
+
*/
|
|
49
|
+
width?: number;
|
|
50
|
+
onSelect: (value: ChipValue) => void;
|
|
51
|
+
dataTestId?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface ChipSelectorGroupProps {
|
|
54
|
+
/**
|
|
55
|
+
* Chips to render, in display order.
|
|
56
|
+
*/
|
|
57
|
+
options: ChipOption[];
|
|
58
|
+
/**
|
|
59
|
+
* Currently selected values. Pass a single-element array for single-select.
|
|
60
|
+
*/
|
|
61
|
+
value: ChipValue[];
|
|
62
|
+
/**
|
|
63
|
+
* Fired with the full next selection, so the caller stores the result
|
|
64
|
+
* directly rather than reimplementing add/remove on every screen.
|
|
65
|
+
*/
|
|
66
|
+
onChange: (value: ChipValue[]) => void;
|
|
67
|
+
/**
|
|
68
|
+
* Allows more than one chip to be selected at a time. When false, picking a
|
|
69
|
+
* chip replaces the selection.
|
|
70
|
+
* @default true
|
|
71
|
+
*/
|
|
72
|
+
multiSelect?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Permits clearing the last selected chip. Set false when the caller always
|
|
75
|
+
* needs at least one value.
|
|
76
|
+
* @default true
|
|
77
|
+
*/
|
|
78
|
+
allowEmpty?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Size applied to every chip in the group.
|
|
81
|
+
* @default 'medium'
|
|
82
|
+
*/
|
|
83
|
+
size?: ChipSize;
|
|
84
|
+
/**
|
|
85
|
+
* Fixed width in px applied to every chip, for grids that must align — a
|
|
86
|
+
* day picker looks uneven if "S" and "W" size to their own content.
|
|
87
|
+
* @default undefined
|
|
88
|
+
*/
|
|
89
|
+
chipWidth?: number;
|
|
90
|
+
/**
|
|
91
|
+
* Gap between chips in px.
|
|
92
|
+
* @default 8
|
|
93
|
+
*/
|
|
94
|
+
gap?: number;
|
|
95
|
+
/**
|
|
96
|
+
* Wraps chips onto multiple lines. Needed for the 31-chip month grid.
|
|
97
|
+
* @default false
|
|
98
|
+
*/
|
|
99
|
+
wrap?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Accessible name for the group as a whole, e.g. "Repeat on".
|
|
102
|
+
*/
|
|
103
|
+
ariaLabel?: string;
|
|
104
|
+
'data-test'?: string;
|
|
105
|
+
}
|
|
106
|
+
export type ChipIconProps = React.SVGAttributes<SVGElement>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { ComboBoxProps } from './model';
|
|
3
|
+
/**
|
|
4
|
+
* A text field that can be typed into or picked from a list.
|
|
5
|
+
*
|
|
6
|
+
* Sits between Dropdown, whose box is not editable, and Input, which offers no
|
|
7
|
+
* options. Reach for it when a field has common values worth listing but must
|
|
8
|
+
* still accept anything valid the user types — a time, a duration, a currency.
|
|
9
|
+
*
|
|
10
|
+
* Typing is validated rather than constrained, so a half-finished entry stays in
|
|
11
|
+
* the box instead of being rewritten on every keystroke; `validate` decides what
|
|
12
|
+
* is worth committing and `format` tidies the text once the field is left.
|
|
13
|
+
*/
|
|
14
|
+
export declare const ComboBox: React.FC<ComboBoxProps>;
|
|
15
|
+
export default ComboBox;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { jsxs as V, jsx as h } from "react/jsx-runtime";
|
|
2
|
+
import { useState as b, useRef as v, useEffect as I, useMemo as j } from "react";
|
|
3
|
+
import q from "../../assets/icons/chevronDown.svg.js";
|
|
4
|
+
import { ComboBoxWrapper as F, OptionList as M, OptionItem as W } from "./ComboBox.styled.js";
|
|
5
|
+
import { Input as H } from "../input/Input.js";
|
|
6
|
+
const N = (n, s) => {
|
|
7
|
+
const c = s.trim().toLowerCase();
|
|
8
|
+
return c ? n.filter(
|
|
9
|
+
(a) => a.value.toLowerCase().startsWith(c)
|
|
10
|
+
) : n;
|
|
11
|
+
}, Z = ({
|
|
12
|
+
value: n,
|
|
13
|
+
options: s,
|
|
14
|
+
onChange: c,
|
|
15
|
+
validate: a,
|
|
16
|
+
format: f,
|
|
17
|
+
sanitize: g,
|
|
18
|
+
filter: O = N,
|
|
19
|
+
placeholder: B,
|
|
20
|
+
errorMessage: k,
|
|
21
|
+
disabled: C,
|
|
22
|
+
onInvalidBlur: R,
|
|
23
|
+
"data-test": K
|
|
24
|
+
}) => {
|
|
25
|
+
const [r, i] = b(n), [x, S] = b(!1), [d, T] = b(-1), E = v(null), L = v(null), m = v(-1), w = v(!1), l = (e) => {
|
|
26
|
+
m.current = e, T(e);
|
|
27
|
+
}, o = (e) => {
|
|
28
|
+
w.current = e, S(e);
|
|
29
|
+
}, D = (e) => a ? a(e) : e.trim().length > 0;
|
|
30
|
+
I(() => {
|
|
31
|
+
i((e) => D(e) && (f ? f(e) : e) === n ? e : n);
|
|
32
|
+
}, [n]);
|
|
33
|
+
const u = j(() => {
|
|
34
|
+
const e = r.trim();
|
|
35
|
+
return s.some((t) => t.value === e) ? s : O(s, r);
|
|
36
|
+
}, [O, s, r]);
|
|
37
|
+
I(() => {
|
|
38
|
+
if (!x) return;
|
|
39
|
+
const e = (t) => {
|
|
40
|
+
E.current?.contains(t.target) || o(!1);
|
|
41
|
+
};
|
|
42
|
+
return document.addEventListener("mousedown", e), () => document.removeEventListener("mousedown", e);
|
|
43
|
+
}, [x]), I(() => {
|
|
44
|
+
if (d < 0) return;
|
|
45
|
+
const e = L.current?.children[d];
|
|
46
|
+
e instanceof HTMLElement && e.scrollIntoView && e.scrollIntoView({ block: "nearest" });
|
|
47
|
+
}, [d]);
|
|
48
|
+
const A = (e) => {
|
|
49
|
+
i(e.value), c(e.value), o(!1), l(-1);
|
|
50
|
+
};
|
|
51
|
+
return /* @__PURE__ */ V(F, { ref: E, "data-test": K, children: [
|
|
52
|
+
/* @__PURE__ */ h("div", { children: /* @__PURE__ */ h(
|
|
53
|
+
H,
|
|
54
|
+
{
|
|
55
|
+
width: "100%",
|
|
56
|
+
placeholder: B,
|
|
57
|
+
value: r,
|
|
58
|
+
isDisabled: C,
|
|
59
|
+
errorMessage: k,
|
|
60
|
+
state: k ? "invalid" : C ? "disabled" : "active",
|
|
61
|
+
rightIcon: {
|
|
62
|
+
icon: q,
|
|
63
|
+
callback: () => o(!w.current),
|
|
64
|
+
"data-test": "combo-box-toggle"
|
|
65
|
+
},
|
|
66
|
+
onKeyDownEvent: (e) => {
|
|
67
|
+
const t = e;
|
|
68
|
+
if (t.key === "ArrowDown" || t.key === "ArrowUp") {
|
|
69
|
+
if (t.preventDefault(), !w.current) {
|
|
70
|
+
o(!0);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const p = t.key === "ArrowDown" ? 1 : -1, y = m.current + p;
|
|
74
|
+
y < 0 ? l(u.length - 1) : y >= u.length ? l(0) : l(y);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (t.key === "Enter" && w.current && m.current >= 0) {
|
|
78
|
+
t.preventDefault(), A(u[m.current]);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
t.key === "Escape" && o(!1);
|
|
82
|
+
},
|
|
83
|
+
noKeyDownChange: !0,
|
|
84
|
+
onFocus: () => o(!0),
|
|
85
|
+
onChangeText: (e) => {
|
|
86
|
+
const t = g ? g(e) : e;
|
|
87
|
+
i(t), o(!0), l(-1), D(t) && c(t);
|
|
88
|
+
},
|
|
89
|
+
onBlur: () => {
|
|
90
|
+
if (D(r)) {
|
|
91
|
+
const e = f ? f(r) : r;
|
|
92
|
+
i(e), e !== n && c(e);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
i(n), R?.();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
) }),
|
|
99
|
+
x && !!u.length && /* @__PURE__ */ h(M, { ref: L, role: "listbox", children: u.map((e, t) => /* @__PURE__ */ h(
|
|
100
|
+
W,
|
|
101
|
+
{
|
|
102
|
+
role: "option",
|
|
103
|
+
"aria-selected": e.value === n,
|
|
104
|
+
isActive: t === d,
|
|
105
|
+
isSelected: e.value === n,
|
|
106
|
+
onMouseDown: (p) => {
|
|
107
|
+
p.preventDefault(), A(e);
|
|
108
|
+
},
|
|
109
|
+
children: e.label ?? e.value
|
|
110
|
+
},
|
|
111
|
+
e.value
|
|
112
|
+
)) })
|
|
113
|
+
] });
|
|
114
|
+
};
|
|
115
|
+
export {
|
|
116
|
+
Z as ComboBox
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=ComboBox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComboBox.js","sources":["../../../../src/components/combo-box/ComboBox.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useRef, useState } from 'react';\nimport DownIcon from '../../assets/icons/chevronDown.svg';\nimport { Input } from '../input';\nimport { ComboBoxWrapper, OptionItem, OptionList } from './ComboBox.styled';\nimport { ComboBoxOption, ComboBoxProps } from './model';\n\n/** Options whose value starts with what has been typed. */\nconst defaultFilter = (\n\toptions: ComboBoxOption[],\n\ttext: string,\n): ComboBoxOption[] => {\n\tconst query = text.trim().toLowerCase();\n\tif (!query) return options;\n\treturn options.filter((option) =>\n\t\toption.value.toLowerCase().startsWith(query),\n\t);\n};\n\n/**\n * A text field that can be typed into or picked from a list.\n *\n * Sits between Dropdown, whose box is not editable, and Input, which offers no\n * options. Reach for it when a field has common values worth listing but must\n * still accept anything valid the user types — a time, a duration, a currency.\n *\n * Typing is validated rather than constrained, so a half-finished entry stays in\n * the box instead of being rewritten on every keystroke; `validate` decides what\n * is worth committing and `format` tidies the text once the field is left.\n */\nexport const ComboBox: React.FC<ComboBoxProps> = ({\n\tvalue,\n\toptions,\n\tonChange,\n\tvalidate,\n\tformat,\n\tsanitize,\n\tfilter = defaultFilter,\n\tplaceholder,\n\terrorMessage,\n\tdisabled,\n\tonInvalidBlur,\n\t'data-test': dataTest,\n}) => {\n\t// What the box shows while typing. Separate from `value` so a partial entry\n\t// survives a keystroke rather than being parsed away.\n\tconst [text, setText] = useState(value);\n\tconst [isOpen, setIsOpen] = useState(false);\n\tconst [activeIndex, setActiveIndex] = useState(-1);\n\tconst wrapperRef = useRef<HTMLDivElement | null>(null);\n\tconst listRef = useRef<HTMLUListElement | null>(null);\n\t// Mirrors activeIndex for the key handler, which closes over the value from\n\t// its own render — two arrow presses in one frame would otherwise both read\n\t// the same starting index.\n\tconst activeIndexRef = useRef(-1);\n\t// Same reason as activeIndexRef: the handler must see the list state left by\n\t// the keypress before it, not the one from its own render.\n\tconst isOpenRef = useRef(false);\n\n\tconst moveActiveIndex = (next: number) => {\n\t\tactiveIndexRef.current = next;\n\t\tsetActiveIndex(next);\n\t};\n\n\tconst setListOpen = (open: boolean) => {\n\t\tisOpenRef.current = open;\n\t\tsetIsOpen(open);\n\t};\n\n\tconst isValid = (candidate: string) =>\n\t\tvalidate ? validate(candidate) : candidate.trim().length > 0;\n\n\t// Mirror committed changes from outside — a loaded value, a reset, a\n\t// recommendation — without disturbing text being typed right now.\n\tuseEffect(() => {\n\t\tsetText((prev) => {\n\t\t\tif (!isValid(prev)) return value;\n\t\t\t// Compare through `format` so text that already means the committed\n\t\t\t// value is left as typed. Without this, typing \"11:4\" is rewritten to\n\t\t\t// the formatted \"11:04\" and the next keystroke lands as \"11:045\".\n\t\t\tconst asCommitted = format ? format(prev) : prev;\n\t\t\treturn asCommitted === value ? prev : value;\n\t\t});\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [value]);\n\n\t// An exact match still shows every option, so neighbouring values stay\n\t// reachable after picking one.\n\tconst visibleOptions = useMemo(() => {\n\t\tconst query = text.trim();\n\t\tif (options.some((option) => option.value === query)) return options;\n\t\treturn filter(options, text);\n\t}, [filter, options, text]);\n\n\tuseEffect(() => {\n\t\tif (!isOpen) return;\n\n\t\tconst handlePointerDown = (event: MouseEvent) => {\n\t\t\tif (!wrapperRef.current?.contains(event.target as Node)) {\n\t\t\t\tsetListOpen(false);\n\t\t\t}\n\t\t};\n\n\t\tdocument.addEventListener('mousedown', handlePointerDown);\n\t\treturn () => document.removeEventListener('mousedown', handlePointerDown);\n\t}, [isOpen]);\n\n\t// Keep the keyboard cursor in view when it moves past the visible rows.\n\tuseEffect(() => {\n\t\tif (activeIndex < 0) return;\n\t\tconst activeNode = listRef.current?.children[activeIndex];\n\t\t// Guarded because scrollIntoView is not implemented everywhere the library\n\t\t// renders — jsdom being the common case.\n\t\tif (activeNode instanceof HTMLElement && activeNode.scrollIntoView) {\n\t\t\tactiveNode.scrollIntoView({ block: 'nearest' });\n\t\t}\n\t}, [activeIndex]);\n\n\tconst selectOption = (option: ComboBoxOption) => {\n\t\tsetText(option.value);\n\t\tonChange(option.value);\n\t\tsetListOpen(false);\n\t\tmoveActiveIndex(-1);\n\t};\n\n\t// Typed as the DOM Event the Input hook passes; the fields read below are\n\t// KeyboardEvent's, so it is narrowed on entry.\n\tconst handleKeyDown = (nativeEvent: Event) => {\n\t\tconst event = nativeEvent as unknown as KeyboardEvent;\n\t\tif (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n\t\t\tevent.preventDefault();\n\t\t\tif (!isOpenRef.current) {\n\t\t\t\tsetListOpen(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst step = event.key === 'ArrowDown' ? 1 : -1;\n\t\t\tconst next = activeIndexRef.current + step;\n\t\t\tif (next < 0) moveActiveIndex(visibleOptions.length - 1);\n\t\t\telse if (next >= visibleOptions.length) moveActiveIndex(0);\n\t\t\telse moveActiveIndex(next);\n\t\t\treturn;\n\t\t}\n\n\t\tif (\n\t\t\tevent.key === 'Enter' &&\n\t\t\tisOpenRef.current &&\n\t\t\tactiveIndexRef.current >= 0\n\t\t) {\n\t\t\tevent.preventDefault();\n\t\t\tselectOption(visibleOptions[activeIndexRef.current]);\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === 'Escape') setListOpen(false);\n\t};\n\n\tconst handleBlur = () => {\n\t\tif (isValid(text)) {\n\t\t\tconst formatted = format ? format(text) : text;\n\t\t\tsetText(formatted);\n\t\t\tif (formatted !== value) onChange(formatted);\n\t\t\treturn;\n\t\t}\n\t\t// Unusable text falls back to the last committed value.\n\t\tsetText(value);\n\t\tonInvalidBlur?.();\n\t};\n\n\treturn (\n\t\t<ComboBoxWrapper ref={wrapperRef} data-test={dataTest}>\n\t\t\t<div>\n\t\t\t\t<Input\n\t\t\t\t\twidth=\"100%\"\n\t\t\t\t\tplaceholder={placeholder}\n\t\t\t\t\tvalue={text}\n\t\t\t\t\tisDisabled={disabled}\n\t\t\t\t\terrorMessage={errorMessage}\n\t\t\t\t\tstate={errorMessage ? 'invalid' : disabled ? 'disabled' : 'active'}\n\t\t\t\t\t// Input handles its own keydown, so the arrow/Enter handling goes\n\t\t\t\t\t// through its hook rather than a wrapper that never sees the event.\n\t\t\t\t\t// Same chevron Dropdown uses, so the field reads as one rather than\n\t\t\t\t\t// looking like a plain text box that happens to suggest values.\n\t\t\t\t\trightIcon={{\n\t\t\t\t\t\ticon: DownIcon,\n\t\t\t\t\t\tcallback: () => setListOpen(!isOpenRef.current),\n\t\t\t\t\t\t'data-test': 'combo-box-toggle',\n\t\t\t\t\t}}\n\t\t\t\t\tonKeyDownEvent={handleKeyDown}\n\t\t\t\t\t// Without this Input re-fires onChangeText on every keypress, which\n\t\t\t\t\t// would re-open the list on Escape and fight the arrow keys.\n\t\t\t\t\tnoKeyDownChange\n\t\t\t\t\tonFocus={() => setListOpen(true)}\n\t\t\t\t\tonChangeText={(input: string) => {\n\t\t\t\t\t\tconst cleaned = sanitize ? sanitize(input) : input;\n\t\t\t\t\t\tsetText(cleaned);\n\t\t\t\t\t\tsetListOpen(true);\n\t\t\t\t\t\tmoveActiveIndex(-1);\n\t\t\t\t\t\tif (isValid(cleaned)) onChange(cleaned);\n\t\t\t\t\t}}\n\t\t\t\t\tonBlur={handleBlur}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t{isOpen && !!visibleOptions.length && (\n\t\t\t\t<OptionList ref={listRef} role=\"listbox\">\n\t\t\t\t\t{visibleOptions.map((option, index) => (\n\t\t\t\t\t\t<OptionItem\n\t\t\t\t\t\t\tkey={option.value}\n\t\t\t\t\t\t\trole=\"option\"\n\t\t\t\t\t\t\taria-selected={option.value === value}\n\t\t\t\t\t\t\tisActive={index === activeIndex}\n\t\t\t\t\t\t\tisSelected={option.value === value}\n\t\t\t\t\t\t\t// mousedown fires before the input's blur, so the click is not\n\t\t\t\t\t\t\t// lost to the field closing first.\n\t\t\t\t\t\t\tonMouseDown={(event) => {\n\t\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\t\tselectOption(option);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{option.label ?? option.value}\n\t\t\t\t\t\t</OptionItem>\n\t\t\t\t\t))}\n\t\t\t\t</OptionList>\n\t\t\t)}\n\t\t</ComboBoxWrapper>\n\t);\n};\n\nexport default ComboBox;\n"],"names":["defaultFilter","options","text","query","option","ComboBox","value","onChange","validate","format","sanitize","filter","placeholder","errorMessage","disabled","onInvalidBlur","dataTest","setText","useState","isOpen","setIsOpen","activeIndex","setActiveIndex","wrapperRef","useRef","listRef","activeIndexRef","isOpenRef","moveActiveIndex","next","setListOpen","open","isValid","candidate","useEffect","prev","visibleOptions","useMemo","handlePointerDown","event","activeNode","selectOption","jsxs","ComboBoxWrapper","jsx","Input","DownIcon","nativeEvent","step","input","cleaned","formatted","OptionList","index","OptionItem"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Anchors the option list to the input below it. */
|
|
2
|
+
export declare const ComboBoxWrapper: import('styled-components').StyledComponent<"div", any, {}, never>;
|
|
3
|
+
/**
|
|
4
|
+
* The option list. Sits above neighbouring fields — date pickers in the same
|
|
5
|
+
* form commonly overlay at z-index 4, so this clears them.
|
|
6
|
+
*/
|
|
7
|
+
export declare const OptionList: import('styled-components').StyledComponent<"ul", any, {}, never>;
|
|
8
|
+
/** `isActive` tracks the keyboard cursor, which is separate from selection. */
|
|
9
|
+
export declare const OptionItem: import('styled-components').StyledComponent<"li", any, {
|
|
10
|
+
isActive: boolean;
|
|
11
|
+
isSelected: boolean;
|
|
12
|
+
}, never>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import r from "styled-components";
|
|
2
|
+
import { COLORS as o } from "../../constants/Theme.js";
|
|
3
|
+
const n = r.div`
|
|
4
|
+
position: relative;
|
|
5
|
+
width: 100%;
|
|
6
|
+
`, p = r.ul`
|
|
7
|
+
position: absolute;
|
|
8
|
+
top: calc(100% + 4px);
|
|
9
|
+
left: 0;
|
|
10
|
+
right: 0;
|
|
11
|
+
z-index: 5;
|
|
12
|
+
margin: 0;
|
|
13
|
+
padding: 4px 0;
|
|
14
|
+
list-style: none;
|
|
15
|
+
max-height: 240px;
|
|
16
|
+
overflow-y: auto;
|
|
17
|
+
background: ${o.surface.standard};
|
|
18
|
+
border: 1px solid ${o.stroke.primary};
|
|
19
|
+
border-radius: 4px;
|
|
20
|
+
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.08);
|
|
21
|
+
`, a = r.li`
|
|
22
|
+
padding: 10px 12px;
|
|
23
|
+
cursor: pointer;
|
|
24
|
+
font-family: 'Inter', sans-serif;
|
|
25
|
+
font-size: 14px;
|
|
26
|
+
line-height: 20px;
|
|
27
|
+
color: ${(e) => e.isSelected ? o.content.brand : o.content.primary};
|
|
28
|
+
font-weight: ${(e) => e.isSelected ? 600 : 400};
|
|
29
|
+
background: ${(e) => e.isActive ? o.surface.hovered : "transparent"};
|
|
30
|
+
|
|
31
|
+
:hover {
|
|
32
|
+
background: ${o.surface.hovered};
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
export {
|
|
36
|
+
n as ComboBoxWrapper,
|
|
37
|
+
a as OptionItem,
|
|
38
|
+
p as OptionList
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=ComboBox.styled.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComboBox.styled.js","sources":["../../../../src/components/combo-box/ComboBox.styled.tsx"],"sourcesContent":["import styled from 'styled-components';\nimport { COLORS } from '@src/constants/Theme';\n\n/** Anchors the option list to the input below it. */\nexport const ComboBoxWrapper = styled.div`\n\tposition: relative;\n\twidth: 100%;\n`;\n\n/**\n * The option list. Sits above neighbouring fields — date pickers in the same\n * form commonly overlay at z-index 4, so this clears them.\n */\nexport const OptionList = styled.ul`\n\tposition: absolute;\n\ttop: calc(100% + 4px);\n\tleft: 0;\n\tright: 0;\n\tz-index: 5;\n\tmargin: 0;\n\tpadding: 4px 0;\n\tlist-style: none;\n\tmax-height: 240px;\n\toverflow-y: auto;\n\tbackground: ${COLORS.surface.standard};\n\tborder: 1px solid ${COLORS.stroke.primary};\n\tborder-radius: 4px;\n\tbox-shadow: 0px 4px 12px rgba(0, 0, 0, 0.08);\n`;\n\n/** `isActive` tracks the keyboard cursor, which is separate from selection. */\nexport const OptionItem = styled.li<{ isActive: boolean; isSelected: boolean }>`\n\tpadding: 10px 12px;\n\tcursor: pointer;\n\tfont-family: 'Inter', sans-serif;\n\tfont-size: 14px;\n\tline-height: 20px;\n\tcolor: ${(props) =>\n\t\tprops.isSelected ? COLORS.content.brand : COLORS.content.primary};\n\tfont-weight: ${(props) => (props.isSelected ? 600 : 400)};\n\tbackground: ${(props) =>\n\t\tprops.isActive ? COLORS.surface.hovered : 'transparent'};\n\n\t:hover {\n\t\tbackground: ${COLORS.surface.hovered};\n\t}\n`;\n"],"names":["ComboBoxWrapper","styled","OptionList","COLORS","OptionItem","props"],"mappings":";;AAIO,MAAMA,IAAkBC,EAAO;AAAA;AAAA;AAAA,GASzBC,IAAaD,EAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAWlBE,EAAO,QAAQ,QAAQ;AAAA,qBACjBA,EAAO,OAAO,OAAO;AAAA;AAAA;AAAA,GAM7BC,IAAaH,EAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAMvB,CAACI,MACTA,EAAM,aAAaF,EAAO,QAAQ,QAAQA,EAAO,QAAQ,OAAO;AAAA,gBAClD,CAACE,MAAWA,EAAM,aAAa,MAAM,GAAI;AAAA,eAC1C,CAACA,MACdA,EAAM,WAAWF,EAAO,QAAQ,UAAU,aAAa;AAAA;AAAA;AAAA,gBAGzCA,EAAO,QAAQ,OAAO;AAAA;AAAA;"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** A value the combo box can offer. */
|
|
2
|
+
export interface ComboBoxOption {
|
|
3
|
+
/**
|
|
4
|
+
* Committed through `onChange` when this option is picked, and compared
|
|
5
|
+
* against the typed text to decide what the list shows.
|
|
6
|
+
*/
|
|
7
|
+
value: string;
|
|
8
|
+
/**
|
|
9
|
+
* Text rendered in the list. Defaults to `value`, which suits lists where the
|
|
10
|
+
* two are the same — times, codes, plain identifiers.
|
|
11
|
+
*/
|
|
12
|
+
label?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ComboBoxProps {
|
|
15
|
+
/** Current committed value. Empty string when nothing is chosen yet. */
|
|
16
|
+
value: string;
|
|
17
|
+
options: ComboBoxOption[];
|
|
18
|
+
/**
|
|
19
|
+
* Fires whenever the typed text is accepted by `validate`, and whenever an
|
|
20
|
+
* option is picked from the list.
|
|
21
|
+
*/
|
|
22
|
+
onChange: (value: string) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Rejects text that should not be committed — a half-typed entry, or one
|
|
25
|
+
* outside the allowed range. Return false and the text stays in the box
|
|
26
|
+
* without being committed, which is what keeps the field editable mid-entry.
|
|
27
|
+
* @default accepts any non-empty text
|
|
28
|
+
*/
|
|
29
|
+
validate?: (text: string) => boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Rewrites the text when the field is left, e.g. padding "9:5" to "9:05".
|
|
32
|
+
* Only called for text that passes `validate`.
|
|
33
|
+
*
|
|
34
|
+
* Also used to tell whether typed text already means the committed value:
|
|
35
|
+
* "9:5" and "9:05" format alike, so the box is left alone rather than being
|
|
36
|
+
* rewritten mid-entry.
|
|
37
|
+
*/
|
|
38
|
+
format?: (text: string) => string;
|
|
39
|
+
/**
|
|
40
|
+
* Strips characters as they are typed, so unusable input never reaches the
|
|
41
|
+
* box. Applied before `validate`.
|
|
42
|
+
*/
|
|
43
|
+
sanitize?: (text: string) => string;
|
|
44
|
+
/**
|
|
45
|
+
* Narrows the list against the typed text. The default keeps options whose
|
|
46
|
+
* value starts with what has been typed.
|
|
47
|
+
*/
|
|
48
|
+
filter?: (options: ComboBoxOption[], text: string) => ComboBoxOption[];
|
|
49
|
+
placeholder?: string;
|
|
50
|
+
errorMessage?: string;
|
|
51
|
+
disabled?: boolean;
|
|
52
|
+
/** Called on blur when the text is not valid, for the caller's own checks. */
|
|
53
|
+
onInvalidBlur?: () => void;
|
|
54
|
+
'data-test'?: string;
|
|
55
|
+
}
|
|
@@ -13,4 +13,11 @@ export declare const DatePickerWrapper: React.FC<{
|
|
|
13
13
|
maxDate?: Date;
|
|
14
14
|
minDate?: Date;
|
|
15
15
|
dateButtonType?: DateButtonType;
|
|
16
|
+
/**
|
|
17
|
+
* date-fns format for the button label, e.g. 'dd MMM yyyy' -> "10 Aug 2026".
|
|
18
|
+
* Omit to keep the locale default (toLocaleDateString).
|
|
19
|
+
*/
|
|
20
|
+
displayFormat?: string;
|
|
21
|
+
/** Shown instead of a date when `val` is empty. */
|
|
22
|
+
placeholder?: string;
|
|
16
23
|
}>;
|
|
@@ -1,83 +1,85 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
1
|
+
import { jsxs as p, Fragment as A, jsx as n } from "react/jsx-runtime";
|
|
2
|
+
import { format as P } from "date-fns";
|
|
3
|
+
import { useState as R, useRef as y } from "react";
|
|
4
|
+
import L from "react-calendar";
|
|
5
|
+
import { useOutside as x } from "../../hooks/useOutside.js";
|
|
6
|
+
import E from "../../utils/DateUtils.js";
|
|
6
7
|
import { COLORS as r } from "../../constants/Theme.js";
|
|
7
|
-
import
|
|
8
|
-
import { BodyPrimary as
|
|
9
|
-
import { DateBtn as
|
|
10
|
-
import { DateButtonType as
|
|
11
|
-
const
|
|
12
|
-
val:
|
|
13
|
-
onChange:
|
|
14
|
-
calendarProps:
|
|
15
|
-
disabled:
|
|
16
|
-
pickerStyles:
|
|
17
|
-
nativeID:
|
|
18
|
-
maxDate:
|
|
19
|
-
minDate:
|
|
20
|
-
dateButtonType:
|
|
21
|
-
dateButtonStyles:
|
|
8
|
+
import O from "../../assets/icons/calendar.svg.js";
|
|
9
|
+
import { BodyPrimary as _, BodySecondary as $ } from "../TypographyStyle.js";
|
|
10
|
+
import { DateBtn as G, PickerContainer as j } from "./DatePickerWrapper.styled.js";
|
|
11
|
+
import { DateButtonType as c } from "./model.js";
|
|
12
|
+
const K = ({
|
|
13
|
+
val: e,
|
|
14
|
+
onChange: D,
|
|
15
|
+
calendarProps: g,
|
|
16
|
+
disabled: o,
|
|
17
|
+
pickerStyles: d,
|
|
18
|
+
nativeID: C,
|
|
19
|
+
maxDate: h,
|
|
20
|
+
minDate: w,
|
|
21
|
+
dateButtonType: i,
|
|
22
|
+
dateButtonStyles: k,
|
|
23
|
+
displayFormat: m,
|
|
24
|
+
placeholder: S
|
|
22
25
|
}) => {
|
|
23
|
-
const [
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
const [f, s] = R(!1), b = y(null), l = y(null);
|
|
27
|
+
x(l, () => {
|
|
28
|
+
s(!1);
|
|
26
29
|
});
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
)
|
|
30
|
-
/* @__PURE__ */ new Date()
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
E,
|
|
30
|
+
const t = (a) => m ? P(a, m) : a.toLocaleDateString(), u = () => e ? Array.isArray(e) ? e?.[0]?.toString() && e?.[1]?.toString() ? `${t(new Date(e[0].toString()))}-${t(
|
|
31
|
+
new Date(e[1].toString())
|
|
32
|
+
)}` : `${t(E.getPastDate(/* @__PURE__ */ new Date(), 30))}-${t(
|
|
33
|
+
/* @__PURE__ */ new Date()
|
|
34
|
+
)}` : t(new Date(e.toString())) : S ?? t(/* @__PURE__ */ new Date());
|
|
35
|
+
return /* @__PURE__ */ p(A, { children: [
|
|
36
|
+
/* @__PURE__ */ p(
|
|
37
|
+
G,
|
|
36
38
|
{
|
|
37
|
-
id:
|
|
38
|
-
ref:
|
|
39
|
-
bgColor:
|
|
39
|
+
id: C,
|
|
40
|
+
ref: b,
|
|
41
|
+
bgColor: o ? r.background.base : r.surface.standard,
|
|
40
42
|
borderColor: r.stroke.primary,
|
|
41
|
-
onClick: () =>
|
|
42
|
-
dateButtonType:
|
|
43
|
-
style:
|
|
43
|
+
onClick: () => s(!f),
|
|
44
|
+
dateButtonType: i || c.DEFAULT,
|
|
45
|
+
style: k,
|
|
44
46
|
children: [
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
i === c.LARGE ? /* @__PURE__ */ n(
|
|
48
|
+
_,
|
|
47
49
|
{
|
|
48
50
|
style: {
|
|
49
|
-
color:
|
|
51
|
+
color: o ? r.content.inactive : r.content.primary,
|
|
50
52
|
flex: 1
|
|
51
53
|
},
|
|
52
|
-
children:
|
|
54
|
+
children: u()
|
|
53
55
|
}
|
|
54
|
-
) : /* @__PURE__ */
|
|
55
|
-
|
|
56
|
+
) : /* @__PURE__ */ n(
|
|
57
|
+
$,
|
|
56
58
|
{
|
|
57
59
|
style: {
|
|
58
|
-
color:
|
|
60
|
+
color: o ? r.content.inactive : r.content.primary,
|
|
59
61
|
flex: 1
|
|
60
62
|
},
|
|
61
|
-
children:
|
|
63
|
+
children: u()
|
|
62
64
|
}
|
|
63
65
|
),
|
|
64
|
-
/* @__PURE__ */
|
|
65
|
-
|
|
66
|
+
/* @__PURE__ */ n(
|
|
67
|
+
O,
|
|
66
68
|
{
|
|
67
|
-
width:
|
|
68
|
-
height:
|
|
69
|
-
color:
|
|
69
|
+
width: i === c.LARGE ? 24 : 20,
|
|
70
|
+
height: i === c.LARGE ? 24 : 20,
|
|
71
|
+
color: o ? r.content.inactive : r.content.primary
|
|
70
72
|
}
|
|
71
73
|
)
|
|
72
74
|
]
|
|
73
75
|
}
|
|
74
76
|
),
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
f && !o && /* @__PURE__ */ n(
|
|
78
|
+
j,
|
|
77
79
|
{
|
|
78
|
-
ref:
|
|
80
|
+
ref: l,
|
|
79
81
|
style: {
|
|
80
|
-
...
|
|
82
|
+
...d
|
|
81
83
|
// top: openUpwards ? 'auto' : 'calc(100% + 8px)',
|
|
82
84
|
// bottom: openUpwards ? 'calc(100% + 8px)' : 'auto',
|
|
83
85
|
},
|
|
@@ -86,18 +88,18 @@ const q = ({
|
|
|
86
88
|
brandColor: r.background.brand,
|
|
87
89
|
bgColor: r.surface.standard,
|
|
88
90
|
borderColor: r.stroke.primary,
|
|
89
|
-
children: /* @__PURE__ */
|
|
90
|
-
|
|
91
|
+
children: /* @__PURE__ */ n(
|
|
92
|
+
L,
|
|
91
93
|
{
|
|
92
94
|
tileClassName: ["custom__tile"],
|
|
93
95
|
className: ["custom__calendar"],
|
|
94
|
-
onChange: (
|
|
95
|
-
|
|
96
|
+
onChange: (a) => {
|
|
97
|
+
D(a), s(!1);
|
|
96
98
|
},
|
|
97
|
-
maxDate:
|
|
98
|
-
minDate:
|
|
99
|
-
value:
|
|
100
|
-
...
|
|
99
|
+
maxDate: h,
|
|
100
|
+
minDate: w,
|
|
101
|
+
value: e ? Array.isArray(e) ? e.length === 2 ? [new Date(e[0]), new Date(e[1])] : null : new Date(e) : null,
|
|
102
|
+
...g
|
|
101
103
|
}
|
|
102
104
|
)
|
|
103
105
|
}
|
|
@@ -105,6 +107,6 @@ const q = ({
|
|
|
105
107
|
] });
|
|
106
108
|
};
|
|
107
109
|
export {
|
|
108
|
-
|
|
110
|
+
K as DatePickerWrapper
|
|
109
111
|
};
|
|
110
112
|
//# sourceMappingURL=DatePickerWrapper.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatePickerWrapper.js","sources":["../../../../src/components/datePicker/DatePickerWrapper.tsx"],"sourcesContent":["import React, { useRef, useState } from 'react';\nimport Calendar, { CalendarProps } from 'react-calendar';\nimport { CSSProperties } from 'styled-components';\nimport { useOutside } from '@src/hooks/useOutside';\nimport DateUtils from '@src/utils/DateUtils';\nimport { COLORS } from '@src/constants/Theme';\nimport CalendarIcon from '../../assets/icons/calendar.svg';\nimport { BodyPrimary, BodySecondary } from '../TypographyStyle';\nimport { DateBtn, PickerContainer } from './DatePickerWrapper.styled';\nimport { DateButtonType } from './model';\n\nexport const DatePickerWrapper: React.FC<{\n\tval?: Date | Date[];\n\tonChange: (val: Date | Date[]) => void;\n\tcalendarProps?: CalendarProps;\n\tpickerStyles?: CSSProperties;\n\tdateButtonStyles?: CSSProperties;\n\tnativeID?: string;\n\tdisabled?: boolean;\n\tmaxDate?: Date;\n\tminDate?: Date;\n\tdateButtonType?: DateButtonType;\n}> = ({\n\tval,\n\tonChange,\n\tcalendarProps,\n\tdisabled,\n\tpickerStyles,\n\tnativeID,\n\tmaxDate,\n\tminDate,\n\tdateButtonType,\n\tdateButtonStyles,\n}) => {\n\tconst [showPicker, setPicker] = useState(false);\n\t// const [openUpwards, setOpenUpwards] = useState(false);\n\tconst buttonRef = useRef<HTMLDivElement | null>(null);\n\tconst pickerRef = useRef<HTMLDivElement | null>(null);\n\n\tuseOutside(pickerRef, () => {\n\t\tsetPicker(false);\n\t});\n\n\t// useEffect(() => {\n\t// \tif (buttonRef.current && pickerRef.current) {\n\t// \t\tconst buttonRect = buttonRef.current.getBoundingClientRect();\n\t// \t\tconst pickerHeight = pickerRef.current.offsetHeight;\n\t// \t\tconst spaceBelow = window.innerHeight - buttonRect.bottom;\n\n\t// \t\t// If not enough space below, open upwards\n\t// \t\tsetOpenUpwards(spaceBelow < pickerHeight);\n\t// \t}\n\t// }, [showPicker]);\n\n\tconst getDisplayValue = () => {\n\t\
|
|
1
|
+
{"version":3,"file":"DatePickerWrapper.js","sources":["../../../../src/components/datePicker/DatePickerWrapper.tsx"],"sourcesContent":["import { format as formatDate } from 'date-fns';\nimport React, { useRef, useState } from 'react';\nimport Calendar, { CalendarProps } from 'react-calendar';\nimport { CSSProperties } from 'styled-components';\nimport { useOutside } from '@src/hooks/useOutside';\nimport DateUtils from '@src/utils/DateUtils';\nimport { COLORS } from '@src/constants/Theme';\nimport CalendarIcon from '../../assets/icons/calendar.svg';\nimport { BodyPrimary, BodySecondary } from '../TypographyStyle';\nimport { DateBtn, PickerContainer } from './DatePickerWrapper.styled';\nimport { DateButtonType } from './model';\n\nexport const DatePickerWrapper: React.FC<{\n\tval?: Date | Date[];\n\tonChange: (val: Date | Date[]) => void;\n\tcalendarProps?: CalendarProps;\n\tpickerStyles?: CSSProperties;\n\tdateButtonStyles?: CSSProperties;\n\tnativeID?: string;\n\tdisabled?: boolean;\n\tmaxDate?: Date;\n\tminDate?: Date;\n\tdateButtonType?: DateButtonType;\n\t/**\n\t * date-fns format for the button label, e.g. 'dd MMM yyyy' -> \"10 Aug 2026\".\n\t * Omit to keep the locale default (toLocaleDateString).\n\t */\n\tdisplayFormat?: string;\n\t/** Shown instead of a date when `val` is empty. */\n\tplaceholder?: string;\n}> = ({\n\tval,\n\tonChange,\n\tcalendarProps,\n\tdisabled,\n\tpickerStyles,\n\tnativeID,\n\tmaxDate,\n\tminDate,\n\tdateButtonType,\n\tdateButtonStyles,\n\tdisplayFormat,\n\tplaceholder,\n}) => {\n\tconst [showPicker, setPicker] = useState(false);\n\t// const [openUpwards, setOpenUpwards] = useState(false);\n\tconst buttonRef = useRef<HTMLDivElement | null>(null);\n\tconst pickerRef = useRef<HTMLDivElement | null>(null);\n\n\tuseOutside(pickerRef, () => {\n\t\tsetPicker(false);\n\t});\n\n\t// useEffect(() => {\n\t// \tif (buttonRef.current && pickerRef.current) {\n\t// \t\tconst buttonRect = buttonRef.current.getBoundingClientRect();\n\t// \t\tconst pickerHeight = pickerRef.current.offsetHeight;\n\t// \t\tconst spaceBelow = window.innerHeight - buttonRect.bottom;\n\n\t// \t\t// If not enough space below, open upwards\n\t// \t\tsetOpenUpwards(spaceBelow < pickerHeight);\n\t// \t}\n\t// }, [showPicker]);\n\n\t/** Honours `displayFormat` when given, else the locale default. */\n\tconst formatOne = (date: Date) =>\n\t\tdisplayFormat ? formatDate(date, displayFormat) : date.toLocaleDateString();\n\n\tconst getDisplayValue = () => {\n\t\tif (!val) {\n\t\t\treturn placeholder ?? formatOne(new Date());\n\t\t}\n\n\t\tif (Array.isArray(val)) {\n\t\t\treturn val?.[0]?.toString() && val?.[1]?.toString()\n\t\t\t\t? `${formatOne(new Date(val[0].toString()))}-${formatOne(\n\t\t\t\t\t\tnew Date(val[1].toString()),\n\t\t\t\t )}`\n\t\t\t\t: `${formatOne(DateUtils.getPastDate(new Date(), 30))}-${formatOne(\n\t\t\t\t\t\tnew Date(),\n\t\t\t\t )}`;\n\t\t}\n\n\t\treturn formatOne(new Date(val.toString()));\n\t};\n\treturn (\n\t\t<>\n\t\t\t<DateBtn\n\t\t\t\tid={nativeID}\n\t\t\t\tref={buttonRef}\n\t\t\t\tbgColor={disabled ? COLORS.background.base : COLORS.surface.standard}\n\t\t\t\tborderColor={COLORS.stroke.primary}\n\t\t\t\tonClick={() => setPicker(!showPicker)}\n\t\t\t\tdateButtonType={dateButtonType || DateButtonType.DEFAULT}\n\t\t\t\tstyle={dateButtonStyles}\n\t\t\t>\n\t\t\t\t{dateButtonType === DateButtonType.LARGE ? (\n\t\t\t\t\t<BodyPrimary\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tcolor: disabled\n\t\t\t\t\t\t\t\t? COLORS.content.inactive\n\t\t\t\t\t\t\t\t: COLORS.content.primary,\n\t\t\t\t\t\t\tflex: 1,\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{getDisplayValue()}\n\t\t\t\t\t</BodyPrimary>\n\t\t\t\t) : (\n\t\t\t\t\t<BodySecondary\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tcolor: disabled\n\t\t\t\t\t\t\t\t? COLORS.content.inactive\n\t\t\t\t\t\t\t\t: COLORS.content.primary,\n\t\t\t\t\t\t\tflex: 1,\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{getDisplayValue()}\n\t\t\t\t\t</BodySecondary>\n\t\t\t\t)}\n\t\t\t\t<CalendarIcon\n\t\t\t\t\twidth={dateButtonType === DateButtonType.LARGE ? 24 : 20}\n\t\t\t\t\theight={dateButtonType === DateButtonType.LARGE ? 24 : 20}\n\t\t\t\t\tcolor={disabled ? COLORS.content.inactive : COLORS.content.primary}\n\t\t\t\t/>\n\t\t\t</DateBtn>\n\t\t\t{showPicker && !disabled && (\n\t\t\t\t<PickerContainer\n\t\t\t\t\tref={\n\t\t\t\t\t\tpickerRef as\n\t\t\t\t\t\t\t| ((instance: HTMLDivElement | null) => void)\n\t\t\t\t\t\t\t| React.RefObject<HTMLDivElement>\n\t\t\t\t\t\t\t| null\n\t\t\t\t\t\t\t| undefined\n\t\t\t\t\t}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\t...pickerStyles,\n\t\t\t\t\t\t// top: openUpwards ? 'auto' : 'calc(100% + 8px)',\n\t\t\t\t\t\t// bottom: openUpwards ? 'calc(100% + 8px)' : 'auto',\n\t\t\t\t\t}}\n\t\t\t\t\tcontentPrimary={COLORS.content.primary}\n\t\t\t\t\tsubDued={COLORS.surface.subdued}\n\t\t\t\t\tbrandColor={COLORS.background.brand}\n\t\t\t\t\tbgColor={COLORS.surface.standard}\n\t\t\t\t\tborderColor={COLORS.stroke.primary}\n\t\t\t\t>\n\t\t\t\t\t<Calendar\n\t\t\t\t\t\ttileClassName={['custom__tile']}\n\t\t\t\t\t\tclassName={['custom__calendar']}\n\t\t\t\t\t\tonChange={(d) => {\n\t\t\t\t\t\t\tonChange(d);\n\t\t\t\t\t\t\tsetPicker(false);\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tmaxDate={maxDate}\n\t\t\t\t\t\tminDate={minDate}\n\t\t\t\t\t\tvalue={\n\t\t\t\t\t\t\tval\n\t\t\t\t\t\t\t\t? Array.isArray(val)\n\t\t\t\t\t\t\t\t\t? val.length === 2\n\t\t\t\t\t\t\t\t\t\t? [new Date(val[0]), new Date(val[1])]\n\t\t\t\t\t\t\t\t\t\t: null\n\t\t\t\t\t\t\t\t\t: new Date(val)\n\t\t\t\t\t\t\t\t: null\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{...calendarProps}\n\t\t\t\t\t/>\n\t\t\t\t</PickerContainer>\n\t\t\t)}\n\t\t</>\n\t);\n};\n"],"names":["DatePickerWrapper","val","onChange","calendarProps","disabled","pickerStyles","nativeID","maxDate","minDate","dateButtonType","dateButtonStyles","displayFormat","placeholder","showPicker","setPicker","useState","buttonRef","useRef","pickerRef","useOutside","formatOne","date","formatDate","getDisplayValue","DateUtils","jsxs","Fragment","DateBtn","COLORS","DateButtonType","jsx","BodyPrimary","BodySecondary","CalendarIcon","PickerContainer","Calendar","d"],"mappings":"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"}
|