npm package: @newtonedev/components
Description: React + React Native Web component library for Newtone
Latest version: 0.1.22

# @newtone/components

Production-ready React component library built on the Newtone color engine. Cross-platform support for web and React Native using `react-native-web`.

## Features

- 🎨 **Powered by Newtone** — Accessible semantic color systems with OKLCH color space
- 🌓 **Light/Dark Mode** — Automatic theme switching with Context API
- 🎯 **Four Themes** — Neutral, Primary, Secondary, Strong with elevation support
- 📱 **Cross-Platform** — Single codebase for web and React Native
- 🔧 **Runtime Tokens** — Dynamic color computation with memoization
- ♿️ **Accessible** — WCAG contrast ratios built-in
- 📦 **Zero Config** — Sensible defaults, fully customizable
- 🎭 **TypeScript** — Full type safety

## Installation

```bash
npm install @newtone/components react react-native
```

### Additional Setup

**For Web Projects:**
```bash
npm install react-native-web react-dom
```

**For React Native Projects:**
No additional dependencies required.

## Quick Start

### Web Application

```tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { NewtoneProvider, Button } from '@newtone/components';

function App() {
  return (
    <NewtoneProvider initialMode="light" initialTheme="neutral">
      <div style={{ padding: 20 }}>
        <Button variant="primary" onPress={() => console.log('Clicked!')}>
          Click me
        </Button>
      </div>
    </NewtoneProvider>
  );
}

ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
```

### React Native Application

```tsx
import React from 'react';
import { SafeAreaView } from 'react-native';
import { NewtoneProvider, Button } from '@newtone/components';

export default function App() {
  return (
    <NewtoneProvider initialMode="light" initialTheme="neutral">
      <SafeAreaView style={{ flex: 1, padding: 20 }}>
        <Button variant="primary" onPress={() => console.log('Clicked!')}>
          Click me
        </Button>
      </SafeAreaView>
    </NewtoneProvider>
  );
}
```

## Components

### Button

A cross-platform button component with multiple variants and sizes.

#### Props

```typescript
interface ButtonProps {
  children: React.ReactNode;
  variant?: 'primary' | 'secondary' | 'ghost' | 'outline'; // default: 'primary'
  size?: 'sm' | 'md' | 'lg'; // default: 'md'
  disabled?: boolean; // default: false
  onPress?: () => void;
  style?: ViewStyle | ViewStyle[];
  textStyle?: TextStyle | TextStyle[];
  // ... all other Pressable props
}
```

#### Variants

- **Primary** — Filled button with interactive color background
- **Secondary** — Filled button with elevated background
- **Ghost** — Transparent button with colored text
- **Outline** — Border-only button with colored text

#### Sizes

- **Small (`sm`)** — Compact button for tight spaces (6px vertical padding)
- **Medium (`md`)** — Default size for most use cases (10px vertical padding)
- **Large (`lg`)** — Prominent button for primary actions (14px vertical padding)

#### Examples

```tsx
import { Button } from '@newtone/components';

// Primary button (default)
<Button onPress={() => {}}>Submit</Button>

// Secondary button
<Button variant="secondary" onPress={() => {}}>Cancel</Button>

// Large ghost button
<Button variant="ghost" size="lg" onPress={() => {}}>Learn More</Button>

// Disabled outline button
<Button variant="outline" disabled>Unavailable</Button>

// Custom styles
<Button
  variant="primary"
  style={{ marginTop: 20, width: 200 }}
  textStyle={{ fontWeight: 'bold' }}
  onPress={() => {}}
>
  Custom Styled
</Button>
```

## Theme System

### NewtoneProvider

Wrap your application root with `NewtoneProvider` to enable theming.

```tsx
import { NewtoneProvider } from '@newtone/components';

<NewtoneProvider
  config={customConfig} // optional, uses defaults if omitted
  initialMode="light" // 'light' | 'dark', default: 'light'
  initialTheme="neutral" // 'neutral' | 'primary' | 'secondary' | 'strong', default: 'neutral'
>
  <App />
</NewtoneProvider>
```

#### Default Configuration

The library ships with a sensible default configuration:

- **5 Palettes**: Neutral (gray), Accent (blue), Success (green), Warning (yellow), Error (red)
- **4 Themes**: Each maps to a palette with light/dark normalized values
- **3 Elevations**: Surface levels with subtle color shifts (-0.02, 0, +0.04)
- **Dynamic Range**: Lightest = 1 (white), Darkest = 1 (black)

### useNewtoneTheme Hook

Access and modify theme state from any component within the provider.

```tsx
import { useNewtoneTheme } from '@newtone/components';

function ThemeToggle() {
  const { mode, theme, setMode, setTheme } = useNewtoneTheme();

  return (
    <div>
      <button onClick={() => setMode(mode === 'light' ? 'dark' : 'light')}>
        Toggle {mode} mode
      </button>
      <button onClick={() => setTheme('primary')}>
        Switch to primary theme
      </button>
    </div>
  );
}
```

### useTokens Hook

Compute design tokens for the current theme context at a specific elevation.

```tsx
import { useTokens } from '@newtone/components';

function CustomComponent() {
  const tokens = useTokens(1); // Elevation level: 0, 1, or 2

  return (
    <div style={{
      backgroundColor: tokens.background.srgb,
      color: tokens.textPrimary.srgb
    }}>
      Custom component using tokens
    </div>
  );
}
```

#### Available Tokens

```typescript
interface ResolvedTokens {
  background: ColorResult;           // Base surface color
  backgroundElevated: ColorResult;   // Elevated surface (+1 level)
  backgroundSunken: ColorResult;     // Sunken surface (-1 level)
  textPrimary: ColorResult;          // Primary text (WCAG 4.5:1)
  textSecondary: ColorResult;        // Secondary text (WCAG 3.0:1)
  interactive: ColorResult;          // Interactive elements (accent palette)
  interactiveHover: ColorResult;     // Hover state
  interactiveActive: ColorResult;    // Active/pressed state
  border: ColorResult;               // Subtle borders
}
```

Each `ColorResult` contains:
```typescript
{
  srgb: { r: number, g: number, b: number }, // 0-1 range
  oklch: { L: number, C: number, h: number }
}
```

**Helper for hex conversion:**
```tsx
import { srgbToHex } from 'newtone';

const hexColor = srgbToHex(tokens.background.srgb); // "#ffffff"
```

## Customization

### Custom Color System

Override the default palette configuration:

```tsx
import { NewtoneProvider } from '@newtone/components';
import type { ColorSystemConfig } from 'newtone';

const customColorSystem: ColorSystemConfig = {
  dynamicRange: {
    lightest: 1,    // 0-1, where 1 = pure white
    darkest: 1,     // 0-1, where 1 = pure black
  },
  palettes: [
    {
      hue: 220,           // OKLCH hue (0-360)
      saturation: 80,     // 0-100, relative to max in-gamut chroma
      desaturation: {
        direction: 'light',  // 'light' | 'dark'
        strength: 'medium'   // 'low' | 'medium' | 'hard'
      },
      paletteHueGrading: {
        hue: 200,
        strength: 'low',
        direction: 'light'
      }
    },
    // ... more palettes
  ],
};

const customConfig = {
  colorSystem: customColorSystem,
  themes: {
    neutral: { paletteIndex: 0, lightModeNv: 0.97, darkModeNv: 0.08 },
    primary: { paletteIndex: 1, lightModeNv: 0.50, darkModeNv: 0.45 },
    secondary: { paletteIndex: 2, lightModeNv: 0.60, darkModeNv: 0.55 },
    strong: { paletteIndex: 3, lightModeNv: 0.30, darkModeNv: 0.70 },
  },
  elevation: {
    offsets: [-0.02, 0, 0.04] as const,
  },
};

<NewtoneProvider config={customConfig}>
  <App />
</NewtoneProvider>
```

### Custom Button Styles

Override button appearance with `style` and `textStyle` props:

```tsx
<Button
  variant="primary"
  style={[
    { borderRadius: 20, paddingHorizontal: 30 },
    isHighlighted && { transform: [{ scale: 1.05 }] }
  ]}
  textStyle={{
    fontFamily: 'CustomFont',
    letterSpacing: 1
  }}
>
  Custom Button
</Button>
```

## Architecture

### Token Computation Strategy

**Phase 1 (Current): Runtime Computation**

Tokens are computed on-demand using the Newtone color engine:

```
Config → getColor() → OKLCH → Gamut Mapping → sRGB → ColorResult
```

- ✅ Maximum flexibility (change config, colors update instantly)
- ✅ Smallest bundle (no pre-generated tokens)
- ✅ Always accurate (no stale tokens)
- ⚠️  Requires engine in bundle (~20KB minified)
- ⚠️  Runtime computation cost (mitigated by memoization)

**Phase 2 (Future): Hybrid Strategy**

- **Development**: Runtime computation (hot reload, instant feedback)
- **Production**: Build-time token generation (W3C Design Tokens JSON)
- Zero engine in production bundle
- Cross-platform export (CSS, iOS, Android, etc.)

### Component Patterns

All components follow these conventions:

1. **Pure Components** — Props in, UI out, no side effects
2. **Token-Driven** — Colors from `useTokens()`, not hardcoded
3. **Cross-Platform** — `Pressable`, `Text`, `StyleSheet` from React Native
4. **Memoized Styles** — `useMemo` for style computation
5. **Accessible** — WCAG-compliant contrast ratios
6. **Type-Safe** — Full TypeScript with readonly interfaces

### File Structure

```
packages/components/
├── src/
│   ├── theme/
│   │   ├── types.ts              # ColorMode, ThemeName, ThemeMapping
│   │   ├── defaults.ts           # DEFAULT_THEME_CONFIG
│   │   └── NewtoneProvider.tsx   # Context provider + hook
│   ├── tokens/
│   │   ├── types.ts              # ResolvedTokens interface
│   │   ├── computeTokens.ts      # Pure token computation
│   │   └── useTokens.ts          # Memoized token hook
│   ├── Button/
│   │   ├── Button.tsx            # Component implementation
│   │   ├── Button.types.ts       # Props and type definitions
│   │   ├── Button.styles.ts      # Style computation function
│   │   └── index.ts              # Barrel export
│   └── index.ts                  # Public API
├── tests/
│   ├── theme.test.tsx            # Theme system tests
│   ├── tokens.test.ts            # Token computation tests
│   └── Button.test.tsx           # Button component tests
└── package.json
```

## Platform-Specific Notes

### Web

- Uses `react-native-web` to render React Native primitives as HTML
- `Pressable` → `<button>` or `<div>` with click handlers
- `Text` → `<span>`
- `StyleSheet` → Optimized CSS-in-JS

**Bundler Configuration:**

Most modern bundlers (Vite, Next.js, Create React App) automatically handle `react-native-web` aliasing. If needed, manually alias:

```js
// vite.config.js
export default {
  resolve: {
    alias: {
      'react-native': 'react-native-web',
    },
  },
};
```

### React Native

- Native components render directly
- No additional bundler configuration needed
- Full gesture support via `Pressable`
- Metro bundler resolves `react-native` imports automatically

## Testing

The library uses Vitest + React Testing Library for comprehensive testing:

```bash
cd packages/components
npm test              # Run tests in watch mode
npm run test:run      # Run tests once
npm run test:coverage # Generate coverage report
```

**Test Setup:**

- Environment: `jsdom` (browser simulation)
- Alias: `react-native` → `react-native-web` for web compatibility
- Coverage: 90% threshold (branches, functions, lines, statements)

**Example Test:**

```tsx
import { render, fireEvent } from '@testing-library/react';
import { Button, NewtoneProvider } from '@newtone/components';

it('calls onPress when clicked', () => {
  const onPress = vi.fn();
  const { getByText } = render(
    <NewtoneProvider>
      <Button onPress={onPress}>Click me</Button>
    </NewtoneProvider>
  );

  fireEvent.click(getByText('Click me'));
  expect(onPress).toHaveBeenCalledTimes(1);
});
```

## TypeScript

All types are exported for consumer applications:

```tsx
import type {
  // Components
  ButtonProps,
  ButtonVariant,
  ButtonSize,

  // Theme System
  ColorMode,
  ThemeName,
  ElevationLevel,
  ThemeMapping,
  ColorSystemConfig,
  NewtoneThemeConfig,
  NewtoneThemeContext,

  // Tokens
  ResolvedTokens,

  // Engine (re-exported from newtone)
  DynamicRange,
  PaletteConfig,
  ColorResult,
  Srgb,
  Oklch,
  HexColor,
} from '@newtone/components';
```

## Roadmap

### Phase 1 (Complete) ✅
- [x] Monorepo setup with npm workspaces
- [x] Theme system (NewtoneProvider, useNewtoneTheme)
- [x] Runtime token computation (useTokens)
- [x] Button component (4 variants, 3 sizes)
- [x] Comprehensive test suite (48 tests)
- [x] Documentation

### Phase 2 (Planned)
- [ ] Build-time token generation (W3C Design Tokens JSON)
- [ ] CSS variable export for web
- [ ] Platform-specific token formats (iOS, Android)
- [ ] Additional components (Input, Card, Modal, etc.)
- [ ] Storybook integration
- [ ] Visual regression testing
- [ ] Performance benchmarks

## Contributing

This library is part of the [Newtone](https://github.com/joshuaallenmx/newtone) project. Please refer to the main repository for contribution guidelines.

## License

MIT

## Support

For issues, questions, or feature requests, please open an issue in the main [Newtone repository](https://github.com/joshuaallenmx/newtone/issues).

---

Built with ❤️ by the Newtone team