@newtonedev/components 0.1.0
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/README.md +506 -0
- package/dist/Button/Button.d.ts +23 -0
- package/dist/Button/Button.d.ts.map +1 -0
- package/dist/Button/Button.styles.d.ts +39 -0
- package/dist/Button/Button.styles.d.ts.map +1 -0
- package/dist/Button/Button.types.d.ts +42 -0
- package/dist/Button/Button.types.d.ts.map +1 -0
- package/dist/Button/index.d.ts +3 -0
- package/dist/Button/index.d.ts.map +1 -0
- package/dist/Card/Card.d.ts +4 -0
- package/dist/Card/Card.d.ts.map +1 -0
- package/dist/Card/Card.styles.d.ts +12 -0
- package/dist/Card/Card.styles.d.ts.map +1 -0
- package/dist/Card/Card.types.d.ts +9 -0
- package/dist/Card/Card.types.d.ts.map +1 -0
- package/dist/Card/index.d.ts +3 -0
- package/dist/Card/index.d.ts.map +1 -0
- package/dist/HueSlider/HueSlider.d.ts +14 -0
- package/dist/HueSlider/HueSlider.d.ts.map +1 -0
- package/dist/HueSlider/HueSlider.styles.d.ts +28 -0
- package/dist/HueSlider/HueSlider.styles.d.ts.map +1 -0
- package/dist/HueSlider/HueSlider.types.d.ts +12 -0
- package/dist/HueSlider/HueSlider.types.d.ts.map +1 -0
- package/dist/HueSlider/index.d.ts +3 -0
- package/dist/HueSlider/index.d.ts.map +1 -0
- package/dist/Select/Select.d.ts +11 -0
- package/dist/Select/Select.d.ts.map +1 -0
- package/dist/Select/Select.styles.d.ts +23 -0
- package/dist/Select/Select.styles.d.ts.map +1 -0
- package/dist/Select/Select.types.d.ts +14 -0
- package/dist/Select/Select.types.d.ts.map +1 -0
- package/dist/Select/index.d.ts +3 -0
- package/dist/Select/index.d.ts.map +1 -0
- package/dist/Slider/Slider.d.ts +4 -0
- package/dist/Slider/Slider.d.ts.map +1 -0
- package/dist/Slider/Slider.styles.d.ts +27 -0
- package/dist/Slider/Slider.styles.d.ts.map +1 -0
- package/dist/Slider/Slider.types.d.ts +13 -0
- package/dist/Slider/Slider.types.d.ts.map +1 -0
- package/dist/Slider/index.d.ts +3 -0
- package/dist/Slider/index.d.ts.map +1 -0
- package/dist/TextInput/TextInput.d.ts +4 -0
- package/dist/TextInput/TextInput.d.ts.map +1 -0
- package/dist/TextInput/TextInput.styles.d.ts +23 -0
- package/dist/TextInput/TextInput.styles.d.ts.map +1 -0
- package/dist/TextInput/TextInput.types.d.ts +7 -0
- package/dist/TextInput/TextInput.types.d.ts.map +1 -0
- package/dist/TextInput/index.d.ts +3 -0
- package/dist/TextInput/index.d.ts.map +1 -0
- package/dist/Toggle/Toggle.d.ts +4 -0
- package/dist/Toggle/Toggle.d.ts.map +1 -0
- package/dist/Toggle/Toggle.styles.d.ts +30 -0
- package/dist/Toggle/Toggle.styles.d.ts.map +1 -0
- package/dist/Toggle/Toggle.types.d.ts +9 -0
- package/dist/Toggle/Toggle.types.d.ts.map +1 -0
- package/dist/Toggle/index.d.ts +3 -0
- package/dist/Toggle/index.d.ts.map +1 -0
- package/dist/index.cjs +736 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +719 -0
- package/dist/index.js.map +1 -0
- package/dist/theme/NewtoneProvider.d.ts +33 -0
- package/dist/theme/NewtoneProvider.d.ts.map +1 -0
- package/dist/theme/defaults.d.ts +7 -0
- package/dist/theme/defaults.d.ts.map +1 -0
- package/dist/theme/types.d.ts +56 -0
- package/dist/theme/types.d.ts.map +1 -0
- package/dist/tokens/computeTokens.d.ts +30 -0
- package/dist/tokens/computeTokens.d.ts.map +1 -0
- package/dist/tokens/types.d.ts +31 -0
- package/dist/tokens/types.d.ts.map +1 -0
- package/dist/tokens/useTokens.d.ts +26 -0
- package/dist/tokens/useTokens.d.ts.map +1 -0
- package/package.json +57 -0
- package/src/Button/Button.styles.ts +100 -0
- package/src/Button/Button.tsx +67 -0
- package/src/Button/Button.types.ts +49 -0
- package/src/Button/index.ts +2 -0
- package/src/Card/Card.styles.ts +16 -0
- package/src/Card/Card.tsx +25 -0
- package/src/Card/Card.types.ts +9 -0
- package/src/Card/index.ts +2 -0
- package/src/HueSlider/HueSlider.styles.ts +77 -0
- package/src/HueSlider/HueSlider.tsx +70 -0
- package/src/HueSlider/HueSlider.types.ts +12 -0
- package/src/HueSlider/index.ts +2 -0
- package/src/Select/Select.styles.ts +29 -0
- package/src/Select/Select.tsx +60 -0
- package/src/Select/Select.types.ts +15 -0
- package/src/Select/index.ts +2 -0
- package/src/Slider/Slider.styles.ts +45 -0
- package/src/Slider/Slider.tsx +57 -0
- package/src/Slider/Slider.types.ts +13 -0
- package/src/Slider/index.ts +2 -0
- package/src/TextInput/TextInput.styles.ts +29 -0
- package/src/TextInput/TextInput.tsx +32 -0
- package/src/TextInput/TextInput.types.ts +7 -0
- package/src/TextInput/index.ts +2 -0
- package/src/Toggle/Toggle.styles.ts +45 -0
- package/src/Toggle/Toggle.tsx +42 -0
- package/src/Toggle/Toggle.types.ts +9 -0
- package/src/Toggle/index.ts +2 -0
- package/src/index.ts +49 -0
- package/src/theme/NewtoneProvider.tsx +65 -0
- package/src/theme/defaults.ts +42 -0
- package/src/theme/types.ts +62 -0
- package/src/tokens/computeTokens.ts +217 -0
- package/src/tokens/types.ts +31 -0
- package/src/tokens/useTokens.ts +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
# @newtone/components
|
|
2
|
+
|
|
3
|
+
Production-ready React component library built on the Newtone color engine. Cross-platform support for web and React Native using `react-native-web`.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎨 **Powered by Newtone** — Accessible semantic color systems with OKLCH color space
|
|
8
|
+
- 🌓 **Light/Dark Mode** — Automatic theme switching with Context API
|
|
9
|
+
- 🎯 **Four Themes** — Neutral, Primary, Secondary, Strong with elevation support
|
|
10
|
+
- 📱 **Cross-Platform** — Single codebase for web and React Native
|
|
11
|
+
- 🔧 **Runtime Tokens** — Dynamic color computation with memoization
|
|
12
|
+
- ♿️ **Accessible** — WCAG contrast ratios built-in
|
|
13
|
+
- 📦 **Zero Config** — Sensible defaults, fully customizable
|
|
14
|
+
- 🎭 **TypeScript** — Full type safety
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @newtone/components react react-native
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Additional Setup
|
|
23
|
+
|
|
24
|
+
**For Web Projects:**
|
|
25
|
+
```bash
|
|
26
|
+
npm install react-native-web react-dom
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**For React Native Projects:**
|
|
30
|
+
No additional dependencies required.
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### Web Application
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import React from 'react';
|
|
38
|
+
import ReactDOM from 'react-dom/client';
|
|
39
|
+
import { NewtoneProvider, Button } from '@newtone/components';
|
|
40
|
+
|
|
41
|
+
function App() {
|
|
42
|
+
return (
|
|
43
|
+
<NewtoneProvider initialMode="light" initialTheme="neutral">
|
|
44
|
+
<div style={{ padding: 20 }}>
|
|
45
|
+
<Button variant="primary" onPress={() => console.log('Clicked!')}>
|
|
46
|
+
Click me
|
|
47
|
+
</Button>
|
|
48
|
+
</div>
|
|
49
|
+
</NewtoneProvider>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### React Native Application
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import React from 'react';
|
|
60
|
+
import { SafeAreaView } from 'react-native';
|
|
61
|
+
import { NewtoneProvider, Button } from '@newtone/components';
|
|
62
|
+
|
|
63
|
+
export default function App() {
|
|
64
|
+
return (
|
|
65
|
+
<NewtoneProvider initialMode="light" initialTheme="neutral">
|
|
66
|
+
<SafeAreaView style={{ flex: 1, padding: 20 }}>
|
|
67
|
+
<Button variant="primary" onPress={() => console.log('Clicked!')}>
|
|
68
|
+
Click me
|
|
69
|
+
</Button>
|
|
70
|
+
</SafeAreaView>
|
|
71
|
+
</NewtoneProvider>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Components
|
|
77
|
+
|
|
78
|
+
### Button
|
|
79
|
+
|
|
80
|
+
A cross-platform button component with multiple variants and sizes.
|
|
81
|
+
|
|
82
|
+
#### Props
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
interface ButtonProps {
|
|
86
|
+
children: React.ReactNode;
|
|
87
|
+
variant?: 'primary' | 'secondary' | 'ghost' | 'outline'; // default: 'primary'
|
|
88
|
+
size?: 'sm' | 'md' | 'lg'; // default: 'md'
|
|
89
|
+
disabled?: boolean; // default: false
|
|
90
|
+
onPress?: () => void;
|
|
91
|
+
style?: ViewStyle | ViewStyle[];
|
|
92
|
+
textStyle?: TextStyle | TextStyle[];
|
|
93
|
+
// ... all other Pressable props
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### Variants
|
|
98
|
+
|
|
99
|
+
- **Primary** — Filled button with interactive color background
|
|
100
|
+
- **Secondary** — Filled button with elevated background
|
|
101
|
+
- **Ghost** — Transparent button with colored text
|
|
102
|
+
- **Outline** — Border-only button with colored text
|
|
103
|
+
|
|
104
|
+
#### Sizes
|
|
105
|
+
|
|
106
|
+
- **Small (`sm`)** — Compact button for tight spaces (6px vertical padding)
|
|
107
|
+
- **Medium (`md`)** — Default size for most use cases (10px vertical padding)
|
|
108
|
+
- **Large (`lg`)** — Prominent button for primary actions (14px vertical padding)
|
|
109
|
+
|
|
110
|
+
#### Examples
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
import { Button } from '@newtone/components';
|
|
114
|
+
|
|
115
|
+
// Primary button (default)
|
|
116
|
+
<Button onPress={() => {}}>Submit</Button>
|
|
117
|
+
|
|
118
|
+
// Secondary button
|
|
119
|
+
<Button variant="secondary" onPress={() => {}}>Cancel</Button>
|
|
120
|
+
|
|
121
|
+
// Large ghost button
|
|
122
|
+
<Button variant="ghost" size="lg" onPress={() => {}}>Learn More</Button>
|
|
123
|
+
|
|
124
|
+
// Disabled outline button
|
|
125
|
+
<Button variant="outline" disabled>Unavailable</Button>
|
|
126
|
+
|
|
127
|
+
// Custom styles
|
|
128
|
+
<Button
|
|
129
|
+
variant="primary"
|
|
130
|
+
style={{ marginTop: 20, width: 200 }}
|
|
131
|
+
textStyle={{ fontWeight: 'bold' }}
|
|
132
|
+
onPress={() => {}}
|
|
133
|
+
>
|
|
134
|
+
Custom Styled
|
|
135
|
+
</Button>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Theme System
|
|
139
|
+
|
|
140
|
+
### NewtoneProvider
|
|
141
|
+
|
|
142
|
+
Wrap your application root with `NewtoneProvider` to enable theming.
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
import { NewtoneProvider } from '@newtone/components';
|
|
146
|
+
|
|
147
|
+
<NewtoneProvider
|
|
148
|
+
config={customConfig} // optional, uses defaults if omitted
|
|
149
|
+
initialMode="light" // 'light' | 'dark', default: 'light'
|
|
150
|
+
initialTheme="neutral" // 'neutral' | 'primary' | 'secondary' | 'strong', default: 'neutral'
|
|
151
|
+
>
|
|
152
|
+
<App />
|
|
153
|
+
</NewtoneProvider>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### Default Configuration
|
|
157
|
+
|
|
158
|
+
The library ships with a sensible default configuration:
|
|
159
|
+
|
|
160
|
+
- **5 Palettes**: Neutral (gray), Accent (blue), Success (green), Warning (yellow), Error (red)
|
|
161
|
+
- **4 Themes**: Each maps to a palette with light/dark normalized values
|
|
162
|
+
- **3 Elevations**: Surface levels with subtle color shifts (-0.02, 0, +0.04)
|
|
163
|
+
- **Dynamic Range**: Lightest = 1 (white), Darkest = 1 (black)
|
|
164
|
+
|
|
165
|
+
### useNewtoneTheme Hook
|
|
166
|
+
|
|
167
|
+
Access and modify theme state from any component within the provider.
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
import { useNewtoneTheme } from '@newtone/components';
|
|
171
|
+
|
|
172
|
+
function ThemeToggle() {
|
|
173
|
+
const { mode, theme, setMode, setTheme } = useNewtoneTheme();
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<div>
|
|
177
|
+
<button onClick={() => setMode(mode === 'light' ? 'dark' : 'light')}>
|
|
178
|
+
Toggle {mode} mode
|
|
179
|
+
</button>
|
|
180
|
+
<button onClick={() => setTheme('primary')}>
|
|
181
|
+
Switch to primary theme
|
|
182
|
+
</button>
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### useTokens Hook
|
|
189
|
+
|
|
190
|
+
Compute design tokens for the current theme context at a specific elevation.
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
import { useTokens } from '@newtone/components';
|
|
194
|
+
|
|
195
|
+
function CustomComponent() {
|
|
196
|
+
const tokens = useTokens(1); // Elevation level: 0, 1, or 2
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<div style={{
|
|
200
|
+
backgroundColor: tokens.background.srgb,
|
|
201
|
+
color: tokens.textPrimary.srgb
|
|
202
|
+
}}>
|
|
203
|
+
Custom component using tokens
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### Available Tokens
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
interface ResolvedTokens {
|
|
213
|
+
background: ColorResult; // Base surface color
|
|
214
|
+
backgroundElevated: ColorResult; // Elevated surface (+1 level)
|
|
215
|
+
backgroundSunken: ColorResult; // Sunken surface (-1 level)
|
|
216
|
+
textPrimary: ColorResult; // Primary text (WCAG 4.5:1)
|
|
217
|
+
textSecondary: ColorResult; // Secondary text (WCAG 3.0:1)
|
|
218
|
+
interactive: ColorResult; // Interactive elements (accent palette)
|
|
219
|
+
interactiveHover: ColorResult; // Hover state
|
|
220
|
+
interactiveActive: ColorResult; // Active/pressed state
|
|
221
|
+
border: ColorResult; // Subtle borders
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Each `ColorResult` contains:
|
|
226
|
+
```typescript
|
|
227
|
+
{
|
|
228
|
+
srgb: { r: number, g: number, b: number }, // 0-1 range
|
|
229
|
+
oklch: { L: number, C: number, h: number }
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Helper for hex conversion:**
|
|
234
|
+
```tsx
|
|
235
|
+
import { srgbToHex } from 'newtone';
|
|
236
|
+
|
|
237
|
+
const hexColor = srgbToHex(tokens.background.srgb); // "#ffffff"
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Customization
|
|
241
|
+
|
|
242
|
+
### Custom Color System
|
|
243
|
+
|
|
244
|
+
Override the default palette configuration:
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
import { NewtoneProvider } from '@newtone/components';
|
|
248
|
+
import type { ColorSystemConfig } from 'newtone';
|
|
249
|
+
|
|
250
|
+
const customColorSystem: ColorSystemConfig = {
|
|
251
|
+
dynamicRange: {
|
|
252
|
+
lightest: 1, // 0-1, where 1 = pure white
|
|
253
|
+
darkest: 1, // 0-1, where 1 = pure black
|
|
254
|
+
},
|
|
255
|
+
palettes: [
|
|
256
|
+
{
|
|
257
|
+
hue: 220, // OKLCH hue (0-360)
|
|
258
|
+
saturation: 80, // 0-100, relative to max in-gamut chroma
|
|
259
|
+
desaturation: {
|
|
260
|
+
direction: 'light', // 'light' | 'dark'
|
|
261
|
+
strength: 'medium' // 'low' | 'medium' | 'hard'
|
|
262
|
+
},
|
|
263
|
+
paletteHueGrading: {
|
|
264
|
+
hue: 200,
|
|
265
|
+
strength: 'low',
|
|
266
|
+
direction: 'light'
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
// ... more palettes
|
|
270
|
+
],
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const customConfig = {
|
|
274
|
+
colorSystem: customColorSystem,
|
|
275
|
+
themes: {
|
|
276
|
+
neutral: { paletteIndex: 0, lightModeNv: 0.97, darkModeNv: 0.08 },
|
|
277
|
+
primary: { paletteIndex: 1, lightModeNv: 0.50, darkModeNv: 0.45 },
|
|
278
|
+
secondary: { paletteIndex: 2, lightModeNv: 0.60, darkModeNv: 0.55 },
|
|
279
|
+
strong: { paletteIndex: 3, lightModeNv: 0.30, darkModeNv: 0.70 },
|
|
280
|
+
},
|
|
281
|
+
elevation: {
|
|
282
|
+
offsets: [-0.02, 0, 0.04] as const,
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
<NewtoneProvider config={customConfig}>
|
|
287
|
+
<App />
|
|
288
|
+
</NewtoneProvider>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Custom Button Styles
|
|
292
|
+
|
|
293
|
+
Override button appearance with `style` and `textStyle` props:
|
|
294
|
+
|
|
295
|
+
```tsx
|
|
296
|
+
<Button
|
|
297
|
+
variant="primary"
|
|
298
|
+
style={[
|
|
299
|
+
{ borderRadius: 20, paddingHorizontal: 30 },
|
|
300
|
+
isHighlighted && { transform: [{ scale: 1.05 }] }
|
|
301
|
+
]}
|
|
302
|
+
textStyle={{
|
|
303
|
+
fontFamily: 'CustomFont',
|
|
304
|
+
letterSpacing: 1
|
|
305
|
+
}}
|
|
306
|
+
>
|
|
307
|
+
Custom Button
|
|
308
|
+
</Button>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Architecture
|
|
312
|
+
|
|
313
|
+
### Token Computation Strategy
|
|
314
|
+
|
|
315
|
+
**Phase 1 (Current): Runtime Computation**
|
|
316
|
+
|
|
317
|
+
Tokens are computed on-demand using the Newtone color engine:
|
|
318
|
+
|
|
319
|
+
```
|
|
320
|
+
Config → getColor() → OKLCH → Gamut Mapping → sRGB → ColorResult
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
- ✅ Maximum flexibility (change config, colors update instantly)
|
|
324
|
+
- ✅ Smallest bundle (no pre-generated tokens)
|
|
325
|
+
- ✅ Always accurate (no stale tokens)
|
|
326
|
+
- ⚠️ Requires engine in bundle (~20KB minified)
|
|
327
|
+
- ⚠️ Runtime computation cost (mitigated by memoization)
|
|
328
|
+
|
|
329
|
+
**Phase 2 (Future): Hybrid Strategy**
|
|
330
|
+
|
|
331
|
+
- **Development**: Runtime computation (hot reload, instant feedback)
|
|
332
|
+
- **Production**: Build-time token generation (W3C Design Tokens JSON)
|
|
333
|
+
- Zero engine in production bundle
|
|
334
|
+
- Cross-platform export (CSS, iOS, Android, etc.)
|
|
335
|
+
|
|
336
|
+
### Component Patterns
|
|
337
|
+
|
|
338
|
+
All components follow these conventions:
|
|
339
|
+
|
|
340
|
+
1. **Pure Components** — Props in, UI out, no side effects
|
|
341
|
+
2. **Token-Driven** — Colors from `useTokens()`, not hardcoded
|
|
342
|
+
3. **Cross-Platform** — `Pressable`, `Text`, `StyleSheet` from React Native
|
|
343
|
+
4. **Memoized Styles** — `useMemo` for style computation
|
|
344
|
+
5. **Accessible** — WCAG-compliant contrast ratios
|
|
345
|
+
6. **Type-Safe** — Full TypeScript with readonly interfaces
|
|
346
|
+
|
|
347
|
+
### File Structure
|
|
348
|
+
|
|
349
|
+
```
|
|
350
|
+
packages/components/
|
|
351
|
+
├── src/
|
|
352
|
+
│ ├── theme/
|
|
353
|
+
│ │ ├── types.ts # ColorMode, ThemeName, ThemeMapping
|
|
354
|
+
│ │ ├── defaults.ts # DEFAULT_THEME_CONFIG
|
|
355
|
+
│ │ └── NewtoneProvider.tsx # Context provider + hook
|
|
356
|
+
│ ├── tokens/
|
|
357
|
+
│ │ ├── types.ts # ResolvedTokens interface
|
|
358
|
+
│ │ ├── computeTokens.ts # Pure token computation
|
|
359
|
+
│ │ └── useTokens.ts # Memoized token hook
|
|
360
|
+
│ ├── Button/
|
|
361
|
+
│ │ ├── Button.tsx # Component implementation
|
|
362
|
+
│ │ ├── Button.types.ts # Props and type definitions
|
|
363
|
+
│ │ ├── Button.styles.ts # Style computation function
|
|
364
|
+
│ │ └── index.ts # Barrel export
|
|
365
|
+
│ └── index.ts # Public API
|
|
366
|
+
├── tests/
|
|
367
|
+
│ ├── theme.test.tsx # Theme system tests
|
|
368
|
+
│ ├── tokens.test.ts # Token computation tests
|
|
369
|
+
│ └── Button.test.tsx # Button component tests
|
|
370
|
+
└── package.json
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Platform-Specific Notes
|
|
374
|
+
|
|
375
|
+
### Web
|
|
376
|
+
|
|
377
|
+
- Uses `react-native-web` to render React Native primitives as HTML
|
|
378
|
+
- `Pressable` → `<button>` or `<div>` with click handlers
|
|
379
|
+
- `Text` → `<span>`
|
|
380
|
+
- `StyleSheet` → Optimized CSS-in-JS
|
|
381
|
+
|
|
382
|
+
**Bundler Configuration:**
|
|
383
|
+
|
|
384
|
+
Most modern bundlers (Vite, Next.js, Create React App) automatically handle `react-native-web` aliasing. If needed, manually alias:
|
|
385
|
+
|
|
386
|
+
```js
|
|
387
|
+
// vite.config.js
|
|
388
|
+
export default {
|
|
389
|
+
resolve: {
|
|
390
|
+
alias: {
|
|
391
|
+
'react-native': 'react-native-web',
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### React Native
|
|
398
|
+
|
|
399
|
+
- Native components render directly
|
|
400
|
+
- No additional bundler configuration needed
|
|
401
|
+
- Full gesture support via `Pressable`
|
|
402
|
+
- Metro bundler resolves `react-native` imports automatically
|
|
403
|
+
|
|
404
|
+
## Testing
|
|
405
|
+
|
|
406
|
+
The library uses Vitest + React Testing Library for comprehensive testing:
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
cd packages/components
|
|
410
|
+
npm test # Run tests in watch mode
|
|
411
|
+
npm run test:run # Run tests once
|
|
412
|
+
npm run test:coverage # Generate coverage report
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**Test Setup:**
|
|
416
|
+
|
|
417
|
+
- Environment: `jsdom` (browser simulation)
|
|
418
|
+
- Alias: `react-native` → `react-native-web` for web compatibility
|
|
419
|
+
- Coverage: 90% threshold (branches, functions, lines, statements)
|
|
420
|
+
|
|
421
|
+
**Example Test:**
|
|
422
|
+
|
|
423
|
+
```tsx
|
|
424
|
+
import { render, fireEvent } from '@testing-library/react';
|
|
425
|
+
import { Button, NewtoneProvider } from '@newtone/components';
|
|
426
|
+
|
|
427
|
+
it('calls onPress when clicked', () => {
|
|
428
|
+
const onPress = vi.fn();
|
|
429
|
+
const { getByText } = render(
|
|
430
|
+
<NewtoneProvider>
|
|
431
|
+
<Button onPress={onPress}>Click me</Button>
|
|
432
|
+
</NewtoneProvider>
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
fireEvent.click(getByText('Click me'));
|
|
436
|
+
expect(onPress).toHaveBeenCalledTimes(1);
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
## TypeScript
|
|
441
|
+
|
|
442
|
+
All types are exported for consumer applications:
|
|
443
|
+
|
|
444
|
+
```tsx
|
|
445
|
+
import type {
|
|
446
|
+
// Components
|
|
447
|
+
ButtonProps,
|
|
448
|
+
ButtonVariant,
|
|
449
|
+
ButtonSize,
|
|
450
|
+
|
|
451
|
+
// Theme System
|
|
452
|
+
ColorMode,
|
|
453
|
+
ThemeName,
|
|
454
|
+
ElevationLevel,
|
|
455
|
+
ThemeMapping,
|
|
456
|
+
ColorSystemConfig,
|
|
457
|
+
NewtoneThemeConfig,
|
|
458
|
+
NewtoneThemeContext,
|
|
459
|
+
|
|
460
|
+
// Tokens
|
|
461
|
+
ResolvedTokens,
|
|
462
|
+
|
|
463
|
+
// Engine (re-exported from newtone)
|
|
464
|
+
DynamicRange,
|
|
465
|
+
PaletteConfig,
|
|
466
|
+
ColorResult,
|
|
467
|
+
Srgb,
|
|
468
|
+
Oklch,
|
|
469
|
+
HexColor,
|
|
470
|
+
} from '@newtone/components';
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
## Roadmap
|
|
474
|
+
|
|
475
|
+
### Phase 1 (Complete) ✅
|
|
476
|
+
- [x] Monorepo setup with npm workspaces
|
|
477
|
+
- [x] Theme system (NewtoneProvider, useNewtoneTheme)
|
|
478
|
+
- [x] Runtime token computation (useTokens)
|
|
479
|
+
- [x] Button component (4 variants, 3 sizes)
|
|
480
|
+
- [x] Comprehensive test suite (48 tests)
|
|
481
|
+
- [x] Documentation
|
|
482
|
+
|
|
483
|
+
### Phase 2 (Planned)
|
|
484
|
+
- [ ] Build-time token generation (W3C Design Tokens JSON)
|
|
485
|
+
- [ ] CSS variable export for web
|
|
486
|
+
- [ ] Platform-specific token formats (iOS, Android)
|
|
487
|
+
- [ ] Additional components (Input, Card, Modal, etc.)
|
|
488
|
+
- [ ] Storybook integration
|
|
489
|
+
- [ ] Visual regression testing
|
|
490
|
+
- [ ] Performance benchmarks
|
|
491
|
+
|
|
492
|
+
## Contributing
|
|
493
|
+
|
|
494
|
+
This library is part of the [Newtone](https://github.com/joshuaallenmx/newtone) project. Please refer to the main repository for contribution guidelines.
|
|
495
|
+
|
|
496
|
+
## License
|
|
497
|
+
|
|
498
|
+
MIT
|
|
499
|
+
|
|
500
|
+
## Support
|
|
501
|
+
|
|
502
|
+
For issues, questions, or feature requests, please open an issue in the main [Newtone repository](https://github.com/joshuaallenmx/newtone/issues).
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
Built with ❤️ by the Newtone team
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ButtonProps } from './Button.types';
|
|
3
|
+
/**
|
|
4
|
+
* Button component with support for multiple variants and sizes.
|
|
5
|
+
*
|
|
6
|
+
* Automatically adapts to the current theme and mode from NewtoneProvider.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* <Button variant="primary" size="md" onPress={() => console.log('Pressed')}>
|
|
11
|
+
* Click me
|
|
12
|
+
* </Button>
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <Button variant="outline" size="lg" disabled>
|
|
18
|
+
* Disabled button
|
|
19
|
+
* </Button>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function Button({ children, variant, size, disabled, style, textStyle, ...pressableProps }: ButtonProps): React.JSX.Element;
|
|
23
|
+
//# sourceMappingURL=Button.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Button.d.ts","sourceRoot":"","sources":["../../src/Button/Button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAIlD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,MAAM,CAAC,EACrB,QAAQ,EACR,OAAmB,EACnB,IAAW,EACX,QAAgB,EAChB,KAAK,EACL,SAAS,EACT,GAAG,cAAc,EAClB,EAAE,WAAW,qBAiCb"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ResolvedTokens } from '../tokens/types';
|
|
2
|
+
import type { ButtonVariant, ButtonSize } from './Button.types';
|
|
3
|
+
/**
|
|
4
|
+
* Compute button styles based on tokens, variant, size, and state
|
|
5
|
+
*
|
|
6
|
+
* @param tokens - Resolved design tokens from useTokens
|
|
7
|
+
* @param variant - Button variant (primary, secondary, ghost, outline)
|
|
8
|
+
* @param size - Button size (sm, md, lg)
|
|
9
|
+
* @param disabled - Whether button is disabled
|
|
10
|
+
* @returns StyleSheet with button styles
|
|
11
|
+
*/
|
|
12
|
+
export declare function getButtonStyles(tokens: ResolvedTokens, variant: ButtonVariant, size: ButtonSize, disabled: boolean): {
|
|
13
|
+
base: {
|
|
14
|
+
borderColor?: string | undefined;
|
|
15
|
+
backgroundColor: string;
|
|
16
|
+
borderWidth: number;
|
|
17
|
+
paddingVertical: 6 | 10 | 14;
|
|
18
|
+
paddingHorizontal: 12 | 20 | 28;
|
|
19
|
+
borderRadius: 8 | 4 | 6;
|
|
20
|
+
alignItems: "center";
|
|
21
|
+
justifyContent: "center";
|
|
22
|
+
flexDirection: "row";
|
|
23
|
+
};
|
|
24
|
+
pressed: {
|
|
25
|
+
backgroundColor: string;
|
|
26
|
+
opacity: number;
|
|
27
|
+
};
|
|
28
|
+
disabled: {
|
|
29
|
+
opacity: number;
|
|
30
|
+
};
|
|
31
|
+
text: {
|
|
32
|
+
fontSize: 12 | 14 | 16;
|
|
33
|
+
fontWeight: "600";
|
|
34
|
+
color: string;
|
|
35
|
+
};
|
|
36
|
+
textPressed: {};
|
|
37
|
+
textDisabled: {};
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=Button.styles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Button.styles.d.ts","sourceRoot":"","sources":["../../src/Button/Button.styles.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAWhE;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;EAwElB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { PressableProps, ViewStyle, TextStyle } from 'react-native';
|
|
2
|
+
/**
|
|
3
|
+
* Visual variants for the Button component
|
|
4
|
+
*/
|
|
5
|
+
export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'outline';
|
|
6
|
+
/**
|
|
7
|
+
* Size presets for the Button component
|
|
8
|
+
*/
|
|
9
|
+
export type ButtonSize = 'sm' | 'md' | 'lg';
|
|
10
|
+
/**
|
|
11
|
+
* Props for the Button component
|
|
12
|
+
*/
|
|
13
|
+
export interface ButtonProps extends Omit<PressableProps, 'children' | 'style'> {
|
|
14
|
+
/**
|
|
15
|
+
* Button text or custom content
|
|
16
|
+
*/
|
|
17
|
+
readonly children: React.ReactNode;
|
|
18
|
+
/**
|
|
19
|
+
* Visual variant
|
|
20
|
+
* @default 'primary'
|
|
21
|
+
*/
|
|
22
|
+
readonly variant?: ButtonVariant;
|
|
23
|
+
/**
|
|
24
|
+
* Size preset
|
|
25
|
+
* @default 'md'
|
|
26
|
+
*/
|
|
27
|
+
readonly size?: ButtonSize;
|
|
28
|
+
/**
|
|
29
|
+
* Disabled state
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
readonly disabled?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Custom style overrides for container
|
|
35
|
+
*/
|
|
36
|
+
readonly style?: ViewStyle | ViewStyle[];
|
|
37
|
+
/**
|
|
38
|
+
* Custom style overrides for text
|
|
39
|
+
*/
|
|
40
|
+
readonly textStyle?: TextStyle | TextStyle[];
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=Button.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Button.types.d.ts","sourceRoot":"","sources":["../../src/Button/Button.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzE;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,SAAS,CAAC;AAE1E;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,IAAI,CAAC,cAAc,EAAE,UAAU,GAAG,OAAO,CAAC;IAC7E;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAEnC;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;IAEjC;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;IAE3B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAE5B;;OAEG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,EAAE,CAAC;IAEzC;;OAEG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,EAAE,CAAC;CAC9C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Button/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Card.d.ts","sourceRoot":"","sources":["../../src/Card/Card.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAI9C,wBAAgB,IAAI,CAAC,EACnB,QAAQ,EACR,SAAa,EACb,KAAK,EACL,QAAgB,GACjB,EAAE,SAAS,qBAaX"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ResolvedTokens } from '../tokens/types';
|
|
2
|
+
export declare function getCardStyles(tokens: ResolvedTokens, disabled: boolean): {
|
|
3
|
+
container: {
|
|
4
|
+
backgroundColor: string;
|
|
5
|
+
borderWidth: number;
|
|
6
|
+
borderColor: string;
|
|
7
|
+
borderRadius: number;
|
|
8
|
+
padding: number;
|
|
9
|
+
opacity: number;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=Card.styles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Card.styles.d.ts","sourceRoot":"","sources":["../../src/Card/Card.styles.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO;;;;;;;;;EAWtE"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ViewStyle } from 'react-native';
|
|
2
|
+
import type { ElevationLevel } from '../theme/types';
|
|
3
|
+
export interface CardProps {
|
|
4
|
+
readonly children: React.ReactNode;
|
|
5
|
+
readonly elevation?: ElevationLevel;
|
|
6
|
+
readonly style?: ViewStyle | ViewStyle[];
|
|
7
|
+
readonly disabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=Card.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Card.types.d.ts","sourceRoot":"","sources":["../../src/Card/Card.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,EAAE,CAAC;IACzC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Card/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { HueSliderProps } from './HueSlider.types';
|
|
3
|
+
/**
|
|
4
|
+
* Hue slider with rainbow gradient track.
|
|
5
|
+
*
|
|
6
|
+
* Value range: 0-359 (traditional color wheel hue).
|
|
7
|
+
* 0=red, 60=yellow, 120=green, 180=cyan, 240=blue, 300=magenta.
|
|
8
|
+
*
|
|
9
|
+
* Optional min/max constrain the selectable range.
|
|
10
|
+
* For wrapping ranges (e.g. red: min=345 max=375), max may exceed 359;
|
|
11
|
+
* the returned value is always normalized to [0, 359].
|
|
12
|
+
*/
|
|
13
|
+
export declare function HueSlider({ value, onValueChange, min, max, label, showValue, disabled, style, }: HueSliderProps): React.JSX.Element;
|
|
14
|
+
//# sourceMappingURL=HueSlider.d.ts.map
|