@trackunit/react-components 0.1.119 → 0.1.124

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
  import React__default, { useRef, useCallback, useEffect, useState, useLayoutEffect, useMemo, createContext, forwardRef, useContext, isValidElement, cloneElement, Children, useReducer, memo, Component } from 'react';
4
- import { tw, tws, twx } from '@trackunit/tailwind-styled-components';
4
+ import { tw, tws } from '@trackunit/tailwind-styled-components';
5
5
  import { cvaMerge } from '@trackunit/css-class-variance-utilities';
6
6
  import { HeroIconsArray, heroIconLookup, TrackunitSolidIconsArray, TrackunitOutlineIconsArray, trackunitIconLookup } from '@trackunit/ui-icons';
7
7
  import * as ReactDOM from 'react-dom';
@@ -417,6 +417,53 @@ const StarButton = ({ starred, onClick }) => {
417
417
  return (jsx("div", { onClick: onClick, "data-test-id": "starred-filter", children: jsx(Icon, { name: "StarIcon", color: starred ? "primary" : "neutral", type: starred ? "solid" : "outline", size: "medium" }) }));
418
418
  };
419
419
 
420
+ const cvaText = cvaMerge(["m-0", "relative", "text-sm", "font-normal"], {
421
+ variants: {
422
+ align: {
423
+ left: "text-left",
424
+ right: "text-right",
425
+ center: "text-center",
426
+ },
427
+ italic: {
428
+ true: "italic",
429
+ false: "",
430
+ },
431
+ uppercase: {
432
+ true: "uppercase",
433
+ false: "",
434
+ },
435
+ underline: {
436
+ true: [
437
+ "underline",
438
+ "after:bg-inherit",
439
+ "after:content-none",
440
+ "after:absolute",
441
+ "after:w-full",
442
+ "after:h-0.5",
443
+ "after:bottom-0",
444
+ "after:left-0",
445
+ ],
446
+ false: "",
447
+ },
448
+ weight: {
449
+ normal: "font-normal",
450
+ bold: "font-medium",
451
+ thick: "font-semibold",
452
+ },
453
+ size: {
454
+ small: "text-xs",
455
+ medium: "text-sm",
456
+ large: "text-base",
457
+ },
458
+ color: {
459
+ default: "text-black",
460
+ subtleInverted: "text-neutral-300",
461
+ subtle: "text-neutral-600",
462
+ inverted: "text-white",
463
+ },
464
+ },
465
+ });
466
+
420
467
  /**
421
468
  * The Text component is used to apply Trackunit default typography styles to text.
422
469
  *
@@ -428,49 +475,10 @@ const StarButton = ({ starred, onClick }) => {
428
475
  * @returns {JSX.Element} Text component
429
476
  */
430
477
  const Text = ({ children, type = "p", size = "medium", align = "left", weight = "normal", underline = false, inverted = false, subtle = false, italicize = false, uppercase = false, className, dataTestId, }) => {
431
- return (jsx(StyledText, { "$as": type, className: className, "$align": align, "$inverted": inverted, "$subtle": subtle, "$italicize": italicize, "$uppercase": uppercase, "$size": size, "$weight": weight, "$underline": underline, "data-testid": dataTestId, children: children }));
432
- };
433
- const getTextSize = () => ({ $size }) => {
434
- const sizes = {
435
- small: tws `tu-text--small`,
436
- large: tws `tu-text--large`,
437
- medium: null,
438
- default: null,
439
- };
440
- return sizes[$size];
441
- };
442
- const getTextWeight = () => ({ $weight }) => {
443
- const weights = {
444
- thick: tws `tu-text--medium`,
445
- bold: tws `tu-text--semibold`,
446
- normal: null,
447
- default: null,
448
- };
449
- return weights[$weight];
450
- };
451
- const getFontColor = () => ({ $inverted, $subtle }) => {
452
- const subtle = $inverted ? tws `tu-text--subtle-inverted` : tws `tu-text--subtle`;
453
- const inverted = $inverted ? tws `tu-text--inverted` : null;
454
- return $subtle ? subtle : inverted;
455
- };
456
- const getTextAlignment = () => ({ $align }) => {
457
- const alignments = {
458
- left: tws `tu-text--align-left`,
459
- center: tws `tu-text--align-center`,
460
- right: tws `tu-text--align-right`,
461
- };
462
- return alignments[$align];
463
- };
464
- const StyledText = tw.text `
465
- tu-text
466
- ${getFontColor()}
467
- ${p => (p.$italicize ? tws `tu-text--italic` : "")}
468
- ${p => (p.$uppercase ? tws `tu-text--uppercase` : "")}
469
- ${getTextAlignment()}
470
- ${p => (p.$underline ? tws `tu-text--underline` : "")}
471
- ${getTextSize()}
472
- ${getTextWeight()}
473
- `;
478
+ const color = inverted && subtle ? "subtleInverted" : inverted ? "inverted" : subtle ? "subtle" : "default";
479
+ return (jsx(StyledText, { "$as": type, className: cvaText({ color, uppercase, align, italic: italicize, size, underline, weight, className }), "data-testid": dataTestId, children: children }));
480
+ };
481
+ const StyledText = tw.text ``;
474
482
 
475
483
  const cvaAlert = cvaMerge(["flex", "relative", "p-4", "rounded"], {
476
484
  variants: {
@@ -1065,6 +1073,14 @@ var useCopyToClipboard$1 = useCopyToClipboard;
1065
1073
  var useIsomorphicLayoutEffect$1 = isBrowser ? useLayoutEffect : useEffect;
1066
1074
  var useIsomorphicLayoutEffect$2 = useIsomorphicLayoutEffect$1;
1067
1075
 
1076
+ function usePrevious(state) {
1077
+ var ref = useRef();
1078
+ useEffect(function () {
1079
+ ref.current = state;
1080
+ });
1081
+ return ref.current;
1082
+ }
1083
+
1068
1084
  var defaultState = {
1069
1085
  x: 0,
1070
1086
  y: 0,
@@ -14545,7 +14561,7 @@ function Month(props) {
14545
14561
  }
14546
14562
 
14547
14563
  /** Render the container with the months according to the number of months to display. */
14548
- function Root$1(_a) {
14564
+ function Root(_a) {
14549
14565
  var initialProps = _a.initialProps;
14550
14566
  var dayPicker = useDayPicker();
14551
14567
  var focusContext = useFocusContext();
@@ -14688,7 +14704,7 @@ function RootProvider(props) {
14688
14704
  */
14689
14705
  function DayPicker$1(props) {
14690
14706
  return (React__default.createElement(RootProvider, __assign({}, props),
14691
- React__default.createElement(Root$1, { initialProps: props })));
14707
+ React__default.createElement(Root, { initialProps: props })));
14692
14708
  }
14693
14709
 
14694
14710
  const localeLookup = {
@@ -18888,6 +18904,68 @@ const Indicator = (_a) => {
18888
18904
  return (jsxs("div", Object.assign({ className: cvaIndicator({ className }), "data-testid": dataTestId }, rest, { title: !withLabel ? label : undefined, children: [jsxs("div", { className: cvaIndicatorIconBackground({ color, background: withBackground ? "visible" : "hidden" }), "data-testid": dataTestId ? `${dataTestId}-background` : "indicator-background", children: [ping && (jsx("div", { className: cvaIndicatorPing({ color }), "data-testid": dataTestId ? `${dataTestId}-ping` : "indicator-ping" })), icon] }), label !== undefined && withLabel && (jsx("div", { className: cvaIndicatorLabel(), "data-testid": dataTestId ? `${dataTestId}-label` : "indicator-label", children: label }))] })));
18889
18905
  };
18890
18906
 
18907
+ const cvaMenuItem = cvaMerge([
18908
+ "py-2",
18909
+ "pl-2",
18910
+ "pr-3",
18911
+ "w-full",
18912
+ "h-auto",
18913
+ "cursor-pointer",
18914
+ "flex",
18915
+ "flex-row",
18916
+ "items-center",
18917
+ "gap-x-2",
18918
+ "select-none",
18919
+ "rounded",
18920
+ "hover:bg-slate-100",
18921
+ ], {
18922
+ variants: {
18923
+ selected: {
18924
+ true: "bg-primary-100 hover:bg-primary-200",
18925
+ false: "",
18926
+ },
18927
+ disabled: {
18928
+ true: "!bg-slate-100 pointer-events-none",
18929
+ false: "",
18930
+ },
18931
+ focused: {
18932
+ true: "bg-slate-100",
18933
+ false: "",
18934
+ },
18935
+ },
18936
+ compoundVariants: [
18937
+ {
18938
+ focused: true,
18939
+ selected: true,
18940
+ className: "bg-primary-200",
18941
+ },
18942
+ ],
18943
+ });
18944
+ const cvaMenuItemLabel = cvaMerge([
18945
+ "flex-grow",
18946
+ "text-gray-700",
18947
+ "text-ellipsis",
18948
+ "truncate",
18949
+ "group-hover:text-gray-900",
18950
+ "group-active:text-gray-700",
18951
+ ]);
18952
+ const cvaMenuItemSuffix = cvaMerge(["self-center", "flex-grow-0", "text-slate-400"], {
18953
+ variants: {
18954
+ selected: {
18955
+ true: "text-primary-600 hover:text-primary-700",
18956
+ false: "",
18957
+ },
18958
+ },
18959
+ });
18960
+ const cvaMenuItemPrefix = cvaMerge(["self-center", "flex-grow-0", "text-slate-400", "group-hover:text-slate-600"], {
18961
+ variants: {
18962
+ selected: {
18963
+ true: "text-primary-600 hover:text-primary-700",
18964
+ false: "",
18965
+ },
18966
+ },
18967
+ });
18968
+
18891
18969
  /**
18892
18970
  * The MenuItem component is used to display a menu, primarily meant to be used in a list form.
18893
18971
  *
@@ -18895,30 +18973,32 @@ const Indicator = (_a) => {
18895
18973
  * @returns {JSX.Element} MenuItem component
18896
18974
  */
18897
18975
  const MenuItem = ({ className, dataTestId, label, children, selected, focused, prefix, suffix, disabled, onClick, }) => {
18898
- const containerProps = useContainerProps({ className, dataTestId });
18899
- return (jsxs(Container$1, Object.assign({}, containerProps, { onClick: e => {
18976
+ const containerProps = useContainerProps({ className, dataTestId: dataTestId || "menu-item" });
18977
+ return (jsxs("div", Object.assign({}, containerProps, { className: cvaMenuItem({
18978
+ selected,
18979
+ focused,
18980
+ disabled,
18981
+ className: containerProps.className,
18982
+ }), onClick: e => {
18900
18983
  e.stopPropagation();
18901
18984
  onClick && onClick(e);
18902
- }, "$selected": selected, "$focused": focused, tabIndex: 0, "$disabled": disabled, children: [prefix && (jsx(Prefix, { "$selected": selected, "data-testid": dataTestId && `${dataTestId}-prefix`, children: prefix })), children && typeof children !== "string" ? (children) : (jsx(Label, { "data-testid": dataTestId && `${dataTestId}-label`, children: children !== null && children !== void 0 ? children : label })), suffix && (jsx(Suffix, { "$selected": selected, "data-testid": dataTestId && `${dataTestId}-suffix`, children: suffix }))] })));
18985
+ }, tabIndex: 0, children: [prefix && (jsx("div", { className: cvaMenuItemPrefix({ selected }), "data-testid": dataTestId && `${dataTestId}-prefix`, children: prefix })), children && typeof children !== "string" ? (children) : (jsx("div", { className: cvaMenuItemLabel(), "data-testid": dataTestId && `${dataTestId}-label`, children: children !== null && children !== void 0 ? children : label })), suffix && (jsx("div", { className: cvaMenuItemSuffix({ selected }), "data-testid": dataTestId && `${dataTestId}-suffix`, children: suffix }))] })));
18903
18986
  };
18904
- const Container$1 = tw.div `
18905
- group tu-menu-item
18906
- ${({ $selected }) => ($selected ? tws `tu-menu-item--selected ` : "tu-menu-item--unselected")}
18907
- ${({ $focused }) => ($focused ? tws `tu-menu-item--focused ` : "")}
18908
- ${({ $disabled }) => ($disabled ? tws `tu-menu-item--disabled ` : "")}
18909
18987
 
18910
- `;
18911
- const Suffix = tw.div `
18912
- tu-menu-item__suffix
18913
- ${({ $selected }) => ($selected ? tws `tu-menu-item__suffix--selected ` : "")}
18914
- `;
18915
- const Prefix = tw.div `
18916
- tu-menu-item__prefix
18917
- ${({ $selected }) => ($selected ? tws `tu-menu-item__prefix--selected ` : "")}
18918
- `;
18919
- const Label = tw.div `
18920
- tu-menu-item__label
18921
- `;
18988
+ const cvaMenuList = cvaMerge(["shadow-md", "rounded-lg", "z-20", "bg-white", "p-1", "border", "border-slate-300", "gap-1", "grid"], {
18989
+ variants: {
18990
+ stickyHeader: {
18991
+ true: "grid grid-rows-min-fr !p-0",
18992
+ false: "",
18993
+ },
18994
+ },
18995
+ });
18996
+ const cvaMenuListDivider = cvaMerge([
18997
+ "file:w-full",
18998
+ "selection:col-span-full",
18999
+ "selection:h-px",
19000
+ "selection:bg-slate-200",
19001
+ ]);
18922
19002
 
18923
19003
  /**
18924
19004
  * The MenuList component is used for menus and selects.
@@ -18930,211 +19010,62 @@ const MenuList = (_a) => {
18930
19010
  var { dataTestId, className, children, withStickyHeader = false, showDivider = false } = _a; __rest$1(_a, ["dataTestId", "className", "children", "withStickyHeader", "showDivider"]);
18931
19011
  const childrenArr = Children.toArray(children);
18932
19012
  const isLastItem = (index) => index === childrenArr.length - 1;
18933
- return (jsx(Container, { "$withStickyHeader": withStickyHeader, "data-testid": dataTestId, className: className, tabIndex: 0, role: "list", children: Children.map(childrenArr, (menuItem, index) => (jsxs(Fragment, { children: [menuItem, showDivider && !isLastItem(index) && jsx(Divider, {})] }))) }));
19013
+ return (jsx("div", { "data-testid": dataTestId, className: cvaMenuList({ stickyHeader: withStickyHeader, className }), tabIndex: 0, role: "list", children: Children.map(childrenArr, (menuItem, index) => (jsxs(Fragment, { children: [menuItem, showDivider && !isLastItem(index) && jsx("hr", { className: cvaMenuListDivider() })] }))) }));
18934
19014
  };
18935
- const Container = tw.div `
18936
- tu-menu-list
18937
- ${props => props.$withStickyHeader && tws `tu-menu-list--sticky-header`}
18938
- `;
18939
- const Divider = tw.hr `
18940
- tu-menu-list__divider
18941
- `;
18942
19015
 
18943
19016
  /**
18944
- * Custom hook to handle click outside events.
19017
+ * Validates the state object using a Zod schema.
18945
19018
  *
18946
- * @param {RefObject<HTMLElement> | Array<RefObject<HTMLElement>>} el - The reference(s) to the element(s) to watch for click outside events.
18947
- * @param {Object | Function} [options={}] - Configuration options or the click event handler if options are not provided.
18948
- * @param {boolean} [options.active=true] - Whether the click outside event listener should be active.
18949
- * @param {Function} [onClick] - The event handler for the click outside event.
19019
+ * @template State - The type of the state.
19020
+ * @param {ValidateStateOptions<State>} params - The parameters for the validateState function.
19021
+ * @returns {boolean} - `true` if the state is valid according to the schema, `false` otherwise.
18950
19022
  */
18951
- const useClickOutside = (el, options = {}, onClick) => {
18952
- const els = Array.isArray(el) ? el : [el];
18953
- let active = true;
18954
- if (!onClick && typeof options === "function") {
18955
- onClick = options;
18956
- }
18957
- else if (typeof options === "object") {
18958
- active = options.active !== undefined ? options.active : active;
18959
- }
18960
- const handler = (ev) => {
18961
- const target = ev.target;
18962
- if (els.every(ref => !ref.current || !ref.current.contains(target))) {
18963
- onClick && onClick(ev);
18964
- }
18965
- };
18966
- const cleanup = () => window.removeEventListener("click", handler);
18967
- useEffect(() => {
18968
- if (active) {
18969
- window.addEventListener("click", handler);
18970
- }
18971
- else {
18972
- cleanup();
18973
- }
18974
- return cleanup;
18975
- });
18976
- };
18977
-
18978
- /**
18979
- * The useDebounce hook works like useState, but adds a delay where previous values will be ignored.
18980
- *
18981
- * @param {any} value The value to set
18982
- * @param {number} delay The debounce time
18983
- * @param {"in" | "out" | "both"} direction Considers truthiness of value. If set to "in", the value will only be debounced if it is truthy. If set to "out", the value will only be debounced if it is falsy. If set to "both" or if not defined, the value will be debounced regardless of truthiness.
18984
- * @returns {any} The latest value received
18985
- */
18986
- /**
18987
- *
18988
- */
18989
- const useDebounce = (value, delay = 500, direction) => {
18990
- const [debouncedValue, setDebouncedValue] = useState(value);
18991
- useEffect(() => {
18992
- let handler;
18993
- if ((direction === "in" && !debouncedValue && value) ||
18994
- (direction === "out" && debouncedValue && !value) ||
18995
- direction === "both" ||
18996
- !direction) {
18997
- handler = setTimeout(() => {
18998
- setDebouncedValue(value);
18999
- }, delay);
19023
+ const validateState = ({ state, schema, onValidationFailed, onValidationSuccessful, }) => {
19024
+ try {
19025
+ schema.parse(state);
19026
+ if (onValidationSuccessful) {
19027
+ onValidationSuccessful();
19000
19028
  }
19001
- else {
19002
- setDebouncedValue(value);
19029
+ return true;
19030
+ }
19031
+ catch (error) {
19032
+ // eslint-disable-next-line no-console
19033
+ console.error("State validation failed.", error);
19034
+ if (onValidationFailed) {
19035
+ onValidationFailed(error);
19003
19036
  }
19004
- return () => {
19005
- clearTimeout(handler);
19006
- };
19007
- }, [value, delay, direction, debouncedValue]);
19008
- return debouncedValue;
19037
+ return false;
19038
+ }
19009
19039
  };
19010
19040
 
19011
- // Copied from https://github.com/rexxars/use-device-pixel-ratio
19012
19041
  /**
19013
- * Get the device pixel ratio, potentially rounded and capped.
19014
- * Will emit new values if it changes.
19042
+ * Initializes the state from local storage, parsing and validating it if a Zod schema is provided.
19015
19043
  *
19016
- * @param {DevicePixelRatioOptions} options the options object
19017
- * @returns { number } The current device pixel ratio, or the default if none can be resolved
19044
+ * @template State - The type of the state stored in local storage.
19045
+ * @param {Omit<LocalStorageParams<State>, "state">} params - The parameters for initializing the local storage state.
19046
+ * @returns {State} - The initialized state.
19018
19047
  */
19019
- function useDevicePixelRatio(options) {
19020
- const dpr = getDevicePixelRatio(options);
19021
- const [currentDpr, setCurrentDpr] = useState(dpr);
19022
- const { defaultDpr, maxDpr, round } = options || {};
19023
- useEffect(() => {
19024
- const canListen = typeof window !== "undefined" && "matchMedia" in window;
19025
- if (!canListen) {
19026
- return;
19027
- }
19028
- const updateDpr = () => setCurrentDpr(getDevicePixelRatio({ defaultDpr, maxDpr, round }));
19029
- const mediaMatcher = window.matchMedia(`screen and (resolution: ${currentDpr}dppx)`);
19030
- // Safari 13.1 does not have `addEventListener`, but does have `addListener`
19031
- if (mediaMatcher.addEventListener) {
19032
- mediaMatcher.addEventListener("change", updateDpr);
19033
- }
19034
- else {
19035
- mediaMatcher.addListener(updateDpr);
19036
- }
19037
- return () => {
19038
- if (mediaMatcher.removeEventListener) {
19039
- mediaMatcher.removeEventListener("change", updateDpr);
19048
+ const initLocalStorageState = ({ key, defaultState, schema, }) => {
19049
+ const localStorageItem = localStorage.getItem(key);
19050
+ if (localStorageItem) {
19051
+ try {
19052
+ const parsedState = JSON.parse(localStorageItem);
19053
+ if (schema) {
19054
+ if (validateState({ state: parsedState, schema })) {
19055
+ return parsedState;
19056
+ }
19040
19057
  }
19041
19058
  else {
19042
- mediaMatcher.removeListener(updateDpr);
19059
+ return parsedState;
19043
19060
  }
19044
- };
19045
- }, [currentDpr, defaultDpr, maxDpr, round]);
19046
- return currentDpr;
19047
- }
19048
- /**
19049
- * Returns the current device pixel ratio (DPR) given the passed options
19050
- *
19051
- * @param { DevicePixelRatioOptions } options the options object
19052
- * @returns { number } current device pixel ratio
19053
- */
19054
- function getDevicePixelRatio(options) {
19055
- const { defaultDpr = 1, maxDpr = 3, round = true } = options || {};
19056
- const hasDprProp = typeof window !== "undefined" && typeof window.devicePixelRatio === "number";
19057
- const dpr = hasDprProp ? window.devicePixelRatio : defaultDpr;
19058
- const rounded = Math.min(Math.max(1, round ? Math.floor(dpr) : dpr), maxDpr);
19059
- return rounded;
19060
- }
19061
-
19062
- /**
19063
- * The useHover hook returns a onMouseEnter, onMouseLeave and a boolean indicating whether the element is being hovered.
19064
- * The boolean will be true if the element is being hovered, and false if it is not.
19065
- * Can be directionally debounced.
19066
- *
19067
- * @param {ConditionalUseHoverProps} param0 The options
19068
- * @returns {Object} The object containing the onMouseEnter, onMouseLeave and hovering props
19069
- */
19070
- const useHover = ({ debounced = false, delay = 100, direction = "out" } = { debounced: false }) => {
19071
- const [hovering, setHovering] = useState(false);
19072
- const debouncedHovering = useDebounce(hovering, delay, direction);
19073
- const onMouseEnter = () => {
19074
- setHovering(true);
19075
- };
19076
- const onMouseLeave = () => {
19077
- setHovering(false);
19078
- };
19079
- return { onMouseEnter, onMouseLeave, hovering: debounced ? debouncedHovering : hovering };
19080
- };
19081
-
19082
- /**
19083
- * Differentiate between the first and subsequent renders.
19084
- *
19085
- * @returns {boolean} Returns true if it is the first render, false otherwise.
19086
- */
19087
- const useIsFirstRender = () => {
19088
- const renderRef = useRef(true);
19089
- if (renderRef.current === true) {
19090
- renderRef.current = false;
19091
- return true;
19092
- }
19093
- return renderRef.current;
19094
- };
19095
-
19096
- /**
19097
- * The useLocalStorage hook works like useState, but saves to localstorage.
19098
- *
19099
- * @param {string} key - The key the values is saved under.
19100
- * @param {string | null | undefined} initialValue - The initial value for the state/local storage item
19101
- * @returns {string | null | undefined} value
19102
- */
19103
- const useLocalStorage = (key, initialValue) => {
19104
- if (!key) {
19105
- throw new Error("useLocalStorage key may not be falsy");
19106
- }
19107
- const setLocalStorageItem = useCallback((value) => window.localStorage.setItem(key, typeof value === "object" ? JSON.stringify(value) : `${value}`), [key]);
19108
- const removeLocalStorageItem = useCallback(() => window.localStorage.removeItem(key), [key]);
19109
- const [item, setValue] = useState(() => {
19110
- const value = window.localStorage.getItem(key);
19111
- if (value !== undefined && value !== null) {
19112
- const parsedValue = tryParse(value);
19113
- return parsedValue;
19114
- }
19115
- return initialValue !== null && initialValue !== void 0 ? initialValue : null;
19116
- });
19117
- useEffect(() => {
19118
- if (item !== undefined && item !== null) {
19119
- setLocalStorageItem(item);
19120
19061
  }
19121
- else {
19122
- removeLocalStorageItem();
19062
+ catch (error) {
19063
+ // eslint-disable-next-line no-console
19064
+ console.error("Failed to parse and validate the state from local storage.", error);
19123
19065
  }
19124
- }, [item, setLocalStorageItem, removeLocalStorageItem, key]);
19125
- const removeItem = () => {
19126
- setValue(null);
19127
- };
19128
- return [item, setValue, removeItem];
19129
- };
19130
- function tryParse(value) {
19131
- try {
19132
- return JSON.parse(value);
19133
19066
  }
19134
- catch (_a) {
19135
- return value;
19136
- }
19137
- }
19067
+ return defaultState;
19068
+ };
19138
19069
 
19139
19070
  var lodash = {exports: {}};
19140
19071
 
@@ -36338,47 +36269,229 @@ var lodash = {exports: {}};
36338
36269
  }(lodash, lodash.exports));
36339
36270
 
36340
36271
  /**
36341
- * Given any value this hook will return the previous value whenever the current value changes
36272
+ * Custom hook for synchronizing a state variable with local storage,
36273
+ * with optional schema validation and callbacks.
36274
+ *
36275
+ * @template State - The type of the state variable.
36276
+ * @param {LocalStorageParams<State> & LocalStorageCallbacks & { state: State }} params - The parameters for the useLocalStorageEffect hook.
36342
36277
  */
36343
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
36344
- function usePrevious(value) {
36345
- const ref = useRef();
36278
+ const useLocalStorageEffect = ({ key, state, defaultState, schema, onValidationFailed, onValidationSuccessful, }) => {
36279
+ const prevState = usePrevious(state);
36346
36280
  useEffect(() => {
36347
- ref.current = value;
36281
+ if (lodash.exports.isEqual(prevState, state)) {
36282
+ return;
36283
+ }
36284
+ if (schema) {
36285
+ validateState({
36286
+ state,
36287
+ schema,
36288
+ onValidationFailed: error => {
36289
+ // eslint-disable-next-line no-console
36290
+ console.error(`State validation failed. Resetting local storage to default state: ${defaultState}.`, error);
36291
+ localStorage.removeItem(key);
36292
+ onValidationFailed === null || onValidationFailed === void 0 ? void 0 : onValidationFailed(error);
36293
+ },
36294
+ onValidationSuccessful: () => {
36295
+ const stringifiedState = JSON.stringify(state);
36296
+ localStorage.setItem(key, stringifiedState);
36297
+ onValidationSuccessful === null || onValidationSuccessful === void 0 ? void 0 : onValidationSuccessful();
36298
+ },
36299
+ });
36300
+ }
36301
+ else {
36302
+ const stringifiedState = JSON.stringify(state);
36303
+ localStorage.setItem(key, stringifiedState);
36304
+ }
36305
+ }, [state, key, schema, defaultState, prevState, onValidationFailed, onValidationSuccessful]);
36306
+ };
36307
+
36308
+ /**
36309
+ * Works like a normal useState, but saves to local storage and has optional schema validation.
36310
+ *
36311
+ * @template State - The type of the value stored in local storage.
36312
+ * @param {Omit<LocalStorageParams<State>, "state"> & LocalStorageCallbacks} options - The options for useLocalStorage.
36313
+ * @returns {[State, Dispatch<SetStateAction<State>>, () => void]} - A tuple containing the current value, a function to update the value, and a function to remove the value from local storage.
36314
+ */
36315
+ const useLocalStorage = ({ key, defaultState, schema, onValidationFailed, onValidationSuccessful, }) => {
36316
+ if (!key) {
36317
+ throw new Error("useLocalStorage key may not be falsy");
36318
+ }
36319
+ const [state, setState] = useState(initLocalStorageState({ key, defaultState, schema }));
36320
+ useLocalStorageEffect({ key, state, defaultState, schema, onValidationFailed, onValidationSuccessful });
36321
+ const reset = () => {
36322
+ setState(defaultState);
36323
+ };
36324
+ return [state, setState, reset];
36325
+ };
36326
+
36327
+ /**
36328
+ * Works like a normal useReducer, but saves to local storage and has optional schema validation.
36329
+ *
36330
+ * @template State - The type of the state.
36331
+ * @template Action - The type of the action.
36332
+ * @param {LocalStorageParams<State> & LocalStorageCallbacks} params - The parameters for the useLocalStorageReducer function.
36333
+ * @returns {[State, Dispatch<Action>]} - A tuple containing the state and the dispatch function.
36334
+ */
36335
+ const useLocalStorageReducer = ({ key, defaultState, reducer, schema, onValidationFailed, onValidationSuccessful, }) => {
36336
+ if (!key) {
36337
+ throw new Error("useLocalStorage key may not be falsy");
36338
+ }
36339
+ const [state, dispatch] = useReducer(reducer, defaultState, () => initLocalStorageState({ key, defaultState, schema }));
36340
+ useLocalStorageEffect({ key, state, defaultState, schema, onValidationFailed, onValidationSuccessful });
36341
+ return [state, dispatch];
36342
+ };
36343
+
36344
+ /**
36345
+ * Custom hook to handle click outside events.
36346
+ *
36347
+ * @param {RefObject<HTMLElement> | Array<RefObject<HTMLElement>>} el - The reference(s) to the element(s) to watch for click outside events.
36348
+ * @param {Object | Function} [options={}] - Configuration options or the click event handler if options are not provided.
36349
+ * @param {boolean} [options.active=true] - Whether the click outside event listener should be active.
36350
+ * @param {Function} [onClick] - The event handler for the click outside event.
36351
+ */
36352
+ const useClickOutside = (el, options = {}, onClick) => {
36353
+ const els = Array.isArray(el) ? el : [el];
36354
+ let active = true;
36355
+ if (!onClick && typeof options === "function") {
36356
+ onClick = options;
36357
+ }
36358
+ else if (typeof options === "object") {
36359
+ active = options.active !== undefined ? options.active : active;
36360
+ }
36361
+ const handler = (ev) => {
36362
+ const target = ev.target;
36363
+ if (els.every(ref => !ref.current || !ref.current.contains(target))) {
36364
+ onClick && onClick(ev);
36365
+ }
36366
+ };
36367
+ const cleanup = () => window.removeEventListener("click", handler);
36368
+ useEffect(() => {
36369
+ if (active) {
36370
+ window.addEventListener("click", handler);
36371
+ }
36372
+ else {
36373
+ cleanup();
36374
+ }
36375
+ return cleanup;
36348
36376
  });
36349
- return ref.current;
36350
- }
36377
+ };
36378
+
36379
+ /**
36380
+ * The useDebounce hook works like useState, but adds a delay where previous values will be ignored.
36381
+ *
36382
+ * @param {any} value The value to set
36383
+ * @param {number} delay The debounce time
36384
+ * @param {"in" | "out" | "both"} direction Considers truthiness of value. If set to "in", the value will only be debounced if it is truthy. If set to "out", the value will only be debounced if it is falsy. If set to "both" or if not defined, the value will be debounced regardless of truthiness.
36385
+ * @returns {any} The latest value received
36386
+ */
36351
36387
  /**
36352
- * Wraps a normal useReducer with local storage functionality
36353
36388
  *
36354
- * @param {string} key - The key the state is saved with in local storage.
36355
36389
  */
36356
- const useLocalStorageReducer = (key, reducer, initialState
36357
- //return must be explicitly typed atm for some reason
36358
- ) => {
36359
- const init = () => {
36360
- const stringState = localStorage.getItem(key);
36361
- if (stringState) {
36362
- try {
36363
- return JSON.parse(stringState);
36364
- }
36365
- catch (error) {
36366
- return initialState;
36367
- }
36390
+ const useDebounce = (value, delay = 500, direction) => {
36391
+ const [debouncedValue, setDebouncedValue] = useState(value);
36392
+ useEffect(() => {
36393
+ let handler;
36394
+ if ((direction === "in" && !debouncedValue && value) ||
36395
+ (direction === "out" && debouncedValue && !value) ||
36396
+ direction === "both" ||
36397
+ !direction) {
36398
+ handler = setTimeout(() => {
36399
+ setDebouncedValue(value);
36400
+ }, delay);
36368
36401
  }
36369
36402
  else {
36370
- return initialState;
36403
+ setDebouncedValue(value);
36371
36404
  }
36372
- };
36373
- const [state, dispatch] = useReducer(reducer, initialState, init);
36374
- const prevState = usePrevious(state);
36405
+ return () => {
36406
+ clearTimeout(handler);
36407
+ };
36408
+ }, [value, delay, direction, debouncedValue]);
36409
+ return debouncedValue;
36410
+ };
36411
+
36412
+ // Copied from https://github.com/rexxars/use-device-pixel-ratio
36413
+ /**
36414
+ * Get the device pixel ratio, potentially rounded and capped.
36415
+ * Will emit new values if it changes.
36416
+ *
36417
+ * @param {DevicePixelRatioOptions} options the options object
36418
+ * @returns { number } The current device pixel ratio, or the default if none can be resolved
36419
+ */
36420
+ function useDevicePixelRatio(options) {
36421
+ const dpr = getDevicePixelRatio(options);
36422
+ const [currentDpr, setCurrentDpr] = useState(dpr);
36423
+ const { defaultDpr, maxDpr, round } = options || {};
36375
36424
  useEffect(() => {
36376
- if (!lodash.exports.isEqual(prevState, state)) {
36377
- const stringifiedState = JSON.stringify(state);
36378
- localStorage.setItem(key, stringifiedState);
36425
+ const canListen = typeof window !== "undefined" && "matchMedia" in window;
36426
+ if (!canListen) {
36427
+ return;
36379
36428
  }
36380
- }, [prevState, state, key]);
36381
- return [state, dispatch];
36429
+ const updateDpr = () => setCurrentDpr(getDevicePixelRatio({ defaultDpr, maxDpr, round }));
36430
+ const mediaMatcher = window.matchMedia(`screen and (resolution: ${currentDpr}dppx)`);
36431
+ // Safari 13.1 does not have `addEventListener`, but does have `addListener`
36432
+ if (mediaMatcher.addEventListener) {
36433
+ mediaMatcher.addEventListener("change", updateDpr);
36434
+ }
36435
+ else {
36436
+ mediaMatcher.addListener(updateDpr);
36437
+ }
36438
+ return () => {
36439
+ if (mediaMatcher.removeEventListener) {
36440
+ mediaMatcher.removeEventListener("change", updateDpr);
36441
+ }
36442
+ else {
36443
+ mediaMatcher.removeListener(updateDpr);
36444
+ }
36445
+ };
36446
+ }, [currentDpr, defaultDpr, maxDpr, round]);
36447
+ return currentDpr;
36448
+ }
36449
+ /**
36450
+ * Returns the current device pixel ratio (DPR) given the passed options
36451
+ *
36452
+ * @param { DevicePixelRatioOptions } options the options object
36453
+ * @returns { number } current device pixel ratio
36454
+ */
36455
+ function getDevicePixelRatio(options) {
36456
+ const { defaultDpr = 1, maxDpr = 3, round = true } = options || {};
36457
+ const hasDprProp = typeof window !== "undefined" && typeof window.devicePixelRatio === "number";
36458
+ const dpr = hasDprProp ? window.devicePixelRatio : defaultDpr;
36459
+ const rounded = Math.min(Math.max(1, round ? Math.floor(dpr) : dpr), maxDpr);
36460
+ return rounded;
36461
+ }
36462
+
36463
+ /**
36464
+ * The useHover hook returns a onMouseEnter, onMouseLeave and a boolean indicating whether the element is being hovered.
36465
+ * The boolean will be true if the element is being hovered, and false if it is not.
36466
+ * Can be directionally debounced.
36467
+ *
36468
+ * @param {ConditionalUseHoverProps} param0 The options
36469
+ * @returns {Object} The object containing the onMouseEnter, onMouseLeave and hovering props
36470
+ */
36471
+ const useHover = ({ debounced = false, delay = 100, direction = "out" } = { debounced: false }) => {
36472
+ const [hovering, setHovering] = useState(false);
36473
+ const debouncedHovering = useDebounce(hovering, delay, direction);
36474
+ const onMouseEnter = () => {
36475
+ setHovering(true);
36476
+ };
36477
+ const onMouseLeave = () => {
36478
+ setHovering(false);
36479
+ };
36480
+ return { onMouseEnter, onMouseLeave, hovering: debounced ? debouncedHovering : hovering };
36481
+ };
36482
+
36483
+ /**
36484
+ * Differentiate between the first and subsequent renders.
36485
+ *
36486
+ * @returns {boolean} Returns true if it is the first render, false otherwise.
36487
+ */
36488
+ const useIsFirstRender = () => {
36489
+ const renderRef = useRef(true);
36490
+ if (renderRef.current === true) {
36491
+ renderRef.current = false;
36492
+ return true;
36493
+ }
36494
+ return renderRef.current;
36382
36495
  };
36383
36496
 
36384
36497
  /**
@@ -36450,6 +36563,8 @@ const useTimeout = ({ onTimeout, duration }) => {
36450
36563
  return [isReady, stopTimeout, startTimeout];
36451
36564
  };
36452
36565
 
36566
+ const cvaMoreMenu = cvaMerge(["p-4"]);
36567
+
36453
36568
  /**
36454
36569
  * A kebab menu component.
36455
36570
  * Advice: fill it with MenuList
@@ -36471,14 +36586,41 @@ const MoreMenu = ({ className, dataTestId, menuPlacement, iconProps = {
36471
36586
  useClickOutside(actionMenuRef, () => {
36472
36587
  setActionMenuIsOpen(false);
36473
36588
  });
36474
- return (jsx(Root, { ref: actionMenuRef, className: className, "data-testid": dataTestId, children: jsxs(Popover, { placement: menuPlacement, children: [jsx(PopoverTrigger, { children: customButton !== null && customButton !== void 0 ? customButton : (jsx(IconButton, Object.assign({}, iconButtonProps, { icon: iconProps.name ? jsx(Icon, Object.assign({}, iconProps, { name: iconProps.name })) : null, onClick: () => {
36589
+ return (jsx("div", { ref: actionMenuRef, className: cvaMoreMenu({ className }), "data-testid": dataTestId, children: jsxs(Popover, { placement: menuPlacement, children: [jsx(PopoverTrigger, { children: customButton !== null && customButton !== void 0 ? customButton : (jsx(IconButton, Object.assign({}, iconButtonProps, { icon: iconProps.name ? jsx(Icon, Object.assign({}, iconProps, { name: iconProps.name })) : null, onClick: () => {
36475
36590
  setActionMenuIsOpen(!actionMenuIsOpen);
36476
36591
  } }))) }), jsx(PopoverContent, { children: close => (typeof children === "function" ? children(close) : children) })] }) }));
36477
36592
  };
36478
- const Root = tw.div `
36479
- tu-more-menu
36480
- ${({ variant }) => (variant === "primary" ? tws `tu-more-menu--primary` : "")}
36481
- `;
36593
+
36594
+ const cvaModalContainer = cvaMerge(["h-full", "w-full", "flex", "items-center"]);
36595
+ const cvaModalCard = cvaMerge([
36596
+ "m-auto",
36597
+ "shadow-2xl",
36598
+ "w-[calc(100%-1rem)]",
36599
+ "max-h-[min(100%-1rem,30%+600px)]",
36600
+ "max-w-[min(100%-1rem,30%+600px)]",
36601
+ ]);
36602
+ const cvaModalBackdrop = cvaMerge([
36603
+ "flex",
36604
+ "justify-center",
36605
+ "items-center",
36606
+ "fixed",
36607
+ "top-0",
36608
+ "left-0",
36609
+ "w-full",
36610
+ "h-full",
36611
+ "bg-black/50",
36612
+ "z-20",
36613
+ "invisible",
36614
+ "opacity-0",
36615
+ "transition-all",
36616
+ ], {
36617
+ variants: {
36618
+ show: {
36619
+ true: "animate-modal-fade",
36620
+ false: "",
36621
+ },
36622
+ },
36623
+ });
36482
36624
 
36483
36625
  /**
36484
36626
  * The Backdrop for the modal.
@@ -36488,7 +36630,7 @@ const Root = tw.div `
36488
36630
  */
36489
36631
  const ModalBackdrop = ({ children, isVisible, onClick }) => {
36490
36632
  const ref = React.useRef(null);
36491
- return (jsx(Backdrop, { ref: ref, "$isVisible": isVisible, onMouseDown: e => {
36633
+ return (jsx("div", { ref: ref, className: cvaModalBackdrop({ show: isVisible }), onMouseDown: e => {
36492
36634
  var _a, _b;
36493
36635
  // only react on click on backdrop - accessing first child to get around styled element element
36494
36636
  if (ref.current !== null) {
@@ -36498,12 +36640,6 @@ const ModalBackdrop = ({ children, isVisible, onClick }) => {
36498
36640
  }
36499
36641
  }, children: children }));
36500
36642
  };
36501
- const Backdrop = tw.div `
36502
- tu-modal-backdrop
36503
- ${({ $isVisible }) => {
36504
- return $isVisible && tws `tu-modal-backdrop--show`;
36505
- }}
36506
- `;
36507
36643
 
36508
36644
  /**
36509
36645
  * - Modals are used to present critical information or request user input needed to complete a user's workflow.
@@ -36513,14 +36649,8 @@ const Backdrop = tw.div `
36513
36649
  const Modal = React.forwardRef(({ isOpen, children, dataTestId, className, role = "", onDismissClick = () => {
36514
36650
  return;
36515
36651
  }, }, ref) => {
36516
- return (jsx(ModalBackdrop, { isVisible: isOpen, onClick: onDismissClick, children: isOpen && (jsx(StyledDiv, { ref: ref, role: role, children: jsx(StyledCard, { dataTestId: dataTestId, className: className, children: children }) })) }));
36652
+ return (jsx(ModalBackdrop, { isVisible: isOpen, onClick: onDismissClick, children: isOpen && (jsx("div", { className: cvaModalContainer(), ref: ref, role: role, children: jsx(Card, { dataTestId: dataTestId, className: cvaModalCard({ className }), children: children }) })) }));
36517
36653
  });
36518
- const StyledCard = twx(Card) `
36519
- tu-modal__card
36520
- `;
36521
- const StyledDiv = tw.div `
36522
- tu-modal__container
36523
- `;
36524
36654
 
36525
36655
  var usePortal$1 = {};
36526
36656
 
@@ -39034,4 +39164,4 @@ const ContentGridArea = tw.article `
39034
39164
  tu-layout-area-content
39035
39165
  `;
39036
39166
 
39037
- export { Alert, Badge, Button$1 as Button, Card, CardBody, CardFooter, CardHeader, Collapse, ContentGridArea, CopyableText, DayPicker, DayPickerPopover, DayRangePicker, DensityContainer, Drawer, ExternalLink, FilterListMapLayout, FullWidthRepeatingLayout, GridArea, Heading, Icon, IconButton, Indicator, IntrinsicallyResponsiveGrid, MenuItem, MenuList, MinTopFullContentLayout, Modal, ModalBackdrop, MoreMenu, NavGridArea, PageHeader, Pagination, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Prompt, ROLE_CARD, SectionHeader, Sidebar, SkeletonLines, Spacer, Spinner, StarButton, Tag, Text, Timeline, TimelineElement, Tip, TitleGridArea, TitleNavContentLayout, Tooltip, ValueBar, cvaButton, cvaButtonSpinner, cvaClickable, cvaIconButtonContainer, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, docs, getDevicePixelRatio, getValueBarColorByValue, useClickOutside, useContainerProps, useDebounce, useDevicePixelRatio, useHover, useIsFirstRender, useLocalStorage, useLocalStorageReducer, useModal, usePopoverContext, usePrompt, useResize, useTimeout };
39167
+ export { Alert, Badge, Button$1 as Button, Card, CardBody, CardFooter, CardHeader, Collapse, ContentGridArea, CopyableText, DayPicker, DayPickerPopover, DayRangePicker, DensityContainer, Drawer, ExternalLink, FilterListMapLayout, FullWidthRepeatingLayout, GridArea, Heading, Icon, IconButton, Indicator, IntrinsicallyResponsiveGrid, MenuItem, MenuList, MinTopFullContentLayout, Modal, ModalBackdrop, MoreMenu, NavGridArea, PageHeader, Pagination, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Prompt, ROLE_CARD, SectionHeader, Sidebar, SkeletonLines, Spacer, Spinner, StarButton, Tag, Text, Timeline, TimelineElement, Tip, TitleGridArea, TitleNavContentLayout, Tooltip, ValueBar, cvaButton, cvaButtonSpinner, cvaClickable, cvaIconButtonContainer, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemSuffix, docs, getDevicePixelRatio, getValueBarColorByValue, useClickOutside, useContainerProps, useDebounce, useDevicePixelRatio, useHover, useIsFirstRender, useLocalStorage, useLocalStorageReducer, useModal, usePopoverContext, usePrompt, useResize, useTimeout };