UNPKG

@neo4j-ndl/react

Version:

React implementation of Neo4j Design System

293 lines 16.7 kB
"use strict"; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TimePicker = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); /** * * Copyright (c) "Neo4j" * Neo4j Sweden AB [http://neo4j.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ const react_1 = require("@floating-ui/react"); const base_1 = require("@neo4j-ndl/base"); const classnames_1 = __importDefault(require("classnames")); const react_2 = require("react"); const conditional_wrap_1 = require("../conditional-wrap"); const dialog_context_1 = require("../dialog/dialog-context"); const icons_1 = require("../icons"); const theme_1 = require("../theme"); const typography_1 = require("../typography"); const generate_time_options_1 = require("./generate-time-options"); const needle_time_1 = require("./needle-time"); const time_picker_hooks_1 = require("./time-picker-hooks"); const TimePicker = (_a) => { var _b, _c, _d; var { format = 'hh:mm', isDisabled, isFluid, isReadOnly, isRequired, value, timeInterval = 15, label, onChange, className, onError, style, size = 'medium', errorText, htmlAttributes, floatingStrategy, isPortaled: isPortaledProp, ref } = _a, restProps = __rest(_a, ["format", "isDisabled", "isFluid", "isReadOnly", "isRequired", "value", "timeInterval", "label", "onChange", "className", "onError", "style", "size", "errorText", "htmlAttributes", "floatingStrategy", "isPortaled", "ref"]); const [editedInput, setEditedInput] = (0, react_2.useState)(value); const is24Hour = format === 'hh:mm'; const [inputValue, setInputValue] = (0, react_2.useState)((_b = value === null || value === void 0 ? void 0 : value.toString(is24Hour)) !== null && _b !== void 0 ? _b : ''); const listRef = (0, react_2.useRef)(null); const inputRef = (0, react_2.useRef)(null); (0, react_2.useImperativeHandle)(ref, () => inputRef.current, []); const [lastAction, setLastAction] = (0, react_2.useState)('type'); const [error, setError] = (0, react_2.useState)(undefined); const timeOptions = (0, react_2.useMemo)(() => (0, generate_time_options_1.generateTimeOptions)(is24Hour, timeInterval), [is24Hour, timeInterval]); const { themeClassName } = (0, theme_1.useNeedleTheme)(); const { isPopoverOpen, setIsPopoverOpen } = (0, time_picker_hooks_1.useTimePickerPopover)(false); const { focusedIndex, setFocusedIndex, handleArrowNavigation } = (0, time_picker_hooks_1.useKeyboardNavigation)(listRef, timeOptions.length); const classes = { 'ndl-small': size === 'small', 'ndl-medium': size === 'medium', 'ndl-large': size === 'large', 'ndl-error': error !== undefined, 'ndl-fluid': isFluid, 'ndl-disabled': isDisabled, 'ndl-read-only': isReadOnly, }; const inputWidth = isFluid === true ? '100%' : is24Hour === true ? '6rem' : '9rem'; const errorToShow = errorText !== undefined && errorText !== '' ? errorText : error; const isValidTime = (0, react_2.useCallback)((hours, minutes) => { if (hours > 23 || hours < 0) { return false; } if (minutes > 59 || minutes < 0) { return false; } return true; }, []); (0, react_2.useEffect)(() => { if (error !== undefined) { onError === null || onError === void 0 ? void 0 : onError(error, needle_time_1.NeedleTime.fromString(inputValue)); } }, [error, inputValue, onError]); (0, react_2.useEffect)(() => { var _a; setEditedInput(value); setInputValue((_a = value === null || value === void 0 ? void 0 : value.toString(is24Hour)) !== null && _a !== void 0 ? _a : ''); }, [is24Hour, value]); (0, react_2.useEffect)(() => { if (isPopoverOpen) { setTimeout(() => { var _a, _b; (_b = (_a = listRef.current) === null || _a === void 0 ? void 0 : _a.children[focusedIndex]) === null || _b === void 0 ? void 0 : _b.scrollIntoView({ block: 'center', inline: 'nearest', }); }, 0); } else { setTimeout(() => { var _a, _b, _c; (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur(); const selectedIndex = (_c = timeOptions.indexOf((_b = value === null || value === void 0 ? void 0 : value.toString(is24Hour)) !== null && _b !== void 0 ? _b : '')) !== null && _c !== void 0 ? _c : 0; setFocusedIndex(selectedIndex); }, 0); } }, [ focusedIndex, is24Hour, isPopoverOpen, value, setFocusedIndex, timeOptions, ]); const handleOnClick = (timeText) => { if (onChange) { onChange(needle_time_1.NeedleTime.fromString(timeText)); } setError(undefined); setInputValue(timeText); setEditedInput(needle_time_1.NeedleTime.fromString(timeText)); setIsPopoverOpen(false); }; const newHandleInputChange = (e) => { scrollToTime(e.target.value); setInputValue(e.target.value); setLastAction('type'); const time = needle_time_1.NeedleTime.fromString(e.target.value); if (!isValidTime(time.hour, time.minute)) { setError('Invalid time'); } else { setError(undefined); } }; const handleKeyDown = (e) => { if (isReadOnly === true || isDisabled === true) { e.preventDefault(); return; } if (e.key === 'Escape') { setIsPopoverOpen(false); } setIsPopoverOpen(true); if (e.key === 'Enter') { if (lastAction === 'type' && inputValue.length >= (is24Hour ? 5 : 6)) { const time = needle_time_1.NeedleTime.fromString(inputValue); handleOnClick(time.toString(is24Hour)); } else if (lastAction === 'type' && !inputValue.includes(':')) { const time = needle_time_1.NeedleTime.fromString(inputValue); if (isValidTime(time.hour, time.minute)) { handleOnClick(time.toString(is24Hour)); } setIsPopoverOpen(false); } else { handleOnClick(timeOptions[focusedIndex]); } } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { e.preventDefault(); setLastAction('arrow'); handleArrowNavigation(e.key); } }; const scrollToTime = (startOfTime) => { const parsedTime = startOfTime.toLowerCase().replace(/[^0-9amp]/g, ''); let index = -1; // Handle AM/PM input const isPM = parsedTime.includes('p'); const isAM = parsedTime.includes('a'); const timeDigits = parsedTime.replace(/[amp]/g, ''); if (timeDigits.length === 1) { // Single digit - assume it's an hour const hour = `0${timeDigits}`; const searchTime = is24Hour ? `${hour}:00` : `${hour}:00 ${isPM ? 'PM' : 'AM'}`; index = timeOptions.indexOf(searchTime); } else { // Find closest matching time const closestTime = timeOptions.find((time) => { const timeWithoutSpecialChars = time .toLowerCase() .replace(/[^0-9amp]/g, ''); if (isPM || isAM) { // If AM/PM specified, match that specifically return timeWithoutSpecialChars.startsWith(parsedTime); } else { // Otherwise just match the numbers return timeWithoutSpecialChars .replace(/[amp]/g, '') .startsWith(timeDigits); } }); index = closestTime !== undefined ? timeOptions.indexOf(closestTime) : -1; } if (index !== -1) { setFocusedIndex(index); } return index; }; /** Custom floating ui solution */ const isInsideDialog = (0, dialog_context_1.useIsInsideDialog)(); const isPortaled = isPortaledProp !== null && isPortaledProp !== void 0 ? isPortaledProp : !isInsideDialog; const containerRef = (0, react_2.useRef)(null); const { refs, floatingStyles, context } = (0, react_1.useFloating)({ elements: { reference: containerRef.current, }, middleware: [ (0, react_1.offset)(10), (0, react_1.flip)({ fallbackPlacements: ['top'], fallbackStrategy: 'bestFit', }), (0, react_1.shift)({ padding: 5 }), ], onOpenChange: (open) => { setIsPopoverOpen(open); }, open: isDisabled !== true && isReadOnly !== true && isPopoverOpen, placement: 'bottom', strategy: floatingStrategy !== null && floatingStrategy !== void 0 ? floatingStrategy : (isInsideDialog ? 'fixed' : 'absolute'), whileElementsMounted: react_1.autoUpdate, }); const dismiss = (0, react_1.useDismiss)(context); const { getReferenceProps, getFloatingProps } = (0, react_1.useInteractions)([dismiss]); const { styles: transitionStyles } = (0, react_1.useTransitionStyles)(context, { duration: (_c = Number.parseInt(base_1.tokens.motion.duration.quick)) !== null && _c !== void 0 ? _c : 0, }); const mergedRef = (0, react_1.useMergeRefs)([refs.setReference, containerRef]); return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", Object.assign({ className: (0, classnames_1.default)('ndl-time-picker', className, Object.assign({}, classes)), ref: mergedRef }, getReferenceProps(), { style: { width: isFluid === true ? '100%' : 'fit-content', }, children: [(0, jsx_runtime_1.jsxs)("label", { className: "ndl-time-picker-label", children: [label !== undefined && ((0, jsx_runtime_1.jsx)(typography_1.Typography, { variant: size === 'large' ? 'body-large' : 'body-medium', children: label })), (0, jsx_runtime_1.jsxs)("div", { className: "ndl-time-picker-input-wrapper", style: Object.assign({ width: inputWidth }, style), children: [(0, jsx_runtime_1.jsx)("input", Object.assign({ "aria-label": label, className: "ndl-time-picker-input", type: "text", ref: inputRef, value: inputValue, disabled: isDisabled, readOnly: isReadOnly, required: isRequired, onChange: newHandleInputChange, onKeyDown: handleKeyDown, onClick: () => { if (isReadOnly === true || isDisabled === true) { return; } setIsPopoverOpen(true); }, onBlur: (e) => { var _a; if (isReadOnly === true || isDisabled === true) { return; } if (((_a = e.relatedTarget) === null || _a === void 0 ? void 0 : _a.classList.contains('ndl-time-picker-popover-item')) === true) { return; } if (inputValue.length < 4) { setError('Invalid time'); return; } const time = needle_time_1.NeedleTime.fromString(inputValue); if (time === undefined || time === null) { setError('Invalid time'); return; } if (!isValidTime(time.hour, time.minute)) { setError('Invalid time'); } else { setError(undefined); setInputValue(time.toString(is24Hour)); } }, placeholder: format, maxLength: is24Hour ? 5 : 8 }, restProps, htmlAttributes)), (0, jsx_runtime_1.jsx)(icons_1.ClockIconOutline, { className: "ndl-icon-svg ndl-time-picker-icon" })] })] }), errorToShow !== undefined && ((0, jsx_runtime_1.jsxs)("div", { className: "ndl-time-picker-error-wrapper", children: [(0, jsx_runtime_1.jsx)(icons_1.ExclamationCircleIconSolid, { className: "ndl-time-picker-error-icon" }), (0, jsx_runtime_1.jsx)(typography_1.Typography, { variant: size === 'large' ? 'body-medium' : 'body-small', className: "ndl-time-picker-error-text", children: errorToShow })] }))] })), context.open && ((0, jsx_runtime_1.jsx)(conditional_wrap_1.ConditionalWrap, { shouldWrap: isPortaled, wrap: (wrapChildren) => ((0, jsx_runtime_1.jsx)(react_1.FloatingPortal, { children: wrapChildren })), children: (0, jsx_runtime_1.jsx)(react_1.FloatingFocusManager, { context: context, modal: false, initialFocus: -1, children: (0, jsx_runtime_1.jsx)("div", Object.assign({ ref: refs.setFloating, className: (0, classnames_1.default)(themeClassName, 'ndl-time-picker-popover', Object.assign({}, classes)), style: Object.assign(Object.assign(Object.assign({}, transitionStyles), floatingStyles), { width: isFluid === true ? (_d = containerRef.current) === null || _d === void 0 ? void 0 : _d.clientWidth : undefined }) }, getFloatingProps(), { children: (0, jsx_runtime_1.jsx)("ul", { ref: listRef, tabIndex: -1, children: timeOptions.map((time, i) => ((0, jsx_runtime_1.jsx)("li", { role: "option", "aria-selected": (value === null || value === void 0 ? void 0 : value.toString(is24Hour)) === time || (editedInput === null || editedInput === void 0 ? void 0 : editedInput.toString(is24Hour)) === time, value: time, onClick: (e) => { e.stopPropagation(); e.preventDefault(); handleOnClick(time); setIsPopoverOpen(false); }, onMouseDown: (e) => { e.stopPropagation(); e.preventDefault(); }, tabIndex: -1, className: (0, classnames_1.default)(focusedIndex === i ? 'focused' : '', 'ndl-time-picker-popover-item'), onKeyDown: (e) => { if (e.key === 'Enter') { e.stopPropagation(); handleOnClick(time); } }, children: time }, i))) }) })) }) }))] })); }; exports.TimePicker = TimePicker; //# sourceMappingURL=TimePicker.js.map