@trackunit/react-components 1.10.40 → 1.10.43
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 +224 -116
- package/index.esm.js +228 -120
- package/package.json +5 -7
- package/src/components/HorizontalOverflowScroller/HorizontalOverflowScroller.d.ts +8 -3
- package/src/hooks/index.d.ts +2 -3
- package/src/hooks/{useMeasure/useMeasure.d.ts → useMeasure.d.ts} +15 -3
- package/src/hooks/useMergeRefs.d.ts +23 -0
- package/src/hooks/useScrollDetection.d.ts +14 -4
- package/src/hooks/useMeasure/useMeasureElement.d.ts +0 -19
- package/src/hooks/useMeasure/useMeasureShared.d.ts +0 -19
package/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
2
|
-
import { useRef, useMemo, useEffect, useState, useCallback, createElement, useReducer, forwardRef, Fragment, memo, Children, isValidElement, cloneElement, createContext, useContext
|
|
2
|
+
import { useRef, useMemo, useEffect, useState, useCallback, createElement, useReducer, useLayoutEffect, forwardRef, Fragment, memo, Children, isValidElement, cloneElement, createContext, useContext } from 'react';
|
|
3
3
|
import { objectKeys, uuidv4, objectEntries, objectValues, nonNullable } from '@trackunit/shared-utils';
|
|
4
4
|
import { intentPalette, generalPalette, criticalityPalette, activityPalette, utilizationPalette, sitesPalette, rentalStatusPalette, themeScreenSizeAsNumber, color } from '@trackunit/ui-design-tokens';
|
|
5
5
|
import { iconNames } from '@trackunit/ui-icons';
|
|
@@ -14,7 +14,7 @@ import { isEqual, omit } from 'es-toolkit';
|
|
|
14
14
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
15
15
|
import { useDebounceCallback, useCopyToClipboard } from 'usehooks-ts';
|
|
16
16
|
import { Link, useBlocker } from '@tanstack/react-router';
|
|
17
|
-
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
|
+
import { useFloating, autoUpdate, offset, flip, shift, size, useClick, useDismiss, useHover as useHover$1, useRole, useInteractions, FloatingPortal, useMergeRefs as useMergeRefs$1, FloatingFocusManager, arrow, useTransitionStatus, FloatingArrow } from '@floating-ui/react';
|
|
18
18
|
import { twMerge } from 'tailwind-merge';
|
|
19
19
|
import { HelmetProvider, Helmet } from 'react-helmet-async';
|
|
20
20
|
import { Trigger, Content, List as List$1, Root } from '@radix-ui/react-tabs';
|
|
@@ -1332,34 +1332,40 @@ const useElevatedState = (initialState, customState) => {
|
|
|
1332
1332
|
* @returns {object} The object containing the onMouseEnter, onMouseLeave and hovering props
|
|
1333
1333
|
*/
|
|
1334
1334
|
const useHover = ({ debounced = false, delay = 100, direction = "out" } = { debounced: false }) => {
|
|
1335
|
-
const [
|
|
1336
|
-
const [
|
|
1337
|
-
useEffect(() => {
|
|
1338
|
-
if (!debounced) {
|
|
1339
|
-
setDebouncedHovering(hovering);
|
|
1340
|
-
return undefined;
|
|
1341
|
-
}
|
|
1342
|
-
const shouldDebounce = direction === "both" || (direction === "in" && hovering) || (direction === "out" && !hovering);
|
|
1343
|
-
if (shouldDebounce) {
|
|
1344
|
-
const timer = setTimeout(() => {
|
|
1345
|
-
setDebouncedHovering(hovering);
|
|
1346
|
-
}, delay);
|
|
1347
|
-
return () => clearTimeout(timer);
|
|
1348
|
-
}
|
|
1349
|
-
setDebouncedHovering(hovering);
|
|
1350
|
-
return undefined;
|
|
1351
|
-
}, [debounced, direction, delay, hovering]);
|
|
1335
|
+
const [isHovering, setIsHovering] = useState(false);
|
|
1336
|
+
const [debouncedIsHovering, setDebouncedIsHovering] = useState(false);
|
|
1352
1337
|
const onMouseEnter = useCallback(() => {
|
|
1353
|
-
|
|
1338
|
+
setIsHovering(true);
|
|
1354
1339
|
}, []);
|
|
1355
1340
|
const onMouseLeave = useCallback(() => {
|
|
1356
|
-
|
|
1341
|
+
setIsHovering(false);
|
|
1357
1342
|
}, []);
|
|
1343
|
+
// Determine if the current transition should be debounced based on direction
|
|
1344
|
+
const isTransitionDebounced = debounced && (direction === "both" || (direction === "in" && isHovering) || (direction === "out" && !isHovering));
|
|
1345
|
+
// Sync debouncedIsHovering immediately for non-debounced transitions
|
|
1346
|
+
useLayoutEffect(() => {
|
|
1347
|
+
if (!debounced || isTransitionDebounced) {
|
|
1348
|
+
return undefined;
|
|
1349
|
+
}
|
|
1350
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- Synchronizing derived state for directional debouncing
|
|
1351
|
+
setDebouncedIsHovering(isHovering);
|
|
1352
|
+
}, [debounced, isTransitionDebounced, isHovering]);
|
|
1353
|
+
// Apply delay for debounced transitions
|
|
1354
|
+
useEffect(() => {
|
|
1355
|
+
if (!isTransitionDebounced) {
|
|
1356
|
+
return undefined;
|
|
1357
|
+
}
|
|
1358
|
+
const timer = setTimeout(() => {
|
|
1359
|
+
setDebouncedIsHovering(isHovering);
|
|
1360
|
+
}, delay);
|
|
1361
|
+
return () => clearTimeout(timer);
|
|
1362
|
+
}, [isTransitionDebounced, delay, isHovering]);
|
|
1363
|
+
const value = debounced ? (isTransitionDebounced ? debouncedIsHovering : isHovering) : isHovering;
|
|
1358
1364
|
return useMemo(() => ({
|
|
1359
1365
|
onMouseEnter,
|
|
1360
1366
|
onMouseLeave,
|
|
1361
|
-
hovering:
|
|
1362
|
-
}), [onMouseEnter, onMouseLeave,
|
|
1367
|
+
hovering: value,
|
|
1368
|
+
}), [onMouseEnter, onMouseLeave, value]);
|
|
1363
1369
|
};
|
|
1364
1370
|
|
|
1365
1371
|
const OVERSCAN = 10;
|
|
@@ -1389,10 +1395,13 @@ const DEFAULT_ROW_HEIGHT = 50;
|
|
|
1389
1395
|
* });
|
|
1390
1396
|
*/
|
|
1391
1397
|
const useInfiniteScroll = ({ pagination, scrollElementRef, count, estimateSize, overscan, onChange, skip = false, }) => {
|
|
1398
|
+
"use no memo"; //! TODO: remove this once Tanstack Virtual is updated to support React Compiler
|
|
1392
1399
|
const handleChange = useCallback((virtualizer) => {
|
|
1393
1400
|
onChange?.(virtualizer);
|
|
1394
1401
|
}, [onChange]);
|
|
1395
1402
|
const handleEstimateSize = useCallback((index) => estimateSize?.(index) ?? DEFAULT_ROW_HEIGHT, [estimateSize]);
|
|
1403
|
+
//! TODO: remove this once Tanstack Virtual is updated to support React Compiler
|
|
1404
|
+
// eslint-disable-next-line react-hooks/incompatible-library
|
|
1396
1405
|
const rowVirtualizer = useVirtualizer({
|
|
1397
1406
|
count,
|
|
1398
1407
|
getScrollElement: () => scrollElementRef.current,
|
|
@@ -1441,8 +1450,10 @@ const useInfiniteScroll = ({ pagination, scrollElementRef, count, estimateSize,
|
|
|
1441
1450
|
*/
|
|
1442
1451
|
const useIsFirstRender = () => {
|
|
1443
1452
|
const [isFirstRender, setIsFirstRender] = useState(true);
|
|
1444
|
-
|
|
1445
|
-
|
|
1453
|
+
useLayoutEffect(() => {
|
|
1454
|
+
queueMicrotask(() => {
|
|
1455
|
+
setIsFirstRender(false);
|
|
1456
|
+
});
|
|
1446
1457
|
}, []);
|
|
1447
1458
|
return isFirstRender;
|
|
1448
1459
|
};
|
|
@@ -1514,27 +1525,51 @@ const useIsTextTruncated = (text, { skip = false } = {}) => {
|
|
|
1514
1525
|
};
|
|
1515
1526
|
|
|
1516
1527
|
/**
|
|
1517
|
-
*
|
|
1518
|
-
*
|
|
1528
|
+
* Custom hook to measure the geometry of an element using a callback ref.
|
|
1529
|
+
* Measures the size and position of the element relative to the viewport.
|
|
1530
|
+
*
|
|
1531
|
+
* @template TElement extends HTMLElement
|
|
1532
|
+
* @returns {UseMeasureResult<HTMLElement>} An object containing `geometry`, `ref` callback, and `element`.
|
|
1533
|
+
* @example
|
|
1534
|
+
* ```tsx
|
|
1535
|
+
* const { geometry, ref } = useMeasure();
|
|
1536
|
+
* return <div ref={ref}>Width: {geometry.width}</div>;
|
|
1537
|
+
* ```
|
|
1519
1538
|
*/
|
|
1520
|
-
const
|
|
1539
|
+
const useMeasure = ({ skip = false, onChange, } = {}) => {
|
|
1540
|
+
const [element, setElement] = useState(null);
|
|
1521
1541
|
const [geometry, setGeometry] = useState(undefined);
|
|
1522
1542
|
const observerRef = useRef(null);
|
|
1523
1543
|
const onChangeRef = useRef(onChange);
|
|
1524
1544
|
const isInitialRender = useRef(true);
|
|
1525
|
-
|
|
1545
|
+
// Callback ref to track the element
|
|
1546
|
+
const ref = useCallback((node) => {
|
|
1547
|
+
setElement(node);
|
|
1548
|
+
}, []);
|
|
1526
1549
|
const disconnectObserver = () => {
|
|
1527
1550
|
if (observerRef.current !== null) {
|
|
1528
1551
|
observerRef.current.disconnect();
|
|
1529
1552
|
observerRef.current = null;
|
|
1530
1553
|
}
|
|
1531
1554
|
};
|
|
1555
|
+
useEffect(() => {
|
|
1556
|
+
onChangeRef.current = onChange;
|
|
1557
|
+
}, [onChange]);
|
|
1532
1558
|
useEffect(() => {
|
|
1533
1559
|
if (skip || element === null) {
|
|
1534
1560
|
return;
|
|
1535
1561
|
}
|
|
1536
1562
|
const updateGeometry = (rect) => {
|
|
1537
|
-
const newGeometry =
|
|
1563
|
+
const newGeometry = {
|
|
1564
|
+
width: rect.width,
|
|
1565
|
+
height: rect.height,
|
|
1566
|
+
top: rect.top,
|
|
1567
|
+
bottom: rect.bottom,
|
|
1568
|
+
left: rect.left,
|
|
1569
|
+
right: rect.right,
|
|
1570
|
+
x: rect.x,
|
|
1571
|
+
y: rect.y,
|
|
1572
|
+
};
|
|
1538
1573
|
setGeometry(prevGeometry => {
|
|
1539
1574
|
if (isEqual(prevGeometry, newGeometry)) {
|
|
1540
1575
|
return prevGeometry;
|
|
@@ -1560,48 +1595,72 @@ const useMeasureShared = (element, { skip = false, onChange } = {}) => {
|
|
|
1560
1595
|
updateGeometry(initialRect);
|
|
1561
1596
|
return disconnectObserver;
|
|
1562
1597
|
}, [element, skip]);
|
|
1563
|
-
return geometry;
|
|
1598
|
+
return useMemo(() => ({ geometry, ref, element }), [geometry, ref, element]);
|
|
1564
1599
|
};
|
|
1565
1600
|
|
|
1601
|
+
// This hook is _heavily_ inspired by floating-ui's useMergeRefs
|
|
1566
1602
|
/**
|
|
1567
|
-
*
|
|
1568
|
-
*
|
|
1603
|
+
* Merges an array of refs into a single memoized callback ref or `null`.
|
|
1604
|
+
* Useful when you need to attach multiple refs to the same element,
|
|
1605
|
+
* such as when composing multiple hooks that each need a ref.
|
|
1569
1606
|
*
|
|
1570
|
-
* @
|
|
1571
|
-
* @
|
|
1607
|
+
* @template TInstance - The type of the element instance
|
|
1608
|
+
* @param {ReadonlyArray<MergeableRef<TInstance> | undefined>} refs - Array of refs to merge (can be RefObjects, RefCallbacks, or undefined)
|
|
1609
|
+
* @returns {null | RefCallback<TInstance>} A single ref callback that will update all provided refs, or null if all refs are null
|
|
1572
1610
|
* @example
|
|
1573
1611
|
* ```tsx
|
|
1574
|
-
* const {
|
|
1575
|
-
*
|
|
1576
|
-
*
|
|
1577
|
-
*/
|
|
1578
|
-
const useMeasure = ({ skip = false, onChange, } = {}) => {
|
|
1579
|
-
const [element, setElement] = useState(null);
|
|
1580
|
-
// Callback ref to track the element
|
|
1581
|
-
const ref = useCallback((node) => {
|
|
1582
|
-
setElement(node);
|
|
1583
|
-
}, []);
|
|
1584
|
-
const geometry = useMeasureShared(element, { skip, onChange });
|
|
1585
|
-
return { geometry, ref, element };
|
|
1586
|
-
};
|
|
1587
|
-
|
|
1588
|
-
/**
|
|
1589
|
-
* Custom hook to measure the geometry of an element from a RefObject.
|
|
1590
|
-
* Use this when you already have a ref (e.g., from useRef or for composition with other hooks).
|
|
1591
|
-
* Measures the size and position of the element relative to the viewport.
|
|
1612
|
+
* const { ref: measureRef } = useMeasure();
|
|
1613
|
+
* const { ref: scrollRef } = useScrollDetection();
|
|
1614
|
+
* const mergedRef = useMergeRefs([measureRef, scrollRef]);
|
|
1592
1615
|
*
|
|
1593
|
-
*
|
|
1594
|
-
* @returns {Geometry} The geometry of the element
|
|
1595
|
-
* @example
|
|
1596
|
-
* ```tsx
|
|
1597
|
-
* const ref = useRef<HTMLDivElement>(null);
|
|
1598
|
-
* const geometry = useMeasureElement(ref);
|
|
1599
|
-
* return <div ref={ref}>Width: {geometry.width}</div>;
|
|
1616
|
+
* return <div ref={mergedRef}>Content</div>;
|
|
1600
1617
|
* ```
|
|
1601
1618
|
*/
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1619
|
+
function useMergeRefs(refs) {
|
|
1620
|
+
const cleanupRef = useRef(undefined);
|
|
1621
|
+
const refsRef = useRef(refs);
|
|
1622
|
+
useEffect(() => {
|
|
1623
|
+
refsRef.current = refs;
|
|
1624
|
+
}, [refs]);
|
|
1625
|
+
const refEffect = useCallback((instance) => {
|
|
1626
|
+
const cleanups = refsRef.current.map(ref => {
|
|
1627
|
+
if (ref === null || ref === undefined) {
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
if (typeof ref === "function") {
|
|
1631
|
+
const refCallback = ref;
|
|
1632
|
+
const refCleanup = refCallback(instance);
|
|
1633
|
+
return typeof refCleanup === "function"
|
|
1634
|
+
? refCleanup
|
|
1635
|
+
: () => {
|
|
1636
|
+
refCallback(null);
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
ref.current = instance;
|
|
1640
|
+
return () => {
|
|
1641
|
+
ref.current = null;
|
|
1642
|
+
};
|
|
1643
|
+
});
|
|
1644
|
+
return () => {
|
|
1645
|
+
cleanups.forEach(refCleanup => refCleanup?.());
|
|
1646
|
+
};
|
|
1647
|
+
}, []);
|
|
1648
|
+
return useMemo(() => {
|
|
1649
|
+
// eslint-disable-next-line react-hooks/refs
|
|
1650
|
+
if (refsRef.current.every(ref => ref === null)) {
|
|
1651
|
+
return null;
|
|
1652
|
+
}
|
|
1653
|
+
return (value) => {
|
|
1654
|
+
if (cleanupRef.current) {
|
|
1655
|
+
cleanupRef.current();
|
|
1656
|
+
cleanupRef.current = undefined;
|
|
1657
|
+
}
|
|
1658
|
+
if (value !== null) {
|
|
1659
|
+
cleanupRef.current = refEffect(value);
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
}, [refEffect]);
|
|
1663
|
+
}
|
|
1605
1664
|
|
|
1606
1665
|
/**
|
|
1607
1666
|
* Hook that returns true if any modifier key (Ctrl, Alt, Shift, Meta/Cmd) is pressed
|
|
@@ -1641,22 +1700,24 @@ const useRelayPagination = ({ onReset, pageSize } = { pageSize: defaultPageSize
|
|
|
1641
1700
|
const [variables, setVariables] = useState({ first: pageSize });
|
|
1642
1701
|
const [pageInfo, setPageInfo] = useState();
|
|
1643
1702
|
const [isLoading, setIsLoading] = useState(true);
|
|
1703
|
+
// Destructure pageInfo properties early to avoid depending on the entire object
|
|
1704
|
+
const { hasNextPage, endCursor, hasPreviousPage, startCursor } = pageInfo ?? {};
|
|
1644
1705
|
const nextPage = useCallback(() => {
|
|
1645
|
-
if (
|
|
1706
|
+
if (hasNextPage === true) {
|
|
1646
1707
|
setVariables({
|
|
1647
|
-
after:
|
|
1708
|
+
after: endCursor === null ? undefined : endCursor,
|
|
1648
1709
|
first: pageSize,
|
|
1649
1710
|
});
|
|
1650
1711
|
}
|
|
1651
|
-
}, [
|
|
1712
|
+
}, [hasNextPage, endCursor, pageSize]);
|
|
1652
1713
|
const previousPage = useCallback(() => {
|
|
1653
|
-
if (
|
|
1714
|
+
if (hasPreviousPage === true) {
|
|
1654
1715
|
setVariables({
|
|
1655
|
-
before:
|
|
1716
|
+
before: startCursor === null ? undefined : startCursor,
|
|
1656
1717
|
last: pageSize,
|
|
1657
1718
|
});
|
|
1658
1719
|
}
|
|
1659
|
-
}, [
|
|
1720
|
+
}, [hasPreviousPage, startCursor, pageSize]);
|
|
1660
1721
|
const reset = useCallback(() => {
|
|
1661
1722
|
setVariables({
|
|
1662
1723
|
last: undefined,
|
|
@@ -1871,21 +1932,31 @@ const useScrollBlock = (scrollContainer = typeof document !== "undefined" ? docu
|
|
|
1871
1932
|
const SCROLL_DEBOUNCE_TIME = 50;
|
|
1872
1933
|
/**
|
|
1873
1934
|
* Hook for detecting scroll values in horizontal or vertical direction.
|
|
1935
|
+
* Returns a ref callback to attach to the element you want to observe.
|
|
1874
1936
|
*
|
|
1875
|
-
* @param {useRef} elementRef - Ref hook holding the element that needs to be observed during scrolling
|
|
1876
1937
|
* @param {object} options - Options object containing direction, onScrollStateChange callback, and skip
|
|
1877
|
-
* @returns {object} An object containing
|
|
1938
|
+
* @returns {object} An object containing ref callback, element, and scroll state (isScrollable, isAtBeginning, isAtEnd, scrollPosition)
|
|
1939
|
+
* @template TElement extends HTMLElement
|
|
1940
|
+
* @example
|
|
1941
|
+
* ```tsx
|
|
1942
|
+
* const { ref, isScrollable, isAtBeginning } = useScrollDetection({ direction: "horizontal" });
|
|
1943
|
+
* return <div ref={ref}>Scrollable content</div>;
|
|
1944
|
+
* ```
|
|
1878
1945
|
*/
|
|
1879
|
-
const useScrollDetection = (
|
|
1946
|
+
const useScrollDetection = (options) => {
|
|
1880
1947
|
const { direction = "vertical", onScrollStateChange, skip = false } = options ?? {};
|
|
1948
|
+
const [element, setElement] = useState(null);
|
|
1881
1949
|
const [isScrollable, setIsScrollable] = useState(false);
|
|
1882
1950
|
const [isAtBeginning, setIsAtBeginning] = useState(true);
|
|
1883
1951
|
const [isAtEnd, setIsAtEnd] = useState(false);
|
|
1884
1952
|
const [scrollPosition, setScrollPosition] = useState({ start: 0, end: 0 });
|
|
1885
1953
|
const observerRef = useRef(null);
|
|
1886
1954
|
const isFirstRender = useIsFirstRender();
|
|
1955
|
+
// Callback ref to track the element
|
|
1956
|
+
const ref = useCallback((node) => {
|
|
1957
|
+
setElement(node);
|
|
1958
|
+
}, []);
|
|
1887
1959
|
const checkScrollable = useCallback(() => {
|
|
1888
|
-
const element = elementRef?.current;
|
|
1889
1960
|
if (!element) {
|
|
1890
1961
|
return;
|
|
1891
1962
|
}
|
|
@@ -1899,9 +1970,8 @@ const useScrollDetection = (elementRef, options) => {
|
|
|
1899
1970
|
}
|
|
1900
1971
|
return prev;
|
|
1901
1972
|
});
|
|
1902
|
-
}, [
|
|
1973
|
+
}, [element, direction]);
|
|
1903
1974
|
const checkScrollPosition = useCallback(() => {
|
|
1904
|
-
const element = elementRef?.current;
|
|
1905
1975
|
if (!element) {
|
|
1906
1976
|
return;
|
|
1907
1977
|
}
|
|
@@ -1923,8 +1993,16 @@ const useScrollDetection = (elementRef, options) => {
|
|
|
1923
1993
|
setIsAtEnd(newIsAtEnd);
|
|
1924
1994
|
setScrollPosition(newScrollPosition);
|
|
1925
1995
|
}
|
|
1926
|
-
}, [
|
|
1996
|
+
}, [element, direction]);
|
|
1927
1997
|
const debouncedCheckScrollPosition = useDebounceCallback(checkScrollPosition, SCROLL_DEBOUNCE_TIME);
|
|
1998
|
+
const checkScrollableRef = useRef(checkScrollable);
|
|
1999
|
+
useEffect(() => {
|
|
2000
|
+
checkScrollableRef.current = checkScrollable;
|
|
2001
|
+
}, [checkScrollable]);
|
|
2002
|
+
const checkScrollPositionRef = useRef(checkScrollPosition);
|
|
2003
|
+
useEffect(() => {
|
|
2004
|
+
checkScrollPositionRef.current = checkScrollPosition;
|
|
2005
|
+
}, [checkScrollPosition]);
|
|
1928
2006
|
// Notify about state changes whenever any state value changes
|
|
1929
2007
|
useEffect(() => {
|
|
1930
2008
|
if (isFirstRender) {
|
|
@@ -1938,19 +2016,15 @@ const useScrollDetection = (elementRef, options) => {
|
|
|
1938
2016
|
});
|
|
1939
2017
|
}, [isScrollable, isAtBeginning, isAtEnd, scrollPosition, onScrollStateChange, isFirstRender]);
|
|
1940
2018
|
useEffect(() => {
|
|
1941
|
-
if (skip) {
|
|
1942
|
-
return;
|
|
1943
|
-
}
|
|
1944
|
-
const element = elementRef?.current;
|
|
1945
|
-
if (!element) {
|
|
2019
|
+
if (skip || !element) {
|
|
1946
2020
|
return;
|
|
1947
2021
|
}
|
|
1948
2022
|
// Initial checks
|
|
1949
|
-
|
|
1950
|
-
|
|
2023
|
+
checkScrollableRef.current();
|
|
2024
|
+
checkScrollPositionRef.current();
|
|
1951
2025
|
observerRef.current = new ResizeObserver(() => {
|
|
1952
|
-
|
|
1953
|
-
|
|
2026
|
+
checkScrollableRef.current();
|
|
2027
|
+
checkScrollPositionRef.current();
|
|
1954
2028
|
});
|
|
1955
2029
|
observerRef.current.observe(element);
|
|
1956
2030
|
element.addEventListener("scroll", debouncedCheckScrollPosition);
|
|
@@ -1960,8 +2034,8 @@ const useScrollDetection = (elementRef, options) => {
|
|
|
1960
2034
|
}
|
|
1961
2035
|
element.removeEventListener("scroll", debouncedCheckScrollPosition);
|
|
1962
2036
|
};
|
|
1963
|
-
}, [
|
|
1964
|
-
return useMemo(() => ({ isScrollable, isAtBeginning, isAtEnd, scrollPosition }), [isScrollable, isAtBeginning, isAtEnd, scrollPosition]);
|
|
2037
|
+
}, [element, debouncedCheckScrollPosition, skip]);
|
|
2038
|
+
return useMemo(() => ({ ref, element, isScrollable, isAtBeginning, isAtEnd, scrollPosition }), [ref, element, isScrollable, isAtBeginning, isAtEnd, scrollPosition]);
|
|
1965
2039
|
};
|
|
1966
2040
|
|
|
1967
2041
|
/**
|
|
@@ -2014,24 +2088,28 @@ const useViewportBreakpoints = (options = {}) => {
|
|
|
2014
2088
|
}), { ...defaultBreakpointState });
|
|
2015
2089
|
setViewportSize(newViewportSize);
|
|
2016
2090
|
}, []);
|
|
2091
|
+
const updateViewportSizeRef = useRef(updateViewportSize);
|
|
2092
|
+
useEffect(() => {
|
|
2093
|
+
updateViewportSizeRef.current = updateViewportSize;
|
|
2094
|
+
}, [updateViewportSize]);
|
|
2017
2095
|
useEffect(() => {
|
|
2018
2096
|
if (skip) {
|
|
2019
2097
|
return;
|
|
2020
2098
|
}
|
|
2021
2099
|
// Initial check
|
|
2022
|
-
|
|
2100
|
+
updateViewportSizeRef.current();
|
|
2023
2101
|
// Set up listeners for each breakpoint
|
|
2024
2102
|
const mediaQueryLists = objectEntries(themeScreenSizeAsNumber).map(([_, minWidth]) => window.matchMedia(`(min-width: ${minWidth}px)`));
|
|
2025
2103
|
mediaQueryLists.forEach(mql => {
|
|
2026
|
-
mql.addEventListener("change",
|
|
2104
|
+
mql.addEventListener("change", updateViewportSizeRef.current);
|
|
2027
2105
|
});
|
|
2028
2106
|
// Cleanup
|
|
2029
2107
|
return () => {
|
|
2030
2108
|
mediaQueryLists.forEach(mql => {
|
|
2031
|
-
mql.removeEventListener("change",
|
|
2109
|
+
mql.removeEventListener("change", updateViewportSizeRef.current);
|
|
2032
2110
|
});
|
|
2033
2111
|
};
|
|
2034
|
-
}, [
|
|
2112
|
+
}, [skip]);
|
|
2035
2113
|
return viewportSize;
|
|
2036
2114
|
};
|
|
2037
2115
|
|
|
@@ -2049,11 +2127,15 @@ const useWindowActivity = ({ onFocus, onBlur, skip = false } = { onBlur: undefin
|
|
|
2049
2127
|
setFocused(hasFocus());
|
|
2050
2128
|
onBlur?.();
|
|
2051
2129
|
}, [onBlur]);
|
|
2130
|
+
const setFocusedRef = useRef(setFocused);
|
|
2131
|
+
useEffect(() => {
|
|
2132
|
+
setFocusedRef.current = setFocused;
|
|
2133
|
+
}, [setFocused]);
|
|
2052
2134
|
useEffect(() => {
|
|
2053
2135
|
if (skip) {
|
|
2054
2136
|
return;
|
|
2055
2137
|
}
|
|
2056
|
-
|
|
2138
|
+
setFocusedRef.current(hasFocus());
|
|
2057
2139
|
window.addEventListener("focus", onFocusInternal);
|
|
2058
2140
|
window.addEventListener("blur", onBlurInternal);
|
|
2059
2141
|
return () => {
|
|
@@ -2967,16 +3049,20 @@ const OverflowIndicator = ({ className, dataTestId, direction, onClickScroll, })
|
|
|
2967
3049
|
};
|
|
2968
3050
|
|
|
2969
3051
|
/**
|
|
2970
|
-
* Container for displaying components in a horizontal layout with overflow detection
|
|
3052
|
+
* Container for displaying components in a horizontal layout with overflow detection.
|
|
3053
|
+
* Provides visual indicators when content overflows and can be scrolled.
|
|
2971
3054
|
*
|
|
2972
|
-
* @param
|
|
2973
|
-
* @
|
|
3055
|
+
* @param props - Component properties
|
|
3056
|
+
* @param props.children - The content to display in the horizontal scroller
|
|
3057
|
+
* @param props.className - Optional CSS class name for styling
|
|
3058
|
+
* @param props.dataTestId - Optional test ID for testing purposes
|
|
3059
|
+
* @param props.onScrollStateChange - Optional callback fired when scroll state changes
|
|
3060
|
+
* @returns {ReactElement} A horizontal overflow scroller component with visual indicators
|
|
2974
3061
|
*/
|
|
2975
3062
|
const HorizontalOverflowScroller = ({ className, dataTestId, children, onScrollStateChange, }) => {
|
|
2976
3063
|
const childrenArray = Children.toArray(children);
|
|
2977
|
-
const
|
|
2978
|
-
const
|
|
2979
|
-
const { isScrollable, isAtBeginning, isAtEnd } = useScrollDetection(containerRef, {
|
|
3064
|
+
const { geometry: containerGeometry, ref: measureRef, element } = useMeasure();
|
|
3065
|
+
const { ref: scrollRef, isScrollable, isAtBeginning, isAtEnd, } = useScrollDetection({
|
|
2980
3066
|
direction: "horizontal",
|
|
2981
3067
|
onScrollStateChange: onScrollStateChange
|
|
2982
3068
|
? (state) => onScrollStateChange({
|
|
@@ -2986,23 +3072,24 @@ const HorizontalOverflowScroller = ({ className, dataTestId, children, onScrollS
|
|
|
2986
3072
|
})
|
|
2987
3073
|
: undefined,
|
|
2988
3074
|
});
|
|
3075
|
+
const mergedRef = useMergeRefs([measureRef, scrollRef]);
|
|
2989
3076
|
const handleScrollLeft = () => {
|
|
2990
|
-
if (!
|
|
3077
|
+
if (!element || containerGeometry?.width === undefined)
|
|
2991
3078
|
return;
|
|
2992
|
-
|
|
3079
|
+
element.scrollBy({
|
|
2993
3080
|
left: -containerGeometry.width,
|
|
2994
3081
|
behavior: "smooth",
|
|
2995
3082
|
});
|
|
2996
3083
|
};
|
|
2997
3084
|
const handleScrollRight = () => {
|
|
2998
|
-
if (!
|
|
3085
|
+
if (!element || containerGeometry?.width === undefined)
|
|
2999
3086
|
return;
|
|
3000
|
-
|
|
3087
|
+
element.scrollBy({
|
|
3001
3088
|
left: containerGeometry.width,
|
|
3002
3089
|
behavior: "smooth",
|
|
3003
3090
|
});
|
|
3004
3091
|
};
|
|
3005
|
-
return (jsxs(ZStack, { className: cvaHorizontalOverflowScrollerAndIndicatorsContainer({ className }), children: [jsx("div", { className: cvaHorizontalOverflowScroller(), "data-testid": dataTestId, ref:
|
|
3092
|
+
return (jsxs(ZStack, { className: cvaHorizontalOverflowScrollerAndIndicatorsContainer({ className }), children: [jsx("div", { className: cvaHorizontalOverflowScroller(), "data-testid": dataTestId, ref: mergedRef, children: childrenArray.map((child, index) => (jsx(Fragment, { children: child }, index))) }), isScrollable && !isAtBeginning ? (jsx(OverflowIndicator, { dataTestId: `${dataTestId}-left-indicator`, direction: "left", onClickScroll: handleScrollLeft })) : null, isScrollable && !isAtEnd ? (jsx(OverflowIndicator, { dataTestId: `${dataTestId}-right-indicator`, direction: "right", onClickScroll: handleScrollRight })) : null] }));
|
|
3006
3093
|
};
|
|
3007
3094
|
|
|
3008
3095
|
const PADDING = 12;
|
|
@@ -3282,7 +3369,7 @@ const cvaPopoverTitleText = cvaMerge(["flex-1", "text-neutral-500"]);
|
|
|
3282
3369
|
*/
|
|
3283
3370
|
const PopoverContent = function PopoverContent({ className, dataTestId, children, portalId, ref: propRef, ...props }) {
|
|
3284
3371
|
const { context: floatingContext, customProps, ...context } = usePopoverContext();
|
|
3285
|
-
const ref = useMergeRefs([context.refs.setFloating, propRef]);
|
|
3372
|
+
const ref = useMergeRefs$1([context.refs.setFloating, propRef]);
|
|
3286
3373
|
return (jsx(Portal, { id: portalId, children: context.isOpen === true ? (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 ?? customProps.className }), "data-testid": dataTestId ?? customProps.dataTestId ?? "popover-content", ref: ref, style: {
|
|
3287
3374
|
position: context.strategy,
|
|
3288
3375
|
top: context.y,
|
|
@@ -3310,14 +3397,16 @@ const PopoverTitle = ({ children, action, divider = false, className, dataTestId
|
|
|
3310
3397
|
*/
|
|
3311
3398
|
const PopoverTrigger = function PopoverTrigger({ children, renderButton = false, ref: propRef, ...props }) {
|
|
3312
3399
|
const context = usePopoverContext();
|
|
3313
|
-
const ref = useMergeRefs([context.refs.setReference, propRef]);
|
|
3400
|
+
const ref = useMergeRefs$1([context.refs.setReference, propRef]);
|
|
3314
3401
|
if (!renderButton && isValidElement(children)) {
|
|
3315
|
-
|
|
3316
|
-
ref,
|
|
3402
|
+
const referenceProps = context.getReferenceProps({
|
|
3317
3403
|
...props,
|
|
3318
3404
|
...children.props,
|
|
3319
3405
|
"data-state": context.isOpen === true ? "open" : "closed",
|
|
3320
|
-
})
|
|
3406
|
+
});
|
|
3407
|
+
const cloneProps = { ...referenceProps };
|
|
3408
|
+
cloneProps.ref = ref;
|
|
3409
|
+
return cloneElement(children, cloneProps);
|
|
3321
3410
|
}
|
|
3322
3411
|
return (jsx(Button, { "data-state": context.isOpen === true ? "open" : "closed", ref: ref, type: "button", ...context.getReferenceProps(props), children: children }));
|
|
3323
3412
|
};
|
|
@@ -4131,7 +4220,10 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
|
|
|
4131
4220
|
const isAtTop = useMemo(() => virtualizer.scrollOffset === 0, [virtualizer.scrollOffset]);
|
|
4132
4221
|
// totalSize must be out from from useMemo since otherwise we'll not be able to have the the totalSize as a dependency for the contentFillsContainer
|
|
4133
4222
|
const totalSize = virtualizer.getTotalSize();
|
|
4134
|
-
const contentFillsContainer = useMemo(() =>
|
|
4223
|
+
const contentFillsContainer = useMemo(() => {
|
|
4224
|
+
// eslint-disable-next-line react-hooks/refs
|
|
4225
|
+
return containerRef.current ? totalSize >= containerRef.current.clientHeight : false;
|
|
4226
|
+
}, [totalSize]);
|
|
4135
4227
|
return {
|
|
4136
4228
|
...virtualizer,
|
|
4137
4229
|
containerRef,
|
|
@@ -4797,12 +4889,20 @@ const Pagination = ({ previousPage, nextPage, canPreviousPage = false, canNextPa
|
|
|
4797
4889
|
if (!loading && pageCount === undefined) {
|
|
4798
4890
|
throw Error("Pagination should be provided with a pageCount");
|
|
4799
4891
|
}
|
|
4892
|
+
const setCurrentPageRef = useRef(setCurrentPage);
|
|
4893
|
+
useEffect(() => {
|
|
4894
|
+
setCurrentPageRef.current = setCurrentPage;
|
|
4895
|
+
}, [setCurrentPage]);
|
|
4896
|
+
const setPageRef = useRef(setPage);
|
|
4897
|
+
useEffect(() => {
|
|
4898
|
+
setPageRef.current = setPage;
|
|
4899
|
+
}, [setPage]);
|
|
4800
4900
|
useEffect(() => {
|
|
4801
4901
|
if (pageIndex !== undefined && (isNaN(pageIndex) || pageIndex < 0)) {
|
|
4802
|
-
|
|
4803
|
-
|
|
4902
|
+
setPageRef.current(pageIndex);
|
|
4903
|
+
setCurrentPageRef.current(String(pageIndex + 1));
|
|
4804
4904
|
}
|
|
4805
|
-
}, [pageIndex,
|
|
4905
|
+
}, [pageIndex, pageCount]);
|
|
4806
4906
|
const handlePageChange = useCallback((action) => {
|
|
4807
4907
|
const from = page;
|
|
4808
4908
|
let to = from;
|
|
@@ -5302,17 +5402,25 @@ const cvaToggleItemContent = cvaMerge([], {
|
|
|
5302
5402
|
*/
|
|
5303
5403
|
const ToggleGroup = ({ list, selected, setSelected, onChange, disabled = false, size = "medium", isIconOnly = false, className, dataTestId, }) => {
|
|
5304
5404
|
const [isMounted, setIsMounted] = useState(false);
|
|
5405
|
+
const [slidingLeft, setSlidingLeft] = useState(0);
|
|
5406
|
+
const [slidingWidth, setSlidingWidth] = useState(0);
|
|
5305
5407
|
const buttonRefs = useRef([]);
|
|
5306
5408
|
const selectedIndex = list.findIndex(item => item.id === selected);
|
|
5307
5409
|
const validIndex = selectedIndex >= 0 ? selectedIndex : 0;
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
const slidingWidth = buttonRefs.current[validIndex]?.offsetWidth ?? 0;
|
|
5313
|
-
useEffect(() => {
|
|
5314
|
-
setIsMounted(true);
|
|
5410
|
+
useLayoutEffect(() => {
|
|
5411
|
+
queueMicrotask(() => {
|
|
5412
|
+
setIsMounted(true);
|
|
5413
|
+
});
|
|
5315
5414
|
}, []);
|
|
5415
|
+
useEffect(() => {
|
|
5416
|
+
const containerPadding = 2; // p-0.5 = 2px
|
|
5417
|
+
const gap = 4;
|
|
5418
|
+
const left = containerPadding +
|
|
5419
|
+
buttonRefs.current.slice(0, validIndex).reduce((sum, ref) => sum + (ref?.offsetWidth ?? 0) + gap, 0);
|
|
5420
|
+
const width = buttonRefs.current[validIndex]?.offsetWidth ?? 0;
|
|
5421
|
+
setSlidingLeft(left);
|
|
5422
|
+
setSlidingWidth(width);
|
|
5423
|
+
}, [validIndex]);
|
|
5316
5424
|
return (jsx("div", { className: twMerge(cvaToggleGroup({ className }), cvaToggleGroupWithSlidingBackground({ isMounted })), "data-testid": dataTestId, style:
|
|
5317
5425
|
// eslint-disable-next-line local-rules/no-typescript-assertion
|
|
5318
5426
|
{
|
|
@@ -5487,4 +5595,4 @@ const cvaClickable = cvaMerge([
|
|
|
5487
5595
|
},
|
|
5488
5596
|
});
|
|
5489
5597
|
|
|
5490
|
-
export { ActionRenderer, Alert, Badge, Breadcrumb, BreadcrumbContainer, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, DetailsList, EmptyState, EmptyValue, ExternalLink, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, List, ListItem, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, Prompt, ROLE_CARD, SectionHeader, Sidebar, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, ValueBar, ZStack, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getResponsiveRandomWidthPercentage, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useList, useListItemHeight, useMeasure,
|
|
5598
|
+
export { ActionRenderer, Alert, Badge, Breadcrumb, BreadcrumbContainer, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, DetailsList, EmptyState, EmptyValue, ExternalLink, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, List, ListItem, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, Prompt, ROLE_CARD, SectionHeader, Sidebar, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, ValueBar, ZStack, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getResponsiveRandomWidthPercentage, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useList, useListItemHeight, useMeasure, useMergeRefs, useModifierKey, useOverflowItems, usePopoverContext, usePrompt, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSelfUpdatingRef, useTimeout, useViewportBreakpoints, useWindowActivity };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-components",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.43",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
@@ -12,15 +12,13 @@
|
|
|
12
12
|
"react": "19.0.0",
|
|
13
13
|
"usehooks-ts": "^3.1.0",
|
|
14
14
|
"react-helmet-async": "^1.3.0",
|
|
15
|
-
"@testing-library/react": "16.2.0",
|
|
16
15
|
"@floating-ui/react": "^0.26.25",
|
|
17
16
|
"string-ts": "^2.0.0",
|
|
18
17
|
"tailwind-merge": "^2.0.0",
|
|
19
|
-
"@trackunit/ui-design-tokens": "1.7.
|
|
20
|
-
"@trackunit/css-class-variance-utilities": "1.7.
|
|
21
|
-
"@trackunit/shared-utils": "1.9.
|
|
22
|
-
"@trackunit/ui-icons": "1.7.
|
|
23
|
-
"@trackunit/react-test-setup": "1.4.70",
|
|
18
|
+
"@trackunit/ui-design-tokens": "1.7.73",
|
|
19
|
+
"@trackunit/css-class-variance-utilities": "1.7.73",
|
|
20
|
+
"@trackunit/shared-utils": "1.9.73",
|
|
21
|
+
"@trackunit/ui-icons": "1.7.74",
|
|
24
22
|
"@tanstack/react-router": "1.114.29",
|
|
25
23
|
"es-toolkit": "^1.39.10",
|
|
26
24
|
"@tanstack/react-virtual": "3.13.12"
|
|
@@ -13,9 +13,14 @@ export interface HorizontalOverflowScrollerProps extends CommonProps, Styleable
|
|
|
13
13
|
}) => void;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Container for displaying components in a horizontal layout with overflow detection
|
|
16
|
+
* Container for displaying components in a horizontal layout with overflow detection.
|
|
17
|
+
* Provides visual indicators when content overflows and can be scrolled.
|
|
17
18
|
*
|
|
18
|
-
* @param
|
|
19
|
-
* @
|
|
19
|
+
* @param props - Component properties
|
|
20
|
+
* @param props.children - The content to display in the horizontal scroller
|
|
21
|
+
* @param props.className - Optional CSS class name for styling
|
|
22
|
+
* @param props.dataTestId - Optional test ID for testing purposes
|
|
23
|
+
* @param props.onScrollStateChange - Optional callback fired when scroll state changes
|
|
24
|
+
* @returns {ReactElement} A horizontal overflow scroller component with visual indicators
|
|
20
25
|
*/
|
|
21
26
|
export declare const HorizontalOverflowScroller: ({ className, dataTestId, children, onScrollStateChange, }: HorizontalOverflowScrollerProps) => ReactElement;
|