UNPKG

@helpwave/hightide

Version:

helpwave's component and theming library

1,520 lines (1,484 loc) 54.8 kB
// src/components/table/Table.tsx import { useCallback as useCallback3, useEffect as useEffect11, useMemo, useRef as useRef3, useState as useState10 } from "react"; // src/components/layout-and-navigation/Pagination.tsx import { ChevronFirst, ChevronLast, ChevronLeft, ChevronRight } from "lucide-react"; import clsx4 from "clsx"; // src/localization/LanguageProvider.tsx import { createContext, useContext, useEffect, useState as useState2 } from "react"; // src/hooks/useLocalStorage.ts import { useCallback, useState } from "react"; // src/localization/util.ts var languages = ["en", "de"]; var languagesLocalNames = { en: "English", de: "Deutsch" }; var DEFAULT_LANGUAGE = "en"; var LanguageUtil = { languages, DEFAULT_LANGUAGE, languagesLocalNames }; // src/localization/LanguageProvider.tsx import { jsx } from "react/jsx-runtime"; var LanguageContext = createContext({ language: LanguageUtil.DEFAULT_LANGUAGE, setLanguage: (v) => v }); var useLanguage = () => useContext(LanguageContext); // src/localization/useTranslation.ts var TranslationPluralCount = { zero: 0, one: 1, two: 2, few: 3, many: 11, other: -1 }; var useTranslation = (translations, overwriteTranslation = {}) => { const { language: languageProp, translation: overwrite } = overwriteTranslation; const { language: inferredLanguage } = useLanguage(); const usedLanguage = languageProp ?? inferredLanguage; const usedTranslations = [...translations]; if (overwrite) { usedTranslations.push(overwrite); } return (key, options) => { const { count, replacements } = { ...{ count: 0, replacements: {} }, ...options }; try { for (let i = translations.length - 1; i >= 0; i--) { const translation = translations[i]; const localizedTranslation = translation[usedLanguage]; if (!localizedTranslation) { continue; } const value = localizedTranslation[key]; if (!value) { continue; } let forProcessing; if (typeof value !== "string") { if (count === TranslationPluralCount.zero && value?.zero) { forProcessing = value.zero; } else if (count === TranslationPluralCount.one && value?.one) { forProcessing = value.one; } else if (count === TranslationPluralCount.two && value?.two) { forProcessing = value.two; } else if (TranslationPluralCount.few <= count && count < TranslationPluralCount.many && value?.few) { forProcessing = value.few; } else if (count > TranslationPluralCount.many && value?.many) { forProcessing = value.many; } else { forProcessing = value.other; } } else { forProcessing = value; } forProcessing = forProcessing.replace(/\{\{(\w+)}}/g, (_, placeholder) => { return replacements[placeholder] ?? `{{key:${placeholder}}}`; }); return forProcessing; } } catch (e) { console.error(e); } return `{{${usedLanguage}:${key}}}`; }; }; // src/localization/defaults/form.ts var formTranslation = { en: { add: "Add", all: "All", apply: "Apply", back: "Back", cancel: "Cancel", change: "Change", clear: "Clear", click: "Click", clickToCopy: "Click to Copy", close: "Close", confirm: "Confirm", copy: "Copy", copied: "Copied", create: "Create", decline: "Decline", delete: "Delete", discard: "Discard", discardChanges: "Discard Changes", done: "Done", edit: "Edit", enterText: "Enter text here", error: "Error", exit: "Exit", fieldRequiredError: "This field is required.", invalidEmailError: "Please enter a valid email address.", less: "Less", loading: "Loading", maxLengthError: "Maximum length exceeded.", minLengthError: "Minimum length not met.", more: "More", next: "Next", no: "No", none: "None", of: "of", optional: "Optional", pleaseWait: "Please wait...", previous: "Previous", remove: "Remove", required: "Required", reset: "Reset", save: "Save", saved: "Saved", search: "Search", select: "Select", selectOption: "Select an option", show: "Show", showMore: "Show more", showLess: "Show less", submit: "Submit", success: "Success", update: "Update", unsavedChanges: "Unsaved Changes", unsavedChangesSaveQuestion: "Do you want to save your changes?", yes: "Yes" }, de: { add: "Hinzuf\xFCgen", all: "Alle", apply: "Anwenden", back: "Zur\xFCck", cancel: "Abbrechen", change: "\xC4ndern", clear: "L\xF6schen", click: "Klicken", clickToCopy: "Zum kopieren klicken", close: "Schlie\xDFen", confirm: "Best\xE4tigen", copy: "Kopieren", copied: "Kopiert", create: "Erstellen", decline: "Ablehnen", delete: "L\xF6schen", discard: "Verwerfen", discardChanges: "\xC4nderungen Verwerfen", done: "Fertig", edit: "Bearbeiten", enterText: "Text hier eingeben", error: "Fehler", exit: "Beenden", fieldRequiredError: "Dieses Feld ist erforderlich.", invalidEmailError: "Bitte geben Sie eine g\xFCltige E-Mail-Adresse ein.", less: "Weniger", loading: "L\xE4dt", maxLengthError: "Maximale L\xE4nge \xFCberschritten.", minLengthError: "Mindestl\xE4nge nicht erreicht.", more: "Mehr", next: "Weiter", no: "Nein", none: "Nichts", of: "von", optional: "Optional", pleaseWait: "Bitte warten...", previous: "Vorherige", remove: "Entfernen", required: "Erforderlich", reset: "Zur\xFCcksetzen", save: "Speichern", saved: "Gespeichert", search: "Suche", select: "Select", selectOption: "Option ausw\xE4hlen", show: "Anzeigen", showMore: "Mehr anzeigen", showLess: "Weniger anzeigen", submit: "Abschicken", success: "Erfolg", update: "Update", unsavedChanges: "Ungespeicherte \xC4nderungen", unsavedChangesSaveQuestion: "M\xF6chtest du die \xC4nderungen speichern?", yes: "Ja" } }; // src/components/user-action/Input.tsx import { forwardRef, useEffect as useEffect4, useImperativeHandle, useRef, useState as useState4 } from "react"; import clsx2 from "clsx"; // src/hooks/useDelay.ts import { useEffect as useEffect2, useState as useState3 } from "react"; var defaultOptions = { delay: 3e3, disabled: false }; function useDelay(options) { const [timer, setTimer] = useState3(void 0); const { delay, disabled } = { ...defaultOptions, ...options }; const clearTimer = () => { clearTimeout(timer); setTimer(void 0); }; const restartTimer = (onDelayFinish) => { if (disabled) { return; } clearTimeout(timer); setTimer(setTimeout(() => { onDelayFinish(); setTimer(void 0); }, delay)); }; useEffect2(() => { return () => { clearTimeout(timer); }; }, [timer]); useEffect2(() => { if (disabled) { clearTimeout(timer); setTimer(void 0); } }, [disabled, timer]); return { restartTimer, clearTimer, hasActiveTimer: !!timer }; } // src/util/noop.ts var noop = () => void 0; // src/components/user-action/Label.tsx import clsx from "clsx"; import { jsx as jsx2 } from "react/jsx-runtime"; var styleMapping = { labelSmall: "textstyle-label-sm", labelMedium: "textstyle-label-md", labelBig: "textstyle-label-lg" }; var Label = ({ children, name, labelType = "labelSmall", className, ...props }) => { return /* @__PURE__ */ jsx2("label", { ...props, className: clsx(styleMapping[labelType], className), children: children ? children : name }); }; // src/hooks/useFocusManagement.ts import { useCallback as useCallback2 } from "react"; function useFocusManagement() { const getFocusableElements = useCallback2(() => { return Array.from( document.querySelectorAll( 'input, button, select, textarea, a[href], [tabindex]:not([tabindex="-1"])' ) ).filter( (el) => el instanceof HTMLElement && !el.hasAttribute("disabled") && !el.hasAttribute("hidden") && el.tabIndex !== -1 ); }, []); const getNextFocusElement = useCallback2(() => { const elements = getFocusableElements(); if (elements.length === 0) { return void 0; } let nextElement = elements[0]; if (document.activeElement instanceof HTMLElement) { const currentIndex = elements.indexOf(document.activeElement); nextElement = elements[(currentIndex + 1) % elements.length]; } return nextElement; }, [getFocusableElements]); const focusNext = useCallback2(() => { const nextElement = getNextFocusElement(); nextElement?.focus(); }, [getNextFocusElement]); const getPreviousFocusElement = useCallback2(() => { const elements = getFocusableElements(); if (elements.length === 0) { return void 0; } let previousElement = elements[0]; if (document.activeElement instanceof HTMLElement) { const currentIndex = elements.indexOf(document.activeElement); if (currentIndex === 0) { previousElement = elements[elements.length - 1]; } else { previousElement = elements[currentIndex - 1]; } } return previousElement; }, [getFocusableElements]); const focusPrevious = useCallback2(() => { const previousElement = getPreviousFocusElement(); if (previousElement) previousElement.focus(); }, [getPreviousFocusElement]); return { getFocusableElements, getNextFocusElement, getPreviousFocusElement, focusNext, focusPrevious }; } // src/hooks/useFocusOnceVisible.ts import React, { useEffect as useEffect3 } from "react"; var useFocusOnceVisible = (ref, disable = false) => { const [hasUsedFocus, setHasUsedFocus] = React.useState(false); useEffect3(() => { if (disable || hasUsedFocus) { return; } const observer = new IntersectionObserver(([entry]) => { if (entry.isIntersecting && !hasUsedFocus) { ref.current?.focus(); setHasUsedFocus(hasUsedFocus); } }, { threshold: 0.1 }); if (ref.current) { observer.observe(ref.current); } return () => observer.disconnect(); }, [disable, hasUsedFocus, ref]); }; // src/components/user-action/Input.tsx import { jsx as jsx3, jsxs } from "react/jsx-runtime"; var getInputClassName = ({ disabled = false, hasError = false }) => { return clsx2( "px-2 py-1.5 rounded-md border-2", { "bg-input-background text-input-text hover:border-primary focus:border-primary": !disabled && !hasError, "bg-on-negative text-negative border-negative-border hover:border-negative-border-hover": !disabled && hasError, "bg-disabled-background text-disabled-text border-disabled-border": disabled } ); }; var defaultEditCompleteOptions = { onBlur: true, afterDelay: true, delay: 2500 }; var Input = forwardRef(function Input2({ id, type = "text", value, label, onChange = noop, onChangeText = noop, onEditCompleted, className = "", allowEnterComplete = true, expanded = true, autoFocus = false, onBlur, editCompleteOptions, containerClassName, disabled, ...restProps }, forwardedRef) { const { onBlur: allowEditCompleteOnBlur, afterDelay, delay } = { ...defaultEditCompleteOptions, ...editCompleteOptions }; const { restartTimer, clearTimer } = useDelay({ delay, disabled: !afterDelay }); const innerRef = useRef(null); const { focusNext } = useFocusManagement(); useFocusOnceVisible(innerRef, !autoFocus); useImperativeHandle(forwardedRef, () => innerRef.current); const handleKeyDown = (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); innerRef.current?.blur(); focusNext(); } }; return /* @__PURE__ */ jsxs("div", { className: clsx2({ "w-full": expanded }, containerClassName), children: [ label && /* @__PURE__ */ jsx3(Label, { ...label, htmlFor: id, className: clsx2("mb-1", label.className) }), /* @__PURE__ */ jsx3( "input", { ...restProps, ref: innerRef, value, id, type, disabled, className: clsx2(getInputClassName({ disabled }), className), onKeyDown: allowEnterComplete ? handleKeyDown : void 0, onBlur: (event) => { onBlur?.(event); if (onEditCompleted && allowEditCompleteOnBlur) { onEditCompleted(event.target.value); clearTimer(); } }, onChange: (e) => { const value2 = e.target.value; if (onEditCompleted) { restartTimer(() => { if (innerRef.current) { innerRef.current.blur(); if (!allowEditCompleteOnBlur) { onEditCompleted(value2); } } else { onEditCompleted(value2); } }); } onChange(e); onChangeText(value2); } } ) ] }); }); var FormInput = forwardRef(function FormInput2({ id, labelText, errorText, className, labelClassName, errorClassName, containerClassName, required, disabled, ...restProps }, ref) { const input = /* @__PURE__ */ jsx3( "input", { ...restProps, ref, id, disabled, className: clsx2( getInputClassName({ disabled, hasError: !!errorText }), className ) } ); return /* @__PURE__ */ jsxs("div", { className: clsx2("flex flex-col gap-y-1", containerClassName), children: [ labelText && /* @__PURE__ */ jsxs("label", { htmlFor: id, className: clsx2("textstyle-label-md", labelClassName), children: [ labelText, required && /* @__PURE__ */ jsx3("span", { className: "text-primary font-bold", children: "*" }) ] }), input, errorText && /* @__PURE__ */ jsx3("label", { htmlFor: id, className: clsx2("text-negative", errorClassName), children: errorText }) ] }); }); // src/util/math.ts var clamp = (value, min = 0, max = 1) => { return Math.min(Math.max(value, min), max); }; // src/components/layout-and-navigation/Pagination.tsx import { useEffect as useEffect5, useState as useState5 } from "react"; // src/components/user-action/Button.tsx import { forwardRef as forwardRef2 } from "react"; import clsx3 from "clsx"; import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime"; var ButtonColorUtil = { solid: ["primary", "secondary", "tertiary", "positive", "warning", "negative", "neutral"], text: ["primary", "negative", "neutral"], outline: ["primary"] }; var IconButtonUtil = { icon: [...ButtonColorUtil.solid, "transparent"] }; var paddingMapping = { small: "btn-sm", medium: "btn-md", large: "btn-lg" }; var iconPaddingMapping = { tiny: "icon-btn-xs", small: "icon-btn-sm", medium: "icon-btn-md", large: "icon-btn-lg" }; var ButtonUtil = { paddingMapping, iconPaddingMapping }; var SolidButton = forwardRef2(function SolidButton2({ children, color = "primary", size = "medium", startIcon, endIcon, onClick, className, ...restProps }, ref) { const colorClasses = { primary: "not-disabled:bg-button-solid-primary-background not-disabled:text-button-solid-primary-text", secondary: "not-disabled:bg-button-solid-secondary-background not-disabled:text-button-solid-secondary-text", tertiary: "not-disabled:bg-button-solid-tertiary-background not-disabled:text-button-solid-tertiary-text", positive: "not-disabled:bg-button-solid-positive-background not-disabled:text-button-solid-positive-text", warning: "not-disabled:bg-button-solid-warning-background not-disabled:text-button-solid-warning-text", negative: "not-disabled:bg-button-solid-negative-background not-disabled:text-button-solid-negative-text", neutral: "not-disabled:bg-button-solid-neutral-background not-disabled:text-button-solid-neutral-text" }[color]; const iconColorClasses = { primary: "not-group-disabled:text-button-solid-primary-icon", secondary: "not-group-disabled:text-button-solid-secondary-icon", tertiary: "not-group-disabled:text-button-solid-tertiary-icon", positive: "not-group-disabled:text-button-solid-positive-icon", warning: "not-group-disabled:text-button-solid-warning-icon", negative: "not-group-disabled:text-button-solid-negative-icon", neutral: "not-group-disabled:text-button-solid-neutral-icon" }[color]; return /* @__PURE__ */ jsxs2( "button", { ref, onClick, className: clsx3( "group font-semibold", colorClasses, "not-disabled:hover:brightness-90", "disabled:text-disabled-text disabled:bg-disabled-background", ButtonUtil.paddingMapping[size], className ), ...restProps, children: [ startIcon && /* @__PURE__ */ jsx4( "span", { className: clsx3( iconColorClasses, "group-disabled:text-disabled-icon" ), children: startIcon } ), children, endIcon && /* @__PURE__ */ jsx4( "span", { className: clsx3( iconColorClasses, "group-disabled:text-disabled-icon" ), children: endIcon } ) ] } ); }); var IconButton = ({ children, color = "primary", size = "medium", className, ...restProps }) => { const colorClasses = { primary: "not-disabled:bg-button-solid-primary-background not-disabled:text-button-solid-primary-text", secondary: "not-disabled:bg-button-solid-secondary-background not-disabled:text-button-solid-secondary-text", tertiary: "not-disabled:bg-button-solid-tertiary-background not-disabled:text-button-solid-tertiary-text", positive: "not-disabled:bg-button-solid-positive-background not-disabled:text-button-solid-positive-text", warning: "not-disabled:bg-button-solid-warning-background not-disabled:text-button-solid-warning-text", negative: "not-disabled:bg-button-solid-negative-background not-disabled:text-button-solid-negative-text", neutral: "not-disabled:bg-button-solid-neutral-background not-disabled:text-button-solid-neutral-text", transparent: "not-disabled:bg-transparent" }[color]; return /* @__PURE__ */ jsx4( "button", { className: clsx3( colorClasses, "not-disabled:hover:brightness-90", "disabled:text-disabled-text", { "disabled:bg-disabled-background": color !== "transparent", "disabled:opacity-70": color === "transparent", "not-disabled:hover:bg-button-text-hover-background": color === "transparent" }, ButtonUtil.iconPaddingMapping[size], className ), ...restProps, children } ); }; // src/components/layout-and-navigation/Pagination.tsx import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime"; var Pagination = ({ overwriteTranslation, pageIndex, pageCount, onPageChanged, className, style }) => { const translation = useTranslation([formTranslation], overwriteTranslation); const [value, setValue] = useState5((pageIndex + 1).toString()); const noPages = pageCount === 0; const onFirstPage = pageIndex === 0 && !noPages; const onLastPage = pageIndex === pageCount - 1; useEffect5(() => { if (noPages) { setValue("0"); } else { setValue((pageIndex + 1).toString()); } }, [pageIndex, noPages]); const changePage = (page) => { onPageChanged(page); }; return /* @__PURE__ */ jsxs3("div", { className: clsx4("flex-row-1", className), style, children: [ /* @__PURE__ */ jsx5(IconButton, { color: "transparent", onClick: () => changePage(0), disabled: onFirstPage || noPages, children: /* @__PURE__ */ jsx5(ChevronFirst, {}) }), /* @__PURE__ */ jsx5(IconButton, { color: "transparent", onClick: () => changePage(pageIndex - 1), disabled: onFirstPage || noPages, children: /* @__PURE__ */ jsx5(ChevronLeft, {}) }), /* @__PURE__ */ jsxs3("div", { className: "flex-row-2 min-w-56 items-center justify-center mx-2 text-center", children: [ /* @__PURE__ */ jsx5( Input, { value, containerClassName: "flex flex-1 h-10", className: clsx4( "w-full text-center font-bold input-indicator-hidden" ), type: "number", min: 1, max: pageCount, disabled: noPages, onChangeText: (value2) => { if (value2) { setValue(clamp(Number(value2), 1, pageCount).toString()); } else { setValue(value2); } }, onEditCompleted: (value2) => { changePage(clamp(Number(value2) - 1, 0, pageCount - 1)); }, editCompleteOptions: { delay: 800 } } ), /* @__PURE__ */ jsx5("span", { className: "select-none w-10", children: translation("of") }), /* @__PURE__ */ jsx5( "span", { className: "flex-row-2 flex-1 items-center justify-center select-none h-10 bg-input-background text-input-text rounded-md font-bold", children: pageCount } ) ] }), /* @__PURE__ */ jsx5(IconButton, { color: "transparent", onClick: () => changePage(pageIndex + 1), disabled: onLastPage || noPages, children: /* @__PURE__ */ jsx5(ChevronRight, {}) }), /* @__PURE__ */ jsx5(IconButton, { color: "transparent", onClick: () => changePage(pageCount - 1), disabled: onLastPage || noPages, children: /* @__PURE__ */ jsx5(ChevronLast, {}) }) ] }); }; // src/components/table/Table.tsx import clsx10 from "clsx"; import { flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table"; // src/util/array.ts var defaultRangeOptions = { allowEmptyRange: false, stepSize: 1, exclusiveStart: false, exclusiveEnd: true }; var range = (endOrRange, options) => { const { allowEmptyRange, stepSize, exclusiveStart, exclusiveEnd } = { ...defaultRangeOptions, ...options }; let start = 0; let end; if (typeof endOrRange === "number") { end = endOrRange; } else { start = endOrRange[0]; end = endOrRange[1]; } if (!exclusiveEnd) { end -= 1; } if (exclusiveStart) { start += 1; } if (end - 1 < start) { if (!allowEmptyRange) { console.warn(`range: end (${end}) < start (${start}) should be allowed explicitly, set options.allowEmptyRange to true`); } return []; } return Array.from({ length: end - start }, (_, index) => index * stepSize + start); }; // src/components/table/Table.tsx import { Scrollbars } from "react-custom-scrollbars-2"; // src/components/user-action/Checkbox.tsx import { useState as useState6 } from "react"; import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; import { Check, Minus } from "lucide-react"; import clsx5 from "clsx"; import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime"; var checkboxSizeMapping = { small: "size-5", medium: "size-6", large: "size-8" }; var checkboxIconSizeMapping = { small: "size-4", medium: "size-5", large: "size-7" }; var Checkbox = ({ id, label, checked, disabled, onChange, onChangeTristate, size = "medium", className = "", containerClassName }) => { const usedSizeClass = checkboxSizeMapping[size]; const innerIconSize = checkboxIconSizeMapping[size]; const propagateChange = (checked2) => { if (onChangeTristate) { onChangeTristate(checked2); } if (onChange) { onChange(checked2 === "indeterminate" ? false : checked2); } }; const changeValue = () => { if (disabled) { return; } const newValue = checked === "indeterminate" ? false : !checked; propagateChange(newValue); }; return /* @__PURE__ */ jsxs4( "div", { className: clsx5("group flex-row-2 items-center", { "cursor-pointer": !disabled, "cursor-not-allowed": disabled }, containerClassName), onClick: changeValue, children: [ /* @__PURE__ */ jsx6( CheckboxPrimitive.Root, { onCheckedChange: propagateChange, checked, disabled, id, className: clsx5(usedSizeClass, `items-center border-2 rounded outline-none `, { "text-disabled-text border-disabled-outline bg-disabled-background cursor-not-allowed": disabled, "focus:border-primary group-hover:border-primary ": !disabled, "bg-input-background": !disabled && !checked, "bg-primary/30 border-primary text-primary": !disabled && checked === true || checked === "indeterminate" }, className), children: /* @__PURE__ */ jsxs4(CheckboxPrimitive.Indicator, { children: [ checked === true && /* @__PURE__ */ jsx6(Check, { className: innerIconSize }), checked === "indeterminate" && /* @__PURE__ */ jsx6(Minus, { className: innerIconSize }) ] }) } ), label && /* @__PURE__ */ jsx6( Label, { ...label, className: clsx5( label.className, { "cursor-pointer": !disabled, "cursor-not-allowed": disabled } ), htmlFor: id } ) ] } ); }; // src/components/table/TableFilterButton.tsx import { FilterIcon } from "lucide-react"; // src/components/user-action/Menu.tsx import { useEffect as useEffect8, useRef as useRef2, useState as useState8 } from "react"; import clsx6 from "clsx"; // src/hooks/useOutsideClick.ts import { useEffect as useEffect6 } from "react"; var useOutsideClick = (refs, handler) => { useEffect6(() => { const listener = (event) => { if (event.target === null) return; if (refs.some((ref) => !ref.current || ref.current.contains(event.target))) { return; } handler(); }; document.addEventListener("mousedown", listener); document.addEventListener("touchstart", listener); return () => { document.removeEventListener("mousedown", listener); document.removeEventListener("touchstart", listener); }; }, [refs, handler]); }; // src/hooks/useHoverState.ts import { useEffect as useEffect7, useState as useState7 } from "react"; var defaultUseHoverStateProps = { closingDelay: 200, isDisabled: false }; var useHoverState = (props = void 0) => { const { closingDelay, isDisabled } = { ...defaultUseHoverStateProps, ...props }; const [isHovered, setIsHovered] = useState7(false); const [timer, setTimer] = useState7(); const onMouseEnter = () => { if (isDisabled) { return; } clearTimeout(timer); setIsHovered(true); }; const onMouseLeave = () => { if (isDisabled) { return; } setTimer(setTimeout(() => { setIsHovered(false); }, closingDelay)); }; useEffect7(() => { if (timer) { return () => { clearTimeout(timer); }; } }); useEffect7(() => { if (timer) { clearTimeout(timer); } }, [isDisabled]); return { isHovered, setIsHovered, handlers: { onMouseEnter, onMouseLeave } }; }; // src/util/PropsWithFunctionChildren.ts var resolve = (children, bag) => { if (typeof children === "function") { return children(bag); } return children ?? void 0; }; var BagFunctionUtil = { resolve }; // src/hooks/usePopoverPosition.ts var defaultPopoverPositionOptions = { edgePadding: 16, outerGap: 4, horizontalAlignment: "leftInside", verticalAlignment: "bottomOutside", disabled: false }; var usePopoverPosition = (trigger, options) => { const { edgePadding, outerGap, verticalAlignment, horizontalAlignment, disabled } = { ...defaultPopoverPositionOptions, ...options }; if (disabled || !trigger) { return {}; } const left = { leftOutside: trigger.left - outerGap, leftInside: trigger.left, rightOutside: trigger.right + outerGap, rightInside: trigger.right, center: trigger.left + trigger.width / 2 }[horizontalAlignment]; const top = { topOutside: trigger.top - outerGap, topInside: trigger.top, bottomOutside: trigger.bottom + outerGap, bottomInside: trigger.bottom, center: trigger.top + trigger.height / 2 }[verticalAlignment]; const translateX = { leftOutside: "-100%", leftInside: void 0, rightOutside: void 0, rightInside: "-100%", center: "-50%" }[horizontalAlignment]; const translateY = { topOutside: "-100%", topInside: void 0, bottomOutside: void 0, bottomInside: "-100%", center: "-50%" }[verticalAlignment]; return { left: Math.max(left, edgePadding), top: Math.max(top, edgePadding), translate: [translateX ?? "0", translateY ?? "0"].join(" ") }; }; // src/components/user-action/Menu.tsx import { createPortal } from "react-dom"; import { Fragment, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime"; function getScrollableParents(element) { const scrollables = []; let parent = element.parentElement; while (parent) { scrollables.push(parent); parent = parent.parentElement; } return scrollables; } var Menu = ({ trigger, children, alignmentHorizontal = "leftInside", alignmentVertical = "bottomOutside", showOnHover = false, disabled = false, menuClassName = "" }) => { const { isHovered: isOpen, setIsHovered: setIsOpen } = useHoverState({ isDisabled: !showOnHover || disabled }); const triggerRef = useRef2(null); const menuRef = useRef2(null); useOutsideClick([triggerRef, menuRef], () => setIsOpen(false)); const [isHidden, setIsHidden] = useState8(true); const bag = { isOpen, close: () => setIsOpen(false), toggleOpen: () => setIsOpen((prevState) => !prevState), disabled }; const menuPosition = usePopoverPosition( triggerRef.current?.getBoundingClientRect(), { verticalAlignment: alignmentVertical, horizontalAlignment: alignmentHorizontal, disabled } ); useEffect8(() => { if (!isOpen) return; const triggerEl = triggerRef.current; if (!triggerEl) return; const scrollableParents = getScrollableParents(triggerEl); const close = () => setIsOpen(false); scrollableParents.forEach((parent) => { parent.addEventListener("scroll", close); }); window.addEventListener("resize", close); return () => { scrollableParents.forEach((parent) => { parent.removeEventListener("scroll", close); }); window.removeEventListener("resize", close); }; }, [isOpen, setIsOpen]); useEffect8(() => { if (isOpen) { setIsHidden(false); } }, [isOpen]); return /* @__PURE__ */ jsxs5(Fragment, { children: [ trigger(bag, triggerRef), createPortal(/* @__PURE__ */ jsx7( "div", { ref: menuRef, onClick: (e) => e.stopPropagation(), className: clsx6( "absolute rounded-md bg-menu-background text-menu-text shadow-around-lg shadow-strong z-[300]", { "animate-pop-in": isOpen, "animate-pop-out": !isOpen, "hidden": isHidden }, menuClassName ), onAnimationEnd: () => { if (!isOpen) { setIsHidden(true); } }, style: { ...menuPosition }, children: BagFunctionUtil.resolve(children, bag) } ), document.body) ] }); }; // src/components/table/TableFilterButton.tsx import { useEffect as useEffect9, useState as useState9 } from "react"; import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime"; var defaultTableFilterTranslation = { en: { filter: "Filter", min: "Min", max: "Max", startDate: "Start", endDate: "End", text: "Text..." }, de: { filter: "Filter", min: "Min", max: "Max", startDate: "Start", endDate: "Ende", text: "Text..." } }; var TableFilterButton = ({ filterType, column }) => { const translation = useTranslation([formTranslation, defaultTableFilterTranslation]); const columnFilterValue = column.getFilterValue(); const [filterValue, setFilterValue] = useState9(columnFilterValue); const hasFilter = !!filterValue; useEffect9(() => { setFilterValue(columnFilterValue); }, [columnFilterValue]); return /* @__PURE__ */ jsx8( Menu, { trigger: ({ toggleOpen }, ref) => /* @__PURE__ */ jsxs6("div", { ref, className: "relative", children: [ /* @__PURE__ */ jsx8(IconButton, { color: "neutral", size: "tiny", onClick: toggleOpen, children: /* @__PURE__ */ jsx8(FilterIcon, {}) }), hasFilter && /* @__PURE__ */ jsx8( "div", { className: "absolute top-0.5 right-0.5 w-2 h-2 rounded-full bg-primary pointer-events-none", "aria-hidden": true } ) ] }), children: ({ close }) => /* @__PURE__ */ jsxs6("div", { className: "flex-col-1 p-2 items-start font-normal text-menu-text", children: [ /* @__PURE__ */ jsx8("h4", { className: "textstyle-title-sm", children: translation("filter") }), filterType === "text" && /* @__PURE__ */ jsx8( Input, { value: filterValue ?? "", autoFocus: true, placeholder: translation("text"), onChangeText: setFilterValue, className: "h-10" } ), filterType === "range" && /* @__PURE__ */ jsxs6("div", { className: "flex-row-2 items-center", children: [ /* @__PURE__ */ jsx8( Input, { value: filterValue?.[0] ?? "", type: "number", placeholder: translation("min"), onChangeText: (text) => { const num = Number(text); setFilterValue((old) => [num, old?.[1]]); }, className: "h-10 input-indicator-hidden w-40" } ), /* @__PURE__ */ jsx8("span", { className: "font-bold", children: "-" }), /* @__PURE__ */ jsx8( Input, { value: filterValue?.[1] ?? "", type: "number", placeholder: translation("max"), onChangeText: (text) => { const num = Number(text); setFilterValue((old) => [old?.[0], num]); }, className: "h-10 input-indicator-hidden w-40" } ) ] }), filterType === "dateRange" && /* @__PURE__ */ jsxs6(Fragment2, { children: [ /* @__PURE__ */ jsx8( Input, { value: filterValue?.[0] ? filterValue?.[0].toISOString().slice(0, 16) : "", type: "datetime-local", placeholder: translation("startDate"), onChangeText: (text) => { const value = new Date(text); setFilterValue((old) => [value, old?.[1]]); }, className: "h-10 w-50" } ), /* @__PURE__ */ jsx8( Input, { value: filterValue?.[1] ? filterValue?.[1].toISOString().slice(0, 16) : "", type: "datetime-local", placeholder: translation("endDate"), onChangeText: (text) => { const value = new Date(text); setFilterValue((old) => [old?.[0], value]); }, className: "h-10 w-50" } ) ] }), /* @__PURE__ */ jsxs6("div", { className: "flex-row-2 justify-end w-full", children: [ hasFilter && /* @__PURE__ */ jsx8(SolidButton, { color: "negative", size: "small", onClick: () => { column.setFilterValue(void 0); close(); }, children: translation("remove") }), /* @__PURE__ */ jsx8(SolidButton, { size: "small", onClick: () => { column.setFilterValue(filterValue); close(); }, children: translation("apply") }) ] }) ] }) } ); }; // src/components/table/TableSortButton.tsx import { ChevronDown, ChevronsUpDown, ChevronUp } from "lucide-react"; import clsx7 from "clsx"; import { jsx as jsx9 } from "react/jsx-runtime"; var TableSortButton = ({ sortDirection, invert = false, color = "neutral", className, ...buttonProps }) => { let icon = /* @__PURE__ */ jsx9(ChevronsUpDown, { className: "w-full h-full" }); if (sortDirection) { let usedSortDirection = sortDirection; if (invert) { usedSortDirection = usedSortDirection === "desc" ? "asc" : "desc"; } icon = usedSortDirection === "asc" ? /* @__PURE__ */ jsx9(ChevronUp, { className: "w-full h-full" }) : /* @__PURE__ */ jsx9(ChevronDown, { className: "w-full h-full" }); } return /* @__PURE__ */ jsx9( IconButton, { size: "tiny", color, className: clsx7(className), ...buttonProps, children: icon } ); }; // src/components/table/FillerRowElement.tsx import { clsx as clsx8 } from "clsx"; import { jsx as jsx10 } from "react/jsx-runtime"; var FillerRowElement = ({ className }) => { return /* @__PURE__ */ jsx10("div", { className: clsx8("flex flex-row items-center w-1/2 h-4 text-disabled-text font-bold", className), children: "-" }); }; // src/components/table/Filter.ts var dateRange = (row, columnId, filterValue) => { const [min, max] = filterValue; const value = row.getValue(columnId); const date = value instanceof Date ? value : new Date(value); if (isNaN(date.getTime())) return false; if (min && date < min) return false; if (max && date > max) return false; return true; }; var TableFilters = { dateRange }; // src/hooks/useResizeCallbackWrapper.ts import { useEffect as useEffect10 } from "react"; var useResizeCallbackWrapper = (callback) => { useEffect10(() => { window.addEventListener("resize", callback); return () => { window.removeEventListener("resize", callback); }; }, [callback]); }; // src/components/table/TableCell.tsx import { clsx as clsx9 } from "clsx"; import { jsx as jsx11 } from "react/jsx-runtime"; var TableCell = ({ children, className }) => { return /* @__PURE__ */ jsx11("span", { className: clsx9("block max-w-full overflow-ellipsis truncate", className), children }); }; // src/components/table/Table.tsx import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime"; var Table = ({ data, fillerRow, initialState, onRowClick = noop, className, tableClassName, defaultColumn, state, columns, ...tableOptions }) => { const ref = useRef3(null); const tableRef = useRef3(null); const [columnSizing, setColumnSizing] = useState10(columns.reduce((previousValue, currentValue) => { return { ...previousValue, [currentValue.id]: currentValue.minSize ?? defaultColumn.minSize }; }, {})); const [columnSizingInfo, setColumnSizingInfo] = useState10(); const [pagination, setPagination] = useState10({ pageSize: 10, pageIndex: 0, ...initialState?.pagination }); const [columnFilters, setColumnFilters] = useState10(initialState?.columnFilters); const computedColumnMinWidths = useMemo(() => { return columns.reduce((previousValue, column) => { return { ...previousValue, // every column is at least 12px wide [column.id]: column.minSize ?? defaultColumn?.minSize ?? 12 }; }, {}); }, [columns, defaultColumn]); const computedColumnMaxWidths = useMemo(() => { return columns.reduce((previousValue, column) => { return { ...previousValue, [column.id]: column.maxSize ?? defaultColumn?.maxSize }; }, {}); }, [columns, defaultColumn]); const tableMinWidth = useMemo(() => { return columns.reduce((sum, column) => { return sum + computedColumnMinWidths[column.id]; }, 0); }, [columns, computedColumnMinWidths]); const updateColumnSizes = useMemo(() => { return (previous) => { const updateSizing = { ...columnSizing, ...previous }; const containerWidth = ref.current.offsetWidth; columns.forEach((column) => { updateSizing[column.id] = clamp(updateSizing[column.id], computedColumnMinWidths[column.id], computedColumnMaxWidths[column.id] ?? containerWidth); }); const width = columns.reduce((previousValue, currentValue) => previousValue + updateSizing[currentValue.id], 0); if (width > containerWidth) { if (tableMinWidth >= containerWidth) { return columns.reduce((previousValue, currentValue) => ({ ...previousValue, [currentValue.id]: computedColumnMinWidths[currentValue.id] }), {}); } let reduceableColumns = columns.map((value) => value.id).filter((id) => updateSizing[id] - computedColumnMinWidths[id] > 0); let spaceToReduce = width - containerWidth; while (spaceToReduce > 0 && reduceableColumns.length > 0) { let maxReduceAmount = reduceableColumns.reduce((previousValue, id) => Math.max(previousValue, updateSizing[id] - computedColumnMinWidths[id]), 0); if (maxReduceAmount * reduceableColumns.length > spaceToReduce) { maxReduceAmount = spaceToReduce / reduceableColumns.length; } reduceableColumns.forEach((id) => { updateSizing[id] -= maxReduceAmount; }); spaceToReduce -= maxReduceAmount * reduceableColumns.length; reduceableColumns = reduceableColumns.filter((id) => updateSizing[id] - computedColumnMinWidths[id] > 0); } } else if (width <= containerWidth) { let distributableWidth = containerWidth - width; const violatingColumns = columns.filter((value) => computedColumnMaxWidths[value.id] && updateSizing[value.id] > computedColumnMaxWidths[value.id]); const violationColumnsAmount = violatingColumns.reduce( (previousValue, column) => previousValue + updateSizing[column.id] - computedColumnMaxWidths[column.id], 0 ); distributableWidth += violationColumnsAmount; let enlargeableColumns = columns.filter((col) => !computedColumnMaxWidths[col.id] || updateSizing[col.id] < computedColumnMaxWidths[col.id]).map((value) => value.id); while (distributableWidth > 0 && enlargeableColumns.length > 0) { let minEnlargeableAmount = enlargeableColumns.reduce((previousValue, id) => Math.min(previousValue, computedColumnMaxWidths[id] ? computedColumnMaxWidths[id] - updateSizing[id] : distributableWidth), distributableWidth); if (minEnlargeableAmount * enlargeableColumns.length > distributableWidth) { minEnlargeableAmount = distributableWidth / enlargeableColumns.length; } enlargeableColumns.forEach((id) => { updateSizing[id] += minEnlargeableAmount; }); distributableWidth -= minEnlargeableAmount * enlargeableColumns.length; enlargeableColumns = enlargeableColumns.filter((id) => !computedColumnMaxWidths[id] || updateSizing[id] < computedColumnMaxWidths[id]); } if (distributableWidth > 0) { updateSizing[columns[columns.length - 1].id] += distributableWidth; } } return updateSizing; }; }, [columns, computedColumnMaxWidths, computedColumnMinWidths, tableMinWidth]); const table = useReactTable({ data, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getSortedRowModel: getSortedRowModel(), getPaginationRowModel: getPaginationRowModel(), initialState, defaultColumn: { minSize: 60, maxSize: 700, cell: ({ cell }) => { return /* @__PURE__ */ jsx12(TableCell, { children: cell.getValue() }); }, ...defaultColumn }, columns, state: { columnSizing, columnSizingInfo, pagination, columnFilters, ...state }, filterFns: { ...tableOptions?.filterFns, dateRange: TableFilters.dateRange }, onColumnSizingInfoChange: (updaterOrValue) => { setColumnSizingInfo(updaterOrValue); if (tableOptions.onColumnSizingInfoChange) { tableOptions?.onColumnSizingInfoChange(updaterOrValue); } }, onColumnSizingChange: (updaterOrValue) => { setColumnSizing((previous) => { const newSizing = typeof updaterOrValue === "function" ? updaterOrValue(previous) : updaterOrValue; return updateColumnSizes(newSizing); }); if (tableOptions.onColumnSizingChange) { tableOptions.onColumnSizingChange(updaterOrValue); } }, onPaginationChange: (updaterOrValue) => { setPagination(updaterOrValue); if (tableOptions.onPaginationChange) { tableOptions.onPaginationChange(updaterOrValue); } }, onColumnFiltersChange: (updaterOrValue) => { setColumnFilters(updaterOrValue); table.toggleAllRowsSelected(false); if (tableOptions.onColumnFiltersChange) { tableOptions.onColumnFiltersChange(updaterOrValue); } }, autoResetPageIndex: false, columnResizeMode: "onChange", ...tableOptions }); const [hasInitializedSizing, setHasInitializedSizing] = useState10(false); useEffect11(() => { if (!hasInitializedSizing && ref.current) { setHasInitializedSizing(true); table.setColumnSizing(updateColumnSizes(columnSizing)); } }, [columnSizing, hasInitializedSizing]); useResizeCallbackWrapper(useCallback3(() => { table.setColumnSizing(updateColumnSizes); }, [updateColumnSizes])); const pageCount = table.getPageCount(); useEffect11(() => { const totalPages = pageCount; if (totalPages === 0) { if (pagination.pageIndex !== 0) { table.setPagination((prevState) => ({ ...prevState, pageIndex: 0 })); } } else if (pagination.pageIndex >= totalPages) { table.setPagination((prev) => ({ ...prev, pageIndex: totalPages - 1 })); } }, [data, pageCount, pagination.pageSize, pagination.pageIndex]); const columnSizeVars = useMemo(() => { const headers = table.getFlatHeaders(); const colSizes = {}; for (let i = 0; i < headers.length; i++) { const header = headers[i]; colSizes[`--header-${header.id}-size`] = Math.floor(header.getSize()); colSizes[`--col-${header.column.id}-size`] = Math.floor(header.column.getSize()); } return colSizes; }, [table.getState().columnSizingInfo, table.getState().columnSizing]); return /* @__PURE__ */ jsxs7("div", { ref, className: clsx10("flex-col-4", className), children: [ /* @__PURE__ */ jsx12( Scrollbars, { autoHeight: true, autoHeightMax: tableRef.current?.offsetHeight ? tableRef.current?.offsetHeight + 2 : void 0, autoHide: true, children: /* @__PURE__ */ jsxs7( "table", { ref: tableRef, className: clsx10(tableClassName), style: { ...columnSizeVars, width: Math.floor(Math.max(table.getTotalSize() - columns.length, ref.current?.offsetWidth ?? table.getTotalSize())) }, children: [ table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx12("colgroup", { children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx12( "col", { style: { width: `calc(var(--header-${header?.id}-size) * 1px)`, minWidth: header.column.columnDef.minSize, maxWidth: header.column.columnDef.maxSize } }, header.id )) }, headerGroup.id)), /* @__PURE__ */ jsx12("thead", { children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx12("tr", { className: table.options.meta?.headerRowClassName, children: headerGroup.headers.map((header) => { return /* @__PURE__ */ jsxs7( "th", { colSpan: header.colSpan, className: clsx10("relative group", header.column.columnDef.meta?.className), children: [ /* @__PURE__ */ jsx12("div", { className: "flex-row-2 w-full", children: header.isPlaceholder ? null : /* @__PURE__ */ jsxs7("div", { className: "flex-row-1 items-center", children: [ header.column.getCanSort() && /* @__PURE__ */ jsx12( TableSortButton, { sortDirection: header.column.getIsSorted(), onClick: () => header.column.toggleSorting() } ), header.column.getCanFilter() && header.column.columnDef.meta?.filterType ? /* @__PURE__ */ jsx12( TableFilterButton, { column: header.column, filterType: header.column.columnDef.meta.filterType } ) : null, flexRender( header.column.columnDef.header, header.getContext() ) ] }) }), header.column.getCanResize() && /* @__PURE__ */ jsx12( "div", { onMouseDown: header.getR