@opensite/ui 0.7.9 → 0.8.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.
@@ -0,0 +1,601 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var clsx = require('clsx');
5
+ var tailwindMerge = require('tailwind-merge');
6
+ var React = require('react');
7
+ var classVarianceAuthority = require('class-variance-authority');
8
+ var jsxRuntime = require('react/jsx-runtime');
9
+
10
+ function _interopNamespace(e) {
11
+ if (e && e.__esModule) return e;
12
+ var n = Object.create(null);
13
+ if (e) {
14
+ Object.keys(e).forEach(function (k) {
15
+ if (k !== 'default') {
16
+ var d = Object.getOwnPropertyDescriptor(e, k);
17
+ Object.defineProperty(n, k, d.get ? d : {
18
+ enumerable: true,
19
+ get: function () { return e[k]; }
20
+ });
21
+ }
22
+ });
23
+ }
24
+ n.default = e;
25
+ return Object.freeze(n);
26
+ }
27
+
28
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
29
+
30
+ function cn(...inputs) {
31
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
32
+ }
33
+ function normalizePhoneNumber(input) {
34
+ const trimmed = input.trim();
35
+ if (trimmed.toLowerCase().startsWith("tel:")) {
36
+ return trimmed;
37
+ }
38
+ const match = trimmed.match(/^[\s\+\-\(\)]*(\d[\d\s\-\(\)\.]*\d)[\s\-]*(x|ext\.?|extension)?[\s\-]*(\d+)?$/i);
39
+ if (match) {
40
+ const mainNumber = match[1].replace(/[\s\-\(\)\.]/g, "");
41
+ const extension = match[3];
42
+ const normalized = mainNumber.length >= 10 && !trimmed.startsWith("+") ? `+${mainNumber}` : mainNumber;
43
+ const withExtension = extension ? `${normalized};ext=${extension}` : normalized;
44
+ return `tel:${withExtension}`;
45
+ }
46
+ const cleaned = trimmed.replace(/[\s\-\(\)\.]/g, "");
47
+ return `tel:${cleaned}`;
48
+ }
49
+ function normalizeEmail(input) {
50
+ const trimmed = input.trim();
51
+ if (trimmed.toLowerCase().startsWith("mailto:")) {
52
+ return trimmed;
53
+ }
54
+ return `mailto:${trimmed}`;
55
+ }
56
+ function isEmail(input) {
57
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
58
+ return emailRegex.test(input.trim());
59
+ }
60
+ function isPhoneNumber(input) {
61
+ const trimmed = input.trim();
62
+ if (trimmed.toLowerCase().startsWith("tel:")) {
63
+ return true;
64
+ }
65
+ const phoneRegex = /^[\s\+\-\(\)]*\d[\d\s\-\(\)\.]*\d[\s\-]*(x|ext\.?|extension)?[\s\-]*\d*$/i;
66
+ return phoneRegex.test(trimmed);
67
+ }
68
+ function isInternalUrl(href) {
69
+ if (typeof window === "undefined") {
70
+ return href.startsWith("/") && !href.startsWith("//");
71
+ }
72
+ const trimmed = href.trim();
73
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
74
+ return true;
75
+ }
76
+ try {
77
+ const url = new URL(trimmed, window.location.href);
78
+ const currentOrigin = window.location.origin;
79
+ const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
80
+ return normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin);
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+ function toRelativePath(href) {
86
+ if (typeof window === "undefined") {
87
+ return href;
88
+ }
89
+ const trimmed = href.trim();
90
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
91
+ return trimmed;
92
+ }
93
+ try {
94
+ const url = new URL(trimmed, window.location.href);
95
+ const currentOrigin = window.location.origin;
96
+ const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
97
+ if (normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin)) {
98
+ return url.pathname + url.search + url.hash;
99
+ }
100
+ } catch {
101
+ }
102
+ return trimmed;
103
+ }
104
+ function useNavigation({
105
+ href,
106
+ onClick
107
+ } = {}) {
108
+ const linkType = React__namespace.useMemo(() => {
109
+ if (!href || href.trim() === "") {
110
+ return onClick ? "none" : "none";
111
+ }
112
+ const trimmed = href.trim();
113
+ if (trimmed.toLowerCase().startsWith("mailto:") || isEmail(trimmed)) {
114
+ return "mailto";
115
+ }
116
+ if (trimmed.toLowerCase().startsWith("tel:") || isPhoneNumber(trimmed)) {
117
+ return "tel";
118
+ }
119
+ if (isInternalUrl(trimmed)) {
120
+ return "internal";
121
+ }
122
+ try {
123
+ new URL(trimmed, typeof window !== "undefined" ? window.location.href : "http://localhost");
124
+ return "external";
125
+ } catch {
126
+ return "internal";
127
+ }
128
+ }, [href, onClick]);
129
+ const normalizedHref = React__namespace.useMemo(() => {
130
+ if (!href || href.trim() === "") {
131
+ return void 0;
132
+ }
133
+ const trimmed = href.trim();
134
+ switch (linkType) {
135
+ case "tel":
136
+ return normalizePhoneNumber(trimmed);
137
+ case "mailto":
138
+ return normalizeEmail(trimmed);
139
+ case "internal":
140
+ return toRelativePath(trimmed);
141
+ case "external":
142
+ return trimmed;
143
+ default:
144
+ return trimmed;
145
+ }
146
+ }, [href, linkType]);
147
+ const target = React__namespace.useMemo(() => {
148
+ switch (linkType) {
149
+ case "external":
150
+ return "_blank";
151
+ case "internal":
152
+ return "_self";
153
+ case "mailto":
154
+ case "tel":
155
+ return void 0;
156
+ default:
157
+ return void 0;
158
+ }
159
+ }, [linkType]);
160
+ const rel = React__namespace.useMemo(() => {
161
+ if (linkType === "external") {
162
+ return "noopener noreferrer";
163
+ }
164
+ return void 0;
165
+ }, [linkType]);
166
+ const isExternal = linkType === "external";
167
+ const isInternal = linkType === "internal";
168
+ const shouldUseRouter = isInternal && typeof normalizedHref === "string" && normalizedHref.startsWith("/");
169
+ const handleClick = React__namespace.useCallback(
170
+ (event) => {
171
+ if (onClick) {
172
+ try {
173
+ onClick(event);
174
+ } catch (error) {
175
+ console.error("Error in user onClick handler:", error);
176
+ }
177
+ }
178
+ if (event.defaultPrevented) {
179
+ return;
180
+ }
181
+ if (shouldUseRouter && normalizedHref && event.button === 0 && // left-click only
182
+ !event.metaKey && !event.altKey && !event.ctrlKey && !event.shiftKey) {
183
+ if (typeof window !== "undefined") {
184
+ const handler = window.__opensiteNavigationHandler;
185
+ if (typeof handler === "function") {
186
+ try {
187
+ const handled = handler(normalizedHref, event.nativeEvent || event);
188
+ if (handled !== false) {
189
+ event.preventDefault();
190
+ }
191
+ } catch (error) {
192
+ console.error("Error in navigation handler:", error);
193
+ }
194
+ }
195
+ }
196
+ }
197
+ },
198
+ [onClick, shouldUseRouter, normalizedHref]
199
+ );
200
+ return {
201
+ linkType,
202
+ normalizedHref,
203
+ target,
204
+ rel,
205
+ isExternal,
206
+ isInternal,
207
+ shouldUseRouter,
208
+ handleClick
209
+ };
210
+ }
211
+ var baseStyles = [
212
+ // Layout
213
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap shrink-0",
214
+ // Typography - using CSS variables with sensible defaults
215
+ "font-[var(--button-font-family,inherit)]",
216
+ "font-[var(--button-font-weight,500)]",
217
+ "tracking-[var(--button-letter-spacing,0)]",
218
+ "leading-[var(--button-line-height,1.25)]",
219
+ "[text-transform:var(--button-text-transform,none)]",
220
+ "text-sm",
221
+ // Border radius
222
+ "rounded-[var(--button-radius,var(--radius,0.375rem))]",
223
+ // Smooth transition - using [transition:...] to set full shorthand property (not just transition-property)
224
+ "[transition:var(--button-transition,all_250ms_cubic-bezier(0.4,0,0.2,1))]",
225
+ // Box shadow (master level) - using [box-shadow:...] for complex multi-value shadows
226
+ "[box-shadow:var(--button-shadow,none)]",
227
+ "hover:[box-shadow:var(--button-shadow-hover,var(--button-shadow,none))]",
228
+ // Disabled state
229
+ "disabled:pointer-events-none disabled:opacity-50",
230
+ // SVG handling
231
+ "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0",
232
+ // Focus styles
233
+ "outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
234
+ // Invalid state
235
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive"
236
+ ].join(" ");
237
+ var buttonVariants = classVarianceAuthority.cva(baseStyles, {
238
+ variants: {
239
+ variant: {
240
+ // Default (Primary) variant - full customization
241
+ default: [
242
+ "bg-[var(--button-default-bg,hsl(var(--primary)))]",
243
+ "text-[var(--button-default-fg,hsl(var(--primary-foreground)))]",
244
+ "border-[length:var(--button-default-border-width,0px)]",
245
+ "border-[color:var(--button-default-border,transparent)]",
246
+ "[box-shadow:var(--button-default-shadow,var(--button-shadow,none))]",
247
+ "hover:bg-[var(--button-default-hover-bg,hsl(var(--primary)/0.9))]",
248
+ "hover:text-[var(--button-default-hover-fg,var(--button-default-fg,hsl(var(--primary-foreground))))]",
249
+ "hover:border-[color:var(--button-default-hover-border,var(--button-default-border,transparent))]",
250
+ "hover:[box-shadow:var(--button-default-shadow-hover,var(--button-shadow-hover,var(--button-default-shadow,var(--button-shadow,none))))]"
251
+ ].join(" "),
252
+ // Destructive variant - full customization
253
+ destructive: [
254
+ "bg-[var(--button-destructive-bg,hsl(var(--destructive)))]",
255
+ "text-[var(--button-destructive-fg,white)]",
256
+ "border-[length:var(--button-destructive-border-width,0px)]",
257
+ "border-[color:var(--button-destructive-border,transparent)]",
258
+ "[box-shadow:var(--button-destructive-shadow,var(--button-shadow,none))]",
259
+ "hover:bg-[var(--button-destructive-hover-bg,hsl(var(--destructive)/0.9))]",
260
+ "hover:text-[var(--button-destructive-hover-fg,var(--button-destructive-fg,white))]",
261
+ "hover:border-[color:var(--button-destructive-hover-border,var(--button-destructive-border,transparent))]",
262
+ "hover:[box-shadow:var(--button-destructive-shadow-hover,var(--button-shadow-hover,var(--button-destructive-shadow,var(--button-shadow,none))))]",
263
+ "focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",
264
+ "dark:bg-destructive/60"
265
+ ].join(" "),
266
+ // Outline variant - full customization with proper border handling
267
+ outline: [
268
+ "bg-[var(--button-outline-bg,hsl(var(--background)))]",
269
+ "text-[var(--button-outline-fg,inherit)]",
270
+ "border-[length:var(--button-outline-border-width,1px)]",
271
+ "border-[color:var(--button-outline-border,hsl(var(--border)))]",
272
+ "[box-shadow:var(--button-outline-shadow,var(--button-shadow,0_1px_2px_0_rgb(0_0_0/0.05)))]",
273
+ "hover:bg-[var(--button-outline-hover-bg,hsl(var(--accent)))]",
274
+ "hover:text-[var(--button-outline-hover-fg,hsl(var(--accent-foreground)))]",
275
+ "hover:border-[color:var(--button-outline-hover-border,var(--button-outline-border,hsl(var(--border))))]",
276
+ "hover:[box-shadow:var(--button-outline-shadow-hover,var(--button-shadow-hover,var(--button-outline-shadow,var(--button-shadow,none))))]",
277
+ "dark:bg-input/30 dark:border-input dark:hover:bg-input/50"
278
+ ].join(" "),
279
+ // Secondary variant - full customization
280
+ secondary: [
281
+ "bg-[var(--button-secondary-bg,hsl(var(--secondary)))]",
282
+ "text-[var(--button-secondary-fg,hsl(var(--secondary-foreground)))]",
283
+ "border-[length:var(--button-secondary-border-width,0px)]",
284
+ "border-[color:var(--button-secondary-border,transparent)]",
285
+ "[box-shadow:var(--button-secondary-shadow,var(--button-shadow,none))]",
286
+ "hover:bg-[var(--button-secondary-hover-bg,hsl(var(--secondary)/0.8))]",
287
+ "hover:text-[var(--button-secondary-hover-fg,var(--button-secondary-fg,hsl(var(--secondary-foreground))))]",
288
+ "hover:border-[color:var(--button-secondary-hover-border,var(--button-secondary-border,transparent))]",
289
+ "hover:[box-shadow:var(--button-secondary-shadow-hover,var(--button-shadow-hover,var(--button-secondary-shadow,var(--button-shadow,none))))]"
290
+ ].join(" "),
291
+ // Ghost variant - full customization
292
+ ghost: [
293
+ "bg-[var(--button-ghost-bg,transparent)]",
294
+ "text-[var(--button-ghost-fg,inherit)]",
295
+ "border-[length:var(--button-ghost-border-width,0px)]",
296
+ "border-[color:var(--button-ghost-border,transparent)]",
297
+ "[box-shadow:var(--button-ghost-shadow,var(--button-shadow,none))]",
298
+ "hover:bg-[var(--button-ghost-hover-bg,hsl(var(--accent)))]",
299
+ "hover:text-[var(--button-ghost-hover-fg,hsl(var(--accent-foreground)))]",
300
+ "hover:border-[color:var(--button-ghost-hover-border,var(--button-ghost-border,transparent))]",
301
+ "hover:[box-shadow:var(--button-ghost-shadow-hover,var(--button-shadow-hover,var(--button-ghost-shadow,var(--button-shadow,none))))]",
302
+ "dark:hover:bg-accent/50"
303
+ ].join(" "),
304
+ // Link variant - full customization
305
+ link: [
306
+ "bg-[var(--button-link-bg,transparent)]",
307
+ "text-[var(--button-link-fg,hsl(var(--primary)))]",
308
+ "border-[length:var(--button-link-border-width,0px)]",
309
+ "border-[color:var(--button-link-border,transparent)]",
310
+ "[box-shadow:var(--button-link-shadow,none)]",
311
+ "hover:bg-[var(--button-link-hover-bg,transparent)]",
312
+ "hover:text-[var(--button-link-hover-fg,var(--button-link-fg,hsl(var(--primary))))]",
313
+ "hover:[box-shadow:var(--button-link-shadow-hover,none)]",
314
+ "underline-offset-4 hover:underline"
315
+ ].join(" ")
316
+ },
317
+ size: {
318
+ default: [
319
+ "h-[var(--button-height-md,2.25rem)]",
320
+ "px-[var(--button-padding-x-md,1rem)]",
321
+ "py-[var(--button-padding-y-md,0.5rem)]",
322
+ "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
323
+ ].join(" "),
324
+ sm: [
325
+ "h-[var(--button-height-sm,2rem)]",
326
+ "px-[var(--button-padding-x-sm,0.75rem)]",
327
+ "py-[var(--button-padding-y-sm,0.25rem)]",
328
+ "gap-1.5",
329
+ "has-[>svg]:px-[calc(var(--button-padding-x-sm,0.75rem)*0.83)]"
330
+ ].join(" "),
331
+ md: [
332
+ "h-[var(--button-height-md,2.25rem)]",
333
+ "px-[var(--button-padding-x-md,1rem)]",
334
+ "py-[var(--button-padding-y-md,0.5rem)]",
335
+ "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
336
+ ].join(" "),
337
+ lg: [
338
+ "h-[var(--button-height-lg,2.5rem)]",
339
+ "px-[var(--button-padding-x-lg,1.5rem)]",
340
+ "py-[var(--button-padding-y-lg,0.5rem)]",
341
+ "has-[>svg]:px-[calc(var(--button-padding-x-lg,1.5rem)*0.67)]"
342
+ ].join(" "),
343
+ icon: "size-[var(--button-height-md,2.25rem)]",
344
+ "icon-sm": "size-[var(--button-height-sm,2rem)]",
345
+ "icon-lg": "size-[var(--button-height-lg,2.5rem)]"
346
+ }
347
+ },
348
+ defaultVariants: {
349
+ variant: "default",
350
+ size: "default"
351
+ }
352
+ });
353
+ var Pressable = React__namespace.forwardRef(
354
+ ({
355
+ children,
356
+ className,
357
+ href,
358
+ onClick,
359
+ variant,
360
+ size,
361
+ asButton = false,
362
+ fallbackComponentType = "span",
363
+ componentType,
364
+ "aria-label": ariaLabel,
365
+ "aria-describedby": ariaDescribedby,
366
+ id,
367
+ ...props
368
+ }, ref) => {
369
+ const navigation = useNavigation({ href, onClick });
370
+ const {
371
+ normalizedHref,
372
+ target,
373
+ rel,
374
+ linkType,
375
+ isInternal,
376
+ handleClick
377
+ } = navigation;
378
+ const shouldRenderLink = normalizedHref && linkType !== "none";
379
+ const shouldRenderButton = !shouldRenderLink && onClick;
380
+ const effectiveComponentType = componentType || (shouldRenderLink ? "a" : shouldRenderButton ? "button" : fallbackComponentType);
381
+ const finalComponentType = isInternal && shouldRenderLink ? "a" : effectiveComponentType;
382
+ const shouldApplyButtonStyles = asButton || variant || size;
383
+ const combinedClassName = cn(
384
+ shouldApplyButtonStyles && buttonVariants({ variant, size }),
385
+ className
386
+ );
387
+ const dataProps = Object.fromEntries(
388
+ Object.entries(props).filter(([key]) => key.startsWith("data-"))
389
+ );
390
+ const buttonDataAttributes = shouldApplyButtonStyles ? {
391
+ "data-slot": "button",
392
+ "data-variant": variant ?? "default",
393
+ "data-size": size ?? "default"
394
+ } : {};
395
+ const commonProps = {
396
+ className: combinedClassName,
397
+ onClick: handleClick,
398
+ "aria-label": ariaLabel,
399
+ "aria-describedby": ariaDescribedby,
400
+ id,
401
+ ...dataProps,
402
+ ...buttonDataAttributes
403
+ };
404
+ if (finalComponentType === "a" && shouldRenderLink) {
405
+ return /* @__PURE__ */ jsxRuntime.jsx(
406
+ "a",
407
+ {
408
+ ref,
409
+ href: normalizedHref,
410
+ target,
411
+ rel,
412
+ ...commonProps,
413
+ ...props,
414
+ children
415
+ }
416
+ );
417
+ }
418
+ if (finalComponentType === "button") {
419
+ return /* @__PURE__ */ jsxRuntime.jsx(
420
+ "button",
421
+ {
422
+ ref,
423
+ type: props.type || "button",
424
+ ...commonProps,
425
+ ...props,
426
+ children
427
+ }
428
+ );
429
+ }
430
+ if (finalComponentType === "div") {
431
+ return /* @__PURE__ */ jsxRuntime.jsx(
432
+ "div",
433
+ {
434
+ ref,
435
+ ...commonProps,
436
+ children
437
+ }
438
+ );
439
+ }
440
+ return /* @__PURE__ */ jsxRuntime.jsx(
441
+ "span",
442
+ {
443
+ ref,
444
+ ...commonProps,
445
+ children
446
+ }
447
+ );
448
+ }
449
+ );
450
+ Pressable.displayName = "Pressable";
451
+ var svgCache = /* @__PURE__ */ new Map();
452
+ function DynamicIcon({
453
+ name,
454
+ size = 28,
455
+ color,
456
+ className,
457
+ alt
458
+ }) {
459
+ const [svgContent, setSvgContent] = React__namespace.useState(null);
460
+ const [isLoading, setIsLoading] = React__namespace.useState(true);
461
+ const [error, setError] = React__namespace.useState(null);
462
+ const { url, iconName } = React__namespace.useMemo(() => {
463
+ const separator = name.includes("/") ? "/" : ":";
464
+ const [prefix, iconName2] = name.split(separator);
465
+ const baseUrl = `https://icons.opensite.ai/api/icon/${prefix}/${iconName2}?format=svg&width=${size}&height=${size}`;
466
+ return {
467
+ url: baseUrl,
468
+ iconName: iconName2
469
+ };
470
+ }, [name, size]);
471
+ React__namespace.useEffect(() => {
472
+ let isMounted = true;
473
+ const fetchSvg = async () => {
474
+ const cached = svgCache.get(url);
475
+ if (cached) {
476
+ if (isMounted) {
477
+ setSvgContent(cached);
478
+ setIsLoading(false);
479
+ }
480
+ return;
481
+ }
482
+ try {
483
+ setIsLoading(true);
484
+ setError(null);
485
+ const response = await fetch(url);
486
+ if (!response.ok) {
487
+ throw new Error(`Failed to fetch icon: ${response.status}`);
488
+ }
489
+ let svg = await response.text();
490
+ svg = processSvgForCurrentColor(svg);
491
+ svgCache.set(url, svg);
492
+ if (isMounted) {
493
+ setSvgContent(svg);
494
+ setIsLoading(false);
495
+ }
496
+ } catch (err) {
497
+ if (isMounted) {
498
+ setError(err instanceof Error ? err.message : "Failed to load icon");
499
+ setIsLoading(false);
500
+ }
501
+ }
502
+ };
503
+ fetchSvg();
504
+ return () => {
505
+ isMounted = false;
506
+ };
507
+ }, [url]);
508
+ if (isLoading) {
509
+ return /* @__PURE__ */ jsxRuntime.jsx(
510
+ "span",
511
+ {
512
+ className: cn("inline-block", className),
513
+ style: { width: size, height: size },
514
+ "aria-hidden": "true"
515
+ }
516
+ );
517
+ }
518
+ if (error || !svgContent) {
519
+ return /* @__PURE__ */ jsxRuntime.jsx(
520
+ "span",
521
+ {
522
+ className: cn("inline-block", className),
523
+ style: { width: size, height: size },
524
+ role: "img",
525
+ "aria-label": alt || iconName
526
+ }
527
+ );
528
+ }
529
+ return /* @__PURE__ */ jsxRuntime.jsx(
530
+ "span",
531
+ {
532
+ className: cn("inline-flex items-center justify-center", className),
533
+ style: {
534
+ width: size,
535
+ height: size,
536
+ color: color || "inherit"
537
+ },
538
+ role: "img",
539
+ "aria-label": alt || iconName,
540
+ dangerouslySetInnerHTML: { __html: svgContent }
541
+ }
542
+ );
543
+ }
544
+ function processSvgForCurrentColor(svg) {
545
+ let processed = svg;
546
+ processed = processed.replace(
547
+ /stroke=["'](#000000|#000|black)["']/gi,
548
+ 'stroke="currentColor"'
549
+ );
550
+ processed = processed.replace(
551
+ /fill=["'](#000000|#000|black)["']/gi,
552
+ 'fill="currentColor"'
553
+ );
554
+ return processed;
555
+ }
556
+ function CarouselPagination({
557
+ onPrevious,
558
+ onNext,
559
+ canScrollPrevious = true,
560
+ canScrollNext = true,
561
+ iconSize = 24,
562
+ className,
563
+ buttonClassName,
564
+ previousIcon = "lucide/arrow-left",
565
+ nextIcon = "lucide/arrow-right",
566
+ previousAriaLabel = "Previous",
567
+ nextAriaLabel = "Next"
568
+ }) {
569
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex justify-end gap-2", className), children: [
570
+ /* @__PURE__ */ jsxRuntime.jsx(
571
+ Pressable,
572
+ {
573
+ onClick: onPrevious,
574
+ disabled: !canScrollPrevious,
575
+ "aria-label": previousAriaLabel,
576
+ asButton: true,
577
+ className: cn(
578
+ "relative z-40 flex h-10 w-10 items-center justify-center rounded-full disabled:opacity-50",
579
+ buttonClassName
580
+ ),
581
+ children: /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: previousIcon, size: iconSize })
582
+ }
583
+ ),
584
+ /* @__PURE__ */ jsxRuntime.jsx(
585
+ Pressable,
586
+ {
587
+ onClick: onNext,
588
+ disabled: !canScrollNext,
589
+ "aria-label": nextAriaLabel,
590
+ asButton: true,
591
+ className: cn(
592
+ "relative z-40 flex h-10 w-10 items-center justify-center rounded-full disabled:opacity-50",
593
+ buttonClassName
594
+ ),
595
+ children: /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: nextIcon, size: iconSize })
596
+ }
597
+ )
598
+ ] });
599
+ }
600
+
601
+ exports.CarouselPagination = CarouselPagination;
@@ -0,0 +1,75 @@
1
+ import * as React from 'react';
2
+
3
+ interface CarouselPaginationProps {
4
+ /**
5
+ * Handler for scrolling to the previous item
6
+ */
7
+ onPrevious: () => void;
8
+ /**
9
+ * Handler for scrolling to the next item
10
+ */
11
+ onNext: () => void;
12
+ /**
13
+ * Whether the previous button should be disabled
14
+ */
15
+ canScrollPrevious?: boolean;
16
+ /**
17
+ * Whether the next button should be disabled
18
+ */
19
+ canScrollNext?: boolean;
20
+ /**
21
+ * Size of the icon in pixels
22
+ * @default 24
23
+ */
24
+ iconSize?: number;
25
+ /**
26
+ * Additional CSS classes for the container
27
+ */
28
+ className?: string;
29
+ /**
30
+ * Additional CSS classes for the buttons
31
+ */
32
+ buttonClassName?: string;
33
+ /**
34
+ * Icon name for the previous button
35
+ * @default "lucide/arrow-left"
36
+ */
37
+ previousIcon?: string;
38
+ /**
39
+ * Icon name for the next button
40
+ * @default "lucide/arrow-right"
41
+ */
42
+ nextIcon?: string;
43
+ /**
44
+ * Aria label for the previous button
45
+ * @default "Previous"
46
+ */
47
+ previousAriaLabel?: string;
48
+ /**
49
+ * Aria label for the next button
50
+ * @default "Next"
51
+ */
52
+ nextAriaLabel?: string;
53
+ }
54
+ /**
55
+ * CarouselPagination - Reusable carousel navigation buttons based on AppleCarousel design.
56
+ *
57
+ * Features:
58
+ * - Circular buttons with consistent sizing
59
+ * - Disabled state with reduced opacity
60
+ * - Positioned with flexbox for flexible placement
61
+ * - Works with any carousel implementation
62
+ *
63
+ * @example
64
+ * ```tsx
65
+ * <CarouselPagination
66
+ * onPrevious={() => scroll("left")}
67
+ * onNext={() => scroll("right")}
68
+ * canScrollPrevious={!isAtStart}
69
+ * canScrollNext={!isAtEnd}
70
+ * />
71
+ * ```
72
+ */
73
+ declare function CarouselPagination({ onPrevious, onNext, canScrollPrevious, canScrollNext, iconSize, className, buttonClassName, previousIcon, nextIcon, previousAriaLabel, nextAriaLabel, }: CarouselPaginationProps): React.JSX.Element;
74
+
75
+ export { CarouselPagination, type CarouselPaginationProps };