@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
@@ -0,0 +1,132 @@
1
+ import type { ComponentMeta } from './types';
2
+
3
+ /**
4
+ * Handler props that should be included as placeholders in generated code.
5
+ * Maps component ID to the handler props it needs.
6
+ */
7
+ const HANDLER_PROPS: Record<string, readonly string[]> = {
8
+ button: ['onPress={() => {}}'],
9
+ 'text-input': ['value={value}', 'onChangeText={setValue}'],
10
+ select: ['value={value}', 'onValueChange={setValue}'],
11
+ toggle: ['value={value}', 'onValueChange={setValue}'],
12
+ slider: ['value={value}', 'onValueChange={setValue}'],
13
+ 'hue-slider': ['value={value}', 'onValueChange={setValue}'],
14
+ };
15
+
16
+ /**
17
+ * Default children content for components with children.
18
+ */
19
+ const CHILDREN_CONTENT: Record<string, string> = {
20
+ button: 'Button',
21
+ card: '{/* content */}',
22
+ frame: '{/* content */}',
23
+ };
24
+
25
+ /**
26
+ * Generates a ready-to-paste JSX code snippet for a component with the given prop overrides.
27
+ *
28
+ * - Includes the import statement
29
+ * - Omits props that match their default values
30
+ * - Adds placeholder handlers for interactive props
31
+ * - Formats Select's options as a const declaration
32
+ */
33
+ export function generateComponentCode(
34
+ component: ComponentMeta,
35
+ propOverrides: Record<string, unknown>,
36
+ ): string {
37
+ const lines: string[] = [];
38
+
39
+ // Import line
40
+ lines.push(`import { ${component.importName} } from '@newtonedev/components';`);
41
+
42
+ // Select needs options declaration above JSX
43
+ if (component.id === 'select') {
44
+ lines.push('');
45
+ lines.push('const options = [');
46
+ lines.push(" { label: 'Option 1', value: 'option-1' },");
47
+ lines.push(" { label: 'Option 2', value: 'option-2' },");
48
+ lines.push(" { label: 'Option 3', value: 'option-3' },");
49
+ lines.push('];');
50
+ }
51
+
52
+ lines.push('');
53
+
54
+ // Build props string
55
+ const propEntries: string[] = [];
56
+
57
+ // Add editable props (only non-default values)
58
+ for (const prop of component.editableProps) {
59
+ const value = propOverrides[prop.name] ?? prop.defaultValue;
60
+ if (value === prop.defaultValue) continue;
61
+
62
+ propEntries.push(formatProp(prop.name, value));
63
+ }
64
+
65
+ // Add handler/runtime props
66
+ const handlers = HANDLER_PROPS[component.id];
67
+ if (handlers) {
68
+ for (const handler of handlers) {
69
+ propEntries.push(handler);
70
+ }
71
+ }
72
+
73
+ // Special case: Select gets options={options} reference
74
+ if (component.id === 'select') {
75
+ propEntries.push('options={options}');
76
+ }
77
+
78
+ // Build the JSX
79
+ const children = CHILDREN_CONTENT[component.id];
80
+ const hasProps = propEntries.length > 0;
81
+ const tag = component.importName;
82
+
83
+ if (component.hasChildren && children) {
84
+ if (hasProps && propEntries.length <= 3) {
85
+ // Single-line opening tag
86
+ lines.push(`<${tag} ${propEntries.join(' ')}>`);
87
+ lines.push(` ${children}`);
88
+ lines.push(`</${tag}>`);
89
+ } else if (hasProps) {
90
+ // Multi-line opening tag
91
+ lines.push(`<${tag}`);
92
+ for (const entry of propEntries) {
93
+ lines.push(` ${entry}`);
94
+ }
95
+ lines.push('>');
96
+ lines.push(` ${children}`);
97
+ lines.push(`</${tag}>`);
98
+ } else {
99
+ lines.push(`<${tag}>`);
100
+ lines.push(` ${children}`);
101
+ lines.push(`</${tag}>`);
102
+ }
103
+ } else {
104
+ // Self-closing tag
105
+ if (hasProps && propEntries.length <= 3) {
106
+ lines.push(`<${tag} ${propEntries.join(' ')} />`);
107
+ } else if (hasProps) {
108
+ lines.push(`<${tag}`);
109
+ for (const entry of propEntries) {
110
+ lines.push(` ${entry}`);
111
+ }
112
+ lines.push('/>');
113
+ } else {
114
+ lines.push(`<${tag} />`);
115
+ }
116
+ }
117
+
118
+ return lines.join('\n');
119
+ }
120
+
121
+ function formatProp(name: string, value: unknown): string {
122
+ if (typeof value === 'string') {
123
+ return `${name}="${value}"`;
124
+ }
125
+ if (typeof value === 'boolean') {
126
+ return value ? name : `${name}={false}`;
127
+ }
128
+ if (typeof value === 'number') {
129
+ return `${name}={${value}}`;
130
+ }
131
+ return `${name}={${JSON.stringify(value)}}`;
132
+ }
@@ -0,0 +1,17 @@
1
+ export type {
2
+ CategoryMeta,
3
+ VariantMeta,
4
+ EditableProp,
5
+ EditablePropOption,
6
+ ComponentMeta,
7
+ } from './types';
8
+
9
+ export {
10
+ CATEGORIES,
11
+ COMPONENTS,
12
+ getComponent,
13
+ getCategory,
14
+ getComponentsByCategory,
15
+ } from './registry';
16
+
17
+ export { generateComponentCode } from './codegen';
@@ -0,0 +1,402 @@
1
+ import type { CategoryMeta, ComponentMeta } from './types';
2
+
3
+ export const CATEGORIES: readonly CategoryMeta[] = [
4
+ { id: 'actions', name: 'Actions', description: 'Interactive elements that trigger actions' },
5
+ { id: 'form-controls', name: 'Form Controls', description: 'Input elements for user data entry' },
6
+ { id: 'range-inputs', name: 'Range Inputs', description: 'Slider controls for numeric values' },
7
+ { id: 'layout', name: 'Layout', description: 'Structural and container components' },
8
+ ];
9
+
10
+ export const COMPONENTS: readonly ComponentMeta[] = [
11
+ {
12
+ id: 'button',
13
+ name: 'Button',
14
+ importName: 'Button',
15
+ categoryId: 'actions',
16
+ description: 'Interactive button with multiple variants, sizes, and optional icon',
17
+ hasChildren: true,
18
+ variants: [
19
+ { id: 'primary-md', label: 'Primary', props: { variant: 'primary', size: 'md' } },
20
+ { id: 'secondary-md', label: 'Secondary', props: { variant: 'secondary', size: 'md' } },
21
+ { id: 'ghost-md', label: 'Ghost', props: { variant: 'ghost', size: 'md' } },
22
+ { id: 'outline-md', label: 'Outline', props: { variant: 'outline', size: 'md' } },
23
+ { id: 'primary-sm', label: 'Primary Small', props: { variant: 'primary', size: 'sm' } },
24
+ { id: 'primary-lg', label: 'Primary Large', props: { variant: 'primary', size: 'lg' } },
25
+ { id: 'icon-left', label: 'Icon Left', props: { variant: 'primary', size: 'md', icon: 'add' } },
26
+ { id: 'icon-right', label: 'Icon Right', props: { variant: 'primary', size: 'md', icon: 'arrow_forward', iconPosition: 'right' } },
27
+ { id: 'icon-only', label: 'Icon Only', props: { variant: 'ghost', size: 'md', icon: 'settings' } },
28
+ ],
29
+ editableProps: [
30
+ {
31
+ name: 'variant',
32
+ label: 'Variant',
33
+ control: 'select',
34
+ options: [
35
+ { label: 'Primary', value: 'primary' },
36
+ { label: 'Secondary', value: 'secondary' },
37
+ { label: 'Ghost', value: 'ghost' },
38
+ { label: 'Outline', value: 'outline' },
39
+ ],
40
+ defaultValue: 'primary',
41
+ },
42
+ {
43
+ name: 'size',
44
+ label: 'Size',
45
+ control: 'select',
46
+ options: [
47
+ { label: 'Small', value: 'sm' },
48
+ { label: 'Medium', value: 'md' },
49
+ { label: 'Large', value: 'lg' },
50
+ ],
51
+ defaultValue: 'md',
52
+ },
53
+ {
54
+ name: 'icon',
55
+ label: 'Icon',
56
+ control: 'text',
57
+ defaultValue: '',
58
+ },
59
+ {
60
+ name: 'iconPosition',
61
+ label: 'Icon Position',
62
+ control: 'select',
63
+ options: [
64
+ { label: 'Left', value: 'left' },
65
+ { label: 'Right', value: 'right' },
66
+ ],
67
+ defaultValue: 'left',
68
+ },
69
+ {
70
+ name: 'disabled',
71
+ label: 'Disabled',
72
+ control: 'toggle',
73
+ defaultValue: false,
74
+ },
75
+ ],
76
+ },
77
+ {
78
+ id: 'text-input',
79
+ name: 'TextInput',
80
+ importName: 'TextInput',
81
+ categoryId: 'form-controls',
82
+ description: 'Text input field with label support',
83
+ hasChildren: false,
84
+ variants: [
85
+ { id: 'default', label: 'Default', props: { label: 'Label', value: 'Sample text' } },
86
+ { id: 'empty', label: 'Empty', props: { label: 'Email', value: '' } },
87
+ { id: 'disabled', label: 'Disabled', props: { label: 'Disabled', value: 'Read only', disabled: true } },
88
+ ],
89
+ editableProps: [
90
+ {
91
+ name: 'label',
92
+ label: 'Label',
93
+ control: 'text',
94
+ defaultValue: 'Label',
95
+ },
96
+ {
97
+ name: 'disabled',
98
+ label: 'Disabled',
99
+ control: 'toggle',
100
+ defaultValue: false,
101
+ },
102
+ ],
103
+ },
104
+ {
105
+ id: 'select',
106
+ name: 'Select',
107
+ importName: 'Select',
108
+ categoryId: 'form-controls',
109
+ description: 'Dropdown selector with options',
110
+ hasChildren: false,
111
+ variants: [
112
+ {
113
+ id: 'default',
114
+ label: 'Default',
115
+ props: {
116
+ label: 'Choose an option',
117
+ value: 'option-1',
118
+ options: [
119
+ { label: 'Option 1', value: 'option-1' },
120
+ { label: 'Option 2', value: 'option-2' },
121
+ { label: 'Option 3', value: 'option-3' },
122
+ ],
123
+ },
124
+ },
125
+ {
126
+ id: 'disabled',
127
+ label: 'Disabled',
128
+ props: {
129
+ label: 'Disabled select',
130
+ value: 'option-1',
131
+ disabled: true,
132
+ options: [{ label: 'Option 1', value: 'option-1' }],
133
+ },
134
+ },
135
+ ],
136
+ editableProps: [
137
+ {
138
+ name: 'label',
139
+ label: 'Label',
140
+ control: 'text',
141
+ defaultValue: 'Choose an option',
142
+ },
143
+ {
144
+ name: 'disabled',
145
+ label: 'Disabled',
146
+ control: 'toggle',
147
+ defaultValue: false,
148
+ },
149
+ ],
150
+ },
151
+ {
152
+ id: 'toggle',
153
+ name: 'Toggle',
154
+ importName: 'Toggle',
155
+ categoryId: 'form-controls',
156
+ description: 'Binary switch component',
157
+ hasChildren: false,
158
+ variants: [
159
+ { id: 'on', label: 'On', props: { label: 'Enabled', value: true } },
160
+ { id: 'off', label: 'Off', props: { label: 'Disabled', value: false } },
161
+ { id: 'disabled', label: 'Disabled', props: { label: 'Locked', value: true, disabled: true } },
162
+ ],
163
+ editableProps: [
164
+ {
165
+ name: 'label',
166
+ label: 'Label',
167
+ control: 'text',
168
+ defaultValue: 'Toggle',
169
+ },
170
+ {
171
+ name: 'disabled',
172
+ label: 'Disabled',
173
+ control: 'toggle',
174
+ defaultValue: false,
175
+ },
176
+ ],
177
+ },
178
+ {
179
+ id: 'slider',
180
+ name: 'Slider',
181
+ importName: 'Slider',
182
+ categoryId: 'range-inputs',
183
+ description: 'Numeric range slider',
184
+ hasChildren: false,
185
+ variants: [
186
+ { id: 'default', label: 'Default', props: { label: 'Volume', value: 50, min: 0, max: 100 } },
187
+ { id: 'with-step', label: 'With Step', props: { label: 'Steps', value: 3, min: 0, max: 10, step: 1, showValue: true } },
188
+ ],
189
+ editableProps: [
190
+ {
191
+ name: 'label',
192
+ label: 'Label',
193
+ control: 'text',
194
+ defaultValue: 'Slider',
195
+ },
196
+ {
197
+ name: 'min',
198
+ label: 'Min',
199
+ control: 'number',
200
+ defaultValue: 0,
201
+ },
202
+ {
203
+ name: 'max',
204
+ label: 'Max',
205
+ control: 'number',
206
+ defaultValue: 100,
207
+ },
208
+ {
209
+ name: 'showValue',
210
+ label: 'Show Value',
211
+ control: 'toggle',
212
+ defaultValue: false,
213
+ },
214
+ {
215
+ name: 'disabled',
216
+ label: 'Disabled',
217
+ control: 'toggle',
218
+ defaultValue: false,
219
+ },
220
+ ],
221
+ },
222
+ {
223
+ id: 'hue-slider',
224
+ name: 'HueSlider',
225
+ importName: 'HueSlider',
226
+ categoryId: 'range-inputs',
227
+ description: 'Specialized slider for hue selection (0-360\u00b0)',
228
+ hasChildren: false,
229
+ variants: [
230
+ { id: 'default', label: 'Default', props: { label: 'Hue', value: 180 } },
231
+ { id: 'red', label: 'Red', props: { label: 'Warm Hue', value: 0 } },
232
+ { id: 'blue', label: 'Blue', props: { label: 'Cool Hue', value: 240 } },
233
+ ],
234
+ editableProps: [
235
+ {
236
+ name: 'label',
237
+ label: 'Label',
238
+ control: 'text',
239
+ defaultValue: 'Hue',
240
+ },
241
+ {
242
+ name: 'showValue',
243
+ label: 'Show Value',
244
+ control: 'toggle',
245
+ defaultValue: false,
246
+ },
247
+ {
248
+ name: 'disabled',
249
+ label: 'Disabled',
250
+ control: 'toggle',
251
+ defaultValue: false,
252
+ },
253
+ ],
254
+ },
255
+ {
256
+ id: 'frame',
257
+ name: 'Frame',
258
+ importName: 'Frame',
259
+ categoryId: 'layout',
260
+ description: 'Foundational layout container with theme, elevation, spacing, and interactivity',
261
+ hasChildren: true,
262
+ variants: [
263
+ { id: 'default', label: 'Default', props: {} },
264
+ { id: 'primary', label: 'Primary', props: { theme: 'primary' } },
265
+ { id: 'secondary', label: 'Secondary', props: { theme: 'secondary' } },
266
+ { id: 'strong', label: 'Strong', props: { theme: 'strong' } },
267
+ { id: 'deeply-sunken', label: 'Deeply Sunken', props: { elevation: -2, radius: 'md' } },
268
+ { id: 'sunken', label: 'Sunken', props: { elevation: -1 } },
269
+ { id: 'elevated', label: 'Elevated', props: { elevation: 1 } },
270
+ { id: 'prominent', label: 'Prominent', props: { elevation: 2, radius: 'lg' } },
271
+ { id: 'padded', label: 'Padded', props: { padding: 'lg', gap: 'md' } },
272
+ { id: 'horizontal', label: 'Horizontal', props: { direction: 'horizontal', gap: 'md', align: 'center' } },
273
+ { id: 'grid-3col', label: '3-Column Grid', props: { layout: 'grid', columns: 3, gap: 'md' } },
274
+ { id: 'card-like', label: 'Card-like', props: { radius: 'lg', padding: 'lg', bordered: true, elevation: 1 } },
275
+ ],
276
+ editableProps: [
277
+ {
278
+ name: 'theme',
279
+ label: 'Theme',
280
+ control: 'select',
281
+ options: [
282
+ { label: 'Neutral', value: 'neutral' },
283
+ { label: 'Primary', value: 'primary' },
284
+ { label: 'Secondary', value: 'secondary' },
285
+ { label: 'Strong', value: 'strong' },
286
+ ],
287
+ defaultValue: 'neutral',
288
+ },
289
+ {
290
+ name: 'elevation',
291
+ label: 'Elevation',
292
+ control: 'select',
293
+ options: [
294
+ { label: 'Deeply Sunken (-2)', value: -2 },
295
+ { label: 'Sunken (-1)', value: -1 },
296
+ { label: 'Default (0)', value: 0 },
297
+ { label: 'Elevated (1)', value: 1 },
298
+ { label: 'Prominent (2)', value: 2 },
299
+ ],
300
+ defaultValue: 0,
301
+ },
302
+ {
303
+ name: 'direction',
304
+ label: 'Direction',
305
+ control: 'select',
306
+ options: [
307
+ { label: 'Vertical', value: 'vertical' },
308
+ { label: 'Horizontal', value: 'horizontal' },
309
+ ],
310
+ defaultValue: 'vertical',
311
+ },
312
+ {
313
+ name: 'padding',
314
+ label: 'Padding',
315
+ control: 'select',
316
+ options: [
317
+ { label: 'None', value: '' },
318
+ { label: 'Small', value: 'sm' },
319
+ { label: 'Medium', value: 'md' },
320
+ { label: 'Large', value: 'lg' },
321
+ { label: 'Extra Large', value: 'xl' },
322
+ ],
323
+ defaultValue: '',
324
+ },
325
+ {
326
+ name: 'gap',
327
+ label: 'Gap',
328
+ control: 'select',
329
+ options: [
330
+ { label: 'None', value: '' },
331
+ { label: 'Small', value: 'sm' },
332
+ { label: 'Medium', value: 'md' },
333
+ { label: 'Large', value: 'lg' },
334
+ ],
335
+ defaultValue: '',
336
+ },
337
+ {
338
+ name: 'radius',
339
+ label: 'Radius',
340
+ control: 'select',
341
+ options: [
342
+ { label: 'None', value: 'none' },
343
+ { label: 'Small', value: 'sm' },
344
+ { label: 'Medium', value: 'md' },
345
+ { label: 'Large', value: 'lg' },
346
+ { label: 'Pill', value: 'pill' },
347
+ ],
348
+ defaultValue: 'none',
349
+ },
350
+ {
351
+ name: 'bordered',
352
+ label: 'Bordered',
353
+ control: 'toggle',
354
+ defaultValue: false,
355
+ },
356
+ ],
357
+ },
358
+ {
359
+ id: 'card',
360
+ name: 'Card',
361
+ importName: 'Card',
362
+ categoryId: 'layout',
363
+ description: 'Surface container with elevation levels',
364
+ hasChildren: true,
365
+ variants: [
366
+ { id: 'elevation-0', label: 'Elevation 0', props: { elevation: 0 } },
367
+ { id: 'elevation-1', label: 'Elevation 1', props: { elevation: 1 } },
368
+ { id: 'elevation-2', label: 'Elevation 2', props: { elevation: 2 } },
369
+ ],
370
+ editableProps: [
371
+ {
372
+ name: 'elevation',
373
+ label: 'Elevation',
374
+ control: 'select',
375
+ options: [
376
+ { label: 'Level 0', value: 0 },
377
+ { label: 'Level 1', value: 1 },
378
+ { label: 'Level 2', value: 2 },
379
+ ],
380
+ defaultValue: 0,
381
+ },
382
+ {
383
+ name: 'disabled',
384
+ label: 'Disabled',
385
+ control: 'toggle',
386
+ defaultValue: false,
387
+ },
388
+ ],
389
+ },
390
+ ];
391
+
392
+ export function getComponent(id: string): ComponentMeta | undefined {
393
+ return COMPONENTS.find((c) => c.id === id);
394
+ }
395
+
396
+ export function getCategory(id: string): CategoryMeta | undefined {
397
+ return CATEGORIES.find((c) => c.id === id);
398
+ }
399
+
400
+ export function getComponentsByCategory(categoryId: string): readonly ComponentMeta[] {
401
+ return COMPONENTS.filter((c) => c.categoryId === categoryId);
402
+ }
@@ -0,0 +1,35 @@
1
+ export interface CategoryMeta {
2
+ readonly id: string;
3
+ readonly name: string;
4
+ readonly description: string;
5
+ }
6
+
7
+ export interface VariantMeta {
8
+ readonly id: string;
9
+ readonly label: string;
10
+ readonly props: Record<string, unknown>;
11
+ }
12
+
13
+ export interface EditablePropOption {
14
+ readonly label: string;
15
+ readonly value: string | number | boolean;
16
+ }
17
+
18
+ export interface EditableProp {
19
+ readonly name: string;
20
+ readonly label: string;
21
+ readonly control: 'select' | 'text' | 'toggle' | 'number';
22
+ readonly options?: readonly EditablePropOption[];
23
+ readonly defaultValue: string | number | boolean;
24
+ }
25
+
26
+ export interface ComponentMeta {
27
+ readonly id: string;
28
+ readonly name: string;
29
+ readonly importName: string;
30
+ readonly categoryId: string;
31
+ readonly description: string;
32
+ readonly hasChildren: boolean;
33
+ readonly variants: readonly VariantMeta[];
34
+ readonly editableProps: readonly EditableProp[];
35
+ }
@@ -0,0 +1,29 @@
1
+ import { createContext, useContext } from 'react';
2
+ import type { ThemeName, ElevationLevel } from './types';
3
+
4
+ /**
5
+ * Context value provided by Frame to its descendants.
6
+ * Contains the resolved theme and elevation that children should use.
7
+ */
8
+ export interface FrameContextValue {
9
+ readonly theme: ThemeName;
10
+ readonly elevation: ElevationLevel;
11
+ }
12
+
13
+ /**
14
+ * FrameContext - Propagates theme and elevation overrides from Frame to descendants.
15
+ *
16
+ * When null, components fall back to NewtoneProvider's theme and default elevation (1).
17
+ * When present, useTokens() reads from this context instead.
18
+ */
19
+ export const FrameContext = createContext<FrameContextValue | null>(null);
20
+
21
+ /**
22
+ * useFrameContext - Read the nearest Frame's context, if any.
23
+ *
24
+ * Returns null when no parent Frame exists. This is intentionally not an error —
25
+ * components outside a Frame simply fall back to the NewtoneProvider defaults.
26
+ */
27
+ export function useFrameContext(): FrameContextValue | null {
28
+ return useContext(FrameContext);
29
+ }
@@ -1,6 +1,8 @@
1
1
  import React, { createContext, useState, useMemo, useContext } from 'react';
2
2
  import type { NewtoneThemeConfig, NewtoneThemeContext, ColorMode, ThemeName } from './types';
3
3
  import { DEFAULT_THEME_CONFIG } from './defaults';
4
+ import { GoogleFontLoader } from '../fonts/GoogleFontLoader';
5
+ import { IconFontLoader } from '../fonts/IconFontLoader';
4
6
 
5
7
  const ThemeContext = createContext<NewtoneThemeContext | null>(null);
6
8
 
@@ -43,7 +45,13 @@ export function NewtoneProvider({
43
45
  [config, mode, theme]
44
46
  );
45
47
 
46
- return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
48
+ return (
49
+ <ThemeContext.Provider value={value}>
50
+ <GoogleFontLoader fonts={config.typography.fonts} />
51
+ <IconFontLoader icons={config.icons} />
52
+ {children}
53
+ </ThemeContext.Provider>
54
+ );
47
55
  }
48
56
 
49
57
  /**
@@ -39,4 +39,55 @@ export const DEFAULT_THEME_CONFIG: NewtoneThemeConfig = {
39
39
  elevation: {
40
40
  offsets: [-0.02, 0, 0.04], // [sunken, default, elevated]
41
41
  },
42
+ spacing: {
43
+ xs: 4,
44
+ sm: 8,
45
+ md: 12,
46
+ lg: 16,
47
+ xl: 24,
48
+ xxl: 32,
49
+ },
50
+ radius: {
51
+ none: 0,
52
+ sm: 4,
53
+ md: 6,
54
+ lg: 8,
55
+ xl: 12,
56
+ pill: 999,
57
+ },
58
+ typography: {
59
+ fonts: {
60
+ mono: {
61
+ type: 'system',
62
+ family: 'ui-monospace',
63
+ fallback: 'SFMono-Regular, Menlo, Monaco, Consolas, monospace',
64
+ },
65
+ display: {
66
+ type: 'system',
67
+ family: 'system-ui',
68
+ fallback: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
69
+ },
70
+ default: {
71
+ type: 'system',
72
+ family: 'system-ui',
73
+ fallback: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
74
+ },
75
+ },
76
+ scale: {
77
+ xs: 10, // 16 / 1.25^2
78
+ sm: 13, // 16 / 1.25
79
+ base: 16,
80
+ md: 20, // 16 * 1.25
81
+ lg: 25, // 16 * 1.25^2
82
+ xl: 31, // 16 * 1.25^3
83
+ xxl: 39, // 16 * 1.25^4
84
+ },
85
+ lineHeight: { tight: 1.25, normal: 1.5, relaxed: 1.75 },
86
+ fontWeight: { regular: 400, medium: 500, semibold: 600, bold: 700 },
87
+ },
88
+ icons: {
89
+ variant: 'rounded', // Material Design 3 aesthetic
90
+ weight: 400, // Normal weight
91
+ autoGrade: true, // Enable mode-aware grade
92
+ },
42
93
  };