UNPKG

@helpwave/hightide

Version:

helpwave's component and theming library

702 lines (690 loc) 23 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/components/layout-and-navigation/Overlay.tsx var Overlay_exports = {}; __export(Overlay_exports, { Dialog: () => Dialog, Modal: () => Modal, Overlay: () => Overlay, OverlayHeader: () => OverlayHeader }); module.exports = __toCommonJS(Overlay_exports); var import_react5 = require("react"); var import_react_dom = __toESM(require("react-dom")); var import_clsx3 = __toESM(require("clsx")); // src/hooks/useHoverState.ts var import_react = require("react"); var defaultUseHoverStateProps = { closingDelay: 200, isDisabled: false }; var useHoverState = (props = void 0) => { const { closingDelay, isDisabled } = { ...defaultUseHoverStateProps, ...props }; const [isHovered, setIsHovered] = (0, import_react.useState)(false); const [timer, setTimer] = (0, import_react.useState)(); const onMouseEnter = () => { if (isDisabled) { return; } clearTimeout(timer); setIsHovered(true); }; const onMouseLeave = () => { if (isDisabled) { return; } setTimer(setTimeout(() => { setIsHovered(false); }, closingDelay)); }; (0, import_react.useEffect)(() => { if (timer) { return () => { clearTimeout(timer); }; } }); (0, import_react.useEffect)(() => { if (timer) { clearTimeout(timer); } }, [isDisabled]); return { isHovered, setIsHovered, handlers: { onMouseEnter, onMouseLeave } }; }; // src/components/user-action/Tooltip.tsx var import_clsx = require("clsx"); var import_jsx_runtime = require("react/jsx-runtime"); var Tooltip = ({ tooltip, children, animationDelay = 650, tooltipClassName = "", containerClassName = "", position = "bottom", zIndex = 10 }) => { const { isHovered, handlers } = useHoverState(); const positionClasses = { top: `bottom-full left-1/2 -translate-x-1/2 mb-[6px]`, bottom: `top-full left-1/2 -translate-x-1/2 mt-[6px]`, left: `right-full top-1/2 -translate-y-1/2 mr-[6px]`, right: `left-full top-1/2 -translate-y-1/2 ml-[6px]` }; const triangleSize = 6; const triangleClasses = { top: `top-full left-1/2 -translate-x-1/2 border-t-tooltip-background border-l-transparent border-r-transparent`, bottom: `bottom-full left-1/2 -translate-x-1/2 border-b-tooltip-background border-l-transparent border-r-transparent`, left: `left-full top-1/2 -translate-y-1/2 border-l-tooltip-background border-t-transparent border-b-transparent`, right: `right-full top-1/2 -translate-y-1/2 border-r-tooltip-background border-t-transparent border-b-transparent` }; const triangleStyle = { top: { borderWidth: `${triangleSize}px ${triangleSize}px 0 ${triangleSize}px` }, bottom: { borderWidth: `0 ${triangleSize}px ${triangleSize}px ${triangleSize}px` }, left: { borderWidth: `${triangleSize}px 0 ${triangleSize}px ${triangleSize}px` }, right: { borderWidth: `${triangleSize}px ${triangleSize}px ${triangleSize}px 0` } }; return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)( "div", { className: (0, import_clsx.clsx)("relative inline-block", containerClassName), ...handlers, children: [ children, isHovered && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)( "div", { className: (0, import_clsx.clsx)( `opacity-0 absolute text-xs font-semibold text-tooltip-text px-2 py-1 rounded whitespace-nowrap animate-tooltip-fade-in shadow-around-md bg-tooltip-background`, positionClasses[position], tooltipClassName ), style: { zIndex, animationDelay: animationDelay + "ms" }, children: [ tooltip, /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "div", { className: (0, import_clsx.clsx)(`absolute w-0 h-0`, triangleClasses[position]), style: { ...triangleStyle[position], zIndex: zIndex + 1 } } ) ] } ) ] } ); }; // src/components/layout-and-navigation/Overlay.tsx var import_lucide_react = require("lucide-react"); // src/components/user-action/Button.tsx var import_react2 = require("react"); var import_clsx2 = __toESM(require("clsx")); var import_jsx_runtime2 = require("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 = (0, import_react2.forwardRef)(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__ */ (0, import_jsx_runtime2.jsxs)( "button", { ref, onClick, className: (0, import_clsx2.default)( "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__ */ (0, import_jsx_runtime2.jsx)( "span", { className: (0, import_clsx2.default)( iconColorClasses, "group-disabled:text-disabled-icon" ), children: startIcon } ), children, endIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "span", { className: (0, import_clsx2.default)( 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__ */ (0, import_jsx_runtime2.jsx)( "button", { className: (0, import_clsx2.default)( 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/localization/LanguageProvider.tsx var import_react4 = require("react"); // src/hooks/useLocalStorage.ts var import_react3 = require("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 var import_jsx_runtime3 = require("react/jsx-runtime"); var LanguageContext = (0, import_react4.createContext)({ language: LanguageUtil.DEFAULT_LANGUAGE, setLanguage: (v) => v }); var useLanguage = () => (0, import_react4.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/layout-and-navigation/Overlay.tsx var import_jsx_runtime4 = require("react/jsx-runtime"); var Overlay = ({ children, isOpen, onBackgroundClick, backgroundClassName }) => { const [root, setRoot] = (0, import_react5.useState)(); (0, import_react5.useEffect)(() => { setRoot(document.body); }, []); if (!root || !isOpen) return null; return import_react_dom.default.createPortal( /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: (0, import_clsx3.default)("fixed inset-0 z-[200]"), children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "div", { className: (0, import_clsx3.default)("fixed inset-0 h-screen w-screen bg-overlay-shadow", backgroundClassName), onClick: onBackgroundClick } ), children ] }), root ); }; var overlayStack = []; var defaultModalHeaderTranslation = { en: { ...formTranslation.en }, de: { ...formTranslation.de } }; var OverlayHeader = ({ overwriteTranslation, onClose, title, titleText = "", description, descriptionText = "" }) => { const translation = useTranslation([defaultModalHeaderTranslation], overwriteTranslation); const hasTitleRow = !!title || !!titleText || !!onClose; const titleRow = /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex-row-8 justify-between items-start", children: [ title ?? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "h2", { className: (0, import_clsx3.default)("textstyle-title-lg", { "mb-1": description || descriptionText }), children: titleText } ), !!onClose && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tooltip, { tooltip: translation("close"), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconButton, { color: "neutral", size: "small", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react.X, { className: "w-full h-full" }) }) }) ] }); return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex-col-2", children: [ hasTitleRow && titleRow, description ?? (descriptionText && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "textstyle-description", children: descriptionText })) ] }); }; var Modal = ({ children, isOpen, onClose, className, backgroundClassName, headerProps }) => { const ref = (0, import_react5.useRef)(null); (0, import_react5.useEffect)(() => { if (!isOpen) return; const modal = ref.current; if (!modal) { console.error("modal open, but no ref found"); return; } overlayStack.push(modal); const focusable = modal?.querySelectorAll( 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])' ); const first = focusable[0]; const last = focusable[focusable.length - 1]; const handleKeyDown = (e) => { const isTopmost = overlayStack[overlayStack.length - 1] === modal; if (!isTopmost) return; if (e.key === "Escape") { e.stopPropagation(); onClose(); } else if (e.key === "Tab") { if (focusable.length === 0) return; if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } } }; modal.focus(); document.addEventListener("keydown", handleKeyDown); return () => { document.removeEventListener("keydown", handleKeyDown); overlayStack = overlayStack.filter((m) => m !== modal); }; }, [isOpen, onClose]); return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( Overlay, { isOpen, onBackgroundClick: onClose, backgroundClassName, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)( "div", { ref, tabIndex: -1, className: (0, import_clsx3.default)( "flex-col-2 fixed left-1/2 top-1/2 -translate-y-1/2 -translate-x-1/2 p-4 bg-overlay-background text-overlay-text rounded-xl shadow-around-lg shadow-strong animate-pop-in", className ), role: "dialog", "aria-modal": true, children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(OverlayHeader, { ...headerProps, onClose }), children ] } ) } ); }; var Dialog = ({ children, isOpen, className, backgroundClassName, headerProps }) => { const ref = (0, import_react5.useRef)(null); (0, import_react5.useEffect)(() => { if (!isOpen) return; const dialog = ref.current; if (!dialog) { console.error("dialog open, but no ref found"); return; } overlayStack.push(dialog); const focusable = dialog?.querySelectorAll( 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])' ); const first = focusable[0]; const last = focusable[focusable.length - 1]; const handleKeyDown = (e) => { const isTopmost = overlayStack[overlayStack.length - 1] === dialog; if (!isTopmost) return; if (e.key === "Escape") { e.stopPropagation(); } else if (e.key === "Tab") { if (focusable.length === 0) return; if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } } }; dialog.focus(); document.addEventListener("keydown", handleKeyDown); return () => { document.removeEventListener("keydown", handleKeyDown); overlayStack = overlayStack.filter((m) => m !== dialog); }; }, [isOpen]); return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( Overlay, { isOpen, backgroundClassName, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)( "div", { ref, tabIndex: -1, className: (0, import_clsx3.default)( "flex-col-2 fixed left-1/2 top-1/2 -translate-y-1/2 -translate-x-1/2 p-4 bg-overlay-background text-overlay-text rounded-xl shadow-around-lg shadow-strong animate-pop-in", className ), role: "dialog", "aria-modal": true, children: [ !!headerProps && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(OverlayHeader, { ...headerProps }), children ] } ) } ); }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Dialog, Modal, Overlay, OverlayHeader }); //# sourceMappingURL=Overlay.js.map