next-data-kit 7.4.1 → 7.6.0

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/dist/index.cjs CHANGED
@@ -11,6 +11,7 @@ var SwitchPrimitive = require('@radix-ui/react-switch');
11
11
  var reactSlot = require('@radix-ui/react-slot');
12
12
  var classVarianceAuthority = require('class-variance-authority');
13
13
  var SelectPrimitive = require('@radix-ui/react-select');
14
+ var reactIntersectionObserver = require('react-intersection-observer');
14
15
 
15
16
  function _interopNamespace(e) {
16
17
  if (e && e.__esModule) return e;
@@ -611,6 +612,7 @@ var useDataKit = (props) => {
611
612
  mounted.current = false;
612
613
  };
613
614
  }, []);
615
+ const hasNextPage = page * limit < total;
614
616
  return {
615
617
  page,
616
618
  limit,
@@ -620,7 +622,7 @@ var useDataKit = (props) => {
620
622
  query,
621
623
  items,
622
624
  total,
623
- state: { isLoading, error },
625
+ state: { isLoading, error, hasNextPage },
624
626
  actions: {
625
627
  setPage,
626
628
  setLimit,
@@ -1799,6 +1801,240 @@ var DataKitInner = (props, ref) => {
1799
1801
  ] });
1800
1802
  };
1801
1803
  var DataKit = React2__namespace.default.forwardRef(DataKitInner);
1804
+ var DataKitInfinityInner = (props, ref) => {
1805
+ const {
1806
+ action,
1807
+ query,
1808
+ filterConfig,
1809
+ filters = [],
1810
+ limit: limitConfig,
1811
+ defaultSort = [],
1812
+ className,
1813
+ autoFetch = true,
1814
+ debounce: debounce2 = 300,
1815
+ state: stateMode = "memory",
1816
+ inverse = false,
1817
+ manual = false,
1818
+ pullDownToRefresh,
1819
+ children
1820
+ } = props;
1821
+ const containerRef = React2.useRef(null);
1822
+ const scrollContainerRef = React2.useRef(null);
1823
+ const [allItems, setAllItems] = React2.useState([]);
1824
+ const [isFilterOpen, setIsFilterOpen] = React2.useState(false);
1825
+ const [isPullRefreshing, setIsPullRefreshing] = React2.useState(false);
1826
+ const [pullStartY, setPullStartY] = React2.useState(0);
1827
+ const [pullDistance, setPullDistance] = React2.useState(0);
1828
+ const overlayContainer = containerRef.current;
1829
+ const pullThreshold = pullDownToRefresh?.threshold ?? 50;
1830
+ const dataKit = useDataKit({
1831
+ action,
1832
+ filterConfig,
1833
+ autoFetch: false,
1834
+ // We'll control fetching manually
1835
+ debounce: debounce2,
1836
+ state: stateMode,
1837
+ initial: {
1838
+ limit: limitConfig?.default ?? 10,
1839
+ query: query ?? {},
1840
+ sorts: defaultSort,
1841
+ filter: filters.reduce((acc, f) => {
1842
+ if (f.defaultValue !== void 0) acc[f.id] = f.defaultValue;
1843
+ return acc;
1844
+ }, {})
1845
+ }
1846
+ });
1847
+ const { ref: loadMoreRef, inView } = reactIntersectionObserver.useInView({
1848
+ threshold: 0,
1849
+ rootMargin: "100px"
1850
+ });
1851
+ React2__namespace.default.useImperativeHandle(ref, () => dataKit, [dataKit]);
1852
+ const handleResetFilters = React2.useCallback(() => {
1853
+ filters.forEach((f) => {
1854
+ dataKit.actions.setFilter(f.id, f.defaultValue ?? (f.type === "BOOLEAN" ? false : ""));
1855
+ });
1856
+ }, [filters, dataKit.actions]);
1857
+ const loadMore = React2.useCallback(async () => {
1858
+ if (dataKit.state.isLoading || !dataKit.state.hasNextPage) return;
1859
+ dataKit.actions.setPage(dataKit.page + 1);
1860
+ }, [dataKit]);
1861
+ const resetAndFetch = React2.useCallback(async () => {
1862
+ setAllItems([]);
1863
+ dataKit.actions.setPage(1);
1864
+ await dataKit.actions.refresh();
1865
+ }, [dataKit.actions]);
1866
+ const handleTouchStart = React2.useCallback((e) => {
1867
+ if (!pullDownToRefresh?.isActive) return;
1868
+ const scrollTop = scrollContainerRef.current?.scrollTop ?? 0;
1869
+ if (scrollTop === 0 && e.touches && e.touches[0]) {
1870
+ setPullStartY(e.touches[0].clientY);
1871
+ }
1872
+ }, [pullDownToRefresh?.isActive]);
1873
+ const handleTouchMove = React2.useCallback((e) => {
1874
+ if (!pullDownToRefresh?.isActive || pullStartY === 0) return;
1875
+ if (!e.touches || !e.touches[0]) return;
1876
+ const scrollTop = scrollContainerRef.current?.scrollTop ?? 0;
1877
+ if (scrollTop === 0) {
1878
+ const distance = e.touches[0].clientY - pullStartY;
1879
+ if (distance > 0) {
1880
+ setPullDistance(Math.min(distance, pullThreshold * 2));
1881
+ if (distance > pullThreshold * 2) {
1882
+ e.preventDefault();
1883
+ }
1884
+ }
1885
+ }
1886
+ }, [pullDownToRefresh?.isActive, pullStartY, pullThreshold]);
1887
+ const handleTouchEnd = React2.useCallback(async () => {
1888
+ if (!pullDownToRefresh?.isActive) return;
1889
+ if (pullDistance > pullThreshold) {
1890
+ setIsPullRefreshing(true);
1891
+ await resetAndFetch();
1892
+ setIsPullRefreshing(false);
1893
+ }
1894
+ setPullStartY(0);
1895
+ setPullDistance(0);
1896
+ }, [pullDownToRefresh?.isActive, pullDistance, pullThreshold, resetAndFetch]);
1897
+ React2.useEffect(() => {
1898
+ if (!pullDownToRefresh?.isActive) return;
1899
+ const container = scrollContainerRef.current;
1900
+ if (!container) return;
1901
+ container.addEventListener("touchstart", handleTouchStart);
1902
+ container.addEventListener("touchmove", handleTouchMove, { passive: false });
1903
+ container.addEventListener("touchend", handleTouchEnd);
1904
+ return () => {
1905
+ container.removeEventListener("touchstart", handleTouchStart);
1906
+ container.removeEventListener("touchmove", handleTouchMove);
1907
+ container.removeEventListener("touchend", handleTouchEnd);
1908
+ };
1909
+ }, [pullDownToRefresh?.isActive, handleTouchStart, handleTouchMove, handleTouchEnd]);
1910
+ React2.useEffect(() => {
1911
+ if (autoFetch && dataKit.page === 1 && allItems.length === 0) {
1912
+ dataKit.actions.refresh();
1913
+ }
1914
+ }, [autoFetch]);
1915
+ React2.useEffect(() => {
1916
+ if (dataKit.items.length > 0) {
1917
+ setAllItems((prev) => {
1918
+ if (dataKit.page === 1) {
1919
+ return dataKit.items;
1920
+ }
1921
+ const newItems = dataKit.items.filter(
1922
+ (newItem) => !prev.some((existingItem) => existingItem.id === newItem.id)
1923
+ );
1924
+ return inverse ? [...newItems, ...prev] : [...prev, ...newItems];
1925
+ });
1926
+ }
1927
+ }, [dataKit.items, dataKit.page, inverse]);
1928
+ React2.useEffect(() => {
1929
+ if (inView && !inverse) {
1930
+ loadMore();
1931
+ }
1932
+ }, [inView, inverse, loadMore]);
1933
+ React2.useEffect(() => {
1934
+ if (!inverse) return;
1935
+ const handleScroll = () => {
1936
+ const container2 = scrollContainerRef.current;
1937
+ if (!container2) return;
1938
+ if (container2.scrollTop === 0 && !dataKit.state.isLoading && dataKit.state.hasNextPage) {
1939
+ loadMore();
1940
+ }
1941
+ };
1942
+ const container = scrollContainerRef.current;
1943
+ if (container) {
1944
+ container.addEventListener("scroll", handleScroll);
1945
+ return () => container.removeEventListener("scroll", handleScroll);
1946
+ }
1947
+ return void 0;
1948
+ }, [inverse, loadMore, dataKit.state.isLoading, dataKit.state.hasNextPage]);
1949
+ const enhancedDataKit = {
1950
+ ...dataKit,
1951
+ items: allItems
1952
+ };
1953
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: `flex flex-col ${className ?? ""}`, children: [
1954
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-3 flex items-center justify-between gap-2", children: [
1955
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1956
+ filters.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open: isFilterOpen, onOpenChange: setIsFilterOpen, children: [
1957
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "outline", size: "sm", children: [
1958
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Filter, { className: "mr-1.5 size-4" }),
1959
+ "Filters"
1960
+ ] }) }),
1961
+ /* @__PURE__ */ jsxRuntime.jsxs(PopoverContent, { align: "start", className: "w-80", container: overlayContainer, children: [
1962
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-3", children: filters.map((f) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-1.5", children: [
1963
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-sm font-medium", children: f.label }),
1964
+ f.type === "TEXT" && /* @__PURE__ */ jsxRuntime.jsx(
1965
+ "input",
1966
+ {
1967
+ type: "text",
1968
+ className: "h-9 w-full rounded-md border bg-transparent px-3 text-sm outline-none focus:ring-2 focus:ring-ring",
1969
+ placeholder: f.placeholder,
1970
+ value: dataKit.filter[f.id] ?? "",
1971
+ onChange: (e) => dataKit.actions.setFilter(f.id, e.target.value)
1972
+ }
1973
+ ),
1974
+ f.type === "SELECT" && /* @__PURE__ */ jsxRuntime.jsxs(
1975
+ Select,
1976
+ {
1977
+ value: String(dataKit.filter[f.id] || "__all__"),
1978
+ onValueChange: (v) => dataKit.actions.setFilter(f.id, v === "__all__" ? "" : v),
1979
+ children: [
1980
+ /* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, {}) }),
1981
+ /* @__PURE__ */ jsxRuntime.jsxs(SelectContent, { container: overlayContainer, children: [
1982
+ /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "__all__", children: "All" }),
1983
+ f.dataset?.map((d) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: d.id, children: d.label }, d.id))
1984
+ ] })
1985
+ ]
1986
+ }
1987
+ ),
1988
+ f.type === "BOOLEAN" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
1989
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: f.placeholder ?? "Enable" }),
1990
+ /* @__PURE__ */ jsxRuntime.jsx(
1991
+ Switch,
1992
+ {
1993
+ checked: Boolean(dataKit.filter[f.id]),
1994
+ onCheckedChange: (c) => dataKit.actions.setFilter(f.id, c)
1995
+ }
1996
+ )
1997
+ ] })
1998
+ ] }, f.id)) }),
1999
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex justify-between border-t pt-3", children: [
2000
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", size: "sm", onClick: handleResetFilters, children: "Reset" }),
2001
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "sm", onClick: () => setIsFilterOpen(false), children: "Done" })
2002
+ ] })
2003
+ ] })
2004
+ ] }),
2005
+ /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "outline", size: "sm", onClick: resetAndFetch, disabled: dataKit.state.isLoading, children: [
2006
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: `mr-1.5 size-4 ${dataKit.state.isLoading ? "animate-spin" : ""}` }),
2007
+ "Refresh"
2008
+ ] })
2009
+ ] }),
2010
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mr-2 text-sm text-muted-foreground", children: [
2011
+ allItems.length,
2012
+ " loaded"
2013
+ ] }) })
2014
+ ] }),
2015
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: scrollContainerRef, className: "relative flex-1 overflow-auto", children: [
2016
+ pullDownToRefresh?.isActive && pullDistance > 0 && /* @__PURE__ */ jsxRuntime.jsx(
2017
+ "div",
2018
+ {
2019
+ className: "absolute left-0 right-0 top-0 flex items-center justify-center bg-background/80 backdrop-blur-sm transition-all",
2020
+ style: { height: `${pullDistance}px` },
2021
+ children: pullDistance > pullThreshold ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "size-5 text-primary" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: "Pull to refresh" })
2022
+ }
2023
+ ),
2024
+ isPullRefreshing && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-4", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-6 animate-spin text-muted-foreground" }) }),
2025
+ inverse && dataKit.state.hasNextPage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-4", children: dataKit.state.isLoading && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-6 animate-spin text-muted-foreground" }) }),
2026
+ manual ? children(enhancedDataKit) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2027
+ children(enhancedDataKit),
2028
+ !dataKit.state.isLoading && allItems.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-48 items-center justify-center text-muted-foreground", children: "No results found." })
2029
+ ] }),
2030
+ !inverse && /* @__PURE__ */ jsxRuntime.jsx("div", { ref: loadMoreRef, className: "flex items-center justify-center py-4", children: !manual && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2031
+ dataKit.state.isLoading && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-6 animate-spin text-muted-foreground" }),
2032
+ !dataKit.state.isLoading && !dataKit.state.hasNextPage && allItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "You're all set" })
2033
+ ] }) })
2034
+ ] })
2035
+ ] });
2036
+ };
2037
+ React2__namespace.default.forwardRef(DataKitInfinityInner);
1802
2038
 
1803
2039
  exports.DataKit = DataKit;
1804
2040
  exports.DataKitContext = DefaultDataKitContext;