react-accessible-time-picker
Version:
A simple and customizable time picker component for React
568 lines (562 loc) • 25.4 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
TimePicker: () => TimePicker,
default: () => index_default
});
module.exports = __toCommonJS(index_exports);
// src/component/TimePicker/TimePicker.tsx
var import_style = require("./assets/style-LAC5NIQT.css");
var import_react = require("react");
var Select = __toESM(require("@radix-ui/react-select"), 1);
var Popover = __toESM(require("@radix-ui/react-popover"), 1);
var Toolbar = __toESM(require("@radix-ui/react-toolbar"), 1);
var import_react_icons = require("@radix-ui/react-icons");
var import_TimePicker = __toESM(require("./assets/TimePicker.module-LJD4BTOI.module.css"), 1);
var import_classnames2 = __toESM(require("classnames"), 1);
// src/component/ScrollArea/ScrollArea.tsx
var RadixScroll = __toESM(require("@radix-ui/react-scroll-area"), 1);
var import_ScrollArea = __toESM(require("./assets/ScrollArea.module-3UBZVTPC.module.css"), 1);
var import_classnames = __toESM(require("classnames"), 1);
var import_jsx_runtime = require("react/jsx-runtime");
function ScrollArea({ children, className, ...rest }) {
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(RadixScroll.Root, { className: import_ScrollArea.default.scrollArea, children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
RadixScroll.Viewport,
{
className: (0, import_classnames.default)(import_ScrollArea.default.viewport, className),
...rest,
children
}
),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
RadixScroll.Scrollbar,
{
className: import_ScrollArea.default.scrollbar,
orientation: "vertical",
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RadixScroll.Thumb, { className: import_ScrollArea.default.thumb })
}
),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
RadixScroll.Scrollbar,
{
className: import_ScrollArea.default.scrollbar,
orientation: "horizontal",
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RadixScroll.Thumb, { className: import_ScrollArea.default.thumb })
}
)
] });
}
var ScrollArea_default = ScrollArea;
// src/component/TimePicker/TimePicker.tsx
var import_jsx_runtime2 = require("react/jsx-runtime");
function TimePicker({
is24Hour = true,
value,
onChange,
label,
id,
disabled,
required,
minuteStep = 5,
hourStep = 1,
hourPlaceholder = "HH",
minutePlaceholder = "MM",
popoverColumnHourTitle = "Hours",
popoverColumnMinuteTitle = "Minutes",
classes = {}
}) {
const [internalHour, setInternalHour] = (0, import_react.useState)("");
const [internalMinute, setInternalMinute] = (0, import_react.useState)("");
const [internalPeriod, setInternalPeriod] = (0, import_react.useState)("AM");
const [popoverOpen, setPopoverOpen] = (0, import_react.useState)(false);
const hour = value?.hour ?? internalHour;
const minute = value?.minute ?? internalMinute;
const period = value?.period ?? internalPeriod;
const hourRef = (0, import_react.useRef)(null);
const minuteRef = (0, import_react.useRef)(null);
const updateTime = (newTime) => {
const updated = {
hour,
minute,
period,
...newTime
};
if (onChange) {
onChange(updated);
} else {
if (newTime.hour !== void 0) setInternalHour(newTime.hour);
if (newTime.minute !== void 0) setInternalMinute(newTime.minute);
if (newTime.period !== void 0) setInternalPeriod(newTime.period);
}
};
const handleHourChange = (e) => {
const val = e.target.value;
if (disabled) return;
if (val === "" || /^\d+$/.test(val)) {
if (val.length <= 1) {
updateTime({ hour: val });
} else if (val.length === 2) {
const numVal = parseInt(val, 10);
const minHour = is24Hour ? 0 : 1;
const maxHour = is24Hour ? 23 : 12;
if (numVal >= minHour && numVal <= maxHour) {
updateTime({ hour: val });
setTimeout(() => {
minuteRef.current?.focus();
}, 0);
}
}
}
};
const handleMinuteChange = (e) => {
const val = e.target.value;
if (disabled) return;
if (val === "" || /^\d+$/.test(val)) {
const numVal = val === "" ? 0 : parseInt(val, 10);
if (val === "" || numVal >= 0 && numVal <= 59) {
updateTime({ minute: val });
}
}
};
const handleKeyDown = (e, type) => {
const items = Array.from(
document.querySelectorAll(
type === "hour" ? "[data-hour]" : "[data-minute]"
)
);
if (!items.length) return;
const currentIndex = items.findIndex(
(item) => document.activeElement === item
);
let nextIndex;
if (e.key === "ArrowDown") {
nextIndex = (currentIndex + 1) % items.length;
} else if (e.key === "ArrowUp") {
nextIndex = (currentIndex - 1 + items.length) % items.length;
} else {
return;
}
e.preventDefault();
items[nextIndex].focus();
};
const handleHourKeyDown = (e) => {
if (disabled) return;
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
e.preventDefault();
const currentHour = hour === "" ? is24Hour ? 0 : 1 : parseInt(hour, 10);
const minHour = is24Hour ? 0 : 1;
const maxHour = is24Hour ? 23 : 12;
let newHour;
if (e.key === "ArrowUp") {
newHour = currentHour >= maxHour - (hourStep - 1) ? minHour : currentHour + hourStep;
if (newHour > maxHour) newHour = minHour;
} else {
newHour = currentHour <= minHour + (hourStep - 1) ? maxHour : currentHour - hourStep;
if (newHour < minHour) newHour = maxHour - maxHour % hourStep;
}
updateTime({ hour: newHour.toString().padStart(2, "0") });
}
};
const handleMinuteKeyDown = (e) => {
if (disabled) return;
if (e.key === "Backspace" && minute.length <= 1) {
setTimeout(() => {
hourRef.current?.focus();
}, 0);
}
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
e.preventDefault();
const currentMinute = minute === "" ? 0 : parseInt(minute, 10);
let newMinute;
if (e.key === "ArrowUp") {
newMinute = (currentMinute + minuteStep) % 60;
} else {
newMinute = (currentMinute - minuteStep + 60) % 60;
}
updateTime({ minute: newMinute.toString().padStart(2, "0") });
}
};
const handleHourBlur = () => {
if (disabled) return;
if (!is24Hour && hour === "0") {
updateTime({ hour: "12" });
} else if (hour.length === 1) {
updateTime({ hour: `0${hour}` });
}
};
const handleMinuteBlur = () => {
if (disabled) return;
if (minute === "") {
updateTime({ minute: "00" });
} else if (minute.length === 1) {
updateTime({ minute: `0${minute}` });
} else {
const minuteNum = parseInt(minute, 10);
const roundedMinute = Math.round(minuteNum / minuteStep) * minuteStep;
const finalMinute = roundedMinute % 60;
updateTime({ minute: finalMinute.toString().padStart(2, "0") });
}
};
const generateHours = () => {
const minHour = is24Hour ? 0 : 1;
const maxHour = is24Hour ? 23 : 12;
const hours2 = [];
for (let i = minHour; i <= maxHour; i += hourStep) {
hours2.push(i.toString().padStart(2, "0"));
}
return hours2;
};
const generateMinutes = () => {
const minutes2 = [];
for (let i = 0; i < 60; i += minuteStep) {
minutes2.push(i.toString().padStart(2, "0"));
}
return minutes2;
};
const hours = generateHours();
const minutes = generateMinutes();
const handleHourClick = (h) => {
updateTime({ hour: h });
};
const handleMinuteClick = (m) => {
updateTime({ minute: m });
if (hour !== "") {
setPopoverOpen(false);
}
};
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
"div",
{
className: (0, import_classnames2.default)(import_TimePicker.default.container, classes.container),
role: "group",
"aria-labelledby": id ? `${id}-label` : void 0,
children: [
label && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
"label",
{
id: id ? `${id}-label` : void 0,
className: (0, import_classnames2.default)(import_TimePicker.default.label, classes.label),
children: [
label,
required && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "aria-hidden": "true", children: "*" })
]
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
"div",
{
className: (0, import_classnames2.default)(import_TimePicker.default.timePicker, classes.timePicker, {
[import_TimePicker.default.disabled]: disabled
}),
children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: (0, import_classnames2.default)(import_TimePicker.default.timeInputs, classes.timeInputs), children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"input",
{
ref: hourRef,
type: "text",
inputMode: "numeric",
pattern: "[0-9]*",
value: hour,
onChange: handleHourChange,
onKeyDown: handleHourKeyDown,
onBlur: handleHourBlur,
placeholder: hourPlaceholder,
className: (0, import_classnames2.default)(import_TimePicker.default.timeInput, classes.timeInput),
"aria-label": "Hour",
id: id ? `${id}-hour` : void 0,
disabled,
required
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: (0, import_classnames2.default)(import_TimePicker.default.separator, classes.separator), children: ":" }),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"input",
{
ref: minuteRef,
type: "text",
inputMode: "numeric",
pattern: "[0-9]*",
value: minute,
onChange: handleMinuteChange,
onKeyDown: handleMinuteKeyDown,
onBlur: handleMinuteBlur,
placeholder: minutePlaceholder,
className: (0, import_classnames2.default)(import_TimePicker.default.timeInput, classes.timeInput),
"aria-label": "Minute",
id: id ? `${id}-minute` : void 0,
disabled,
required
}
)
] }),
!is24Hour && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"span",
{
className: (0, import_classnames2.default)(import_TimePicker.default.pipe, classes.pipe),
"aria-hidden": "true",
children: "|"
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
Select.Root,
{
value: period,
onValueChange: (val) => updateTime({ period: val }),
disabled,
children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
Select.Trigger,
{
className: (0, import_classnames2.default)(
import_TimePicker.default.periodSelect,
classes.periodSelect
),
"aria-label": "AM/PM",
tabIndex: 0,
children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Select.Value, { placeholder: "AM/PM" }),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Select.Icon, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_icons.ChevronDownIcon, {}) })
]
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Select.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
Select.Content,
{
className: (0, import_classnames2.default)(
import_TimePicker.default.selectContent,
classes.selectContent
),
children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
Select.ScrollUpButton,
{
className: (0, import_classnames2.default)(
import_TimePicker.default.selectScrollButton,
classes.selectScrollButton
)
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Select.Viewport, { children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
Select.Item,
{
value: "AM",
className: (0, import_classnames2.default)(
import_TimePicker.default.selectItem,
classes.selectItem
),
children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
Select.ItemIndicator,
{
className: (0, import_classnames2.default)(
import_TimePicker.default.selectIndicator,
classes.selectIndicator
),
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_icons.CheckIcon, {})
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Select.ItemText, { children: "AM" })
]
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
Select.Item,
{
value: "PM",
className: (0, import_classnames2.default)(
import_TimePicker.default.selectItem,
classes.selectItem
),
children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
Select.ItemIndicator,
{
className: (0, import_classnames2.default)(
import_TimePicker.default.selectIndicator,
classes.selectIndicator
),
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_icons.CheckIcon, {})
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Select.ItemText, { children: "PM" })
]
}
)
] }),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
Select.ScrollDownButton,
{
className: (0, import_classnames2.default)(
import_TimePicker.default.selectScrollButton,
classes.selectScrollButton
)
}
)
]
}
) })
]
}
)
] }),
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Popover.Root, { open: popoverOpen, onOpenChange: setPopoverOpen, children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
Popover.Trigger,
{
className: (0, import_classnames2.default)(import_TimePicker.default.timerTrigger, classes.timeTrigger),
disabled,
"data-state": popoverOpen ? "open" : "closed",
tabIndex: 0,
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_icons.TimerIcon, {})
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
Popover.Content,
{
className: (0, import_classnames2.default)(
import_TimePicker.default.popoverContent,
classes.popoverContent
),
sideOffset: 5,
"data-ignore-outside-click": true,
children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
"div",
{
className: (0, import_classnames2.default)(
import_TimePicker.default.popoverColumns,
classes.popoverColumns
),
onKeyDown: (e) => {
e.stopPropagation();
},
children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
Toolbar.Root,
{
className: (0, import_classnames2.default)(
import_TimePicker.default.popoverColumn,
classes.popoverColumn
),
"aria-label": "Select Hour",
onKeyDown: (e) => handleKeyDown(e, "hour"),
children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"div",
{
className: (0, import_classnames2.default)(
import_TimePicker.default.popoverColumnTitle,
classes.popoverColumnTitle
),
children: popoverColumnHourTitle
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ScrollArea_default, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: import_TimePicker.default.popoverColumnContent, children: hours.map((h) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Toolbar.Button, { asChild: true, value: h, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"button",
{
type: "button",
"data-hour": true,
className: (0, import_classnames2.default)(
import_TimePicker.default.popoverItem,
hour === h && import_TimePicker.default.popoverActiveItem,
classes.popoverItem,
hour === h && classes.popoverActiveItem
),
onClick: () => handleHourClick(h),
children: h
}
) }, h)) }) })
]
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
Toolbar.Root,
{
className: (0, import_classnames2.default)(
import_TimePicker.default.popoverColumn,
classes.popoverColumn
),
"aria-label": "Select Minute",
onKeyDown: (e) => handleKeyDown(e, "minute"),
children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"div",
{
className: (0, import_classnames2.default)(
import_TimePicker.default.popoverColumnTitle,
classes.popoverColumnTitle
),
children: popoverColumnMinuteTitle
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ScrollArea_default, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: import_TimePicker.default.popoverColumnContent, children: minutes.map((m) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Toolbar.Button, { asChild: true, value: m, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"button",
{
type: "button",
"data-minute": true,
className: (0, import_classnames2.default)(
import_TimePicker.default.popoverItem,
minute === m && import_TimePicker.default.popoverActiveItem,
classes.popoverItem,
minute === m && classes.popoverActiveItem
),
onClick: () => handleMinuteClick(m),
children: m
}
) }, m)) }) })
]
}
)
]
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Popover.Arrow, {})
]
}
) })
] })
]
}
)
]
}
);
}
// src/index.ts
var index_default = TimePicker;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
TimePicker
});