@trackunit/react-components 0.5.19 → 0.5.21

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.js CHANGED
@@ -993,6 +993,99 @@ const useClickOutside = (el, options = {}, onClick) => {
993
993
  });
994
994
  };
995
995
 
996
+ /**
997
+ * Maps size keys to their corresponding state property names.
998
+ */
999
+ const breakpointPropsMap = {
1000
+ xs: "isXs",
1001
+ sm: "isSm",
1002
+ md: "isMd",
1003
+ lg: "isLg",
1004
+ xl: "isXl",
1005
+ "2xl": "is2xl",
1006
+ "3xl": "is3xl",
1007
+ };
1008
+ /**
1009
+ * The default state for sizes, with all values set to false.
1010
+ */
1011
+ const defaultBreakpointState = {
1012
+ isXs: false,
1013
+ isSm: false,
1014
+ isMd: false,
1015
+ isLg: false,
1016
+ isXl: false,
1017
+ is2xl: false,
1018
+ is3xl: false,
1019
+ };
1020
+ /**
1021
+ * Creates a breakpoint state object based on a width value
1022
+ */
1023
+ const createBreakpointState = ({ width }) => {
1024
+ return sharedUtils.objectEntries(uiDesignTokens.themeScreenSizeAsNumber).reduce((acc, [size, minWidth]) => ({
1025
+ ...acc,
1026
+ [breakpointPropsMap[size]]: width >= minWidth,
1027
+ }), { ...defaultBreakpointState });
1028
+ };
1029
+
1030
+ /**
1031
+ * A custom React hook that provides real-time information about a container's size.
1032
+ *
1033
+ * This hook uses ResizeObserver to monitor changes in the container's width and returns
1034
+ * an object with boolean values indicating which breakpoints are currently active.
1035
+ *
1036
+ * @param {RefObject<HTMLElement>} ref - Reference to the container element to observe
1037
+ * @returns {BreakpointState} An object containing boolean values for each container size breakpoint.
1038
+ * @example
1039
+ * const MyComponent = () => {
1040
+ * const containerRef = useRef<HTMLDivElement>(null);
1041
+ * const containerSize = useContainerBreakpoints(containerRef);
1042
+ *
1043
+ * return (
1044
+ * <div ref={containerRef}>
1045
+ * {containerSize.isLg ? (
1046
+ * <LargeLayout />
1047
+ * ) : containerSize.isMd ? (
1048
+ * <MediumLayout />
1049
+ * ) : (
1050
+ * <SmallLayout />
1051
+ * )}
1052
+ * </div>
1053
+ * );
1054
+ * }
1055
+ */
1056
+ const useContainerBreakpoints = (ref) => {
1057
+ const [containerSize, setContainerSize] = React.useState(() => defaultBreakpointState);
1058
+ React.useEffect(() => {
1059
+ if (process.env.NODE_ENV === "development" && !ref.current) {
1060
+ // eslint-disable-next-line no-console
1061
+ console.warn("useContainerBreakpoints: The provided ref is not attached to any element. " +
1062
+ "Make sure to pass the ref to an element using the ref prop, like: <div ref={myRef}>", "\nComponent:", ref.current);
1063
+ }
1064
+ }, [ref]);
1065
+ const updateContainerSize = React.useCallback(() => {
1066
+ if (!ref.current) {
1067
+ return;
1068
+ }
1069
+ const width = ref.current.getBoundingClientRect().width;
1070
+ setContainerSize(createBreakpointState({ width }));
1071
+ }, [ref]);
1072
+ React.useEffect(() => {
1073
+ const element = ref.current;
1074
+ if (!element) {
1075
+ return;
1076
+ }
1077
+ // Initial check
1078
+ updateContainerSize();
1079
+ // Set up ResizeObserver
1080
+ const resizeObserver = new ResizeObserver(updateContainerSize);
1081
+ resizeObserver.observe(element);
1082
+ return () => {
1083
+ resizeObserver.disconnect();
1084
+ };
1085
+ }, [updateContainerSize, ref]);
1086
+ return containerSize;
1087
+ };
1088
+
996
1089
  /**
997
1090
  * Hook for managing timeouts.
998
1091
  *
@@ -1384,61 +1477,32 @@ const useSelfUpdatingRef = (initialState) => {
1384
1477
  return stateRef;
1385
1478
  };
1386
1479
 
1387
- /**
1388
- * Maps viewport size keys to their corresponding state property names.
1389
- */
1390
- const propsMap = {
1391
- xs: "isXs",
1392
- sm: "isSm",
1393
- md: "isMd",
1394
- lg: "isLg",
1395
- xl: "isXl",
1396
- "2xl": "is2xl",
1397
- "3xl": "is3xl",
1398
- };
1399
- /**
1400
- * The default state for viewport sizes, with all values set to false.
1401
- */
1402
- const defaultState = {
1403
- isXs: false,
1404
- isSm: false,
1405
- isMd: false,
1406
- isLg: false,
1407
- isXl: false,
1408
- is2xl: false,
1409
- is3xl: false,
1410
- };
1411
1480
  /**
1412
1481
  * A custom React hook that provides real-time information about the current viewport size.
1482
+ * ! Consider using `useContainerBreakpoints` instead, and only use this when you need to actually react to the viewport size, not the container size.
1413
1483
  *
1414
1484
  * This hook listens to changes in the viewport size and returns an object with boolean values
1415
- * indicating which breakpoints are currently active. It's useful for creating responsive
1416
- * layouts and components that need to adapt to different screen sizes.
1485
+ * indicating which breakpoints are currently active.
1417
1486
  *
1418
- * @returns {ViewportSizeState} An object containing boolean values for each viewport size breakpoint.
1487
+ * @returns {BreakpointState} An object containing boolean values for each viewport size breakpoint.
1419
1488
  * @example
1420
- * function MyComponent() {
1421
- * const viewportSize = useViewportSize();
1489
+ * const MyComponent = () => {
1490
+ * const viewportSize = useViewportBreakpoints();
1422
1491
  *
1423
1492
  * if (viewportSize.isLg) {
1424
1493
  * return <LargeScreenLayout />;
1425
- * } else if (viewportSize.isMd) {
1426
- * return <MediumScreenLayout />;
1427
- * } else {
1428
- * return <SmallScreenLayout />;
1429
1494
  * }
1495
+ *
1496
+ * return viewportSize.isMd ? <MediumScreenLayout /> : <SmallLayout />;
1430
1497
  * }
1431
1498
  */
1432
- const useViewportSize = () => {
1433
- const [viewportSize, setViewportSize] = React.useState(() => defaultState);
1499
+ const useViewportBreakpoints = () => {
1500
+ const [viewportSize, setViewportSize] = React.useState(() => defaultBreakpointState);
1434
1501
  const updateViewportSize = React.useCallback(() => {
1435
- const newViewportSize = sharedUtils.objectEntries(uiDesignTokens.themeScreenSizeAsNumber).reduce((acc, [size, minWidth]) => {
1436
- const matches = window.matchMedia(`(min-width: ${minWidth}px)`).matches;
1437
- return {
1438
- ...acc,
1439
- [propsMap[size]]: matches,
1440
- };
1441
- }, Object.assign({}, defaultState));
1502
+ const newViewportSize = sharedUtils.objectEntries(uiDesignTokens.themeScreenSizeAsNumber).reduce((acc, [size, minWidth]) => ({
1503
+ ...acc,
1504
+ [breakpointPropsMap[size]]: window.matchMedia(`(min-width: ${minWidth}px)`).matches,
1505
+ }), { ...defaultBreakpointState });
1442
1506
  setViewportSize(newViewportSize);
1443
1507
  }, []);
1444
1508
  React.useEffect(() => {
@@ -1579,7 +1643,7 @@ const Breadcrumb = ({ className, dataTestId, breadcrumbItems, back }) => {
1579
1643
  * @param {BreadcrumbContainerProps} props - The props for the BreadcrumbContainer component
1580
1644
  */
1581
1645
  const BreadcrumbContainer = ({ dataTestId, breadcrumbItems }) => {
1582
- const { isMd: isMediumScreen, isXs: isSmallScreen } = useViewportSize();
1646
+ const { isMd: isMediumScreen, isXs: isSmallScreen } = useViewportBreakpoints();
1583
1647
  if (isSmallScreen) {
1584
1648
  return jsxRuntime.jsx(BreadcrumbForSmallScreen, { breadcrumbItems: breadcrumbItems, dataTestId: `smallScreen-${dataTestId}` });
1585
1649
  }
@@ -1816,7 +1880,14 @@ const cvaCollapseHeader = cssClassVarianceUtilities.cvaMerge([
1816
1880
  },
1817
1881
  });
1818
1882
  const cvaCollapseLabelContainer = cssClassVarianceUtilities.cvaMerge(["flex", "items-center", "gap-2"]);
1819
- const cvaCollapsible = cssClassVarianceUtilities.cvaMerge(["block", "relative", "p-4"]);
1883
+ const cvaCollapsible = cssClassVarianceUtilities.cvaMerge(["block", "relative"], {
1884
+ variants: {
1885
+ extraPadding: {
1886
+ true: "p-4",
1887
+ false: "",
1888
+ },
1889
+ },
1890
+ });
1820
1891
  const cvaCollapseAnimated = cssClassVarianceUtilities.cvaMerge(["overflow-y-hidden", "transition-all"], {
1821
1892
  variants: {
1822
1893
  expanded: {
@@ -1857,7 +1928,7 @@ const cvaChevronIcon = cssClassVarianceUtilities.cvaMerge(["transition-transform
1857
1928
  * @param {CollapseProps} props - The props for the Collapse component
1858
1929
  * @returns {JSX.Element} Collapse component
1859
1930
  */
1860
- const Collapse = ({ id, initialExpanded = false, onToggle, label, children, className, headerClassName, headerAddon, dataTestId, }) => {
1931
+ const Collapse = ({ id, initialExpanded = false, onToggle, label, children, className, headerClassName, headerAddon, dataTestId, animate = true, extraPadding = true, }) => {
1861
1932
  const LABEL_ID = sharedUtils.uuidv4();
1862
1933
  const [expanded, setExpanded] = React__namespace.useState(initialExpanded);
1863
1934
  const handleClick = React__namespace.useCallback((e) => {
@@ -1866,12 +1937,12 @@ const Collapse = ({ id, initialExpanded = false, onToggle, label, children, clas
1866
1937
  }
1867
1938
  setExpanded(!expanded);
1868
1939
  }, [expanded, onToggle]);
1869
- return (jsxRuntime.jsxs("div", { className: cvaCollapse({ className }), "data-testid": dataTestId, children: [jsxRuntime.jsxs("div", { "aria-controls": id, "aria-expanded": expanded, className: cvaCollapseHeader({ expanded, className: headerClassName }), onClick: handleClick, role: "button", children: [jsxRuntime.jsxs("div", { className: cvaCollapseLabelContainer(), children: [jsxRuntime.jsx(Icon, { ariaLabelledBy: LABEL_ID, className: cvaChevronIcon({ expanded }), name: "ChevronRight", size: "small" }), jsxRuntime.jsx(Text, { id: LABEL_ID, type: "span", weight: "bold", children: label })] }), headerAddon ? headerAddon : null] }), jsxRuntime.jsx(Collapsible, { expanded: expanded, id: id, children: children })] }));
1940
+ return (jsxRuntime.jsxs("div", { className: cvaCollapse({ className }), "data-testid": dataTestId, children: [jsxRuntime.jsxs("div", { "aria-controls": id, "aria-expanded": expanded, className: cvaCollapseHeader({ expanded, className: headerClassName }), onClick: handleClick, role: "button", children: [jsxRuntime.jsxs("div", { className: cvaCollapseLabelContainer(), children: [jsxRuntime.jsx(Icon, { ariaLabelledBy: LABEL_ID, className: cvaChevronIcon({ expanded }), name: "ChevronRight", size: "small" }), jsxRuntime.jsx(Text, { id: LABEL_ID, type: "span", weight: "bold", children: label })] }), headerAddon ? headerAddon : null] }), jsxRuntime.jsx(Collapsible, { expanded: expanded, extraPadding: extraPadding, id: id, children: expanded || animate ? children : null })] }));
1870
1941
  };
1871
- const Collapsible = ({ children, expanded, id }) => {
1942
+ const Collapsible = ({ children, expanded, id, extraPadding }) => {
1872
1943
  const ref = React.useRef(null);
1873
1944
  const { height } = useGeometry(ref);
1874
- return (jsxRuntime.jsx("div", { className: cvaCollapseAnimated({ expanded }), id: id, style: { height: expanded ? height : "0" }, children: jsxRuntime.jsx("div", { ref: ref, children: jsxRuntime.jsx("div", { className: cvaCollapsible(), children: children }) }) }));
1945
+ return (jsxRuntime.jsx("div", { className: cvaCollapseAnimated({ expanded }), id: id, style: { height: expanded ? height : "0" }, children: jsxRuntime.jsx("div", { ref: ref, children: jsxRuntime.jsx("div", { className: cvaCollapsible({ extraPadding }), children: children }) }) }));
1875
1946
  };
1876
1947
 
1877
1948
  /**
@@ -3356,6 +3427,11 @@ const ExternalLink = ({ rel = "noreferrer", target = "_blank", href, className,
3356
3427
 
3357
3428
  const DEFAULT_ACTIVATION = { click: true, hover: false, keyboardHandlers: true };
3358
3429
  const PADDING = 16;
3430
+ const DEFAULT_DISMISSAL = {
3431
+ enabled: true,
3432
+ outsidePress: true,
3433
+ ancestorScroll: false,
3434
+ };
3359
3435
  /**
3360
3436
  * The hook that powers the Popover component.
3361
3437
  * It should not be used directly, but rather through the Popover component.
@@ -3363,7 +3439,7 @@ const PADDING = 16;
3363
3439
  * @param {PopoverProps} options The options for the popover
3364
3440
  * @returns {ReturnType<typeof usePopover>} The data for the popover
3365
3441
  */
3366
- const usePopover = ({ initialOpen = false, placement = "bottom", isModal, isOpen: controlledIsOpen, activation = DEFAULT_ACTIVATION, dismissal, onOpenStateChange, ...restOptions }) => {
3442
+ const usePopover = ({ initialOpen = false, placement = "bottom", isModal, isOpen: controlledIsOpen, activation = DEFAULT_ACTIVATION, dismissal = DEFAULT_DISMISSAL, onOpenStateChange, ...restOptions }) => {
3367
3443
  const [uncontrolledIsOpen, setUncontrolledIsOpen] = React.useState(initialOpen);
3368
3444
  const [labelId, setLabelId] = React.useState();
3369
3445
  const [descriptionId, setDescriptionId] = React.useState();
@@ -3392,12 +3468,11 @@ const usePopover = ({ initialOpen = false, placement = "bottom", isModal, isOpen
3392
3468
  });
3393
3469
  const popoverContext = popoverData.context;
3394
3470
  const resolvedActivation = typeof activation === "function" ? activation(DEFAULT_ACTIVATION) : activation;
3471
+ const resolvedDismissal = typeof dismissal === "function" ? dismissal(DEFAULT_DISMISSAL) : dismissal;
3395
3472
  const clickInteraction = react.useClick(popoverContext, {
3396
3473
  enabled: resolvedActivation.click,
3397
- ignoreMouse: resolvedActivation.hover,
3398
- keyboardHandlers: false,
3399
3474
  });
3400
- const dismissInteraction = react.useDismiss(popoverContext, dismissal);
3475
+ const dismissInteraction = react.useDismiss(popoverContext, resolvedDismissal);
3401
3476
  const hoverInteraction = react.useHover(popoverContext, {
3402
3477
  enabled: resolvedActivation.hover,
3403
3478
  });
@@ -3461,6 +3536,34 @@ const Popover = ({ children, isModal = false, ...restOptions }) => {
3461
3536
  return (jsxRuntime.jsx(PopoverContext.Provider, { value: popover, children: typeof children === "function" ? children({ isOpen: popover.isOpen, placement: popover.placement }) : children }));
3462
3537
  };
3463
3538
 
3539
+ /** @internal */
3540
+ const PORTAL_CONTAINER = "portal-container";
3541
+ /**
3542
+ * Creates or retrieves the default portal container element
3543
+ *
3544
+ * @internal
3545
+ */
3546
+ const getDefaultPortalContainer = () => {
3547
+ let container = document.getElementById(PORTAL_CONTAINER);
3548
+ if (!container) {
3549
+ container = document.createElement("div");
3550
+ container.id = PORTAL_CONTAINER;
3551
+ document.body.appendChild(container);
3552
+ }
3553
+ return container;
3554
+ };
3555
+
3556
+ /**
3557
+ * Portals the floating element into a given container element
3558
+ * By default they're portalled into an z-index isolated div in
3559
+ * document body -> div#portal-container.
3560
+ * alongside other portalled elements.
3561
+ */
3562
+ const Portal = (props) => {
3563
+ var _a;
3564
+ return jsxRuntime.jsx(react.FloatingPortal, { ...props, root: (_a = props.root) !== null && _a !== void 0 ? _a : getDefaultPortalContainer() });
3565
+ };
3566
+
3464
3567
  const cvaPopoverContainer = cssClassVarianceUtilities.cvaMerge(["component-popover-border", "z-popover", "animate-fade-in-fast"]);
3465
3568
  const cvaPopoverTitleContainer = cssClassVarianceUtilities.cvaMerge(["flex", "items-center", "px-2", "py-1"], {
3466
3569
  variants: {
@@ -3476,7 +3579,7 @@ const PopoverContent = React.forwardRef(function PopoverContent({ className, dat
3476
3579
  var _a;
3477
3580
  const { context: floatingContext, customProps, ...context } = usePopoverContext();
3478
3581
  const ref = react.useMergeRefs([context.refs.setFloating, propRef]);
3479
- return (jsxRuntime.jsx(react.FloatingPortal, { id: portalId !== null && portalId !== void 0 ? portalId : "tu-floating-ui", children: context.isOpen ? (jsxRuntime.jsx(react.FloatingFocusManager, { closeOnFocusOut: false, context: floatingContext, guards: true, modal: context.isModal, order: ["reference", "content"], returnFocus: true, children: jsxRuntime.jsx("div", { "aria-describedby": context.descriptionId, "aria-labelledby": context.labelId, className: cvaPopoverContainer({ className: className !== null && className !== void 0 ? className : customProps.className }), "data-testid": (_a = dataTestId !== null && dataTestId !== void 0 ? dataTestId : customProps.dataTestId) !== null && _a !== void 0 ? _a : "popover-content", ref: ref, style: {
3582
+ return (jsxRuntime.jsx(Portal, { id: portalId, children: context.isOpen ? (jsxRuntime.jsx(react.FloatingFocusManager, { closeOnFocusOut: false, context: floatingContext, guards: true, modal: context.isModal, order: ["reference", "content"], returnFocus: true, children: jsxRuntime.jsx("div", { "aria-describedby": context.descriptionId, "aria-labelledby": context.labelId, className: cvaPopoverContainer({ className: className !== null && className !== void 0 ? className : customProps.className }), "data-testid": (_a = dataTestId !== null && dataTestId !== void 0 ? dataTestId : customProps.dataTestId) !== null && _a !== void 0 ? _a : "popover-content", ref: ref, style: {
3480
3583
  position: context.strategy,
3481
3584
  top: context.y,
3482
3585
  left: context.x,
@@ -3544,7 +3647,7 @@ const cvaTooltipPopoverTail = cssClassVarianceUtilities.cvaMerge("", {
3544
3647
  },
3545
3648
  mode: {
3546
3649
  light: "fill-white",
3547
- dark: "fill-slate-600",
3650
+ dark: "fill-neutral-700",
3548
3651
  },
3549
3652
  },
3550
3653
  defaultVariants: {
@@ -4924,6 +5027,7 @@ exports.Popover = Popover;
4924
5027
  exports.PopoverContent = PopoverContent;
4925
5028
  exports.PopoverTitle = PopoverTitle;
4926
5029
  exports.PopoverTrigger = PopoverTrigger;
5030
+ exports.Portal = Portal;
4927
5031
  exports.Prompt = Prompt;
4928
5032
  exports.ROLE_CARD = ROLE_CARD;
4929
5033
  exports.SectionHeader = SectionHeader;
@@ -4970,6 +5074,7 @@ exports.iconColorNames = iconColorNames;
4970
5074
  exports.iconPalette = iconPalette;
4971
5075
  exports.setLocalStorage = setLocalStorage;
4972
5076
  exports.useClickOutside = useClickOutside;
5077
+ exports.useContainerBreakpoints = useContainerBreakpoints;
4973
5078
  exports.useContinuousTimeout = useContinuousTimeout;
4974
5079
  exports.useDebounce = useDebounce;
4975
5080
  exports.useDevicePixelRatio = useDevicePixelRatio;
@@ -4989,5 +5094,5 @@ exports.useResize = useResize;
4989
5094
  exports.useScrollDetection = useScrollDetection;
4990
5095
  exports.useSelfUpdatingRef = useSelfUpdatingRef;
4991
5096
  exports.useTimeout = useTimeout;
4992
- exports.useViewportSize = useViewportSize;
5097
+ exports.useViewportBreakpoints = useViewportBreakpoints;
4993
5098
  exports.useWindowActivity = useWindowActivity;
package/index.esm.js CHANGED
@@ -13,7 +13,7 @@ import { cvaMerge } from '@trackunit/css-class-variance-utilities';
13
13
  import { Slottable, Slot } from '@radix-ui/react-slot';
14
14
  import { useDebounceCallback, useCopyToClipboard } from 'usehooks-ts';
15
15
  import { Link, useBlocker } from '@tanstack/react-router';
16
- import { useFloating, autoUpdate, offset, flip, shift, size, useClick, useDismiss, useHover as useHover$1, useRole, useInteractions, useMergeRefs, FloatingPortal, FloatingFocusManager, arrow, useTransitionStatus, FloatingArrow } from '@floating-ui/react';
16
+ import { useFloating, autoUpdate, offset, flip, shift, size, useClick, useDismiss, useHover as useHover$1, useRole, useInteractions, FloatingPortal, useMergeRefs, FloatingFocusManager, arrow, useTransitionStatus, FloatingArrow } from '@floating-ui/react';
17
17
  import omit from 'lodash/omit';
18
18
  import { twMerge } from 'tailwind-merge';
19
19
  import { HelmetProvider, Helmet } from 'react-helmet-async';
@@ -973,6 +973,99 @@ const useClickOutside = (el, options = {}, onClick) => {
973
973
  });
974
974
  };
975
975
 
976
+ /**
977
+ * Maps size keys to their corresponding state property names.
978
+ */
979
+ const breakpointPropsMap = {
980
+ xs: "isXs",
981
+ sm: "isSm",
982
+ md: "isMd",
983
+ lg: "isLg",
984
+ xl: "isXl",
985
+ "2xl": "is2xl",
986
+ "3xl": "is3xl",
987
+ };
988
+ /**
989
+ * The default state for sizes, with all values set to false.
990
+ */
991
+ const defaultBreakpointState = {
992
+ isXs: false,
993
+ isSm: false,
994
+ isMd: false,
995
+ isLg: false,
996
+ isXl: false,
997
+ is2xl: false,
998
+ is3xl: false,
999
+ };
1000
+ /**
1001
+ * Creates a breakpoint state object based on a width value
1002
+ */
1003
+ const createBreakpointState = ({ width }) => {
1004
+ return objectEntries(themeScreenSizeAsNumber).reduce((acc, [size, minWidth]) => ({
1005
+ ...acc,
1006
+ [breakpointPropsMap[size]]: width >= minWidth,
1007
+ }), { ...defaultBreakpointState });
1008
+ };
1009
+
1010
+ /**
1011
+ * A custom React hook that provides real-time information about a container's size.
1012
+ *
1013
+ * This hook uses ResizeObserver to monitor changes in the container's width and returns
1014
+ * an object with boolean values indicating which breakpoints are currently active.
1015
+ *
1016
+ * @param {RefObject<HTMLElement>} ref - Reference to the container element to observe
1017
+ * @returns {BreakpointState} An object containing boolean values for each container size breakpoint.
1018
+ * @example
1019
+ * const MyComponent = () => {
1020
+ * const containerRef = useRef<HTMLDivElement>(null);
1021
+ * const containerSize = useContainerBreakpoints(containerRef);
1022
+ *
1023
+ * return (
1024
+ * <div ref={containerRef}>
1025
+ * {containerSize.isLg ? (
1026
+ * <LargeLayout />
1027
+ * ) : containerSize.isMd ? (
1028
+ * <MediumLayout />
1029
+ * ) : (
1030
+ * <SmallLayout />
1031
+ * )}
1032
+ * </div>
1033
+ * );
1034
+ * }
1035
+ */
1036
+ const useContainerBreakpoints = (ref) => {
1037
+ const [containerSize, setContainerSize] = useState(() => defaultBreakpointState);
1038
+ useEffect(() => {
1039
+ if (process.env.NODE_ENV === "development" && !ref.current) {
1040
+ // eslint-disable-next-line no-console
1041
+ console.warn("useContainerBreakpoints: The provided ref is not attached to any element. " +
1042
+ "Make sure to pass the ref to an element using the ref prop, like: <div ref={myRef}>", "\nComponent:", ref.current);
1043
+ }
1044
+ }, [ref]);
1045
+ const updateContainerSize = useCallback(() => {
1046
+ if (!ref.current) {
1047
+ return;
1048
+ }
1049
+ const width = ref.current.getBoundingClientRect().width;
1050
+ setContainerSize(createBreakpointState({ width }));
1051
+ }, [ref]);
1052
+ useEffect(() => {
1053
+ const element = ref.current;
1054
+ if (!element) {
1055
+ return;
1056
+ }
1057
+ // Initial check
1058
+ updateContainerSize();
1059
+ // Set up ResizeObserver
1060
+ const resizeObserver = new ResizeObserver(updateContainerSize);
1061
+ resizeObserver.observe(element);
1062
+ return () => {
1063
+ resizeObserver.disconnect();
1064
+ };
1065
+ }, [updateContainerSize, ref]);
1066
+ return containerSize;
1067
+ };
1068
+
976
1069
  /**
977
1070
  * Hook for managing timeouts.
978
1071
  *
@@ -1364,61 +1457,32 @@ const useSelfUpdatingRef = (initialState) => {
1364
1457
  return stateRef;
1365
1458
  };
1366
1459
 
1367
- /**
1368
- * Maps viewport size keys to their corresponding state property names.
1369
- */
1370
- const propsMap = {
1371
- xs: "isXs",
1372
- sm: "isSm",
1373
- md: "isMd",
1374
- lg: "isLg",
1375
- xl: "isXl",
1376
- "2xl": "is2xl",
1377
- "3xl": "is3xl",
1378
- };
1379
- /**
1380
- * The default state for viewport sizes, with all values set to false.
1381
- */
1382
- const defaultState = {
1383
- isXs: false,
1384
- isSm: false,
1385
- isMd: false,
1386
- isLg: false,
1387
- isXl: false,
1388
- is2xl: false,
1389
- is3xl: false,
1390
- };
1391
1460
  /**
1392
1461
  * A custom React hook that provides real-time information about the current viewport size.
1462
+ * ! Consider using `useContainerBreakpoints` instead, and only use this when you need to actually react to the viewport size, not the container size.
1393
1463
  *
1394
1464
  * This hook listens to changes in the viewport size and returns an object with boolean values
1395
- * indicating which breakpoints are currently active. It's useful for creating responsive
1396
- * layouts and components that need to adapt to different screen sizes.
1465
+ * indicating which breakpoints are currently active.
1397
1466
  *
1398
- * @returns {ViewportSizeState} An object containing boolean values for each viewport size breakpoint.
1467
+ * @returns {BreakpointState} An object containing boolean values for each viewport size breakpoint.
1399
1468
  * @example
1400
- * function MyComponent() {
1401
- * const viewportSize = useViewportSize();
1469
+ * const MyComponent = () => {
1470
+ * const viewportSize = useViewportBreakpoints();
1402
1471
  *
1403
1472
  * if (viewportSize.isLg) {
1404
1473
  * return <LargeScreenLayout />;
1405
- * } else if (viewportSize.isMd) {
1406
- * return <MediumScreenLayout />;
1407
- * } else {
1408
- * return <SmallScreenLayout />;
1409
1474
  * }
1475
+ *
1476
+ * return viewportSize.isMd ? <MediumScreenLayout /> : <SmallLayout />;
1410
1477
  * }
1411
1478
  */
1412
- const useViewportSize = () => {
1413
- const [viewportSize, setViewportSize] = useState(() => defaultState);
1479
+ const useViewportBreakpoints = () => {
1480
+ const [viewportSize, setViewportSize] = useState(() => defaultBreakpointState);
1414
1481
  const updateViewportSize = useCallback(() => {
1415
- const newViewportSize = objectEntries(themeScreenSizeAsNumber).reduce((acc, [size, minWidth]) => {
1416
- const matches = window.matchMedia(`(min-width: ${minWidth}px)`).matches;
1417
- return {
1418
- ...acc,
1419
- [propsMap[size]]: matches,
1420
- };
1421
- }, Object.assign({}, defaultState));
1482
+ const newViewportSize = objectEntries(themeScreenSizeAsNumber).reduce((acc, [size, minWidth]) => ({
1483
+ ...acc,
1484
+ [breakpointPropsMap[size]]: window.matchMedia(`(min-width: ${minWidth}px)`).matches,
1485
+ }), { ...defaultBreakpointState });
1422
1486
  setViewportSize(newViewportSize);
1423
1487
  }, []);
1424
1488
  useEffect(() => {
@@ -1559,7 +1623,7 @@ const Breadcrumb = ({ className, dataTestId, breadcrumbItems, back }) => {
1559
1623
  * @param {BreadcrumbContainerProps} props - The props for the BreadcrumbContainer component
1560
1624
  */
1561
1625
  const BreadcrumbContainer = ({ dataTestId, breadcrumbItems }) => {
1562
- const { isMd: isMediumScreen, isXs: isSmallScreen } = useViewportSize();
1626
+ const { isMd: isMediumScreen, isXs: isSmallScreen } = useViewportBreakpoints();
1563
1627
  if (isSmallScreen) {
1564
1628
  return jsx(BreadcrumbForSmallScreen, { breadcrumbItems: breadcrumbItems, dataTestId: `smallScreen-${dataTestId}` });
1565
1629
  }
@@ -1796,7 +1860,14 @@ const cvaCollapseHeader = cvaMerge([
1796
1860
  },
1797
1861
  });
1798
1862
  const cvaCollapseLabelContainer = cvaMerge(["flex", "items-center", "gap-2"]);
1799
- const cvaCollapsible = cvaMerge(["block", "relative", "p-4"]);
1863
+ const cvaCollapsible = cvaMerge(["block", "relative"], {
1864
+ variants: {
1865
+ extraPadding: {
1866
+ true: "p-4",
1867
+ false: "",
1868
+ },
1869
+ },
1870
+ });
1800
1871
  const cvaCollapseAnimated = cvaMerge(["overflow-y-hidden", "transition-all"], {
1801
1872
  variants: {
1802
1873
  expanded: {
@@ -1837,7 +1908,7 @@ const cvaChevronIcon = cvaMerge(["transition-transform"], {
1837
1908
  * @param {CollapseProps} props - The props for the Collapse component
1838
1909
  * @returns {JSX.Element} Collapse component
1839
1910
  */
1840
- const Collapse = ({ id, initialExpanded = false, onToggle, label, children, className, headerClassName, headerAddon, dataTestId, }) => {
1911
+ const Collapse = ({ id, initialExpanded = false, onToggle, label, children, className, headerClassName, headerAddon, dataTestId, animate = true, extraPadding = true, }) => {
1841
1912
  const LABEL_ID = uuidv4();
1842
1913
  const [expanded, setExpanded] = React.useState(initialExpanded);
1843
1914
  const handleClick = React.useCallback((e) => {
@@ -1846,12 +1917,12 @@ const Collapse = ({ id, initialExpanded = false, onToggle, label, children, clas
1846
1917
  }
1847
1918
  setExpanded(!expanded);
1848
1919
  }, [expanded, onToggle]);
1849
- return (jsxs("div", { className: cvaCollapse({ className }), "data-testid": dataTestId, children: [jsxs("div", { "aria-controls": id, "aria-expanded": expanded, className: cvaCollapseHeader({ expanded, className: headerClassName }), onClick: handleClick, role: "button", children: [jsxs("div", { className: cvaCollapseLabelContainer(), children: [jsx(Icon, { ariaLabelledBy: LABEL_ID, className: cvaChevronIcon({ expanded }), name: "ChevronRight", size: "small" }), jsx(Text, { id: LABEL_ID, type: "span", weight: "bold", children: label })] }), headerAddon ? headerAddon : null] }), jsx(Collapsible, { expanded: expanded, id: id, children: children })] }));
1920
+ return (jsxs("div", { className: cvaCollapse({ className }), "data-testid": dataTestId, children: [jsxs("div", { "aria-controls": id, "aria-expanded": expanded, className: cvaCollapseHeader({ expanded, className: headerClassName }), onClick: handleClick, role: "button", children: [jsxs("div", { className: cvaCollapseLabelContainer(), children: [jsx(Icon, { ariaLabelledBy: LABEL_ID, className: cvaChevronIcon({ expanded }), name: "ChevronRight", size: "small" }), jsx(Text, { id: LABEL_ID, type: "span", weight: "bold", children: label })] }), headerAddon ? headerAddon : null] }), jsx(Collapsible, { expanded: expanded, extraPadding: extraPadding, id: id, children: expanded || animate ? children : null })] }));
1850
1921
  };
1851
- const Collapsible = ({ children, expanded, id }) => {
1922
+ const Collapsible = ({ children, expanded, id, extraPadding }) => {
1852
1923
  const ref = useRef(null);
1853
1924
  const { height } = useGeometry(ref);
1854
- return (jsx("div", { className: cvaCollapseAnimated({ expanded }), id: id, style: { height: expanded ? height : "0" }, children: jsx("div", { ref: ref, children: jsx("div", { className: cvaCollapsible(), children: children }) }) }));
1925
+ return (jsx("div", { className: cvaCollapseAnimated({ expanded }), id: id, style: { height: expanded ? height : "0" }, children: jsx("div", { ref: ref, children: jsx("div", { className: cvaCollapsible({ extraPadding }), children: children }) }) }));
1855
1926
  };
1856
1927
 
1857
1928
  /**
@@ -3336,6 +3407,11 @@ const ExternalLink = ({ rel = "noreferrer", target = "_blank", href, className,
3336
3407
 
3337
3408
  const DEFAULT_ACTIVATION = { click: true, hover: false, keyboardHandlers: true };
3338
3409
  const PADDING = 16;
3410
+ const DEFAULT_DISMISSAL = {
3411
+ enabled: true,
3412
+ outsidePress: true,
3413
+ ancestorScroll: false,
3414
+ };
3339
3415
  /**
3340
3416
  * The hook that powers the Popover component.
3341
3417
  * It should not be used directly, but rather through the Popover component.
@@ -3343,7 +3419,7 @@ const PADDING = 16;
3343
3419
  * @param {PopoverProps} options The options for the popover
3344
3420
  * @returns {ReturnType<typeof usePopover>} The data for the popover
3345
3421
  */
3346
- const usePopover = ({ initialOpen = false, placement = "bottom", isModal, isOpen: controlledIsOpen, activation = DEFAULT_ACTIVATION, dismissal, onOpenStateChange, ...restOptions }) => {
3422
+ const usePopover = ({ initialOpen = false, placement = "bottom", isModal, isOpen: controlledIsOpen, activation = DEFAULT_ACTIVATION, dismissal = DEFAULT_DISMISSAL, onOpenStateChange, ...restOptions }) => {
3347
3423
  const [uncontrolledIsOpen, setUncontrolledIsOpen] = useState(initialOpen);
3348
3424
  const [labelId, setLabelId] = useState();
3349
3425
  const [descriptionId, setDescriptionId] = useState();
@@ -3372,12 +3448,11 @@ const usePopover = ({ initialOpen = false, placement = "bottom", isModal, isOpen
3372
3448
  });
3373
3449
  const popoverContext = popoverData.context;
3374
3450
  const resolvedActivation = typeof activation === "function" ? activation(DEFAULT_ACTIVATION) : activation;
3451
+ const resolvedDismissal = typeof dismissal === "function" ? dismissal(DEFAULT_DISMISSAL) : dismissal;
3375
3452
  const clickInteraction = useClick(popoverContext, {
3376
3453
  enabled: resolvedActivation.click,
3377
- ignoreMouse: resolvedActivation.hover,
3378
- keyboardHandlers: false,
3379
3454
  });
3380
- const dismissInteraction = useDismiss(popoverContext, dismissal);
3455
+ const dismissInteraction = useDismiss(popoverContext, resolvedDismissal);
3381
3456
  const hoverInteraction = useHover$1(popoverContext, {
3382
3457
  enabled: resolvedActivation.hover,
3383
3458
  });
@@ -3441,6 +3516,34 @@ const Popover = ({ children, isModal = false, ...restOptions }) => {
3441
3516
  return (jsx(PopoverContext.Provider, { value: popover, children: typeof children === "function" ? children({ isOpen: popover.isOpen, placement: popover.placement }) : children }));
3442
3517
  };
3443
3518
 
3519
+ /** @internal */
3520
+ const PORTAL_CONTAINER = "portal-container";
3521
+ /**
3522
+ * Creates or retrieves the default portal container element
3523
+ *
3524
+ * @internal
3525
+ */
3526
+ const getDefaultPortalContainer = () => {
3527
+ let container = document.getElementById(PORTAL_CONTAINER);
3528
+ if (!container) {
3529
+ container = document.createElement("div");
3530
+ container.id = PORTAL_CONTAINER;
3531
+ document.body.appendChild(container);
3532
+ }
3533
+ return container;
3534
+ };
3535
+
3536
+ /**
3537
+ * Portals the floating element into a given container element
3538
+ * By default they're portalled into an z-index isolated div in
3539
+ * document body -> div#portal-container.
3540
+ * alongside other portalled elements.
3541
+ */
3542
+ const Portal = (props) => {
3543
+ var _a;
3544
+ return jsx(FloatingPortal, { ...props, root: (_a = props.root) !== null && _a !== void 0 ? _a : getDefaultPortalContainer() });
3545
+ };
3546
+
3444
3547
  const cvaPopoverContainer = cvaMerge(["component-popover-border", "z-popover", "animate-fade-in-fast"]);
3445
3548
  const cvaPopoverTitleContainer = cvaMerge(["flex", "items-center", "px-2", "py-1"], {
3446
3549
  variants: {
@@ -3456,7 +3559,7 @@ const PopoverContent = React__default.forwardRef(function PopoverContent({ class
3456
3559
  var _a;
3457
3560
  const { context: floatingContext, customProps, ...context } = usePopoverContext();
3458
3561
  const ref = useMergeRefs([context.refs.setFloating, propRef]);
3459
- return (jsx(FloatingPortal, { id: portalId !== null && portalId !== void 0 ? portalId : "tu-floating-ui", children: context.isOpen ? (jsx(FloatingFocusManager, { closeOnFocusOut: false, context: floatingContext, guards: true, modal: context.isModal, order: ["reference", "content"], returnFocus: true, children: jsx("div", { "aria-describedby": context.descriptionId, "aria-labelledby": context.labelId, className: cvaPopoverContainer({ className: className !== null && className !== void 0 ? className : customProps.className }), "data-testid": (_a = dataTestId !== null && dataTestId !== void 0 ? dataTestId : customProps.dataTestId) !== null && _a !== void 0 ? _a : "popover-content", ref: ref, style: {
3562
+ return (jsx(Portal, { id: portalId, children: context.isOpen ? (jsx(FloatingFocusManager, { closeOnFocusOut: false, context: floatingContext, guards: true, modal: context.isModal, order: ["reference", "content"], returnFocus: true, children: jsx("div", { "aria-describedby": context.descriptionId, "aria-labelledby": context.labelId, className: cvaPopoverContainer({ className: className !== null && className !== void 0 ? className : customProps.className }), "data-testid": (_a = dataTestId !== null && dataTestId !== void 0 ? dataTestId : customProps.dataTestId) !== null && _a !== void 0 ? _a : "popover-content", ref: ref, style: {
3460
3563
  position: context.strategy,
3461
3564
  top: context.y,
3462
3565
  left: context.x,
@@ -3524,7 +3627,7 @@ const cvaTooltipPopoverTail = cvaMerge("", {
3524
3627
  },
3525
3628
  mode: {
3526
3629
  light: "fill-white",
3527
- dark: "fill-slate-600",
3630
+ dark: "fill-neutral-700",
3528
3631
  },
3529
3632
  },
3530
3633
  defaultVariants: {
@@ -4869,4 +4972,4 @@ const cvaClickable = cvaMerge([
4869
4972
  },
4870
4973
  });
4871
4974
 
4872
- export { Alert, Badge, Breadcrumb, BreadcrumbContainer, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, EmptyState, EmptyValue, ExternalLink, Heading, Icon, IconButton, Indicator, KPICard, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Prompt, ROLE_CARD, SectionHeader, Sidebar, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, Timeline, TimelineElement, ToggleGroup, ToggleItem, Tooltip, ValueBar, VirtualizedList, WidgetBody, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaIconButton, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInteractableItem, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, docs, getDevicePixelRatio, getValueBarColorByValue, iconColorNames, iconPalette, setLocalStorage, useClickOutside, useContinuousTimeout, useDebounce, useDevicePixelRatio, useGeometry, useHover, useIsFirstRender, useIsFullscreen, useIsTextCutOff, useLocalStorage, useLocalStorageReducer, useOptionallyElevatedState, useOverflowItems, usePopoverContext, usePrevious, usePrompt, useResize, useScrollDetection, useSelfUpdatingRef, useTimeout, useViewportSize, useWindowActivity };
4975
+ export { Alert, Badge, Breadcrumb, BreadcrumbContainer, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, EmptyState, EmptyValue, ExternalLink, Heading, Icon, IconButton, Indicator, KPICard, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, Prompt, ROLE_CARD, SectionHeader, Sidebar, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, Timeline, TimelineElement, ToggleGroup, ToggleItem, Tooltip, ValueBar, VirtualizedList, WidgetBody, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaIconButton, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInteractableItem, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, docs, getDevicePixelRatio, getValueBarColorByValue, iconColorNames, iconPalette, setLocalStorage, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useDebounce, useDevicePixelRatio, useGeometry, useHover, useIsFirstRender, useIsFullscreen, useIsTextCutOff, useLocalStorage, useLocalStorageReducer, useOptionallyElevatedState, useOverflowItems, usePopoverContext, usePrevious, usePrompt, useResize, useScrollDetection, useSelfUpdatingRef, useTimeout, useViewportBreakpoints, useWindowActivity };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-components",
3
- "version": "0.5.19",
3
+ "version": "0.5.21",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -26,6 +26,12 @@ export interface CollapseProps extends CommonProps {
26
26
  * An addon to add to the header. This can be used for a status indicator or similar.
27
27
  */
28
28
  headerAddon?: React.ReactNode;
29
+ /**
30
+ * Without animation children wont be rendered until expanded, which improves performance
31
+ * for high volume data
32
+ */
33
+ animate?: boolean;
34
+ extraPadding?: boolean;
29
35
  }
30
36
  /**
31
37
  * The Collapse component is a container for additional information that adds context to various flows. It is used for displaying non-essential information that can be hidden away.
@@ -44,4 +50,4 @@ export interface CollapseProps extends CommonProps {
44
50
  * @param {CollapseProps} props - The props for the Collapse component
45
51
  * @returns {JSX.Element} Collapse component
46
52
  */
47
- export declare const Collapse: ({ id, initialExpanded, onToggle, label, children, className, headerClassName, headerAddon, dataTestId, }: CollapseProps) => import("react/jsx-runtime").JSX.Element;
53
+ export declare const Collapse: ({ id, initialExpanded, onToggle, label, children, className, headerClassName, headerAddon, dataTestId, animate, extraPadding, }: CollapseProps) => import("react/jsx-runtime").JSX.Element;
@@ -3,7 +3,9 @@ export declare const cvaCollapseHeader: (props?: ({
3
3
  expanded?: boolean | null | undefined;
4
4
  } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
5
5
  export declare const cvaCollapseLabelContainer: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
6
- export declare const cvaCollapsible: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
6
+ export declare const cvaCollapsible: (props?: ({
7
+ extraPadding?: boolean | null | undefined;
8
+ } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
7
9
  export declare const cvaCollapseAnimated: (props?: ({
8
10
  expanded?: boolean | null | undefined;
9
11
  } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
@@ -5,7 +5,7 @@ export type PopoverActivation = {
5
5
  hover?: boolean;
6
6
  keyboardHandlers?: boolean;
7
7
  };
8
- export type PopoverDismissal = Pick<UseDismissProps, "ancestorScroll" | "enabled" | "outsidePress" | "referencePress">;
8
+ export type PopoverDismissal = Pick<UseDismissProps, "ancestorScroll" | "enabled" | "outsidePress">;
9
9
  export type PopoverPlacement = "top" | "right" | "bottom" | "left" | "top-start" | "top-end" | "right-start" | "right-end" | "bottom-start" | "bottom-end" | "left-start" | "left-end";
10
10
  export type Strategy = "absolute" | "fixed";
11
11
  export interface PopoverProps extends CommonProps {
@@ -32,7 +32,7 @@ export interface PopoverProps extends CommonProps {
32
32
  /**
33
33
  * Options for dismissal of the popover
34
34
  */
35
- dismissal?: PopoverDismissal;
35
+ dismissal?: PopoverDismissal | ((defaultDismissal: PopoverDismissal) => PopoverDismissal);
36
36
  /**
37
37
  * Callback to be called when the popover open state changes
38
38
  */
@@ -0,0 +1,9 @@
1
+ import { FloatingPortalProps } from "@floating-ui/react";
2
+ export type PortalProps = FloatingPortalProps;
3
+ /**
4
+ * Portals the floating element into a given container element
5
+ * By default they're portalled into an z-index isolated div in
6
+ * document body -> div#portal-container.
7
+ * alongside other portalled elements.
8
+ */
9
+ export declare const Portal: (props: FloatingPortalProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,19 @@
1
+ import { UseFloatingPortalNodeProps } from "@floating-ui/react";
2
+ /** @internal */
3
+ export declare const PORTAL_CONTAINER = "portal-container";
4
+ /**
5
+ * Creates or retrieves the default portal container element
6
+ *
7
+ * @internal
8
+ */
9
+ export declare const getDefaultPortalContainer: () => HTMLElement;
10
+ /**
11
+ * Hook that creates or retrieves a portal node for floating UI components
12
+ *
13
+ * @param props - The portal node props
14
+ * @param props.root - The root element to attach the portal to
15
+ * @param props.id - The ID for the portal node
16
+ * @returns {HTMLElement | null} The portal node props
17
+ * @internal
18
+ */
19
+ export declare const usePortalNode: (props?: UseFloatingPortalNodeProps) => HTMLElement | null;
@@ -21,6 +21,7 @@ export * from "./Pagination/Pagination";
21
21
  export * from "./Polygon/Polygon";
22
22
  export * from "./Popover";
23
23
  export * from "./Popover/Popover";
24
+ export * from "./Portal/Portal";
24
25
  export * from "./Prompt";
25
26
  export * from "./SectionHeader";
26
27
  export * from "./Sidebar/Sidebar";
@@ -0,0 +1,23 @@
1
+ import { themeScreenSizeAsNumber } from "@trackunit/ui-design-tokens";
2
+ export type BreakpointSize = keyof typeof themeScreenSizeAsNumber;
3
+ /**
4
+ * Represents the state of sizes as boolean values.
5
+ * Each property is true if the width is greater than or equal to the corresponding breakpoint.
6
+ */
7
+ export type BreakpointState = {
8
+ [K in BreakpointSize as `is${Capitalize<K>}`]: boolean;
9
+ };
10
+ /**
11
+ * Maps size keys to their corresponding state property names.
12
+ */
13
+ export declare const breakpointPropsMap: Record<BreakpointSize, keyof BreakpointState>;
14
+ /**
15
+ * The default state for sizes, with all values set to false.
16
+ */
17
+ export declare const defaultBreakpointState: BreakpointState;
18
+ /**
19
+ * Creates a breakpoint state object based on a width value
20
+ */
21
+ export declare const createBreakpointState: ({ width }: {
22
+ width: number;
23
+ }) => BreakpointState;
@@ -2,6 +2,7 @@ export * from "./localStorage/setLocalStorage";
2
2
  export * from "./localStorage/useLocalStorage";
3
3
  export * from "./localStorage/useLocalStorageReducer";
4
4
  export * from "./useClickOutside";
5
+ export * from "./useContainerBreakpoints";
5
6
  export * from "./useContinuousTimeout";
6
7
  export * from "./useDebounce";
7
8
  export * from "./useDevicePixelRatio";
@@ -16,5 +17,5 @@ export * from "./useResize";
16
17
  export * from "./useScrollDetection";
17
18
  export * from "./useSelfUpdatingRef";
18
19
  export * from "./useTimeout";
19
- export * from "./useViewportSize";
20
+ export * from "./useViewportBreakpoints";
20
21
  export * from "./useWindowActivity";
@@ -0,0 +1,29 @@
1
+ import { type RefObject } from "react";
2
+ import { type BreakpointState } from "./breakpoint-utils";
3
+ /**
4
+ * A custom React hook that provides real-time information about a container's size.
5
+ *
6
+ * This hook uses ResizeObserver to monitor changes in the container's width and returns
7
+ * an object with boolean values indicating which breakpoints are currently active.
8
+ *
9
+ * @param {RefObject<HTMLElement>} ref - Reference to the container element to observe
10
+ * @returns {BreakpointState} An object containing boolean values for each container size breakpoint.
11
+ * @example
12
+ * const MyComponent = () => {
13
+ * const containerRef = useRef<HTMLDivElement>(null);
14
+ * const containerSize = useContainerBreakpoints(containerRef);
15
+ *
16
+ * return (
17
+ * <div ref={containerRef}>
18
+ * {containerSize.isLg ? (
19
+ * <LargeLayout />
20
+ * ) : containerSize.isMd ? (
21
+ * <MediumLayout />
22
+ * ) : (
23
+ * <SmallLayout />
24
+ * )}
25
+ * </div>
26
+ * );
27
+ * }
28
+ */
29
+ export declare const useContainerBreakpoints: (ref: RefObject<HTMLElement>) => BreakpointState;
@@ -0,0 +1,21 @@
1
+ import { type BreakpointState } from "./breakpoint-utils";
2
+ /**
3
+ * A custom React hook that provides real-time information about the current viewport size.
4
+ * ! Consider using `useContainerBreakpoints` instead, and only use this when you need to actually react to the viewport size, not the container size.
5
+ *
6
+ * This hook listens to changes in the viewport size and returns an object with boolean values
7
+ * indicating which breakpoints are currently active.
8
+ *
9
+ * @returns {BreakpointState} An object containing boolean values for each viewport size breakpoint.
10
+ * @example
11
+ * const MyComponent = () => {
12
+ * const viewportSize = useViewportBreakpoints();
13
+ *
14
+ * if (viewportSize.isLg) {
15
+ * return <LargeScreenLayout />;
16
+ * }
17
+ *
18
+ * return viewportSize.isMd ? <MediumScreenLayout /> : <SmallLayout />;
19
+ * }
20
+ */
21
+ export declare const useViewportBreakpoints: () => BreakpointState;
@@ -1,32 +0,0 @@
1
- import { themeScreenSizeAsNumber } from "@trackunit/ui-design-tokens";
2
- type ViewportSize = keyof typeof themeScreenSizeAsNumber;
3
- /**
4
- * Represents the state of viewport sizes as boolean values.
5
- * Each property is true if the viewport width is greater than or equal to the corresponding breakpoint.
6
- */
7
- type ViewportSizeState = {
8
- [K in ViewportSize as `is${Capitalize<K>}`]: boolean;
9
- };
10
- /**
11
- * A custom React hook that provides real-time information about the current viewport size.
12
- *
13
- * This hook listens to changes in the viewport size and returns an object with boolean values
14
- * indicating which breakpoints are currently active. It's useful for creating responsive
15
- * layouts and components that need to adapt to different screen sizes.
16
- *
17
- * @returns {ViewportSizeState} An object containing boolean values for each viewport size breakpoint.
18
- * @example
19
- * function MyComponent() {
20
- * const viewportSize = useViewportSize();
21
- *
22
- * if (viewportSize.isLg) {
23
- * return <LargeScreenLayout />;
24
- * } else if (viewportSize.isMd) {
25
- * return <MediumScreenLayout />;
26
- * } else {
27
- * return <SmallScreenLayout />;
28
- * }
29
- * }
30
- */
31
- export declare const useViewportSize: () => ViewportSizeState;
32
- export {};