UNPKG

react-accessible-time-picker

Version:

A simple and customizable time picker component for React

568 lines (562 loc) 25.4 kB
"use strict"; 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 });