@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.
package/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, OpenSite AI. All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,417 @@
1
+ # @page-speed/pressable
2
+
3
+ Performance-optimized universal link/button component with automatic URL detection and normalization for the DashTrack ecosystem.
4
+
5
+ ## Features
6
+
7
+ - 🔗 **Universal Component**: Automatically renders `<a>`, `<button>`, or fallback elements based on props
8
+ - 🌐 **Smart URL Detection**: Automatically detects and normalizes internal, external, mailto, and tel links
9
+ - 📱 **Phone Number Normalization**: Converts various phone formats to standard `tel:` format
10
+ - ✉️ **Email Normalization**: Automatically adds `mailto:` prefix to email addresses
11
+ - 🎨 **ShadCN Button Variants**: Full integration with ShadCN button styles and variants
12
+ - ♿ **Accessibility First**: Proper ARIA attributes, keyboard navigation, and screen reader support
13
+ - 🎯 **SEO Optimized**: Internal links always render as `<a>` tags for proper SEO
14
+ - 🌲 **Tree-Shakable**: Granular exports for minimal bundle size
15
+ - 🚀 **Zero Runtime Overhead**: Efficient memoization and minimal re-renders
16
+ - 🔒 **Type Safe**: Full TypeScript support with comprehensive types
17
+
18
+ ## Installation
19
+
20
+ \`\`\`bash
21
+ # Using pnpm (recommended)
22
+ pnpm add @page-speed/pressable
23
+
24
+ # Using npm
25
+ npm install @page-speed/pressable
26
+
27
+ # Using yarn
28
+ yarn add @page-speed/pressable
29
+ \`\`\`
30
+
31
+ ## Peer Dependencies
32
+
33
+ \`\`\`json
34
+ {
35
+ "react": ">=17.0.0",
36
+ "react-dom": ">=17.0.0"
37
+ }
38
+ \`\`\`
39
+
40
+ ## Basic Usage
41
+
42
+ ### Simple Link
43
+
44
+ \`\`\`tsx
45
+ import { Pressable } from "@page-speed/pressable";
46
+
47
+ function Navigation() {
48
+ return <Pressable href="/about">About Us</Pressable>;
49
+ }
50
+ \`\`\`
51
+
52
+ ### External Link
53
+
54
+ Automatically gets \`target="_blank"\` and \`rel="noopener noreferrer"\`:
55
+
56
+ \`\`\`tsx
57
+ <Pressable href="https://google.com">Visit Google</Pressable>
58
+ \`\`\`
59
+
60
+ ### Button-Styled Link
61
+
62
+ \`\`\`tsx
63
+ <Pressable href="/contact" asButton variant="default" size="lg">
64
+ Contact Us
65
+ </Pressable>
66
+ \`\`\`
67
+
68
+ ### Phone Link
69
+
70
+ Automatically normalized to \`tel:\` format:
71
+
72
+ \`\`\`tsx
73
+ <Pressable href="(432) 238-6131">Call Us</Pressable>
74
+ // Renders: <a href="tel:+14322386131">Call Us</a>
75
+ \`\`\`
76
+
77
+ ### Email Link
78
+
79
+ Automatically normalized to \`mailto:\` format:
80
+
81
+ \`\`\`tsx
82
+ <Pressable href="info@example.com">Email Us</Pressable>
83
+ // Renders: <a href="mailto:info@example.com">Email Us</a>
84
+ \`\`\`
85
+
86
+ ### Button with onClick
87
+
88
+ \`\`\`tsx
89
+ <Pressable onClick={() => alert("Clicked")} asButton variant="default">
90
+ Click Me
91
+ </Pressable>
92
+ \`\`\`
93
+
94
+ ## Advanced Usage
95
+
96
+ ### Button Variants
97
+
98
+ Supports all ShadCN button variants:
99
+
100
+ \`\`\`tsx
101
+ // Default variant
102
+ <Pressable href="/primary" variant="default">Primary</Pressable>
103
+
104
+ // Outline variant
105
+ <Pressable href="/outline" variant="outline">Outline</Pressable>
106
+
107
+ // Secondary variant
108
+ <Pressable href="/secondary" variant="secondary">Secondary</Pressable>
109
+
110
+ // Ghost variant
111
+ <Pressable href="/ghost" variant="ghost">Ghost</Pressable>
112
+
113
+ // Link variant
114
+ <Pressable href="/link" variant="link">Link Style</Pressable>
115
+
116
+ // Destructive variant
117
+ <Pressable href="/delete" variant="destructive">Delete</Pressable>
118
+ \`\`\`
119
+
120
+ ### Button Sizes
121
+
122
+ \`\`\`tsx
123
+ <Pressable href="/small" size="sm">Small</Pressable>
124
+ <Pressable href="/default" size="default">Default</Pressable>
125
+ <Pressable href="/medium" size="md">Medium</Pressable>
126
+ <Pressable href="/large" size="lg">Large</Pressable>
127
+
128
+ // Icon sizes
129
+ <Pressable href="/icon" size="icon">
130
+ <Icon />
131
+ </Pressable>
132
+ <Pressable href="/icon-sm" size="icon-sm">
133
+ <Icon />
134
+ </Pressable>
135
+ <Pressable href="/icon-lg" size="icon-lg">
136
+ <Icon />
137
+ </Pressable>
138
+ \`\`\`
139
+
140
+ ### Custom Layouts
141
+
142
+ Full control over children:
143
+
144
+ \`\`\`tsx
145
+ <Pressable href="/services" className="flex flex-col gap-4 p-6 rounded-lg border">
146
+ <div className="flex items-center gap-2">
147
+ <ServiceIcon />
148
+ <h3>Our Services</h3>
149
+ </div>
150
+ <p>Learn more about what we offer</p>
151
+ </Pressable>
152
+ \`\`\`
153
+
154
+ ### Accessibility
155
+
156
+ \`\`\`tsx
157
+ <Pressable
158
+ href="/important"
159
+ aria-label="Important action"
160
+ aria-describedby="description"
161
+ id="important-link"
162
+ >
163
+ <span id="description">Click here for important information</span>
164
+ </Pressable>
165
+ \`\`\`
166
+
167
+ ### Refs
168
+
169
+ \`\`\`tsx
170
+ const linkRef = useRef<HTMLAnchorElement>(null);
171
+
172
+ <Pressable ref={linkRef} href="/ref-example">
173
+ Link with Ref
174
+ </Pressable>
175
+ \`\`\`
176
+
177
+ ## API Reference
178
+
179
+ ### Props
180
+
181
+ #### Core Props
182
+
183
+ | Prop | Type | Default | Description |
184
+ |------|------|---------|-------------|
185
+ | \`children\` | \`ReactNode\` | - | Content inside the component |
186
+ | \`href\` | \`string\` | - | URL to navigate to (supports internal, external, mailto, tel) |
187
+ | \`onClick\` | \`MouseEventHandler\` | - | Click handler function |
188
+ | \`className\` | \`string\` | - | Additional CSS classes |
189
+ | \`asButton\` | \`boolean\` | \`false\` | Apply button styles even when rendering as \`<a>\` |
190
+
191
+ #### Button Styling
192
+
193
+ | Prop | Type | Default | Description |
194
+ |------|------|---------|-------------|
195
+ | \`variant\` | \`'default' \| 'destructive' \| 'outline' \| 'secondary' \| 'ghost' \| 'link'\` | - | Button variant style |
196
+ | \`size\` | \`'default' \| 'sm' \| 'md' \| 'lg' \| 'icon' \| 'icon-sm' \| 'icon-lg'\` | - | Button size |
197
+
198
+ #### Component Type
199
+
200
+ | Prop | Type | Default | Description |
201
+ |------|------|---------|-------------|
202
+ | \`componentType\` | \`'a' \| 'button' \| 'span' \| 'div'\` | auto | Explicit component type to render |
203
+ | \`fallbackComponentType\` | \`'span' \| 'div' \| 'button'\` | \`'span'\` | Component to render when no href/onClick |
204
+
205
+ #### Accessibility
206
+
207
+ | Prop | Type | Default | Description |
208
+ |------|------|---------|-------------|
209
+ | \`aria-label\` | \`string\` | - | ARIA label for accessibility |
210
+ | \`aria-describedby\` | \`string\` | - | ARIA describedby reference |
211
+ | \`id\` | \`string\` | - | Element ID |
212
+
213
+ #### Data Attributes
214
+
215
+ Any \`data-*\` attributes are automatically forwarded to the rendered element.
216
+
217
+ ## URL Detection & Normalization
218
+
219
+ ### Internal Links
220
+
221
+ Full URLs matching the current origin are automatically converted to relative paths:
222
+
223
+ \`\`\`tsx
224
+ // On https://example.com
225
+ <Pressable href="https://example.com/about">About</Pressable>
226
+ // Renders: <a href="/about">About</a>
227
+ \`\`\`
228
+
229
+ ### Phone Number Formats
230
+
231
+ Supports various phone number formats:
232
+
233
+ \`\`\`tsx
234
+ <Pressable href="(432) 238-6131" /> // → tel:+14322386131
235
+ <Pressable href="512-232-2212" /> // → tel:+5122322212
236
+ <Pressable href="512.232.2212" /> // → tel:+5122322212
237
+ <Pressable href="+1 432 238 6131" /> // → tel:+14322386131
238
+ <Pressable href="512-232-2212x123" /> // → tel:+5122322212;ext=123
239
+ \`\`\`
240
+
241
+ ### Email Detection
242
+
243
+ Automatically detects email addresses:
244
+
245
+ \`\`\`tsx
246
+ <Pressable href="hello@example.com" /> // → mailto:hello@example.com
247
+ <Pressable href="mailto:test@ex.com" /> // → mailto:test@ex.com (unchanged)
248
+ \`\`\`
249
+
250
+ ## Hooks
251
+
252
+ ### useNavigation
253
+
254
+ Low-level hook for custom navigation logic:
255
+
256
+ \`\`\`tsx
257
+ import { useNavigation } from "@page-speed/pressable/hooks";
258
+
259
+ function CustomLink({ href }) {
260
+ const {
261
+ linkType,
262
+ normalizedHref,
263
+ target,
264
+ rel,
265
+ isInternal,
266
+ isExternal,
267
+ handleClick,
268
+ } = useNavigation({ href });
269
+
270
+ return (
271
+ <a href={normalizedHref} target={target} rel={rel} onClick={handleClick}>
272
+ {href}
273
+ </a>
274
+ );
275
+ }
276
+ \`\`\`
277
+
278
+ #### useNavigation Return Values
279
+
280
+ | Property | Type | Description |
281
+ |----------|------|-------------|
282
+ | \`linkType\` | \`'internal' \| 'external' \| 'mailto' \| 'tel' \| 'none' \| 'unknown'\` | Detected link type |
283
+ | \`normalizedHref\` | \`string \| undefined\` | Normalized URL |
284
+ | \`target\` | \`'_blank' \| '_self' \| undefined\` | Link target attribute |
285
+ | \`rel\` | \`string \| undefined\` | Link rel attribute |
286
+ | \`isInternal\` | \`boolean\` | Whether link is internal |
287
+ | \`isExternal\` | \`boolean\` | Whether link is external |
288
+ | \`shouldUseRouter\` | \`boolean\` | Whether to use client-side routing |
289
+ | \`handleClick\` | \`MouseEventHandler\` | Click handler function |
290
+
291
+ ## Utilities
292
+
293
+ ### cn
294
+
295
+ Utility for merging Tailwind classes:
296
+
297
+ \`\`\`tsx
298
+ import { cn } from "@page-speed/pressable/utils";
299
+
300
+ function CustomButton() {
301
+ return (
302
+ <Pressable
303
+ href="/test"
304
+ className={cn(
305
+ "base-class",
306
+ isActive && "active-class",
307
+ { "conditional": someCondition }
308
+ )}
309
+ >
310
+ Custom Button
311
+ </Pressable>
312
+ );
313
+ }
314
+ \`\`\`
315
+
316
+ ## Integration with opensite-blocks
317
+
318
+ The Pressable component integrates seamlessly with the opensite-blocks navigation system:
319
+
320
+ \`\`\`tsx
321
+ // Set up navigation handler (typically done in opensite-blocks)
322
+ window.__opensiteNavigationHandler = (href, event) => {
323
+ // Custom navigation logic (e.g., React Router)
324
+ navigate(href);
325
+ return true; // Indicates navigation was handled
326
+ };
327
+
328
+ // Pressable automatically uses the handler for internal links
329
+ <Pressable href="/about">About</Pressable>
330
+ \`\`\`
331
+
332
+ ## CSS Variables
333
+
334
+ The component supports extensive CSS variable customization for button styles. See the [button-variants.ts](./src/core/button-variants.ts) file for the complete list of CSS variables.
335
+
336
+ ### Master Variables
337
+
338
+ \`\`\`css
339
+ :root {
340
+ --button-font-family: inherit;
341
+ --button-font-weight: 500;
342
+ --button-letter-spacing: 0;
343
+ --button-line-height: 1.25;
344
+ --button-text-transform: none;
345
+ --button-transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
346
+ --button-radius: 0.375rem;
347
+ --button-shadow: none;
348
+ --button-shadow-hover: none;
349
+ }
350
+ \`\`\`
351
+
352
+ ### Per-Variant Variables
353
+
354
+ \`\`\`css
355
+ :root {
356
+ /* Default variant */
357
+ --button-default-bg: hsl(var(--primary));
358
+ --button-default-fg: hsl(var(--primary-foreground));
359
+ --button-default-hover-bg: hsl(var(--primary) / 0.9);
360
+
361
+ /* Outline variant */
362
+ --button-outline-bg: hsl(var(--background));
363
+ --button-outline-border: hsl(var(--border));
364
+ --button-outline-border-width: 1px;
365
+
366
+ /* ... and more */
367
+ }
368
+ \`\`\`
369
+
370
+ ## Tree-Shaking
371
+
372
+ The package is fully tree-shakable. Import only what you need:
373
+
374
+ \`\`\`tsx
375
+ // Import specific components
376
+ import { Pressable } from "@page-speed/pressable/core";
377
+ import { useNavigation } from "@page-speed/pressable/hooks";
378
+ import { cn } from "@page-speed/pressable/utils";
379
+
380
+ // Or use granular imports
381
+ import { Pressable } from "@page-speed/pressable/core/Pressable";
382
+ import { buttonVariants } from "@page-speed/pressable/core/button-variants";
383
+ \`\`\`
384
+
385
+ ## Performance
386
+
387
+ - **Bundle Size**: ~8KB gzipped (including all dependencies)
388
+ - **Tree-Shaking**: Unused code is automatically eliminated
389
+ - **Memoization**: All computed values are memoized with React.useMemo
390
+ - **Zero Runtime Overhead**: Efficient URL detection and normalization
391
+ - **SSR Compatible**: Works seamlessly with server-side rendering
392
+
393
+ ## Browser Support
394
+
395
+ - Modern browsers (Chrome, Firefox, Safari, Edge)
396
+ - React 17+
397
+ - Server-side rendering (SSR)
398
+ - Static site generation (SSG)
399
+
400
+ ## License
401
+
402
+ MIT
403
+
404
+ ## Contributing
405
+
406
+ Contributions are welcome! Please follow the [DashTrack ecosystem guidelines](./docs/ECOSYSTEM_GUIDELINES.md).
407
+
408
+ ## Related Packages
409
+
410
+ - [@page-speed/img](https://www.npmjs.com/package/@page-speed/img) - Performance-optimized image component
411
+ - [@page-speed/markdown-to-jsx](https://www.npmjs.com/package/@page-speed/markdown-to-jsx) - Markdown renderer with Pressable integration
412
+ - [@opensite/blocks](https://www.npmjs.com/package/@opensite/blocks) - Chai design payload renderer
413
+
414
+ ## Support
415
+
416
+ - [GitHub Issues](https://github.com/opensite-ai/pressable/issues)
417
+ - [DashTrack Documentation](https://docs.dashtrack.com)