Skip to content

opensite-ai/pressable

Repository files navigation

@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

Page Speed Pressable Component


npm version npm downloads TypeScript

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

Our Services

Learn more about what we offer

\`\`\`

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);

Link with Ref \`\`\`

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:hello@example.com // → mailto:test@ex.com (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

Support

About

Performance-optimized universal link/button component with automatic URL detection and normalization for the OpenSite Semantic Site Builder ecosystem.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors