@opensite/ui 0.7.7 → 0.7.8

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,16 +1,540 @@
1
1
  "use client";
2
- import * as React3 from 'react';
3
- import React3__default from 'react';
2
+ import * as React6 from 'react';
3
+ import React6__default from 'react';
4
4
  import { AnimatePresence, motion } from 'framer-motion';
5
5
  import { clsx } from 'clsx';
6
6
  import { twMerge } from 'tailwind-merge';
7
- import { Img } from '@page-speed/img';
7
+ import { cva } from 'class-variance-authority';
8
8
  import { jsx, jsxs } from 'react/jsx-runtime';
9
+ import { Img } from '@page-speed/img';
9
10
 
10
11
  // components/blocks/carousel/carousel-progress-slider.tsx
11
12
  function cn(...inputs) {
12
13
  return twMerge(clsx(inputs));
13
14
  }
15
+ function normalizePhoneNumber(input) {
16
+ const trimmed = input.trim();
17
+ if (trimmed.toLowerCase().startsWith("tel:")) {
18
+ return trimmed;
19
+ }
20
+ const match = trimmed.match(/^[\s\+\-\(\)]*(\d[\d\s\-\(\)\.]*\d)[\s\-]*(x|ext\.?|extension)?[\s\-]*(\d+)?$/i);
21
+ if (match) {
22
+ const mainNumber = match[1].replace(/[\s\-\(\)\.]/g, "");
23
+ const extension = match[3];
24
+ const normalized = mainNumber.length >= 10 && !trimmed.startsWith("+") ? `+${mainNumber}` : mainNumber;
25
+ const withExtension = extension ? `${normalized};ext=${extension}` : normalized;
26
+ return `tel:${withExtension}`;
27
+ }
28
+ const cleaned = trimmed.replace(/[\s\-\(\)\.]/g, "");
29
+ return `tel:${cleaned}`;
30
+ }
31
+ function normalizeEmail(input) {
32
+ const trimmed = input.trim();
33
+ if (trimmed.toLowerCase().startsWith("mailto:")) {
34
+ return trimmed;
35
+ }
36
+ return `mailto:${trimmed}`;
37
+ }
38
+ function isEmail(input) {
39
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
40
+ return emailRegex.test(input.trim());
41
+ }
42
+ function isPhoneNumber(input) {
43
+ const trimmed = input.trim();
44
+ if (trimmed.toLowerCase().startsWith("tel:")) {
45
+ return true;
46
+ }
47
+ const phoneRegex = /^[\s\+\-\(\)]*\d[\d\s\-\(\)\.]*\d[\s\-]*(x|ext\.?|extension)?[\s\-]*\d*$/i;
48
+ return phoneRegex.test(trimmed);
49
+ }
50
+ function isInternalUrl(href) {
51
+ if (typeof window === "undefined") {
52
+ return href.startsWith("/") && !href.startsWith("//");
53
+ }
54
+ const trimmed = href.trim();
55
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
56
+ return true;
57
+ }
58
+ try {
59
+ const url = new URL(trimmed, window.location.href);
60
+ const currentOrigin = window.location.origin;
61
+ const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
62
+ return normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin);
63
+ } catch {
64
+ return false;
65
+ }
66
+ }
67
+ function toRelativePath(href) {
68
+ if (typeof window === "undefined") {
69
+ return href;
70
+ }
71
+ const trimmed = href.trim();
72
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
73
+ return trimmed;
74
+ }
75
+ try {
76
+ const url = new URL(trimmed, window.location.href);
77
+ const currentOrigin = window.location.origin;
78
+ const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
79
+ if (normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin)) {
80
+ return url.pathname + url.search + url.hash;
81
+ }
82
+ } catch {
83
+ }
84
+ return trimmed;
85
+ }
86
+ function useNavigation({
87
+ href,
88
+ onClick
89
+ } = {}) {
90
+ const linkType = React6.useMemo(() => {
91
+ if (!href || href.trim() === "") {
92
+ return onClick ? "none" : "none";
93
+ }
94
+ const trimmed = href.trim();
95
+ if (trimmed.toLowerCase().startsWith("mailto:") || isEmail(trimmed)) {
96
+ return "mailto";
97
+ }
98
+ if (trimmed.toLowerCase().startsWith("tel:") || isPhoneNumber(trimmed)) {
99
+ return "tel";
100
+ }
101
+ if (isInternalUrl(trimmed)) {
102
+ return "internal";
103
+ }
104
+ try {
105
+ new URL(trimmed, typeof window !== "undefined" ? window.location.href : "http://localhost");
106
+ return "external";
107
+ } catch {
108
+ return "internal";
109
+ }
110
+ }, [href, onClick]);
111
+ const normalizedHref = React6.useMemo(() => {
112
+ if (!href || href.trim() === "") {
113
+ return void 0;
114
+ }
115
+ const trimmed = href.trim();
116
+ switch (linkType) {
117
+ case "tel":
118
+ return normalizePhoneNumber(trimmed);
119
+ case "mailto":
120
+ return normalizeEmail(trimmed);
121
+ case "internal":
122
+ return toRelativePath(trimmed);
123
+ case "external":
124
+ return trimmed;
125
+ default:
126
+ return trimmed;
127
+ }
128
+ }, [href, linkType]);
129
+ const target = React6.useMemo(() => {
130
+ switch (linkType) {
131
+ case "external":
132
+ return "_blank";
133
+ case "internal":
134
+ return "_self";
135
+ case "mailto":
136
+ case "tel":
137
+ return void 0;
138
+ default:
139
+ return void 0;
140
+ }
141
+ }, [linkType]);
142
+ const rel = React6.useMemo(() => {
143
+ if (linkType === "external") {
144
+ return "noopener noreferrer";
145
+ }
146
+ return void 0;
147
+ }, [linkType]);
148
+ const isExternal = linkType === "external";
149
+ const isInternal = linkType === "internal";
150
+ const shouldUseRouter = isInternal && typeof normalizedHref === "string" && normalizedHref.startsWith("/");
151
+ const handleClick = React6.useCallback(
152
+ (event) => {
153
+ if (onClick) {
154
+ try {
155
+ onClick(event);
156
+ } catch (error) {
157
+ console.error("Error in user onClick handler:", error);
158
+ }
159
+ }
160
+ if (event.defaultPrevented) {
161
+ return;
162
+ }
163
+ if (shouldUseRouter && normalizedHref && event.button === 0 && // left-click only
164
+ !event.metaKey && !event.altKey && !event.ctrlKey && !event.shiftKey) {
165
+ if (typeof window !== "undefined") {
166
+ const handler = window.__opensiteNavigationHandler;
167
+ if (typeof handler === "function") {
168
+ try {
169
+ const handled = handler(normalizedHref, event.nativeEvent || event);
170
+ if (handled !== false) {
171
+ event.preventDefault();
172
+ }
173
+ } catch (error) {
174
+ console.error("Error in navigation handler:", error);
175
+ }
176
+ }
177
+ }
178
+ }
179
+ },
180
+ [onClick, shouldUseRouter, normalizedHref]
181
+ );
182
+ return {
183
+ linkType,
184
+ normalizedHref,
185
+ target,
186
+ rel,
187
+ isExternal,
188
+ isInternal,
189
+ shouldUseRouter,
190
+ handleClick
191
+ };
192
+ }
193
+ var baseStyles = [
194
+ // Layout
195
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap shrink-0",
196
+ // Typography - using CSS variables with sensible defaults
197
+ "font-[var(--button-font-family,inherit)]",
198
+ "font-[var(--button-font-weight,500)]",
199
+ "tracking-[var(--button-letter-spacing,0)]",
200
+ "leading-[var(--button-line-height,1.25)]",
201
+ "[text-transform:var(--button-text-transform,none)]",
202
+ "text-sm",
203
+ // Border radius
204
+ "rounded-[var(--button-radius,var(--radius,0.375rem))]",
205
+ // Smooth transition - using [transition:...] to set full shorthand property (not just transition-property)
206
+ "[transition:var(--button-transition,all_250ms_cubic-bezier(0.4,0,0.2,1))]",
207
+ // Box shadow (master level) - using [box-shadow:...] for complex multi-value shadows
208
+ "[box-shadow:var(--button-shadow,none)]",
209
+ "hover:[box-shadow:var(--button-shadow-hover,var(--button-shadow,none))]",
210
+ // Disabled state
211
+ "disabled:pointer-events-none disabled:opacity-50",
212
+ // SVG handling
213
+ "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0",
214
+ // Focus styles
215
+ "outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
216
+ // Invalid state
217
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive"
218
+ ].join(" ");
219
+ var buttonVariants = cva(baseStyles, {
220
+ variants: {
221
+ variant: {
222
+ // Default (Primary) variant - full customization
223
+ default: [
224
+ "bg-[var(--button-default-bg,hsl(var(--primary)))]",
225
+ "text-[var(--button-default-fg,hsl(var(--primary-foreground)))]",
226
+ "border-[length:var(--button-default-border-width,0px)]",
227
+ "border-[color:var(--button-default-border,transparent)]",
228
+ "[box-shadow:var(--button-default-shadow,var(--button-shadow,none))]",
229
+ "hover:bg-[var(--button-default-hover-bg,hsl(var(--primary)/0.9))]",
230
+ "hover:text-[var(--button-default-hover-fg,var(--button-default-fg,hsl(var(--primary-foreground))))]",
231
+ "hover:border-[color:var(--button-default-hover-border,var(--button-default-border,transparent))]",
232
+ "hover:[box-shadow:var(--button-default-shadow-hover,var(--button-shadow-hover,var(--button-default-shadow,var(--button-shadow,none))))]"
233
+ ].join(" "),
234
+ // Destructive variant - full customization
235
+ destructive: [
236
+ "bg-[var(--button-destructive-bg,hsl(var(--destructive)))]",
237
+ "text-[var(--button-destructive-fg,white)]",
238
+ "border-[length:var(--button-destructive-border-width,0px)]",
239
+ "border-[color:var(--button-destructive-border,transparent)]",
240
+ "[box-shadow:var(--button-destructive-shadow,var(--button-shadow,none))]",
241
+ "hover:bg-[var(--button-destructive-hover-bg,hsl(var(--destructive)/0.9))]",
242
+ "hover:text-[var(--button-destructive-hover-fg,var(--button-destructive-fg,white))]",
243
+ "hover:border-[color:var(--button-destructive-hover-border,var(--button-destructive-border,transparent))]",
244
+ "hover:[box-shadow:var(--button-destructive-shadow-hover,var(--button-shadow-hover,var(--button-destructive-shadow,var(--button-shadow,none))))]",
245
+ "focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",
246
+ "dark:bg-destructive/60"
247
+ ].join(" "),
248
+ // Outline variant - full customization with proper border handling
249
+ outline: [
250
+ "bg-[var(--button-outline-bg,hsl(var(--background)))]",
251
+ "text-[var(--button-outline-fg,inherit)]",
252
+ "border-[length:var(--button-outline-border-width,1px)]",
253
+ "border-[color:var(--button-outline-border,hsl(var(--border)))]",
254
+ "[box-shadow:var(--button-outline-shadow,var(--button-shadow,0_1px_2px_0_rgb(0_0_0/0.05)))]",
255
+ "hover:bg-[var(--button-outline-hover-bg,hsl(var(--accent)))]",
256
+ "hover:text-[var(--button-outline-hover-fg,hsl(var(--accent-foreground)))]",
257
+ "hover:border-[color:var(--button-outline-hover-border,var(--button-outline-border,hsl(var(--border))))]",
258
+ "hover:[box-shadow:var(--button-outline-shadow-hover,var(--button-shadow-hover,var(--button-outline-shadow,var(--button-shadow,none))))]",
259
+ "dark:bg-input/30 dark:border-input dark:hover:bg-input/50"
260
+ ].join(" "),
261
+ // Secondary variant - full customization
262
+ secondary: [
263
+ "bg-[var(--button-secondary-bg,hsl(var(--secondary)))]",
264
+ "text-[var(--button-secondary-fg,hsl(var(--secondary-foreground)))]",
265
+ "border-[length:var(--button-secondary-border-width,0px)]",
266
+ "border-[color:var(--button-secondary-border,transparent)]",
267
+ "[box-shadow:var(--button-secondary-shadow,var(--button-shadow,none))]",
268
+ "hover:bg-[var(--button-secondary-hover-bg,hsl(var(--secondary)/0.8))]",
269
+ "hover:text-[var(--button-secondary-hover-fg,var(--button-secondary-fg,hsl(var(--secondary-foreground))))]",
270
+ "hover:border-[color:var(--button-secondary-hover-border,var(--button-secondary-border,transparent))]",
271
+ "hover:[box-shadow:var(--button-secondary-shadow-hover,var(--button-shadow-hover,var(--button-secondary-shadow,var(--button-shadow,none))))]"
272
+ ].join(" "),
273
+ // Ghost variant - full customization
274
+ ghost: [
275
+ "bg-[var(--button-ghost-bg,transparent)]",
276
+ "text-[var(--button-ghost-fg,inherit)]",
277
+ "border-[length:var(--button-ghost-border-width,0px)]",
278
+ "border-[color:var(--button-ghost-border,transparent)]",
279
+ "[box-shadow:var(--button-ghost-shadow,var(--button-shadow,none))]",
280
+ "hover:bg-[var(--button-ghost-hover-bg,hsl(var(--accent)))]",
281
+ "hover:text-[var(--button-ghost-hover-fg,hsl(var(--accent-foreground)))]",
282
+ "hover:border-[color:var(--button-ghost-hover-border,var(--button-ghost-border,transparent))]",
283
+ "hover:[box-shadow:var(--button-ghost-shadow-hover,var(--button-shadow-hover,var(--button-ghost-shadow,var(--button-shadow,none))))]",
284
+ "dark:hover:bg-accent/50"
285
+ ].join(" "),
286
+ // Link variant - full customization
287
+ link: [
288
+ "bg-[var(--button-link-bg,transparent)]",
289
+ "text-[var(--button-link-fg,hsl(var(--primary)))]",
290
+ "border-[length:var(--button-link-border-width,0px)]",
291
+ "border-[color:var(--button-link-border,transparent)]",
292
+ "[box-shadow:var(--button-link-shadow,none)]",
293
+ "hover:bg-[var(--button-link-hover-bg,transparent)]",
294
+ "hover:text-[var(--button-link-hover-fg,var(--button-link-fg,hsl(var(--primary))))]",
295
+ "hover:[box-shadow:var(--button-link-shadow-hover,none)]",
296
+ "underline-offset-4 hover:underline"
297
+ ].join(" ")
298
+ },
299
+ size: {
300
+ default: [
301
+ "h-[var(--button-height-md,2.25rem)]",
302
+ "px-[var(--button-padding-x-md,1rem)]",
303
+ "py-[var(--button-padding-y-md,0.5rem)]",
304
+ "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
305
+ ].join(" "),
306
+ sm: [
307
+ "h-[var(--button-height-sm,2rem)]",
308
+ "px-[var(--button-padding-x-sm,0.75rem)]",
309
+ "py-[var(--button-padding-y-sm,0.25rem)]",
310
+ "gap-1.5",
311
+ "has-[>svg]:px-[calc(var(--button-padding-x-sm,0.75rem)*0.83)]"
312
+ ].join(" "),
313
+ md: [
314
+ "h-[var(--button-height-md,2.25rem)]",
315
+ "px-[var(--button-padding-x-md,1rem)]",
316
+ "py-[var(--button-padding-y-md,0.5rem)]",
317
+ "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
318
+ ].join(" "),
319
+ lg: [
320
+ "h-[var(--button-height-lg,2.5rem)]",
321
+ "px-[var(--button-padding-x-lg,1.5rem)]",
322
+ "py-[var(--button-padding-y-lg,0.5rem)]",
323
+ "has-[>svg]:px-[calc(var(--button-padding-x-lg,1.5rem)*0.67)]"
324
+ ].join(" "),
325
+ icon: "size-[var(--button-height-md,2.25rem)]",
326
+ "icon-sm": "size-[var(--button-height-sm,2rem)]",
327
+ "icon-lg": "size-[var(--button-height-lg,2.5rem)]"
328
+ }
329
+ },
330
+ defaultVariants: {
331
+ variant: "default",
332
+ size: "default"
333
+ }
334
+ });
335
+ var Pressable = React6.forwardRef(
336
+ ({
337
+ children,
338
+ className,
339
+ href,
340
+ onClick,
341
+ variant,
342
+ size,
343
+ asButton = false,
344
+ fallbackComponentType = "span",
345
+ componentType,
346
+ "aria-label": ariaLabel,
347
+ "aria-describedby": ariaDescribedby,
348
+ id,
349
+ ...props
350
+ }, ref) => {
351
+ const navigation = useNavigation({ href, onClick });
352
+ const {
353
+ normalizedHref,
354
+ target,
355
+ rel,
356
+ linkType,
357
+ isInternal,
358
+ handleClick
359
+ } = navigation;
360
+ const shouldRenderLink = normalizedHref && linkType !== "none";
361
+ const shouldRenderButton = !shouldRenderLink && onClick;
362
+ const effectiveComponentType = componentType || (shouldRenderLink ? "a" : shouldRenderButton ? "button" : fallbackComponentType);
363
+ const finalComponentType = isInternal && shouldRenderLink ? "a" : effectiveComponentType;
364
+ const shouldApplyButtonStyles = asButton || variant || size;
365
+ const combinedClassName = cn(
366
+ shouldApplyButtonStyles && buttonVariants({ variant, size }),
367
+ className
368
+ );
369
+ const dataProps = Object.fromEntries(
370
+ Object.entries(props).filter(([key]) => key.startsWith("data-"))
371
+ );
372
+ const buttonDataAttributes = shouldApplyButtonStyles ? {
373
+ "data-slot": "button",
374
+ "data-variant": variant ?? "default",
375
+ "data-size": size ?? "default"
376
+ } : {};
377
+ const commonProps = {
378
+ className: combinedClassName,
379
+ onClick: handleClick,
380
+ "aria-label": ariaLabel,
381
+ "aria-describedby": ariaDescribedby,
382
+ id,
383
+ ...dataProps,
384
+ ...buttonDataAttributes
385
+ };
386
+ if (finalComponentType === "a" && shouldRenderLink) {
387
+ return /* @__PURE__ */ jsx(
388
+ "a",
389
+ {
390
+ ref,
391
+ href: normalizedHref,
392
+ target,
393
+ rel,
394
+ ...commonProps,
395
+ ...props,
396
+ children
397
+ }
398
+ );
399
+ }
400
+ if (finalComponentType === "button") {
401
+ return /* @__PURE__ */ jsx(
402
+ "button",
403
+ {
404
+ ref,
405
+ type: props.type || "button",
406
+ ...commonProps,
407
+ ...props,
408
+ children
409
+ }
410
+ );
411
+ }
412
+ if (finalComponentType === "div") {
413
+ return /* @__PURE__ */ jsx(
414
+ "div",
415
+ {
416
+ ref,
417
+ ...commonProps,
418
+ children
419
+ }
420
+ );
421
+ }
422
+ return /* @__PURE__ */ jsx(
423
+ "span",
424
+ {
425
+ ref,
426
+ ...commonProps,
427
+ children
428
+ }
429
+ );
430
+ }
431
+ );
432
+ Pressable.displayName = "Pressable";
433
+ var svgCache = /* @__PURE__ */ new Map();
434
+ function DynamicIcon({
435
+ name,
436
+ size = 28,
437
+ color,
438
+ className,
439
+ alt
440
+ }) {
441
+ const [svgContent, setSvgContent] = React6.useState(null);
442
+ const [isLoading, setIsLoading] = React6.useState(true);
443
+ const [error, setError] = React6.useState(null);
444
+ const { url, iconName } = React6.useMemo(() => {
445
+ const separator = name.includes("/") ? "/" : ":";
446
+ const [prefix, iconName2] = name.split(separator);
447
+ const baseUrl = `https://icons.opensite.ai/api/icon/${prefix}/${iconName2}?format=svg&width=${size}&height=${size}`;
448
+ return {
449
+ url: baseUrl,
450
+ iconName: iconName2
451
+ };
452
+ }, [name, size]);
453
+ React6.useEffect(() => {
454
+ let isMounted = true;
455
+ const fetchSvg = async () => {
456
+ const cached = svgCache.get(url);
457
+ if (cached) {
458
+ if (isMounted) {
459
+ setSvgContent(cached);
460
+ setIsLoading(false);
461
+ }
462
+ return;
463
+ }
464
+ try {
465
+ setIsLoading(true);
466
+ setError(null);
467
+ const response = await fetch(url);
468
+ if (!response.ok) {
469
+ throw new Error(`Failed to fetch icon: ${response.status}`);
470
+ }
471
+ let svg = await response.text();
472
+ svg = processSvgForCurrentColor(svg);
473
+ svgCache.set(url, svg);
474
+ if (isMounted) {
475
+ setSvgContent(svg);
476
+ setIsLoading(false);
477
+ }
478
+ } catch (err) {
479
+ if (isMounted) {
480
+ setError(err instanceof Error ? err.message : "Failed to load icon");
481
+ setIsLoading(false);
482
+ }
483
+ }
484
+ };
485
+ fetchSvg();
486
+ return () => {
487
+ isMounted = false;
488
+ };
489
+ }, [url]);
490
+ if (isLoading) {
491
+ return /* @__PURE__ */ jsx(
492
+ "span",
493
+ {
494
+ className: cn("inline-block", className),
495
+ style: { width: size, height: size },
496
+ "aria-hidden": "true"
497
+ }
498
+ );
499
+ }
500
+ if (error || !svgContent) {
501
+ return /* @__PURE__ */ jsx(
502
+ "span",
503
+ {
504
+ className: cn("inline-block", className),
505
+ style: { width: size, height: size },
506
+ role: "img",
507
+ "aria-label": alt || iconName
508
+ }
509
+ );
510
+ }
511
+ return /* @__PURE__ */ jsx(
512
+ "span",
513
+ {
514
+ className: cn("inline-flex items-center justify-center", className),
515
+ style: {
516
+ width: size,
517
+ height: size,
518
+ color: color || "inherit"
519
+ },
520
+ role: "img",
521
+ "aria-label": alt || iconName,
522
+ dangerouslySetInnerHTML: { __html: svgContent }
523
+ }
524
+ );
525
+ }
526
+ function processSvgForCurrentColor(svg) {
527
+ let processed = svg;
528
+ processed = processed.replace(
529
+ /stroke=["'](#000000|#000|black)["']/gi,
530
+ 'stroke="currentColor"'
531
+ );
532
+ processed = processed.replace(
533
+ /fill=["'](#000000|#000|black)["']/gi,
534
+ 'fill="currentColor"'
535
+ );
536
+ return processed;
537
+ }
14
538
  var maxWidthStyles = {
15
539
  sm: "max-w-screen-sm",
16
540
  md: "max-w-screen-md",
@@ -20,7 +544,7 @@ var maxWidthStyles = {
20
544
  "4xl": "max-w-[1536px]",
21
545
  full: "max-w-full"
22
546
  };
23
- var Container = React3__default.forwardRef(
547
+ var Container = React6__default.forwardRef(
24
548
  ({ children, maxWidth = "xl", className, as = "div", ...props }, ref) => {
25
549
  const Component = as;
26
550
  return /* @__PURE__ */ jsx(
@@ -325,7 +849,7 @@ var spacingStyles = {
325
849
  };
326
850
  var predefinedSpacings = ["none", "sm", "md", "lg", "xl"];
327
851
  var isPredefinedSpacing = (spacing) => predefinedSpacings.includes(spacing);
328
- var Section = React3__default.forwardRef(
852
+ var Section = React6__default.forwardRef(
329
853
  ({
330
854
  id,
331
855
  title,
@@ -386,9 +910,9 @@ var Section = React3__default.forwardRef(
386
910
  }
387
911
  );
388
912
  Section.displayName = "Section";
389
- var ProgressSliderContext = React3.createContext(void 0);
913
+ var ProgressSliderContext = React6.createContext(void 0);
390
914
  function useProgressSliderContext() {
391
- const context = React3.useContext(ProgressSliderContext);
915
+ const context = React6.useContext(ProgressSliderContext);
392
916
  if (!context) {
393
917
  throw new Error(
394
918
  "useProgressSliderContext must be used within a ProgressSlider"
@@ -417,13 +941,13 @@ function SliderBtn({
417
941
  /* @__PURE__ */ jsx(
418
942
  "div",
419
943
  {
420
- className: "absolute inset-0 -z-10 max-h-full max-w-full overflow-hidden",
944
+ className: "absolute inset-0 -z-10 max-h-full max-w-full overflow-hidden rounded-lg",
421
945
  role: "progressbar",
422
946
  "aria-valuenow": active === value ? progress : 0,
423
947
  children: /* @__PURE__ */ jsx(
424
948
  "span",
425
949
  {
426
- className: cn("absolute left-0", progressBarClass),
950
+ className: cn("absolute left-0 rounded-b-lg", progressBarClass),
427
951
  style: {
428
952
  [vertical ? "height" : "width"]: active === value ? `${progress}%` : "0%"
429
953
  }
@@ -454,7 +978,7 @@ function CarouselProgressSlider({
454
978
  subheading,
455
979
  slides,
456
980
  slidesSlot,
457
- duration = 5e3,
981
+ duration = 8e3,
458
982
  fastDuration = 400,
459
983
  vertical = false,
460
984
  className,
@@ -470,25 +994,38 @@ function CarouselProgressSlider({
470
994
  pattern,
471
995
  patternOpacity
472
996
  }) {
473
- const [active, setActive] = React3.useState(slides?.[0]?.id ?? "");
474
- const [progress, setProgress] = React3.useState(0);
475
- const [isFastForward, setIsFastForward] = React3.useState(false);
476
- const frame = React3.useRef(0);
477
- const firstFrameTime = React3.useRef(performance.now());
478
- const targetValue = React3.useRef(null);
479
- const sliderValues = React3.useMemo(
997
+ const [active, setActive] = React6.useState(slides?.[0]?.id ?? "");
998
+ const [progress, setProgress] = React6.useState(0);
999
+ const [isFastForward, setIsFastForward] = React6.useState(false);
1000
+ const [isPaused, setIsPaused] = React6.useState(false);
1001
+ const frame = React6.useRef(0);
1002
+ const firstFrameTime = React6.useRef(performance.now());
1003
+ const targetValue = React6.useRef(null);
1004
+ const pausedProgress = React6.useRef(0);
1005
+ const sliderValues = React6.useMemo(
480
1006
  () => slides?.map((slide) => slide.id),
481
1007
  [slides]
482
1008
  );
483
- React3.useEffect(() => {
484
- if ((sliderValues?.length ?? 0) > 0) {
1009
+ React6.useEffect(() => {
1010
+ if ((sliderValues?.length ?? 0) > 0 && !isPaused) {
485
1011
  firstFrameTime.current = performance.now();
1012
+ if (pausedProgress.current > 0) {
1013
+ setProgress(pausedProgress.current);
1014
+ pausedProgress.current = 0;
1015
+ }
486
1016
  frame.current = requestAnimationFrame(animate);
487
1017
  }
488
1018
  return () => {
489
1019
  cancelAnimationFrame(frame.current);
490
1020
  };
491
- }, [sliderValues, active, isFastForward]);
1021
+ }, [sliderValues, active, isFastForward, isPaused]);
1022
+ const togglePause = () => {
1023
+ if (!isPaused) {
1024
+ pausedProgress.current = progress;
1025
+ cancelAnimationFrame(frame.current);
1026
+ }
1027
+ setIsPaused(!isPaused);
1028
+ };
492
1029
  const animate = (now) => {
493
1030
  const currentDuration = isFastForward ? fastDuration : duration;
494
1031
  const elapsedTime = now - firstFrameTime.current;
@@ -540,26 +1077,40 @@ function CarouselProgressSlider({
540
1077
  pattern,
541
1078
  patternOpacity,
542
1079
  children: /* @__PURE__ */ jsx("div", { className: cn("relative", containerClassName), children: /* @__PURE__ */ jsxs("div", { className: cn("grid gap-8 lg:grid-cols-2", contentClassName), children: [
543
- /* @__PURE__ */ jsx("div", { className: cn("relative min-h-[300px]", imageClassName), children: slidesSlot ? slidesSlot : slides?.map((slide) => /* @__PURE__ */ jsx(
544
- SliderWrapper,
545
- {
546
- value: slide.id,
547
- className: cn("absolute inset-0", slide.className),
548
- children: /* @__PURE__ */ jsx("div", { className: "aspect-video overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx(
549
- Img,
550
- {
551
- src: slide.image,
552
- alt: typeof slide.title === "string" ? slide.title : `Slide ${slide.id}`,
553
- className: cn(
554
- "h-full w-full object-cover",
555
- slide.imageClassName
556
- ),
557
- optixFlowConfig
558
- }
559
- ) })
560
- },
561
- slide.id
562
- )) }),
1080
+ /* @__PURE__ */ jsxs("div", { className: cn("relative", imageClassName), children: [
1081
+ slidesSlot ? slidesSlot : slides?.map((slide) => /* @__PURE__ */ jsx(
1082
+ SliderWrapper,
1083
+ {
1084
+ value: slide.id,
1085
+ className: cn("", slide.className),
1086
+ children: /* @__PURE__ */ jsx("div", { className: "aspect-video overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx(
1087
+ Img,
1088
+ {
1089
+ src: slide.image,
1090
+ alt: typeof slide.title === "string" ? slide.title : `Slide ${slide.id}`,
1091
+ className: cn(
1092
+ "h-full w-full object-cover",
1093
+ slide.imageClassName
1094
+ ),
1095
+ optixFlowConfig
1096
+ }
1097
+ ) })
1098
+ },
1099
+ slide.id
1100
+ )),
1101
+ /* @__PURE__ */ jsx("div", { className: "mt-4 flex justify-center lg:justify-start", children: /* @__PURE__ */ jsx(
1102
+ Pressable,
1103
+ {
1104
+ onClick: togglePause,
1105
+ asButton: true,
1106
+ variant: "outline",
1107
+ size: "icon",
1108
+ className: "flex h-10 w-10 items-center justify-center rounded-full",
1109
+ "aria-label": isPaused ? "Play" : "Pause",
1110
+ children: /* @__PURE__ */ jsx(DynamicIcon, { name: isPaused ? "lucide/play" : "lucide/pause", size: 18 })
1111
+ }
1112
+ ) })
1113
+ ] }),
563
1114
  /* @__PURE__ */ jsx(
564
1115
  "div",
565
1116
  {