reablocks
Version:
Component library for React
1,672 lines • 343 kB
JavaScript
import * as React from "react";
import { useEffect, createContext, useState, useRef, useLayoutEffect, forwardRef, useImperativeHandle, useCallback, Fragment, useMemo, useContext, createElement, Children, memo, isValidElement, cloneElement, Suspense } from "react";
import { jsx, jsxs, Fragment as Fragment$1 } from "react/jsx-runtime";
import { disableBodyScroll, clearAllBodyScrollLocks } from "body-scroll-lock-upgrade";
import { motion, AnimatePresence, useMotionValue, useTransform } from "motion/react";
import { createPortal } from "react-dom";
import { useFloating, autoUpdate, flip, shift, limitShift, size, offset, arrow } from "@floating-ui/react";
import { getHours, getMinutes, getSeconds, setSeconds, setMinutes, setHours, isValid, getDaysInMonth, startOfMonth, getDay, subDays, format, getISODay, getDate, isSameMonth, isSameDay, addDays, startOfDay, endOfDay, min, max, isBefore, isAfter, differenceInMinutes, differenceInSeconds, isWithinInterval, isToday, isSameHour, subWeeks, subMonths, subYears, addWeeks, addMonths, addYears, startOfWeek, endOfWeek, endOfMonth, startOfYear, endOfYear, getMonth, getYear, startOfDecade, endOfDecade, sub, add, setMonth, setYear, parse, formatDistance } from "date-fns";
import FocusTrap from "focus-trap-react";
import getInitials from "name-initials";
import ellipsize from "ellipsize";
import { keys } from "ctrl-keys";
import classNames from "classnames";
import { twMerge } from "tailwind-merge";
import isEqual from "react-fast-compare";
import TextareaAutosize from "react-textarea-autosize";
import pluralizeLib from "pluralize";
import humanFormat from "human-format";
import chroma from "chroma-js";
import creteGlobalStateHook from "create-global-state-hook";
const useExitListener = ({
ref,
open = true,
onClickOutside,
onEscape
}) => {
useEffect(() => {
if (!open) {
return;
}
const handleClick = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
onClickOutside?.(event);
}
};
const handleKey = (event) => {
if (event.code === "Escape") {
onEscape?.(event);
}
};
if (onClickOutside) {
document.addEventListener("mousedown", handleClick);
document.addEventListener("touchstart", handleClick);
}
if (onEscape) {
document.addEventListener("keydown", handleKey);
}
return () => {
if (onClickOutside) {
document.removeEventListener("mousedown", handleClick);
document.removeEventListener("touchstart", handleClick);
}
if (onEscape) {
document.removeEventListener("keydown", handleKey);
}
};
}, [ref, onClickOutside, onEscape, open]);
};
const OverlayContext = createContext({
close: () => void 0
});
let id = 0;
const genId = () => `ref-${++id}`;
const useId = (idFromProps) => {
const [id2] = useState(idFromProps || genId());
return `${id2}`;
};
const useUnmount = (fn) => {
const fnRef = useRef(fn);
fnRef.current = fn;
useLayoutEffect(() => () => fnRef.current(), []);
};
const Portal = forwardRef(
({ children, className, style, element = "div", onMount, onUnmount }, ref) => {
const elementRef = useRef(null);
const mounted = useRef(false);
useEffect(() => {
if (className && elementRef.current) {
elementRef.current.setAttribute("class", `${className} rdk-portal`);
}
if (style && elementRef.current) {
Object.keys(style)?.forEach(
(key) => elementRef.current.style?.setProperty(key, style[key])
);
}
}, [className, style, elementRef.current]);
useLayoutEffect(() => {
elementRef.current = document.createElement(element);
onMount?.();
}, []);
useUnmount(() => {
onUnmount?.();
const ref2 = elementRef.current;
if (ref2 && document.body.contains(ref2)) {
document.body.removeChild(ref2);
}
});
useImperativeHandle(ref, () => elementRef.current);
if (!elementRef.current) {
return null;
}
if (!mounted.current) {
mounted.current = true;
elementRef.current.classList.add("rdk-portal");
document.body.appendChild(elementRef.current);
}
return createPortal(children, elementRef.current);
}
);
const portals = [];
const START_INDEX = 990;
const OverlayPortal = forwardRef(
({
className,
children,
onMount,
onUnmount,
appendToBody = true,
id: id2,
style
}, ref) => {
let portalId = useId(id2);
const [portalIndex, setPortalIndex] = useState(null);
const [overlayIndex, setOverlayIndex] = useState(null);
const portalRef = useRef(null);
useImperativeHandle(ref, () => portalRef.current);
return /* @__PURE__ */ jsx(
Portal,
{
className,
ref: portalRef,
style,
onMount: () => {
portals.push(portalId);
let pidx = portals.indexOf(portalId);
setPortalIndex(pidx);
const overlayIdx = START_INDEX + pidx * 2 + 1;
setOverlayIndex(overlayIdx);
onMount?.({
portalId,
overlayIndex: overlayIdx,
portalIndex: pidx,
backdropIndex: overlayIdx
});
},
onUnmount: () => {
onUnmount?.();
portals.splice(portals.indexOf(portalId), 1);
setPortalIndex(null);
setOverlayIndex(null);
},
children: children({
overlayIndex,
portalIndex,
backdropIndex: overlayIndex,
portalId
})
}
);
}
);
const Backdrop = ({
zIndex = 998,
portalIndex = 0,
className,
theme: customTheme,
onClick
}) => {
const theme2 = useComponentTheme("backdrop", customTheme);
return /* @__PURE__ */ jsx(
motion.div,
{
className: cn(theme2.base, className),
initial: { opacity: 0 },
animate: { opacity: theme2.opacity - portalIndex / 10 },
exit: { opacity: 0 },
style: { zIndex },
onClick
}
);
};
const backdropTheme = {
base: "fixed top-0 left-0 w-full h-full opacity-0 select-none bg-black",
opacity: 0.8
};
const GlobalOverlay = ({
open,
hasBackdrop = true,
closeOnEscape = true,
closeOnBackdropClick = true,
backdropClassName,
children,
onClose
}) => {
const overlayRef = useRef(null);
const onBackdropClick = useCallback(() => {
if (closeOnBackdropClick) {
onClose?.();
}
}, [closeOnBackdropClick, onClose]);
useExitListener({
ref: overlayRef,
open,
onEscape: () => closeOnEscape && onClose?.()
});
useEffect(() => {
if (open && overlayRef.current !== void 0) {
disableBodyScroll(overlayRef.current, {
// allowTouchMove determines which elements to allow touchmove events for iOS
// Reference: https://github.com/rick-liruixin/body-scroll-lock-upgrade?tab=readme-ov-file#allowtouchmove
// NOTE: allowTouchMove is typed wrong: https://github.com/rick-liruixin/body-scroll-lock-upgrade/issues/21
allowTouchMove: (el) => {
while (el && el !== document.body) {
if (el.getAttribute("body-scroll-lock-ignore") !== null) {
return true;
}
if (el.parentElement !== null) {
el = el.parentElement;
}
}
return false;
}
});
} else {
clearAllBodyScrollLocks();
}
return () => {
clearAllBodyScrollLocks();
};
}, [children, open]);
return /* @__PURE__ */ jsx(OverlayContext.Provider, { value: { close: () => onClose?.() }, children: /* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsx(OverlayPortal, { ref: overlayRef, children: ({ overlayIndex, portalIndex }) => /* @__PURE__ */ jsxs(Fragment, { children: [
hasBackdrop && /* @__PURE__ */ jsx(
Backdrop,
{
zIndex: overlayIndex,
portalIndex,
onClick: onBackdropClick,
className: backdropClassName
}
),
/* @__PURE__ */ jsx("div", { "body-scroll-lock-ignore": "true", children: children({ overlayIndex, portalIndex }) })
] }) }) }) });
};
const OverlayTrigger = forwardRef(
({
children,
className,
elementType = "span",
trigger = ["click"],
onOpen = () => void 0,
onClose = () => void 0
}, ref) => {
const hasTrigger = useCallback(
(type) => {
if (Array.isArray(trigger)) {
return trigger.includes(type);
} else {
return type === trigger;
}
},
[trigger]
);
const onFocus = useCallback(
(event) => {
if (hasTrigger("focus")) {
onOpen({ type: "focus", nativeEvent: event });
}
},
[onOpen, hasTrigger]
);
const onBlur = useCallback(
(event) => {
if (hasTrigger("focus")) {
onClose({ type: "focus", nativeEvent: event });
}
},
[onClose, hasTrigger]
);
const onMouseEnter = useCallback(
(event) => {
if (hasTrigger("hover")) {
onOpen({ type: "hover", nativeEvent: event });
}
},
[onOpen, hasTrigger]
);
const onMouseLeave = useCallback(
(event) => {
if (hasTrigger("hover")) {
onClose({ type: "hover", nativeEvent: event });
}
},
[onClose, hasTrigger]
);
const onClick = useCallback(
(event) => {
if (hasTrigger("click")) {
onOpen({ type: "click", nativeEvent: event });
}
if (!hasTrigger("click")) {
onClose({ type: "hover", nativeEvent: event });
}
},
[onOpen, onClose, hasTrigger]
);
const onContextMenu = useCallback(
(event) => {
if (hasTrigger("contextmenu")) {
event.preventDefault();
onOpen({ type: "contextmenu", nativeEvent: event });
}
},
[hasTrigger, onOpen]
);
const tabIndex = hasTrigger("focus") ? -1 : void 0;
const Component = elementType;
return /* @__PURE__ */ jsx(
Component,
{
ref,
tabIndex,
onMouseEnter,
onMouseLeave,
onFocus,
onBlur,
onClick,
onContextMenu,
className,
children
}
);
}
);
const usePosition = ({
reference,
floating,
followCursor,
placement = "top",
modifiers = [flip(), shift({ limiter: limitShift() })]
} = {}) => {
const isVirtualElement = useMemo(
() => !reference?.nodeType,
[reference]
);
const { refs, floatingStyles, update } = useFloating({
open: true,
placement,
middleware: modifiers,
elements: {
reference: isVirtualElement ? null : reference,
floating
},
whileElementsMounted: autoUpdate
});
useEffect(() => {
if (isVirtualElement && reference && !followCursor) {
const refObject = reference;
refs.setPositionReference({
getBoundingClientRect() {
return {
width: refObject.width,
height: refObject.height,
x: refObject.left,
y: refObject.top,
left: refObject.left,
top: refObject.top,
right: refObject.left + refObject.width,
bottom: refObject.top + refObject.height
};
}
});
}
}, [reference, refs, isVirtualElement, followCursor]);
const onMouseMove = useCallback(
({ clientX, clientY }) => {
refs.setPositionReference({
getBoundingClientRect() {
return {
width: 0,
height: 0,
x: clientX,
y: clientY,
left: clientX,
top: clientY,
right: clientX,
bottom: clientY
};
}
});
},
[refs]
);
useLayoutEffect(() => {
if (followCursor) {
window.addEventListener("mousemove", onMouseMove);
}
return () => {
window.removeEventListener("mousemove", onMouseMove);
};
}, [followCursor, onMouseMove]);
return {
refs,
anchorRef: refs.reference,
floatingRef: refs.floating,
floatingStyles,
update
};
};
const ConnectedOverlayContent = forwardRef(
({
triggerRef,
children,
portalClassName,
closeOnBodyClick = true,
closeOnEscape = true,
elementType,
appendToBody = true,
followCursor,
modifiers,
placement = "bottom",
onClose
}, ref) => {
const id2 = useId();
const [overlayIndex, setOverlayIndex] = useState(null);
const { refs, floatingStyles, update } = usePosition({
reference: triggerRef.current ?? triggerRef,
followCursor,
modifiers,
placement
});
useImperativeHandle(ref, () => ({
updatePosition: () => {
update();
}
}));
const onClickOutside = useCallback(
(event) => {
if (closeOnBodyClick) {
let ref2 = null;
if (triggerRef.current) {
ref2 = triggerRef.current;
} else if (triggerRef.contains !== void 0) {
ref2 = triggerRef;
}
const container = event.target.closest(".rdk-portal");
const isLast = portals.indexOf(id2) === portals.length - 1;
if (!ref2?.contains(event.target) && (isLast || !container)) {
onClose?.(event);
}
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[closeOnBodyClick, onClose]
);
const onEscape = useCallback(() => {
if (closeOnEscape) {
onClose?.();
}
}, [closeOnEscape, onClose]);
useExitListener({
open: true,
ref: refs.floating,
onClickOutside,
onEscape
});
return /* @__PURE__ */ jsx(
OverlayPortal,
{
id: id2,
ref: refs.setFloating,
style: { ...floatingStyles, zIndex: overlayIndex },
className: portalClassName,
appendToBody,
onMount: (event) => setOverlayIndex(event.overlayIndex),
onUnmount: () => setOverlayIndex(null),
children
}
);
}
);
const ConnectedOverlay = forwardRef(
({
reference,
children,
open,
content,
triggerElement,
triggerClassName,
trigger = "click",
onOpen,
onClose,
...rest
}, ref) => {
const mounted = useRef(false);
const overlayTriggerRef = useRef(null);
const contentRef = useRef(null);
const triggerRef = reference || overlayTriggerRef;
useImperativeHandle(ref, () => ({
updatePosition: () => {
contentRef.current?.updatePosition();
}
}));
useEffect(() => {
if (mounted.current) {
if (!open) {
onClose?.();
} else {
onOpen?.();
}
}
}, [open]);
useEffect(() => {
if (!mounted.current) {
mounted.current = true;
}
});
const providerValue = useMemo(
() => ({
close: () => onClose?.()
}),
[onClose]
);
return /* @__PURE__ */ jsxs(OverlayContext.Provider, { value: providerValue, children: [
children && /* @__PURE__ */ jsx(Fragment, { children: trigger ? /* @__PURE__ */ jsx(
OverlayTrigger,
{
elementType: triggerElement,
ref: overlayTriggerRef,
className: triggerClassName,
trigger,
onOpen,
onClose,
children
}
) : children }),
/* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsx(
ConnectedOverlayContent,
{
...rest,
ref: contentRef,
triggerRef,
onClose,
children: content
}
) })
] });
}
);
const useOverlay = () => {
const context = useContext(OverlayContext);
if (context === void 0) {
throw new Error(
"`useOverlay` hook can only be used inside a overlay component."
);
}
return context;
};
const baseTheme$F = {
base: "inline-flex whitespace-no-wrap select-none items-center justify-center px-2.5 py-1 rounded-xs font-sans cursor-pointer",
disabled: "disabled:cursor-not-allowed",
fullWidth: "flex w-full",
group: "rounded-none first:rounded-s last:rounded-e border-s-0 first:border-s",
groupText: "border border-y-transparent border-l-transparent last:border-r-transparent hover:bg-initial",
adornment: {
base: "flex",
start: "pr-1",
end: "pl-1",
sizes: {
small: "[&>svg]:w-3 [&>svg]:h-3",
medium: "[&>svg]:w-4 [&>svg]:h-4",
large: "[&>svg]:w-5 [&>svg]:h-5"
}
},
sizes: {
small: "text-sm px-2 py-1 leading-[normal]",
medium: "text-base px-4 py-2 leading-[normal]",
large: "text-xl px-5 py-2.5 leading-[normal]"
},
iconSizes: {
small: "px-2 py-1",
medium: "px-4 py-2",
large: "px-5 py-2.5"
}
};
const buttonTheme = {
base: [baseTheme$F.base, "text-text-primary font-semibold"].join(" "),
disabled: [
baseTheme$F.disabled,
"data-[variant=filled]:disabled:bg-gray-600 disabled:text-gray-400 border-gray-500"
].join(" "),
fullWidth: baseTheme$F.fullWidth,
group: baseTheme$F.group,
groupText: baseTheme$F.groupText,
adornment: baseTheme$F.adornment,
sizes: baseTheme$F.sizes,
iconSizes: baseTheme$F.iconSizes,
variants: {
filled: "bg-secondary hover:bg-border-secondary-hover border-secondary light:text-gray-100",
outline: "border-grey border",
text: "border-0"
},
colors: {
default: {
filled: "bg-gray-800 hover:bg-gray-700 border-gray-800",
outline: "border-secondary border",
text: "text-text-primary"
},
primary: {
filled: "bg-primary hover:bg-primary-hover border-primary text-text-primary",
outline: "border border-primary",
text: "text-primary hover:text-primary-hover"
},
secondary: {
filled: "bg-secondary hover:bg-secondary-hover text-text-primary!",
outline: "border border-secondary",
text: "text-secondary hover:text-secondary-hover"
},
success: {
filled: "bg-success hover:bg-success-hover border-success text-text-primary",
outline: "border border-success",
text: "text-success hover:text-success-hover"
},
warning: {
filled: "bg-warning hover:bg-warning-hover border-warning text-text-primary",
outline: "border border-warning",
text: "text-warning hover:text-warning-hover"
},
error: {
filled: "bg-error hover:bg-error-hover border-error text-text-primary",
outline: "border border-error",
text: "text-error hover:text-error-hover"
}
}
};
const ButtonGroupContext = createContext({
variant: null,
size: null
});
const Button = forwardRef(
({
color = "default",
variant = "filled",
children,
fullWidth,
size: size2 = "medium",
disableAnimation,
className,
disableMargins,
disablePadding,
disabled,
start,
end,
theme: customTheme,
type = "button",
...rest
}, ref) => {
const theme2 = useComponentTheme("button", customTheme);
const { variant: groupVariant, size: groupSize } = useContext(ButtonGroupContext);
const isGroup = !!groupVariant && !!groupSize;
return /* @__PURE__ */ jsxs(
motion.button,
{
...rest,
type,
disabled,
ref,
whileTap: { scale: disabled || disableAnimation ? 1 : 0.9 },
"data-variant": groupVariant || variant,
className: cn(
theme2.base,
theme2.disabled,
fullWidth && theme2.fullWidth,
theme2.variants[groupVariant || variant],
theme2.colors[color][groupVariant || variant],
theme2.sizes[groupSize || size2],
isGroup && theme2.group,
isGroup && groupVariant === "text" && theme2.groupText,
disableMargins && "m-0",
disablePadding && "p-0",
className
),
children: [
start && /* @__PURE__ */ jsx(
"div",
{
className: cn(
theme2.adornment.base,
theme2.adornment.start,
theme2.adornment.sizes[size2]
),
children: start
}
),
children,
end && /* @__PURE__ */ jsx(
"div",
{
className: cn(
theme2.adornment.base,
theme2.adornment.end,
theme2.adornment.sizes[size2]
),
children: end
}
)
]
}
);
}
);
const ButtonGroup = ({
children,
className,
variant,
size: size2
}) => {
const values = useMemo(
() => ({
variant: variant || "filled",
size: size2 || "medium"
}),
[size2, variant]
);
return /* @__PURE__ */ jsx(ButtonGroupContext.Provider, { value: values, children: /* @__PURE__ */ jsx("div", { className, children }) });
};
const Chip = forwardRef(
({
children,
color = "default",
variant = "filled",
size: size2 = "medium",
selected,
disabled,
className,
disableMargins,
start,
end,
onClick,
theme: customTheme,
...rest
}, ref) => {
const theme2 = useComponentTheme("chip", customTheme);
return /* @__PURE__ */ jsxs(
"div",
{
...rest,
ref,
tabIndex: onClick ? 0 : -1,
role: onClick ? "button" : void 0,
onClick: !disabled ? onClick : void 0,
onKeyDown: onClick && !disabled ? (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onClick(e);
}
} : void 0,
className: cn(
theme2.base,
theme2.variants[variant],
theme2.colors[color]?.base,
theme2.colors[color]?.variants?.[variant],
theme2.sizes[size2],
theme2.focus,
!!onClick && !disabled && theme2.colors[color]?.selectable?.base,
!!onClick && !disabled && theme2.colors[color]?.selectable?.variants?.[variant]?.base,
selected && theme2.colors[color]?.selectable?.variants?.[variant]?.selected,
disableMargins && "m-0",
"transition-colors duration-300 ease [&>svg]:transition-[fill] [&>svg]:will-change-[fill]",
className,
disabled && theme2.disabled
),
"aria-disabled": disabled,
children: [
start && /* @__PURE__ */ jsx(
"div",
{
className: cn(
theme2.adornment.base,
theme2.adornment.start,
theme2.adornment.sizes[size2]
),
children: start
}
),
/* @__PURE__ */ jsx("div", { className: theme2.label, children }),
end && /* @__PURE__ */ jsx(
"div",
{
className: cn(
theme2.adornment.base,
theme2.adornment.end,
theme2.adornment.sizes[size2]
),
children: end
}
)
]
}
);
}
);
function getMonthNames(locale, format2 = "short") {
if (!locale && typeof window !== "undefined") {
locale = navigator.language;
}
const formatter = new Intl.DateTimeFormat(locale, {
month: format2,
timeZone: "UTC"
});
const months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((month) => {
const mm = month < 10 ? `0${month}` : month;
return /* @__PURE__ */ new Date(`2017-${mm}-01T00:00:00+00:00`);
});
return months.map((date) => formatter.format(date));
}
const monthNames = getMonthNames();
function getDayLabels(locale) {
return Array.from({ length: 7 }, (_, i) => {
if (!locale && typeof window !== "undefined") {
locale = navigator.language;
}
return new Intl.DateTimeFormat(locale, {
weekday: "short"
}).format(new Date(1970, 0, 4 + i));
});
}
const daysOfWeek = getDayLabels();
function getWeeks(date, options = { format: "MM/dd/yyyy" }) {
if (!date) {
throw new Error("A date is required");
} else if (!isValid(date)) {
console.warn("Invalid date - setting to today", date);
date = /* @__PURE__ */ new Date();
}
const daysInMonth = getDaysInMonth(date);
let day = startOfMonth(date);
let offset2 = getDay(day);
const numOfWeeks = Math.ceil((daysInMonth + offset2) / 7);
const weeks = Array.apply(null, {
length: numOfWeeks
}).map(() => []);
const current = /* @__PURE__ */ new Date();
const [firstWeek] = weeks;
for (let i = offset2; i > 0; i--) {
const offsetDay = subDays(day, i);
firstWeek.push({
date: offsetDay,
dayOfMonth: getDate(offsetDay),
isWeekendDay: getISODay(offsetDay) > 5,
isPreviousMonth: true,
isNextMonth: false,
isToday: false,
formattedDate: format(offsetDay, options.format)
});
}
for (let i = 0, week = weeks[i]; i < numOfWeeks; i++, week = weeks[i]) {
for (let dayOfWeek = offset2; dayOfWeek < 7; dayOfWeek++) {
week.push({
date: day,
dayOfMonth: getDate(day),
isPreviousMonth: false,
isToday: isSameDay(day, current),
isNextMonth: !isSameMonth(day, date),
isWeekendDay: getISODay(day) > 5,
formattedDate: format(day, options.format)
});
day = addDays(day, 1);
}
offset2 = 0;
}
return weeks;
}
function getDayAttributes(day, current, hover, isRange) {
let isActive = false;
let isRangeStart = false;
let isRangeEnd = false;
const isInRange = (date, range) => {
const startDate = min(range);
const endDate = max(range);
return isWithinInterval(date, { start: startDate, end: endDate });
};
const isSelectionStarted = Array.isArray(current) && isValid(current[0]);
const isSelectionComplete = isSelectionStarted && isValid(current[1]);
if (!isRange && isValid(current)) {
isActive = isSameDay(day, current);
} else if (!isSelectionStarted) {
isActive = isSameDay(day, hover);
isRangeStart = isActive;
isRangeEnd = isActive;
} else if (isSelectionComplete) {
const activeRange = [
startOfDay(current[0]),
endOfDay(current[1])
];
isActive = isInRange(day, activeRange);
isRangeStart = isSameDay(day, current[0]);
isRangeEnd = isSameDay(day, current[1]);
} else {
const activeRange = [
startOfDay(current[0]),
endOfDay(hover ?? current[0])
];
isActive = isInRange(day, activeRange);
isRangeStart = isSameDay(day, min(activeRange));
isRangeEnd = isSameDay(day, max(activeRange));
}
return { isActive, isRangeStart, isRangeEnd };
}
function isNextWeekEmpty(day, range, hideNextMonth) {
const nextWeek = addDays(day, 7);
const nextWeekInRange = isBefore(nextWeek, max(range)) || isSameDay(nextWeek, max(range));
return !(nextWeekInRange && (isSameMonth(day, nextWeek) || !hideNextMonth));
}
function isPreviousWeekEmpty(day, range, hidePrevMonth) {
const prevWeek = addDays(day, -7);
const prevWeekInRange = isAfter(prevWeek, min(range)) || isSameDay(prevWeek, min(range));
return !(prevWeekInRange && (isSameMonth(day, prevWeek) || !hidePrevMonth));
}
function updateDateTime(currentDate, newDate, isRange = false, rangeStart = false) {
let finalDate = newDate;
if (currentDate) {
const hasTime = getHours(newDate) !== 0 || getMinutes(newDate) !== 0 || getSeconds(newDate) !== 0;
if (!hasTime) {
if (!isRange) {
const originalTimeSource = Array.isArray(currentDate) ? currentDate[0] ?? /* @__PURE__ */ new Date() : currentDate ?? /* @__PURE__ */ new Date();
finalDate = setSeconds(
setMinutes(
setHours(newDate, getHours(originalTimeSource)),
getMinutes(originalTimeSource)
),
getSeconds(originalTimeSource)
);
} else {
if (!rangeStart) {
const originalTimeSource = Array.isArray(currentDate) ? currentDate[0] ?? /* @__PURE__ */ new Date() : currentDate ?? /* @__PURE__ */ new Date();
finalDate = setSeconds(
setMinutes(
setHours(newDate, getHours(originalTimeSource)),
getMinutes(originalTimeSource)
),
getSeconds(originalTimeSource)
);
} else {
finalDate = setSeconds(setMinutes(setHours(newDate, 0), 0), 0);
}
}
}
}
return finalDate;
}
const isPresetActive = (presetValue, value, includeTime = false) => {
if (!value) return false;
if (Array.isArray(presetValue) && Array.isArray(value)) {
if (includeTime) {
const startMinutesDiff = Math.abs(
differenceInMinutes(presetValue[0], value[0])
);
const endMinutesDiff = Math.abs(
differenceInMinutes(presetValue[1], value[1])
);
return startMinutesDiff === 0 && endMinutesDiff === 0;
}
return isSameDay(presetValue[0], value[0]) && isSameDay(presetValue[1], value[1]);
}
if (!Array.isArray(presetValue) && !Array.isArray(value)) {
if (includeTime) {
return Math.abs(differenceInSeconds(presetValue, value)) === 0;
}
return isSameDay(presetValue, value);
}
return false;
};
const CalendarDays = ({
value,
current,
hover = null,
isRange,
disabled,
min: minLimit,
max: max2,
animated,
xAnimation = 0,
animation,
showDayOfWeek,
showToday,
showTime,
dayOfWeekLabels = daysOfWeek,
hidePrevMonthDays,
hideNextMonthDays,
onChange,
onHover,
theme: customTheme
}) => {
const { days } = useComponentTheme("calendar", customTheme);
const [hoveringDate, setHoveringDate] = useState(hover);
const weeks = useMemo(() => getWeeks(value), [value]);
const maxLimit = useMemo(() => max2 === "now" ? /* @__PURE__ */ new Date() : max2, [max2]);
const dayChangeHandler = useCallback(
(dayDate) => {
if (showTime && !isRange && current) {
const currentDate = current;
let newDate = new Date(dayDate);
newDate.setHours(currentDate.getHours());
newDate.setMinutes(currentDate.getMinutes());
newDate.setSeconds(currentDate.getSeconds());
if (isAfter(newDate, maxLimit)) {
newDate = maxLimit;
} else if (isBefore(newDate, minLimit)) {
newDate = minLimit;
}
onChange(newDate);
} else {
onChange(dayDate);
}
},
[showTime, isRange, current, maxLimit, minLimit, onChange]
);
const renderDay = useCallback(
(day) => {
if (day.isPreviousMonth && hidePrevMonthDays || day.isNextMonth && hideNextMonthDays) {
return /* @__PURE__ */ jsx("div", {}, day.dayOfMonth);
}
const handleHover = (value2) => {
if (onHover) {
onHover(value2);
} else {
setHoveringDate(value2);
}
};
const isDisabled = disabled || minLimit && isBefore(startOfDay(day.date), startOfDay(minLimit)) || maxLimit && isAfter(endOfDay(day.date), endOfDay(maxLimit));
const currentHover = hover || hoveringDate;
const { isActive, isRangeStart, isRangeEnd } = getDayAttributes(
day.date,
current,
currentHover,
isRange
);
const currentRange = Array.isArray(current) ? [current[0], current[1] ?? currentHover] : [current ?? hoveringDate, current ?? hoveringDate];
const isRangeMiddle = isRange && isActive && !isRangeStart && !isRangeEnd;
const rangeConnectsBottom = isRangeStart && isNextWeekEmpty(day.date, currentRange, hideNextMonthDays);
const rangeConnectsTop = isRangeEnd && isPreviousWeekEmpty(day.date, currentRange, hidePrevMonthDays);
const colorVariant = isActive ? "primary" : "default";
const buttonVariant = isActive ? "filled" : "text";
return /* @__PURE__ */ jsx(
Button,
{
className: cn(days.day, {
[days.outside]: !isActive && (day.isNextMonth || day.isPreviousMonth),
[days.today]: showToday && isToday(day.date),
[days.selected]: isActive,
[days.hover]: day.date === currentHover,
[days.range]: isRangeMiddle,
[days.startRangeDate]: isRange && isRangeStart && !isRangeEnd,
[days.cornerStartDateBottom]: isRange && isActive && !rangeConnectsBottom,
[days.endRangeDate]: isRange && isRangeEnd && !isRangeStart,
[days.cornerEndDateTop]: isRange && isActive && !rangeConnectsTop
}),
onMouseEnter: () => handleHover(day.date),
onMouseLeave: () => handleHover(null),
variant: buttonVariant,
color: colorVariant,
disableMargins: true,
disabled: isDisabled,
title: day.formattedDate,
onClick: () => dayChangeHandler(day.date),
children: day.dayOfMonth
},
day.formattedDate
);
},
[
disabled,
minLimit,
maxLimit,
current,
hover,
isRange,
dayChangeHandler,
onHover,
hoveringDate,
days,
hideNextMonthDays,
hidePrevMonthDays,
showToday
]
);
return /* @__PURE__ */ jsx(AnimatePresence, { mode: "popLayout", children: /* @__PURE__ */ jsxs(
motion.div,
{
...animation ? animation : {
initial: { opacity: 0, x: xAnimation },
animate: { opacity: 1, x: 0 },
transition: {
x: { type: animated ? "keyframes" : false },
opacity: { duration: 0.2, type: animated ? "tween" : false }
}
},
children: [
showDayOfWeek && /* @__PURE__ */ jsx("div", { className: days.header, children: dayOfWeekLabels.map((day) => /* @__PURE__ */ jsx("div", { className: days.dayOfWeek, children: day.substring(0, 2) }, `day-${day}`)) }),
weeks.map((week, i) => /* @__PURE__ */ jsx("div", { className: days.week, children: week.map(renderDay) }, `week-${i}`))
]
},
value.toString()
) });
};
const CalendarMonths = ({
value,
onChange,
theme: customTheme
}) => {
const { months } = useComponentTheme("calendar", customTheme);
return /* @__PURE__ */ jsx("div", { className: months.root, children: monthNames.map((month, i) => /* @__PURE__ */ jsx(
Button,
{
className: cn(months.month, { [months.selected]: value === i }),
color: value === i ? "primary" : "default",
variant: value === i ? "filled" : "text",
disableMargins: true,
title: month,
onClick: () => onChange(i),
children: month
},
month
)) });
};
const CalendarYears = ({
decadeStart,
decadeEnd,
value,
animated,
xAnimation = 0,
animation,
onChange,
theme: customTheme
}) => {
const { years } = useComponentTheme("calendar", customTheme);
const yearDates = useMemo(() => {
const arr = [];
const start = decadeStart.getFullYear();
const end = decadeEnd.getFullYear();
for (let i = start - 1; i < end + 2; i++) {
arr.push(i);
}
return arr;
}, [decadeEnd, decadeStart]);
return /* @__PURE__ */ jsx(AnimatePresence, { mode: "popLayout", children: /* @__PURE__ */ jsx(
motion.div,
{
className: years.root,
...animation ? animation : {
initial: { opacity: 0, x: xAnimation },
animate: { opacity: 1, x: 0 },
transition: {
x: { type: animated ? "keyframes" : false },
opacity: { duration: 0.2, type: animated ? "tween" : false }
}
},
children: yearDates.map((year) => /* @__PURE__ */ jsx(
Button,
{
className: cn(years.year, { [years.selected]: value === year }),
color: value === year ? "primary" : "default",
variant: value === year ? "filled" : "text",
disableMargins: true,
title: year,
onClick: () => onChange(year),
children: year
},
year
))
},
`${decadeStart.toString()}-${decadeEnd.toString()}`
) });
};
const Divider = ({
className,
disableMargins = false,
orientation = "horizontal",
variant = "primary",
theme: customTheme,
...rest
}) => {
const theme2 = useComponentTheme("divider", customTheme);
return /* @__PURE__ */ jsx(
"hr",
{
...rest,
className: cn(
theme2.base,
theme2.variant[variant],
theme2.orientation[orientation],
disableMargins && theme2.disableMargins,
className
)
}
);
};
const baseTheme$E = {
base: "border-none",
orientation: {
horizontal: "h-px w-full my-2.5",
vertical: "w-px h-full mx-2.5"
},
variant: {
primary: "bg-surface",
secondary: "bg-linear-to-r from-transparent to-transparent via-blue-500"
},
disableMargins: "my-0 mx-0"
};
const dividerTheme = {
...baseTheme$E
};
const typographyTheme = {
h1: "scroll-m-20 text-4xl font-extrabold tracking-tight text-balance",
h2: "scroll-m-20 border-b border-surface pb-2 text-3xl font-semibold tracking-tight first:mt-0",
h3: "scroll-m-20 text-2xl font-semibold tracking-tight",
h4: "scroll-m-20 text-xl font-semibold tracking-tight",
h5: "scroll-m-20 text-lg font-semibold tracking-tight",
h6: "scroll-m-20 text-base font-semibold tracking-tight",
p: "leading-7 [&:not(:first-child)]:mt-6",
blockquote: "mt-6 border-l-2 border-surface pl-6 italic",
lead: "text-xl text-text-secondary",
large: "text-lg font-semibold",
small: "text-sm leading-none font-medium",
muted: "text-sm text-text-secondary"
};
const H1 = forwardRef(
({ className, theme: customTheme, children, ...rest }, ref) => {
const theme2 = useComponentTheme("typography", customTheme);
return /* @__PURE__ */ jsx("h1", { ref, className: cn(theme2.h1, className), ...rest, children });
}
);
H1.displayName = "H1";
const H2 = forwardRef(
({ className, theme: customTheme, children, ...rest }, ref) => {
const theme2 = useComponentTheme("typography", customTheme);
return /* @__PURE__ */ jsx("h2", { ref, className: cn(theme2.h2, className), ...rest, children });
}
);
H2.displayName = "H2";
const H3 = forwardRef(
({ className, theme: customTheme, children, ...rest }, ref) => {
const theme2 = useComponentTheme("typography", customTheme);
return /* @__PURE__ */ jsx("h3", { ref, className: cn(theme2.h3, className), ...rest, children });
}
);
H3.displayName = "H3";
const H4 = forwardRef(
({ className, theme: customTheme, children, ...rest }, ref) => {
const theme2 = useComponentTheme("typography", customTheme);
return /* @__PURE__ */ jsx("h4", { ref, className: cn(theme2.h4, className), ...rest, children });
}
);
H4.displayName = "H4";
const H5 = forwardRef(
({ className, theme: customTheme, children, ...rest }, ref) => {
const theme2 = useComponentTheme("typography", customTheme);
return /* @__PURE__ */ jsx("h5", { ref, className: cn(theme2.h5, className), ...rest, children });
}
);
H5.displayName = "H5";
const H6 = forwardRef(
({ className, theme: customTheme, children, ...rest }, ref) => {
const theme2 = useComponentTheme("typography", customTheme);
return /* @__PURE__ */ jsx("h6", { ref, className: cn(theme2.h6, className), ...rest, children });
}
);
H6.displayName = "H6";
const P = forwardRef(
({ className, theme: customTheme, children, ...rest }, ref) => {
const theme2 = useComponentTheme("typography", customTheme);
return /* @__PURE__ */ jsx("p", { ref, className: cn(theme2.p, className), ...rest, children });
}
);
P.displayName = "P";
const BlockQuote = forwardRef(
({ className, theme: customTheme, children, ...rest }, ref) => {
const theme2 = useComponentTheme("typography", customTheme);
return /* @__PURE__ */ jsx(
"blockquote",
{
ref,
className: cn(theme2.blockquote, className),
...rest,
children
}
);
}
);
BlockQuote.displayName = "BlockQuote";
const Lead = forwardRef(
({ className, theme: customTheme, children, ...rest }, ref) => {
const theme2 = useComponentTheme("typography", customTheme);
return /* @__PURE__ */ jsx("p", { ref, className: cn(theme2.lead, className), ...rest, children });
}
);
Lead.displayName = "Lead";
const Large = forwardRef(
({ className, theme: customTheme, children, ...rest }, ref) => {
const theme2 = useComponentTheme("typography", customTheme);
return /* @__PURE__ */ jsx("div", { ref, className: cn(theme2.large, className), ...rest, children });
}
);
Large.displayName = "Large";
const Small = forwardRef(
({ className, theme: customTheme, children, ...rest }, ref) => {
const theme2 = useComponentTheme("typography", customTheme);
return /* @__PURE__ */ jsx("small", { ref, className: cn(theme2.small, className), ...rest, children });
}
);
Small.displayName = "Small";
const Muted = forwardRef(
({ className, theme: customTheme, children, ...rest }, ref) => {
const theme2 = useComponentTheme("typography", customTheme);
return /* @__PURE__ */ jsx("p", { ref, className: cn(theme2.muted, className), ...rest, children });
}
);
Muted.displayName = "Muted";
const TimeColumn = ({
options,
value,
min: min2,
max: max2,
theme: theme2,
isPM = false,
is12HourCycle = false,
onSelect
}) => {
const containerRef = useRef(null);
const selectedRef = useRef(null);
const isOptionDisabled = useCallback(
(option) => {
if (typeof option === "number") {
if (options.length === 12 && is12HourCycle) {
if (isPM) {
const pmOption = option < 12 ? option + 12 : option;
return pmOption < min2 || pmOption > max2;
} else {
return option < min2 || option > max2;
}
}
return option < min2 || option > max2;
} else {
if (option === "AM" && min2 >= 12 || option === "PM" && max2 < 12) {
return true;
}
return false;
}
},
[options.length, is12HourCycle, isPM, min2, max2]
);
useEffect(() => {
if (containerRef.current && selectedRef.current) {
const container = containerRef.current;
const selected = selectedRef.current;
const containerHeight = container.clientHeight;
const itemOffsetTop = selected.offsetTop;
const itemHeight = selected.offsetHeight;
let scrollTop = 0;
if (is12HourCycle) {
scrollTop = itemOffsetTop;
} else {
scrollTop = itemOffsetTop - containerHeight / 2 + itemHeight / 2;
}
container.scrollTo({
top: scrollTop,
behavior: "smooth"
});
}
}, [value, is12HourCycle]);
return /* @__PURE__ */ jsx("div", { className: theme2.items.container, children: /* @__PURE__ */ jsx(
"ul",
{
ref: containerRef,
className: theme2.items.list,
style: {
paddingBottom: is12HourCycle && containerRef.current ? containerRef.current?.clientHeight - 24 : void 0
},
children: options.map((option) => /* @__PURE__ */ jsx(
"li",
{
ref: value === option ? selectedRef : null,
className: cn(theme2.items.item.base, {
[theme2.items.item.selected]: value === option,
[theme2.items.item.disabled]: isOptionDisabled(option)
}),
onClick: () => {
if (isOptionDisabled(option)) {
return;
}
onSelect(option);
},
role: "option",
"aria-disabled": isOptionDisabled(option),
"aria-selected": value === option,
children: typeof option === "number" ? option.toString().padStart(2, "0") : option
},
option
))
}
) });
};
const VARIANTS$1 = {
open: { opacity: 1, height: "auto" },
collapsed: { opacity: 0, height: 0 }
};
const TRANSITION = {
duration: 0.5,
ease: [0.04, 0.62, 0.23, 0.98],
when: "beforeChildren"
};
const Collapse = ({
children,
expanded,
className,
animated = true,
animation,
theme: customTheme,
...rest
}) => {
const theme2 = useComponentTheme("collapse", customTheme);
return /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: expanded && /* @__PURE__ */ createElement(
motion.section,
{
...rest,
className: cn(theme2.base, className),
key: "content",
...animation ? animation : {
initial: "collapsed",
animate: "open",
exit: "collapsed",
variants: VARIANTS$1,
transition: animated ? TRANSITION : { duration: 0 }
}
},
typeof children === "function" ? children() : children
) });
};
const baseTheme$D = {
base: "will-change-[height,opacity] overflow-hidden"
};
const collapseTheme = {
...baseTheme$D
};
const groupVariants = {
initial: {
transition: {
staggerChildren: 0.05,
staggerDirection: -1
}
},
animate: {
transition: {
staggerChildren: 0.07,
delayChildren: 0.2
}
}
};
const MotionGroup = ({ children, ...rest }) => /* @__PURE__ */ jsx(
motion.div,
{
variants: groupVariants,
initial: "initial",
animate: "animate",
...rest,
children
}
);
const verticalVariant = {
initial: {
y: -20,
opacity: 0,
transition: {
when: "beforeChildren"
}
},
animate: {
y: 0,
opacity: 1,
transition: {
when: "beforeChildren"
}
},
exit: {
y: -20,
opacity: 0
}
};
const horizontalVariants = {
initial: {
x: "-100%",
opacity: 0,
transition: {
when: "beforeChildren",
x: { stiffness: 10 }
}
},
animate: {
x: "0%",
opacity: 1,
transition: {
x: { stiffness: 10, velocity: -100 },
when: "beforeChildren",
opacity: { duration: 1 }
}
},
exit: {
x: "-100%",
opacity: 0,
transition: {
x: { stiffness: 10 }
}
}
};
const MotionItem = ({
children,
direction = "vertical",
...rest
}) => /* @__PURE__ */ jsx(
motion.div,
{
variants: direction === "vertical" ? verticalVariant : horizontalVariants,
...rest,
children
}
);
const List = forwardRef(({ className, children, theme: customTheme, ...rest }, ref) => {
const theme2 = useComponentTheme("list", customTheme);
return /* @__PURE__ */ jsx("div", { ...rest, ref, role: "list", className: cn(theme2.base, className), children });
});
const ListItem = forwardRef(
({
className,
contentClassName,
children,
active,
disabled,
disablePadding,
disableGutters,
start,
end,
dense,
onClick,
theme: customTheme,
...rest
}, ref) => {
const theme2 = useComponentTheme("list", customTheme);
return /* @__PURE__ */ jsxs(
"div",
{
...rest,
ref,
role: onClick ? "button" : "listitem",
tabIndex: onClick ? 0 : void 0,
onClick: (e) => !disabled && onClick?.(e),
className: cn(
theme2.listItem.base,
dense && theme2.listItem.dense.base,
disabled && theme2.listItem.disabled,
active && theme2.listItem.active,
onClick && !disabled && theme2.listItem.clickable,
disablePadding && theme2.listItem.disablePadding,
disableGutters && theme2.listItem.disableGutters,
className
),
children: [
start && /* @__PURE__ */ jsx(
"div",
{
className: cn(
theme2.listItem.adornment.base,
theme2.listItem.adornment.start,
{ [theme2.listItem.dense.start]: dense }
),
children: start
}
),
/* @__PURE__ */ jsx(
"div",
{
className: cn(
theme2.listItem.content,
{
[theme2.listItem.dense.content]: dense
},
contentClassName
),
children
}
),
end && /* @__PURE__ */ jsx(
"div",
{
className: cn(
theme2.listItem.adornment.base,
theme2.listItem.adornment.end,
{ [theme2.listItem.dense.end]: dense }
),
children: end
}
)
]
}
);
}
);
const ListHeader = ({
className,
children,
theme: customTheme,
...rest
}) => {
const theme2 = useComponentTheme("list", customTheme);
return /* @__PURE__ */ jsx(Small, { ...rest, className: cn(theme2.header, className), children });
};
const baseTheme$C = {
base: "flex flex-col",
header: "pl-2 pr-2 text-sm font-semibold mb-0.5",
listItem: {
base: "items-center flex p-2.5 relative rounded-none",
disabled: "cursor-not-allowed pointer-events-none",
active: "",
clickable: "cursor-pointer transition-color duration-300 ease-linear transition-bg duration-300 ease-linear hover:color-inherit hover:bg-transparent",
disablePadding: "p-0",
disableGutters: "pl-0 pr-0",
dense: {
base: "p-1",
content: "",
start: "pr-[calc(5/2)]",
end: "pl-[calc(5/2)]"
},
adornment: {
base: "items-center flex",
start: "pr-1",
end: "pl-1",
svg: "fill-current"
},
content: "overflow-wrap break-word word-wrap break-all flex-1"
}
};
const listTheme = {
...baseTheme$C,
base: [baseTheme$C.base, "text-text-primary"].join(" "),
header: [baseTheme$C.header, "text-text-primary"].join(" "),
listItem: {
...baseTheme$C.listItem,
base: [
baseTheme$C.listItem.base,
"hover:bg-panel-accent hover:text-mystic light:hover:bg-vulcan/5 light:hover:text-text-secondary [&:has(h3)]:hover:bg-transparent"
].join(" "),
active: [
baseTheme$C.listItem.active,
"text-primary-active hover:text-mystic"
].join(" "),
disabled: [
baseTheme$C.listItem.disabled,
"opacity-40 text-text-secondary"
].join(" ")
}
};
const Field = ({
label,
children,
disableMargin,
labelClassName,
className,
required,
requiredIndicator = "*",
requiredAnnouncement = "required",
htmlFor,
direction = "vertical",
alignment = "sta