@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.cjs CHANGED
@@ -444,6 +444,53 @@ const StarButton = ({ starred, onClick }) => {
444
444
  return (jsxRuntime.jsx("div", { onClick: onClick, "data-test-id": "starred-filter", children: jsxRuntime.jsx(Icon, { name: "StarIcon", color: starred ? "primary" : "neutral", type: starred ? "solid" : "outline", size: "medium" }) }));
445
445
  };
446
446
 
447
+ const cvaText = cssClassVarianceUtilities.cvaMerge(["m-0", "relative", "text-sm", "font-normal"], {
448
+ variants: {
449
+ align: {
450
+ left: "text-left",
451
+ right: "text-right",
452
+ center: "text-center",
453
+ },
454
+ italic: {
455
+ true: "italic",
456
+ false: "",
457
+ },
458
+ uppercase: {
459
+ true: "uppercase",
460
+ false: "",
461
+ },
462
+ underline: {
463
+ true: [
464
+ "underline",
465
+ "after:bg-inherit",
466
+ "after:content-none",
467
+ "after:absolute",
468
+ "after:w-full",
469
+ "after:h-0.5",
470
+ "after:bottom-0",
471
+ "after:left-0",
472
+ ],
473
+ false: "",
474
+ },
475
+ weight: {
476
+ normal: "font-normal",
477
+ bold: "font-medium",
478
+ thick: "font-semibold",
479
+ },
480
+ size: {
481
+ small: "text-xs",
482
+ medium: "text-sm",
483
+ large: "text-base",
484
+ },
485
+ color: {
486
+ default: "text-black",
487
+ subtleInverted: "text-neutral-300",
488
+ subtle: "text-neutral-600",
489
+ inverted: "text-white",
490
+ },
491
+ },
492
+ });
493
+
447
494
  /**
448
495
  * The Text component is used to apply Trackunit default typography styles to text.
449
496
  *
@@ -455,49 +502,10 @@ const StarButton = ({ starred, onClick }) => {
455
502
  * @returns {JSX.Element} Text component
456
503
  */
457
504
  const Text = ({ children, type = "p", size = "medium", align = "left", weight = "normal", underline = false, inverted = false, subtle = false, italicize = false, uppercase = false, className, dataTestId, }) => {
458
- return (jsxRuntime.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 }));
459
- };
460
- const getTextSize = () => ({ $size }) => {
461
- const sizes = {
462
- small: tailwindStyledComponents.tws `tu-text--small`,
463
- large: tailwindStyledComponents.tws `tu-text--large`,
464
- medium: null,
465
- default: null,
466
- };
467
- return sizes[$size];
468
- };
469
- const getTextWeight = () => ({ $weight }) => {
470
- const weights = {
471
- thick: tailwindStyledComponents.tws `tu-text--medium`,
472
- bold: tailwindStyledComponents.tws `tu-text--semibold`,
473
- normal: null,
474
- default: null,
475
- };
476
- return weights[$weight];
477
- };
478
- const getFontColor = () => ({ $inverted, $subtle }) => {
479
- const subtle = $inverted ? tailwindStyledComponents.tws `tu-text--subtle-inverted` : tailwindStyledComponents.tws `tu-text--subtle`;
480
- const inverted = $inverted ? tailwindStyledComponents.tws `tu-text--inverted` : null;
481
- return $subtle ? subtle : inverted;
482
- };
483
- const getTextAlignment = () => ({ $align }) => {
484
- const alignments = {
485
- left: tailwindStyledComponents.tws `tu-text--align-left`,
486
- center: tailwindStyledComponents.tws `tu-text--align-center`,
487
- right: tailwindStyledComponents.tws `tu-text--align-right`,
488
- };
489
- return alignments[$align];
490
- };
491
- const StyledText = tailwindStyledComponents.tw.text `
492
- tu-text
493
- ${getFontColor()}
494
- ${p => (p.$italicize ? tailwindStyledComponents.tws `tu-text--italic` : "")}
495
- ${p => (p.$uppercase ? tailwindStyledComponents.tws `tu-text--uppercase` : "")}
496
- ${getTextAlignment()}
497
- ${p => (p.$underline ? tailwindStyledComponents.tws `tu-text--underline` : "")}
498
- ${getTextSize()}
499
- ${getTextWeight()}
500
- `;
505
+ const color = inverted && subtle ? "subtleInverted" : inverted ? "inverted" : subtle ? "subtle" : "default";
506
+ return (jsxRuntime.jsx(StyledText, { "$as": type, className: cvaText({ color, uppercase, align, italic: italicize, size, underline, weight, className }), "data-testid": dataTestId, children: children }));
507
+ };
508
+ const StyledText = tailwindStyledComponents.tw.text ``;
501
509
 
502
510
  const cvaAlert = cssClassVarianceUtilities.cvaMerge(["flex", "relative", "p-4", "rounded"], {
503
511
  variants: {
@@ -1092,6 +1100,14 @@ var useCopyToClipboard$1 = useCopyToClipboard;
1092
1100
  var useIsomorphicLayoutEffect$1 = isBrowser ? React.useLayoutEffect : React.useEffect;
1093
1101
  var useIsomorphicLayoutEffect$2 = useIsomorphicLayoutEffect$1;
1094
1102
 
1103
+ function usePrevious(state) {
1104
+ var ref = React.useRef();
1105
+ React.useEffect(function () {
1106
+ ref.current = state;
1107
+ });
1108
+ return ref.current;
1109
+ }
1110
+
1095
1111
  var defaultState = {
1096
1112
  x: 0,
1097
1113
  y: 0,
@@ -14572,7 +14588,7 @@ function Month(props) {
14572
14588
  }
14573
14589
 
14574
14590
  /** Render the container with the months according to the number of months to display. */
14575
- function Root$1(_a) {
14591
+ function Root(_a) {
14576
14592
  var initialProps = _a.initialProps;
14577
14593
  var dayPicker = useDayPicker();
14578
14594
  var focusContext = useFocusContext();
@@ -14715,7 +14731,7 @@ function RootProvider(props) {
14715
14731
  */
14716
14732
  function DayPicker$1(props) {
14717
14733
  return (React__default["default"].createElement(RootProvider, __assign({}, props),
14718
- React__default["default"].createElement(Root$1, { initialProps: props })));
14734
+ React__default["default"].createElement(Root, { initialProps: props })));
14719
14735
  }
14720
14736
 
14721
14737
  const localeLookup = {
@@ -18915,6 +18931,68 @@ const Indicator = (_a) => {
18915
18931
  return (jsxRuntime.jsxs("div", Object.assign({ className: cvaIndicator({ className }), "data-testid": dataTestId }, rest, { title: !withLabel ? label : undefined, children: [jsxRuntime.jsxs("div", { className: cvaIndicatorIconBackground({ color, background: withBackground ? "visible" : "hidden" }), "data-testid": dataTestId ? `${dataTestId}-background` : "indicator-background", children: [ping && (jsxRuntime.jsx("div", { className: cvaIndicatorPing({ color }), "data-testid": dataTestId ? `${dataTestId}-ping` : "indicator-ping" })), icon] }), label !== undefined && withLabel && (jsxRuntime.jsx("div", { className: cvaIndicatorLabel(), "data-testid": dataTestId ? `${dataTestId}-label` : "indicator-label", children: label }))] })));
18916
18932
  };
18917
18933
 
18934
+ const cvaMenuItem = cssClassVarianceUtilities.cvaMerge([
18935
+ "py-2",
18936
+ "pl-2",
18937
+ "pr-3",
18938
+ "w-full",
18939
+ "h-auto",
18940
+ "cursor-pointer",
18941
+ "flex",
18942
+ "flex-row",
18943
+ "items-center",
18944
+ "gap-x-2",
18945
+ "select-none",
18946
+ "rounded",
18947
+ "hover:bg-slate-100",
18948
+ ], {
18949
+ variants: {
18950
+ selected: {
18951
+ true: "bg-primary-100 hover:bg-primary-200",
18952
+ false: "",
18953
+ },
18954
+ disabled: {
18955
+ true: "!bg-slate-100 pointer-events-none",
18956
+ false: "",
18957
+ },
18958
+ focused: {
18959
+ true: "bg-slate-100",
18960
+ false: "",
18961
+ },
18962
+ },
18963
+ compoundVariants: [
18964
+ {
18965
+ focused: true,
18966
+ selected: true,
18967
+ className: "bg-primary-200",
18968
+ },
18969
+ ],
18970
+ });
18971
+ const cvaMenuItemLabel = cssClassVarianceUtilities.cvaMerge([
18972
+ "flex-grow",
18973
+ "text-gray-700",
18974
+ "text-ellipsis",
18975
+ "truncate",
18976
+ "group-hover:text-gray-900",
18977
+ "group-active:text-gray-700",
18978
+ ]);
18979
+ const cvaMenuItemSuffix = cssClassVarianceUtilities.cvaMerge(["self-center", "flex-grow-0", "text-slate-400"], {
18980
+ variants: {
18981
+ selected: {
18982
+ true: "text-primary-600 hover:text-primary-700",
18983
+ false: "",
18984
+ },
18985
+ },
18986
+ });
18987
+ const cvaMenuItemPrefix = cssClassVarianceUtilities.cvaMerge(["self-center", "flex-grow-0", "text-slate-400", "group-hover:text-slate-600"], {
18988
+ variants: {
18989
+ selected: {
18990
+ true: "text-primary-600 hover:text-primary-700",
18991
+ false: "",
18992
+ },
18993
+ },
18994
+ });
18995
+
18918
18996
  /**
18919
18997
  * The MenuItem component is used to display a menu, primarily meant to be used in a list form.
18920
18998
  *
@@ -18922,30 +19000,32 @@ const Indicator = (_a) => {
18922
19000
  * @returns {JSX.Element} MenuItem component
18923
19001
  */
18924
19002
  const MenuItem = ({ className, dataTestId, label, children, selected, focused, prefix, suffix, disabled, onClick, }) => {
18925
- const containerProps = useContainerProps({ className, dataTestId });
18926
- return (jsxRuntime.jsxs(Container$1, Object.assign({}, containerProps, { onClick: e => {
19003
+ const containerProps = useContainerProps({ className, dataTestId: dataTestId || "menu-item" });
19004
+ return (jsxRuntime.jsxs("div", Object.assign({}, containerProps, { className: cvaMenuItem({
19005
+ selected,
19006
+ focused,
19007
+ disabled,
19008
+ className: containerProps.className,
19009
+ }), onClick: e => {
18927
19010
  e.stopPropagation();
18928
19011
  onClick && onClick(e);
18929
- }, "$selected": selected, "$focused": focused, tabIndex: 0, "$disabled": disabled, children: [prefix && (jsxRuntime.jsx(Prefix, { "$selected": selected, "data-testid": dataTestId && `${dataTestId}-prefix`, children: prefix })), children && typeof children !== "string" ? (children) : (jsxRuntime.jsx(Label, { "data-testid": dataTestId && `${dataTestId}-label`, children: children !== null && children !== void 0 ? children : label })), suffix && (jsxRuntime.jsx(Suffix, { "$selected": selected, "data-testid": dataTestId && `${dataTestId}-suffix`, children: suffix }))] })));
19012
+ }, tabIndex: 0, children: [prefix && (jsxRuntime.jsx("div", { className: cvaMenuItemPrefix({ selected }), "data-testid": dataTestId && `${dataTestId}-prefix`, children: prefix })), children && typeof children !== "string" ? (children) : (jsxRuntime.jsx("div", { className: cvaMenuItemLabel(), "data-testid": dataTestId && `${dataTestId}-label`, children: children !== null && children !== void 0 ? children : label })), suffix && (jsxRuntime.jsx("div", { className: cvaMenuItemSuffix({ selected }), "data-testid": dataTestId && `${dataTestId}-suffix`, children: suffix }))] })));
18930
19013
  };
18931
- const Container$1 = tailwindStyledComponents.tw.div `
18932
- group tu-menu-item
18933
- ${({ $selected }) => ($selected ? tailwindStyledComponents.tws `tu-menu-item--selected ` : "tu-menu-item--unselected")}
18934
- ${({ $focused }) => ($focused ? tailwindStyledComponents.tws `tu-menu-item--focused ` : "")}
18935
- ${({ $disabled }) => ($disabled ? tailwindStyledComponents.tws `tu-menu-item--disabled ` : "")}
18936
19014
 
18937
- `;
18938
- const Suffix = tailwindStyledComponents.tw.div `
18939
- tu-menu-item__suffix
18940
- ${({ $selected }) => ($selected ? tailwindStyledComponents.tws `tu-menu-item__suffix--selected ` : "")}
18941
- `;
18942
- const Prefix = tailwindStyledComponents.tw.div `
18943
- tu-menu-item__prefix
18944
- ${({ $selected }) => ($selected ? tailwindStyledComponents.tws `tu-menu-item__prefix--selected ` : "")}
18945
- `;
18946
- const Label = tailwindStyledComponents.tw.div `
18947
- tu-menu-item__label
18948
- `;
19015
+ const cvaMenuList = cssClassVarianceUtilities.cvaMerge(["shadow-md", "rounded-lg", "z-20", "bg-white", "p-1", "border", "border-slate-300", "gap-1", "grid"], {
19016
+ variants: {
19017
+ stickyHeader: {
19018
+ true: "grid grid-rows-min-fr !p-0",
19019
+ false: "",
19020
+ },
19021
+ },
19022
+ });
19023
+ const cvaMenuListDivider = cssClassVarianceUtilities.cvaMerge([
19024
+ "file:w-full",
19025
+ "selection:col-span-full",
19026
+ "selection:h-px",
19027
+ "selection:bg-slate-200",
19028
+ ]);
18949
19029
 
18950
19030
  /**
18951
19031
  * The MenuList component is used for menus and selects.
@@ -18957,211 +19037,62 @@ const MenuList = (_a) => {
18957
19037
  var { dataTestId, className, children, withStickyHeader = false, showDivider = false } = _a; __rest$1(_a, ["dataTestId", "className", "children", "withStickyHeader", "showDivider"]);
18958
19038
  const childrenArr = React.Children.toArray(children);
18959
19039
  const isLastItem = (index) => index === childrenArr.length - 1;
18960
- return (jsxRuntime.jsx(Container, { "$withStickyHeader": withStickyHeader, "data-testid": dataTestId, className: className, tabIndex: 0, role: "list", children: React.Children.map(childrenArr, (menuItem, index) => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [menuItem, showDivider && !isLastItem(index) && jsxRuntime.jsx(Divider, {})] }))) }));
19040
+ return (jsxRuntime.jsx("div", { "data-testid": dataTestId, className: cvaMenuList({ stickyHeader: withStickyHeader, className }), tabIndex: 0, role: "list", children: React.Children.map(childrenArr, (menuItem, index) => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [menuItem, showDivider && !isLastItem(index) && jsxRuntime.jsx("hr", { className: cvaMenuListDivider() })] }))) }));
18961
19041
  };
18962
- const Container = tailwindStyledComponents.tw.div `
18963
- tu-menu-list
18964
- ${props => props.$withStickyHeader && tailwindStyledComponents.tws `tu-menu-list--sticky-header`}
18965
- `;
18966
- const Divider = tailwindStyledComponents.tw.hr `
18967
- tu-menu-list__divider
18968
- `;
18969
19042
 
18970
19043
  /**
18971
- * Custom hook to handle click outside events.
19044
+ * Validates the state object using a Zod schema.
18972
19045
  *
18973
- * @param {RefObject<HTMLElement> | Array<RefObject<HTMLElement>>} el - The reference(s) to the element(s) to watch for click outside events.
18974
- * @param {Object | Function} [options={}] - Configuration options or the click event handler if options are not provided.
18975
- * @param {boolean} [options.active=true] - Whether the click outside event listener should be active.
18976
- * @param {Function} [onClick] - The event handler for the click outside event.
19046
+ * @template State - The type of the state.
19047
+ * @param {ValidateStateOptions<State>} params - The parameters for the validateState function.
19048
+ * @returns {boolean} - `true` if the state is valid according to the schema, `false` otherwise.
18977
19049
  */
18978
- const useClickOutside = (el, options = {}, onClick) => {
18979
- const els = Array.isArray(el) ? el : [el];
18980
- let active = true;
18981
- if (!onClick && typeof options === "function") {
18982
- onClick = options;
18983
- }
18984
- else if (typeof options === "object") {
18985
- active = options.active !== undefined ? options.active : active;
18986
- }
18987
- const handler = (ev) => {
18988
- const target = ev.target;
18989
- if (els.every(ref => !ref.current || !ref.current.contains(target))) {
18990
- onClick && onClick(ev);
18991
- }
18992
- };
18993
- const cleanup = () => window.removeEventListener("click", handler);
18994
- React.useEffect(() => {
18995
- if (active) {
18996
- window.addEventListener("click", handler);
18997
- }
18998
- else {
18999
- cleanup();
19000
- }
19001
- return cleanup;
19002
- });
19003
- };
19004
-
19005
- /**
19006
- * The useDebounce hook works like useState, but adds a delay where previous values will be ignored.
19007
- *
19008
- * @param {any} value The value to set
19009
- * @param {number} delay The debounce time
19010
- * @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.
19011
- * @returns {any} The latest value received
19012
- */
19013
- /**
19014
- *
19015
- */
19016
- const useDebounce = (value, delay = 500, direction) => {
19017
- const [debouncedValue, setDebouncedValue] = React.useState(value);
19018
- React.useEffect(() => {
19019
- let handler;
19020
- if ((direction === "in" && !debouncedValue && value) ||
19021
- (direction === "out" && debouncedValue && !value) ||
19022
- direction === "both" ||
19023
- !direction) {
19024
- handler = setTimeout(() => {
19025
- setDebouncedValue(value);
19026
- }, delay);
19050
+ const validateState = ({ state, schema, onValidationFailed, onValidationSuccessful, }) => {
19051
+ try {
19052
+ schema.parse(state);
19053
+ if (onValidationSuccessful) {
19054
+ onValidationSuccessful();
19027
19055
  }
19028
- else {
19029
- setDebouncedValue(value);
19056
+ return true;
19057
+ }
19058
+ catch (error) {
19059
+ // eslint-disable-next-line no-console
19060
+ console.error("State validation failed.", error);
19061
+ if (onValidationFailed) {
19062
+ onValidationFailed(error);
19030
19063
  }
19031
- return () => {
19032
- clearTimeout(handler);
19033
- };
19034
- }, [value, delay, direction, debouncedValue]);
19035
- return debouncedValue;
19064
+ return false;
19065
+ }
19036
19066
  };
19037
19067
 
19038
- // Copied from https://github.com/rexxars/use-device-pixel-ratio
19039
19068
  /**
19040
- * Get the device pixel ratio, potentially rounded and capped.
19041
- * Will emit new values if it changes.
19069
+ * Initializes the state from local storage, parsing and validating it if a Zod schema is provided.
19042
19070
  *
19043
- * @param {DevicePixelRatioOptions} options the options object
19044
- * @returns { number } The current device pixel ratio, or the default if none can be resolved
19071
+ * @template State - The type of the state stored in local storage.
19072
+ * @param {Omit<LocalStorageParams<State>, "state">} params - The parameters for initializing the local storage state.
19073
+ * @returns {State} - The initialized state.
19045
19074
  */
19046
- function useDevicePixelRatio(options) {
19047
- const dpr = getDevicePixelRatio(options);
19048
- const [currentDpr, setCurrentDpr] = React.useState(dpr);
19049
- const { defaultDpr, maxDpr, round } = options || {};
19050
- React.useEffect(() => {
19051
- const canListen = typeof window !== "undefined" && "matchMedia" in window;
19052
- if (!canListen) {
19053
- return;
19054
- }
19055
- const updateDpr = () => setCurrentDpr(getDevicePixelRatio({ defaultDpr, maxDpr, round }));
19056
- const mediaMatcher = window.matchMedia(`screen and (resolution: ${currentDpr}dppx)`);
19057
- // Safari 13.1 does not have `addEventListener`, but does have `addListener`
19058
- if (mediaMatcher.addEventListener) {
19059
- mediaMatcher.addEventListener("change", updateDpr);
19060
- }
19061
- else {
19062
- mediaMatcher.addListener(updateDpr);
19063
- }
19064
- return () => {
19065
- if (mediaMatcher.removeEventListener) {
19066
- mediaMatcher.removeEventListener("change", updateDpr);
19075
+ const initLocalStorageState = ({ key, defaultState, schema, }) => {
19076
+ const localStorageItem = localStorage.getItem(key);
19077
+ if (localStorageItem) {
19078
+ try {
19079
+ const parsedState = JSON.parse(localStorageItem);
19080
+ if (schema) {
19081
+ if (validateState({ state: parsedState, schema })) {
19082
+ return parsedState;
19083
+ }
19067
19084
  }
19068
19085
  else {
19069
- mediaMatcher.removeListener(updateDpr);
19086
+ return parsedState;
19070
19087
  }
19071
- };
19072
- }, [currentDpr, defaultDpr, maxDpr, round]);
19073
- return currentDpr;
19074
- }
19075
- /**
19076
- * Returns the current device pixel ratio (DPR) given the passed options
19077
- *
19078
- * @param { DevicePixelRatioOptions } options the options object
19079
- * @returns { number } current device pixel ratio
19080
- */
19081
- function getDevicePixelRatio(options) {
19082
- const { defaultDpr = 1, maxDpr = 3, round = true } = options || {};
19083
- const hasDprProp = typeof window !== "undefined" && typeof window.devicePixelRatio === "number";
19084
- const dpr = hasDprProp ? window.devicePixelRatio : defaultDpr;
19085
- const rounded = Math.min(Math.max(1, round ? Math.floor(dpr) : dpr), maxDpr);
19086
- return rounded;
19087
- }
19088
-
19089
- /**
19090
- * The useHover hook returns a onMouseEnter, onMouseLeave and a boolean indicating whether the element is being hovered.
19091
- * The boolean will be true if the element is being hovered, and false if it is not.
19092
- * Can be directionally debounced.
19093
- *
19094
- * @param {ConditionalUseHoverProps} param0 The options
19095
- * @returns {Object} The object containing the onMouseEnter, onMouseLeave and hovering props
19096
- */
19097
- const useHover = ({ debounced = false, delay = 100, direction = "out" } = { debounced: false }) => {
19098
- const [hovering, setHovering] = React.useState(false);
19099
- const debouncedHovering = useDebounce(hovering, delay, direction);
19100
- const onMouseEnter = () => {
19101
- setHovering(true);
19102
- };
19103
- const onMouseLeave = () => {
19104
- setHovering(false);
19105
- };
19106
- return { onMouseEnter, onMouseLeave, hovering: debounced ? debouncedHovering : hovering };
19107
- };
19108
-
19109
- /**
19110
- * Differentiate between the first and subsequent renders.
19111
- *
19112
- * @returns {boolean} Returns true if it is the first render, false otherwise.
19113
- */
19114
- const useIsFirstRender = () => {
19115
- const renderRef = React.useRef(true);
19116
- if (renderRef.current === true) {
19117
- renderRef.current = false;
19118
- return true;
19119
- }
19120
- return renderRef.current;
19121
- };
19122
-
19123
- /**
19124
- * The useLocalStorage hook works like useState, but saves to localstorage.
19125
- *
19126
- * @param {string} key - The key the values is saved under.
19127
- * @param {string | null | undefined} initialValue - The initial value for the state/local storage item
19128
- * @returns {string | null | undefined} value
19129
- */
19130
- const useLocalStorage = (key, initialValue) => {
19131
- if (!key) {
19132
- throw new Error("useLocalStorage key may not be falsy");
19133
- }
19134
- const setLocalStorageItem = React.useCallback((value) => window.localStorage.setItem(key, typeof value === "object" ? JSON.stringify(value) : `${value}`), [key]);
19135
- const removeLocalStorageItem = React.useCallback(() => window.localStorage.removeItem(key), [key]);
19136
- const [item, setValue] = React.useState(() => {
19137
- const value = window.localStorage.getItem(key);
19138
- if (value !== undefined && value !== null) {
19139
- const parsedValue = tryParse(value);
19140
- return parsedValue;
19141
- }
19142
- return initialValue !== null && initialValue !== void 0 ? initialValue : null;
19143
- });
19144
- React.useEffect(() => {
19145
- if (item !== undefined && item !== null) {
19146
- setLocalStorageItem(item);
19147
19088
  }
19148
- else {
19149
- removeLocalStorageItem();
19089
+ catch (error) {
19090
+ // eslint-disable-next-line no-console
19091
+ console.error("Failed to parse and validate the state from local storage.", error);
19150
19092
  }
19151
- }, [item, setLocalStorageItem, removeLocalStorageItem, key]);
19152
- const removeItem = () => {
19153
- setValue(null);
19154
- };
19155
- return [item, setValue, removeItem];
19156
- };
19157
- function tryParse(value) {
19158
- try {
19159
- return JSON.parse(value);
19160
19093
  }
19161
- catch (_a) {
19162
- return value;
19163
- }
19164
- }
19094
+ return defaultState;
19095
+ };
19165
19096
 
19166
19097
  var lodash = {exports: {}};
19167
19098
 
@@ -36365,47 +36296,229 @@ var lodash = {exports: {}};
36365
36296
  }(lodash, lodash.exports));
36366
36297
 
36367
36298
  /**
36368
- * Given any value this hook will return the previous value whenever the current value changes
36299
+ * Custom hook for synchronizing a state variable with local storage,
36300
+ * with optional schema validation and callbacks.
36301
+ *
36302
+ * @template State - The type of the state variable.
36303
+ * @param {LocalStorageParams<State> & LocalStorageCallbacks & { state: State }} params - The parameters for the useLocalStorageEffect hook.
36369
36304
  */
36370
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
36371
- function usePrevious(value) {
36372
- const ref = React.useRef();
36305
+ const useLocalStorageEffect = ({ key, state, defaultState, schema, onValidationFailed, onValidationSuccessful, }) => {
36306
+ const prevState = usePrevious(state);
36373
36307
  React.useEffect(() => {
36374
- ref.current = value;
36308
+ if (lodash.exports.isEqual(prevState, state)) {
36309
+ return;
36310
+ }
36311
+ if (schema) {
36312
+ validateState({
36313
+ state,
36314
+ schema,
36315
+ onValidationFailed: error => {
36316
+ // eslint-disable-next-line no-console
36317
+ console.error(`State validation failed. Resetting local storage to default state: ${defaultState}.`, error);
36318
+ localStorage.removeItem(key);
36319
+ onValidationFailed === null || onValidationFailed === void 0 ? void 0 : onValidationFailed(error);
36320
+ },
36321
+ onValidationSuccessful: () => {
36322
+ const stringifiedState = JSON.stringify(state);
36323
+ localStorage.setItem(key, stringifiedState);
36324
+ onValidationSuccessful === null || onValidationSuccessful === void 0 ? void 0 : onValidationSuccessful();
36325
+ },
36326
+ });
36327
+ }
36328
+ else {
36329
+ const stringifiedState = JSON.stringify(state);
36330
+ localStorage.setItem(key, stringifiedState);
36331
+ }
36332
+ }, [state, key, schema, defaultState, prevState, onValidationFailed, onValidationSuccessful]);
36333
+ };
36334
+
36335
+ /**
36336
+ * Works like a normal useState, but saves to local storage and has optional schema validation.
36337
+ *
36338
+ * @template State - The type of the value stored in local storage.
36339
+ * @param {Omit<LocalStorageParams<State>, "state"> & LocalStorageCallbacks} options - The options for useLocalStorage.
36340
+ * @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.
36341
+ */
36342
+ const useLocalStorage = ({ key, defaultState, schema, onValidationFailed, onValidationSuccessful, }) => {
36343
+ if (!key) {
36344
+ throw new Error("useLocalStorage key may not be falsy");
36345
+ }
36346
+ const [state, setState] = React.useState(initLocalStorageState({ key, defaultState, schema }));
36347
+ useLocalStorageEffect({ key, state, defaultState, schema, onValidationFailed, onValidationSuccessful });
36348
+ const reset = () => {
36349
+ setState(defaultState);
36350
+ };
36351
+ return [state, setState, reset];
36352
+ };
36353
+
36354
+ /**
36355
+ * Works like a normal useReducer, but saves to local storage and has optional schema validation.
36356
+ *
36357
+ * @template State - The type of the state.
36358
+ * @template Action - The type of the action.
36359
+ * @param {LocalStorageParams<State> & LocalStorageCallbacks} params - The parameters for the useLocalStorageReducer function.
36360
+ * @returns {[State, Dispatch<Action>]} - A tuple containing the state and the dispatch function.
36361
+ */
36362
+ const useLocalStorageReducer = ({ key, defaultState, reducer, schema, onValidationFailed, onValidationSuccessful, }) => {
36363
+ if (!key) {
36364
+ throw new Error("useLocalStorage key may not be falsy");
36365
+ }
36366
+ const [state, dispatch] = React.useReducer(reducer, defaultState, () => initLocalStorageState({ key, defaultState, schema }));
36367
+ useLocalStorageEffect({ key, state, defaultState, schema, onValidationFailed, onValidationSuccessful });
36368
+ return [state, dispatch];
36369
+ };
36370
+
36371
+ /**
36372
+ * Custom hook to handle click outside events.
36373
+ *
36374
+ * @param {RefObject<HTMLElement> | Array<RefObject<HTMLElement>>} el - The reference(s) to the element(s) to watch for click outside events.
36375
+ * @param {Object | Function} [options={}] - Configuration options or the click event handler if options are not provided.
36376
+ * @param {boolean} [options.active=true] - Whether the click outside event listener should be active.
36377
+ * @param {Function} [onClick] - The event handler for the click outside event.
36378
+ */
36379
+ const useClickOutside = (el, options = {}, onClick) => {
36380
+ const els = Array.isArray(el) ? el : [el];
36381
+ let active = true;
36382
+ if (!onClick && typeof options === "function") {
36383
+ onClick = options;
36384
+ }
36385
+ else if (typeof options === "object") {
36386
+ active = options.active !== undefined ? options.active : active;
36387
+ }
36388
+ const handler = (ev) => {
36389
+ const target = ev.target;
36390
+ if (els.every(ref => !ref.current || !ref.current.contains(target))) {
36391
+ onClick && onClick(ev);
36392
+ }
36393
+ };
36394
+ const cleanup = () => window.removeEventListener("click", handler);
36395
+ React.useEffect(() => {
36396
+ if (active) {
36397
+ window.addEventListener("click", handler);
36398
+ }
36399
+ else {
36400
+ cleanup();
36401
+ }
36402
+ return cleanup;
36375
36403
  });
36376
- return ref.current;
36377
- }
36404
+ };
36405
+
36406
+ /**
36407
+ * The useDebounce hook works like useState, but adds a delay where previous values will be ignored.
36408
+ *
36409
+ * @param {any} value The value to set
36410
+ * @param {number} delay The debounce time
36411
+ * @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.
36412
+ * @returns {any} The latest value received
36413
+ */
36378
36414
  /**
36379
- * Wraps a normal useReducer with local storage functionality
36380
36415
  *
36381
- * @param {string} key - The key the state is saved with in local storage.
36382
36416
  */
36383
- const useLocalStorageReducer = (key, reducer, initialState
36384
- //return must be explicitly typed atm for some reason
36385
- ) => {
36386
- const init = () => {
36387
- const stringState = localStorage.getItem(key);
36388
- if (stringState) {
36389
- try {
36390
- return JSON.parse(stringState);
36391
- }
36392
- catch (error) {
36393
- return initialState;
36394
- }
36417
+ const useDebounce = (value, delay = 500, direction) => {
36418
+ const [debouncedValue, setDebouncedValue] = React.useState(value);
36419
+ React.useEffect(() => {
36420
+ let handler;
36421
+ if ((direction === "in" && !debouncedValue && value) ||
36422
+ (direction === "out" && debouncedValue && !value) ||
36423
+ direction === "both" ||
36424
+ !direction) {
36425
+ handler = setTimeout(() => {
36426
+ setDebouncedValue(value);
36427
+ }, delay);
36395
36428
  }
36396
36429
  else {
36397
- return initialState;
36430
+ setDebouncedValue(value);
36398
36431
  }
36399
- };
36400
- const [state, dispatch] = React.useReducer(reducer, initialState, init);
36401
- const prevState = usePrevious(state);
36432
+ return () => {
36433
+ clearTimeout(handler);
36434
+ };
36435
+ }, [value, delay, direction, debouncedValue]);
36436
+ return debouncedValue;
36437
+ };
36438
+
36439
+ // Copied from https://github.com/rexxars/use-device-pixel-ratio
36440
+ /**
36441
+ * Get the device pixel ratio, potentially rounded and capped.
36442
+ * Will emit new values if it changes.
36443
+ *
36444
+ * @param {DevicePixelRatioOptions} options the options object
36445
+ * @returns { number } The current device pixel ratio, or the default if none can be resolved
36446
+ */
36447
+ function useDevicePixelRatio(options) {
36448
+ const dpr = getDevicePixelRatio(options);
36449
+ const [currentDpr, setCurrentDpr] = React.useState(dpr);
36450
+ const { defaultDpr, maxDpr, round } = options || {};
36402
36451
  React.useEffect(() => {
36403
- if (!lodash.exports.isEqual(prevState, state)) {
36404
- const stringifiedState = JSON.stringify(state);
36405
- localStorage.setItem(key, stringifiedState);
36452
+ const canListen = typeof window !== "undefined" && "matchMedia" in window;
36453
+ if (!canListen) {
36454
+ return;
36406
36455
  }
36407
- }, [prevState, state, key]);
36408
- return [state, dispatch];
36456
+ const updateDpr = () => setCurrentDpr(getDevicePixelRatio({ defaultDpr, maxDpr, round }));
36457
+ const mediaMatcher = window.matchMedia(`screen and (resolution: ${currentDpr}dppx)`);
36458
+ // Safari 13.1 does not have `addEventListener`, but does have `addListener`
36459
+ if (mediaMatcher.addEventListener) {
36460
+ mediaMatcher.addEventListener("change", updateDpr);
36461
+ }
36462
+ else {
36463
+ mediaMatcher.addListener(updateDpr);
36464
+ }
36465
+ return () => {
36466
+ if (mediaMatcher.removeEventListener) {
36467
+ mediaMatcher.removeEventListener("change", updateDpr);
36468
+ }
36469
+ else {
36470
+ mediaMatcher.removeListener(updateDpr);
36471
+ }
36472
+ };
36473
+ }, [currentDpr, defaultDpr, maxDpr, round]);
36474
+ return currentDpr;
36475
+ }
36476
+ /**
36477
+ * Returns the current device pixel ratio (DPR) given the passed options
36478
+ *
36479
+ * @param { DevicePixelRatioOptions } options the options object
36480
+ * @returns { number } current device pixel ratio
36481
+ */
36482
+ function getDevicePixelRatio(options) {
36483
+ const { defaultDpr = 1, maxDpr = 3, round = true } = options || {};
36484
+ const hasDprProp = typeof window !== "undefined" && typeof window.devicePixelRatio === "number";
36485
+ const dpr = hasDprProp ? window.devicePixelRatio : defaultDpr;
36486
+ const rounded = Math.min(Math.max(1, round ? Math.floor(dpr) : dpr), maxDpr);
36487
+ return rounded;
36488
+ }
36489
+
36490
+ /**
36491
+ * The useHover hook returns a onMouseEnter, onMouseLeave and a boolean indicating whether the element is being hovered.
36492
+ * The boolean will be true if the element is being hovered, and false if it is not.
36493
+ * Can be directionally debounced.
36494
+ *
36495
+ * @param {ConditionalUseHoverProps} param0 The options
36496
+ * @returns {Object} The object containing the onMouseEnter, onMouseLeave and hovering props
36497
+ */
36498
+ const useHover = ({ debounced = false, delay = 100, direction = "out" } = { debounced: false }) => {
36499
+ const [hovering, setHovering] = React.useState(false);
36500
+ const debouncedHovering = useDebounce(hovering, delay, direction);
36501
+ const onMouseEnter = () => {
36502
+ setHovering(true);
36503
+ };
36504
+ const onMouseLeave = () => {
36505
+ setHovering(false);
36506
+ };
36507
+ return { onMouseEnter, onMouseLeave, hovering: debounced ? debouncedHovering : hovering };
36508
+ };
36509
+
36510
+ /**
36511
+ * Differentiate between the first and subsequent renders.
36512
+ *
36513
+ * @returns {boolean} Returns true if it is the first render, false otherwise.
36514
+ */
36515
+ const useIsFirstRender = () => {
36516
+ const renderRef = React.useRef(true);
36517
+ if (renderRef.current === true) {
36518
+ renderRef.current = false;
36519
+ return true;
36520
+ }
36521
+ return renderRef.current;
36409
36522
  };
36410
36523
 
36411
36524
  /**
@@ -36477,6 +36590,8 @@ const useTimeout = ({ onTimeout, duration }) => {
36477
36590
  return [isReady, stopTimeout, startTimeout];
36478
36591
  };
36479
36592
 
36593
+ const cvaMoreMenu = cssClassVarianceUtilities.cvaMerge(["p-4"]);
36594
+
36480
36595
  /**
36481
36596
  * A kebab menu component.
36482
36597
  * Advice: fill it with MenuList
@@ -36498,14 +36613,41 @@ const MoreMenu = ({ className, dataTestId, menuPlacement, iconProps = {
36498
36613
  useClickOutside(actionMenuRef, () => {
36499
36614
  setActionMenuIsOpen(false);
36500
36615
  });
36501
- return (jsxRuntime.jsx(Root, { ref: actionMenuRef, className: className, "data-testid": dataTestId, children: jsxRuntime.jsxs(Popover, { placement: menuPlacement, children: [jsxRuntime.jsx(PopoverTrigger, { children: customButton !== null && customButton !== void 0 ? customButton : (jsxRuntime.jsx(IconButton, Object.assign({}, iconButtonProps, { icon: iconProps.name ? jsxRuntime.jsx(Icon, Object.assign({}, iconProps, { name: iconProps.name })) : null, onClick: () => {
36616
+ return (jsxRuntime.jsx("div", { ref: actionMenuRef, className: cvaMoreMenu({ className }), "data-testid": dataTestId, children: jsxRuntime.jsxs(Popover, { placement: menuPlacement, children: [jsxRuntime.jsx(PopoverTrigger, { children: customButton !== null && customButton !== void 0 ? customButton : (jsxRuntime.jsx(IconButton, Object.assign({}, iconButtonProps, { icon: iconProps.name ? jsxRuntime.jsx(Icon, Object.assign({}, iconProps, { name: iconProps.name })) : null, onClick: () => {
36502
36617
  setActionMenuIsOpen(!actionMenuIsOpen);
36503
36618
  } }))) }), jsxRuntime.jsx(PopoverContent, { children: close => (typeof children === "function" ? children(close) : children) })] }) }));
36504
36619
  };
36505
- const Root = tailwindStyledComponents.tw.div `
36506
- tu-more-menu
36507
- ${({ variant }) => (variant === "primary" ? tailwindStyledComponents.tws `tu-more-menu--primary` : "")}
36508
- `;
36620
+
36621
+ const cvaModalContainer = cssClassVarianceUtilities.cvaMerge(["h-full", "w-full", "flex", "items-center"]);
36622
+ const cvaModalCard = cssClassVarianceUtilities.cvaMerge([
36623
+ "m-auto",
36624
+ "shadow-2xl",
36625
+ "w-[calc(100%-1rem)]",
36626
+ "max-h-[min(100%-1rem,30%+600px)]",
36627
+ "max-w-[min(100%-1rem,30%+600px)]",
36628
+ ]);
36629
+ const cvaModalBackdrop = cssClassVarianceUtilities.cvaMerge([
36630
+ "flex",
36631
+ "justify-center",
36632
+ "items-center",
36633
+ "fixed",
36634
+ "top-0",
36635
+ "left-0",
36636
+ "w-full",
36637
+ "h-full",
36638
+ "bg-black/50",
36639
+ "z-20",
36640
+ "invisible",
36641
+ "opacity-0",
36642
+ "transition-all",
36643
+ ], {
36644
+ variants: {
36645
+ show: {
36646
+ true: "animate-modal-fade",
36647
+ false: "",
36648
+ },
36649
+ },
36650
+ });
36509
36651
 
36510
36652
  /**
36511
36653
  * The Backdrop for the modal.
@@ -36515,7 +36657,7 @@ const Root = tailwindStyledComponents.tw.div `
36515
36657
  */
36516
36658
  const ModalBackdrop = ({ children, isVisible, onClick }) => {
36517
36659
  const ref = React__namespace.useRef(null);
36518
- return (jsxRuntime.jsx(Backdrop, { ref: ref, "$isVisible": isVisible, onMouseDown: e => {
36660
+ return (jsxRuntime.jsx("div", { ref: ref, className: cvaModalBackdrop({ show: isVisible }), onMouseDown: e => {
36519
36661
  var _a, _b;
36520
36662
  // only react on click on backdrop - accessing first child to get around styled element element
36521
36663
  if (ref.current !== null) {
@@ -36525,12 +36667,6 @@ const ModalBackdrop = ({ children, isVisible, onClick }) => {
36525
36667
  }
36526
36668
  }, children: children }));
36527
36669
  };
36528
- const Backdrop = tailwindStyledComponents.tw.div `
36529
- tu-modal-backdrop
36530
- ${({ $isVisible }) => {
36531
- return $isVisible && tailwindStyledComponents.tws `tu-modal-backdrop--show`;
36532
- }}
36533
- `;
36534
36670
 
36535
36671
  /**
36536
36672
  * - Modals are used to present critical information or request user input needed to complete a user's workflow.
@@ -36540,14 +36676,8 @@ const Backdrop = tailwindStyledComponents.tw.div `
36540
36676
  const Modal = React__namespace.forwardRef(({ isOpen, children, dataTestId, className, role = "", onDismissClick = () => {
36541
36677
  return;
36542
36678
  }, }, ref) => {
36543
- return (jsxRuntime.jsx(ModalBackdrop, { isVisible: isOpen, onClick: onDismissClick, children: isOpen && (jsxRuntime.jsx(StyledDiv, { ref: ref, role: role, children: jsxRuntime.jsx(StyledCard, { dataTestId: dataTestId, className: className, children: children }) })) }));
36679
+ return (jsxRuntime.jsx(ModalBackdrop, { isVisible: isOpen, onClick: onDismissClick, children: isOpen && (jsxRuntime.jsx("div", { className: cvaModalContainer(), ref: ref, role: role, children: jsxRuntime.jsx(Card, { dataTestId: dataTestId, className: cvaModalCard({ className }), children: children }) })) }));
36544
36680
  });
36545
- const StyledCard = tailwindStyledComponents.twx(Card) `
36546
- tu-modal__card
36547
- `;
36548
- const StyledDiv = tailwindStyledComponents.tw.div `
36549
- tu-modal__container
36550
- `;
36551
36681
 
36552
36682
  var usePortal$1 = {};
36553
36683
 
@@ -39124,6 +39254,10 @@ exports.cvaIndicatorIcon = cvaIndicatorIcon;
39124
39254
  exports.cvaIndicatorIconBackground = cvaIndicatorIconBackground;
39125
39255
  exports.cvaIndicatorLabel = cvaIndicatorLabel;
39126
39256
  exports.cvaIndicatorPing = cvaIndicatorPing;
39257
+ exports.cvaMenuItem = cvaMenuItem;
39258
+ exports.cvaMenuItemLabel = cvaMenuItemLabel;
39259
+ exports.cvaMenuItemPrefix = cvaMenuItemPrefix;
39260
+ exports.cvaMenuItemSuffix = cvaMenuItemSuffix;
39127
39261
  exports.docs = docs;
39128
39262
  exports.getDevicePixelRatio = getDevicePixelRatio;
39129
39263
  exports.getValueBarColorByValue = getValueBarColorByValue;