@sikka/hawa
Version:
Modern UI Kit made with Tailwind
536 lines (516 loc) • 15 kB
JavaScript
"use client";
// hooks/useIsomorphicEffect.ts
import { useEffect, useLayoutEffect } from "react";
var useIsomorphicEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
// hooks/useDiscloser.ts
import { useState } from "react";
// hooks/useHover.ts
import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
// hooks/useToast.ts
import * as React3 from "react";
var TOAST_LIMIT = 5;
var TOAST_REMOVE_DELAY = 1e5;
var count = 0;
function genId() {
count = (count + 1) % Number.MAX_VALUE;
return count.toString();
}
var toastTimeouts = /* @__PURE__ */ new Map();
var addToRemoveQueue = (toastId) => {
if (toastTimeouts.has(toastId)) {
return;
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId);
dispatch({ type: "REMOVE_TOAST", toastId });
}, TOAST_REMOVE_DELAY);
toastTimeouts.set(toastId, timeout);
};
var reducer = (state, action) => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT)
};
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map(
(t) => t.id === action.toast.id ? { ...t, ...action.toast } : t
)
};
case "DISMISS_TOAST": {
const { toastId } = action;
if (toastId) {
addToRemoveQueue(toastId);
} else {
state.toasts.forEach((toast2) => {
addToRemoveQueue(toast2.id);
});
}
return {
...state,
toasts: state.toasts.map(
(t) => t.id === toastId || toastId === void 0 ? { ...t, open: false } : t
)
};
}
case "REMOVE_TOAST":
if (action.toastId === void 0) {
return { ...state, toasts: [] };
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId)
};
}
};
var listeners = [];
var memoryState = { toasts: [] };
function dispatch(action) {
memoryState = reducer(memoryState, action);
listeners.forEach((listener) => {
listener(memoryState);
});
}
function toast({ ...props }) {
const id = genId();
const update = (props2) => dispatch({ type: "UPDATE_TOAST", toast: { ...props2, id } });
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss();
}
}
});
return { id, dismiss, update };
}
function useToast() {
const [state, setState] = React3.useState(memoryState);
React3.useEffect(() => {
listeners.push(setState);
return () => {
const index = listeners.indexOf(setState);
if (index > -1) {
listeners.splice(index, 1);
}
};
}, [state]);
return {
...state,
toast,
dismiss: (toastId) => dispatch({ type: "DISMISS_TOAST", toastId })
};
}
// hooks/useCarousel.ts
import { useState as useState4, useRef as useRef2 } from "react";
// hooks/useDialogCarousel.ts
import { useEffect as useEffect4, useState as useState5 } from "react";
import AutoHeight from "embla-carousel-auto-height";
import useEmblaCarousel from "embla-carousel-react";
var useDialogCarousel = (options) => {
const [emblaRef, emblaApi] = useEmblaCarousel(
{ loop: false, watchDrag: false, startIndex: 0, ...options },
[AutoHeight({ active: true })]
);
const [canScrollPrev, setCanScrollPrev] = useState5(false);
const checkCanScrollPrev = () => {
if (emblaApi) {
setCanScrollPrev(emblaApi.canScrollPrev());
}
};
const nextStep = () => {
if (emblaApi) {
console.log("going to NEXT \u{1F449}");
emblaApi.scrollNext();
}
};
const prevStep = () => {
if (emblaApi) {
console.log("going to BACK \u{1F448}");
emblaApi.scrollPrev();
}
};
useEffect4(() => {
checkCanScrollPrev();
emblaApi && emblaApi.on("select", checkCanScrollPrev);
return () => {
emblaApi && emblaApi.off("select", checkCanScrollPrev);
};
}, [emblaApi]);
return {
emblaRef,
emblaApi,
nextStep,
prevStep,
canScrollPrev
};
};
// hooks/useDialogSteps.ts
import { useState as useState6, useEffect as useEffect5, useRef as useRef3 } from "react";
var useMultiStepDialog = (initialStep, stepIds, setOpenDialog) => {
const [currentStep, setCurrentStep] = useState6(initialStep);
const [dialogHeight, setDialogHeight] = useState6(null);
const visibleStepRef = useRef3(null);
useEffect5(() => {
if (visibleStepRef.current) {
setDialogHeight(visibleStepRef.current.offsetHeight);
}
}, [currentStep, setOpenDialog]);
const handleNext = () => {
const currentIndex = stepIds.indexOf(currentStep);
if (currentIndex < stepIds.length - 1) {
setTimeout(() => {
setCurrentStep(stepIds[currentIndex + 1]);
}, 100);
}
};
const handleBack = () => {
const currentIndex = stepIds.indexOf(currentStep);
if (currentIndex > 0) {
setTimeout(() => {
setCurrentStep(stepIds[currentIndex - 1]);
}, 100);
}
};
return {
currentStep,
dialogHeight,
visibleStepRef,
handleNext,
handleBack
};
};
// hooks/useClipboard.ts
import { useState as useState7 } from "react";
function useClipboard({ timeout = 2e3 } = {}) {
const [error, setError] = useState7(null);
const [copied, setCopied] = useState7(false);
const [copyTimeout, setCopyTimeout] = useState7(null);
const handleCopyResult = (value) => {
clearTimeout(copyTimeout);
setCopyTimeout(setTimeout(() => setCopied(false), timeout));
setCopied(value);
};
const copy = (valueToCopy) => {
if ("clipboard" in navigator) {
navigator.clipboard.writeText(valueToCopy).then(() => handleCopyResult(true)).catch((err) => setError(err));
} else {
setError(new Error("useClipboard: navigator.clipboard is not supported"));
}
};
const reset = () => {
setCopied(false);
setError(null);
clearTimeout(copyTimeout);
};
return { copy, reset, error, copied };
}
// hooks/useWindowSize.ts
import { useEffect as useEffect6, useState as useState8 } from "react";
var useWindowSize = () => {
const [windowSize, setWindowSize] = useState8({
width: void 0,
height: void 0
});
useEffect6(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
}
window.addEventListener("resize", handleResize);
handleResize();
return () => window.removeEventListener("resize", handleResize);
}, []);
return windowSize;
};
// hooks/useFocusWithin.ts
import { useRef as useRef4, useState as useState9, useEffect as useEffect7 } from "react";
function containsRelatedTarget(event) {
if (event.currentTarget instanceof HTMLElement && event.relatedTarget instanceof HTMLElement) {
return event.currentTarget.contains(event.relatedTarget);
}
return false;
}
function useFocusWithin({
onBlur,
onFocus
} = {}) {
const ref = useRef4(null);
const [focused, _setFocused] = useState9(false);
const focusedRef = useRef4(false);
const setFocused = (value) => {
_setFocused(value);
focusedRef.current = value;
};
const handleFocusIn = (event) => {
if (!focusedRef.current) {
setFocused(true);
onFocus == null ? void 0 : onFocus(event);
}
};
const handleFocusOut = (event) => {
if (focusedRef.current && !containsRelatedTarget(event)) {
setFocused(false);
onBlur == null ? void 0 : onBlur(event);
}
};
useEffect7(() => {
if (ref.current) {
ref.current.addEventListener("focusin", handleFocusIn);
ref.current.addEventListener("focusout", handleFocusOut);
return () => {
var _a, _b;
(_a = ref.current) == null ? void 0 : _a.removeEventListener("focusin", handleFocusIn);
(_b = ref.current) == null ? void 0 : _b.removeEventListener("focusout", handleFocusOut);
};
}
return void 0;
}, [handleFocusIn, handleFocusOut]);
return { ref, focused };
}
// hooks/useMediaQuery.ts
import { useState as useState10, useEffect as useEffect8, useRef as useRef5 } from "react";
function attachMediaListener(query, callback) {
try {
query.addEventListener("change", callback);
return () => query.removeEventListener("change", callback);
} catch (e) {
query.addListener(callback);
return () => query.removeListener(callback);
}
}
function getInitialValue(query, initialValue) {
if (typeof initialValue === "boolean") {
return initialValue;
}
if (typeof window !== "undefined" && "matchMedia" in window) {
return window.matchMedia(query).matches;
}
return false;
}
function useMediaQuery(query, initialValue, { getInitialValueInEffect } = {
getInitialValueInEffect: true
}) {
const [matches, setMatches] = useState10(
getInitialValueInEffect ? initialValue : getInitialValue(query, initialValue)
);
const queryRef = useRef5();
useEffect8(() => {
if ("matchMedia" in window) {
queryRef.current = window.matchMedia(query);
setMatches(queryRef.current.matches);
return attachMediaListener(
queryRef.current,
(event) => setMatches(event.matches)
);
}
return void 0;
}, [query]);
return matches;
}
// hooks/useScrollPosition.ts
import { useState as useState11, useEffect as useEffect9 } from "react";
// hooks/useTable.ts
import { useState as useState12, useEffect as useEffect10 } from "react";
// hooks/useTabs.ts
import { useEffect as useEffect11, useState as useState13 } from "react";
function useTabs(initialTab = "") {
const [activeTab, setActiveTab] = useState13(initialTab);
useEffect11(() => {
const handleHashChange = () => {
const hash = window.location.hash.substring(1);
setActiveTab(hash || initialTab);
};
window.addEventListener("hashchange", handleHashChange);
handleHashChange();
return () => {
window.removeEventListener("hashchange", handleHashChange);
};
}, [initialTab]);
const handleTabChange = (index) => {
setActiveTab(index);
window.location.hash = index;
};
return {
activeTab,
handleTabChange
};
}
// hooks/useMeasureDirty.ts
import { useEffect as useEffect12, useRef as useRef7, useState as useState14 } from "react";
var useMeasureDirty = (ref) => {
const frame = useRef7(0);
const [rect, set] = useState14({
width: 0,
height: 0,
top: 0,
left: 0,
bottom: 0,
right: 0
});
const [observer] = useState14(
() => new ResizeObserver((entries) => {
const entry = entries[0];
if (entry) {
cancelAnimationFrame(frame.current);
frame.current = requestAnimationFrame(() => {
if (ref.current) {
set(entry.contentRect);
}
});
}
})
);
useEffect12(() => {
observer.disconnect();
if (ref.current) {
observer.observe(ref.current);
}
}, [ref]);
return rect;
};
// hooks/useShortcuts.ts
import { useEffect as useEffect13 } from "react";
function parseHotkey(hotkey) {
const keys = hotkey.toLowerCase().split("+").map((part) => part.trim());
const modifiers = {
alt: keys.includes("alt"),
ctrl: keys.includes("ctrl"),
meta: keys.includes("meta"),
mod: keys.includes("mod"),
shift: keys.includes("shift")
};
const reservedKeys = ["alt", "ctrl", "meta", "shift", "mod"];
const freeKey = keys.find((key) => !reservedKeys.includes(key));
return {
...modifiers,
key: freeKey
};
}
function isExactHotkey(hotkey, event) {
const { alt, ctrl, meta, mod, shift, key } = hotkey;
const { altKey, ctrlKey, metaKey, shiftKey, key: pressedKey } = event;
if (alt !== altKey) {
return false;
}
if (mod) {
if (!ctrlKey && !metaKey) {
return false;
}
} else {
if (ctrl !== ctrlKey) {
return false;
}
if (meta !== metaKey) {
return false;
}
}
if (shift !== shiftKey) {
return false;
}
if (key && (pressedKey.toLowerCase() === key.toLowerCase() || event.code.replace("Key", "").toLowerCase() === key.toLowerCase())) {
return true;
}
return false;
}
function getHotkeyMatcher(hotkey) {
return (event) => isExactHotkey(parseHotkey(hotkey), event);
}
function getHotkeyHandler(hotkeys) {
return (event) => {
const _event = "nativeEvent" in event ? event.nativeEvent : event;
hotkeys.forEach(([hotkey, handler, options = { preventDefault: true }]) => {
if (getHotkeyMatcher(hotkey)(_event)) {
if (options.preventDefault) {
event.preventDefault();
}
handler(_event);
}
});
};
}
function shouldFireEvent(event, tagsToIgnore, triggerOnContentEditable = false) {
if (event.target instanceof HTMLElement) {
if (triggerOnContentEditable) {
return !tagsToIgnore.includes(event.target.tagName);
}
return !event.target.isContentEditable && !tagsToIgnore.includes(event.target.tagName);
}
return true;
}
function useShortcuts(hotkeys, tagsToIgnore = ["INPUT", "TEXTAREA", "SELECT"], triggerOnContentEditable = false) {
useEffect13(() => {
const keydownListener = (event) => {
hotkeys.forEach(
([hotkey, handler, options = { preventDefault: true }]) => {
if (getHotkeyMatcher(hotkey)(event) && shouldFireEvent(event, tagsToIgnore, triggerOnContentEditable)) {
if (options.preventDefault) {
event.preventDefault();
}
handler(event);
}
}
);
};
document.documentElement.addEventListener("keydown", keydownListener);
return () => document.documentElement.removeEventListener("keydown", keydownListener);
}, [hotkeys]);
}
// hooks/useWindowEvent.ts
import { useEffect as useEffect14 } from "react";
function useWindowEvent(type, listener, options) {
useEffect14(() => {
window.addEventListener(type, listener, options);
return () => window.removeEventListener(type, listener, options);
}, [type, listener]);
}
// hooks/useViewportSize.ts
import { useCallback, useEffect as useEffect15, useState as useState15 } from "react";
var eventListerOptions = {
passive: true
};
function useViewportSize() {
const [windowSize, setWindowSize] = useState15({
width: 0,
height: 0
});
const setSize = useCallback(() => {
setWindowSize({
width: window.innerWidth || 0,
height: window.innerHeight || 0
});
}, []);
useWindowEvent("resize", setSize, eventListerOptions);
useWindowEvent("orientationchange", setSize, eventListerOptions);
useEffect15(setSize, []);
return windowSize;
}
export {
useIsomorphicEffect,
reducer,
toast,
useToast,
useDialogCarousel,
useMultiStepDialog,
useClipboard,
useWindowSize,
useFocusWithin,
useMediaQuery,
useTabs,
useMeasureDirty,
parseHotkey,
getHotkeyMatcher,
getHotkeyHandler,
useShortcuts,
useWindowEvent,
useViewportSize
};