@newtonedev/components
v0.1.12
Published
React + React Native Web component library for Newtone
Readme
@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
npm install @newtone/components react react-nativeAdditional Setup
For Web Projects:
npm install react-native-web react-domFor React Native Projects: No additional dependencies required.
Quick Start
Web Application
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
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
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
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.
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.
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.
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
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:
{
srgb: { r: number, g: number, b: number }, // 0-1 range
oklch: { L: number, C: number, h: number }
}Helper for hex conversion:
import { srgbToHex } from 'newtone';
const hexColor = srgbToHex(tokens.background.srgb); // "#ffffff"Customization
Custom Color System
Override the default palette configuration:
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:
<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:
- Pure Components — Props in, UI out, no side effects
- Token-Driven — Colors from
useTokens(), not hardcoded - Cross-Platform —
Pressable,Text,StyleSheetfrom React Native - Memoized Styles —
useMemofor style computation - Accessible — WCAG-compliant contrast ratios
- 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.jsonPlatform-Specific Notes
Web
- Uses
react-native-webto render React Native primitives as HTML Pressable→<button>or<div>with click handlersText→<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:
// 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-nativeimports automatically
Testing
The library uses Vitest + React Testing Library for comprehensive testing:
cd packages/components
npm test # Run tests in watch mode
npm run test:run # Run tests once
npm run test:coverage # Generate coverage reportTest Setup:
- Environment:
jsdom(browser simulation) - Alias:
react-native→react-native-webfor web compatibility - Coverage: 90% threshold (branches, functions, lines, statements)
Example Test:
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:
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 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.
Built with ❤️ by the Newtone team
