@opensite/ui 0.7.4 → 0.7.5

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.
@@ -1,15 +1,434 @@
1
1
  "use client";
2
- import * as React3 from 'react';
3
- import React3__default from 'react';
2
+ import * as React5 from 'react';
3
+ import React5__default from 'react';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
6
  import { Img } from '@page-speed/img';
7
- import { jsx, jsxs } from 'react/jsx-runtime';
7
+ import { cva } from 'class-variance-authority';
8
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
9
 
9
10
  // components/blocks/carousel/carousel-fullscreen-scroll-fx.tsx
10
11
  function cn(...inputs) {
11
12
  return twMerge(clsx(inputs));
12
13
  }
14
+ function normalizePhoneNumber(input) {
15
+ const trimmed = input.trim();
16
+ if (trimmed.toLowerCase().startsWith("tel:")) {
17
+ return trimmed;
18
+ }
19
+ const match = trimmed.match(/^[\s\+\-\(\)]*(\d[\d\s\-\(\)\.]*\d)[\s\-]*(x|ext\.?|extension)?[\s\-]*(\d+)?$/i);
20
+ if (match) {
21
+ const mainNumber = match[1].replace(/[\s\-\(\)\.]/g, "");
22
+ const extension = match[3];
23
+ const normalized = mainNumber.length >= 10 && !trimmed.startsWith("+") ? `+${mainNumber}` : mainNumber;
24
+ const withExtension = extension ? `${normalized};ext=${extension}` : normalized;
25
+ return `tel:${withExtension}`;
26
+ }
27
+ const cleaned = trimmed.replace(/[\s\-\(\)\.]/g, "");
28
+ return `tel:${cleaned}`;
29
+ }
30
+ function normalizeEmail(input) {
31
+ const trimmed = input.trim();
32
+ if (trimmed.toLowerCase().startsWith("mailto:")) {
33
+ return trimmed;
34
+ }
35
+ return `mailto:${trimmed}`;
36
+ }
37
+ function isEmail(input) {
38
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
39
+ return emailRegex.test(input.trim());
40
+ }
41
+ function isPhoneNumber(input) {
42
+ const trimmed = input.trim();
43
+ if (trimmed.toLowerCase().startsWith("tel:")) {
44
+ return true;
45
+ }
46
+ const phoneRegex = /^[\s\+\-\(\)]*\d[\d\s\-\(\)\.]*\d[\s\-]*(x|ext\.?|extension)?[\s\-]*\d*$/i;
47
+ return phoneRegex.test(trimmed);
48
+ }
49
+ function isInternalUrl(href) {
50
+ if (typeof window === "undefined") {
51
+ return href.startsWith("/") && !href.startsWith("//");
52
+ }
53
+ const trimmed = href.trim();
54
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
55
+ return true;
56
+ }
57
+ try {
58
+ const url = new URL(trimmed, window.location.href);
59
+ const currentOrigin = window.location.origin;
60
+ const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
61
+ return normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin);
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+ function toRelativePath(href) {
67
+ if (typeof window === "undefined") {
68
+ return href;
69
+ }
70
+ const trimmed = href.trim();
71
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
72
+ return trimmed;
73
+ }
74
+ try {
75
+ const url = new URL(trimmed, window.location.href);
76
+ const currentOrigin = window.location.origin;
77
+ const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
78
+ if (normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin)) {
79
+ return url.pathname + url.search + url.hash;
80
+ }
81
+ } catch {
82
+ }
83
+ return trimmed;
84
+ }
85
+ function useNavigation({
86
+ href,
87
+ onClick
88
+ } = {}) {
89
+ const linkType = React5.useMemo(() => {
90
+ if (!href || href.trim() === "") {
91
+ return onClick ? "none" : "none";
92
+ }
93
+ const trimmed = href.trim();
94
+ if (trimmed.toLowerCase().startsWith("mailto:") || isEmail(trimmed)) {
95
+ return "mailto";
96
+ }
97
+ if (trimmed.toLowerCase().startsWith("tel:") || isPhoneNumber(trimmed)) {
98
+ return "tel";
99
+ }
100
+ if (isInternalUrl(trimmed)) {
101
+ return "internal";
102
+ }
103
+ try {
104
+ new URL(trimmed, typeof window !== "undefined" ? window.location.href : "http://localhost");
105
+ return "external";
106
+ } catch {
107
+ return "internal";
108
+ }
109
+ }, [href, onClick]);
110
+ const normalizedHref = React5.useMemo(() => {
111
+ if (!href || href.trim() === "") {
112
+ return void 0;
113
+ }
114
+ const trimmed = href.trim();
115
+ switch (linkType) {
116
+ case "tel":
117
+ return normalizePhoneNumber(trimmed);
118
+ case "mailto":
119
+ return normalizeEmail(trimmed);
120
+ case "internal":
121
+ return toRelativePath(trimmed);
122
+ case "external":
123
+ return trimmed;
124
+ default:
125
+ return trimmed;
126
+ }
127
+ }, [href, linkType]);
128
+ const target = React5.useMemo(() => {
129
+ switch (linkType) {
130
+ case "external":
131
+ return "_blank";
132
+ case "internal":
133
+ return "_self";
134
+ case "mailto":
135
+ case "tel":
136
+ return void 0;
137
+ default:
138
+ return void 0;
139
+ }
140
+ }, [linkType]);
141
+ const rel = React5.useMemo(() => {
142
+ if (linkType === "external") {
143
+ return "noopener noreferrer";
144
+ }
145
+ return void 0;
146
+ }, [linkType]);
147
+ const isExternal = linkType === "external";
148
+ const isInternal = linkType === "internal";
149
+ const shouldUseRouter = isInternal && typeof normalizedHref === "string" && normalizedHref.startsWith("/");
150
+ const handleClick = React5.useCallback(
151
+ (event) => {
152
+ if (onClick) {
153
+ try {
154
+ onClick(event);
155
+ } catch (error) {
156
+ console.error("Error in user onClick handler:", error);
157
+ }
158
+ }
159
+ if (event.defaultPrevented) {
160
+ return;
161
+ }
162
+ if (shouldUseRouter && normalizedHref && event.button === 0 && // left-click only
163
+ !event.metaKey && !event.altKey && !event.ctrlKey && !event.shiftKey) {
164
+ if (typeof window !== "undefined") {
165
+ const handler = window.__opensiteNavigationHandler;
166
+ if (typeof handler === "function") {
167
+ try {
168
+ const handled = handler(normalizedHref, event.nativeEvent || event);
169
+ if (handled !== false) {
170
+ event.preventDefault();
171
+ }
172
+ } catch (error) {
173
+ console.error("Error in navigation handler:", error);
174
+ }
175
+ }
176
+ }
177
+ }
178
+ },
179
+ [onClick, shouldUseRouter, normalizedHref]
180
+ );
181
+ return {
182
+ linkType,
183
+ normalizedHref,
184
+ target,
185
+ rel,
186
+ isExternal,
187
+ isInternal,
188
+ shouldUseRouter,
189
+ handleClick
190
+ };
191
+ }
192
+ var baseStyles = [
193
+ // Layout
194
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap shrink-0",
195
+ // Typography - using CSS variables with sensible defaults
196
+ "font-[var(--button-font-family,inherit)]",
197
+ "font-[var(--button-font-weight,500)]",
198
+ "tracking-[var(--button-letter-spacing,0)]",
199
+ "leading-[var(--button-line-height,1.25)]",
200
+ "[text-transform:var(--button-text-transform,none)]",
201
+ "text-sm",
202
+ // Border radius
203
+ "rounded-[var(--button-radius,var(--radius,0.375rem))]",
204
+ // Smooth transition - using [transition:...] to set full shorthand property (not just transition-property)
205
+ "[transition:var(--button-transition,all_250ms_cubic-bezier(0.4,0,0.2,1))]",
206
+ // Box shadow (master level) - using [box-shadow:...] for complex multi-value shadows
207
+ "[box-shadow:var(--button-shadow,none)]",
208
+ "hover:[box-shadow:var(--button-shadow-hover,var(--button-shadow,none))]",
209
+ // Disabled state
210
+ "disabled:pointer-events-none disabled:opacity-50",
211
+ // SVG handling
212
+ "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0",
213
+ // Focus styles
214
+ "outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
215
+ // Invalid state
216
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive"
217
+ ].join(" ");
218
+ var buttonVariants = cva(baseStyles, {
219
+ variants: {
220
+ variant: {
221
+ // Default (Primary) variant - full customization
222
+ default: [
223
+ "bg-[var(--button-default-bg,hsl(var(--primary)))]",
224
+ "text-[var(--button-default-fg,hsl(var(--primary-foreground)))]",
225
+ "border-[length:var(--button-default-border-width,0px)]",
226
+ "border-[color:var(--button-default-border,transparent)]",
227
+ "[box-shadow:var(--button-default-shadow,var(--button-shadow,none))]",
228
+ "hover:bg-[var(--button-default-hover-bg,hsl(var(--primary)/0.9))]",
229
+ "hover:text-[var(--button-default-hover-fg,var(--button-default-fg,hsl(var(--primary-foreground))))]",
230
+ "hover:border-[color:var(--button-default-hover-border,var(--button-default-border,transparent))]",
231
+ "hover:[box-shadow:var(--button-default-shadow-hover,var(--button-shadow-hover,var(--button-default-shadow,var(--button-shadow,none))))]"
232
+ ].join(" "),
233
+ // Destructive variant - full customization
234
+ destructive: [
235
+ "bg-[var(--button-destructive-bg,hsl(var(--destructive)))]",
236
+ "text-[var(--button-destructive-fg,white)]",
237
+ "border-[length:var(--button-destructive-border-width,0px)]",
238
+ "border-[color:var(--button-destructive-border,transparent)]",
239
+ "[box-shadow:var(--button-destructive-shadow,var(--button-shadow,none))]",
240
+ "hover:bg-[var(--button-destructive-hover-bg,hsl(var(--destructive)/0.9))]",
241
+ "hover:text-[var(--button-destructive-hover-fg,var(--button-destructive-fg,white))]",
242
+ "hover:border-[color:var(--button-destructive-hover-border,var(--button-destructive-border,transparent))]",
243
+ "hover:[box-shadow:var(--button-destructive-shadow-hover,var(--button-shadow-hover,var(--button-destructive-shadow,var(--button-shadow,none))))]",
244
+ "focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",
245
+ "dark:bg-destructive/60"
246
+ ].join(" "),
247
+ // Outline variant - full customization with proper border handling
248
+ outline: [
249
+ "bg-[var(--button-outline-bg,hsl(var(--background)))]",
250
+ "text-[var(--button-outline-fg,inherit)]",
251
+ "border-[length:var(--button-outline-border-width,1px)]",
252
+ "border-[color:var(--button-outline-border,hsl(var(--border)))]",
253
+ "[box-shadow:var(--button-outline-shadow,var(--button-shadow,0_1px_2px_0_rgb(0_0_0/0.05)))]",
254
+ "hover:bg-[var(--button-outline-hover-bg,hsl(var(--accent)))]",
255
+ "hover:text-[var(--button-outline-hover-fg,hsl(var(--accent-foreground)))]",
256
+ "hover:border-[color:var(--button-outline-hover-border,var(--button-outline-border,hsl(var(--border))))]",
257
+ "hover:[box-shadow:var(--button-outline-shadow-hover,var(--button-shadow-hover,var(--button-outline-shadow,var(--button-shadow,none))))]",
258
+ "dark:bg-input/30 dark:border-input dark:hover:bg-input/50"
259
+ ].join(" "),
260
+ // Secondary variant - full customization
261
+ secondary: [
262
+ "bg-[var(--button-secondary-bg,hsl(var(--secondary)))]",
263
+ "text-[var(--button-secondary-fg,hsl(var(--secondary-foreground)))]",
264
+ "border-[length:var(--button-secondary-border-width,0px)]",
265
+ "border-[color:var(--button-secondary-border,transparent)]",
266
+ "[box-shadow:var(--button-secondary-shadow,var(--button-shadow,none))]",
267
+ "hover:bg-[var(--button-secondary-hover-bg,hsl(var(--secondary)/0.8))]",
268
+ "hover:text-[var(--button-secondary-hover-fg,var(--button-secondary-fg,hsl(var(--secondary-foreground))))]",
269
+ "hover:border-[color:var(--button-secondary-hover-border,var(--button-secondary-border,transparent))]",
270
+ "hover:[box-shadow:var(--button-secondary-shadow-hover,var(--button-shadow-hover,var(--button-secondary-shadow,var(--button-shadow,none))))]"
271
+ ].join(" "),
272
+ // Ghost variant - full customization
273
+ ghost: [
274
+ "bg-[var(--button-ghost-bg,transparent)]",
275
+ "text-[var(--button-ghost-fg,inherit)]",
276
+ "border-[length:var(--button-ghost-border-width,0px)]",
277
+ "border-[color:var(--button-ghost-border,transparent)]",
278
+ "[box-shadow:var(--button-ghost-shadow,var(--button-shadow,none))]",
279
+ "hover:bg-[var(--button-ghost-hover-bg,hsl(var(--accent)))]",
280
+ "hover:text-[var(--button-ghost-hover-fg,hsl(var(--accent-foreground)))]",
281
+ "hover:border-[color:var(--button-ghost-hover-border,var(--button-ghost-border,transparent))]",
282
+ "hover:[box-shadow:var(--button-ghost-shadow-hover,var(--button-shadow-hover,var(--button-ghost-shadow,var(--button-shadow,none))))]",
283
+ "dark:hover:bg-accent/50"
284
+ ].join(" "),
285
+ // Link variant - full customization
286
+ link: [
287
+ "bg-[var(--button-link-bg,transparent)]",
288
+ "text-[var(--button-link-fg,hsl(var(--primary)))]",
289
+ "border-[length:var(--button-link-border-width,0px)]",
290
+ "border-[color:var(--button-link-border,transparent)]",
291
+ "[box-shadow:var(--button-link-shadow,none)]",
292
+ "hover:bg-[var(--button-link-hover-bg,transparent)]",
293
+ "hover:text-[var(--button-link-hover-fg,var(--button-link-fg,hsl(var(--primary))))]",
294
+ "hover:[box-shadow:var(--button-link-shadow-hover,none)]",
295
+ "underline-offset-4 hover:underline"
296
+ ].join(" ")
297
+ },
298
+ size: {
299
+ default: [
300
+ "h-[var(--button-height-md,2.25rem)]",
301
+ "px-[var(--button-padding-x-md,1rem)]",
302
+ "py-[var(--button-padding-y-md,0.5rem)]",
303
+ "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
304
+ ].join(" "),
305
+ sm: [
306
+ "h-[var(--button-height-sm,2rem)]",
307
+ "px-[var(--button-padding-x-sm,0.75rem)]",
308
+ "py-[var(--button-padding-y-sm,0.25rem)]",
309
+ "gap-1.5",
310
+ "has-[>svg]:px-[calc(var(--button-padding-x-sm,0.75rem)*0.83)]"
311
+ ].join(" "),
312
+ md: [
313
+ "h-[var(--button-height-md,2.25rem)]",
314
+ "px-[var(--button-padding-x-md,1rem)]",
315
+ "py-[var(--button-padding-y-md,0.5rem)]",
316
+ "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
317
+ ].join(" "),
318
+ lg: [
319
+ "h-[var(--button-height-lg,2.5rem)]",
320
+ "px-[var(--button-padding-x-lg,1.5rem)]",
321
+ "py-[var(--button-padding-y-lg,0.5rem)]",
322
+ "has-[>svg]:px-[calc(var(--button-padding-x-lg,1.5rem)*0.67)]"
323
+ ].join(" "),
324
+ icon: "size-[var(--button-height-md,2.25rem)]",
325
+ "icon-sm": "size-[var(--button-height-sm,2rem)]",
326
+ "icon-lg": "size-[var(--button-height-lg,2.5rem)]"
327
+ }
328
+ },
329
+ defaultVariants: {
330
+ variant: "default",
331
+ size: "default"
332
+ }
333
+ });
334
+ var Pressable = React5.forwardRef(
335
+ ({
336
+ children,
337
+ className,
338
+ href,
339
+ onClick,
340
+ variant,
341
+ size,
342
+ asButton = false,
343
+ fallbackComponentType = "span",
344
+ componentType,
345
+ "aria-label": ariaLabel,
346
+ "aria-describedby": ariaDescribedby,
347
+ id,
348
+ ...props
349
+ }, ref) => {
350
+ const navigation = useNavigation({ href, onClick });
351
+ const {
352
+ normalizedHref,
353
+ target,
354
+ rel,
355
+ linkType,
356
+ isInternal,
357
+ handleClick
358
+ } = navigation;
359
+ const shouldRenderLink = normalizedHref && linkType !== "none";
360
+ const shouldRenderButton = !shouldRenderLink && onClick;
361
+ const effectiveComponentType = componentType || (shouldRenderLink ? "a" : shouldRenderButton ? "button" : fallbackComponentType);
362
+ const finalComponentType = isInternal && shouldRenderLink ? "a" : effectiveComponentType;
363
+ const shouldApplyButtonStyles = asButton || variant || size;
364
+ const combinedClassName = cn(
365
+ shouldApplyButtonStyles && buttonVariants({ variant, size }),
366
+ className
367
+ );
368
+ const dataProps = Object.fromEntries(
369
+ Object.entries(props).filter(([key]) => key.startsWith("data-"))
370
+ );
371
+ const buttonDataAttributes = shouldApplyButtonStyles ? {
372
+ "data-slot": "button",
373
+ "data-variant": variant ?? "default",
374
+ "data-size": size ?? "default"
375
+ } : {};
376
+ const commonProps = {
377
+ className: combinedClassName,
378
+ onClick: handleClick,
379
+ "aria-label": ariaLabel,
380
+ "aria-describedby": ariaDescribedby,
381
+ id,
382
+ ...dataProps,
383
+ ...buttonDataAttributes
384
+ };
385
+ if (finalComponentType === "a" && shouldRenderLink) {
386
+ return /* @__PURE__ */ jsx(
387
+ "a",
388
+ {
389
+ ref,
390
+ href: normalizedHref,
391
+ target,
392
+ rel,
393
+ ...commonProps,
394
+ ...props,
395
+ children
396
+ }
397
+ );
398
+ }
399
+ if (finalComponentType === "button") {
400
+ return /* @__PURE__ */ jsx(
401
+ "button",
402
+ {
403
+ ref,
404
+ type: props.type || "button",
405
+ ...commonProps,
406
+ ...props,
407
+ children
408
+ }
409
+ );
410
+ }
411
+ if (finalComponentType === "div") {
412
+ return /* @__PURE__ */ jsx(
413
+ "div",
414
+ {
415
+ ref,
416
+ ...commonProps,
417
+ children
418
+ }
419
+ );
420
+ }
421
+ return /* @__PURE__ */ jsx(
422
+ "span",
423
+ {
424
+ ref,
425
+ ...commonProps,
426
+ children
427
+ }
428
+ );
429
+ }
430
+ );
431
+ Pressable.displayName = "Pressable";
13
432
  var maxWidthStyles = {
14
433
  sm: "max-w-screen-sm",
15
434
  md: "max-w-screen-md",
@@ -19,7 +438,7 @@ var maxWidthStyles = {
19
438
  "4xl": "max-w-[1536px]",
20
439
  full: "max-w-full"
21
440
  };
22
- var Container = React3__default.forwardRef(
441
+ var Container = React5__default.forwardRef(
23
442
  ({ children, maxWidth = "xl", className, as = "div", ...props }, ref) => {
24
443
  const Component = as;
25
444
  return /* @__PURE__ */ jsx(
@@ -324,7 +743,7 @@ var spacingStyles = {
324
743
  };
325
744
  var predefinedSpacings = ["none", "sm", "md", "lg", "xl"];
326
745
  var isPredefinedSpacing = (spacing) => predefinedSpacings.includes(spacing);
327
- var Section = React3__default.forwardRef(
746
+ var Section = React5__default.forwardRef(
328
747
  ({
329
748
  id,
330
749
  title,
@@ -404,42 +823,45 @@ function CarouselFullscreenScrollFx({
404
823
  pattern = "diagonalCrossBasic",
405
824
  patternOpacity = 0.033
406
825
  }) {
407
- const containerRef = React3.useRef(null);
408
- const [activeIndex, setActiveIndex] = React3.useState(0);
409
- React3.useEffect(() => {
410
- const observers = [];
411
- slides?.forEach((slide, index) => {
412
- const element = document.getElementById(`fullscreen-${slide.id}`);
413
- if (element) {
414
- const observer = new IntersectionObserver(
415
- (entries) => {
416
- entries.forEach((entry) => {
417
- if (entry.isIntersecting && entry.intersectionRatio > 0.5) {
418
- setActiveIndex(index);
419
- }
420
- });
421
- },
422
- { threshold: 0.5 }
423
- );
424
- observer.observe(element);
425
- observers.push(observer);
426
- }
427
- });
428
- return () => {
429
- observers.forEach((observer) => observer.disconnect());
826
+ const containerRef = React5.useRef(null);
827
+ const scrollContainerRef = React5.useRef(null);
828
+ const [activeIndex, setActiveIndex] = React5.useState(0);
829
+ React5.useEffect(() => {
830
+ const scrollContainer = scrollContainerRef.current;
831
+ if (!scrollContainer || !slides?.length) return;
832
+ const handleScroll = () => {
833
+ const scrollLeft = scrollContainer.scrollLeft;
834
+ const slideWidth = scrollContainer.clientWidth;
835
+ const newIndex = Math.round(scrollLeft / slideWidth);
836
+ setActiveIndex(newIndex);
430
837
  };
838
+ scrollContainer.addEventListener("scroll", handleScroll);
839
+ return () => scrollContainer.removeEventListener("scroll", handleScroll);
431
840
  }, [slides]);
841
+ const scrollToSlide = React5.useCallback((index) => {
842
+ const scrollContainer = scrollContainerRef.current;
843
+ if (!scrollContainer) return;
844
+ const slideWidth = scrollContainer.clientWidth;
845
+ if (typeof scrollContainer.scrollTo === "function") {
846
+ scrollContainer.scrollTo({
847
+ left: slideWidth * index,
848
+ behavior: "smooth"
849
+ });
850
+ } else {
851
+ scrollContainer.scrollLeft = slideWidth * index;
852
+ }
853
+ }, []);
432
854
  return /* @__PURE__ */ jsxs(
433
855
  Section,
434
856
  {
435
857
  ref: containerRef,
436
858
  background,
437
859
  spacing,
438
- className: cn(className),
860
+ className: cn("relative overflow-hidden", className),
439
861
  pattern,
440
862
  patternOpacity,
441
863
  containerMaxWidth,
442
- containerClassName,
864
+ containerClassName: "p-0",
443
865
  children: [
444
866
  /* @__PURE__ */ jsx(
445
867
  "div",
@@ -451,10 +873,7 @@ function CarouselFullscreenScrollFx({
451
873
  children: slides?.map((slide, index) => /* @__PURE__ */ jsx(
452
874
  "button",
453
875
  {
454
- onClick: () => {
455
- const element = document.getElementById(`fullscreen-${slide.id}`);
456
- element?.scrollIntoView({ behavior: "smooth" });
457
- },
876
+ onClick: () => scrollToSlide(index),
458
877
  className: cn(
459
878
  "h-3 w-3 rounded-full border-2 transition-all",
460
879
  activeIndex === index ? "scale-125 border-white bg-white" : "border-white/50 bg-transparent hover:border-white"
@@ -465,111 +884,150 @@ function CarouselFullscreenScrollFx({
465
884
  ))
466
885
  }
467
886
  ),
468
- slidesSlot ? slidesSlot : slides?.map((slide, index) => /* @__PURE__ */ jsxs(
887
+ /* @__PURE__ */ jsx(
469
888
  "div",
470
889
  {
471
- id: `fullscreen-${slide.id}`,
472
- className: cn(
473
- "relative flex h-screen w-full items-center justify-center overflow-hidden",
474
- slide.className
475
- ),
476
- children: [
477
- /* @__PURE__ */ jsxs("div", { className: "absolute inset-0", children: [
478
- /* @__PURE__ */ jsx(
479
- Img,
480
- {
481
- src: slide.image,
482
- alt: typeof slide.title === "string" ? slide.title : `Slide ${index + 1}`,
483
- className: cn(
484
- "h-full w-full object-cover",
485
- slide.imageClassName
486
- ),
487
- optixFlowConfig
488
- }
489
- ),
490
- /* @__PURE__ */ jsx(
491
- "div",
492
- {
493
- className: "absolute inset-0",
494
- style: {
495
- backgroundColor: slide.overlayColor || "rgba(0, 0, 0, 0.5)"
496
- }
497
- }
498
- )
499
- ] }),
500
- /* @__PURE__ */ jsxs(
890
+ ref: scrollContainerRef,
891
+ className: "flex h-screen snap-x snap-mandatory overflow-x-auto overflow-y-hidden scroll-smooth",
892
+ style: { scrollbarWidth: "none", msOverflowStyle: "none" },
893
+ children: slidesSlot ? slidesSlot : slides?.map((slide, index) => {
894
+ const renderActions = React5.useMemo(() => {
895
+ if (!slide.actions || slide.actions.length === 0) return null;
896
+ return slide.actions.map((action, actionIndex) => {
897
+ const {
898
+ label,
899
+ icon,
900
+ iconAfter,
901
+ children,
902
+ className: actionClassName,
903
+ asButton,
904
+ ...pressableProps
905
+ } = action;
906
+ return /* @__PURE__ */ jsx(
907
+ Pressable,
908
+ {
909
+ asButton: asButton ?? true,
910
+ className: actionClassName,
911
+ ...pressableProps,
912
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
913
+ icon,
914
+ label,
915
+ iconAfter
916
+ ] })
917
+ },
918
+ actionIndex
919
+ );
920
+ });
921
+ }, [slide.actions]);
922
+ return /* @__PURE__ */ jsxs(
501
923
  "div",
502
924
  {
925
+ id: `fullscreen-${slide.id}`,
503
926
  className: cn(
504
- "relative z-10 mx-auto max-w-4xl md:max-w-2xl px-6 text-center text-white text-shadow",
505
- contentClassName
927
+ "relative flex h-screen min-w-full snap-start items-center justify-center overflow-hidden",
928
+ slide.className
506
929
  ),
507
930
  children: [
508
- slide.subtitle && (typeof slide.subtitle === "string" ? /* @__PURE__ */ jsx(
509
- "p",
931
+ /* @__PURE__ */ jsxs("div", { className: "absolute inset-0", children: [
932
+ /* @__PURE__ */ jsx(
933
+ Img,
934
+ {
935
+ src: slide.image,
936
+ alt: typeof slide.title === "string" ? slide.title : `Slide ${index + 1}`,
937
+ className: cn(
938
+ "h-full w-full object-cover",
939
+ slide.imageClassName
940
+ ),
941
+ optixFlowConfig
942
+ }
943
+ ),
944
+ /* @__PURE__ */ jsx(
945
+ "div",
946
+ {
947
+ className: "absolute inset-0",
948
+ style: {
949
+ backgroundColor: slide.overlayColor || "rgba(0, 0, 0, 0.5)"
950
+ }
951
+ }
952
+ )
953
+ ] }),
954
+ /* @__PURE__ */ jsxs(
955
+ "div",
510
956
  {
511
957
  className: cn(
512
- "mb-4 text-sm font-medium uppercase tracking-widest text-white/70",
513
- subtitleClassName
958
+ "relative z-10 mx-auto max-w-4xl md:max-w-2xl px-6 text-center text-white text-shadow",
959
+ contentClassName
514
960
  ),
515
- children: slide.subtitle
961
+ children: [
962
+ slide.subtitle && (typeof slide.subtitle === "string" ? /* @__PURE__ */ jsx(
963
+ "p",
964
+ {
965
+ className: cn(
966
+ "mb-4 text-sm font-medium uppercase tracking-widest text-white/70",
967
+ subtitleClassName
968
+ ),
969
+ children: slide.subtitle
970
+ }
971
+ ) : /* @__PURE__ */ jsx("div", { className: subtitleClassName, children: slide.subtitle })),
972
+ slide.title && (typeof slide.title === "string" ? /* @__PURE__ */ jsx(
973
+ "h2",
974
+ {
975
+ className: cn(
976
+ "mb-6 text-4xl font-bold tracking-tight md:text-5xl lg:text-6xl",
977
+ titleClassName
978
+ ),
979
+ children: slide.title
980
+ }
981
+ ) : /* @__PURE__ */ jsx("div", { className: titleClassName, children: slide.title })),
982
+ slide.description && (typeof slide.description === "string" ? /* @__PURE__ */ jsx(
983
+ "p",
984
+ {
985
+ className: cn(
986
+ "mx-auto text-lg text-white/80 md:text-xl text-balance",
987
+ descriptionClassName
988
+ ),
989
+ children: slide.description
990
+ }
991
+ ) : /* @__PURE__ */ jsx("div", { className: descriptionClassName, children: slide.description })),
992
+ renderActions && /* @__PURE__ */ jsx("div", { className: "mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row", children: renderActions })
993
+ ]
516
994
  }
517
- ) : /* @__PURE__ */ jsx("div", { className: subtitleClassName, children: slide.subtitle })),
518
- slide.title && (typeof slide.title === "string" ? /* @__PURE__ */ jsx(
519
- "h2",
995
+ ),
996
+ index < (slides?.length ?? 0) - 1 && /* @__PURE__ */ jsx(
997
+ "div",
520
998
  {
521
999
  className: cn(
522
- "mb-6 text-4xl font-bold tracking-tight md:text-5xl lg:text-6xl",
523
- titleClassName
1000
+ "absolute bottom-8 left-1/2 -translate-x-1/2",
1001
+ scrollIndicatorClassName
524
1002
  ),
525
- children: slide.title
1003
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2", children: [
1004
+ /* @__PURE__ */ jsx("span", { className: "text-xs uppercase tracking-widest text-white/50", children: "Scroll" }),
1005
+ /* @__PURE__ */ jsx("div", { className: "h-12 w-px animate-pulse bg-linear-to-b from-white/50 to-transparent" })
1006
+ ] })
526
1007
  }
527
- ) : /* @__PURE__ */ jsx("div", { className: titleClassName, children: slide.title })),
528
- slide.description && (typeof slide.description === "string" ? /* @__PURE__ */ jsx(
529
- "p",
1008
+ ),
1009
+ /* @__PURE__ */ jsxs(
1010
+ "div",
530
1011
  {
531
1012
  className: cn(
532
- "mx-auto text-lg text-white/80 md:text-xl text-balance",
533
- descriptionClassName
1013
+ "absolute bottom-8 right-8 text-sm text-white/50",
1014
+ counterClassName
534
1015
  ),
535
- children: slide.description
1016
+ children: [
1017
+ String(index + 1).padStart(2, "0"),
1018
+ " /",
1019
+ " ",
1020
+ String(slides?.length ?? 0).padStart(2, "0")
1021
+ ]
536
1022
  }
537
- ) : /* @__PURE__ */ jsx("div", { className: descriptionClassName, children: slide.description }))
538
- ]
539
- }
540
- ),
541
- index < (slides?.length ?? 0) - 1 && /* @__PURE__ */ jsx(
542
- "div",
543
- {
544
- className: cn(
545
- "absolute bottom-8 left-1/2 -translate-x-1/2",
546
- scrollIndicatorClassName
547
- ),
548
- children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2", children: [
549
- /* @__PURE__ */ jsx("span", { className: "text-xs uppercase tracking-widest text-white/50", children: "Scroll" }),
550
- /* @__PURE__ */ jsx("div", { className: "h-12 w-px animate-pulse bg-linear-to-b from-white/50 to-transparent" })
551
- ] })
552
- }
553
- ),
554
- /* @__PURE__ */ jsxs(
555
- "div",
556
- {
557
- className: cn(
558
- "absolute bottom-8 right-8 text-sm text-white/50",
559
- counterClassName
560
- ),
561
- children: [
562
- String(index + 1).padStart(2, "0"),
563
- " /",
564
- " ",
565
- String(slides?.length ?? 0).padStart(2, "0")
1023
+ )
566
1024
  ]
567
- }
568
- )
569
- ]
570
- },
571
- slide.id
572
- ))
1025
+ },
1026
+ slide.id
1027
+ );
1028
+ })
1029
+ }
1030
+ )
573
1031
  ]
574
1032
  }
575
1033
  );