@page-speed/pressable
v0.0.7
Published
Performance-optimized universal link/button component with automatic URL detection and normalization for DashTrack ecosystem
Readme
@page-speed/pressable
Performance-optimized universal link/button component with automatic URL detection and normalization for the OpenSite Semantic Site Builder ecosystem. Provides tree-shakable, performance-optimized components with abstract styling support

Features
- 🔗 Universal Component: Automatically renders
<a>,<button>, or fallback elements based on props - 🌐 Smart URL Detection: Automatically detects and normalizes internal, external, mailto, and tel links
- 📱 Phone Number Normalization: Converts various phone formats to standard
tel:format - ✉️ Email Normalization: Automatically adds
mailto:prefix to email addresses - 🎨 ShadCN Button Variants: Full integration with ShadCN button styles and variants
- ♿ Accessibility First: Proper ARIA attributes, keyboard navigation, and screen reader support
- 🎯 SEO Optimized: Internal links always render as
<a>tags for proper SEO - 🌲 Tree-Shakable: Granular exports for minimal bundle size
- 🚀 Zero Runtime Overhead: Efficient memoization and minimal re-renders
- 🔒 Type Safe: Full TypeScript support with comprehensive types
Installation
```bash
Using pnpm (recommended)
pnpm add @page-speed/pressable
Using npm
npm install @page-speed/pressable
Using yarn
yarn add @page-speed/pressable ```
Peer Dependencies
```json { "react": ">=17.0.0", "react-dom": ">=17.0.0" } ```
Setup Requirements
1. Tailwind CSS Configuration
CRITICAL: Add @page-speed/pressable to your Tailwind content paths so button styles are included:
```ts // tailwind.config.ts import type { Config } from "tailwindcss";
const config: Config = { content: [ "./app//*.{js,ts,jsx,tsx,mdx}", "./components//*.{js,ts,jsx,tsx,mdx}", // Add one of these lines to scan Pressable's button-variant classes:
// For standard npm/yarn installations:
"./node_modules/@page-speed/pressable/dist/**/*.{js,cjs}",
// For pnpm monorepos (use both if unsure):
"./node_modules/.pnpm/@page-speed+pressable*/node_modules/@page-speed/pressable/**/*.{js,jsx,ts,tsx}",], // ...rest of config }; ```
Without this, button variants won't have styles applied because Tailwind will purge the classes.
2. Router Setup (For Navigation)
Wrap your app with `RouterProvider` from `@page-speed/router` to enable internal navigation.
For Next.js App Router (requires client component wrapper):
```tsx // components/providers/RouterWrapper.tsx "use client";
import { RouterProvider } from "@page-speed/router"; import { ReactNode } from "react";
export function RouterWrapper({ children }: { children: ReactNode }) { return {children}; } ```
```tsx // app/layout.tsx import { RouterWrapper } from "@/components/providers/RouterWrapper";
export default function RootLayout({ children }) { return ( {children} ); } ```
For standard React apps (Create React App, Vite, etc.):
```tsx // App.tsx import { RouterProvider } from "@page-speed/router";
function App() { return ( {/* your app */} ); } ```
Install `@page-speed/router` directly for better type support:
```bash pnpm add @page-speed/router ```
Basic Usage
Simple Link
```tsx import { Pressable } from "@page-speed/pressable";
function Navigation() { return About Us; } ```
External Link
Automatically gets `target="_blank"` and `rel="noopener noreferrer"`:
```tsx Visit Google ```
Button-Styled Link
```tsx Contact Us ```
Phone Link
Automatically normalized to `tel:` format:
```tsx Call Us // Renders: Call Us ```
Email Link
Automatically normalized to `mailto:` format:
```tsx Email Us // Renders: Email Us ```
Button with onClick
```tsx <Pressable onClick={() => alert("Clicked")} asButton variant="default"> Click Me ```
Advanced Usage
Button Variants
Supports all ShadCN button variants:
```tsx // Default variant Primary
// Outline variant Outline
// Secondary variant Secondary
// Ghost variant Ghost
// Link variant Link Style
// Destructive variant Delete ```
Button Sizes
```tsx Small Default Medium Large
// Icon sizes ```
Custom Layouts
Full control over children:
```tsx
Accessibility
```tsx <Pressable href="/important" aria-label="Important action" aria-describedby="description" id="important-link"
Click here for important information ```
Refs
```tsx const linkRef = useRef(null);
API Reference
Props
Core Props
| Prop | Type | Default | Description | |------|------|---------|-------------| | `children` | `ReactNode` | - | Content inside the component | | `href` | `string` | - | URL to navigate to (supports internal, external, mailto, tel) | | `onClick` | `MouseEventHandler` | - | Click handler function | | `className` | `string` | - | Additional CSS classes | | `asButton` | `boolean` | `false` | Apply button styles even when rendering as `` |
Button Styling
| Prop | Type | Default | Description | |------|------|---------|-------------| | `variant` | `'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'` | - | Button variant style | | `size` | `'default' | 'sm' | 'md' | 'lg' | 'icon' | 'icon-sm' | 'icon-lg'` | - | Button size |
Component Type
| Prop | Type | Default | Description | |------|------|---------|-------------| | `componentType` | `'a' | 'button' | 'span' | 'div'` | auto | Explicit component type to render | | `fallbackComponentType` | `'span' | 'div' | 'button'` | `'span'` | Component to render when no href/onClick |
Accessibility
| Prop | Type | Default | Description | |------|------|---------|-------------| | `aria-label` | `string` | - | ARIA label for accessibility | | `aria-describedby` | `string` | - | ARIA describedby reference | | `id` | `string` | - | Element ID |
Data Attributes
Any `data-*` attributes are automatically forwarded to the rendered element.
URL Detection & Normalization
Internal Links
Full URLs matching the current origin are automatically converted to relative paths:
```tsx // On https://example.com About // Renders: About ```
Phone Number Formats
Supports various phone number formats:
```tsx // → tel:+14322386131 // → tel:+5122322212 // → tel:+5122322212 // → tel:+14322386131 // → tel:+5122322212;ext=123 ```
Email Detection
Automatically detects email addresses:
```tsx // → mailto:[email protected] // → mailto:[email protected] (unchanged) ```
Hooks
useNavigation
Low-level hook for custom navigation logic:
```tsx import { useNavigation } from "@page-speed/pressable/hooks";
function CustomLink({ href }) { const { linkType, normalizedHref, target, rel, isInternal, isExternal, handleClick, } = useNavigation({ href });
return ( {href} ); } ```
useNavigation Return Values
| Property | Type | Description | |----------|------|-------------| | `linkType` | `'internal' | 'external' | 'mailto' | 'tel' | 'none' | 'unknown'` | Detected link type | | `normalizedHref` | `string | undefined` | Normalized URL | | `target` | `'_blank' | '_self' | undefined` | Link target attribute | | `rel` | `string | undefined` | Link rel attribute | | `isInternal` | `boolean` | Whether link is internal | | `isExternal` | `boolean` | Whether link is external | | `shouldUseRouter` | `boolean` | Whether to use client-side routing | | `handleClick` | `MouseEventHandler` | Click handler function |
Utilities
cn
Utility for merging Tailwind classes:
```tsx import { cn } from "@page-speed/pressable/utils";
function CustomButton() { return ( <Pressable href="/test" className={cn( "base-class", isActive && "active-class", { "conditional": someCondition } )} > Custom Button ); } ```
Integration with opensite-blocks
The Pressable component integrates seamlessly with the opensite-blocks navigation system:
```tsx // Set up navigation handler (typically done in opensite-blocks) window.__opensiteNavigationHandler = (href, event) => { // Custom navigation logic (e.g., React Router) navigate(href); return true; // Indicates navigation was handled };
// Pressable automatically uses the handler for internal links About ```
CSS Variables
The component supports extensive CSS variable customization for button styles. See the button-variants.ts file for the complete list of CSS variables.
Master Variables
```css :root { --button-font-family: inherit; --button-font-weight: 500; --button-letter-spacing: 0; --button-line-height: 1.25; --button-text-transform: none; --button-transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1); --button-radius: 0.375rem; --button-shadow: none; --button-shadow-hover: none; } ```
Per-Variant Variables
```css :root { /* Default variant */ --button-default-bg: hsl(var(--primary)); --button-default-fg: hsl(var(--primary-foreground)); --button-default-hover-bg: hsl(var(--primary) / 0.9);
/* Outline variant */ --button-outline-bg: hsl(var(--background)); --button-outline-border: hsl(var(--border)); --button-outline-border-width: 1px;
/* ... and more */ } ```
Tree-Shaking
The package is fully tree-shakable. Import only what you need:
```tsx // Import specific components import { Pressable } from "@page-speed/pressable/core"; import { useNavigation } from "@page-speed/pressable/hooks"; import { cn } from "@page-speed/pressable/utils";
// Or use granular imports import { Pressable } from "@page-speed/pressable/core/Pressable"; import { buttonVariants } from "@page-speed/pressable/core/button-variants"; ```
Performance
- Bundle Size: ~8KB gzipped (including all dependencies)
- Tree-Shaking: Unused code is automatically eliminated
- Memoization: All computed values are memoized with React.useMemo
- Zero Runtime Overhead: Efficient URL detection and normalization
- SSR Compatible: Works seamlessly with server-side rendering
Browser Support
- Modern browsers (Chrome, Firefox, Safari, Edge)
- React 17+
- Server-side rendering (SSR)
- Static site generation (SSG)
License
MIT
Contributing
Contributions are welcome! Please follow the DashTrack ecosystem guidelines.
Related Packages
- @page-speed/img - Performance-optimized image component
- @page-speed/markdown-to-jsx - Markdown renderer with Pressable integration
- @opensite/blocks - Chai design payload renderer
