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