2024-03-01 16:54:56 +00:00

289 lines
11 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import { DateTime } from "luxon";
import { useFlash } from "../App/FlashContext";
import { useTimezone } from "../App/TimezoneProvider";
type TimeUpdate = {
year?: number | null;
month?: number;
day?: number;
hour?: number;
minute?: number;
second?: number;
};
export const TimeInput = ({ defaultValue, onInput }) => {
const ref = useRef(null);
const [timezone] = useTimezone();
const [time, setTime] = useState(
defaultValue ? DateTime.fromISO(defaultValue).setZone(timezone) : null,
);
const updateTime = useCallback(
(upd: TimeUpdate) => {
if (upd === null) {
setTime(null);
}
let newTime = time;
if (newTime === null) {
newTime = DateTime.now().setZone(timezone);
}
setTime(newTime.set(upd));
},
[time, timezone],
);
useEffect(() => {
onInput(time?.toFormat("yyyy-LL-dd'T'HH:mm:ss"));
}, [time]);
const flash = useFlash();
return (
<>
<div
class={"input"}
onPaste={(ev) => {
ev.preventDefault();
const pasteValue = ev.clipboardData.getData("text/plain");
let dt = DateTime.fromISO(pasteValue);
if (dt.isValid) {
setTime(dt);
return;
}
dt = DateTime.fromSQL(pasteValue);
if (dt.isValid) {
setTime(dt);
return;
}
flash({
message: "Couldn't parse your clipboard data as a valid date-time",
type: "error",
});
}}
>
<div style={{ flexGrow: "1" }}>
<label>
<span class="is-sr-only">Years input</span>
<input
style={{
borderStyle: "none",
fontFamily: "monospace",
width: "calc(4ch + 4px)",
fontSize: "1rem",
}}
type="text"
pattern="\d*"
maxlength={4}
placeholder="YYYY"
value={
time
? time.year.toLocaleString("en-US", {
minimumIntegerDigits: 4,
useGrouping: false,
})
: ""
}
onBlur={(ev) => {
ev.currentTarget.value
? updateTime({
year: parseInt(ev.currentTarget.value),
})
: updateTime(null);
}}
></input>{" "}
</label>
-
<label>
<span class="is-sr-only">Months input</span>
<input
style={{
borderStyle: "none",
fontFamily: "monospace",
width: "calc(2ch + 4px)",
fontSize: "1rem",
}}
type="text"
pattern="\d*"
maxlength={2}
placeholder="MM"
value={
time
? time.month.toLocaleString("en-US", {
minimumIntegerDigits: 2,
})
: ""
}
onBlur={(ev) => {
ev.currentTarget.value
? updateTime({
month: parseInt(ev.currentTarget.value),
})
: updateTime(null);
}}
></input>{" "}
</label>
-
<label>
<span class="is-sr-only">Days input</span>
<input
style={{
borderStyle: "none",
fontFamily: "monospace",
width: "calc(2ch + 4px)",
fontSize: "1rem",
}}
type="text"
pattern="\d*"
maxlength={2}
placeholder="DD"
value={
time
? time.day.toLocaleString("en-US", { minimumIntegerDigits: 2 })
: ""
}
onBlur={(ev) => {
ev.currentTarget.value
? updateTime({
day: parseInt(ev.currentTarget.value),
})
: updateTime(null);
}}
></input>{" "}
</label>
<label style={{ marginLeft: "8px" }}>
<span class="is-sr-only">Hours input</span>
<input
style={{
borderStyle: "none",
fontFamily: "monospace",
width: "calc(2ch + 4px)",
fontSize: "1rem",
}}
type="text"
pattern="\d*"
maxlength={2}
placeholder="hh"
value={
time
? time.hour.toLocaleString("en-US", { minimumIntegerDigits: 2 })
: ""
}
onBlur={(ev) => {
ev.currentTarget.value
? updateTime({
hour: parseInt(ev.currentTarget.value),
})
: updateTime(null);
}}
></input>{" "}
</label>
:
<label>
<span class="is-sr-only">Minutes input</span>
<input
style={{
borderStyle: "none",
fontFamily: "monospace",
width: "calc(2ch + 4px)",
fontSize: "1rem",
}}
type="text"
pattern="\d*"
maxlength={2}
placeholder="mm"
value={
time
? time.minute.toLocaleString("en-US", {
minimumIntegerDigits: 2,
})
: ""
}
onBlur={(ev) => {
ev.currentTarget.value
? updateTime({
minute: parseInt(ev.currentTarget.value),
})
: updateTime(null);
}}
></input>{" "}
</label>
:
<label>
<span class="is-sr-only">Seconds input</span>
<input
style={{
borderStyle: "none",
fontFamily: "monospace",
width: "calc(2ch + 4px)",
fontSize: "1rem",
}}
type="text"
pattern="\d*"
maxlength={2}
placeholder="ss"
value={
time
? time.second.toLocaleString("en-US", {
minimumIntegerDigits: 2,
})
: ""
}
onBlur={(ev) => {
ev.currentTarget.value
? updateTime({
second: parseInt(ev.currentTarget.value),
})
: updateTime(null);
}}
></input>{" "}
</label>
</div>
<button
style={{
background: "none",
border: "none",
padding: "1px",
marginRight: "-3px",
}}
onClick={() => {
ref.current.showPicker();
}}
>
<span class="is-sr-only">Show time picker</span>
<span class="icon">
<i class="fas fa-calendar"></i>
</span>
</button>
</div>
<input
style={{
position: "absolute",
left: 0,
visibility: "hidden",
}}
class={"input"}
type="datetime-local"
step="1"
value={
time
? time.toFormat("yyyy-LL-dd'T'HH:mm:ss")
: DateTime.now().toFormat("yyyy-LL-dd'T'HH:mm:ss")
}
ref={ref}
onInput={(ev) => {
ev.currentTarget.value === ""
? setTime(null)
: setTime(DateTime.fromISO(ev.currentTarget.value));
}}
></input>
</>
);
};