@newtonedev/components 0.1.13 → 0.1.15

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 (173) hide show
  1. package/dist/composites/actions/Button/Button.d.ts +17 -20
  2. package/dist/composites/actions/Button/Button.d.ts.map +1 -1
  3. package/dist/composites/actions/Button/Button.styles.d.ts +12 -24
  4. package/dist/composites/actions/Button/Button.styles.d.ts.map +1 -1
  5. package/dist/composites/actions/Button/Button.types.d.ts +14 -13
  6. package/dist/composites/actions/Button/Button.types.d.ts.map +1 -1
  7. package/dist/composites/actions/Button/index.d.ts +1 -1
  8. package/dist/composites/actions/Button/index.d.ts.map +1 -1
  9. package/dist/composites/branding/LogoMonogram/LogoMonogram.d.ts +10 -0
  10. package/dist/composites/branding/LogoMonogram/LogoMonogram.d.ts.map +1 -0
  11. package/dist/composites/branding/LogoMonogram/LogoMonogram.types.d.ts +35 -0
  12. package/dist/composites/branding/LogoMonogram/LogoMonogram.types.d.ts.map +1 -0
  13. package/dist/composites/branding/LogoMonogram/index.d.ts +3 -0
  14. package/dist/composites/branding/LogoMonogram/index.d.ts.map +1 -0
  15. package/dist/composites/branding/LogoWordmark/LogoWordmark.d.ts +9 -0
  16. package/dist/composites/branding/LogoWordmark/LogoWordmark.d.ts.map +1 -0
  17. package/dist/composites/branding/LogoWordmark/LogoWordmark.types.d.ts +26 -0
  18. package/dist/composites/branding/LogoWordmark/LogoWordmark.types.d.ts.map +1 -0
  19. package/dist/composites/branding/LogoWordmark/index.d.ts +3 -0
  20. package/dist/composites/branding/LogoWordmark/index.d.ts.map +1 -0
  21. package/dist/composites/display/Chip/Chip.d.ts +25 -0
  22. package/dist/composites/display/Chip/Chip.d.ts.map +1 -0
  23. package/dist/composites/display/Chip/Chip.styles.d.ts +29 -0
  24. package/dist/composites/display/Chip/Chip.styles.d.ts.map +1 -0
  25. package/dist/composites/display/Chip/Chip.types.d.ts +63 -0
  26. package/dist/composites/display/Chip/Chip.types.d.ts.map +1 -0
  27. package/dist/composites/display/Chip/index.d.ts +3 -0
  28. package/dist/composites/display/Chip/index.d.ts.map +1 -0
  29. package/dist/composites/form-controls/Select/Select.d.ts.map +1 -1
  30. package/dist/composites/form-controls/Select/Select.styles.d.ts +2 -2
  31. package/dist/composites/form-controls/Select/Select.styles.d.ts.map +1 -1
  32. package/dist/composites/form-controls/Select/SelectOption.d.ts.map +1 -1
  33. package/dist/composites/form-controls/TextInput/TextInput.d.ts.map +1 -1
  34. package/dist/composites/form-controls/TextInput/TextInput.styles.d.ts +2 -2
  35. package/dist/composites/form-controls/TextInput/TextInput.styles.d.ts.map +1 -1
  36. package/dist/composites/form-controls/Toggle/Toggle.d.ts.map +1 -1
  37. package/dist/composites/form-controls/Toggle/Toggle.styles.d.ts +2 -2
  38. package/dist/composites/form-controls/Toggle/Toggle.styles.d.ts.map +1 -1
  39. package/dist/composites/layout/AppShell/AppShell.d.ts.map +1 -1
  40. package/dist/composites/layout/AppShell/AppShell.styles.d.ts +2 -2
  41. package/dist/composites/layout/AppShell/AppShell.styles.d.ts.map +1 -1
  42. package/dist/composites/layout/Card/Card.d.ts.map +1 -1
  43. package/dist/composites/layout/Card/Card.styles.d.ts +2 -2
  44. package/dist/composites/layout/Card/Card.styles.d.ts.map +1 -1
  45. package/dist/composites/layout/ContentCard/ContentCard.d.ts +33 -0
  46. package/dist/composites/layout/ContentCard/ContentCard.d.ts.map +1 -0
  47. package/dist/composites/layout/ContentCard/ContentCard.styles.d.ts +10 -0
  48. package/dist/composites/layout/ContentCard/ContentCard.styles.d.ts.map +1 -0
  49. package/dist/composites/layout/ContentCard/ContentCard.types.d.ts +52 -0
  50. package/dist/composites/layout/ContentCard/ContentCard.types.d.ts.map +1 -0
  51. package/dist/composites/layout/ContentCard/index.d.ts +3 -0
  52. package/dist/composites/layout/ContentCard/index.d.ts.map +1 -0
  53. package/dist/composites/layout/Divider/Divider.d.ts +25 -0
  54. package/dist/composites/layout/Divider/Divider.d.ts.map +1 -0
  55. package/dist/composites/layout/Divider/Divider.styles.d.ts +8 -0
  56. package/dist/composites/layout/Divider/Divider.styles.d.ts.map +1 -0
  57. package/dist/composites/layout/Divider/Divider.types.d.ts +25 -0
  58. package/dist/composites/layout/Divider/Divider.types.d.ts.map +1 -0
  59. package/dist/composites/layout/Divider/index.d.ts +3 -0
  60. package/dist/composites/layout/Divider/index.d.ts.map +1 -0
  61. package/dist/composites/layout/Navbar/Navbar.d.ts.map +1 -1
  62. package/dist/composites/layout/Navbar/Navbar.styles.d.ts +4 -3
  63. package/dist/composites/layout/Navbar/Navbar.styles.d.ts.map +1 -1
  64. package/dist/composites/layout/Sidebar/Sidebar.d.ts.map +1 -1
  65. package/dist/composites/layout/Sidebar/Sidebar.styles.d.ts +4 -3
  66. package/dist/composites/layout/Sidebar/Sidebar.styles.d.ts.map +1 -1
  67. package/dist/composites/overlays/Popover/Popover.d.ts.map +1 -1
  68. package/dist/composites/overlays/Popover/Popover.styles.d.ts +2 -2
  69. package/dist/composites/overlays/Popover/Popover.styles.d.ts.map +1 -1
  70. package/dist/composites/range-inputs/ColorScaleSlider/ColorScaleSlider.d.ts.map +1 -1
  71. package/dist/composites/range-inputs/ColorScaleSlider/ColorScaleSlider.styles.d.ts +2 -2
  72. package/dist/composites/range-inputs/ColorScaleSlider/ColorScaleSlider.styles.d.ts.map +1 -1
  73. package/dist/composites/range-inputs/HueSlider/HueSlider.styles.d.ts +2 -2
  74. package/dist/composites/range-inputs/HueSlider/HueSlider.styles.d.ts.map +1 -1
  75. package/dist/composites/range-inputs/Slider/Slider.styles.d.ts +2 -2
  76. package/dist/composites/range-inputs/Slider/Slider.styles.d.ts.map +1 -1
  77. package/dist/index.cjs +935 -523
  78. package/dist/index.cjs.map +1 -1
  79. package/dist/index.d.ts +13 -3
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +860 -473
  82. package/dist/index.js.map +1 -1
  83. package/dist/primitives/Frame/Frame.d.ts +1 -35
  84. package/dist/primitives/Frame/Frame.d.ts.map +1 -1
  85. package/dist/primitives/Frame/Frame.styles.d.ts +13 -4
  86. package/dist/primitives/Frame/Frame.styles.d.ts.map +1 -1
  87. package/dist/primitives/Frame/Frame.types.d.ts +99 -1
  88. package/dist/primitives/Frame/Frame.types.d.ts.map +1 -1
  89. package/dist/primitives/Frame/index.d.ts +1 -1
  90. package/dist/primitives/Frame/index.d.ts.map +1 -1
  91. package/dist/primitives/Icon/Icon.d.ts.map +1 -1
  92. package/dist/primitives/Icon/Icon.types.d.ts +2 -2
  93. package/dist/primitives/Icon/Icon.types.d.ts.map +1 -1
  94. package/dist/primitives/Text/Text.d.ts +5 -3
  95. package/dist/primitives/Text/Text.d.ts.map +1 -1
  96. package/dist/primitives/Text/Text.spans.d.ts.map +1 -1
  97. package/dist/primitives/Text/Text.types.d.ts +3 -6
  98. package/dist/primitives/Text/Text.types.d.ts.map +1 -1
  99. package/dist/primitives/Wrapper/Wrapper.d.ts +1 -1
  100. package/dist/primitives/Wrapper/Wrapper.d.ts.map +1 -1
  101. package/dist/primitives/Wrapper/Wrapper.styles.d.ts +9 -1
  102. package/dist/primitives/Wrapper/Wrapper.styles.d.ts.map +1 -1
  103. package/dist/primitives/Wrapper/Wrapper.types.d.ts +31 -1
  104. package/dist/primitives/Wrapper/Wrapper.types.d.ts.map +1 -1
  105. package/dist/registry/registry.d.ts.map +1 -1
  106. package/package.json +1 -1
  107. package/src/composites/actions/Button/Button.styles.ts +90 -182
  108. package/src/composites/actions/Button/Button.tsx +37 -33
  109. package/src/composites/actions/Button/Button.types.ts +16 -15
  110. package/src/composites/actions/Button/index.ts +1 -1
  111. package/src/composites/branding/LogoMonogram/LogoMonogram.tsx +29 -0
  112. package/src/composites/branding/LogoMonogram/LogoMonogram.types.ts +35 -0
  113. package/src/composites/branding/LogoMonogram/index.ts +2 -0
  114. package/src/composites/branding/LogoWordmark/LogoWordmark.tsx +29 -0
  115. package/src/composites/branding/LogoWordmark/LogoWordmark.types.ts +25 -0
  116. package/src/composites/branding/LogoWordmark/index.ts +2 -0
  117. package/src/composites/display/Chip/Chip.styles.ts +189 -0
  118. package/src/composites/display/Chip/Chip.tsx +97 -0
  119. package/src/composites/display/Chip/Chip.types.ts +74 -0
  120. package/src/composites/display/Chip/index.ts +2 -0
  121. package/src/composites/form-controls/Select/Select.styles.ts +10 -10
  122. package/src/composites/form-controls/Select/Select.tsx +9 -6
  123. package/src/composites/form-controls/Select/SelectOption.tsx +10 -7
  124. package/src/composites/form-controls/TextInput/TextInput.styles.ts +12 -8
  125. package/src/composites/form-controls/TextInput/TextInput.tsx +7 -4
  126. package/src/composites/form-controls/Toggle/Toggle.styles.ts +10 -7
  127. package/src/composites/form-controls/Toggle/Toggle.tsx +4 -3
  128. package/src/composites/layout/AppShell/AppShell.styles.ts +8 -3
  129. package/src/composites/layout/AppShell/AppShell.tsx +6 -2
  130. package/src/composites/layout/Card/Card.styles.ts +10 -4
  131. package/src/composites/layout/Card/Card.tsx +4 -3
  132. package/src/composites/layout/ContentCard/ContentCard.styles.ts +44 -0
  133. package/src/composites/layout/ContentCard/ContentCard.tsx +71 -0
  134. package/src/composites/layout/ContentCard/ContentCard.types.ts +60 -0
  135. package/src/composites/layout/ContentCard/index.ts +2 -0
  136. package/src/composites/layout/Divider/Divider.styles.ts +30 -0
  137. package/src/composites/layout/Divider/Divider.tsx +46 -0
  138. package/src/composites/layout/Divider/Divider.types.ts +28 -0
  139. package/src/composites/layout/Divider/index.ts +2 -0
  140. package/src/composites/layout/Navbar/Navbar.styles.ts +7 -5
  141. package/src/composites/layout/Navbar/Navbar.tsx +4 -3
  142. package/src/composites/layout/Sidebar/Sidebar.styles.ts +7 -5
  143. package/src/composites/layout/Sidebar/Sidebar.tsx +4 -3
  144. package/src/composites/overlays/Popover/Popover.styles.ts +7 -5
  145. package/src/composites/overlays/Popover/Popover.tsx +4 -3
  146. package/src/composites/range-inputs/ColorScaleSlider/ColorScaleSlider.styles.ts +5 -5
  147. package/src/composites/range-inputs/ColorScaleSlider/ColorScaleSlider.tsx +4 -3
  148. package/src/composites/range-inputs/HueSlider/HueSlider.styles.ts +7 -7
  149. package/src/composites/range-inputs/HueSlider/HueSlider.tsx +1 -1
  150. package/src/composites/range-inputs/Slider/Slider.styles.ts +9 -9
  151. package/src/composites/range-inputs/Slider/Slider.tsx +1 -1
  152. package/src/index.ts +49 -10
  153. package/src/primitives/Frame/Frame.styles.ts +55 -12
  154. package/src/primitives/Frame/Frame.tsx +139 -140
  155. package/src/primitives/Frame/Frame.types.ts +119 -1
  156. package/src/primitives/Frame/index.ts +4 -0
  157. package/src/primitives/Icon/Icon.tsx +9 -6
  158. package/src/primitives/Icon/Icon.types.ts +2 -2
  159. package/src/primitives/Text/Text.spans.ts +9 -5
  160. package/src/primitives/Text/Text.tsx +26 -15
  161. package/src/primitives/Text/Text.types.ts +3 -6
  162. package/src/primitives/Wrapper/Wrapper.styles.ts +32 -0
  163. package/src/primitives/Wrapper/Wrapper.tsx +22 -3
  164. package/src/primitives/Wrapper/Wrapper.types.ts +45 -0
  165. package/src/registry/registry.ts +5 -21
  166. package/dist/_COMPONENT_TEMPLATE/ComponentName.d.ts +0 -70
  167. package/dist/_COMPONENT_TEMPLATE/ComponentName.d.ts.map +0 -1
  168. package/dist/_COMPONENT_TEMPLATE/ComponentName.styles.d.ts +0 -23
  169. package/dist/_COMPONENT_TEMPLATE/ComponentName.styles.d.ts.map +0 -1
  170. package/dist/_COMPONENT_TEMPLATE/ComponentName.types.d.ts +0 -45
  171. package/dist/_COMPONENT_TEMPLATE/ComponentName.types.d.ts.map +0 -1
  172. package/dist/_COMPONENT_TEMPLATE/index.d.ts +0 -3
  173. package/dist/_COMPONENT_TEMPLATE/index.d.ts.map +0 -1
@@ -2,19 +2,15 @@ import React, { useMemo } from 'react';
2
2
  import { View, Pressable, Text } from 'react-native';
3
3
  import type { ViewStyle, TextStyle } from 'react-native';
4
4
  import type { FrameProps } from './Frame.types';
5
- import type { ElevationLevel, FrameElevation } from 'newtone-api';
5
+ import type { ElevationLevel, FrameElevation, ElevationName, ThemeName, AppearanceName } from 'newtone-api';
6
6
  import { FrameContext, useFrameContext } from 'newtone-api';
7
- import { useNewtoneTheme } from 'newtone-api';
7
+ import { useNewtoneTheme, _ThemeContext } from 'newtone-api';
8
8
  import { computeTokens } from 'newtone-api';
9
9
  import { getFrameStyles } from './Frame.styles';
10
10
  import { useFocusVisible } from '../useFocusVisible';
11
11
 
12
12
  /**
13
13
  * Wrap raw string/number children in <Text> so they display correctly.
14
- *
15
- * In React Native, raw text like <View>"hello"</View> will crash on native
16
- * and show console warnings on web. All text must be inside a <Text> element.
17
- * This helper scans children and auto-wraps any bare strings or numbers.
18
14
  */
19
15
  function wrapTextChildren(
20
16
  children: React.ReactNode,
@@ -28,16 +24,15 @@ function wrapTextChildren(
28
24
  });
29
25
  }
30
26
 
27
+ /** Map numeric elevation levels to named elevations for the swatches layer. */
28
+ const ELEVATION_MAP: Record<ElevationLevel, ElevationName> = {
29
+ 0: 'sunken',
30
+ 1: 'grounded',
31
+ 2: 'elevated',
32
+ };
33
+
31
34
  /**
32
35
  * Convert user-facing FrameElevation (-2..2) to internal ElevationLevel (0..2).
33
- *
34
- * | Frame | Internal | Background |
35
- * |:-----:|:--------:|:-----------|
36
- * | -2 | 0 | Sunken |
37
- * | -1 | 0 | Sunken |
38
- * | 0 | 1 | Default |
39
- * | 1 | 2 | Elevated |
40
- * | 2 | 2 | Elevated |
41
36
  */
42
37
  function toElevationLevel(frameElevation: FrameElevation): ElevationLevel {
43
38
  if (frameElevation <= -1) return 0;
@@ -53,45 +48,16 @@ function toElevationLevel(frameElevation: FrameElevation): ElevationLevel {
53
48
  * and interactivity capabilities.
54
49
  *
55
50
  * Frames can be nested. Inner frames override outer frames for their subtree.
56
- * Unspecified props inherit from the nearest parent Frame.
57
- *
58
- * @example
59
- * ```tsx
60
- * // Basic layout
61
- * <Frame direction="horizontal" gap="md" padding="lg" align="center">
62
- * <Button onPress={() => {}}>Save</Button>
63
- * <Button variant="tertiary" onPress={() => {}}>Cancel</Button>
64
- * </Frame>
65
- * ```
66
- *
67
- * @example
68
- * ```tsx
69
- * // Card-like frame
70
- * <Frame
71
- * elevation={2}
72
- * radius="lg"
73
- * padding="xl"
74
- * bordered
75
- * onPress={() => navigate('/details')}
76
- * >
77
- * <Text>Clickable card</Text>
78
- * </Frame>
79
- * ```
80
- *
81
- * @example
82
- * ```tsx
83
- * // Grid layout (web)
84
- * <Frame layout="grid" columns={3} gap="md" padding="lg">
85
- * <Frame radius="md" padding="md" bordered>Cell 1</Frame>
86
- * <Frame radius="md" padding="md" bordered>Cell 2</Frame>
87
- * <Frame radius="md" padding="md" bordered>Cell 3</Frame>
88
- * </Frame>
89
- * ```
90
51
  */
91
52
  export function Frame({
92
53
  children,
93
54
  // Elevation
94
55
  elevation,
56
+ // Scheme
57
+ scheme,
58
+ // Theme / Appearance
59
+ theme,
60
+ appearance,
95
61
  // Layout
96
62
  layout,
97
63
  direction,
@@ -112,6 +78,16 @@ export function Frame({
112
78
  maxWidth,
113
79
  minHeight,
114
80
  maxHeight,
81
+ // Positioning
82
+ position,
83
+ top,
84
+ right,
85
+ bottom,
86
+ left,
87
+ zIndex,
88
+ overflow,
89
+ pointerEvents,
90
+ opacity,
115
91
  // Appearance
116
92
  radius,
117
93
  bordered,
@@ -129,45 +105,55 @@ export function Frame({
129
105
  // Style override
130
106
  style,
131
107
  }: FrameProps) {
132
- // Read the global theme configuration and current color mode (light/dark).
133
- const { config, mode, gamut } = useNewtoneTheme();
134
- // Read the elevation from the nearest parent Frame (if nested).
108
+ const themeCtx = useNewtoneTheme();
109
+ const { mode, gamut } = themeCtx;
135
110
  const parentFrameCtx = useFrameContext();
136
111
 
137
- // The user-facing elevation (-2 to 2) controls visual depth (shadows, background).
112
+ // Resolve which config to use: explicit scheme > inherited from parent > default.
113
+ const resolvedConfig = useMemo(() => {
114
+ if (scheme && themeCtx.schemes) {
115
+ const schemeConfig = themeCtx.schemes[scheme];
116
+ if (schemeConfig) return schemeConfig;
117
+ }
118
+ return themeCtx.config;
119
+ }, [scheme, themeCtx.schemes, themeCtx.config]);
120
+
121
+ // Track whether this Frame switches the scheme (needs theme context override).
122
+ const isSchemeOverride = resolvedConfig !== themeCtx.config;
123
+
124
+ // Resolve theme/appearance: explicit prop > parent Frame > defaults.
125
+ const resolvedTheme: ThemeName = theme ?? parentFrameCtx?.theme ?? 'primary';
126
+ const resolvedAppearance: AppearanceName = appearance ?? parentFrameCtx?.appearance ?? 'main';
127
+
138
128
  const resolvedFrameElevation: FrameElevation = elevation ?? 0;
139
129
 
140
- // Convert user-facing elevation to internal level (0-2) for token computation.
141
- // When no elevation is set, inherit from parent Frame or default to 1.
142
130
  const resolvedElevation: ElevationLevel = elevation !== undefined
143
131
  ? toElevationLevel(elevation)
144
132
  : parentFrameCtx?.elevation ?? 1;
145
133
 
146
- // Generate the design tokens (colors, spacing, fonts) for this Frame.
147
- // Frame computes its own tokens instead of using useTokens() because
148
- // useTokens() reads from FrameContext — but this Frame IS the provider,
149
- // so it needs to compute fresh tokens from the resolved theme/elevation.
150
- // Wrapped in useMemo so it only recalculates when the theme/mode/elevation changes.
134
+ // Compute tokens with gamut pre-applied (no [gamut] indexing needed downstream).
151
135
  const tokens = useMemo(() => {
152
136
  return computeTokens(
153
- config.colorSystem,
137
+ resolvedConfig.colorSystem,
154
138
  mode,
155
- resolvedElevation,
156
- config.spacing,
157
- config.radius,
158
- config.typography,
159
- config.icons,
160
- config.tokenOverrides,
139
+ gamut,
140
+ ELEVATION_MAP[resolvedElevation],
141
+ resolvedConfig.spacing,
142
+ resolvedConfig.radius,
143
+ resolvedConfig.typography,
144
+ resolvedConfig.icons,
145
+ resolvedConfig.themeMappings,
146
+ resolvedConfig.swatchDefaults,
147
+ resolvedConfig.relativeSwatchDefaults,
161
148
  );
162
- }, [config, mode, resolvedElevation]);
149
+ }, [resolvedConfig, mode, gamut, resolvedElevation]);
163
150
 
164
- // Calculate all visual styles (background, layout, border, shadow, etc.).
165
- // Only recalculates when one of the style-related props changes.
166
151
  const styles = useMemo(
167
152
  () => getFrameStyles({
168
153
  tokens,
169
- gamut,
170
154
  frameElevation: resolvedFrameElevation,
155
+ theme: resolvedTheme,
156
+ appearance: resolvedAppearance,
171
157
  layout,
172
158
  direction,
173
159
  wrap,
@@ -184,29 +170,53 @@ export function Frame({
184
170
  maxWidth,
185
171
  minHeight,
186
172
  maxHeight,
173
+ position,
174
+ top,
175
+ right,
176
+ bottom,
177
+ left,
178
+ zIndex,
179
+ overflow,
180
+ opacity,
187
181
  radius,
188
182
  bordered,
189
183
  disabled,
190
184
  }),
191
185
  [
192
- tokens, gamut, resolvedFrameElevation,
186
+ tokens, resolvedFrameElevation, resolvedTheme, resolvedAppearance,
193
187
  layout, direction, wrap, reverse, columns, rows,
194
188
  align, justify, padding, gap,
195
189
  width, height, minWidth, maxWidth, minHeight, maxHeight,
190
+ position, top, right, bottom, left, zIndex, overflow, opacity,
196
191
  radius, bordered, disabled,
197
192
  ],
198
193
  );
199
194
 
200
- // This is the value that child components will inherit via FrameContext.
201
- // Any nested Frame, Text, Icon, etc. will read this theme and elevation.
195
+ // Resolved scheme name: explicit prop > inherited from parent Frame.
196
+ const resolvedScheme = scheme ?? parentFrameCtx?.scheme;
197
+
202
198
  const contextValue = useMemo(
203
- () => ({ elevation: resolvedElevation, tokens }),
204
- [resolvedElevation, tokens],
199
+ () => ({
200
+ elevation: resolvedElevation,
201
+ tokens,
202
+ scheme: resolvedScheme,
203
+ theme: resolvedTheme,
204
+ appearance: resolvedAppearance,
205
+ }),
206
+ [resolvedElevation, tokens, resolvedScheme, resolvedTheme, resolvedAppearance],
205
207
  );
206
208
 
207
- // Some styles only work on web browsers (CSS grid, inset shadows).
208
- // We collect them separately and cast them to ViewStyle so React Native
209
- // doesn't complain about unknown properties (they're silently ignored on native).
209
+ // When this Frame switches the scheme, override ThemeContext so that
210
+ // useNewtoneTheme().config returns the scheme's config for descendants.
211
+ const schemeThemeCtx = useMemo(() => {
212
+ if (!isSchemeOverride) return null;
213
+ return {
214
+ ...themeCtx,
215
+ config: resolvedConfig,
216
+ activeScheme: scheme ?? themeCtx.activeScheme,
217
+ };
218
+ }, [isSchemeOverride, themeCtx, resolvedConfig, scheme]);
219
+
210
220
  const webOverrides: ViewStyle[] = [];
211
221
  if (styles.gridWebStyle) {
212
222
  webOverrides.push(styles.gridWebStyle as unknown as ViewStyle);
@@ -215,94 +225,83 @@ export function Frame({
215
225
  webOverrides.push({ boxShadow: styles.insetBoxShadow } as unknown as ViewStyle);
216
226
  }
217
227
 
218
- // Normalize user's custom styles into an array for merging.
219
228
  const userStyles = Array.isArray(style) ? style : style ? [style] : [];
220
229
 
221
- // If the Frame has onPress or href, it needs to respond to taps/clicks.
222
- // In that case we render a Pressable (tappable); otherwise a plain View.
230
+
223
231
  const isInteractive = onPress !== undefined || href !== undefined;
224
232
 
225
- // Detect keyboard-only focus (Tab, arrows) vs mouse/touch focus.
226
- // Only shows a visible focus ring when the user navigated via keyboard,
227
- // matching the browser's native :focus-visible behavior.
228
233
  const { isFocusVisible, focusProps } = useFocusVisible();
229
234
 
230
- // Focus ring style: 2px solid outline in the theme's interactive color,
231
- // offset by 2px so it doesn't overlap the Frame's border.
232
- // Uses CSS outline properties — silently ignored on native platforms.
235
+ // Focus ring: uses the emphasis background of the resolved theme as the outline color.
233
236
  const focusRingStyle = isFocusVisible && !disabled ? {
234
237
  outlineWidth: 2,
235
238
  outlineStyle: 'solid',
236
- outlineColor: tokens.accent.fill[gamut],
239
+ outlineColor: tokens.colors[resolvedTheme].emphasis.background,
237
240
  outlineOffset: 2,
238
- } as unknown as ViewStyle : undefined; // web-only
241
+ } as unknown as ViewStyle : undefined;
239
242
 
240
- // Spread focus event handlers onto the Pressable. These are web-only
241
- // handlers supported by react-native-web — silently ignored on native.
242
- const webFocusProps = isInteractive ? focusProps as any : {}; // web-only
243
+ const webFocusProps = isInteractive ? focusProps as any : {};
243
244
 
244
- // Default text style for any raw strings/numbers passed as children.
245
- // Uses the theme's primary text color, default font, and base size.
246
245
  const textStyle = useMemo<TextStyle>(
247
246
  () => ({
248
- color: tokens.textPrimary[gamut],
247
+ color: tokens.colors[resolvedTheme][resolvedAppearance].fontPrimary,
249
248
  fontSize: tokens.typography.fontSizes['05'],
250
249
  fontFamily: tokens.typography.fonts.main.family,
251
250
  lineHeight: tokens.typography.lineHeights['06'],
252
251
  }),
253
- [tokens],
252
+ [tokens, resolvedTheme, resolvedAppearance],
254
253
  );
255
- // Auto-wrap bare text ("hello") in <Text> elements (required by React Native).
256
- // Wrapped in useMemo so it only re-scans children when they or the text style changes.
257
254
  const wrappedChildren = useMemo(
258
255
  () => wrapTextChildren(children, textStyle),
259
256
  [children, textStyle],
260
257
  );
261
258
 
262
- // FrameContext.Provider shares this Frame's theme and elevation with all
263
- // descendants, so nested components automatically pick up the right colors.
259
+ const content = isInteractive ? (
260
+ <Pressable
261
+ ref={ref}
262
+ testID={testID}
263
+ nativeID={nativeID}
264
+ pointerEvents={pointerEvents}
265
+ accessibilityLabel={accessibilityLabel}
266
+ accessibilityHint={accessibilityHint}
267
+ accessibilityState={disabled ? { disabled: true } : undefined}
268
+ onPress={onPress}
269
+ disabled={disabled}
270
+ {...(href ? { href, accessibilityRole: 'link' as const } : { accessibilityRole: 'button' as const })}
271
+ {...webFocusProps}
272
+ style={({ pressed }) => [
273
+ styles.container,
274
+ pressed && !disabled && styles.pressed,
275
+ focusRingStyle,
276
+ ...webOverrides,
277
+ ...userStyles,
278
+ ]}
279
+ >
280
+ {wrappedChildren}
281
+ </Pressable>
282
+ ) : (
283
+ <View
284
+ ref={ref}
285
+ testID={testID}
286
+ nativeID={nativeID}
287
+ pointerEvents={pointerEvents}
288
+ accessibilityLabel={accessibilityLabel}
289
+ accessibilityHint={accessibilityHint}
290
+ style={[styles.container, ...webOverrides, ...userStyles]}
291
+ >
292
+ {wrappedChildren}
293
+ </View>
294
+ );
295
+
296
+ const wrappedContent = schemeThemeCtx ? (
297
+ <_ThemeContext.Provider value={schemeThemeCtx}>
298
+ {content}
299
+ </_ThemeContext.Provider>
300
+ ) : content;
301
+
264
302
  return (
265
303
  <FrameContext.Provider value={contextValue}>
266
- {isInteractive ? (
267
- // Pressable handles taps. When href is set, react-native-web renders
268
- // it as an <a> tag so it works like a regular link on the web.
269
- <Pressable
270
- ref={ref}
271
- testID={testID}
272
- nativeID={nativeID}
273
- accessibilityLabel={accessibilityLabel}
274
- accessibilityHint={accessibilityHint}
275
- // Tell screen readers this is disabled so assistive technology can announce it.
276
- accessibilityState={disabled ? { disabled: true } : undefined}
277
- onPress={onPress}
278
- disabled={disabled}
279
- {...(href ? { href, accessibilityRole: 'link' as const } : { accessibilityRole: 'button' as const })}
280
- {...webFocusProps}
281
- // The style callback receives { pressed: true/false } so we can
282
- // change the background when the user is actively pressing.
283
- style={({ pressed }) => [
284
- styles.container,
285
- pressed && !disabled && styles.pressed,
286
- focusRingStyle,
287
- ...webOverrides,
288
- ...userStyles,
289
- ]}
290
- >
291
- {wrappedChildren}
292
- </Pressable>
293
- ) : (
294
- // Non-interactive Frame: just a plain View with no tap handling.
295
- <View
296
- ref={ref}
297
- testID={testID}
298
- nativeID={nativeID}
299
- accessibilityLabel={accessibilityLabel}
300
- accessibilityHint={accessibilityHint}
301
- style={[styles.container, ...webOverrides, ...userStyles]}
302
- >
303
- {wrappedChildren}
304
- </View>
305
- )}
304
+ {wrappedContent}
306
305
  </FrameContext.Provider>
307
306
  );
308
307
  }
@@ -1,5 +1,5 @@
1
1
  import type { View, ViewStyle, GestureResponderEvent } from 'react-native';
2
- import type { FrameElevation } from 'newtone-api';
2
+ import type { FrameElevation, ThemeName, AppearanceName } from 'newtone-api';
3
3
 
4
4
  // ── Spacing Types ──────────────────────────────────────────────
5
5
 
@@ -76,6 +76,20 @@ export interface RadiusCorners {
76
76
  */
77
77
  export type RadiusProp = RadiusValue | RadiusCorners;
78
78
 
79
+ // ── Positioning Types ──────────────────────────────────────────
80
+
81
+ /** CSS position mode. `'fixed'` and `'sticky'` are web-only (via react-native-web). */
82
+ export type PositionType = 'absolute' | 'relative' | 'fixed' | 'sticky';
83
+
84
+ /** Position offset value: pixels (number) or percentage string (e.g. `'50%'`). */
85
+ export type OffsetValue = number | string;
86
+
87
+ /** Overflow clipping mode. */
88
+ export type OverflowMode = 'visible' | 'hidden' | 'scroll';
89
+
90
+ /** Pointer-events mode (View prop, not a style). */
91
+ export type PointerEventsMode = 'auto' | 'none' | 'box-none' | 'box-only';
92
+
79
93
  // ── Sizing Types ───────────────────────────────────────────────
80
94
 
81
95
  /**
@@ -159,6 +173,23 @@ export interface FrameProps {
159
173
  */
160
174
  readonly elevation?: FrameElevation;
161
175
 
176
+ /**
177
+ * Named scheme to use for this Frame's subtree.
178
+ * Looks up the scheme's config from the ancestor NewtoneProvider's `schemes` map
179
+ * and uses it instead of the default for token computation and context.
180
+ * Ignored when the provider is in single-config mode.
181
+ *
182
+ * @example
183
+ * ```tsx
184
+ * <NewtoneProvider schemes={{ Default: config1, Dark: config2 }} defaultScheme="Default">
185
+ * <Frame scheme="Dark">
186
+ * {/* Children here get the "Dark" scheme's tokens *​/}
187
+ * </Frame>
188
+ * </NewtoneProvider>
189
+ * ```
190
+ */
191
+ readonly scheme?: string;
192
+
162
193
  // ── Layout ──
163
194
 
164
195
  /** Layout mode. @default 'flex' */
@@ -248,8 +279,95 @@ export interface FrameProps {
248
279
  /** Maximum height in pixels. */
249
280
  readonly maxHeight?: number;
250
281
 
282
+ // ── Positioning ──
283
+
284
+ /**
285
+ * CSS position mode.
286
+ *
287
+ * `'fixed'` and `'sticky'` are web-only (supported via react-native-web).
288
+ * React Native natively supports `'absolute'` and `'relative'`.
289
+ */
290
+ readonly position?: PositionType;
291
+
292
+ /** Offset from the top edge. Accepts pixels (number) or percentage string. */
293
+ readonly top?: OffsetValue;
294
+
295
+ /** Offset from the right edge. Accepts pixels (number) or percentage string. */
296
+ readonly right?: OffsetValue;
297
+
298
+ /** Offset from the bottom edge. Accepts pixels (number) or percentage string. */
299
+ readonly bottom?: OffsetValue;
300
+
301
+ /** Offset from the left edge. Accepts pixels (number) or percentage string. */
302
+ readonly left?: OffsetValue;
303
+
304
+ /** Stacking order. Higher values render above lower values. */
305
+ readonly zIndex?: number;
306
+
307
+ /**
308
+ * Content overflow behavior.
309
+ *
310
+ * When `radius` is set with positive values, Frame auto-applies `overflow: 'hidden'`
311
+ * to clip content at rounded corners. Setting `overflow` explicitly overrides this.
312
+ */
313
+ readonly overflow?: OverflowMode;
314
+
315
+ /**
316
+ * Controls whether the element can be the target of touch/pointer events.
317
+ *
318
+ * - `'auto'` — default, element and children receive events
319
+ * - `'none'` — element and children are invisible to events (pass through)
320
+ * - `'box-none'` — element ignores events but children can receive them
321
+ * - `'box-only'` — element receives events but children cannot
322
+ */
323
+ readonly pointerEvents?: PointerEventsMode;
324
+
325
+ /**
326
+ * Opacity of the element (0 = fully transparent, 1 = fully opaque).
327
+ *
328
+ * When set explicitly, overrides the automatic `0.5` opacity applied by `disabled`.
329
+ */
330
+ readonly opacity?: number;
331
+
251
332
  // ── Appearance ──
252
333
 
334
+ /**
335
+ * Color theme for this Frame's visual rendering (background, border, text).
336
+ *
337
+ * Selects which of the 6 themes to use from the resolved tokens.
338
+ * Does not affect scheme selection — use `scheme` for that.
339
+ *
340
+ * @default 'primary'
341
+ *
342
+ * @example
343
+ * ```tsx
344
+ * <Frame theme="error" appearance="tinted">
345
+ * <Text>Error banner</Text>
346
+ * </Frame>
347
+ * ```
348
+ */
349
+ readonly theme?: ThemeName;
350
+
351
+ /**
352
+ * Appearance variant within the selected theme.
353
+ *
354
+ * Controls the visual emphasis of the Frame's chrome (background, border):
355
+ * - `'main'` — default surface (passive contrast)
356
+ * - `'emphasis'` — prominent, filled surface (active contrast)
357
+ * - `'tinted'` — subtle tinted surface
358
+ * - `'strong'` — bold, high-contrast variant
359
+ *
360
+ * @default 'main'
361
+ *
362
+ * @example
363
+ * ```tsx
364
+ * <Frame theme="primary" appearance="emphasis">
365
+ * <Text>CTA section</Text>
366
+ * </Frame>
367
+ * ```
368
+ */
369
+ readonly appearance?: AppearanceName;
370
+
253
371
  /**
254
372
  * Border radius.
255
373
  *
@@ -17,5 +17,9 @@ export type {
17
17
  Direction,
18
18
  Alignment,
19
19
  Justification,
20
+ PositionType,
21
+ OffsetValue,
22
+ OverflowMode,
23
+ PointerEventsMode,
20
24
  } from './Frame.types';
21
25
  export type { ResolvedCorners } from './Frame.utils';
@@ -1,6 +1,6 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { Text, type TextStyle } from 'react-native';
3
- import { useTokens } from 'newtone-api';
3
+ import { useTokens, useFrameContext } from 'newtone-api';
4
4
  import type { IconProps } from './Icon.types';
5
5
 
6
6
  /**
@@ -17,8 +17,8 @@ import type { IconProps } from './Icon.types';
17
17
  * ```
18
18
  */
19
19
  export function Icon({
20
- name,
21
- size,
20
+ name = 'add',
21
+ size = 24,
22
22
  opticalSize,
23
23
  fill = 0,
24
24
  color,
@@ -32,6 +32,9 @@ export function Icon({
32
32
  }: IconProps) {
33
33
  // Inherit tokens from nearest parent Frame via FrameContext.
34
34
  const tokens = useTokens();
35
+ const frameCtx = useFrameContext();
36
+ const resolvedTheme = frameCtx?.theme ?? 'primary';
37
+ const resolvedAppearance = frameCtx?.appearance ?? 'main';
35
38
 
36
39
  // Build the icon's style from the theme tokens and props.
37
40
  // Wrapped in useMemo so it only recalculates when the inputs change,
@@ -53,8 +56,8 @@ export function Icon({
53
56
  // Use explicit opticalSize if provided, otherwise auto-calculate from fontSize.
54
57
  const opsz = opticalSize ?? getOpticalSize(fontSize);
55
58
 
56
- // Use the provided color, or fall back to the theme's primary text color.
57
- const iconColor = color ?? tokens.textPrimary[tokens.gamut];
59
+ // Use the provided color, or fall back to the inherited theme/appearance text color.
60
+ const iconColor = color ?? tokens.colors[resolvedTheme][resolvedAppearance].fontPrimary;
58
61
 
59
62
  // Build the font family name from the theme's icon variant setting.
60
63
  // Example: variant 'rounded' → 'Material Symbols Rounded'
@@ -80,7 +83,7 @@ export function Icon({
80
83
  fontVariationSettings, // web-only: controls the variable font axes listed above
81
84
  ...style,
82
85
  } as TextStyle; // Cast needed because web-only properties aren't in RN's type definitions
83
- }, [tokens, size, opticalSize, fill, color, style]);
86
+ }, [tokens, size, opticalSize, fill, color, style, resolvedTheme, resolvedAppearance]);
84
87
 
85
88
  // Material Symbols renders icons as text ligatures — the icon name (like "home")
86
89
  // is passed as text content, and the font renders it as the icon glyph.
@@ -23,9 +23,9 @@ export interface IconProps {
23
23
  * @example `'home'`, `'settings'`, `'check'`, `'expand_more'`, `'delete'`, `'add'`, `'search'`
24
24
  * @see {@link https://fonts.google.com/icons Browse all icons}
25
25
  */
26
- readonly name: string;
26
+ readonly name?: string;
27
27
 
28
- /** Font size in pixels. @default tokens.typography.fontSizes['05'] (16px) */
28
+ /** Font size in pixels. @default 24 */
29
29
  readonly size?: number;
30
30
 
31
31
  /** Optical size for variable font axis. Adjusts stroke weight for readability at small sizes. @default same as size */
@@ -1,7 +1,8 @@
1
1
  import React, { useContext, useMemo } from 'react';
2
2
  import { Text as RNText } from 'react-native';
3
3
  import type { TextStyle } from 'react-native';
4
- import { useTokens } from 'newtone-api';
4
+ import { useTokens, useFrameContext } from 'newtone-api';
5
+ import type { ThemeName, AppearanceName } from 'newtone-api';
5
6
  import { TextScopeContext, resolveTextColor } from './Text';
6
7
  import type { TextSpanProps, TextWeight } from './Text.types';
7
8
 
@@ -11,18 +12,21 @@ import type { TextSpanProps, TextWeight } from './Text.types';
11
12
  * Only inline formatting properties (color, weight, italic, underline, highlight) are exposed.
12
13
  */
13
14
  export function TextSpan({ children, color, weight, italic, underline, highlight, style }: TextSpanProps) {
14
- const tokens = useTokens(1);
15
+ const tokens = useTokens();
15
16
  const scopeCtx = useContext(TextScopeContext);
17
+ const frameCtx = useFrameContext();
18
+ const resolvedTheme: ThemeName = frameCtx?.theme ?? 'primary';
19
+ const resolvedAppearance: AppearanceName = frameCtx?.appearance ?? 'main';
16
20
 
17
21
  const spanStyle = useMemo<TextStyle>(() => {
18
22
  const s: TextStyle = {};
19
- if (color) s.color = resolveTextColor(color, tokens);
23
+ if (color) s.color = resolveTextColor(color, tokens, resolvedTheme, resolvedAppearance);
20
24
  if (weight && scopeCtx) s.fontWeight = String(scopeCtx.weights[weight]) as TextStyle['fontWeight'];
21
25
  if (italic) s.fontStyle = 'italic';
22
26
  if (underline) s.textDecorationLine = 'underline';
23
- if (highlight) s.backgroundColor = resolveTextColor(highlight, tokens);
27
+ if (highlight) s.backgroundColor = resolveTextColor(highlight, tokens, resolvedTheme, resolvedAppearance);
24
28
  return s;
25
- }, [tokens, scopeCtx, color, weight, italic, underline, highlight]);
29
+ }, [tokens, scopeCtx, color, weight, italic, underline, highlight, resolvedTheme, resolvedAppearance]);
26
30
 
27
31
  return React.createElement(
28
32
  RNText,