@page-speed/pressable 0.0.1

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,456 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var clsx = require('clsx');
5
+ var tailwindMerge = require('tailwind-merge');
6
+ var classVarianceAuthority = require('class-variance-authority');
7
+ var jsxRuntime = require('react/jsx-runtime');
8
+
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
28
+
29
+ // src/core/Pressable.tsx
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 extensionMatch = trimmed.match(/^(.+?)\s*(x|ext\.?|extension)\s*(\d+)$/i);
39
+ let phoneNumber = trimmed;
40
+ let extension = "";
41
+ if (extensionMatch) {
42
+ phoneNumber = extensionMatch[1];
43
+ extension = extensionMatch[3];
44
+ }
45
+ const hasPlus = phoneNumber.trim().startsWith("+");
46
+ const cleaned = phoneNumber.replace(/[^\d]/g, "");
47
+ let normalized = cleaned;
48
+ if (!hasPlus) {
49
+ if (cleaned.length === 10) {
50
+ normalized = `+1${cleaned}`;
51
+ } else if (cleaned.length >= 11) {
52
+ normalized = `+${cleaned}`;
53
+ }
54
+ }
55
+ const withExtension = extension ? `${normalized};ext=${extension}` : normalized;
56
+ return `tel:${withExtension}`;
57
+ }
58
+ function normalizeEmail(input) {
59
+ const trimmed = input.trim();
60
+ if (trimmed.toLowerCase().startsWith("mailto:")) {
61
+ return trimmed;
62
+ }
63
+ return `mailto:${trimmed}`;
64
+ }
65
+ function isEmail(input) {
66
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
67
+ return emailRegex.test(input.trim());
68
+ }
69
+ function isPhoneNumber(input) {
70
+ const trimmed = input.trim();
71
+ if (trimmed.toLowerCase().startsWith("tel:")) {
72
+ return true;
73
+ }
74
+ const phoneRegex = /^[\s\+\-\(\)]*\d[\d\s\-\(\)\.]*\d[\s\-]*(x|ext\.?|extension)?[\s\-]*\d*$/i;
75
+ return phoneRegex.test(trimmed);
76
+ }
77
+ function isInternalUrl(href) {
78
+ if (typeof window === "undefined") {
79
+ return href.startsWith("/") && !href.startsWith("//");
80
+ }
81
+ const trimmed = href.trim();
82
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
83
+ return true;
84
+ }
85
+ try {
86
+ const url = new URL(trimmed, window.location.href);
87
+ const currentOrigin = window.location.origin;
88
+ const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
89
+ return normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin);
90
+ } catch {
91
+ return false;
92
+ }
93
+ }
94
+ function toRelativePath(href) {
95
+ if (typeof window === "undefined") {
96
+ return href;
97
+ }
98
+ const trimmed = href.trim();
99
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
100
+ return trimmed;
101
+ }
102
+ try {
103
+ const url = new URL(trimmed, window.location.href);
104
+ const currentOrigin = window.location.origin;
105
+ const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
106
+ if (normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin)) {
107
+ return url.pathname + url.search + url.hash;
108
+ }
109
+ } catch {
110
+ }
111
+ return trimmed;
112
+ }
113
+ function useNavigation({
114
+ href,
115
+ onClick
116
+ } = {}) {
117
+ const linkType = React__namespace.useMemo(() => {
118
+ if (!href || href.trim() === "") {
119
+ return onClick ? "none" : "none";
120
+ }
121
+ const trimmed = href.trim();
122
+ if (trimmed.toLowerCase().startsWith("mailto:") || isEmail(trimmed)) {
123
+ return "mailto";
124
+ }
125
+ if (trimmed.toLowerCase().startsWith("tel:") || isPhoneNumber(trimmed)) {
126
+ return "tel";
127
+ }
128
+ if (isInternalUrl(trimmed)) {
129
+ return "internal";
130
+ }
131
+ try {
132
+ new URL(
133
+ trimmed,
134
+ typeof window !== "undefined" ? window.location.href : "http://localhost"
135
+ );
136
+ return "external";
137
+ } catch {
138
+ return "internal";
139
+ }
140
+ }, [href, onClick]);
141
+ const normalizedHref = React__namespace.useMemo(() => {
142
+ if (!href || href.trim() === "") {
143
+ return void 0;
144
+ }
145
+ const trimmed = href.trim();
146
+ switch (linkType) {
147
+ case "tel":
148
+ return normalizePhoneNumber(trimmed);
149
+ case "mailto":
150
+ return normalizeEmail(trimmed);
151
+ case "internal":
152
+ return toRelativePath(trimmed);
153
+ case "external":
154
+ return trimmed;
155
+ default:
156
+ return trimmed;
157
+ }
158
+ }, [href, linkType]);
159
+ const target = React__namespace.useMemo(() => {
160
+ switch (linkType) {
161
+ case "external":
162
+ return "_blank";
163
+ case "internal":
164
+ return "_self";
165
+ case "mailto":
166
+ case "tel":
167
+ return void 0;
168
+ default:
169
+ return void 0;
170
+ }
171
+ }, [linkType]);
172
+ const rel = React__namespace.useMemo(() => {
173
+ if (linkType === "external") {
174
+ return "noopener noreferrer";
175
+ }
176
+ return void 0;
177
+ }, [linkType]);
178
+ const isExternal = linkType === "external";
179
+ const isInternal = linkType === "internal";
180
+ const shouldUseRouter = isInternal && typeof normalizedHref === "string" && normalizedHref.startsWith("/");
181
+ const handleClick = React__namespace.useCallback(
182
+ (event) => {
183
+ if (onClick) {
184
+ try {
185
+ onClick(event);
186
+ } catch (error) {
187
+ console.error("Error in user onClick handler:", error);
188
+ }
189
+ }
190
+ if (event.defaultPrevented) {
191
+ return;
192
+ }
193
+ if (shouldUseRouter && normalizedHref && event.button === 0 && // left-click only
194
+ !event.metaKey && !event.altKey && !event.ctrlKey && !event.shiftKey) {
195
+ if (typeof window !== "undefined") {
196
+ const handler = window.__opensiteNavigationHandler;
197
+ if (typeof handler === "function") {
198
+ try {
199
+ const handled = handler(
200
+ normalizedHref,
201
+ event.nativeEvent || event
202
+ );
203
+ if (handled !== false) {
204
+ event.preventDefault();
205
+ }
206
+ } catch (error) {
207
+ console.error("Error in navigation handler:", error);
208
+ }
209
+ }
210
+ }
211
+ }
212
+ },
213
+ [onClick, shouldUseRouter, normalizedHref]
214
+ );
215
+ return {
216
+ linkType,
217
+ normalizedHref,
218
+ target,
219
+ rel,
220
+ isExternal,
221
+ isInternal,
222
+ shouldUseRouter,
223
+ handleClick
224
+ };
225
+ }
226
+ var baseStyles = [
227
+ // Layout
228
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap shrink-0",
229
+ // Typography - using CSS variables with sensible defaults
230
+ "font-[var(--button-font-family,inherit)]",
231
+ "font-[var(--button-font-weight,500)]",
232
+ "tracking-[var(--button-letter-spacing,0)]",
233
+ "leading-[var(--button-line-height,1.25)]",
234
+ "[text-transform:var(--button-text-transform,none)]",
235
+ "text-sm",
236
+ // Border radius
237
+ "rounded-[var(--button-radius,var(--radius,0.375rem))]",
238
+ // Smooth transition - using [transition:...] to set full shorthand property (not just transition-property)
239
+ "[transition:var(--button-transition,all_250ms_cubic-bezier(0.4,0,0.2,1))]",
240
+ // Box shadow (master level) - using [box-shadow:...] for complex multi-value shadows
241
+ "[box-shadow:var(--button-shadow,none)]",
242
+ "hover:[box-shadow:var(--button-shadow-hover,var(--button-shadow,none))]",
243
+ // Disabled state
244
+ "disabled:pointer-events-none disabled:opacity-50",
245
+ // SVG handling
246
+ "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0",
247
+ // Focus styles
248
+ "outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
249
+ // Invalid state
250
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive"
251
+ ].join(" ");
252
+ var buttonVariants = classVarianceAuthority.cva(baseStyles, {
253
+ variants: {
254
+ variant: {
255
+ // Default (Primary) variant - full customization
256
+ default: [
257
+ "bg-[var(--button-default-bg,hsl(var(--primary)))]",
258
+ "text-[var(--button-default-fg,hsl(var(--primary-foreground)))]",
259
+ "border-[length:var(--button-default-border-width,0px)]",
260
+ "border-[color:var(--button-default-border,transparent)]",
261
+ "[box-shadow:var(--button-default-shadow,var(--button-shadow,none))]",
262
+ "hover:bg-[var(--button-default-hover-bg,hsl(var(--primary)/0.9))]",
263
+ "hover:text-[var(--button-default-hover-fg,var(--button-default-fg,hsl(var(--primary-foreground))))]",
264
+ "hover:border-[color:var(--button-default-hover-border,var(--button-default-border,transparent))]",
265
+ "hover:[box-shadow:var(--button-default-shadow-hover,var(--button-shadow-hover,var(--button-default-shadow,var(--button-shadow,none))))]"
266
+ ].join(" "),
267
+ // Destructive variant - full customization
268
+ destructive: [
269
+ "bg-[var(--button-destructive-bg,hsl(var(--destructive)))]",
270
+ "text-[var(--button-destructive-fg,white)]",
271
+ "border-[length:var(--button-destructive-border-width,0px)]",
272
+ "border-[color:var(--button-destructive-border,transparent)]",
273
+ "[box-shadow:var(--button-destructive-shadow,var(--button-shadow,none))]",
274
+ "hover:bg-[var(--button-destructive-hover-bg,hsl(var(--destructive)/0.9))]",
275
+ "hover:text-[var(--button-destructive-hover-fg,var(--button-destructive-fg,white))]",
276
+ "hover:border-[color:var(--button-destructive-hover-border,var(--button-destructive-border,transparent))]",
277
+ "hover:[box-shadow:var(--button-destructive-shadow-hover,var(--button-shadow-hover,var(--button-destructive-shadow,var(--button-shadow,none))))]",
278
+ "focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",
279
+ "dark:bg-destructive/60"
280
+ ].join(" "),
281
+ // Outline variant - full customization with proper border handling
282
+ outline: [
283
+ "bg-[var(--button-outline-bg,hsl(var(--background)))]",
284
+ "text-[var(--button-outline-fg,inherit)]",
285
+ "border-[length:var(--button-outline-border-width,1px)]",
286
+ "border-[color:var(--button-outline-border,hsl(var(--border)))]",
287
+ "[box-shadow:var(--button-outline-shadow,var(--button-shadow,0_1px_2px_0_rgb(0_0_0/0.05)))]",
288
+ "hover:bg-[var(--button-outline-hover-bg,hsl(var(--accent)))]",
289
+ "hover:text-[var(--button-outline-hover-fg,hsl(var(--accent-foreground)))]",
290
+ "hover:border-[color:var(--button-outline-hover-border,var(--button-outline-border,hsl(var(--border))))]",
291
+ "hover:[box-shadow:var(--button-outline-shadow-hover,var(--button-shadow-hover,var(--button-outline-shadow,var(--button-shadow,none))))]",
292
+ "dark:bg-input/30 dark:border-input dark:hover:bg-input/50"
293
+ ].join(" "),
294
+ // Secondary variant - full customization
295
+ secondary: [
296
+ "bg-[var(--button-secondary-bg,hsl(var(--secondary)))]",
297
+ "text-[var(--button-secondary-fg,hsl(var(--secondary-foreground)))]",
298
+ "border-[length:var(--button-secondary-border-width,0px)]",
299
+ "border-[color:var(--button-secondary-border,transparent)]",
300
+ "[box-shadow:var(--button-secondary-shadow,var(--button-shadow,none))]",
301
+ "hover:bg-[var(--button-secondary-hover-bg,hsl(var(--secondary)/0.8))]",
302
+ "hover:text-[var(--button-secondary-hover-fg,var(--button-secondary-fg,hsl(var(--secondary-foreground))))]",
303
+ "hover:border-[color:var(--button-secondary-hover-border,var(--button-secondary-border,transparent))]",
304
+ "hover:[box-shadow:var(--button-secondary-shadow-hover,var(--button-shadow-hover,var(--button-secondary-shadow,var(--button-shadow,none))))]"
305
+ ].join(" "),
306
+ // Ghost variant - full customization
307
+ ghost: [
308
+ "bg-[var(--button-ghost-bg,transparent)]",
309
+ "text-[var(--button-ghost-fg,inherit)]",
310
+ "border-[length:var(--button-ghost-border-width,0px)]",
311
+ "border-[color:var(--button-ghost-border,transparent)]",
312
+ "[box-shadow:var(--button-ghost-shadow,var(--button-shadow,none))]",
313
+ "hover:bg-[var(--button-ghost-hover-bg,hsl(var(--accent)))]",
314
+ "hover:text-[var(--button-ghost-hover-fg,hsl(var(--accent-foreground)))]",
315
+ "hover:border-[color:var(--button-ghost-hover-border,var(--button-ghost-border,transparent))]",
316
+ "hover:[box-shadow:var(--button-ghost-shadow-hover,var(--button-shadow-hover,var(--button-ghost-shadow,var(--button-shadow,none))))]",
317
+ "dark:hover:bg-accent/50"
318
+ ].join(" "),
319
+ // Link variant - full customization
320
+ link: [
321
+ "bg-[var(--button-link-bg,transparent)]",
322
+ "text-[var(--button-link-fg,hsl(var(--primary)))]",
323
+ "border-[length:var(--button-link-border-width,0px)]",
324
+ "border-[color:var(--button-link-border,transparent)]",
325
+ "[box-shadow:var(--button-link-shadow,none)]",
326
+ "hover:bg-[var(--button-link-hover-bg,transparent)]",
327
+ "hover:text-[var(--button-link-hover-fg,var(--button-link-fg,hsl(var(--primary))))]",
328
+ "hover:[box-shadow:var(--button-link-shadow-hover,none)]",
329
+ "underline-offset-4 hover:underline"
330
+ ].join(" ")
331
+ },
332
+ size: {
333
+ default: [
334
+ "h-[var(--button-height-md,2.25rem)]",
335
+ "px-[var(--button-padding-x-md,1rem)]",
336
+ "py-[var(--button-padding-y-md,0.5rem)]",
337
+ "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
338
+ ].join(" "),
339
+ sm: [
340
+ "h-[var(--button-height-sm,2rem)]",
341
+ "px-[var(--button-padding-x-sm,0.75rem)]",
342
+ "py-[var(--button-padding-y-sm,0.25rem)]",
343
+ "gap-1.5",
344
+ "has-[>svg]:px-[calc(var(--button-padding-x-sm,0.75rem)*0.83)]"
345
+ ].join(" "),
346
+ md: [
347
+ "h-[var(--button-height-md,2.25rem)]",
348
+ "px-[var(--button-padding-x-md,1rem)]",
349
+ "py-[var(--button-padding-y-md,0.5rem)]",
350
+ "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
351
+ ].join(" "),
352
+ lg: [
353
+ "h-[var(--button-height-lg,2.5rem)]",
354
+ "px-[var(--button-padding-x-lg,1.5rem)]",
355
+ "py-[var(--button-padding-y-lg,0.5rem)]",
356
+ "has-[>svg]:px-[calc(var(--button-padding-x-lg,1.5rem)*0.67)]"
357
+ ].join(" "),
358
+ icon: "size-[var(--button-height-md,2.25rem)]",
359
+ "icon-sm": "size-[var(--button-height-sm,2rem)]",
360
+ "icon-lg": "size-[var(--button-height-lg,2.5rem)]"
361
+ }
362
+ },
363
+ defaultVariants: {
364
+ variant: "default",
365
+ size: "default"
366
+ }
367
+ });
368
+ var Pressable = React__namespace.forwardRef(
369
+ ({
370
+ children,
371
+ className,
372
+ href,
373
+ onClick,
374
+ variant,
375
+ size,
376
+ asButton = false,
377
+ fallbackComponentType = "span",
378
+ componentType,
379
+ "aria-label": ariaLabel,
380
+ "aria-describedby": ariaDescribedby,
381
+ id,
382
+ ...props
383
+ }, ref) => {
384
+ const navigation = useNavigation({ href, onClick });
385
+ const {
386
+ normalizedHref,
387
+ target,
388
+ rel,
389
+ linkType,
390
+ isInternal,
391
+ handleClick
392
+ } = navigation;
393
+ const shouldRenderLink = normalizedHref && linkType !== "none";
394
+ const shouldRenderButton = !shouldRenderLink && onClick;
395
+ const effectiveComponentType = componentType || (shouldRenderLink ? "a" : shouldRenderButton ? "button" : fallbackComponentType);
396
+ const finalComponentType = isInternal && shouldRenderLink ? "a" : effectiveComponentType;
397
+ const shouldApplyButtonStyles = asButton || variant || size;
398
+ const combinedClassName = cn(
399
+ shouldApplyButtonStyles && buttonVariants({ variant, size }),
400
+ className
401
+ );
402
+ const dataProps = Object.fromEntries(
403
+ Object.entries(props).filter(([key]) => key.startsWith("data-"))
404
+ );
405
+ const buttonDataAttributes = shouldApplyButtonStyles ? {
406
+ "data-slot": "button",
407
+ "data-variant": variant ?? "default",
408
+ "data-size": size ?? "default"
409
+ } : {};
410
+ const commonProps = {
411
+ className: combinedClassName,
412
+ onClick: handleClick,
413
+ "aria-label": ariaLabel,
414
+ "aria-describedby": ariaDescribedby,
415
+ id,
416
+ ...dataProps,
417
+ ...buttonDataAttributes
418
+ };
419
+ if (finalComponentType === "a" && shouldRenderLink) {
420
+ return /* @__PURE__ */ jsxRuntime.jsx(
421
+ "a",
422
+ {
423
+ ref,
424
+ href: normalizedHref,
425
+ target,
426
+ rel,
427
+ ...commonProps,
428
+ ...props,
429
+ children
430
+ }
431
+ );
432
+ }
433
+ if (finalComponentType === "button") {
434
+ return /* @__PURE__ */ jsxRuntime.jsx(
435
+ "button",
436
+ {
437
+ ref,
438
+ type: props.type || "button",
439
+ ...commonProps,
440
+ ...props,
441
+ children
442
+ }
443
+ );
444
+ }
445
+ if (finalComponentType === "div") {
446
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, ...commonProps, children });
447
+ }
448
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { ref, ...commonProps, children });
449
+ }
450
+ );
451
+ Pressable.displayName = "Pressable";
452
+
453
+ exports.Pressable = Pressable;
454
+ exports.buttonVariants = buttonVariants;
455
+ //# sourceMappingURL=index.cjs.map
456
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/cn.ts","../../src/hooks/useNavigation.ts","../../src/core/button-variants.ts","../../src/core/Pressable.tsx"],"names":["twMerge","clsx","React","cva","React2","jsx"],"mappings":"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","file":"index.cjs","sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Utility function to merge Tailwind CSS classes\n * Combines clsx for conditional classes and tailwind-merge for conflict resolution\n *\n * @param inputs - Class names, arrays, or objects to merge\n * @returns Merged class string with Tailwind conflicts resolved\n *\n * @example\n * ```tsx\n * cn(\"px-2 py-1\", isActive && \"bg-blue-500\", { \"font-bold\": isImportant })\n * // => \"px-2 py-1 bg-blue-500 font-bold\" (if both conditions are true)\n * ```\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport type { UseNavigationArgs, UseNavigationReturn, LinkType } from \"../types\";\n\n/**\n * Normalizes phone numbers to tel: format\n * Handles formats like:\n * - \"+14322386131\"\n * - \"(432) 238-6131\"\n * - \"512-232-2212x123\"\n * - \"tel:+14322386131\"\n */\nfunction normalizePhoneNumber(input: string): string {\n const trimmed = input.trim();\n\n // Already has tel: prefix\n if (trimmed.toLowerCase().startsWith(\"tel:\")) {\n return trimmed;\n }\n\n // Check for extension markers (x, ext, extension)\n const extensionMatch = trimmed.match(/^(.+?)\\s*(x|ext\\.?|extension)\\s*(\\d+)$/i);\n let phoneNumber = trimmed;\n let extension = \"\";\n\n if (extensionMatch) {\n phoneNumber = extensionMatch[1];\n extension = extensionMatch[3];\n }\n\n // Clean the phone number (remove everything except digits and leading +)\n const hasPlus = phoneNumber.trim().startsWith(\"+\");\n const cleaned = phoneNumber.replace(/[^\\d]/g, \"\");\n\n // Add country code if needed:\n // - For exactly 10 digits (US/Canada format), prepend +1\n // - For 11+ digits without +, just add +\n let normalized = cleaned;\n if (!hasPlus) {\n if (cleaned.length === 10) {\n normalized = `+1${cleaned}`;\n } else if (cleaned.length >= 11) {\n normalized = `+${cleaned}`;\n }\n }\n\n // Add extension if present\n const withExtension = extension\n ? `${normalized};ext=${extension}`\n : normalized;\n\n return `tel:${withExtension}`;\n}\n\n/**\n * Normalizes email addresses to mailto: format\n */\nfunction normalizeEmail(input: string): string {\n const trimmed = input.trim();\n\n // Already has mailto: prefix\n if (trimmed.toLowerCase().startsWith(\"mailto:\")) {\n return trimmed;\n }\n\n return `mailto:${trimmed}`;\n}\n\n/**\n * Detects if a string is an email address\n */\nfunction isEmail(input: string): boolean {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n return emailRegex.test(input.trim());\n}\n\n/**\n * Detects if a string is a phone number\n */\nfunction isPhoneNumber(input: string): boolean {\n const trimmed = input.trim();\n\n // Already has tel: prefix\n if (trimmed.toLowerCase().startsWith(\"tel:\")) {\n return true;\n }\n\n // Match various phone formats\n const phoneRegex =\n /^[\\s\\+\\-\\(\\)]*\\d[\\d\\s\\-\\(\\)\\.]*\\d[\\s\\-]*(x|ext\\.?|extension)?[\\s\\-]*\\d*$/i;\n return phoneRegex.test(trimmed);\n}\n\n/**\n * Detects if a URL is internal to the current site\n * Handles cases like:\n * - \"/blog-123\"\n * - \"https://jordansite.com/blog-123\"\n * - \"https://www.jordansite.com/blog-123\"\n */\nfunction isInternalUrl(href: string): boolean {\n if (typeof window === \"undefined\") {\n // SSR fallback: assume relative paths are internal\n return href.startsWith(\"/\") && !href.startsWith(\"//\");\n }\n\n const trimmed = href.trim();\n\n // Relative paths are internal\n if (trimmed.startsWith(\"/\") && !trimmed.startsWith(\"//\")) {\n return true;\n }\n\n // Check if full URL matches current origin\n try {\n const url = new URL(trimmed, window.location.href);\n const currentOrigin = window.location.origin;\n\n // Normalize both origins (remove www. for comparison)\n const normalizeOrigin = (origin: string) =>\n origin.replace(/^(https?:\\/\\/)(www\\.)?/, \"$1\");\n\n return normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin);\n } catch {\n return false;\n }\n}\n\n/**\n * Converts a full URL to a relative path if it's internal\n */\nfunction toRelativePath(href: string): string {\n if (typeof window === \"undefined\") {\n return href;\n }\n\n const trimmed = href.trim();\n\n // Already relative\n if (trimmed.startsWith(\"/\") && !trimmed.startsWith(\"//\")) {\n return trimmed;\n }\n\n try {\n const url = new URL(trimmed, window.location.href);\n const currentOrigin = window.location.origin;\n\n // Normalize both origins for comparison\n const normalizeOrigin = (origin: string) =>\n origin.replace(/^(https?:\\/\\/)(www\\.)?/, \"$1\");\n\n if (normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin)) {\n // Return pathname + search + hash\n return url.pathname + url.search + url.hash;\n }\n } catch {\n // Invalid URL, return as-is\n }\n\n return trimmed;\n}\n\n/**\n * Hook for handling navigation with automatic link type detection,\n * URL normalization, and proper attributes for SEO and accessibility.\n *\n * Features:\n * - Detects link types: internal, external, mailto, tel\n * - Normalizes phone numbers (various formats to tel:)\n * - Normalizes email addresses to mailto:\n * - Converts full URLs matching current origin to relative paths\n * - Determines proper target and rel attributes\n * - Handles React Router-style internal navigation\n *\n * @example\n * ```tsx\n * const nav = useNavigation({ href: \"/about\" });\n * // nav.linkType === \"internal\"\n * // nav.normalizedHref === \"/about\"\n * // nav.target === \"_self\"\n *\n * const nav2 = useNavigation({ href: \"(432) 238-6131\" });\n * // nav2.linkType === \"tel\"\n * // nav2.normalizedHref === \"tel:+14322386131\"\n *\n * const nav3 = useNavigation({ href: \"https://google.com\" });\n * // nav3.linkType === \"external\"\n * // nav3.target === \"_blank\"\n * // nav3.rel === \"noopener noreferrer\"\n * ```\n */\nexport function useNavigation({\n href,\n onClick,\n}: UseNavigationArgs = {}): UseNavigationReturn {\n const linkType = React.useMemo((): LinkType => {\n if (!href || href.trim() === \"\") {\n return onClick ? \"none\" : \"none\";\n }\n\n const trimmed = href.trim();\n\n // Check for mailto\n if (trimmed.toLowerCase().startsWith(\"mailto:\") || isEmail(trimmed)) {\n return \"mailto\";\n }\n\n // Check for tel\n if (trimmed.toLowerCase().startsWith(\"tel:\") || isPhoneNumber(trimmed)) {\n return \"tel\";\n }\n\n // Check for internal vs external\n if (isInternalUrl(trimmed)) {\n return \"internal\";\n }\n\n // Check if it's a valid URL\n try {\n new URL(\n trimmed,\n typeof window !== \"undefined\" ? window.location.href : \"http://localhost\"\n );\n return \"external\";\n } catch {\n // Not a valid URL, treat as internal path\n return \"internal\";\n }\n }, [href, onClick]);\n\n const normalizedHref = React.useMemo((): string | undefined => {\n if (!href || href.trim() === \"\") {\n return undefined;\n }\n\n const trimmed = href.trim();\n\n switch (linkType) {\n case \"tel\":\n return normalizePhoneNumber(trimmed);\n case \"mailto\":\n return normalizeEmail(trimmed);\n case \"internal\":\n return toRelativePath(trimmed);\n case \"external\":\n return trimmed;\n default:\n return trimmed;\n }\n }, [href, linkType]);\n\n const target = React.useMemo((): \"_blank\" | \"_self\" | undefined => {\n switch (linkType) {\n case \"external\":\n return \"_blank\";\n case \"internal\":\n return \"_self\";\n case \"mailto\":\n case \"tel\":\n // Let browser handle default behavior\n return undefined;\n default:\n return undefined;\n }\n }, [linkType]);\n\n const rel = React.useMemo((): string | undefined => {\n if (linkType === \"external\") {\n return \"noopener noreferrer\";\n }\n return undefined;\n }, [linkType]);\n\n const isExternal = linkType === \"external\";\n const isInternal = linkType === \"internal\";\n const shouldUseRouter =\n isInternal &&\n typeof normalizedHref === \"string\" &&\n normalizedHref.startsWith(\"/\");\n\n const handleClick = React.useCallback<React.MouseEventHandler<HTMLElement>>(\n (event) => {\n // Call user's onClick first\n if (onClick) {\n try {\n onClick(event);\n } catch (error) {\n console.error(\"Error in user onClick handler:\", error);\n }\n }\n\n // If event was prevented, don't do anything else\n if (event.defaultPrevented) {\n return;\n }\n\n // Only handle internal navigation for left-clicks without modifiers\n if (\n shouldUseRouter &&\n normalizedHref &&\n event.button === 0 && // left-click only\n !event.metaKey &&\n !event.altKey &&\n !event.ctrlKey &&\n !event.shiftKey\n ) {\n // Check if there's a navigation handler (from opensite-blocks or similar)\n if (typeof window !== \"undefined\") {\n const handler = (window as any).__opensiteNavigationHandler;\n if (typeof handler === \"function\") {\n try {\n const handled = handler(\n normalizedHref,\n event.nativeEvent || event\n );\n if (handled !== false) {\n event.preventDefault();\n }\n } catch (error) {\n console.error(\"Error in navigation handler:\", error);\n }\n }\n }\n }\n },\n [onClick, shouldUseRouter, normalizedHref]\n );\n\n return {\n linkType,\n normalizedHref,\n target,\n rel,\n isExternal,\n isInternal,\n shouldUseRouter,\n handleClick,\n };\n}\n","import { cva } from \"class-variance-authority\";\n\n/**\n * Button variants using class-variance-authority (cva).\n *\n * This is extracted to a separate file to avoid importing @radix-ui/react-slot\n * when only the variants are needed (e.g., in Pressable component).\n *\n * ## CSS Variable Reference\n *\n * ### Master Button Variables (apply to all variants)\n * - `--button-font-family` - Font family (default: inherit)\n * - `--button-font-weight` - Font weight (default: 500)\n * - `--button-letter-spacing` - Letter spacing (default: 0)\n * - `--button-line-height` - Line height (default: 1.25)\n * - `--button-text-transform` - Text transform (default: none)\n * - `--button-transition` - Transition timing (default: all 250ms cubic-bezier(0.4, 0, 0.2, 1))\n * - `--button-radius` - Border radius (default: var(--radius, 0.375rem))\n * - `--button-shadow` - Default box shadow (default: none)\n * - `--button-shadow-hover` - Hover box shadow (default: none)\n *\n * ### Size Variables\n * - `--button-height-sm/md/lg` - Button heights\n * - `--button-padding-x-sm/md/lg` - Horizontal padding\n * - `--button-padding-y-sm/md/lg` - Vertical padding\n *\n * ### Per-Variant Variables (replace {variant} with: default, destructive, outline, secondary, ghost, link)\n * - `--button-{variant}-bg` - Background color\n * - `--button-{variant}-fg` - Text/foreground color\n * - `--button-{variant}-border` - Border color\n * - `--button-{variant}-border-width` - Border width\n * - `--button-{variant}-hover-bg` - Hover background color\n * - `--button-{variant}-hover-fg` - Hover text color\n * - `--button-{variant}-hover-border` - Hover border color\n * - `--button-{variant}-shadow` - Box shadow (overrides master)\n * - `--button-{variant}-shadow-hover` - Hover box shadow (overrides master)\n */\n\n// Base styles applied to all buttons - includes master typography, transition, and layout\nconst baseStyles = [\n // Layout\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap shrink-0\",\n // Typography - using CSS variables with sensible defaults\n \"font-[var(--button-font-family,inherit)]\",\n \"font-[var(--button-font-weight,500)]\",\n \"tracking-[var(--button-letter-spacing,0)]\",\n \"leading-[var(--button-line-height,1.25)]\",\n \"[text-transform:var(--button-text-transform,none)]\",\n \"text-sm\",\n // Border radius\n \"rounded-[var(--button-radius,var(--radius,0.375rem))]\",\n // Smooth transition - using [transition:...] to set full shorthand property (not just transition-property)\n \"[transition:var(--button-transition,all_250ms_cubic-bezier(0.4,0,0.2,1))]\",\n // Box shadow (master level) - using [box-shadow:...] for complex multi-value shadows\n \"[box-shadow:var(--button-shadow,none)]\",\n \"hover:[box-shadow:var(--button-shadow-hover,var(--button-shadow,none))]\",\n // Disabled state\n \"disabled:pointer-events-none disabled:opacity-50\",\n // SVG handling\n \"[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0\",\n // Focus styles\n \"outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]\",\n // Invalid state\n \"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n].join(\" \");\n\nexport const buttonVariants = cva(baseStyles, {\n variants: {\n variant: {\n // Default (Primary) variant - full customization\n default: [\n \"bg-[var(--button-default-bg,hsl(var(--primary)))]\",\n \"text-[var(--button-default-fg,hsl(var(--primary-foreground)))]\",\n \"border-[length:var(--button-default-border-width,0px)]\",\n \"border-[color:var(--button-default-border,transparent)]\",\n \"[box-shadow:var(--button-default-shadow,var(--button-shadow,none))]\",\n \"hover:bg-[var(--button-default-hover-bg,hsl(var(--primary)/0.9))]\",\n \"hover:text-[var(--button-default-hover-fg,var(--button-default-fg,hsl(var(--primary-foreground))))]\",\n \"hover:border-[color:var(--button-default-hover-border,var(--button-default-border,transparent))]\",\n \"hover:[box-shadow:var(--button-default-shadow-hover,var(--button-shadow-hover,var(--button-default-shadow,var(--button-shadow,none))))]\",\n ].join(\" \"),\n\n // Destructive variant - full customization\n destructive: [\n \"bg-[var(--button-destructive-bg,hsl(var(--destructive)))]\",\n \"text-[var(--button-destructive-fg,white)]\",\n \"border-[length:var(--button-destructive-border-width,0px)]\",\n \"border-[color:var(--button-destructive-border,transparent)]\",\n \"[box-shadow:var(--button-destructive-shadow,var(--button-shadow,none))]\",\n \"hover:bg-[var(--button-destructive-hover-bg,hsl(var(--destructive)/0.9))]\",\n \"hover:text-[var(--button-destructive-hover-fg,var(--button-destructive-fg,white))]\",\n \"hover:border-[color:var(--button-destructive-hover-border,var(--button-destructive-border,transparent))]\",\n \"hover:[box-shadow:var(--button-destructive-shadow-hover,var(--button-shadow-hover,var(--button-destructive-shadow,var(--button-shadow,none))))]\",\n \"focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40\",\n \"dark:bg-destructive/60\",\n ].join(\" \"),\n\n // Outline variant - full customization with proper border handling\n outline: [\n \"bg-[var(--button-outline-bg,hsl(var(--background)))]\",\n \"text-[var(--button-outline-fg,inherit)]\",\n \"border-[length:var(--button-outline-border-width,1px)]\",\n \"border-[color:var(--button-outline-border,hsl(var(--border)))]\",\n \"[box-shadow:var(--button-outline-shadow,var(--button-shadow,0_1px_2px_0_rgb(0_0_0/0.05)))]\",\n \"hover:bg-[var(--button-outline-hover-bg,hsl(var(--accent)))]\",\n \"hover:text-[var(--button-outline-hover-fg,hsl(var(--accent-foreground)))]\",\n \"hover:border-[color:var(--button-outline-hover-border,var(--button-outline-border,hsl(var(--border))))]\",\n \"hover:[box-shadow:var(--button-outline-shadow-hover,var(--button-shadow-hover,var(--button-outline-shadow,var(--button-shadow,none))))]\",\n \"dark:bg-input/30 dark:border-input dark:hover:bg-input/50\",\n ].join(\" \"),\n\n // Secondary variant - full customization\n secondary: [\n \"bg-[var(--button-secondary-bg,hsl(var(--secondary)))]\",\n \"text-[var(--button-secondary-fg,hsl(var(--secondary-foreground)))]\",\n \"border-[length:var(--button-secondary-border-width,0px)]\",\n \"border-[color:var(--button-secondary-border,transparent)]\",\n \"[box-shadow:var(--button-secondary-shadow,var(--button-shadow,none))]\",\n \"hover:bg-[var(--button-secondary-hover-bg,hsl(var(--secondary)/0.8))]\",\n \"hover:text-[var(--button-secondary-hover-fg,var(--button-secondary-fg,hsl(var(--secondary-foreground))))]\",\n \"hover:border-[color:var(--button-secondary-hover-border,var(--button-secondary-border,transparent))]\",\n \"hover:[box-shadow:var(--button-secondary-shadow-hover,var(--button-shadow-hover,var(--button-secondary-shadow,var(--button-shadow,none))))]\",\n ].join(\" \"),\n\n // Ghost variant - full customization\n ghost: [\n \"bg-[var(--button-ghost-bg,transparent)]\",\n \"text-[var(--button-ghost-fg,inherit)]\",\n \"border-[length:var(--button-ghost-border-width,0px)]\",\n \"border-[color:var(--button-ghost-border,transparent)]\",\n \"[box-shadow:var(--button-ghost-shadow,var(--button-shadow,none))]\",\n \"hover:bg-[var(--button-ghost-hover-bg,hsl(var(--accent)))]\",\n \"hover:text-[var(--button-ghost-hover-fg,hsl(var(--accent-foreground)))]\",\n \"hover:border-[color:var(--button-ghost-hover-border,var(--button-ghost-border,transparent))]\",\n \"hover:[box-shadow:var(--button-ghost-shadow-hover,var(--button-shadow-hover,var(--button-ghost-shadow,var(--button-shadow,none))))]\",\n \"dark:hover:bg-accent/50\",\n ].join(\" \"),\n\n // Link variant - full customization\n link: [\n \"bg-[var(--button-link-bg,transparent)]\",\n \"text-[var(--button-link-fg,hsl(var(--primary)))]\",\n \"border-[length:var(--button-link-border-width,0px)]\",\n \"border-[color:var(--button-link-border,transparent)]\",\n \"[box-shadow:var(--button-link-shadow,none)]\",\n \"hover:bg-[var(--button-link-hover-bg,transparent)]\",\n \"hover:text-[var(--button-link-hover-fg,var(--button-link-fg,hsl(var(--primary))))]\",\n \"hover:[box-shadow:var(--button-link-shadow-hover,none)]\",\n \"underline-offset-4 hover:underline\",\n ].join(\" \"),\n },\n size: {\n default: [\n \"h-[var(--button-height-md,2.25rem)]\",\n \"px-[var(--button-padding-x-md,1rem)]\",\n \"py-[var(--button-padding-y-md,0.5rem)]\",\n \"has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]\",\n ].join(\" \"),\n sm: [\n \"h-[var(--button-height-sm,2rem)]\",\n \"px-[var(--button-padding-x-sm,0.75rem)]\",\n \"py-[var(--button-padding-y-sm,0.25rem)]\",\n \"gap-1.5\",\n \"has-[>svg]:px-[calc(var(--button-padding-x-sm,0.75rem)*0.83)]\",\n ].join(\" \"),\n md: [\n \"h-[var(--button-height-md,2.25rem)]\",\n \"px-[var(--button-padding-x-md,1rem)]\",\n \"py-[var(--button-padding-y-md,0.5rem)]\",\n \"has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]\",\n ].join(\" \"),\n lg: [\n \"h-[var(--button-height-lg,2.5rem)]\",\n \"px-[var(--button-padding-x-lg,1.5rem)]\",\n \"py-[var(--button-padding-y-lg,0.5rem)]\",\n \"has-[>svg]:px-[calc(var(--button-padding-x-lg,1.5rem)*0.67)]\",\n ].join(\" \"),\n icon: \"size-[var(--button-height-md,2.25rem)]\",\n \"icon-sm\": \"size-[var(--button-height-sm,2rem)]\",\n \"icon-lg\": \"size-[var(--button-height-lg,2.5rem)]\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n});\n","\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../utils/cn\";\nimport { useNavigation } from \"../hooks/useNavigation\";\nimport { buttonVariants } from \"./button-variants\";\nimport type { PressableProps, LinkProps, ButtonProps } from \"../types\";\n\n/**\n * Universal link/button component with automatic URL detection and normalization.\n *\n * Features:\n * - Automatic link type detection (internal, external, mailto, tel)\n * - Phone number normalization (various formats to tel:)\n * - Email normalization to mailto:\n * - Internal URL normalization (full URLs to relative paths)\n * - Proper SEO attributes (always uses <a> for links, even when styled as buttons)\n * - ShadCN button variants and sizes\n * - Flexible layout support (icon+label or custom children)\n * - React Router-style internal navigation\n *\n * @example\n * Simple link\n * ```tsx\n * <Pressable href=\"/about\">About Us</Pressable>\n * ```\n *\n * @example\n * Button-styled link with icon\n * ```tsx\n * <Pressable href=\"/quotes\" variant=\"default\" size=\"lg\" asButton>\n * <DynamicIcon name=\"lucide/calculator\" size={20} />\n * Get a Free Quote\n * </Pressable>\n * ```\n *\n * @example\n * External link (automatically gets target=\"_blank\" and rel=\"noopener noreferrer\")\n * ```tsx\n * <Pressable href=\"https://google.com\">Visit Google</Pressable>\n * ```\n *\n * @example\n * Phone link (automatically normalized to tel: format)\n * ```tsx\n * <Pressable href=\"(432) 238-6131\">Call Us</Pressable>\n * // Renders: <a href=\"tel:+14322386131\">\n * ```\n *\n * @example\n * Custom layout with full children control\n * ```tsx\n * <Pressable href=\"/services\" className=\"custom-card\">\n * <div className=\"card-header\">\n * <DynamicIcon name=\"service-icon\" />\n * <h3>Our Services</h3>\n * </div>\n * <p>Learn more about what we offer</p>\n * </Pressable>\n * ```\n *\n * @example\n * Button with onClick (no href)\n * ```tsx\n * <Pressable onClick={() => alert(\"Clicked\")} variant=\"default\" size=\"md\" asButton>\n * Click Me\n * </Pressable>\n * ```\n */\nexport const Pressable = React.forwardRef<\n HTMLAnchorElement | HTMLButtonElement | HTMLSpanElement,\n PressableProps & Partial<LinkProps> & Partial<ButtonProps>\n>(\n (\n {\n children,\n className,\n href,\n onClick,\n variant,\n size,\n asButton = false,\n fallbackComponentType = \"span\",\n componentType,\n \"aria-label\": ariaLabel,\n \"aria-describedby\": ariaDescribedby,\n id,\n ...props\n },\n ref\n ) => {\n const navigation = useNavigation({ href, onClick });\n const {\n normalizedHref,\n target,\n rel,\n linkType,\n isInternal,\n handleClick,\n } = navigation;\n\n // Determine what component to render\n const shouldRenderLink = normalizedHref && linkType !== \"none\";\n const shouldRenderButton = !shouldRenderLink && onClick;\n\n // Force <a> tag for internal links for SEO (even if componentType=\"button\")\n const effectiveComponentType =\n componentType ||\n (shouldRenderLink\n ? \"a\"\n : shouldRenderButton\n ? \"button\"\n : fallbackComponentType);\n\n // Override for SEO: internal links must be <a> tags\n const finalComponentType =\n isInternal && shouldRenderLink ? \"a\" : effectiveComponentType;\n\n // Determine if we should apply button styles\n const shouldApplyButtonStyles = asButton || variant || size;\n\n // Build className\n const combinedClassName = cn(\n shouldApplyButtonStyles && buttonVariants({ variant, size }),\n className\n );\n\n const dataProps = Object.fromEntries(\n Object.entries(props).filter(([key]) => key.startsWith(\"data-\"))\n );\n const buttonDataAttributes = shouldApplyButtonStyles\n ? {\n \"data-slot\": \"button\",\n \"data-variant\": variant ?? \"default\",\n \"data-size\": size ?? \"default\",\n }\n : {};\n\n // Build common props\n const commonProps = {\n className: combinedClassName,\n onClick: handleClick,\n \"aria-label\": ariaLabel,\n \"aria-describedby\": ariaDescribedby,\n id,\n ...dataProps,\n ...buttonDataAttributes,\n };\n\n // Render link\n if (finalComponentType === \"a\" && shouldRenderLink) {\n return (\n <a\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={normalizedHref}\n target={target}\n rel={rel}\n {...commonProps}\n {...(props as LinkProps)}\n >\n {children}\n </a>\n );\n }\n\n // Render button\n if (finalComponentType === \"button\") {\n return (\n <button\n ref={ref as React.Ref<HTMLButtonElement>}\n type={(props as ButtonProps).type || \"button\"}\n {...commonProps}\n {...(props as ButtonProps)}\n >\n {children}\n </button>\n );\n }\n\n // Render fallback (span or div)\n if (finalComponentType === \"div\") {\n return (\n <div ref={ref as React.Ref<HTMLDivElement>} {...commonProps}>\n {children}\n </div>\n );\n }\n\n // Default to span\n return (\n <span ref={ref as React.Ref<HTMLSpanElement>} {...commonProps}>\n {children}\n </span>\n );\n }\n);\n\nPressable.displayName = \"Pressable\";\n"]}
@@ -0,0 +1,70 @@
1
+ import * as React from 'react';
2
+ import { P as PressableProps, L as LinkProps, B as ButtonProps } from '../index-2mStzo0F.cjs';
3
+ export { c as buttonVariants } from '../index-2mStzo0F.cjs';
4
+ import 'class-variance-authority';
5
+ import 'class-variance-authority/types';
6
+
7
+ /**
8
+ * Universal link/button component with automatic URL detection and normalization.
9
+ *
10
+ * Features:
11
+ * - Automatic link type detection (internal, external, mailto, tel)
12
+ * - Phone number normalization (various formats to tel:)
13
+ * - Email normalization to mailto:
14
+ * - Internal URL normalization (full URLs to relative paths)
15
+ * - Proper SEO attributes (always uses <a> for links, even when styled as buttons)
16
+ * - ShadCN button variants and sizes
17
+ * - Flexible layout support (icon+label or custom children)
18
+ * - React Router-style internal navigation
19
+ *
20
+ * @example
21
+ * Simple link
22
+ * ```tsx
23
+ * <Pressable href="/about">About Us</Pressable>
24
+ * ```
25
+ *
26
+ * @example
27
+ * Button-styled link with icon
28
+ * ```tsx
29
+ * <Pressable href="/quotes" variant="default" size="lg" asButton>
30
+ * <DynamicIcon name="lucide/calculator" size={20} />
31
+ * Get a Free Quote
32
+ * </Pressable>
33
+ * ```
34
+ *
35
+ * @example
36
+ * External link (automatically gets target="_blank" and rel="noopener noreferrer")
37
+ * ```tsx
38
+ * <Pressable href="https://google.com">Visit Google</Pressable>
39
+ * ```
40
+ *
41
+ * @example
42
+ * Phone link (automatically normalized to tel: format)
43
+ * ```tsx
44
+ * <Pressable href="(432) 238-6131">Call Us</Pressable>
45
+ * // Renders: <a href="tel:+14322386131">
46
+ * ```
47
+ *
48
+ * @example
49
+ * Custom layout with full children control
50
+ * ```tsx
51
+ * <Pressable href="/services" className="custom-card">
52
+ * <div className="card-header">
53
+ * <DynamicIcon name="service-icon" />
54
+ * <h3>Our Services</h3>
55
+ * </div>
56
+ * <p>Learn more about what we offer</p>
57
+ * </Pressable>
58
+ * ```
59
+ *
60
+ * @example
61
+ * Button with onClick (no href)
62
+ * ```tsx
63
+ * <Pressable onClick={() => alert("Clicked")} variant="default" size="md" asButton>
64
+ * Click Me
65
+ * </Pressable>
66
+ * ```
67
+ */
68
+ declare const Pressable: React.ForwardRefExoticComponent<PressableProps & Partial<LinkProps> & Partial<ButtonProps> & React.RefAttributes<HTMLAnchorElement | HTMLButtonElement | HTMLSpanElement>>;
69
+
70
+ export { ButtonProps, LinkProps, Pressable, PressableProps };