@classytic/fluid 0.4.2 → 0.5.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.
Files changed (51) hide show
  1. package/README.md +21 -1
  2. package/dist/client/calendar.d.mts +1 -2
  3. package/dist/client/calendar.mjs +4 -4
  4. package/dist/client/color-picker.d.mts +41 -25
  5. package/dist/client/color-picker.mjs +121 -73
  6. package/dist/client/core.d.mts +243 -557
  7. package/dist/client/core.mjs +351 -1462
  8. package/dist/client/error.d.mts +41 -41
  9. package/dist/client/error.mjs +35 -35
  10. package/dist/client/gallery.d.mts +33 -33
  11. package/dist/client/gallery.mjs +128 -127
  12. package/dist/client/hooks.d.mts +57 -39
  13. package/dist/client/hooks.mjs +29 -7
  14. package/dist/client/spreadsheet.d.mts +28 -28
  15. package/dist/client/spreadsheet.mjs +77 -77
  16. package/dist/client/table.d.mts +66 -33
  17. package/dist/client/table.mjs +87 -54
  18. package/dist/client/theme.mjs +1 -1
  19. package/dist/command.d.mts +6 -4
  20. package/dist/command.mjs +3 -3
  21. package/dist/compact.d.mts +97 -95
  22. package/dist/compact.mjs +336 -322
  23. package/dist/dashboard.d.mts +614 -422
  24. package/dist/dashboard.mjs +1051 -762
  25. package/dist/{dropdown-wrapper-B86u9Fri.mjs → dropdown-wrapper-B9nRDUlz.mjs} +25 -35
  26. package/dist/forms.d.mts +1037 -972
  27. package/dist/forms.mjs +2849 -2721
  28. package/dist/index.d.mts +218 -152
  29. package/dist/index.mjs +357 -264
  30. package/dist/layouts.d.mts +94 -94
  31. package/dist/layouts.mjs +115 -110
  32. package/dist/phone-input-B9_XPNvv.mjs +429 -0
  33. package/dist/phone-input-CLH_UjQZ.d.mts +31 -0
  34. package/dist/{search-context-DR7DBs7S.mjs → search-context-1g3ZmOvx.mjs} +1 -1
  35. package/dist/search.d.mts +168 -164
  36. package/dist/search.mjs +305 -301
  37. package/dist/{sheet-wrapper-C13Y-Q6w.mjs → sheet-wrapper-B2uxookb.mjs} +1 -1
  38. package/dist/timeline-Bgu1mIe9.d.mts +373 -0
  39. package/dist/timeline-HJtWf4Op.mjs +804 -0
  40. package/dist/{use-base-search-BGgWnWaF.d.mts → use-base-search-DFC4QKYU.d.mts} +1 -1
  41. package/dist/{use-media-query-BnVNIKT4.mjs → use-media-query-ChLfFChU.mjs} +6 -7
  42. package/package.json +2 -2
  43. /package/dist/{api-pagination-CJ0vR_w6.d.mts → api-pagination-C30ser2L.d.mts} +0 -0
  44. /package/dist/{filter-utils-DqMmy_v-.mjs → filter-utils-BGIvtq1R.mjs} +0 -0
  45. /package/dist/{filter-utils-IZ0GtuPo.d.mts → filter-utils-DOFTBWm1.d.mts} +0 -0
  46. /package/dist/{use-debounce-xmZucz5e.mjs → use-debounce-BNoNiEon.mjs} +0 -0
  47. /package/dist/{use-keyboard-shortcut-Bl6YM5Q7.mjs → use-keyboard-shortcut-C_Vk-36P.mjs} +0 -0
  48. /package/dist/{use-keyboard-shortcut-_mRCh3QO.d.mts → use-keyboard-shortcut-Q4CSPzSI.d.mts} +0 -0
  49. /package/dist/{use-mobile-BX3SQVo2.mjs → use-mobile-CnEmFiQx.mjs} +0 -0
  50. /package/dist/{use-scroll-detection-CsgsQYvy.mjs → use-scroll-detection-BKfqkmEC.mjs} +0 -0
  51. /package/dist/{utils-CDue7cEt.d.mts → utils-rqvYP1by.d.mts} +0 -0
@@ -1,7 +1,47 @@
1
- import * as react_jsx_runtime0 from "react/jsx-runtime";
2
1
  import { ComponentType, ReactNode } from "react";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
3
  import { FallbackProps } from "react-error-boundary";
4
4
 
5
+ //#region src/components/async-boundary.d.ts
6
+ interface AsyncBoundaryProps {
7
+ /** Suspense fallback (shown while loading) */
8
+ fallback?: ReactNode;
9
+ /** Error fallback component (shown on error) */
10
+ errorFallback?: ComponentType<FallbackProps>;
11
+ /** Called when an error is caught */
12
+ onError?: (error: unknown, info: {
13
+ componentStack?: string | null;
14
+ }) => void;
15
+ /** Called when the error boundary resets */
16
+ onReset?: () => void;
17
+ children: ReactNode;
18
+ }
19
+ /**
20
+ * AsyncBoundary — Suspense + ErrorBoundary combined.
21
+ *
22
+ * If `errorFallback` is provided, wraps in ErrorBoundary from react-error-boundary.
23
+ * Otherwise, renders Suspense only.
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * import { AsyncBoundary, InlineErrorFallback } from "@classytic/fluid/client";
28
+ *
29
+ * <AsyncBoundary
30
+ * fallback={<SkeletonTable rows={5} />}
31
+ * errorFallback={InlineErrorFallback}
32
+ * >
33
+ * <DataFetchingComponent />
34
+ * </AsyncBoundary>
35
+ * ```
36
+ */
37
+ declare function AsyncBoundary({
38
+ fallback,
39
+ errorFallback: ErrorFallback,
40
+ onError,
41
+ onReset,
42
+ children
43
+ }: AsyncBoundaryProps): react_jsx_runtime0.JSX.Element;
44
+ //#endregion
5
45
  //#region src/components/error-boundary.d.ts
6
46
  interface ErrorBoundaryFallbackProps extends FallbackProps {}
7
47
  interface ErrorBoundaryProps {
@@ -82,44 +122,4 @@ declare function FeatureErrorBoundary({
82
122
  className
83
123
  }: FeatureErrorBoundaryProps): react_jsx_runtime0.JSX.Element;
84
124
  //#endregion
85
- //#region src/components/async-boundary.d.ts
86
- interface AsyncBoundaryProps {
87
- /** Suspense fallback (shown while loading) */
88
- fallback?: ReactNode;
89
- /** Error fallback component (shown on error) */
90
- errorFallback?: ComponentType<FallbackProps>;
91
- /** Called when an error is caught */
92
- onError?: (error: unknown, info: {
93
- componentStack?: string | null;
94
- }) => void;
95
- /** Called when the error boundary resets */
96
- onReset?: () => void;
97
- children: ReactNode;
98
- }
99
- /**
100
- * AsyncBoundary — Suspense + ErrorBoundary combined.
101
- *
102
- * If `errorFallback` is provided, wraps in ErrorBoundary from react-error-boundary.
103
- * Otherwise, renders Suspense only.
104
- *
105
- * @example
106
- * ```tsx
107
- * import { AsyncBoundary, InlineErrorFallback } from "@classytic/fluid/client";
108
- *
109
- * <AsyncBoundary
110
- * fallback={<SkeletonTable rows={5} />}
111
- * errorFallback={InlineErrorFallback}
112
- * >
113
- * <DataFetchingComponent />
114
- * </AsyncBoundary>
115
- * ```
116
- */
117
- declare function AsyncBoundary({
118
- fallback,
119
- errorFallback: ErrorFallback,
120
- onError,
121
- onReset,
122
- children
123
- }: AsyncBoundaryProps): react_jsx_runtime0.JSX.Element;
124
- //#endregion
125
125
  export { AsyncBoundary, type AsyncBoundaryProps, ErrorBoundary, type ErrorBoundaryFallbackProps, type ErrorBoundaryProps, FeatureErrorBoundary, type FeatureErrorBoundaryProps, FullPageErrorFallback, InlineErrorFallback };
@@ -1,12 +1,46 @@
1
1
  "use client";
2
2
 
3
3
  import { t as cn } from "../utils-DQ5SCVoW.mjs";
4
- import { jsx, jsxs } from "react/jsx-runtime";
5
4
  import { Suspense } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
6
  import { AlertCircle, Home, RefreshCw } from "lucide-react";
7
7
  import { Button } from "@/components/ui/button";
8
8
  import { ErrorBoundary as ErrorBoundary$1 } from "react-error-boundary";
9
9
 
10
+ //#region src/components/async-boundary.tsx
11
+ /**
12
+ * AsyncBoundary — Suspense + ErrorBoundary combined.
13
+ *
14
+ * If `errorFallback` is provided, wraps in ErrorBoundary from react-error-boundary.
15
+ * Otherwise, renders Suspense only.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * import { AsyncBoundary, InlineErrorFallback } from "@classytic/fluid/client";
20
+ *
21
+ * <AsyncBoundary
22
+ * fallback={<SkeletonTable rows={5} />}
23
+ * errorFallback={InlineErrorFallback}
24
+ * >
25
+ * <DataFetchingComponent />
26
+ * </AsyncBoundary>
27
+ * ```
28
+ */
29
+ function AsyncBoundary({ fallback, errorFallback: ErrorFallback, onError, onReset, children }) {
30
+ const suspenseWrapped = /* @__PURE__ */ jsx(Suspense, {
31
+ fallback: fallback ?? null,
32
+ children
33
+ });
34
+ if (!ErrorFallback) return suspenseWrapped;
35
+ return /* @__PURE__ */ jsx(ErrorBoundary$1, {
36
+ FallbackComponent: ErrorFallback,
37
+ onError,
38
+ onReset,
39
+ children: suspenseWrapped
40
+ });
41
+ }
42
+
43
+ //#endregion
10
44
  //#region src/components/error-boundary.tsx
11
45
  /**
12
46
  * Full-page error fallback with retry and go-home buttons.
@@ -128,39 +162,5 @@ function FeatureErrorBoundary({ featureName = "Feature", onError, onReset, child
128
162
  });
129
163
  }
130
164
 
131
- //#endregion
132
- //#region src/components/async-boundary.tsx
133
- /**
134
- * AsyncBoundary — Suspense + ErrorBoundary combined.
135
- *
136
- * If `errorFallback` is provided, wraps in ErrorBoundary from react-error-boundary.
137
- * Otherwise, renders Suspense only.
138
- *
139
- * @example
140
- * ```tsx
141
- * import { AsyncBoundary, InlineErrorFallback } from "@classytic/fluid/client";
142
- *
143
- * <AsyncBoundary
144
- * fallback={<SkeletonTable rows={5} />}
145
- * errorFallback={InlineErrorFallback}
146
- * >
147
- * <DataFetchingComponent />
148
- * </AsyncBoundary>
149
- * ```
150
- */
151
- function AsyncBoundary({ fallback, errorFallback: ErrorFallback, onError, onReset, children }) {
152
- const suspenseWrapped = /* @__PURE__ */ jsx(Suspense, {
153
- fallback: fallback ?? null,
154
- children
155
- });
156
- if (!ErrorFallback) return suspenseWrapped;
157
- return /* @__PURE__ */ jsx(ErrorBoundary$1, {
158
- FallbackComponent: ErrorFallback,
159
- onError,
160
- onReset,
161
- children: suspenseWrapped
162
- });
163
- }
164
-
165
165
  //#endregion
166
166
  export { AsyncBoundary, ErrorBoundary, FeatureErrorBoundary, FullPageErrorFallback, InlineErrorFallback };
@@ -1,6 +1,25 @@
1
- import * as react_jsx_runtime0 from "react/jsx-runtime";
2
1
  import { ReactNode } from "react";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
3
 
4
+ //#region src/components/gallery/gallery-dots.d.ts
5
+ interface GalleryDotsProps {
6
+ /** Show on desktop (default: false — dots are usually mobile-only) */
7
+ showOnDesktop?: boolean;
8
+ className?: string;
9
+ }
10
+ declare function GalleryDots({
11
+ showOnDesktop,
12
+ className
13
+ }: GalleryDotsProps): react_jsx_runtime0.JSX.Element | null;
14
+ //#endregion
15
+ //#region src/components/gallery/gallery-lightbox.d.ts
16
+ interface GalleryLightboxProps {
17
+ className?: string;
18
+ }
19
+ declare function GalleryLightbox({
20
+ className
21
+ }: GalleryLightboxProps): react_jsx_runtime0.JSX.Element | null;
22
+ //#endregion
4
23
  //#region src/components/gallery/types.d.ts
5
24
  interface GalleryImage {
6
25
  src: string;
@@ -60,6 +79,19 @@ declare function GalleryMain({
60
79
  className
61
80
  }: GalleryMainProps): react_jsx_runtime0.JSX.Element;
62
81
  //#endregion
82
+ //#region src/components/gallery/gallery-nav.d.ts
83
+ interface GalleryNavProps {
84
+ /** Which direction this button navigates */
85
+ direction: "prev" | "next";
86
+ className?: string;
87
+ children?: React.ReactNode;
88
+ }
89
+ declare function GalleryNav({
90
+ direction,
91
+ className,
92
+ children
93
+ }: GalleryNavProps): react_jsx_runtime0.JSX.Element | null;
94
+ //#endregion
63
95
  //#region src/components/gallery/gallery-thumbnails.d.ts
64
96
  interface GalleryThumbnailsProps {
65
97
  /** Show on mobile (default: true) */
@@ -77,38 +109,6 @@ declare function GalleryThumbnails({
77
109
  className
78
110
  }: GalleryThumbnailsProps): react_jsx_runtime0.JSX.Element | null;
79
111
  //#endregion
80
- //#region src/components/gallery/gallery-dots.d.ts
81
- interface GalleryDotsProps {
82
- /** Show on desktop (default: false — dots are usually mobile-only) */
83
- showOnDesktop?: boolean;
84
- className?: string;
85
- }
86
- declare function GalleryDots({
87
- showOnDesktop,
88
- className
89
- }: GalleryDotsProps): react_jsx_runtime0.JSX.Element | null;
90
- //#endregion
91
- //#region src/components/gallery/gallery-lightbox.d.ts
92
- interface GalleryLightboxProps {
93
- className?: string;
94
- }
95
- declare function GalleryLightbox({
96
- className
97
- }: GalleryLightboxProps): react_jsx_runtime0.JSX.Element | null;
98
- //#endregion
99
- //#region src/components/gallery/gallery-nav.d.ts
100
- interface GalleryNavProps {
101
- /** Which direction this button navigates */
102
- direction: "prev" | "next";
103
- className?: string;
104
- children?: React.ReactNode;
105
- }
106
- declare function GalleryNav({
107
- direction,
108
- className,
109
- children
110
- }: GalleryNavProps): react_jsx_runtime0.JSX.Element | null;
111
- //#endregion
112
112
  //#region src/components/gallery/index.d.ts
113
113
  interface ImageGalleryRootProps {
114
114
  children: ReactNode;
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
 
3
3
  import { t as cn } from "../utils-DQ5SCVoW.mjs";
4
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
4
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
5
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { ChevronLeft, ChevronRight, RotateCcw, X, ZoomIn, ZoomOut } from "lucide-react";
7
7
  import Image from "next/image";
8
8
 
@@ -63,130 +63,6 @@ function GalleryProvider({ children, images, defaultIndex = 0, classNames, title
63
63
  });
64
64
  }
65
65
 
66
- //#endregion
67
- //#region src/components/gallery/gallery-main.tsx
68
- function GalleryMain({ children, badges, showNav = true, aspectRatio = "aspect-[4/5]", className }) {
69
- const { images, selectedIndex, setLightboxOpen, goToNext, goToPrevious, classNames, title } = useGallery();
70
- const [imagesLoaded, setImagesLoaded] = useState(new Set([0]));
71
- const [touchStart, setTouchStart] = useState(null);
72
- const [touchEnd, setTouchEnd] = useState(null);
73
- const sliderRef = useRef(null);
74
- const minSwipeDistance = 50;
75
- useEffect(() => {
76
- images.forEach((img, index) => {
77
- if (index === 0) return;
78
- const preloadImg = new window.Image();
79
- preloadImg.src = img.src;
80
- preloadImg.onload = () => {
81
- setImagesLoaded((prev) => new Set([...prev, index]));
82
- };
83
- });
84
- }, [images]);
85
- const onTouchStart = useCallback((e) => {
86
- setTouchEnd(null);
87
- setTouchStart(e.targetTouches[0].clientX);
88
- }, []);
89
- const onTouchMove = useCallback((e) => {
90
- setTouchEnd(e.targetTouches[0].clientX);
91
- }, []);
92
- const onTouchEnd = useCallback(() => {
93
- if (!touchStart || !touchEnd) return;
94
- const distance = touchStart - touchEnd;
95
- if (distance > minSwipeDistance) goToNext();
96
- if (distance < -minSwipeDistance) goToPrevious();
97
- }, [
98
- touchStart,
99
- touchEnd,
100
- goToNext,
101
- goToPrevious
102
- ]);
103
- const handleImageLoad = useCallback((index) => {
104
- setImagesLoaded((prev) => new Set([...prev, index]));
105
- }, []);
106
- return /* @__PURE__ */ jsx("div", {
107
- className: cn("relative", className),
108
- children: /* @__PURE__ */ jsxs("div", {
109
- ref: sliderRef,
110
- className: cn(aspectRatio, "bg-muted overflow-hidden relative rounded-lg", classNames?.main),
111
- onTouchStart,
112
- onTouchMove,
113
- onTouchEnd,
114
- children: [
115
- /* @__PURE__ */ jsx("div", {
116
- className: cn("absolute inset-0 flex transition-transform duration-300 ease-out", classNames?.slider),
117
- style: { transform: `translateX(-${selectedIndex * 100}%)` },
118
- children: images.map((img, index) => /* @__PURE__ */ jsxs("div", {
119
- className: "w-full h-full shrink-0 relative cursor-zoom-in",
120
- onClick: () => setLightboxOpen(true),
121
- children: [/* @__PURE__ */ jsx(Image, {
122
- src: img.src,
123
- alt: img.alt || `${title || "Gallery"} - Image ${index + 1}`,
124
- fill: true,
125
- className: cn("object-cover transition-opacity duration-300", imagesLoaded.has(index) ? "opacity-100" : "opacity-0", classNames?.mainImage),
126
- sizes: "(max-width: 768px) 100vw, 50vw",
127
- priority: index === 0,
128
- onLoad: () => handleImageLoad(index)
129
- }), !imagesLoaded.has(index) && /* @__PURE__ */ jsx("div", {
130
- className: "absolute inset-0 flex items-center justify-center bg-muted",
131
- children: /* @__PURE__ */ jsx("div", { className: "w-8 h-8 border-2 border-muted-foreground/20 border-t-muted-foreground rounded-full animate-spin" })
132
- })]
133
- }, index))
134
- }),
135
- badges && badges.length > 0 && /* @__PURE__ */ jsx("div", {
136
- className: cn("absolute top-4 left-4 flex flex-col gap-2 z-10", classNames?.badges),
137
- children: badges.map((badge, index) => /* @__PURE__ */ jsx("span", {
138
- className: cn("px-3 py-1 text-xs font-medium uppercase tracking-wider", classNames?.badge, badge.className),
139
- children: badge.label
140
- }, index))
141
- }),
142
- showNav && images.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
143
- type: "button",
144
- onClick: (e) => {
145
- e.stopPropagation();
146
- goToPrevious();
147
- },
148
- className: cn("hidden sm:flex absolute left-3 top-1/2 -translate-y-1/2 z-10", "w-10 h-10 items-center justify-center rounded-full", "bg-background/80 backdrop-blur-sm shadow-md hover:bg-background transition-colors", classNames?.nav, classNames?.navPrev),
149
- "aria-label": "Previous image",
150
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-5 w-5" })
151
- }), /* @__PURE__ */ jsx("button", {
152
- type: "button",
153
- onClick: (e) => {
154
- e.stopPropagation();
155
- goToNext();
156
- },
157
- className: cn("hidden sm:flex absolute right-3 top-1/2 -translate-y-1/2 z-10", "w-10 h-10 items-center justify-center rounded-full", "bg-background/80 backdrop-blur-sm shadow-md hover:bg-background transition-colors", classNames?.nav, classNames?.navNext),
158
- "aria-label": "Next image",
159
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-5 w-5" })
160
- })] }),
161
- children
162
- ]
163
- })
164
- });
165
- }
166
-
167
- //#endregion
168
- //#region src/components/gallery/gallery-thumbnails.tsx
169
- function GalleryThumbnails({ showOnMobile = true, orientation = "horizontal", sizeClassName = "w-16 h-20 sm:w-20 sm:h-24", className }) {
170
- const { images, selectedIndex, setSelectedIndex, classNames, title } = useGallery();
171
- if (images.length <= 1) return null;
172
- return /* @__PURE__ */ jsx("div", {
173
- className: cn("flex gap-2 sm:gap-3 scrollbar-hide", orientation === "vertical" ? "flex-col overflow-y-auto pr-2" : "overflow-x-auto pb-2", !showOnMobile && "hidden sm:flex", classNames?.thumbnails, className),
174
- children: images.map((img, index) => /* @__PURE__ */ jsx("button", {
175
- type: "button",
176
- onClick: () => setSelectedIndex(index),
177
- className: cn("bg-muted overflow-hidden border-2 transition-all duration-200 shrink-0 rounded-md relative", sizeClassName, selectedIndex === index ? cn("border-foreground", classNames?.thumbnailActive) : cn("border-transparent opacity-60 hover:opacity-100", classNames?.thumbnail)),
178
- "aria-label": `View image ${index + 1}`,
179
- children: /* @__PURE__ */ jsx(Image, {
180
- src: img.thumbnail || img.src,
181
- alt: img.alt || `${title || "Gallery"} thumbnail ${index + 1}`,
182
- fill: true,
183
- className: "object-cover",
184
- sizes: "80px"
185
- })
186
- }, index))
187
- });
188
- }
189
-
190
66
  //#endregion
191
67
  //#region src/components/gallery/gallery-dots.tsx
192
68
  function GalleryDots({ showOnDesktop = false, className }) {
@@ -198,7 +74,8 @@ function GalleryDots({ showOnDesktop = false, className }) {
198
74
  type: "button",
199
75
  onClick: () => setSelectedIndex(index),
200
76
  className: cn("h-2 rounded-full transition-all duration-300", selectedIndex === index ? cn("bg-foreground w-6", classNames?.dotActive) : cn("bg-muted-foreground/30 hover:bg-muted-foreground/50 w-2", classNames?.dot)),
201
- "aria-label": `Go to image ${index + 1}`
77
+ "aria-label": `Go to image ${index + 1}`,
78
+ "aria-current": selectedIndex === index ? "true" : void 0
202
79
  }, index))
203
80
  });
204
81
  }
@@ -436,7 +313,7 @@ function GalleryLightbox({ className }) {
436
313
  })
437
314
  })]
438
315
  }),
439
- images.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
316
+ images.length > 1 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("button", {
440
317
  type: "button",
441
318
  onClick: goToPrevious,
442
319
  className: "absolute left-4 top-1/2 -translate-y-1/2 z-50 p-3 rounded-full bg-white/10 hover:bg-white/20 transition-colors",
@@ -475,6 +352,107 @@ function GalleryLightbox({ className }) {
475
352
  });
476
353
  }
477
354
 
355
+ //#endregion
356
+ //#region src/components/gallery/gallery-main.tsx
357
+ function GalleryMain({ children, badges, showNav = true, aspectRatio = "aspect-[4/5]", className }) {
358
+ const { images, selectedIndex, setLightboxOpen, goToNext, goToPrevious, classNames, title } = useGallery();
359
+ const [imagesLoaded, setImagesLoaded] = useState(new Set([0]));
360
+ const [touchStart, setTouchStart] = useState(null);
361
+ const [touchEnd, setTouchEnd] = useState(null);
362
+ const sliderRef = useRef(null);
363
+ const minSwipeDistance = 50;
364
+ useEffect(() => {
365
+ images.forEach((img, index) => {
366
+ if (index === 0) return;
367
+ const preloadImg = new window.Image();
368
+ preloadImg.src = img.src;
369
+ preloadImg.onload = () => {
370
+ setImagesLoaded((prev) => new Set([...prev, index]));
371
+ };
372
+ });
373
+ }, [images]);
374
+ const onTouchStart = useCallback((e) => {
375
+ setTouchEnd(null);
376
+ setTouchStart(e.targetTouches[0].clientX);
377
+ }, []);
378
+ const onTouchMove = useCallback((e) => {
379
+ setTouchEnd(e.targetTouches[0].clientX);
380
+ }, []);
381
+ const onTouchEnd = useCallback(() => {
382
+ if (!touchStart || !touchEnd) return;
383
+ const distance = touchStart - touchEnd;
384
+ if (distance > minSwipeDistance) goToNext();
385
+ if (distance < -minSwipeDistance) goToPrevious();
386
+ }, [
387
+ touchStart,
388
+ touchEnd,
389
+ goToNext,
390
+ goToPrevious
391
+ ]);
392
+ const handleImageLoad = useCallback((index) => {
393
+ setImagesLoaded((prev) => new Set([...prev, index]));
394
+ }, []);
395
+ return /* @__PURE__ */ jsx("div", {
396
+ className: cn("relative", className),
397
+ children: /* @__PURE__ */ jsxs("div", {
398
+ ref: sliderRef,
399
+ className: cn(aspectRatio, "bg-muted overflow-hidden relative rounded-lg", classNames?.main),
400
+ onTouchStart,
401
+ onTouchMove,
402
+ onTouchEnd,
403
+ children: [
404
+ /* @__PURE__ */ jsx("div", {
405
+ className: cn("absolute inset-0 flex transition-transform duration-300 ease-out", classNames?.slider),
406
+ style: { transform: `translateX(-${selectedIndex * 100}%)` },
407
+ children: images.map((img, index) => /* @__PURE__ */ jsxs("div", {
408
+ className: "w-full h-full shrink-0 relative cursor-zoom-in",
409
+ onClick: () => setLightboxOpen(true),
410
+ children: [/* @__PURE__ */ jsx(Image, {
411
+ src: img.src,
412
+ alt: img.alt || `${title || "Gallery"} - Image ${index + 1}`,
413
+ fill: true,
414
+ className: cn("object-cover transition-opacity duration-300", imagesLoaded.has(index) ? "opacity-100" : "opacity-0", classNames?.mainImage),
415
+ sizes: "(max-width: 768px) 100vw, 50vw",
416
+ priority: index === 0,
417
+ onLoad: () => handleImageLoad(index)
418
+ }), !imagesLoaded.has(index) && /* @__PURE__ */ jsx("div", {
419
+ className: "absolute inset-0 flex items-center justify-center bg-muted",
420
+ children: /* @__PURE__ */ jsx("div", { className: "w-8 h-8 border-2 border-muted-foreground/20 border-t-muted-foreground rounded-full animate-spin" })
421
+ })]
422
+ }, index))
423
+ }),
424
+ badges && badges.length > 0 && /* @__PURE__ */ jsx("div", {
425
+ className: cn("absolute top-4 left-4 flex flex-col gap-2 z-10", classNames?.badges),
426
+ children: badges.map((badge, index) => /* @__PURE__ */ jsx("span", {
427
+ className: cn("px-3 py-1 text-xs font-medium uppercase tracking-wider", classNames?.badge, badge.className),
428
+ children: badge.label
429
+ }, index))
430
+ }),
431
+ showNav && images.length > 1 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("button", {
432
+ type: "button",
433
+ onClick: (e) => {
434
+ e.stopPropagation();
435
+ goToPrevious();
436
+ },
437
+ className: cn("hidden sm:flex absolute left-3 top-1/2 -translate-y-1/2 z-10", "w-10 h-10 items-center justify-center rounded-full", "bg-background/80 backdrop-blur-sm shadow-md hover:bg-background transition-colors", classNames?.nav, classNames?.navPrev),
438
+ "aria-label": "Previous image",
439
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-5 w-5" })
440
+ }), /* @__PURE__ */ jsx("button", {
441
+ type: "button",
442
+ onClick: (e) => {
443
+ e.stopPropagation();
444
+ goToNext();
445
+ },
446
+ className: cn("hidden sm:flex absolute right-3 top-1/2 -translate-y-1/2 z-10", "w-10 h-10 items-center justify-center rounded-full", "bg-background/80 backdrop-blur-sm shadow-md hover:bg-background transition-colors", classNames?.nav, classNames?.navNext),
447
+ "aria-label": "Next image",
448
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-5 w-5" })
449
+ })] }),
450
+ children
451
+ ]
452
+ })
453
+ });
454
+ }
455
+
478
456
  //#endregion
479
457
  //#region src/components/gallery/gallery-nav.tsx
480
458
  function GalleryNav({ direction, className, children }) {
@@ -490,6 +468,29 @@ function GalleryNav({ direction, className, children }) {
490
468
  });
491
469
  }
492
470
 
471
+ //#endregion
472
+ //#region src/components/gallery/gallery-thumbnails.tsx
473
+ function GalleryThumbnails({ showOnMobile = true, orientation = "horizontal", sizeClassName = "w-16 h-20 sm:w-20 sm:h-24", className }) {
474
+ const { images, selectedIndex, setSelectedIndex, classNames, title } = useGallery();
475
+ if (images.length <= 1) return null;
476
+ return /* @__PURE__ */ jsx("div", {
477
+ className: cn("flex gap-2 sm:gap-3 scrollbar-hide", orientation === "vertical" ? "flex-col overflow-y-auto pr-2" : "overflow-x-auto pb-2", !showOnMobile && "hidden sm:flex", classNames?.thumbnails, className),
478
+ children: images.map((img, index) => /* @__PURE__ */ jsx("button", {
479
+ type: "button",
480
+ onClick: () => setSelectedIndex(index),
481
+ className: cn("bg-muted overflow-hidden border-2 transition-all duration-200 shrink-0 rounded-md relative", sizeClassName, selectedIndex === index ? cn("border-foreground", classNames?.thumbnailActive) : cn("border-transparent opacity-60 hover:opacity-100", classNames?.thumbnail)),
482
+ "aria-label": `View image ${index + 1}`,
483
+ children: /* @__PURE__ */ jsx(Image, {
484
+ src: img.thumbnail || img.src,
485
+ alt: img.alt || `${title || "Gallery"} thumbnail ${index + 1}`,
486
+ fill: true,
487
+ className: "object-cover",
488
+ sizes: "80px"
489
+ })
490
+ }, index))
491
+ });
492
+ }
493
+
493
494
  //#endregion
494
495
  //#region src/components/gallery/index.tsx
495
496
  function ImageGalleryRoot({ children, images, defaultIndex = 0, classNames, title, onIndexChange, className }) {
@@ -1,49 +1,24 @@
1
- import { n as UseBaseSearchReturn, r as useBaseSearch, t as UseBaseSearchConfig } from "../use-base-search-BGgWnWaF.mjs";
2
- import { n as useKeyboardShortcut, t as UseKeyboardShortcutOptions } from "../use-keyboard-shortcut-_mRCh3QO.mjs";
1
+ import { n as UseBaseSearchReturn, r as useBaseSearch, t as UseBaseSearchConfig } from "../use-base-search-DFC4QKYU.mjs";
2
+ import { n as useKeyboardShortcut, t as UseKeyboardShortcutOptions } from "../use-keyboard-shortcut-Q4CSPzSI.mjs";
3
3
  import { RefObject } from "react";
4
4
 
5
- //#region src/hooks/use-mobile.d.ts
6
- declare function useIsMobile(): boolean;
7
- //#endregion
8
- //#region src/hooks/use-media-query.d.ts
9
- declare function useMediaQuery(query: string, defaultValue?: boolean): boolean;
10
- //#endregion
11
- //#region src/hooks/use-scroll-detection.d.ts
12
- declare const useScrollDetection: (ref: RefObject<HTMLDivElement | null>, delay?: number) => {
13
- checkScroll: () => void;
14
- canScrollLeft: boolean;
15
- canScrollRight: boolean;
16
- isScrollable: boolean;
17
- };
18
- //#endregion
19
- //#region src/hooks/use-debounce.d.ts
20
- /**
21
- * useDebounce — Returns a debounced version of the input value.
22
- *
23
- * @example
24
- * ```tsx
25
- * const [search, setSearch] = useState("");
26
- * const debouncedSearch = useDebounce(search, 300);
27
- *
28
- * useEffect(() => {
29
- * fetchResults(debouncedSearch);
30
- * }, [debouncedSearch]);
31
- * ```
32
- */
33
- declare function useDebounce<T>(value: T, delay?: number): T;
5
+ //#region src/hooks/create-search-hook.d.ts
6
+ interface CreateSearchHookConfig extends UseBaseSearchConfig {}
34
7
  /**
35
- * useDebouncedCallback Returns a debounced version of a callback function.
8
+ * Factory that pre-configures useBaseSearch with typed defaults.
36
9
  *
37
10
  * @example
38
- * ```tsx
39
- * const debouncedSave = useDebouncedCallback((value: string) => {
40
- * saveToApi(value);
41
- * }, 500);
11
+ * ```ts
12
+ * const useOrderSearch = createSearchHook({
13
+ * basePath: "/dashboard/orders",
14
+ * searchFields: { name: "Name", sku: "SKU", barcode: "Barcode" },
15
+ * });
42
16
  *
43
- * <Input onChange={(e) => debouncedSave(e.target.value)} />
17
+ * // In component:
18
+ * const search = useOrderSearch();
44
19
  * ```
45
20
  */
46
- declare function useDebouncedCallback<T extends (...args: any[]) => any>(callback: T, delay?: number): (...args: Parameters<T>) => void;
21
+ declare function createSearchHook(config: CreateSearchHookConfig): () => UseBaseSearchReturn;
47
22
  //#endregion
48
23
  //#region src/hooks/use-copy-to-clipboard.d.ts
49
24
  interface UseCopyToClipboardReturn {
@@ -72,6 +47,35 @@ interface UseCopyToClipboardReturn {
72
47
  */
73
48
  declare function useCopyToClipboard(resetDelay?: number): UseCopyToClipboardReturn;
74
49
  //#endregion
50
+ //#region src/hooks/use-debounce.d.ts
51
+ /**
52
+ * useDebounce — Returns a debounced version of the input value.
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * const [search, setSearch] = useState("");
57
+ * const debouncedSearch = useDebounce(search, 300);
58
+ *
59
+ * useEffect(() => {
60
+ * fetchResults(debouncedSearch);
61
+ * }, [debouncedSearch]);
62
+ * ```
63
+ */
64
+ declare function useDebounce<T>(value: T, delay?: number): T;
65
+ /**
66
+ * useDebouncedCallback — Returns a debounced version of a callback function.
67
+ *
68
+ * @example
69
+ * ```tsx
70
+ * const debouncedSave = useDebouncedCallback((value: string) => {
71
+ * saveToApi(value);
72
+ * }, 500);
73
+ *
74
+ * <Input onChange={(e) => debouncedSave(e.target.value)} />
75
+ * ```
76
+ */
77
+ declare function useDebouncedCallback<T extends (...args: any[]) => any>(callback: T, delay?: number): (...args: Parameters<T>) => void;
78
+ //#endregion
75
79
  //#region src/hooks/use-local-storage.d.ts
76
80
  /**
77
81
  * useLocalStorage — Persist state in localStorage with type safety and optional expiry.
@@ -92,6 +96,20 @@ declare function useCopyToClipboard(resetDelay?: number): UseCopyToClipboardRetu
92
96
  */
93
97
  declare function useLocalStorage<T>(key: string, initialValue: T, ttl?: number): [T, (value: T | ((prev: T) => T)) => void, () => void];
94
98
  //#endregion
99
+ //#region src/hooks/use-media-query.d.ts
100
+ declare function useMediaQuery(query: string, defaultValue?: boolean): boolean;
101
+ //#endregion
102
+ //#region src/hooks/use-mobile.d.ts
103
+ declare function useIsMobile(): boolean;
104
+ //#endregion
105
+ //#region src/hooks/use-scroll-detection.d.ts
106
+ declare const useScrollDetection: (ref: RefObject<HTMLDivElement | null>, delay?: number) => {
107
+ checkScroll: () => void;
108
+ canScrollLeft: boolean;
109
+ canScrollRight: boolean;
110
+ isScrollable: boolean;
111
+ };
112
+ //#endregion
95
113
  //#region src/lib/storage.d.ts
96
114
  /**
97
115
  * A utility module for handling localStorage operations with error handling and SSR safety
@@ -163,4 +181,4 @@ declare const storage: {
163
181
  };
164
182
  };
165
183
  //#endregion
166
- export { TTL, type UseBaseSearchConfig, type UseBaseSearchReturn, type UseCopyToClipboardReturn, type UseKeyboardShortcutOptions, clearStorage, generateUUID, getStorageItem, isStorageEmpty, removeStorageItem, setStorageItem, storage, useBaseSearch, useCopyToClipboard, useDebounce, useDebouncedCallback, useIsMobile, useKeyboardShortcut, useLocalStorage, useMediaQuery, useScrollDetection };
184
+ export { type CreateSearchHookConfig, TTL, type UseBaseSearchConfig, type UseBaseSearchReturn, type UseCopyToClipboardReturn, type UseKeyboardShortcutOptions, clearStorage, createSearchHook, generateUUID, getStorageItem, isStorageEmpty, removeStorageItem, setStorageItem, storage, useBaseSearch, useCopyToClipboard, useDebounce, useDebouncedCallback, useIsMobile, useKeyboardShortcut, useLocalStorage, useMediaQuery, useScrollDetection };