@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.
Files changed (111) hide show
  1. package/README.md +506 -0
  2. package/dist/Button/Button.d.ts +23 -0
  3. package/dist/Button/Button.d.ts.map +1 -0
  4. package/dist/Button/Button.styles.d.ts +39 -0
  5. package/dist/Button/Button.styles.d.ts.map +1 -0
  6. package/dist/Button/Button.types.d.ts +42 -0
  7. package/dist/Button/Button.types.d.ts.map +1 -0
  8. package/dist/Button/index.d.ts +3 -0
  9. package/dist/Button/index.d.ts.map +1 -0
  10. package/dist/Card/Card.d.ts +4 -0
  11. package/dist/Card/Card.d.ts.map +1 -0
  12. package/dist/Card/Card.styles.d.ts +12 -0
  13. package/dist/Card/Card.styles.d.ts.map +1 -0
  14. package/dist/Card/Card.types.d.ts +9 -0
  15. package/dist/Card/Card.types.d.ts.map +1 -0
  16. package/dist/Card/index.d.ts +3 -0
  17. package/dist/Card/index.d.ts.map +1 -0
  18. package/dist/HueSlider/HueSlider.d.ts +14 -0
  19. package/dist/HueSlider/HueSlider.d.ts.map +1 -0
  20. package/dist/HueSlider/HueSlider.styles.d.ts +28 -0
  21. package/dist/HueSlider/HueSlider.styles.d.ts.map +1 -0
  22. package/dist/HueSlider/HueSlider.types.d.ts +12 -0
  23. package/dist/HueSlider/HueSlider.types.d.ts.map +1 -0
  24. package/dist/HueSlider/index.d.ts +3 -0
  25. package/dist/HueSlider/index.d.ts.map +1 -0
  26. package/dist/Select/Select.d.ts +11 -0
  27. package/dist/Select/Select.d.ts.map +1 -0
  28. package/dist/Select/Select.styles.d.ts +23 -0
  29. package/dist/Select/Select.styles.d.ts.map +1 -0
  30. package/dist/Select/Select.types.d.ts +14 -0
  31. package/dist/Select/Select.types.d.ts.map +1 -0
  32. package/dist/Select/index.d.ts +3 -0
  33. package/dist/Select/index.d.ts.map +1 -0
  34. package/dist/Slider/Slider.d.ts +4 -0
  35. package/dist/Slider/Slider.d.ts.map +1 -0
  36. package/dist/Slider/Slider.styles.d.ts +27 -0
  37. package/dist/Slider/Slider.styles.d.ts.map +1 -0
  38. package/dist/Slider/Slider.types.d.ts +13 -0
  39. package/dist/Slider/Slider.types.d.ts.map +1 -0
  40. package/dist/Slider/index.d.ts +3 -0
  41. package/dist/Slider/index.d.ts.map +1 -0
  42. package/dist/TextInput/TextInput.d.ts +4 -0
  43. package/dist/TextInput/TextInput.d.ts.map +1 -0
  44. package/dist/TextInput/TextInput.styles.d.ts +23 -0
  45. package/dist/TextInput/TextInput.styles.d.ts.map +1 -0
  46. package/dist/TextInput/TextInput.types.d.ts +7 -0
  47. package/dist/TextInput/TextInput.types.d.ts.map +1 -0
  48. package/dist/TextInput/index.d.ts +3 -0
  49. package/dist/TextInput/index.d.ts.map +1 -0
  50. package/dist/Toggle/Toggle.d.ts +4 -0
  51. package/dist/Toggle/Toggle.d.ts.map +1 -0
  52. package/dist/Toggle/Toggle.styles.d.ts +30 -0
  53. package/dist/Toggle/Toggle.styles.d.ts.map +1 -0
  54. package/dist/Toggle/Toggle.types.d.ts +9 -0
  55. package/dist/Toggle/Toggle.types.d.ts.map +1 -0
  56. package/dist/Toggle/index.d.ts +3 -0
  57. package/dist/Toggle/index.d.ts.map +1 -0
  58. package/dist/index.cjs +736 -0
  59. package/dist/index.cjs.map +1 -0
  60. package/dist/index.d.ts +22 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +719 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/theme/NewtoneProvider.d.ts +33 -0
  65. package/dist/theme/NewtoneProvider.d.ts.map +1 -0
  66. package/dist/theme/defaults.d.ts +7 -0
  67. package/dist/theme/defaults.d.ts.map +1 -0
  68. package/dist/theme/types.d.ts +56 -0
  69. package/dist/theme/types.d.ts.map +1 -0
  70. package/dist/tokens/computeTokens.d.ts +30 -0
  71. package/dist/tokens/computeTokens.d.ts.map +1 -0
  72. package/dist/tokens/types.d.ts +31 -0
  73. package/dist/tokens/types.d.ts.map +1 -0
  74. package/dist/tokens/useTokens.d.ts +26 -0
  75. package/dist/tokens/useTokens.d.ts.map +1 -0
  76. package/package.json +57 -0
  77. package/src/Button/Button.styles.ts +100 -0
  78. package/src/Button/Button.tsx +67 -0
  79. package/src/Button/Button.types.ts +49 -0
  80. package/src/Button/index.ts +2 -0
  81. package/src/Card/Card.styles.ts +16 -0
  82. package/src/Card/Card.tsx +25 -0
  83. package/src/Card/Card.types.ts +9 -0
  84. package/src/Card/index.ts +2 -0
  85. package/src/HueSlider/HueSlider.styles.ts +77 -0
  86. package/src/HueSlider/HueSlider.tsx +70 -0
  87. package/src/HueSlider/HueSlider.types.ts +12 -0
  88. package/src/HueSlider/index.ts +2 -0
  89. package/src/Select/Select.styles.ts +29 -0
  90. package/src/Select/Select.tsx +60 -0
  91. package/src/Select/Select.types.ts +15 -0
  92. package/src/Select/index.ts +2 -0
  93. package/src/Slider/Slider.styles.ts +45 -0
  94. package/src/Slider/Slider.tsx +57 -0
  95. package/src/Slider/Slider.types.ts +13 -0
  96. package/src/Slider/index.ts +2 -0
  97. package/src/TextInput/TextInput.styles.ts +29 -0
  98. package/src/TextInput/TextInput.tsx +32 -0
  99. package/src/TextInput/TextInput.types.ts +7 -0
  100. package/src/TextInput/index.ts +2 -0
  101. package/src/Toggle/Toggle.styles.ts +45 -0
  102. package/src/Toggle/Toggle.tsx +42 -0
  103. package/src/Toggle/Toggle.types.ts +9 -0
  104. package/src/Toggle/index.ts +2 -0
  105. package/src/index.ts +49 -0
  106. package/src/theme/NewtoneProvider.tsx +65 -0
  107. package/src/theme/defaults.ts +42 -0
  108. package/src/theme/types.ts +62 -0
  109. package/src/tokens/computeTokens.ts +217 -0
  110. package/src/tokens/types.ts +31 -0
  111. package/src/tokens/useTokens.ts +42 -0
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { View } from 'react-native';
3
+ import type { CardProps } from './Card.types';
4
+ import { useTokens } from '../tokens/useTokens';
5
+ import { getCardStyles } from './Card.styles';
6
+
7
+ export function Card({
8
+ children,
9
+ elevation = 1,
10
+ style,
11
+ disabled = false,
12
+ }: CardProps) {
13
+ const tokens = useTokens(elevation);
14
+
15
+ const styles = React.useMemo(
16
+ () => getCardStyles(tokens, disabled),
17
+ [tokens, disabled]
18
+ );
19
+
20
+ return (
21
+ <View style={[styles.container, ...(Array.isArray(style) ? style : [style])]}>
22
+ {children}
23
+ </View>
24
+ );
25
+ }
@@ -0,0 +1,9 @@
1
+ import type { ViewStyle } from 'react-native';
2
+ import type { ElevationLevel } from '../theme/types';
3
+
4
+ export interface CardProps {
5
+ readonly children: React.ReactNode;
6
+ readonly elevation?: ElevationLevel;
7
+ readonly style?: ViewStyle | ViewStyle[];
8
+ readonly disabled?: boolean;
9
+ }
@@ -0,0 +1,2 @@
1
+ export { Card } from './Card';
2
+ export type { CardProps } from './Card.types';
@@ -0,0 +1,77 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { srgbToHex } from 'newtone';
3
+ import type { ResolvedTokens } from '../tokens/types';
4
+
5
+ /** Rainbow gradient for hue slider track */
6
+ const HUE_GRADIENT =
7
+ 'linear-gradient(to right, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000)';
8
+
9
+ /** Convert an HSL hue (S=100%, L=50%) to a hex string for gradient stops. */
10
+ function hueToHex(hue: number): string {
11
+ const h = ((hue % 360) + 360) % 360;
12
+ const x = 1 - Math.abs((h / 60) % 2 - 1);
13
+ let r: number, g: number, b: number;
14
+ if (h < 60) { r = 1; g = x; b = 0; }
15
+ else if (h < 120) { r = x; g = 1; b = 0; }
16
+ else if (h < 180) { r = 0; g = 1; b = x; }
17
+ else if (h < 240) { r = 0; g = x; b = 1; }
18
+ else if (h < 300) { r = x; g = 0; b = 1; }
19
+ else { r = 1; g = 0; b = x; }
20
+ const toHex = (v: number) => Math.round(v * 255).toString(16).padStart(2, '0');
21
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
22
+ }
23
+
24
+ function buildHueGradient(min: number, max: number): string {
25
+ if (min === 0 && max === 359) return HUE_GRADIENT;
26
+ const steps = 7;
27
+ const stops: string[] = [];
28
+ for (let i = 0; i <= steps; i++) {
29
+ const hue = min + (max - min) * (i / steps);
30
+ stops.push(hueToHex(hue));
31
+ }
32
+ return `linear-gradient(to right, ${stops.join(', ')})`;
33
+ }
34
+
35
+ export function getHueSliderStyles(tokens: ResolvedTokens) {
36
+ return StyleSheet.create({
37
+ container: {
38
+ gap: 4,
39
+ },
40
+ labelRow: {
41
+ flexDirection: 'row',
42
+ justifyContent: 'space-between',
43
+ alignItems: 'center',
44
+ },
45
+ label: {
46
+ fontSize: 12,
47
+ fontWeight: '600',
48
+ color: srgbToHex(tokens.textSecondary.srgb),
49
+ },
50
+ value: {
51
+ fontSize: 12,
52
+ fontWeight: '500',
53
+ color: srgbToHex(tokens.textPrimary.srgb),
54
+ },
55
+ sliderTrack: {
56
+ height: 22,
57
+ borderRadius: 11,
58
+ overflow: 'hidden',
59
+ },
60
+ });
61
+ }
62
+
63
+ export function getHueSliderInputStyle(
64
+ disabled: boolean,
65
+ min: number = 0,
66
+ max: number = 359,
67
+ ): React.CSSProperties {
68
+ return {
69
+ width: '100%',
70
+ height: 22,
71
+ borderRadius: 11,
72
+ cursor: disabled ? 'default' : 'pointer',
73
+ opacity: disabled ? 0.5 : 1,
74
+ background: buildHueGradient(min, max),
75
+ appearance: 'auto' as const,
76
+ };
77
+ }
@@ -0,0 +1,70 @@
1
+ import React from 'react';
2
+ import { View, Text } from 'react-native';
3
+ import type { HueSliderProps } from './HueSlider.types';
4
+ import { useTokens } from '../tokens/useTokens';
5
+ import { getHueSliderStyles, getHueSliderInputStyle } from './HueSlider.styles';
6
+
7
+ /**
8
+ * Hue slider with rainbow gradient track.
9
+ *
10
+ * Value range: 0-359 (traditional color wheel hue).
11
+ * 0=red, 60=yellow, 120=green, 180=cyan, 240=blue, 300=magenta.
12
+ *
13
+ * Optional min/max constrain the selectable range.
14
+ * For wrapping ranges (e.g. red: min=345 max=375), max may exceed 359;
15
+ * the returned value is always normalized to [0, 359].
16
+ */
17
+ export function HueSlider({
18
+ value,
19
+ onValueChange,
20
+ min = 0,
21
+ max = 359,
22
+ label,
23
+ showValue = false,
24
+ disabled = false,
25
+ style,
26
+ }: HueSliderProps) {
27
+ const tokens = useTokens(1);
28
+
29
+ const styles = React.useMemo(
30
+ () => getHueSliderStyles(tokens),
31
+ [tokens]
32
+ );
33
+
34
+ const inputStyle = React.useMemo(
35
+ () => getHueSliderInputStyle(disabled, min, max),
36
+ [disabled, min, max]
37
+ );
38
+
39
+ // For wrapping ranges (max > 359), convert stored value to slider range.
40
+ const sliderValue = (max > 359 && value < min) ? value + 360 : value;
41
+
42
+ const handleChange = React.useCallback(
43
+ (e: React.ChangeEvent<HTMLInputElement>) => {
44
+ const raw = Number(e.target.value);
45
+ onValueChange(((raw % 360) + 360) % 360);
46
+ },
47
+ [onValueChange]
48
+ );
49
+
50
+ return (
51
+ <View style={[styles.container, ...(Array.isArray(style) ? style : [style])]}>
52
+ {(label || showValue) && (
53
+ <View style={styles.labelRow}>
54
+ {label && <Text style={styles.label}>{label}</Text>}
55
+ {showValue && <Text style={styles.value}>{value}°</Text>}
56
+ </View>
57
+ )}
58
+ <input
59
+ type="range"
60
+ min={min}
61
+ max={max}
62
+ step={1}
63
+ value={sliderValue}
64
+ onChange={handleChange}
65
+ disabled={disabled}
66
+ style={inputStyle}
67
+ />
68
+ </View>
69
+ );
70
+ }
@@ -0,0 +1,12 @@
1
+ import type { ViewStyle } from 'react-native';
2
+
3
+ export interface HueSliderProps {
4
+ readonly value: number;
5
+ readonly onValueChange: (value: number) => void;
6
+ readonly min?: number;
7
+ readonly max?: number;
8
+ readonly label?: string;
9
+ readonly showValue?: boolean;
10
+ readonly disabled?: boolean;
11
+ readonly style?: ViewStyle | ViewStyle[];
12
+ }
@@ -0,0 +1,2 @@
1
+ export { HueSlider } from './HueSlider';
2
+ export type { HueSliderProps } from './HueSlider.types';
@@ -0,0 +1,29 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { srgbToHex } from 'newtone';
3
+ import type { ResolvedTokens } from '../tokens/types';
4
+
5
+ export function getSelectStyles(tokens: ResolvedTokens, disabled: boolean) {
6
+ return StyleSheet.create({
7
+ container: {
8
+ gap: 4,
9
+ },
10
+ label: {
11
+ fontSize: 12,
12
+ fontWeight: '600',
13
+ color: srgbToHex(tokens.textSecondary.srgb),
14
+ },
15
+ select: {
16
+ backgroundColor: srgbToHex(tokens.backgroundSunken.srgb),
17
+ borderWidth: 1,
18
+ borderColor: srgbToHex(tokens.border.srgb),
19
+ borderRadius: 6,
20
+ paddingVertical: 8,
21
+ paddingHorizontal: 12,
22
+ fontSize: 14,
23
+ color: disabled
24
+ ? srgbToHex(tokens.textSecondary.srgb)
25
+ : srgbToHex(tokens.textPrimary.srgb),
26
+ opacity: disabled ? 0.5 : 1,
27
+ },
28
+ });
29
+ }
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import { View, Text, StyleSheet } from 'react-native';
3
+ import type { SelectProps } from './Select.types';
4
+ import { useTokens } from '../tokens/useTokens';
5
+ import { getSelectStyles } from './Select.styles';
6
+
7
+ /**
8
+ * Select dropdown component.
9
+ *
10
+ * Renders a native HTML <select> element styled with design tokens.
11
+ * On web (via react-native-web), View renders as a div which can contain
12
+ * standard HTML elements.
13
+ */
14
+ export function Select({
15
+ options,
16
+ value,
17
+ onValueChange,
18
+ label,
19
+ disabled = false,
20
+ style,
21
+ }: SelectProps) {
22
+ const tokens = useTokens(1);
23
+
24
+ const styles = React.useMemo(
25
+ () => getSelectStyles(tokens, disabled),
26
+ [tokens, disabled]
27
+ );
28
+
29
+ const handleChange = React.useCallback(
30
+ (e: React.ChangeEvent<HTMLSelectElement>) => {
31
+ onValueChange(e.target.value);
32
+ },
33
+ [onValueChange]
34
+ );
35
+
36
+ // Flatten StyleSheet styles into inline style for the native <select>
37
+ const selectStyle = StyleSheet.flatten(styles.select);
38
+
39
+ return (
40
+ <View style={[styles.container, ...(Array.isArray(style) ? style : [style])]}>
41
+ {label && <Text style={styles.label}>{label}</Text>}
42
+ <select
43
+ value={value}
44
+ onChange={handleChange}
45
+ disabled={disabled}
46
+ style={{
47
+ ...selectStyle,
48
+ cursor: disabled ? 'default' : 'pointer',
49
+ appearance: 'auto',
50
+ } as React.CSSProperties}
51
+ >
52
+ {options.map((option) => (
53
+ <option key={option.value} value={option.value}>
54
+ {option.label}
55
+ </option>
56
+ ))}
57
+ </select>
58
+ </View>
59
+ );
60
+ }
@@ -0,0 +1,15 @@
1
+ import type { ViewStyle } from 'react-native';
2
+
3
+ export interface SelectOption {
4
+ readonly label: string;
5
+ readonly value: string;
6
+ }
7
+
8
+ export interface SelectProps {
9
+ readonly options: readonly SelectOption[];
10
+ readonly value: string;
11
+ readonly onValueChange: (value: string) => void;
12
+ readonly label?: string;
13
+ readonly disabled?: boolean;
14
+ readonly style?: ViewStyle | ViewStyle[];
15
+ }
@@ -0,0 +1,2 @@
1
+ export { Select } from './Select';
2
+ export type { SelectProps, SelectOption } from './Select.types';
@@ -0,0 +1,45 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { srgbToHex } from 'newtone';
3
+ import type { ResolvedTokens } from '../tokens/types';
4
+
5
+ export function getSliderStyles(tokens: ResolvedTokens) {
6
+ return StyleSheet.create({
7
+ container: {
8
+ gap: 4,
9
+ },
10
+ labelRow: {
11
+ flexDirection: 'row',
12
+ justifyContent: 'space-between',
13
+ alignItems: 'center',
14
+ },
15
+ label: {
16
+ fontSize: 12,
17
+ fontWeight: '600',
18
+ color: srgbToHex(tokens.textSecondary.srgb),
19
+ },
20
+ value: {
21
+ fontSize: 12,
22
+ fontWeight: '500',
23
+ color: srgbToHex(tokens.textPrimary.srgb),
24
+ },
25
+ });
26
+ }
27
+
28
+ /**
29
+ * Generate CSS for the range input styling.
30
+ * Returns inline CSS properties for the native <input type="range">.
31
+ */
32
+ export function getSliderInputStyle(
33
+ tokens: ResolvedTokens,
34
+ disabled: boolean
35
+ ): React.CSSProperties {
36
+ return {
37
+ width: '100%',
38
+ height: 6,
39
+ borderRadius: 3,
40
+ appearance: 'auto' as const,
41
+ cursor: disabled ? 'default' : 'pointer',
42
+ opacity: disabled ? 0.5 : 1,
43
+ accentColor: srgbToHex(tokens.interactive.srgb),
44
+ };
45
+ }
@@ -0,0 +1,57 @@
1
+ import React from 'react';
2
+ import { View, Text } from 'react-native';
3
+ import type { SliderProps } from './Slider.types';
4
+ import { useTokens } from '../tokens/useTokens';
5
+ import { getSliderStyles, getSliderInputStyle } from './Slider.styles';
6
+
7
+ export function Slider({
8
+ value,
9
+ onValueChange,
10
+ min = 0,
11
+ max = 100,
12
+ step = 1,
13
+ label,
14
+ showValue = false,
15
+ disabled = false,
16
+ style,
17
+ }: SliderProps) {
18
+ const tokens = useTokens(1);
19
+
20
+ const styles = React.useMemo(
21
+ () => getSliderStyles(tokens),
22
+ [tokens]
23
+ );
24
+
25
+ const inputStyle = React.useMemo(
26
+ () => getSliderInputStyle(tokens, disabled),
27
+ [tokens, disabled]
28
+ );
29
+
30
+ const handleChange = React.useCallback(
31
+ (e: React.ChangeEvent<HTMLInputElement>) => {
32
+ onValueChange(Number(e.target.value));
33
+ },
34
+ [onValueChange]
35
+ );
36
+
37
+ return (
38
+ <View style={[styles.container, ...(Array.isArray(style) ? style : [style])]}>
39
+ {(label || showValue) && (
40
+ <View style={styles.labelRow}>
41
+ {label && <Text style={styles.label}>{label}</Text>}
42
+ {showValue && <Text style={styles.value}>{value}</Text>}
43
+ </View>
44
+ )}
45
+ <input
46
+ type="range"
47
+ min={min}
48
+ max={max}
49
+ step={step}
50
+ value={value}
51
+ onChange={handleChange}
52
+ disabled={disabled}
53
+ style={inputStyle}
54
+ />
55
+ </View>
56
+ );
57
+ }
@@ -0,0 +1,13 @@
1
+ import type { ViewStyle } from 'react-native';
2
+
3
+ export interface SliderProps {
4
+ readonly value: number;
5
+ readonly onValueChange: (value: number) => void;
6
+ readonly min?: number;
7
+ readonly max?: number;
8
+ readonly step?: number;
9
+ readonly label?: string;
10
+ readonly showValue?: boolean;
11
+ readonly disabled?: boolean;
12
+ readonly style?: ViewStyle | ViewStyle[];
13
+ }
@@ -0,0 +1,2 @@
1
+ export { Slider } from './Slider';
2
+ export type { SliderProps } from './Slider.types';
@@ -0,0 +1,29 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { srgbToHex } from 'newtone';
3
+ import type { ResolvedTokens } from '../tokens/types';
4
+
5
+ export function getTextInputStyles(tokens: ResolvedTokens, disabled: boolean) {
6
+ return StyleSheet.create({
7
+ container: {
8
+ gap: 4,
9
+ },
10
+ label: {
11
+ fontSize: 12,
12
+ fontWeight: '600',
13
+ color: srgbToHex(tokens.textSecondary.srgb),
14
+ },
15
+ input: {
16
+ backgroundColor: srgbToHex(tokens.backgroundSunken.srgb),
17
+ borderWidth: 1,
18
+ borderColor: srgbToHex(tokens.border.srgb),
19
+ borderRadius: 6,
20
+ paddingVertical: 8,
21
+ paddingHorizontal: 12,
22
+ fontSize: 14,
23
+ color: disabled
24
+ ? srgbToHex(tokens.textSecondary.srgb)
25
+ : srgbToHex(tokens.textPrimary.srgb),
26
+ opacity: disabled ? 0.5 : 1,
27
+ },
28
+ });
29
+ }
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { View, Text, TextInput as RNTextInput } from 'react-native';
3
+ import type { TextInputProps } from './TextInput.types';
4
+ import { useTokens } from '../tokens/useTokens';
5
+ import { getTextInputStyles } from './TextInput.styles';
6
+ import { srgbToHex } from 'newtone';
7
+
8
+ export function TextInput({
9
+ label,
10
+ disabled = false,
11
+ style,
12
+ ...textInputProps
13
+ }: TextInputProps) {
14
+ const tokens = useTokens(1);
15
+
16
+ const styles = React.useMemo(
17
+ () => getTextInputStyles(tokens, disabled),
18
+ [tokens, disabled]
19
+ );
20
+
21
+ return (
22
+ <View style={[styles.container, ...(Array.isArray(style) ? style : [style])]}>
23
+ {label && <Text style={styles.label}>{label}</Text>}
24
+ <RNTextInput
25
+ style={styles.input}
26
+ editable={!disabled}
27
+ placeholderTextColor={srgbToHex(tokens.textSecondary.srgb)}
28
+ {...textInputProps}
29
+ />
30
+ </View>
31
+ );
32
+ }
@@ -0,0 +1,7 @@
1
+ import type { TextInputProps as RNTextInputProps, ViewStyle } from 'react-native';
2
+
3
+ export interface TextInputProps extends Omit<RNTextInputProps, 'style'> {
4
+ readonly label?: string;
5
+ readonly disabled?: boolean;
6
+ readonly style?: ViewStyle | ViewStyle[];
7
+ }
@@ -0,0 +1,2 @@
1
+ export { TextInput } from './TextInput';
2
+ export type { TextInputProps } from './TextInput.types';
@@ -0,0 +1,45 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { srgbToHex } from 'newtone';
3
+ import type { ResolvedTokens } from '../tokens/types';
4
+
5
+ const TRACK_WIDTH = 40;
6
+ const TRACK_HEIGHT = 22;
7
+ const THUMB_SIZE = 18;
8
+ const THUMB_OFFSET = 2;
9
+
10
+ export function getToggleStyles(
11
+ tokens: ResolvedTokens,
12
+ value: boolean,
13
+ disabled: boolean
14
+ ) {
15
+ return StyleSheet.create({
16
+ container: {
17
+ flexDirection: 'row',
18
+ alignItems: 'center',
19
+ gap: 8,
20
+ opacity: disabled ? 0.5 : 1,
21
+ },
22
+ label: {
23
+ fontSize: 12,
24
+ fontWeight: '600',
25
+ color: srgbToHex(tokens.textSecondary.srgb),
26
+ },
27
+ track: {
28
+ width: TRACK_WIDTH,
29
+ height: TRACK_HEIGHT,
30
+ borderRadius: TRACK_HEIGHT / 2,
31
+ backgroundColor: value
32
+ ? srgbToHex(tokens.interactive.srgb)
33
+ : srgbToHex(tokens.border.srgb),
34
+ justifyContent: 'center',
35
+ paddingHorizontal: THUMB_OFFSET,
36
+ },
37
+ thumb: {
38
+ width: THUMB_SIZE,
39
+ height: THUMB_SIZE,
40
+ borderRadius: THUMB_SIZE / 2,
41
+ backgroundColor: srgbToHex(tokens.background.srgb),
42
+ alignSelf: value ? 'flex-end' : 'flex-start',
43
+ },
44
+ });
45
+ }
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import { View, Text, Pressable } from 'react-native';
3
+ import type { ToggleProps } from './Toggle.types';
4
+ import { useTokens } from '../tokens/useTokens';
5
+ import { getToggleStyles } from './Toggle.styles';
6
+
7
+ export function Toggle({
8
+ value,
9
+ onValueChange,
10
+ label,
11
+ disabled = false,
12
+ style,
13
+ }: ToggleProps) {
14
+ const tokens = useTokens(1);
15
+
16
+ const styles = React.useMemo(
17
+ () => getToggleStyles(tokens, value, disabled),
18
+ [tokens, value, disabled]
19
+ );
20
+
21
+ const handlePress = React.useCallback(() => {
22
+ if (!disabled) {
23
+ onValueChange(!value);
24
+ }
25
+ }, [disabled, value, onValueChange]);
26
+
27
+ return (
28
+ <View style={[styles.container, ...(Array.isArray(style) ? style : [style])]}>
29
+ {label && <Text style={styles.label}>{label}</Text>}
30
+ <Pressable
31
+ onPress={handlePress}
32
+ disabled={disabled}
33
+ accessibilityRole="switch"
34
+ accessibilityState={{ checked: value, disabled }}
35
+ >
36
+ <View style={styles.track}>
37
+ <View style={styles.thumb} />
38
+ </View>
39
+ </Pressable>
40
+ </View>
41
+ );
42
+ }
@@ -0,0 +1,9 @@
1
+ import type { ViewStyle } from 'react-native';
2
+
3
+ export interface ToggleProps {
4
+ readonly value: boolean;
5
+ readonly onValueChange: (value: boolean) => void;
6
+ readonly label?: string;
7
+ readonly disabled?: boolean;
8
+ readonly style?: ViewStyle | ViewStyle[];
9
+ }
@@ -0,0 +1,2 @@
1
+ export { Toggle } from './Toggle';
2
+ export type { ToggleProps } from './Toggle.types';
package/src/index.ts ADDED
@@ -0,0 +1,49 @@
1
+ // Theme system
2
+ export { NewtoneProvider, useNewtoneTheme } from './theme/NewtoneProvider';
3
+ export type {
4
+ ColorMode,
5
+ ThemeName,
6
+ ElevationLevel,
7
+ ThemeMapping,
8
+ ColorSystemConfig,
9
+ NewtoneThemeConfig,
10
+ NewtoneThemeContext,
11
+ } from './theme/types';
12
+ export { DEFAULT_THEME_CONFIG } from './theme/defaults';
13
+
14
+ // Tokens
15
+ export { useTokens } from './tokens/useTokens';
16
+ export { computeTokens } from './tokens/computeTokens';
17
+ export type { ResolvedTokens } from './tokens/types';
18
+
19
+ // Components
20
+ export { Button } from './Button/Button';
21
+ export type { ButtonProps, ButtonVariant, ButtonSize } from './Button/Button.types';
22
+
23
+ export { Card } from './Card/Card';
24
+ export type { CardProps } from './Card/Card.types';
25
+
26
+ export { TextInput } from './TextInput/TextInput';
27
+ export type { TextInputProps } from './TextInput/TextInput.types';
28
+
29
+ export { Select } from './Select/Select';
30
+ export type { SelectProps, SelectOption } from './Select/Select.types';
31
+
32
+ export { Toggle } from './Toggle/Toggle';
33
+ export type { ToggleProps } from './Toggle/Toggle.types';
34
+
35
+ export { Slider } from './Slider/Slider';
36
+ export type { SliderProps } from './Slider/Slider.types';
37
+
38
+ export { HueSlider } from './HueSlider/HueSlider';
39
+ export type { HueSliderProps } from './HueSlider/HueSlider.types';
40
+
41
+ // Re-export core engine types for convenience
42
+ export type {
43
+ DynamicRange,
44
+ PaletteConfig,
45
+ ColorResult,
46
+ Srgb,
47
+ Oklch,
48
+ HexColor,
49
+ } from 'newtone';