@newtonedev/components 0.1.0 → 0.1.2

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 (181) hide show
  1. package/dist/AppShell/AppShell.d.ts +4 -0
  2. package/dist/AppShell/AppShell.d.ts.map +1 -0
  3. package/dist/AppShell/AppShell.styles.d.ts +16 -0
  4. package/dist/AppShell/AppShell.styles.d.ts.map +1 -0
  5. package/dist/AppShell/AppShell.types.d.ts +8 -0
  6. package/dist/AppShell/AppShell.types.d.ts.map +1 -0
  7. package/dist/AppShell/index.d.ts +3 -0
  8. package/dist/AppShell/index.d.ts.map +1 -0
  9. package/dist/Button/Button.d.ts +9 -4
  10. package/dist/Button/Button.d.ts.map +1 -1
  11. package/dist/Button/Button.styles.d.ts +33 -26
  12. package/dist/Button/Button.styles.d.ts.map +1 -1
  13. package/dist/Button/Button.types.d.ts +17 -2
  14. package/dist/Button/Button.types.d.ts.map +1 -1
  15. package/dist/ColorScaleSlider/ColorScaleSlider.d.ts +13 -0
  16. package/dist/ColorScaleSlider/ColorScaleSlider.d.ts.map +1 -0
  17. package/dist/ColorScaleSlider/ColorScaleSlider.styles.d.ts +54 -0
  18. package/dist/ColorScaleSlider/ColorScaleSlider.styles.d.ts.map +1 -0
  19. package/dist/ColorScaleSlider/ColorScaleSlider.types.d.ts +25 -0
  20. package/dist/ColorScaleSlider/ColorScaleSlider.types.d.ts.map +1 -0
  21. package/dist/ColorScaleSlider/index.d.ts +3 -0
  22. package/dist/ColorScaleSlider/index.d.ts.map +1 -0
  23. package/dist/Frame/Frame.d.ts +48 -0
  24. package/dist/Frame/Frame.d.ts.map +1 -0
  25. package/dist/Frame/Frame.styles.d.ts +39 -0
  26. package/dist/Frame/Frame.styles.d.ts.map +1 -0
  27. package/dist/Frame/Frame.types.d.ts +115 -0
  28. package/dist/Frame/Frame.types.d.ts.map +1 -0
  29. package/dist/Frame/Frame.utils.d.ts +39 -0
  30. package/dist/Frame/Frame.utils.d.ts.map +1 -0
  31. package/dist/Frame/index.d.ts +4 -0
  32. package/dist/Frame/index.d.ts.map +1 -0
  33. package/dist/HueSlider/HueSlider.d.ts +1 -1
  34. package/dist/HueSlider/HueSlider.d.ts.map +1 -1
  35. package/dist/HueSlider/HueSlider.styles.d.ts +47 -5
  36. package/dist/HueSlider/HueSlider.styles.d.ts.map +1 -1
  37. package/dist/HueSlider/HueSlider.types.d.ts +1 -0
  38. package/dist/HueSlider/HueSlider.types.d.ts.map +1 -1
  39. package/dist/Icon/Icon.d.ts +36 -0
  40. package/dist/Icon/Icon.d.ts.map +1 -0
  41. package/dist/Navbar/Navbar.d.ts +4 -0
  42. package/dist/Navbar/Navbar.d.ts.map +1 -0
  43. package/dist/Navbar/Navbar.styles.d.ts +31 -0
  44. package/dist/Navbar/Navbar.styles.d.ts.map +1 -0
  45. package/dist/Navbar/Navbar.types.d.ts +14 -0
  46. package/dist/Navbar/Navbar.types.d.ts.map +1 -0
  47. package/dist/Navbar/index.d.ts +3 -0
  48. package/dist/Navbar/index.d.ts.map +1 -0
  49. package/dist/Popover/Popover.d.ts +4 -0
  50. package/dist/Popover/Popover.d.ts.map +1 -0
  51. package/dist/Popover/Popover.styles.d.ts +9 -0
  52. package/dist/Popover/Popover.styles.d.ts.map +1 -0
  53. package/dist/Popover/Popover.types.d.ts +37 -0
  54. package/dist/Popover/Popover.types.d.ts.map +1 -0
  55. package/dist/Popover/index.d.ts +4 -0
  56. package/dist/Popover/index.d.ts.map +1 -0
  57. package/dist/Popover/usePopover.d.ts +3 -0
  58. package/dist/Popover/usePopover.d.ts.map +1 -0
  59. package/dist/Select/Select.d.ts +1 -8
  60. package/dist/Select/Select.d.ts.map +1 -1
  61. package/dist/Select/Select.styles.d.ts +32 -5
  62. package/dist/Select/Select.styles.d.ts.map +1 -1
  63. package/dist/Select/Select.types.d.ts +25 -1
  64. package/dist/Select/Select.types.d.ts.map +1 -1
  65. package/dist/Select/SelectOption.d.ts +13 -0
  66. package/dist/Select/SelectOption.d.ts.map +1 -0
  67. package/dist/Select/useSelect.d.ts +15 -0
  68. package/dist/Select/useSelect.d.ts.map +1 -0
  69. package/dist/Sidebar/Sidebar.d.ts +4 -0
  70. package/dist/Sidebar/Sidebar.d.ts.map +1 -0
  71. package/dist/Sidebar/Sidebar.styles.d.ts +31 -0
  72. package/dist/Sidebar/Sidebar.styles.d.ts.map +1 -0
  73. package/dist/Sidebar/Sidebar.types.d.ts +14 -0
  74. package/dist/Sidebar/Sidebar.types.d.ts.map +1 -0
  75. package/dist/Sidebar/index.d.ts +3 -0
  76. package/dist/Sidebar/index.d.ts.map +1 -0
  77. package/dist/Slider/Slider.d.ts +1 -1
  78. package/dist/Slider/Slider.d.ts.map +1 -1
  79. package/dist/Slider/Slider.styles.d.ts +48 -8
  80. package/dist/Slider/Slider.styles.d.ts.map +1 -1
  81. package/dist/Slider/Slider.types.d.ts +1 -0
  82. package/dist/Slider/Slider.types.d.ts.map +1 -1
  83. package/dist/TextInput/TextInput.styles.d.ts +3 -1
  84. package/dist/TextInput/TextInput.styles.d.ts.map +1 -1
  85. package/dist/Toggle/Toggle.styles.d.ts +2 -1
  86. package/dist/Toggle/Toggle.styles.d.ts.map +1 -1
  87. package/dist/fonts/GoogleFontLoader.d.ts +19 -0
  88. package/dist/fonts/GoogleFontLoader.d.ts.map +1 -0
  89. package/dist/fonts/IconFontLoader.d.ts +13 -0
  90. package/dist/fonts/IconFontLoader.d.ts.map +1 -0
  91. package/dist/fonts/buildGoogleFontsUrl.d.ts +17 -0
  92. package/dist/fonts/buildGoogleFontsUrl.d.ts.map +1 -0
  93. package/dist/fonts/googleFonts.d.ts +20 -0
  94. package/dist/fonts/googleFonts.d.ts.map +1 -0
  95. package/dist/index.cjs +2303 -205
  96. package/dist/index.cjs.map +1 -1
  97. package/dist/index.d.ts +27 -3
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +2279 -200
  100. package/dist/index.js.map +1 -1
  101. package/dist/registry/codegen.d.ts +11 -0
  102. package/dist/registry/codegen.d.ts.map +1 -0
  103. package/dist/registry/index.d.ts +4 -0
  104. package/dist/registry/index.d.ts.map +1 -0
  105. package/dist/registry/registry.d.ts +7 -0
  106. package/dist/registry/registry.d.ts.map +1 -0
  107. package/dist/registry/types.d.ts +32 -0
  108. package/dist/registry/types.d.ts.map +1 -0
  109. package/dist/theme/FrameContext.d.ts +24 -0
  110. package/dist/theme/FrameContext.d.ts.map +1 -0
  111. package/dist/theme/NewtoneProvider.d.ts.map +1 -1
  112. package/dist/theme/defaults.d.ts.map +1 -1
  113. package/dist/theme/types.d.ts +64 -1
  114. package/dist/theme/types.d.ts.map +1 -1
  115. package/dist/tokens/computeTokens.d.ts +55 -3
  116. package/dist/tokens/computeTokens.d.ts.map +1 -1
  117. package/dist/tokens/types.d.ts +52 -0
  118. package/dist/tokens/types.d.ts.map +1 -1
  119. package/dist/tokens/useTokens.d.ts +12 -9
  120. package/dist/tokens/useTokens.d.ts.map +1 -1
  121. package/package.json +1 -1
  122. package/src/AppShell/AppShell.styles.ts +20 -0
  123. package/src/AppShell/AppShell.tsx +17 -0
  124. package/src/AppShell/AppShell.types.ts +8 -0
  125. package/src/AppShell/index.ts +2 -0
  126. package/src/Button/Button.styles.ts +74 -41
  127. package/src/Button/Button.tsx +36 -17
  128. package/src/Button/Button.types.ts +20 -2
  129. package/src/Card/Card.styles.ts +2 -2
  130. package/src/ColorScaleSlider/ColorScaleSlider.styles.ts +60 -0
  131. package/src/ColorScaleSlider/ColorScaleSlider.tsx +156 -0
  132. package/src/ColorScaleSlider/ColorScaleSlider.types.ts +25 -0
  133. package/src/ColorScaleSlider/index.ts +2 -0
  134. package/src/Frame/Frame.styles.ts +213 -0
  135. package/src/Frame/Frame.tsx +242 -0
  136. package/src/Frame/Frame.types.ts +181 -0
  137. package/src/Frame/Frame.utils.ts +189 -0
  138. package/src/Frame/index.ts +21 -0
  139. package/src/HueSlider/HueSlider.styles.ts +58 -39
  140. package/src/HueSlider/HueSlider.tsx +97 -25
  141. package/src/HueSlider/HueSlider.types.ts +1 -0
  142. package/src/Icon/Icon.tsx +76 -0
  143. package/src/Navbar/Navbar.styles.ts +37 -0
  144. package/src/Navbar/Navbar.tsx +32 -0
  145. package/src/Navbar/Navbar.types.ts +14 -0
  146. package/src/Navbar/index.ts +2 -0
  147. package/src/Popover/Popover.styles.ts +39 -0
  148. package/src/Popover/Popover.tsx +103 -0
  149. package/src/Popover/Popover.types.ts +40 -0
  150. package/src/Popover/index.ts +3 -0
  151. package/src/Popover/usePopover.ts +26 -0
  152. package/src/Select/Select.styles.ts +49 -10
  153. package/src/Select/Select.tsx +127 -36
  154. package/src/Select/Select.types.ts +30 -1
  155. package/src/Select/SelectOption.tsx +104 -0
  156. package/src/Select/useSelect.ts +129 -0
  157. package/src/Sidebar/Sidebar.styles.ts +37 -0
  158. package/src/Sidebar/Sidebar.tsx +27 -0
  159. package/src/Sidebar/Sidebar.types.ts +14 -0
  160. package/src/Sidebar/index.ts +2 -0
  161. package/src/Slider/Slider.styles.ts +53 -25
  162. package/src/Slider/Slider.tsx +89 -24
  163. package/src/Slider/Slider.types.ts +1 -0
  164. package/src/TextInput/TextInput.styles.ts +9 -7
  165. package/src/Toggle/Toggle.styles.ts +4 -3
  166. package/src/fonts/GoogleFontLoader.tsx +63 -0
  167. package/src/fonts/IconFontLoader.tsx +49 -0
  168. package/src/fonts/buildGoogleFontsUrl.ts +31 -0
  169. package/src/fonts/googleFonts.ts +87 -0
  170. package/src/index.ts +70 -2
  171. package/src/registry/codegen.ts +132 -0
  172. package/src/registry/index.ts +17 -0
  173. package/src/registry/registry.ts +402 -0
  174. package/src/registry/types.ts +35 -0
  175. package/src/theme/FrameContext.tsx +29 -0
  176. package/src/theme/NewtoneProvider.tsx +9 -1
  177. package/src/theme/defaults.ts +51 -0
  178. package/src/theme/types.ts +66 -1
  179. package/src/tokens/computeTokens.ts +103 -46
  180. package/src/tokens/types.ts +52 -0
  181. package/src/tokens/useTokens.ts +30 -15
@@ -2,10 +2,14 @@ import { StyleSheet } from 'react-native';
2
2
  import { srgbToHex } from 'newtone';
3
3
  import type { ResolvedTokens } from '../tokens/types';
4
4
 
5
- export function getSliderStyles(tokens: ResolvedTokens) {
5
+ const TRACK_HEIGHT = 6;
6
+ export const THUMB_SIZE = 16;
7
+
8
+ export function getSliderStyles(tokens: ResolvedTokens, disabled: boolean) {
6
9
  return StyleSheet.create({
7
10
  container: {
8
- gap: 4,
11
+ gap: tokens.spacing.xs,
12
+ opacity: disabled ? 0.5 : 1,
9
13
  },
10
14
  labelRow: {
11
15
  flexDirection: 'row',
@@ -13,33 +17,57 @@ export function getSliderStyles(tokens: ResolvedTokens) {
13
17
  alignItems: 'center',
14
18
  },
15
19
  label: {
16
- fontSize: 12,
17
- fontWeight: '600',
20
+ fontFamily: tokens.typography.fonts.default,
21
+ fontSize: tokens.typography.size.sm,
22
+ fontWeight: tokens.typography.weight.semibold as any,
18
23
  color: srgbToHex(tokens.textSecondary.srgb),
19
24
  },
20
25
  value: {
21
- fontSize: 12,
22
- fontWeight: '500',
26
+ fontFamily: tokens.typography.fonts.default,
27
+ fontSize: tokens.typography.size.sm,
28
+ fontWeight: tokens.typography.weight.medium as any,
29
+ color: srgbToHex(tokens.textPrimary.srgb),
30
+ },
31
+ valueInput: {
32
+ width: 48,
33
+ paddingVertical: 0,
34
+ paddingHorizontal: 4,
35
+ borderWidth: 1,
36
+ borderColor: srgbToHex(tokens.border.srgb),
37
+ borderRadius: 4,
38
+ backgroundColor: 'transparent',
23
39
  color: srgbToHex(tokens.textPrimary.srgb),
40
+ fontFamily: tokens.typography.fonts.default,
41
+ fontSize: tokens.typography.size.sm,
42
+ fontWeight: tokens.typography.weight.medium as any,
43
+ textAlign: 'right',
44
+ },
45
+ trackContainer: {
46
+ height: TRACK_HEIGHT + THUMB_SIZE,
47
+ justifyContent: 'center',
48
+ position: 'relative',
49
+ },
50
+ trackRail: {
51
+ position: 'absolute',
52
+ left: 0,
53
+ right: 0,
54
+ height: TRACK_HEIGHT,
55
+ borderRadius: TRACK_HEIGHT / 2,
56
+ backgroundColor: srgbToHex(tokens.border.srgb),
57
+ },
58
+ trackFill: {
59
+ position: 'absolute',
60
+ left: 0,
61
+ height: TRACK_HEIGHT,
62
+ borderRadius: TRACK_HEIGHT / 2,
63
+ backgroundColor: srgbToHex(tokens.interactive.srgb),
64
+ },
65
+ thumb: {
66
+ position: 'absolute',
67
+ width: THUMB_SIZE,
68
+ height: THUMB_SIZE,
69
+ borderRadius: THUMB_SIZE / 2,
70
+ backgroundColor: srgbToHex(tokens.interactive.srgb),
24
71
  },
25
72
  });
26
73
  }
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
- }
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
- import { View, Text } from 'react-native';
2
+ import { View, Text, TextInput, PanResponder } from 'react-native';
3
3
  import type { SliderProps } from './Slider.types';
4
4
  import { useTokens } from '../tokens/useTokens';
5
- import { getSliderStyles, getSliderInputStyle } from './Slider.styles';
5
+ import { getSliderStyles, THUMB_SIZE } from './Slider.styles';
6
6
 
7
7
  export function Slider({
8
8
  value,
@@ -12,46 +12,111 @@ export function Slider({
12
12
  step = 1,
13
13
  label,
14
14
  showValue = false,
15
+ editableValue = false,
15
16
  disabled = false,
16
17
  style,
17
18
  }: SliderProps) {
18
19
  const tokens = useTokens(1);
19
20
 
20
21
  const styles = React.useMemo(
21
- () => getSliderStyles(tokens),
22
- [tokens]
23
- );
24
-
25
- const inputStyle = React.useMemo(
26
- () => getSliderInputStyle(tokens, disabled),
22
+ () => getSliderStyles(tokens, disabled),
27
23
  [tokens, disabled]
28
24
  );
29
25
 
30
- const handleChange = React.useCallback(
31
- (e: React.ChangeEvent<HTMLInputElement>) => {
32
- onValueChange(Number(e.target.value));
26
+ const trackRef = React.useRef<View>(null);
27
+ const trackWidth = React.useRef(0);
28
+ const trackPageX = React.useRef(0);
29
+
30
+ // Mutable refs to avoid stale closures in PanResponder
31
+ const onValueChangeRef = React.useRef(onValueChange);
32
+ const minRef = React.useRef(min);
33
+ const maxRef = React.useRef(max);
34
+ const stepRef = React.useRef(step);
35
+ const disabledRef = React.useRef(disabled);
36
+
37
+ React.useEffect(() => { onValueChangeRef.current = onValueChange; }, [onValueChange]);
38
+ React.useEffect(() => { minRef.current = min; }, [min]);
39
+ React.useEffect(() => { maxRef.current = max; }, [max]);
40
+ React.useEffect(() => { stepRef.current = step; }, [step]);
41
+ React.useEffect(() => { disabledRef.current = disabled; }, [disabled]);
42
+
43
+ const computeValue = React.useCallback((pageX: number) => {
44
+ const localX = pageX - trackPageX.current;
45
+ const ratio = Math.min(1, Math.max(0, localX / trackWidth.current));
46
+ const raw = minRef.current + ratio * (maxRef.current - minRef.current);
47
+ const stepped = Math.round(raw / stepRef.current) * stepRef.current;
48
+ return Math.min(maxRef.current, Math.max(minRef.current, stepped));
49
+ }, []);
50
+
51
+ const panResponder = React.useRef(
52
+ PanResponder.create({
53
+ onStartShouldSetPanResponder: () => !disabledRef.current,
54
+ onMoveShouldSetPanResponder: () => !disabledRef.current,
55
+ onPanResponderGrant: (evt) => {
56
+ onValueChangeRef.current(computeValue(evt.nativeEvent.pageX));
57
+ },
58
+ onPanResponderMove: (_evt, gestureState) => {
59
+ onValueChangeRef.current(computeValue(gestureState.moveX));
60
+ },
61
+ })
62
+ ).current;
63
+
64
+ const ratio = max > min ? (value - min) / (max - min) : 0;
65
+ const usableWidth = Math.max(0, trackWidth.current - THUMB_SIZE);
66
+ const thumbLeft = ratio * usableWidth;
67
+ const fillWidth = thumbLeft + THUMB_SIZE / 2;
68
+
69
+ const handleValueTextSubmit = React.useCallback(
70
+ (text: string) => {
71
+ const raw = Number(text);
72
+ if (!Number.isNaN(raw)) {
73
+ onValueChange(Math.min(max, Math.max(min, raw)));
74
+ }
33
75
  },
34
- [onValueChange]
76
+ [onValueChange, min, max]
35
77
  );
36
78
 
79
+ const [editText, setEditText] = React.useState(String(value));
80
+ React.useEffect(() => { setEditText(String(value)); }, [value]);
81
+
82
+ const showLabel = label || showValue || editableValue;
83
+
37
84
  return (
38
85
  <View style={[styles.container, ...(Array.isArray(style) ? style : [style])]}>
39
- {(label || showValue) && (
86
+ {showLabel && (
40
87
  <View style={styles.labelRow}>
41
88
  {label && <Text style={styles.label}>{label}</Text>}
42
- {showValue && <Text style={styles.value}>{value}</Text>}
89
+ {editableValue ? (
90
+ <TextInput
91
+ style={styles.valueInput}
92
+ value={editText}
93
+ keyboardType="numeric"
94
+ onChangeText={setEditText}
95
+ onSubmitEditing={(e) => handleValueTextSubmit(e.nativeEvent.text)}
96
+ onBlur={() => handleValueTextSubmit(editText)}
97
+ selectTextOnFocus
98
+ editable={!disabled}
99
+ />
100
+ ) : (
101
+ showValue && <Text style={styles.value}>{value}</Text>
102
+ )}
43
103
  </View>
44
104
  )}
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
- />
105
+ <View
106
+ ref={trackRef}
107
+ style={styles.trackContainer}
108
+ onLayout={(e) => {
109
+ trackWidth.current = e.nativeEvent.layout.width;
110
+ trackRef.current?.measure((_x, _y, _w, _h, pageX) => {
111
+ if (pageX != null) trackPageX.current = pageX;
112
+ });
113
+ }}
114
+ {...panResponder.panHandlers}
115
+ >
116
+ <View style={styles.trackRail} />
117
+ <View style={[styles.trackFill, { width: fillWidth }]} />
118
+ <View style={[styles.thumb, { left: thumbLeft }]} />
119
+ </View>
55
120
  </View>
56
121
  );
57
122
  }
@@ -8,6 +8,7 @@ export interface SliderProps {
8
8
  readonly step?: number;
9
9
  readonly label?: string;
10
10
  readonly showValue?: boolean;
11
+ readonly editableValue?: boolean;
11
12
  readonly disabled?: boolean;
12
13
  readonly style?: ViewStyle | ViewStyle[];
13
14
  }
@@ -5,21 +5,23 @@ import type { ResolvedTokens } from '../tokens/types';
5
5
  export function getTextInputStyles(tokens: ResolvedTokens, disabled: boolean) {
6
6
  return StyleSheet.create({
7
7
  container: {
8
- gap: 4,
8
+ gap: tokens.spacing.xs,
9
9
  },
10
10
  label: {
11
- fontSize: 12,
12
- fontWeight: '600',
11
+ fontFamily: tokens.typography.fonts.default,
12
+ fontSize: tokens.typography.size.sm,
13
+ fontWeight: tokens.typography.weight.semibold as any,
13
14
  color: srgbToHex(tokens.textSecondary.srgb),
14
15
  },
15
16
  input: {
17
+ fontFamily: tokens.typography.fonts.default,
16
18
  backgroundColor: srgbToHex(tokens.backgroundSunken.srgb),
17
19
  borderWidth: 1,
18
20
  borderColor: srgbToHex(tokens.border.srgb),
19
- borderRadius: 6,
20
- paddingVertical: 8,
21
- paddingHorizontal: 12,
22
- fontSize: 14,
21
+ borderRadius: tokens.radius.md,
22
+ paddingVertical: tokens.spacing.sm,
23
+ paddingHorizontal: tokens.spacing.md,
24
+ fontSize: tokens.typography.size.base,
23
25
  color: disabled
24
26
  ? srgbToHex(tokens.textSecondary.srgb)
25
27
  : srgbToHex(tokens.textPrimary.srgb),
@@ -16,12 +16,13 @@ export function getToggleStyles(
16
16
  container: {
17
17
  flexDirection: 'row',
18
18
  alignItems: 'center',
19
- gap: 8,
19
+ gap: tokens.spacing.sm,
20
20
  opacity: disabled ? 0.5 : 1,
21
21
  },
22
22
  label: {
23
- fontSize: 12,
24
- fontWeight: '600',
23
+ fontFamily: tokens.typography.fonts.default,
24
+ fontSize: tokens.typography.size.sm,
25
+ fontWeight: tokens.typography.weight.semibold as any,
25
26
  color: srgbToHex(tokens.textSecondary.srgb),
26
27
  },
27
28
  track: {
@@ -0,0 +1,63 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import type { FontConfig } from '../theme/types';
3
+ import { buildGoogleFontsUrl } from './buildGoogleFontsUrl';
4
+
5
+ interface GoogleFontLoaderProps {
6
+ readonly fonts: {
7
+ readonly mono: FontConfig;
8
+ readonly display: FontConfig;
9
+ readonly default: FontConfig;
10
+ };
11
+ }
12
+
13
+ /**
14
+ * Manages a single <link> tag in <head> for Google Fonts.
15
+ * Removes the previous link when fonts change to avoid accumulation.
16
+ * Does nothing when no Google fonts are present.
17
+ *
18
+ * Uses imperative DOM manipulation because react-native-web's View
19
+ * cannot render <link> elements.
20
+ */
21
+ export function GoogleFontLoader({ fonts }: GoogleFontLoaderProps) {
22
+ const linkRef = useRef<HTMLLinkElement | null>(null);
23
+
24
+ useEffect(() => {
25
+ if (typeof document === 'undefined') return;
26
+
27
+ const url = buildGoogleFontsUrl(fonts);
28
+
29
+ // Clean up previous link
30
+ if (linkRef.current) {
31
+ linkRef.current.remove();
32
+ linkRef.current = null;
33
+ }
34
+
35
+ if (!url) return;
36
+
37
+ // Check if an identical link already exists (another provider instance)
38
+ const links = Array.from(document.head.querySelectorAll('link[rel="stylesheet"]'));
39
+ if (links.some((el) => (el as HTMLLinkElement).href === url)) return;
40
+
41
+ const link = document.createElement('link');
42
+ link.rel = 'stylesheet';
43
+ link.href = url;
44
+ document.head.appendChild(link);
45
+ linkRef.current = link;
46
+
47
+ return () => {
48
+ if (linkRef.current) {
49
+ linkRef.current.remove();
50
+ linkRef.current = null;
51
+ }
52
+ };
53
+ }, [
54
+ fonts.mono.family,
55
+ fonts.mono.type,
56
+ fonts.display.family,
57
+ fonts.display.type,
58
+ fonts.default.family,
59
+ fonts.default.type,
60
+ ]);
61
+
62
+ return null;
63
+ }
@@ -0,0 +1,49 @@
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ interface IconFontLoaderProps {
4
+ readonly icons: {
5
+ readonly variant: 'outlined' | 'rounded' | 'sharp';
6
+ };
7
+ }
8
+
9
+ /**
10
+ * Manages a single <link> tag in <head> for the Material Symbols icon font.
11
+ * Loads the variable font with all axes (FILL, wght, GRAD, opsz).
12
+ * Removes the previous link when the variant changes to avoid accumulation.
13
+ */
14
+ export function IconFontLoader({ icons }: IconFontLoaderProps) {
15
+ const linkRef = useRef<HTMLLinkElement | null>(null);
16
+
17
+ useEffect(() => {
18
+ if (typeof document === 'undefined') return;
19
+
20
+ const variantName = icons.variant.charAt(0).toUpperCase() + icons.variant.slice(1);
21
+ const family = `Material+Symbols+${variantName}`;
22
+ const url = `https://fonts.googleapis.com/css2?family=${family}:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=block`;
23
+
24
+ // Clean up previous link
25
+ if (linkRef.current) {
26
+ linkRef.current.remove();
27
+ linkRef.current = null;
28
+ }
29
+
30
+ // Check if an identical link already exists (another provider instance)
31
+ const links = Array.from(document.head.querySelectorAll('link[rel="stylesheet"]'));
32
+ if (links.some((el) => (el as HTMLLinkElement).href === url)) return;
33
+
34
+ const link = document.createElement('link');
35
+ link.rel = 'stylesheet';
36
+ link.href = url;
37
+ document.head.appendChild(link);
38
+ linkRef.current = link;
39
+
40
+ return () => {
41
+ if (linkRef.current) {
42
+ linkRef.current.remove();
43
+ linkRef.current = null;
44
+ }
45
+ };
46
+ }, [icons.variant]);
47
+
48
+ return null;
49
+ }
@@ -0,0 +1,31 @@
1
+ import type { FontConfig } from '../theme/types';
2
+
3
+ /**
4
+ * Build a Google Fonts CSS API v2 URL for all google-type fonts in the config.
5
+ * Returns null if no Google fonts are present.
6
+ *
7
+ * @example
8
+ * ```
9
+ * buildGoogleFontsUrl(fonts)
10
+ * // => "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Fira+Code:wght@400;500;600;700&display=swap"
11
+ * ```
12
+ */
13
+ export function buildGoogleFontsUrl(
14
+ fonts: { readonly mono: FontConfig; readonly display: FontConfig; readonly default: FontConfig },
15
+ ): string | null {
16
+ const googleFonts = [fonts.mono, fonts.display, fonts.default].filter(
17
+ (f) => f.type === 'google',
18
+ );
19
+
20
+ if (googleFonts.length === 0) return null;
21
+
22
+ // Deduplicate by family name
23
+ const unique = [...new Map(googleFonts.map((f) => [f.family, f])).values()];
24
+
25
+ const families = unique.map((f) => {
26
+ const encoded = f.family.replace(/ /g, '+');
27
+ return `family=${encoded}:wght@400;500;600;700`;
28
+ });
29
+
30
+ return `https://fonts.googleapis.com/css2?${families.join('&')}&display=swap`;
31
+ }
@@ -0,0 +1,87 @@
1
+ export interface GoogleFontEntry {
2
+ readonly family: string;
3
+ readonly category: 'sans-serif' | 'serif' | 'monospace' | 'display';
4
+ readonly fallback: string;
5
+ }
6
+
7
+ /**
8
+ * Curated list of popular Google Fonts organized by category.
9
+ * Used by the font picker UI and the GoogleFontLoader.
10
+ */
11
+ export const GOOGLE_FONTS: readonly GoogleFontEntry[] = [
12
+ // Sans-serif
13
+ { family: 'Inter', category: 'sans-serif', fallback: 'sans-serif' },
14
+ { family: 'Roboto', category: 'sans-serif', fallback: 'sans-serif' },
15
+ { family: 'Open Sans', category: 'sans-serif', fallback: 'sans-serif' },
16
+ { family: 'Lato', category: 'sans-serif', fallback: 'sans-serif' },
17
+ { family: 'Montserrat', category: 'sans-serif', fallback: 'sans-serif' },
18
+ { family: 'Poppins', category: 'sans-serif', fallback: 'sans-serif' },
19
+ { family: 'Nunito', category: 'sans-serif', fallback: 'sans-serif' },
20
+ { family: 'Source Sans 3', category: 'sans-serif', fallback: 'sans-serif' },
21
+ { family: 'Work Sans', category: 'sans-serif', fallback: 'sans-serif' },
22
+ { family: 'Raleway', category: 'sans-serif', fallback: 'sans-serif' },
23
+ { family: 'DM Sans', category: 'sans-serif', fallback: 'sans-serif' },
24
+ { family: 'Plus Jakarta Sans', category: 'sans-serif', fallback: 'sans-serif' },
25
+ { family: 'Outfit', category: 'sans-serif', fallback: 'sans-serif' },
26
+ { family: 'Space Grotesk', category: 'sans-serif', fallback: 'sans-serif' },
27
+ { family: 'Manrope', category: 'sans-serif', fallback: 'sans-serif' },
28
+
29
+ // Serif
30
+ { family: 'Playfair Display', category: 'serif', fallback: 'serif' },
31
+ { family: 'Merriweather', category: 'serif', fallback: 'serif' },
32
+ { family: 'Lora', category: 'serif', fallback: 'serif' },
33
+ { family: 'Libre Baskerville', category: 'serif', fallback: 'serif' },
34
+ { family: 'Source Serif 4', category: 'serif', fallback: 'serif' },
35
+ { family: 'Bitter', category: 'serif', fallback: 'serif' },
36
+ { family: 'Cormorant Garamond', category: 'serif', fallback: 'serif' },
37
+ { family: 'EB Garamond', category: 'serif', fallback: 'serif' },
38
+ { family: 'Crimson Text', category: 'serif', fallback: 'serif' },
39
+ { family: 'Noto Serif', category: 'serif', fallback: 'serif' },
40
+
41
+ // Monospace
42
+ { family: 'Fira Code', category: 'monospace', fallback: 'monospace' },
43
+ { family: 'JetBrains Mono', category: 'monospace', fallback: 'monospace' },
44
+ { family: 'Source Code Pro', category: 'monospace', fallback: 'monospace' },
45
+ { family: 'IBM Plex Mono', category: 'monospace', fallback: 'monospace' },
46
+ { family: 'Roboto Mono', category: 'monospace', fallback: 'monospace' },
47
+ { family: 'Space Mono', category: 'monospace', fallback: 'monospace' },
48
+ { family: 'Ubuntu Mono', category: 'monospace', fallback: 'monospace' },
49
+ { family: 'Inconsolata', category: 'monospace', fallback: 'monospace' },
50
+
51
+ // Display
52
+ { family: 'Abril Fatface', category: 'display', fallback: 'serif' },
53
+ { family: 'Bebas Neue', category: 'display', fallback: 'sans-serif' },
54
+ { family: 'Oswald', category: 'display', fallback: 'sans-serif' },
55
+ { family: 'Righteous', category: 'display', fallback: 'sans-serif' },
56
+ { family: 'Lobster', category: 'display', fallback: 'cursive' },
57
+ { family: 'Pacifico', category: 'display', fallback: 'cursive' },
58
+ { family: 'Comfortaa', category: 'display', fallback: 'sans-serif' },
59
+ { family: 'Fredoka', category: 'display', fallback: 'sans-serif' },
60
+ ];
61
+
62
+ export interface SystemFontEntry {
63
+ readonly family: string;
64
+ readonly category: 'sans-serif' | 'serif' | 'monospace';
65
+ readonly fallback: string;
66
+ }
67
+
68
+ /**
69
+ * System font options that don't require external loading.
70
+ */
71
+ export const SYSTEM_FONTS: readonly SystemFontEntry[] = [
72
+ {
73
+ family: 'system-ui',
74
+ category: 'sans-serif',
75
+ fallback: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
76
+ },
77
+ {
78
+ family: 'ui-monospace',
79
+ category: 'monospace',
80
+ fallback: 'SFMono-Regular, Menlo, Monaco, Consolas, monospace',
81
+ },
82
+ {
83
+ family: 'ui-serif',
84
+ category: 'serif',
85
+ fallback: 'Georgia, "Times New Roman", serif',
86
+ },
87
+ ];
package/src/index.ts CHANGED
@@ -4,11 +4,15 @@ export type {
4
4
  ColorMode,
5
5
  ThemeName,
6
6
  ElevationLevel,
7
+ FrameElevation,
7
8
  ThemeMapping,
8
9
  ColorSystemConfig,
9
10
  NewtoneThemeConfig,
10
11
  NewtoneThemeContext,
12
+ FontConfig,
11
13
  } from './theme/types';
14
+ export { useFrameContext } from './theme/FrameContext';
15
+ export type { FrameContextValue } from './theme/FrameContext';
12
16
  export { DEFAULT_THEME_CONFIG } from './theme/defaults';
13
17
 
14
18
  // Tokens
@@ -18,16 +22,43 @@ export type { ResolvedTokens } from './tokens/types';
18
22
 
19
23
  // Components
20
24
  export { Button } from './Button/Button';
21
- export type { ButtonProps, ButtonVariant, ButtonSize } from './Button/Button.types';
25
+ export type { ButtonProps, ButtonVariant, ButtonSize, ButtonIconPosition } from './Button/Button.types';
22
26
 
23
27
  export { Card } from './Card/Card';
24
28
  export type { CardProps } from './Card/Card.types';
25
29
 
30
+ export { Frame } from './Frame/Frame';
31
+ export type {
32
+ FrameProps,
33
+ SpacingToken,
34
+ SpacingValue,
35
+ SpacingSides,
36
+ SpacingAxes,
37
+ PaddingProp,
38
+ GapAxes,
39
+ GapProp,
40
+ RadiusToken,
41
+ RadiusValue,
42
+ RadiusCorners,
43
+ RadiusProp,
44
+ SizingMode,
45
+ LayoutMode,
46
+ Direction,
47
+ Alignment,
48
+ Justification,
49
+ } from './Frame/Frame.types';
50
+ export type { ResolvedCorners } from './Frame/Frame.utils';
51
+
26
52
  export { TextInput } from './TextInput/TextInput';
27
53
  export type { TextInputProps } from './TextInput/TextInput.types';
28
54
 
55
+ export { Popover } from './Popover/Popover';
56
+ export type { PopoverProps, PopoverPlacement } from './Popover/Popover.types';
57
+ export { usePopover } from './Popover/usePopover';
58
+
29
59
  export { Select } from './Select/Select';
30
- export type { SelectProps, SelectOption } from './Select/Select.types';
60
+ export type { SelectProps, SelectOption, SelectOptionGroup, SelectItem } from './Select/Select.types';
61
+ export { isOptionGroup } from './Select/Select.types';
31
62
 
32
63
  export { Toggle } from './Toggle/Toggle';
33
64
  export type { ToggleProps } from './Toggle/Toggle.types';
@@ -38,6 +69,43 @@ export type { SliderProps } from './Slider/Slider.types';
38
69
  export { HueSlider } from './HueSlider/HueSlider';
39
70
  export type { HueSliderProps } from './HueSlider/HueSlider.types';
40
71
 
72
+ export { ColorScaleSlider } from './ColorScaleSlider/ColorScaleSlider';
73
+ export type { ColorScaleSliderProps } from './ColorScaleSlider/ColorScaleSlider.types';
74
+
75
+ export { Icon } from './Icon/Icon';
76
+ export type { IconProps } from './Icon/Icon';
77
+
78
+ export { AppShell } from './AppShell/AppShell';
79
+ export type { AppShellProps } from './AppShell/AppShell.types';
80
+
81
+ export { Sidebar } from './Sidebar/Sidebar';
82
+ export type { SidebarProps } from './Sidebar/Sidebar.types';
83
+
84
+ export { Navbar } from './Navbar/Navbar';
85
+ export type { NavbarProps } from './Navbar/Navbar.types';
86
+
87
+ // Component registry + code generation
88
+ export type {
89
+ CategoryMeta,
90
+ VariantMeta,
91
+ EditableProp,
92
+ EditablePropOption,
93
+ ComponentMeta,
94
+ } from './registry';
95
+ export {
96
+ CATEGORIES,
97
+ COMPONENTS,
98
+ getComponent,
99
+ getCategory,
100
+ getComponentsByCategory,
101
+ generateComponentCode,
102
+ } from './registry';
103
+
104
+ // Fonts
105
+ export { GOOGLE_FONTS, SYSTEM_FONTS } from './fonts/googleFonts';
106
+ export type { GoogleFontEntry, SystemFontEntry } from './fonts/googleFonts';
107
+ export { buildGoogleFontsUrl } from './fonts/buildGoogleFontsUrl';
108
+
41
109
  // Re-export core engine types for convenience
42
110
  export type {
43
111
  DynamicRange,