@opensite/ui 0.7.3 → 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,
@@ -389,6 +808,7 @@ function CarouselFullscreenScrollFx({
389
808
  slides,
390
809
  slidesSlot,
391
810
  className,
811
+ containerClassName = "h-full flex flex-col justify-center",
392
812
  navigationClassName,
393
813
  contentClassName,
394
814
  subtitleClassName,
@@ -403,41 +823,45 @@ function CarouselFullscreenScrollFx({
403
823
  pattern = "diagonalCrossBasic",
404
824
  patternOpacity = 0.033
405
825
  }) {
406
- const containerRef = React3.useRef(null);
407
- const [activeIndex, setActiveIndex] = React3.useState(0);
408
- React3.useEffect(() => {
409
- const observers = [];
410
- slides?.forEach((slide, index) => {
411
- const element = document.getElementById(`fullscreen-${slide.id}`);
412
- if (element) {
413
- const observer = new IntersectionObserver(
414
- (entries) => {
415
- entries.forEach((entry) => {
416
- if (entry.isIntersecting && entry.intersectionRatio > 0.5) {
417
- setActiveIndex(index);
418
- }
419
- });
420
- },
421
- { threshold: 0.5 }
422
- );
423
- observer.observe(element);
424
- observers.push(observer);
425
- }
426
- });
427
- return () => {
428
- 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);
429
837
  };
838
+ scrollContainer.addEventListener("scroll", handleScroll);
839
+ return () => scrollContainer.removeEventListener("scroll", handleScroll);
430
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
+ }, []);
431
854
  return /* @__PURE__ */ jsxs(
432
855
  Section,
433
856
  {
434
857
  ref: containerRef,
435
858
  background,
436
859
  spacing,
437
- className: cn(className),
860
+ className: cn("relative overflow-hidden", className),
438
861
  pattern,
439
862
  patternOpacity,
440
863
  containerMaxWidth,
864
+ containerClassName: "p-0",
441
865
  children: [
442
866
  /* @__PURE__ */ jsx(
443
867
  "div",
@@ -449,10 +873,7 @@ function CarouselFullscreenScrollFx({
449
873
  children: slides?.map((slide, index) => /* @__PURE__ */ jsx(
450
874
  "button",
451
875
  {
452
- onClick: () => {
453
- const element = document.getElementById(`fullscreen-${slide.id}`);
454
- element?.scrollIntoView({ behavior: "smooth" });
455
- },
876
+ onClick: () => scrollToSlide(index),
456
877
  className: cn(
457
878
  "h-3 w-3 rounded-full border-2 transition-all",
458
879
  activeIndex === index ? "scale-125 border-white bg-white" : "border-white/50 bg-transparent hover:border-white"
@@ -463,76 +884,115 @@ function CarouselFullscreenScrollFx({
463
884
  ))
464
885
  }
465
886
  ),
466
- slidesSlot ? slidesSlot : slides?.map((slide, index) => /* @__PURE__ */ jsxs(
887
+ /* @__PURE__ */ jsx(
467
888
  "div",
468
889
  {
469
- id: `fullscreen-${slide.id}`,
470
- className: cn(
471
- "relative flex h-screen w-full items-center justify-center overflow-hidden",
472
- slide.className
473
- ),
474
- children: [
475
- /* @__PURE__ */ jsxs("div", { className: "absolute inset-0", children: [
476
- /* @__PURE__ */ jsx(
477
- Img,
478
- {
479
- src: slide.image,
480
- alt: typeof slide.title === "string" ? slide.title : `Slide ${index + 1}`,
481
- className: cn(
482
- "h-full w-full object-cover",
483
- slide.imageClassName
484
- ),
485
- optixFlowConfig
486
- }
487
- ),
488
- /* @__PURE__ */ jsx(
489
- "div",
490
- {
491
- className: "absolute inset-0",
492
- style: {
493
- backgroundColor: slide.overlayColor || "rgba(0, 0, 0, 0.5)"
494
- }
495
- }
496
- )
497
- ] }),
498
- /* @__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(
499
923
  "div",
500
924
  {
925
+ id: `fullscreen-${slide.id}`,
501
926
  className: cn(
502
- "relative z-10 mx-auto max-w-4xl px-6 text-center text-white",
503
- contentClassName
927
+ "relative flex h-screen min-w-full snap-start items-center justify-center overflow-hidden",
928
+ slide.className
504
929
  ),
505
930
  children: [
506
- slide.subtitle && (typeof slide.subtitle === "string" ? /* @__PURE__ */ jsx(
507
- "p",
508
- {
509
- className: cn(
510
- "mb-4 text-sm font-medium uppercase tracking-widest text-white/70",
511
- subtitleClassName
512
- ),
513
- children: slide.subtitle
514
- }
515
- ) : /* @__PURE__ */ jsx("div", { className: subtitleClassName, children: slide.subtitle })),
516
- slide.title && (typeof slide.title === "string" ? /* @__PURE__ */ jsx(
517
- "h2",
518
- {
519
- className: cn(
520
- "mb-6 text-4xl font-bold tracking-tight md:text-5xl lg:text-6xl",
521
- titleClassName
522
- ),
523
- children: slide.title
524
- }
525
- ) : /* @__PURE__ */ jsx("div", { className: titleClassName, children: slide.title })),
526
- slide.description && (typeof slide.description === "string" ? /* @__PURE__ */ jsx(
527
- "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",
528
956
  {
529
957
  className: cn(
530
- "mx-auto max-w-2xl text-lg text-white/80 md:text-xl",
531
- descriptionClassName
958
+ "relative z-10 mx-auto max-w-4xl md:max-w-2xl px-6 text-center text-white text-shadow",
959
+ contentClassName
532
960
  ),
533
- children: slide.description
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
+ ]
534
994
  }
535
- ) : /* @__PURE__ */ jsx("div", { className: descriptionClassName, children: slide.description })),
995
+ ),
536
996
  index < (slides?.length ?? 0) - 1 && /* @__PURE__ */ jsx(
537
997
  "div",
538
998
  {
@@ -542,32 +1002,32 @@ function CarouselFullscreenScrollFx({
542
1002
  ),
543
1003
  children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2", children: [
544
1004
  /* @__PURE__ */ jsx("span", { className: "text-xs uppercase tracking-widest text-white/50", children: "Scroll" }),
545
- /* @__PURE__ */ jsx("div", { className: "h-12 w-px animate-pulse bg-gradient-to-b from-white/50 to-transparent" })
1005
+ /* @__PURE__ */ jsx("div", { className: "h-12 w-px animate-pulse bg-linear-to-b from-white/50 to-transparent" })
546
1006
  ] })
547
1007
  }
1008
+ ),
1009
+ /* @__PURE__ */ jsxs(
1010
+ "div",
1011
+ {
1012
+ className: cn(
1013
+ "absolute bottom-8 right-8 text-sm text-white/50",
1014
+ counterClassName
1015
+ ),
1016
+ children: [
1017
+ String(index + 1).padStart(2, "0"),
1018
+ " /",
1019
+ " ",
1020
+ String(slides?.length ?? 0).padStart(2, "0")
1021
+ ]
1022
+ }
548
1023
  )
549
1024
  ]
550
- }
551
- ),
552
- /* @__PURE__ */ jsxs(
553
- "div",
554
- {
555
- className: cn(
556
- "absolute bottom-8 right-8 text-sm text-white/50",
557
- counterClassName
558
- ),
559
- children: [
560
- String(index + 1).padStart(2, "0"),
561
- " /",
562
- " ",
563
- String(slides?.length ?? 0).padStart(2, "0")
564
- ]
565
- }
566
- )
567
- ]
568
- },
569
- slide.id
570
- ))
1025
+ },
1026
+ slide.id
1027
+ );
1028
+ })
1029
+ }
1030
+ )
571
1031
  ]
572
1032
  }
573
1033
  );