@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
|
@@ -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,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,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,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,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,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
|
+
}
|
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';
|