@trackunit/react-components 0.4.34 → 0.5.1

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
@@ -13,9 +13,8 @@ var stringTs = require('string-ts');
13
13
  var cssClassVarianceUtilities = require('@trackunit/css-class-variance-utilities');
14
14
  var reactSlot = require('@radix-ui/react-slot');
15
15
  var isEqual = require('lodash/isEqual');
16
- var reactRouter = require('@tanstack/react-router');
17
16
  var usehooksTs = require('usehooks-ts');
18
- var reactSwipeable = require('react-swipeable');
17
+ var reactRouter = require('@tanstack/react-router');
19
18
  var react = require('@floating-ui/react');
20
19
  var omit = require('lodash/omit');
21
20
  var tailwindMerge = require('tailwind-merge');
@@ -1310,6 +1309,62 @@ const getWindowSize = () => {
1310
1309
  }
1311
1310
  };
1312
1311
 
1312
+ const SCROLL_DEBOUNCE_TIME = 50;
1313
+ /**
1314
+ * Hook for getting detecting scroll values.
1315
+ *
1316
+ * @param {useRef} elementRef - Ref hook holding the element that needs to be observed during scrolling
1317
+ * @returns {object} An object containing if the element is scrollable, is at the top, is at the bottom, and its current scroll position.
1318
+ */
1319
+ const useScrollDetection = (elementRef) => {
1320
+ const [isScrollable, setIsScrollable] = React.useState(false);
1321
+ const [isAtTop, setIsAtTop] = React.useState(true);
1322
+ const [isAtBottom, setIsAtBottom] = React.useState(false);
1323
+ const [scrollPosition, setScrollPosition] = React.useState({ top: 0, bottom: 0 });
1324
+ const observerRef = React.useRef();
1325
+ const checkScrollable = React.useCallback(() => {
1326
+ const element = elementRef.current;
1327
+ if (!element) {
1328
+ return;
1329
+ }
1330
+ const hasOverflow = element.scrollHeight > element.clientHeight;
1331
+ setIsScrollable(hasOverflow);
1332
+ }, [elementRef]);
1333
+ const checkScrollPosition = React.useCallback(() => {
1334
+ const element = elementRef.current;
1335
+ if (!element) {
1336
+ return;
1337
+ }
1338
+ const { scrollTop, scrollHeight, clientHeight } = element;
1339
+ setIsAtTop(scrollTop === 0);
1340
+ setIsAtBottom(Math.abs(scrollHeight - scrollTop - clientHeight) < 1);
1341
+ setScrollPosition(prev => ({ ...prev, top: scrollTop, bottom: clientHeight - scrollTop }));
1342
+ }, [elementRef]);
1343
+ const debouncedCheckScrollPosition = usehooksTs.useDebounceCallback(checkScrollPosition, SCROLL_DEBOUNCE_TIME);
1344
+ React.useEffect(() => {
1345
+ const element = elementRef.current;
1346
+ if (!element) {
1347
+ return;
1348
+ }
1349
+ // Initial checks
1350
+ checkScrollable();
1351
+ checkScrollPosition();
1352
+ observerRef.current = new ResizeObserver(() => {
1353
+ checkScrollable();
1354
+ checkScrollPosition();
1355
+ });
1356
+ observerRef.current.observe(element);
1357
+ element.addEventListener("scroll", debouncedCheckScrollPosition);
1358
+ return () => {
1359
+ if (observerRef.current) {
1360
+ observerRef.current.disconnect();
1361
+ }
1362
+ element.removeEventListener("scroll", debouncedCheckScrollPosition);
1363
+ };
1364
+ }, [elementRef, checkScrollable, checkScrollPosition, debouncedCheckScrollPosition]);
1365
+ return { isScrollable, isAtTop, isAtBottom, scrollPosition };
1366
+ };
1367
+
1313
1368
  /**
1314
1369
  * A useRef that updates its given value whenever it changes.
1315
1370
  *
@@ -1881,331 +1936,6 @@ const CopyableText = ({ text, alternativeText, dataTestId, className }) => {
1881
1936
  return (jsxRuntime.jsx("span", { className: cvaCopyableText({ animating, className }), "data-testid": dataTestId, onAnimationEnd: () => setAnimating(false), onClick: handleOnClick, title: value, children: text }));
1882
1937
  };
1883
1938
 
1884
- const cvaDialog = cssClassVarianceUtilities.cvaMerge([
1885
- "z-toast",
1886
- "pointer-events-auto",
1887
- "absolute",
1888
- "transform",
1889
- "flex-col",
1890
- "bg-neutral-50",
1891
- "transition-transform",
1892
- "duration-300",
1893
- "ease-in-out",
1894
- "shadow-lg",
1895
- ], {
1896
- variants: {
1897
- position: {
1898
- left: "left-0 top-0 h-full w-fit rounded-r-lg",
1899
- right: "right-0 top-0 h-full w-fit rounded-l-lg",
1900
- top: "left-0 top-0 h-fit w-full rounded-b-lg",
1901
- bottom: "bottom-0 left-0 right-0 h-fit w-full rounded-t-lg",
1902
- },
1903
- sidebarMode: {
1904
- "semi-closed": "translate-y-[calc(100%-2.2rem)]",
1905
- "1/3": "translate-y-2/3",
1906
- "2/3": "translate-y-1/3",
1907
- full: "translate-y-0",
1908
- "semi-full": "translate-y-5",
1909
- closed: "translate-y-full",
1910
- },
1911
- isLargeScreen: {
1912
- true: "",
1913
- false: "bottom-0 left-0 right-0 h-full w-full rounded-t-lg",
1914
- },
1915
- open: {
1916
- true: "",
1917
- false: "",
1918
- },
1919
- },
1920
- compoundVariants: [
1921
- {
1922
- isLargeScreen: false,
1923
- open: false,
1924
- className: "translate-y-full",
1925
- },
1926
- {
1927
- isLargeScreen: true,
1928
- open: true,
1929
- position: "left",
1930
- className: "translate-x-0",
1931
- },
1932
- {
1933
- isLargeScreen: true,
1934
- open: false,
1935
- position: "left",
1936
- className: "-translate-x-full",
1937
- },
1938
- {
1939
- isLargeScreen: true,
1940
- open: true,
1941
- position: "right",
1942
- className: "translate-x-0",
1943
- },
1944
- {
1945
- isLargeScreen: true,
1946
- open: false,
1947
- position: "right",
1948
- className: "translate-x-full",
1949
- },
1950
- {
1951
- isLargeScreen: true,
1952
- open: true,
1953
- position: "top",
1954
- className: "translate-y-0",
1955
- },
1956
- {
1957
- isLargeScreen: true,
1958
- open: false,
1959
- position: "top",
1960
- className: "-translate-y-full",
1961
- },
1962
- {
1963
- isLargeScreen: true,
1964
- open: true,
1965
- position: "bottom",
1966
- className: "translate-y-0",
1967
- },
1968
- {
1969
- isLargeScreen: true,
1970
- open: false,
1971
- position: "bottom",
1972
- className: "translate-y-full",
1973
- },
1974
- ],
1975
- });
1976
- const cvaDialogContainer = cssClassVarianceUtilities.cvaMerge(["flex", "flex-col", "overflow-hidden", "rounded-[inherit]"], {
1977
- variants: {
1978
- sidebarMode: {
1979
- "semi-closed": "h-auto",
1980
- "1/3": "h-[34%]",
1981
- "2/3": "h-[67%]",
1982
- "semi-full": "h-[calc(100%-1.25rem)]",
1983
- full: "h-full",
1984
- closed: "h-auto",
1985
- },
1986
- },
1987
- });
1988
- const cvaSwipeContainer = cssClassVarianceUtilities.cvaMerge(["my-4", "flex", "items-center", "justify-center", "lg:hidden"]);
1989
- const cvaSwipeIcon = cssClassVarianceUtilities.cvaMerge(["block", "h-1", "w-10", "rounded-full", "bg-gray-400"]);
1990
- const cvaToogleContainer = cssClassVarianceUtilities.cvaMerge(["z-8", "absolute"], {
1991
- variants: {
1992
- position: {
1993
- left: "right-[-24px] top-[calc(50%-24px)]",
1994
- right: "left-[-24px] top-[calc(50%-24px)]",
1995
- top: "bottom-[-24px] left-[calc(50%-24px)]",
1996
- bottom: "left-[calc(50%-24px)] top-[-24px]",
1997
- },
1998
- },
1999
- defaultVariants: {
2000
- position: "left",
2001
- },
2002
- });
2003
- const cvaToogleButton = cssClassVarianceUtilities.cvaMerge([
2004
- "flex",
2005
- "cursor-pointer",
2006
- "items-center",
2007
- "justify-center",
2008
- "border-gray-300",
2009
- "bg-neutral-50",
2010
- "bg-center",
2011
- "bg-no-repeat",
2012
- "shadow-md",
2013
- ], {
2014
- variants: {
2015
- position: {
2016
- left: "h-12 w-6 rounded-r-lg",
2017
- right: "h-12 w-6 rounded-l-lg",
2018
- top: "h-6 w-12 rounded-b-lg",
2019
- bottom: "h-6 w-12 rounded-t-lg",
2020
- },
2021
- },
2022
- defaultVariants: {
2023
- position: "left",
2024
- },
2025
- });
2026
- const cvaOverlayContainer = cssClassVarianceUtilities.cvaMerge([
2027
- "absolute",
2028
- "flex",
2029
- "items-center",
2030
- "justify-center",
2031
- "inset-0",
2032
- "bg-black/30",
2033
- "bg-opacity-50",
2034
- "transition-opacity",
2035
- "duration-200",
2036
- "ease-in-out",
2037
- "opacity-100",
2038
- ], {
2039
- variants: {
2040
- open: {
2041
- true: "opacity-1 z-popover",
2042
- false: "z-[-1] opacity-0",
2043
- },
2044
- },
2045
- });
2046
-
2047
- /**
2048
- * Overlay Component
2049
- *
2050
- * @param {object} props - The Overlay component properties
2051
- * @param {boolean} props.open - Open status of the Overlay
2052
- * @param {Function} props.onClose - Callback function when Overlay is closed
2053
- * @returns {JSX.Element|null} The Overlay component
2054
- */
2055
- const Overlay = ({ open, onClose }) => {
2056
- React.useEffect(() => {
2057
- if (!onClose) {
2058
- return;
2059
- }
2060
- const handleEscape = (event) => {
2061
- if (event.key === "Escape") {
2062
- onClose(event);
2063
- }
2064
- };
2065
- if (open) {
2066
- window.addEventListener("keyup", handleEscape);
2067
- }
2068
- return () => {
2069
- window.removeEventListener("keyup", handleEscape);
2070
- };
2071
- }, [open, onClose]);
2072
- const handleClick = (event) => {
2073
- if (onClose) {
2074
- onClose(event);
2075
- }
2076
- };
2077
- return (jsxRuntime.jsx("div", { "aria-hidden": open, className: cvaOverlayContainer({ open }), "data-testid": "sidebar-overlay", onClick: handleClick }));
2078
- };
2079
-
2080
- const getIconName = (open, position) => {
2081
- switch (position) {
2082
- case "left":
2083
- return open ? "ChevronLeft" : "ChevronRight";
2084
- case "right":
2085
- return open ? "ChevronRight" : "ChevronLeft";
2086
- case "top":
2087
- return open ? "ChevronUp" : "ChevronDown";
2088
- case "bottom":
2089
- return open ? "ChevronDown" : "ChevronUp";
2090
- default:
2091
- return open ? "ChevronLeft" : "ChevronRight";
2092
- }
2093
- };
2094
- /**
2095
- * ToggleButton is a React functional component that returns a button with a chevron icon.
2096
- * The direction of the chevron changes depending on the state of the 'open' prop and
2097
- * the side the button is positioned ('position' prop).
2098
- * The button might be disabled based on the 'disableButton' prop.
2099
- *
2100
- * @param {object} props - The properties passed to the component
2101
- * @param {boolean} props.open - Indicates if the button is in "open" state
2102
- * @param {Function} [props.onToggle] - Optional callback function for when the button is clicked
2103
- * @param {Position} props.position - The position of the button relative to its container
2104
- */
2105
- const ToogleButton = ({ open, position, onToggle }) => {
2106
- const name = getIconName(open, position);
2107
- return (jsxRuntime.jsx("div", { className: cvaToogleContainer({ position }), children: jsxRuntime.jsx("button", { className: cvaToogleButton({ position }), "data-testid": "toggle-button", onClick: onToggle, children: jsxRuntime.jsx(Icon, { name: name }) }) }));
2108
- };
2109
-
2110
- /**
2111
- * MapSidebar is a sidebar component used with Maps.
2112
- * It provides a slide over sidebar drawer which can be used for displaying map related information or controls.
2113
- *
2114
- * @param {DrawerProps} props - The props for the MapSidebar component
2115
- * @returns {JSX.Element | null} Drawer component
2116
- */
2117
- const Drawer = ({ open = true, onToggle, onClose, disableOverlay, position = "left", children, dataTestId, className, dialogClassName, }) => {
2118
- const { width } = useResize();
2119
- const isLargeScreen = width >= 1024;
2120
- const initialSidebarMode = () => {
2121
- if (!open) {
2122
- return "closed";
2123
- }
2124
- return isLargeScreen ? "full" : "2/3";
2125
- };
2126
- const [sidebarMode, setSidebarMode] = React.useState(initialSidebarMode);
2127
- const [isVisible, setIsVisible] = React.useState(open);
2128
- const [isAnimationStart, setStartAnimation] = React.useState(open);
2129
- React.useEffect(() => {
2130
- if (!isLargeScreen && open) {
2131
- setSidebarMode("2/3");
2132
- }
2133
- if (onToggle) {
2134
- setStartAnimation(open);
2135
- return;
2136
- }
2137
- manageVisibilityAndAnimation(open);
2138
- }, [onToggle, isLargeScreen, open]);
2139
- const manageVisibilityAndAnimation = (isOpen) => {
2140
- if (!isOpen) {
2141
- setStartAnimation(false);
2142
- return;
2143
- }
2144
- setIsVisible(true);
2145
- setTimeout(() => {
2146
- setStartAnimation(true);
2147
- }, 150);
2148
- };
2149
- const handlers = reactSwipeable.useSwipeable({
2150
- onSwipedUp: () => !isLargeScreen && handleSwipedUp(),
2151
- onSwipedDown: () => !isLargeScreen && handleSwipedDown(),
2152
- trackMouse: true,
2153
- });
2154
- const handleSwipedUp = () => {
2155
- setSidebarMode(prev => {
2156
- switch (prev) {
2157
- case "semi-closed":
2158
- return "1/3";
2159
- case "1/3":
2160
- return "2/3";
2161
- case "2/3":
2162
- return "semi-full";
2163
- default:
2164
- return prev;
2165
- }
2166
- });
2167
- };
2168
- const handleSwipedDown = () => {
2169
- setSidebarMode(prev => {
2170
- switch (prev) {
2171
- case "semi-full":
2172
- return "2/3";
2173
- case "2/3":
2174
- return "1/3";
2175
- case "1/3":
2176
- return onClose ? "closed" : "semi-closed";
2177
- default:
2178
- return prev;
2179
- }
2180
- });
2181
- /*
2182
- * Within a mobile device context, when swiping down,
2183
- * if the Drawer is open a third and no onToggle function is provided,
2184
- * the Drawer will automatically be closed using the onClose function.
2185
- */
2186
- if (sidebarMode === "1/3" && !onToggle && onClose) {
2187
- onClose();
2188
- }
2189
- };
2190
- const handleAnimationEnd = () => {
2191
- if (!open && !onToggle) {
2192
- setIsVisible(false);
2193
- }
2194
- };
2195
- if (!isVisible && !onToggle) {
2196
- return null;
2197
- }
2198
- return (jsxRuntime.jsxs("div", { className: className, "data-testid": dataTestId, children: [!disableOverlay ? jsxRuntime.jsx(Overlay, { onClose: onClose, open: isAnimationStart }) : null, jsxRuntime.jsxs("div", { ...handlers, className: cvaDialog({
2199
- sidebarMode: isLargeScreen ? null : sidebarMode,
2200
- isLargeScreen,
2201
- open: isAnimationStart,
2202
- position: isLargeScreen ? position : undefined,
2203
- className: dialogClassName,
2204
- }), "data-testid": dataTestId ? `${dataTestId}-container` : undefined, onTransitionEnd: handleAnimationEnd, children: [isLargeScreen && onToggle ? jsxRuntime.jsx(ToogleButton, { onToggle: onToggle, open: open, position: position }) : null, jsxRuntime.jsxs("div", { className: cvaDialogContainer({
2205
- sidebarMode: isLargeScreen ? "full" : sidebarMode,
2206
- }), children: [jsxRuntime.jsx("div", { className: cvaSwipeContainer(), children: jsxRuntime.jsx("div", { className: cvaSwipeIcon() }) }), children] })] })] }));
2207
- };
2208
-
2209
1939
  const cvaSkeletonLine = cssClassVarianceUtilities.cvaMerge([
2210
1940
  "rounded-md",
2211
1941
  "h-3",
@@ -5164,7 +4894,6 @@ exports.CardHeader = CardHeader;
5164
4894
  exports.Collapse = Collapse;
5165
4895
  exports.CompletionStatusIndicator = CompletionStatusIndicator;
5166
4896
  exports.CopyableText = CopyableText;
5167
- exports.Drawer = Drawer;
5168
4897
  exports.EmptyState = EmptyState;
5169
4898
  exports.EmptyValue = EmptyValue;
5170
4899
  exports.ExternalLink = ExternalLink;
@@ -5250,6 +4979,7 @@ exports.usePopoverContext = usePopoverContext;
5250
4979
  exports.usePrevious = usePrevious;
5251
4980
  exports.usePrompt = usePrompt;
5252
4981
  exports.useResize = useResize;
4982
+ exports.useScrollDetection = useScrollDetection;
5253
4983
  exports.useSelfUpdatingRef = useSelfUpdatingRef;
5254
4984
  exports.useTimeout = useTimeout;
5255
4985
  exports.useViewportSize = useViewportSize;
package/index.esm.js CHANGED
@@ -12,9 +12,8 @@ import { snakeCase, titleCase } from 'string-ts';
12
12
  import { cvaMerge } from '@trackunit/css-class-variance-utilities';
13
13
  import { Slottable, Slot } from '@radix-ui/react-slot';
14
14
  import isEqual from 'lodash/isEqual';
15
+ import { useDebounceCallback, useCopyToClipboard } from 'usehooks-ts';
15
16
  import { Link, useBlocker } from '@tanstack/react-router';
16
- import { useCopyToClipboard } from 'usehooks-ts';
17
- import { useSwipeable } from 'react-swipeable';
18
17
  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';
19
18
  import omit from 'lodash/omit';
20
19
  import { twMerge } from 'tailwind-merge';
@@ -1290,6 +1289,62 @@ const getWindowSize = () => {
1290
1289
  }
1291
1290
  };
1292
1291
 
1292
+ const SCROLL_DEBOUNCE_TIME = 50;
1293
+ /**
1294
+ * Hook for getting detecting scroll values.
1295
+ *
1296
+ * @param {useRef} elementRef - Ref hook holding the element that needs to be observed during scrolling
1297
+ * @returns {object} An object containing if the element is scrollable, is at the top, is at the bottom, and its current scroll position.
1298
+ */
1299
+ const useScrollDetection = (elementRef) => {
1300
+ const [isScrollable, setIsScrollable] = useState(false);
1301
+ const [isAtTop, setIsAtTop] = useState(true);
1302
+ const [isAtBottom, setIsAtBottom] = useState(false);
1303
+ const [scrollPosition, setScrollPosition] = useState({ top: 0, bottom: 0 });
1304
+ const observerRef = useRef();
1305
+ const checkScrollable = useCallback(() => {
1306
+ const element = elementRef.current;
1307
+ if (!element) {
1308
+ return;
1309
+ }
1310
+ const hasOverflow = element.scrollHeight > element.clientHeight;
1311
+ setIsScrollable(hasOverflow);
1312
+ }, [elementRef]);
1313
+ const checkScrollPosition = useCallback(() => {
1314
+ const element = elementRef.current;
1315
+ if (!element) {
1316
+ return;
1317
+ }
1318
+ const { scrollTop, scrollHeight, clientHeight } = element;
1319
+ setIsAtTop(scrollTop === 0);
1320
+ setIsAtBottom(Math.abs(scrollHeight - scrollTop - clientHeight) < 1);
1321
+ setScrollPosition(prev => ({ ...prev, top: scrollTop, bottom: clientHeight - scrollTop }));
1322
+ }, [elementRef]);
1323
+ const debouncedCheckScrollPosition = useDebounceCallback(checkScrollPosition, SCROLL_DEBOUNCE_TIME);
1324
+ useEffect(() => {
1325
+ const element = elementRef.current;
1326
+ if (!element) {
1327
+ return;
1328
+ }
1329
+ // Initial checks
1330
+ checkScrollable();
1331
+ checkScrollPosition();
1332
+ observerRef.current = new ResizeObserver(() => {
1333
+ checkScrollable();
1334
+ checkScrollPosition();
1335
+ });
1336
+ observerRef.current.observe(element);
1337
+ element.addEventListener("scroll", debouncedCheckScrollPosition);
1338
+ return () => {
1339
+ if (observerRef.current) {
1340
+ observerRef.current.disconnect();
1341
+ }
1342
+ element.removeEventListener("scroll", debouncedCheckScrollPosition);
1343
+ };
1344
+ }, [elementRef, checkScrollable, checkScrollPosition, debouncedCheckScrollPosition]);
1345
+ return { isScrollable, isAtTop, isAtBottom, scrollPosition };
1346
+ };
1347
+
1293
1348
  /**
1294
1349
  * A useRef that updates its given value whenever it changes.
1295
1350
  *
@@ -1861,331 +1916,6 @@ const CopyableText = ({ text, alternativeText, dataTestId, className }) => {
1861
1916
  return (jsx("span", { className: cvaCopyableText({ animating, className }), "data-testid": dataTestId, onAnimationEnd: () => setAnimating(false), onClick: handleOnClick, title: value, children: text }));
1862
1917
  };
1863
1918
 
1864
- const cvaDialog = cvaMerge([
1865
- "z-toast",
1866
- "pointer-events-auto",
1867
- "absolute",
1868
- "transform",
1869
- "flex-col",
1870
- "bg-neutral-50",
1871
- "transition-transform",
1872
- "duration-300",
1873
- "ease-in-out",
1874
- "shadow-lg",
1875
- ], {
1876
- variants: {
1877
- position: {
1878
- left: "left-0 top-0 h-full w-fit rounded-r-lg",
1879
- right: "right-0 top-0 h-full w-fit rounded-l-lg",
1880
- top: "left-0 top-0 h-fit w-full rounded-b-lg",
1881
- bottom: "bottom-0 left-0 right-0 h-fit w-full rounded-t-lg",
1882
- },
1883
- sidebarMode: {
1884
- "semi-closed": "translate-y-[calc(100%-2.2rem)]",
1885
- "1/3": "translate-y-2/3",
1886
- "2/3": "translate-y-1/3",
1887
- full: "translate-y-0",
1888
- "semi-full": "translate-y-5",
1889
- closed: "translate-y-full",
1890
- },
1891
- isLargeScreen: {
1892
- true: "",
1893
- false: "bottom-0 left-0 right-0 h-full w-full rounded-t-lg",
1894
- },
1895
- open: {
1896
- true: "",
1897
- false: "",
1898
- },
1899
- },
1900
- compoundVariants: [
1901
- {
1902
- isLargeScreen: false,
1903
- open: false,
1904
- className: "translate-y-full",
1905
- },
1906
- {
1907
- isLargeScreen: true,
1908
- open: true,
1909
- position: "left",
1910
- className: "translate-x-0",
1911
- },
1912
- {
1913
- isLargeScreen: true,
1914
- open: false,
1915
- position: "left",
1916
- className: "-translate-x-full",
1917
- },
1918
- {
1919
- isLargeScreen: true,
1920
- open: true,
1921
- position: "right",
1922
- className: "translate-x-0",
1923
- },
1924
- {
1925
- isLargeScreen: true,
1926
- open: false,
1927
- position: "right",
1928
- className: "translate-x-full",
1929
- },
1930
- {
1931
- isLargeScreen: true,
1932
- open: true,
1933
- position: "top",
1934
- className: "translate-y-0",
1935
- },
1936
- {
1937
- isLargeScreen: true,
1938
- open: false,
1939
- position: "top",
1940
- className: "-translate-y-full",
1941
- },
1942
- {
1943
- isLargeScreen: true,
1944
- open: true,
1945
- position: "bottom",
1946
- className: "translate-y-0",
1947
- },
1948
- {
1949
- isLargeScreen: true,
1950
- open: false,
1951
- position: "bottom",
1952
- className: "translate-y-full",
1953
- },
1954
- ],
1955
- });
1956
- const cvaDialogContainer = cvaMerge(["flex", "flex-col", "overflow-hidden", "rounded-[inherit]"], {
1957
- variants: {
1958
- sidebarMode: {
1959
- "semi-closed": "h-auto",
1960
- "1/3": "h-[34%]",
1961
- "2/3": "h-[67%]",
1962
- "semi-full": "h-[calc(100%-1.25rem)]",
1963
- full: "h-full",
1964
- closed: "h-auto",
1965
- },
1966
- },
1967
- });
1968
- const cvaSwipeContainer = cvaMerge(["my-4", "flex", "items-center", "justify-center", "lg:hidden"]);
1969
- const cvaSwipeIcon = cvaMerge(["block", "h-1", "w-10", "rounded-full", "bg-gray-400"]);
1970
- const cvaToogleContainer = cvaMerge(["z-8", "absolute"], {
1971
- variants: {
1972
- position: {
1973
- left: "right-[-24px] top-[calc(50%-24px)]",
1974
- right: "left-[-24px] top-[calc(50%-24px)]",
1975
- top: "bottom-[-24px] left-[calc(50%-24px)]",
1976
- bottom: "left-[calc(50%-24px)] top-[-24px]",
1977
- },
1978
- },
1979
- defaultVariants: {
1980
- position: "left",
1981
- },
1982
- });
1983
- const cvaToogleButton = cvaMerge([
1984
- "flex",
1985
- "cursor-pointer",
1986
- "items-center",
1987
- "justify-center",
1988
- "border-gray-300",
1989
- "bg-neutral-50",
1990
- "bg-center",
1991
- "bg-no-repeat",
1992
- "shadow-md",
1993
- ], {
1994
- variants: {
1995
- position: {
1996
- left: "h-12 w-6 rounded-r-lg",
1997
- right: "h-12 w-6 rounded-l-lg",
1998
- top: "h-6 w-12 rounded-b-lg",
1999
- bottom: "h-6 w-12 rounded-t-lg",
2000
- },
2001
- },
2002
- defaultVariants: {
2003
- position: "left",
2004
- },
2005
- });
2006
- const cvaOverlayContainer = cvaMerge([
2007
- "absolute",
2008
- "flex",
2009
- "items-center",
2010
- "justify-center",
2011
- "inset-0",
2012
- "bg-black/30",
2013
- "bg-opacity-50",
2014
- "transition-opacity",
2015
- "duration-200",
2016
- "ease-in-out",
2017
- "opacity-100",
2018
- ], {
2019
- variants: {
2020
- open: {
2021
- true: "opacity-1 z-popover",
2022
- false: "z-[-1] opacity-0",
2023
- },
2024
- },
2025
- });
2026
-
2027
- /**
2028
- * Overlay Component
2029
- *
2030
- * @param {object} props - The Overlay component properties
2031
- * @param {boolean} props.open - Open status of the Overlay
2032
- * @param {Function} props.onClose - Callback function when Overlay is closed
2033
- * @returns {JSX.Element|null} The Overlay component
2034
- */
2035
- const Overlay = ({ open, onClose }) => {
2036
- useEffect(() => {
2037
- if (!onClose) {
2038
- return;
2039
- }
2040
- const handleEscape = (event) => {
2041
- if (event.key === "Escape") {
2042
- onClose(event);
2043
- }
2044
- };
2045
- if (open) {
2046
- window.addEventListener("keyup", handleEscape);
2047
- }
2048
- return () => {
2049
- window.removeEventListener("keyup", handleEscape);
2050
- };
2051
- }, [open, onClose]);
2052
- const handleClick = (event) => {
2053
- if (onClose) {
2054
- onClose(event);
2055
- }
2056
- };
2057
- return (jsx("div", { "aria-hidden": open, className: cvaOverlayContainer({ open }), "data-testid": "sidebar-overlay", onClick: handleClick }));
2058
- };
2059
-
2060
- const getIconName = (open, position) => {
2061
- switch (position) {
2062
- case "left":
2063
- return open ? "ChevronLeft" : "ChevronRight";
2064
- case "right":
2065
- return open ? "ChevronRight" : "ChevronLeft";
2066
- case "top":
2067
- return open ? "ChevronUp" : "ChevronDown";
2068
- case "bottom":
2069
- return open ? "ChevronDown" : "ChevronUp";
2070
- default:
2071
- return open ? "ChevronLeft" : "ChevronRight";
2072
- }
2073
- };
2074
- /**
2075
- * ToggleButton is a React functional component that returns a button with a chevron icon.
2076
- * The direction of the chevron changes depending on the state of the 'open' prop and
2077
- * the side the button is positioned ('position' prop).
2078
- * The button might be disabled based on the 'disableButton' prop.
2079
- *
2080
- * @param {object} props - The properties passed to the component
2081
- * @param {boolean} props.open - Indicates if the button is in "open" state
2082
- * @param {Function} [props.onToggle] - Optional callback function for when the button is clicked
2083
- * @param {Position} props.position - The position of the button relative to its container
2084
- */
2085
- const ToogleButton = ({ open, position, onToggle }) => {
2086
- const name = getIconName(open, position);
2087
- return (jsx("div", { className: cvaToogleContainer({ position }), children: jsx("button", { className: cvaToogleButton({ position }), "data-testid": "toggle-button", onClick: onToggle, children: jsx(Icon, { name: name }) }) }));
2088
- };
2089
-
2090
- /**
2091
- * MapSidebar is a sidebar component used with Maps.
2092
- * It provides a slide over sidebar drawer which can be used for displaying map related information or controls.
2093
- *
2094
- * @param {DrawerProps} props - The props for the MapSidebar component
2095
- * @returns {JSX.Element | null} Drawer component
2096
- */
2097
- const Drawer = ({ open = true, onToggle, onClose, disableOverlay, position = "left", children, dataTestId, className, dialogClassName, }) => {
2098
- const { width } = useResize();
2099
- const isLargeScreen = width >= 1024;
2100
- const initialSidebarMode = () => {
2101
- if (!open) {
2102
- return "closed";
2103
- }
2104
- return isLargeScreen ? "full" : "2/3";
2105
- };
2106
- const [sidebarMode, setSidebarMode] = useState(initialSidebarMode);
2107
- const [isVisible, setIsVisible] = useState(open);
2108
- const [isAnimationStart, setStartAnimation] = useState(open);
2109
- useEffect(() => {
2110
- if (!isLargeScreen && open) {
2111
- setSidebarMode("2/3");
2112
- }
2113
- if (onToggle) {
2114
- setStartAnimation(open);
2115
- return;
2116
- }
2117
- manageVisibilityAndAnimation(open);
2118
- }, [onToggle, isLargeScreen, open]);
2119
- const manageVisibilityAndAnimation = (isOpen) => {
2120
- if (!isOpen) {
2121
- setStartAnimation(false);
2122
- return;
2123
- }
2124
- setIsVisible(true);
2125
- setTimeout(() => {
2126
- setStartAnimation(true);
2127
- }, 150);
2128
- };
2129
- const handlers = useSwipeable({
2130
- onSwipedUp: () => !isLargeScreen && handleSwipedUp(),
2131
- onSwipedDown: () => !isLargeScreen && handleSwipedDown(),
2132
- trackMouse: true,
2133
- });
2134
- const handleSwipedUp = () => {
2135
- setSidebarMode(prev => {
2136
- switch (prev) {
2137
- case "semi-closed":
2138
- return "1/3";
2139
- case "1/3":
2140
- return "2/3";
2141
- case "2/3":
2142
- return "semi-full";
2143
- default:
2144
- return prev;
2145
- }
2146
- });
2147
- };
2148
- const handleSwipedDown = () => {
2149
- setSidebarMode(prev => {
2150
- switch (prev) {
2151
- case "semi-full":
2152
- return "2/3";
2153
- case "2/3":
2154
- return "1/3";
2155
- case "1/3":
2156
- return onClose ? "closed" : "semi-closed";
2157
- default:
2158
- return prev;
2159
- }
2160
- });
2161
- /*
2162
- * Within a mobile device context, when swiping down,
2163
- * if the Drawer is open a third and no onToggle function is provided,
2164
- * the Drawer will automatically be closed using the onClose function.
2165
- */
2166
- if (sidebarMode === "1/3" && !onToggle && onClose) {
2167
- onClose();
2168
- }
2169
- };
2170
- const handleAnimationEnd = () => {
2171
- if (!open && !onToggle) {
2172
- setIsVisible(false);
2173
- }
2174
- };
2175
- if (!isVisible && !onToggle) {
2176
- return null;
2177
- }
2178
- return (jsxs("div", { className: className, "data-testid": dataTestId, children: [!disableOverlay ? jsx(Overlay, { onClose: onClose, open: isAnimationStart }) : null, jsxs("div", { ...handlers, className: cvaDialog({
2179
- sidebarMode: isLargeScreen ? null : sidebarMode,
2180
- isLargeScreen,
2181
- open: isAnimationStart,
2182
- position: isLargeScreen ? position : undefined,
2183
- className: dialogClassName,
2184
- }), "data-testid": dataTestId ? `${dataTestId}-container` : undefined, onTransitionEnd: handleAnimationEnd, children: [isLargeScreen && onToggle ? jsx(ToogleButton, { onToggle: onToggle, open: open, position: position }) : null, jsxs("div", { className: cvaDialogContainer({
2185
- sidebarMode: isLargeScreen ? "full" : sidebarMode,
2186
- }), children: [jsx("div", { className: cvaSwipeContainer(), children: jsx("div", { className: cvaSwipeIcon() }) }), children] })] })] }));
2187
- };
2188
-
2189
1919
  const cvaSkeletonLine = cvaMerge([
2190
1920
  "rounded-md",
2191
1921
  "h-3",
@@ -5132,4 +4862,4 @@ const cvaClickable = cvaMerge([
5132
4862
  },
5133
4863
  });
5134
4864
 
5135
- export { Alert, Badge, Breadcrumb, BreadcrumbContainer, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, Drawer, 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, useSelfUpdatingRef, useTimeout, useViewportSize, useWindowActivity };
4865
+ 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-components",
3
- "version": "0.4.34",
3
+ "version": "0.5.1",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -23,7 +23,6 @@
23
23
  "@testing-library/react": "15.0.6",
24
24
  "@floating-ui/react": "^0.26.25",
25
25
  "@tanstack/react-router": "1.47.1",
26
- "react-swipeable": "^7.0.1",
27
26
  "string-ts": "^2.0.0",
28
27
  "tailwind-merge": "^2.0.0",
29
28
  "@trackunit/react-table-pagination": "*"
@@ -6,7 +6,6 @@ export * from "./Card";
6
6
  export * from "./Collapse";
7
7
  export * from "./CompletionStatusIndicator/CompletionStatusIndicator";
8
8
  export * from "./CopyableText";
9
- export * from "./Drawer";
10
9
  export * from "./EmptyState/EmptyState";
11
10
  export * from "./EmptyValue/EmptyValue";
12
11
  export * from "./ExternalLink/ExternalLink";
@@ -41,4 +40,3 @@ export * from "./Tooltip";
41
40
  export * from "./ValueBar";
42
41
  export * from "./VirtualizedList/VirtualizedList";
43
42
  export * from "./Widget";
44
- export * from "./buttons";
@@ -13,6 +13,7 @@ export * from "./useIsTextCutOff";
13
13
  export * from "./useOptionallyElevatedState";
14
14
  export * from "./usePrevious";
15
15
  export * from "./useResize";
16
+ export * from "./useScrollDetection";
16
17
  export * from "./useSelfUpdatingRef";
17
18
  export * from "./useTimeout";
18
19
  export * from "./useViewportSize";
@@ -0,0 +1,16 @@
1
+ import { type RefObject } from "react";
2
+ /**
3
+ * Hook for getting detecting scroll values.
4
+ *
5
+ * @param {useRef} elementRef - Ref hook holding the element that needs to be observed during scrolling
6
+ * @returns {object} An object containing if the element is scrollable, is at the top, is at the bottom, and its current scroll position.
7
+ */
8
+ export declare const useScrollDetection: (elementRef: RefObject<HTMLElement>) => {
9
+ isScrollable: boolean;
10
+ isAtTop: boolean;
11
+ isAtBottom: boolean;
12
+ scrollPosition: {
13
+ top: number;
14
+ bottom: number;
15
+ };
16
+ };
@@ -1,37 +0,0 @@
1
- import { CommonProps } from "../../common";
2
- export type Position = "left" | "right" | "top" | "bottom";
3
- export interface DrawerProps extends CommonProps {
4
- /**
5
- * Specifies whether the sidebar drawer is open or not.
6
- */
7
- open?: boolean;
8
- /**
9
- * The onClose handler for the drawer.
10
- */
11
- onClose?: (event?: React.MouseEvent | KeyboardEvent) => void;
12
- /**
13
- * The handler for toggling the drawer state.
14
- */
15
- onToggle?: (event: React.MouseEvent) => void;
16
- /**
17
- * Whether the drawer needs an overlay or not. Without the overlay, user will have to handle mechanism for closing the drawer.
18
- */
19
- disableOverlay?: boolean;
20
- /**
21
- * The position of the drawer.
22
- */
23
- position?: Position;
24
- /**
25
- * The child node that will be rendered inside the drawer.
26
- */
27
- children?: React.ReactNode;
28
- dialogClassName?: string;
29
- }
30
- /**
31
- * MapSidebar is a sidebar component used with Maps.
32
- * It provides a slide over sidebar drawer which can be used for displaying map related information or controls.
33
- *
34
- * @param {DrawerProps} props - The props for the MapSidebar component
35
- * @returns {JSX.Element | null} Drawer component
36
- */
37
- export declare const Drawer: ({ open, onToggle, onClose, disableOverlay, position, children, dataTestId, className, dialogClassName, }: DrawerProps) => JSX.Element | null;
@@ -1,20 +0,0 @@
1
- export declare const cvaDialog: (props?: ({
2
- position?: "left" | "right" | "top" | "bottom" | null | undefined;
3
- sidebarMode?: "semi-closed" | "1/3" | "2/3" | "full" | "semi-full" | "closed" | null | undefined;
4
- isLargeScreen?: boolean | null | undefined;
5
- open?: boolean | null | undefined;
6
- } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
7
- export declare const cvaDialogContainer: (props?: ({
8
- sidebarMode?: "semi-closed" | "1/3" | "2/3" | "full" | "semi-full" | "closed" | null | undefined;
9
- } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
10
- export declare const cvaSwipeContainer: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
11
- export declare const cvaSwipeIcon: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
12
- export declare const cvaToogleContainer: (props?: ({
13
- position?: "left" | "right" | "top" | "bottom" | null | undefined;
14
- } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
15
- export declare const cvaToogleButton: (props?: ({
16
- position?: "left" | "right" | "top" | "bottom" | null | undefined;
17
- } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
18
- export declare const cvaOverlayContainer: (props?: ({
19
- open?: boolean | null | undefined;
20
- } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
@@ -1,14 +0,0 @@
1
- interface OverlayProps {
2
- open: boolean;
3
- onClose?: (event: React.MouseEvent | KeyboardEvent) => void;
4
- }
5
- /**
6
- * Overlay Component
7
- *
8
- * @param {object} props - The Overlay component properties
9
- * @param {boolean} props.open - Open status of the Overlay
10
- * @param {Function} props.onClose - Callback function when Overlay is closed
11
- * @returns {JSX.Element|null} The Overlay component
12
- */
13
- declare const Overlay: ({ open, onClose }: OverlayProps) => JSX.Element | null;
14
- export default Overlay;
@@ -1,19 +0,0 @@
1
- import { Position } from "./Drawer";
2
- interface ToogleButtonProps {
3
- open: boolean;
4
- onToggle?: (event: React.MouseEvent) => void;
5
- position: Position;
6
- }
7
- /**
8
- * ToggleButton is a React functional component that returns a button with a chevron icon.
9
- * The direction of the chevron changes depending on the state of the 'open' prop and
10
- * the side the button is positioned ('position' prop).
11
- * The button might be disabled based on the 'disableButton' prop.
12
- *
13
- * @param {object} props - The properties passed to the component
14
- * @param {boolean} props.open - Indicates if the button is in "open" state
15
- * @param {Function} [props.onToggle] - Optional callback function for when the button is clicked
16
- * @param {Position} props.position - The position of the button relative to its container
17
- */
18
- declare const ToogleButton: ({ open, position, onToggle }: ToogleButtonProps) => import("react/jsx-runtime").JSX.Element;
19
- export default ToogleButton;
@@ -1 +0,0 @@
1
- export * from "./Drawer";