@newtonedev/components 0.1.13 → 0.1.14

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 (160) hide show
  1. package/dist/_COMPONENT_TEMPLATE/ComponentName.styles.d.ts +1 -1
  2. package/dist/composites/actions/Button/Button.d.ts +11 -1
  3. package/dist/composites/actions/Button/Button.d.ts.map +1 -1
  4. package/dist/composites/actions/Button/Button.styles.d.ts.map +1 -1
  5. package/dist/composites/actions/Button/Button.types.d.ts +11 -1
  6. package/dist/composites/actions/Button/Button.types.d.ts.map +1 -1
  7. package/dist/composites/branding/LogoMonogram/LogoMonogram.d.ts +10 -0
  8. package/dist/composites/branding/LogoMonogram/LogoMonogram.d.ts.map +1 -0
  9. package/dist/composites/branding/LogoMonogram/LogoMonogram.types.d.ts +35 -0
  10. package/dist/composites/branding/LogoMonogram/LogoMonogram.types.d.ts.map +1 -0
  11. package/dist/composites/branding/LogoMonogram/index.d.ts +3 -0
  12. package/dist/composites/branding/LogoMonogram/index.d.ts.map +1 -0
  13. package/dist/composites/branding/LogoWordmark/LogoWordmark.d.ts +9 -0
  14. package/dist/composites/branding/LogoWordmark/LogoWordmark.d.ts.map +1 -0
  15. package/dist/composites/branding/LogoWordmark/LogoWordmark.types.d.ts +26 -0
  16. package/dist/composites/branding/LogoWordmark/LogoWordmark.types.d.ts.map +1 -0
  17. package/dist/composites/branding/LogoWordmark/index.d.ts +3 -0
  18. package/dist/composites/branding/LogoWordmark/index.d.ts.map +1 -0
  19. package/dist/composites/display/Chip/Chip.d.ts +25 -0
  20. package/dist/composites/display/Chip/Chip.d.ts.map +1 -0
  21. package/dist/composites/display/Chip/Chip.styles.d.ts +29 -0
  22. package/dist/composites/display/Chip/Chip.styles.d.ts.map +1 -0
  23. package/dist/composites/display/Chip/Chip.types.d.ts +63 -0
  24. package/dist/composites/display/Chip/Chip.types.d.ts.map +1 -0
  25. package/dist/composites/display/Chip/index.d.ts +3 -0
  26. package/dist/composites/display/Chip/index.d.ts.map +1 -0
  27. package/dist/composites/form-controls/Select/Select.d.ts.map +1 -1
  28. package/dist/composites/form-controls/Select/Select.styles.d.ts +2 -2
  29. package/dist/composites/form-controls/Select/Select.styles.d.ts.map +1 -1
  30. package/dist/composites/form-controls/Select/SelectOption.d.ts.map +1 -1
  31. package/dist/composites/form-controls/TextInput/TextInput.d.ts.map +1 -1
  32. package/dist/composites/form-controls/TextInput/TextInput.styles.d.ts +2 -2
  33. package/dist/composites/form-controls/TextInput/TextInput.styles.d.ts.map +1 -1
  34. package/dist/composites/form-controls/Toggle/Toggle.d.ts.map +1 -1
  35. package/dist/composites/form-controls/Toggle/Toggle.styles.d.ts +2 -2
  36. package/dist/composites/form-controls/Toggle/Toggle.styles.d.ts.map +1 -1
  37. package/dist/composites/layout/AppShell/AppShell.d.ts.map +1 -1
  38. package/dist/composites/layout/AppShell/AppShell.styles.d.ts +2 -2
  39. package/dist/composites/layout/AppShell/AppShell.styles.d.ts.map +1 -1
  40. package/dist/composites/layout/Card/Card.d.ts.map +1 -1
  41. package/dist/composites/layout/Card/Card.styles.d.ts +2 -2
  42. package/dist/composites/layout/Card/Card.styles.d.ts.map +1 -1
  43. package/dist/composites/layout/ContentCard/ContentCard.d.ts +33 -0
  44. package/dist/composites/layout/ContentCard/ContentCard.d.ts.map +1 -0
  45. package/dist/composites/layout/ContentCard/ContentCard.styles.d.ts +10 -0
  46. package/dist/composites/layout/ContentCard/ContentCard.styles.d.ts.map +1 -0
  47. package/dist/composites/layout/ContentCard/ContentCard.types.d.ts +52 -0
  48. package/dist/composites/layout/ContentCard/ContentCard.types.d.ts.map +1 -0
  49. package/dist/composites/layout/ContentCard/index.d.ts +3 -0
  50. package/dist/composites/layout/ContentCard/index.d.ts.map +1 -0
  51. package/dist/composites/layout/Divider/Divider.d.ts +25 -0
  52. package/dist/composites/layout/Divider/Divider.d.ts.map +1 -0
  53. package/dist/composites/layout/Divider/Divider.styles.d.ts +8 -0
  54. package/dist/composites/layout/Divider/Divider.styles.d.ts.map +1 -0
  55. package/dist/composites/layout/Divider/Divider.types.d.ts +25 -0
  56. package/dist/composites/layout/Divider/Divider.types.d.ts.map +1 -0
  57. package/dist/composites/layout/Divider/index.d.ts +3 -0
  58. package/dist/composites/layout/Divider/index.d.ts.map +1 -0
  59. package/dist/composites/layout/Navbar/Navbar.d.ts.map +1 -1
  60. package/dist/composites/layout/Navbar/Navbar.styles.d.ts +4 -3
  61. package/dist/composites/layout/Navbar/Navbar.styles.d.ts.map +1 -1
  62. package/dist/composites/layout/Sidebar/Sidebar.d.ts.map +1 -1
  63. package/dist/composites/layout/Sidebar/Sidebar.styles.d.ts +4 -3
  64. package/dist/composites/layout/Sidebar/Sidebar.styles.d.ts.map +1 -1
  65. package/dist/composites/overlays/Popover/Popover.d.ts.map +1 -1
  66. package/dist/composites/overlays/Popover/Popover.styles.d.ts +2 -2
  67. package/dist/composites/overlays/Popover/Popover.styles.d.ts.map +1 -1
  68. package/dist/composites/range-inputs/ColorScaleSlider/ColorScaleSlider.d.ts.map +1 -1
  69. package/dist/composites/range-inputs/ColorScaleSlider/ColorScaleSlider.styles.d.ts +2 -2
  70. package/dist/composites/range-inputs/ColorScaleSlider/ColorScaleSlider.styles.d.ts.map +1 -1
  71. package/dist/composites/range-inputs/HueSlider/HueSlider.styles.d.ts +2 -2
  72. package/dist/composites/range-inputs/HueSlider/HueSlider.styles.d.ts.map +1 -1
  73. package/dist/composites/range-inputs/Slider/Slider.styles.d.ts +2 -2
  74. package/dist/composites/range-inputs/Slider/Slider.styles.d.ts.map +1 -1
  75. package/dist/index.cjs +903 -396
  76. package/dist/index.cjs.map +1 -1
  77. package/dist/index.d.ts +12 -2
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +830 -344
  80. package/dist/index.js.map +1 -1
  81. package/dist/primitives/Frame/Frame.d.ts +1 -35
  82. package/dist/primitives/Frame/Frame.d.ts.map +1 -1
  83. package/dist/primitives/Frame/Frame.styles.d.ts +13 -4
  84. package/dist/primitives/Frame/Frame.styles.d.ts.map +1 -1
  85. package/dist/primitives/Frame/Frame.types.d.ts +99 -1
  86. package/dist/primitives/Frame/Frame.types.d.ts.map +1 -1
  87. package/dist/primitives/Frame/index.d.ts +1 -1
  88. package/dist/primitives/Frame/index.d.ts.map +1 -1
  89. package/dist/primitives/Icon/Icon.d.ts.map +1 -1
  90. package/dist/primitives/Icon/Icon.types.d.ts +2 -2
  91. package/dist/primitives/Icon/Icon.types.d.ts.map +1 -1
  92. package/dist/primitives/Text/Text.d.ts +5 -3
  93. package/dist/primitives/Text/Text.d.ts.map +1 -1
  94. package/dist/primitives/Text/Text.spans.d.ts.map +1 -1
  95. package/dist/primitives/Text/Text.types.d.ts +3 -6
  96. package/dist/primitives/Text/Text.types.d.ts.map +1 -1
  97. package/dist/primitives/Wrapper/Wrapper.d.ts +1 -1
  98. package/dist/primitives/Wrapper/Wrapper.d.ts.map +1 -1
  99. package/dist/primitives/Wrapper/Wrapper.styles.d.ts +9 -1
  100. package/dist/primitives/Wrapper/Wrapper.styles.d.ts.map +1 -1
  101. package/dist/primitives/Wrapper/Wrapper.types.d.ts +31 -1
  102. package/dist/primitives/Wrapper/Wrapper.types.d.ts.map +1 -1
  103. package/package.json +1 -1
  104. package/src/composites/actions/Button/Button.styles.ts +71 -55
  105. package/src/composites/actions/Button/Button.tsx +34 -13
  106. package/src/composites/actions/Button/Button.types.ts +13 -1
  107. package/src/composites/branding/LogoMonogram/LogoMonogram.tsx +29 -0
  108. package/src/composites/branding/LogoMonogram/LogoMonogram.types.ts +35 -0
  109. package/src/composites/branding/LogoMonogram/index.ts +2 -0
  110. package/src/composites/branding/LogoWordmark/LogoWordmark.tsx +29 -0
  111. package/src/composites/branding/LogoWordmark/LogoWordmark.types.ts +25 -0
  112. package/src/composites/branding/LogoWordmark/index.ts +2 -0
  113. package/src/composites/display/Chip/Chip.styles.ts +189 -0
  114. package/src/composites/display/Chip/Chip.tsx +97 -0
  115. package/src/composites/display/Chip/Chip.types.ts +74 -0
  116. package/src/composites/display/Chip/index.ts +2 -0
  117. package/src/composites/form-controls/Select/Select.styles.ts +10 -10
  118. package/src/composites/form-controls/Select/Select.tsx +9 -6
  119. package/src/composites/form-controls/Select/SelectOption.tsx +10 -7
  120. package/src/composites/form-controls/TextInput/TextInput.styles.ts +12 -8
  121. package/src/composites/form-controls/TextInput/TextInput.tsx +7 -4
  122. package/src/composites/form-controls/Toggle/Toggle.styles.ts +10 -7
  123. package/src/composites/form-controls/Toggle/Toggle.tsx +4 -3
  124. package/src/composites/layout/AppShell/AppShell.styles.ts +8 -3
  125. package/src/composites/layout/AppShell/AppShell.tsx +6 -2
  126. package/src/composites/layout/Card/Card.styles.ts +10 -4
  127. package/src/composites/layout/Card/Card.tsx +4 -3
  128. package/src/composites/layout/ContentCard/ContentCard.styles.ts +44 -0
  129. package/src/composites/layout/ContentCard/ContentCard.tsx +71 -0
  130. package/src/composites/layout/ContentCard/ContentCard.types.ts +60 -0
  131. package/src/composites/layout/ContentCard/index.ts +2 -0
  132. package/src/composites/layout/Divider/Divider.styles.ts +30 -0
  133. package/src/composites/layout/Divider/Divider.tsx +46 -0
  134. package/src/composites/layout/Divider/Divider.types.ts +28 -0
  135. package/src/composites/layout/Divider/index.ts +2 -0
  136. package/src/composites/layout/Navbar/Navbar.styles.ts +7 -5
  137. package/src/composites/layout/Navbar/Navbar.tsx +4 -3
  138. package/src/composites/layout/Sidebar/Sidebar.styles.ts +7 -5
  139. package/src/composites/layout/Sidebar/Sidebar.tsx +4 -3
  140. package/src/composites/overlays/Popover/Popover.styles.ts +7 -5
  141. package/src/composites/overlays/Popover/Popover.tsx +4 -3
  142. package/src/composites/range-inputs/ColorScaleSlider/ColorScaleSlider.styles.ts +5 -5
  143. package/src/composites/range-inputs/ColorScaleSlider/ColorScaleSlider.tsx +4 -3
  144. package/src/composites/range-inputs/HueSlider/HueSlider.styles.ts +7 -7
  145. package/src/composites/range-inputs/HueSlider/HueSlider.tsx +1 -1
  146. package/src/composites/range-inputs/Slider/Slider.styles.ts +9 -9
  147. package/src/composites/range-inputs/Slider/Slider.tsx +1 -1
  148. package/src/index.ts +43 -9
  149. package/src/primitives/Frame/Frame.styles.ts +55 -12
  150. package/src/primitives/Frame/Frame.tsx +138 -140
  151. package/src/primitives/Frame/Frame.types.ts +119 -1
  152. package/src/primitives/Frame/index.ts +4 -0
  153. package/src/primitives/Icon/Icon.tsx +9 -6
  154. package/src/primitives/Icon/Icon.types.ts +2 -2
  155. package/src/primitives/Text/Text.spans.ts +9 -5
  156. package/src/primitives/Text/Text.tsx +26 -15
  157. package/src/primitives/Text/Text.types.ts +3 -6
  158. package/src/primitives/Wrapper/Wrapper.styles.ts +32 -0
  159. package/src/primitives/Wrapper/Wrapper.tsx +22 -3
  160. package/src/primitives/Wrapper/Wrapper.types.ts +45 -0
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  export {
3
3
  NewtoneProvider,
4
4
  useNewtoneTheme,
5
+ useScheme,
5
6
  FrameContext,
6
7
  useFrameContext,
7
8
  useBreakpoint,
@@ -11,18 +12,21 @@ export {
11
12
  DEFAULT_ROLE_SCALES,
12
13
  useTokens,
13
14
  computeTokens,
14
- NEUTRAL_DEFAULTS,
15
- ACCENT_DEFAULTS,
15
+ computeSwatches,
16
+ computeColors,
17
+ resolveTheme,
18
+ PRIMARY_DEFAULTS,
19
+ SECONDARY_DEFAULTS,
20
+ TERTIARY_DEFAULTS,
16
21
  SUCCESS_DEFAULTS,
17
22
  WARNING_DEFAULTS,
18
23
  ERROR_DEFAULTS,
24
+ DEFAULT_THEME_MAPPINGS,
19
25
  buildGoogleFontsUrl,
20
26
  measureAvgCharWidth,
21
27
  useLocalCalibration,
22
28
  useTypographyCalibrations,
23
29
  enqueueObservation,
24
- migrateV1ToV2,
25
- isV2TokenOverrides,
26
30
  } from 'newtone-api';
27
31
  export type {
28
32
  ColorMode,
@@ -36,11 +40,16 @@ export type {
36
40
  FontConfig,
37
41
  FontWeights,
38
42
  FontSlot,
39
- TokenOverrides,
40
43
  TokenColor,
41
- PaletteModeTokens,
42
- PaletteTokenOverrides,
43
- LegacyTokenOverrides,
44
+ TokenName,
45
+ ContrastLevel,
46
+ PaletteId,
47
+ ThemeName,
48
+ AppearanceName,
49
+ ColorRef,
50
+ AppearanceMapping,
51
+ ThemeMapping,
52
+ ThemeMappings,
44
53
  FontSizeScale,
45
54
  LineHeightScale,
46
55
  RoleSizeStep,
@@ -53,8 +62,17 @@ export type {
53
62
  FrameContextValue,
54
63
  UseTokensResult,
55
64
  PaletteDefaults,
65
+ ContrastLevelDefaults,
66
+ SwatchDefaults,
56
67
  ResolvedTokens,
57
- PaletteTokens,
68
+ ResolvedSwatches,
69
+ PaletteColors,
70
+ ContrastLevelColors,
71
+ ElevationColors,
72
+ ElevationName,
73
+ AppearanceTokens,
74
+ ThemeTokens,
75
+ ResolvedColor,
58
76
  DynamicRange,
59
77
  ColorResult,
60
78
  Srgb,
@@ -113,6 +131,15 @@ export type { ButtonProps, ButtonVariant, ButtonSemantic, ButtonSize, ButtonIcon
113
131
  export { Card } from './composites/layout/Card/Card';
114
132
  export type { CardProps } from './composites/layout/Card/Card.types';
115
133
 
134
+ export { Chip } from './composites/display/Chip/Chip';
135
+ export type { ChipProps, ChipVariant, ChipSemantic, ChipSize } from './composites/display/Chip/Chip.types';
136
+
137
+ export { Divider } from './composites/layout/Divider/Divider';
138
+ export type { DividerProps, DividerOrientation } from './composites/layout/Divider/Divider.types';
139
+
140
+ export { ContentCard } from './composites/layout/ContentCard/ContentCard';
141
+ export type { ContentCardProps, ContentCardVariant } from './composites/layout/ContentCard/ContentCard.types';
142
+
116
143
  export { TextInput } from './composites/form-controls/TextInput/TextInput';
117
144
  export type { TextInputProps } from './composites/form-controls/TextInput/TextInput.types';
118
145
 
@@ -145,6 +172,13 @@ export type { SidebarProps } from './composites/layout/Sidebar/Sidebar.types';
145
172
  export { Navbar } from './composites/layout/Navbar/Navbar';
146
173
  export type { NavbarProps } from './composites/layout/Navbar/Navbar.types';
147
174
 
175
+ // Branding
176
+ export { LogoMonogram } from './composites/branding/LogoMonogram/LogoMonogram';
177
+ export type { LogoMonogramProps } from './composites/branding/LogoMonogram/LogoMonogram.types';
178
+
179
+ export { LogoWordmark } from './composites/branding/LogoWordmark/LogoWordmark';
180
+ export type { LogoWordmarkProps } from './composites/branding/LogoWordmark/LogoWordmark.types';
181
+
148
182
  // Component registry + code generation
149
183
  export type {
150
184
  CategoryMeta,
@@ -1,7 +1,7 @@
1
1
  import type { ViewStyle } from 'react-native';
2
2
  import { StyleSheet } from 'react-native';
3
- import type { ResolvedTokens, ColorGamut } from 'newtone-api';
4
- import type { FrameElevation } from 'newtone-api';
3
+ import type { ResolvedTokens, AppearanceTokens } from 'newtone-api';
4
+ import type { FrameElevation, ThemeName, AppearanceName } from 'newtone-api';
5
5
  import type {
6
6
  PaddingProp,
7
7
  GapProp,
@@ -11,6 +11,9 @@ import type {
11
11
  Justification,
12
12
  RadiusProp,
13
13
  LayoutMode,
14
+ PositionType,
15
+ OffsetValue,
16
+ OverflowMode,
14
17
  } from './Frame.types';
15
18
  import {
16
19
  resolvePadding,
@@ -27,9 +30,12 @@ import {
27
30
 
28
31
  export interface FrameStyleInput {
29
32
  readonly tokens: ResolvedTokens;
30
- readonly gamut: ColorGamut;
31
33
  readonly frameElevation: FrameElevation;
32
34
 
35
+ // Theme / Appearance
36
+ readonly theme?: ThemeName;
37
+ readonly appearance?: AppearanceName;
38
+
33
39
  // Layout
34
40
  readonly layout?: LayoutMode;
35
41
  readonly direction?: Direction;
@@ -54,6 +60,16 @@ export interface FrameStyleInput {
54
60
  readonly minHeight?: number;
55
61
  readonly maxHeight?: number;
56
62
 
63
+ // Positioning
64
+ readonly position?: PositionType;
65
+ readonly top?: OffsetValue;
66
+ readonly right?: OffsetValue;
67
+ readonly bottom?: OffsetValue;
68
+ readonly left?: OffsetValue;
69
+ readonly zIndex?: number;
70
+ readonly overflow?: OverflowMode;
71
+ readonly opacity?: number;
72
+
57
73
  // Appearance
58
74
  readonly radius?: RadiusProp;
59
75
  readonly bordered?: boolean;
@@ -87,8 +103,9 @@ export interface FrameStyles {
87
103
  export function getFrameStyles(input: FrameStyleInput): FrameStyles {
88
104
  const {
89
105
  tokens,
90
- gamut,
91
106
  frameElevation,
107
+ theme = 'primary',
108
+ appearance = 'main',
92
109
  layout = 'flex',
93
110
  direction = 'vertical',
94
111
  wrap = false,
@@ -105,6 +122,14 @@ export function getFrameStyles(input: FrameStyleInput): FrameStyles {
105
122
  maxWidth,
106
123
  minHeight,
107
124
  maxHeight,
125
+ position,
126
+ top,
127
+ right,
128
+ bottom,
129
+ left,
130
+ zIndex,
131
+ overflow,
132
+ opacity,
108
133
  radius,
109
134
  bordered = false,
110
135
  disabled = false,
@@ -116,9 +141,12 @@ export function getFrameStyles(input: FrameStyleInput): FrameStyles {
116
141
  const container: Record<string, unknown> = {};
117
142
 
118
143
  // ── Background & foreground ──
119
- // Set the surface color and default text color from the current theme.
120
- container.backgroundColor = tokens.background[gamut];
121
- container.color = tokens.textPrimary[gamut];
144
+ // Resolve the appearance tokens for the selected theme + appearance.
145
+ const appearanceTokens: AppearanceTokens = tokens.colors[theme][appearance];
146
+
147
+ // Set the surface color and default text color from the resolved appearance.
148
+ container.backgroundColor = appearanceTokens.background;
149
+ container.color = appearanceTokens.fontPrimary;
122
150
 
123
151
  // ── Layout mode ──
124
152
  if (layout === 'flex') {
@@ -172,6 +200,15 @@ export function getFrameStyles(input: FrameStyleInput): FrameStyles {
172
200
  if (minHeight !== undefined) container.minHeight = minHeight;
173
201
  if (maxHeight !== undefined) container.maxHeight = maxHeight;
174
202
 
203
+ // ── Positioning ──
204
+ // Set CSS position mode and offsets for absolute/fixed/sticky positioning.
205
+ if (position) container.position = position;
206
+ if (top !== undefined) container.top = top;
207
+ if (right !== undefined) container.right = right;
208
+ if (bottom !== undefined) container.bottom = bottom;
209
+ if (left !== undefined) container.left = left;
210
+ if (zIndex !== undefined) container.zIndex = zIndex;
211
+
175
212
  // ── Radius ──
176
213
  // Round the corners of the Frame (e.g. for cards, buttons, pills).
177
214
  if (radius !== undefined) {
@@ -188,11 +225,15 @@ export function getFrameStyles(input: FrameStyleInput): FrameStyles {
188
225
  }
189
226
  }
190
227
 
228
+ // ── Overflow ──
229
+ // Explicit overflow prop overrides the auto-overflow from radius.
230
+ if (overflow) container.overflow = overflow;
231
+
191
232
  // ── Border ──
192
233
  // Add a thin border using the theme's border color.
193
234
  if (bordered) {
194
235
  container.borderWidth = 1;
195
- container.borderColor = tokens.border[gamut];
236
+ container.borderColor = appearanceTokens.fontTertiary;
196
237
  }
197
238
 
198
239
  // ── Outer shadow (elevation 2) ──
@@ -207,9 +248,11 @@ export function getFrameStyles(input: FrameStyleInput): FrameStyles {
207
248
  container.elevation = 4; // Android-specific shadow depth
208
249
  }
209
250
 
210
- // ── Disabled ──
211
- // Make the Frame look faded when disabled.
212
- if (disabled) {
251
+ // ── Opacity ──
252
+ // Explicit opacity takes precedence over the disabled fade.
253
+ if (opacity !== undefined) {
254
+ container.opacity = opacity;
255
+ } else if (disabled) {
213
256
  container.opacity = 0.5;
214
257
  }
215
258
 
@@ -217,7 +260,7 @@ export function getFrameStyles(input: FrameStyleInput): FrameStyles {
217
260
  // When the user is pressing an interactive Frame, shift the background
218
261
  // to a darker "sunken" shade to give visual feedback.
219
262
  const pressed = StyleSheet.create({
220
- s: { backgroundColor: tokens.backgroundSunken[gamut] },
263
+ s: { backgroundColor: appearanceTokens.fontSecondary },
221
264
  }).s;
222
265
 
223
266
  // ── Grid web style (web-only) ──
@@ -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,54 @@ 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,
161
147
  );
162
- }, [config, mode, resolvedElevation]);
148
+ }, [resolvedConfig, mode, gamut, resolvedElevation]);
163
149
 
164
- // Calculate all visual styles (background, layout, border, shadow, etc.).
165
- // Only recalculates when one of the style-related props changes.
166
150
  const styles = useMemo(
167
151
  () => getFrameStyles({
168
152
  tokens,
169
- gamut,
170
153
  frameElevation: resolvedFrameElevation,
154
+ theme: resolvedTheme,
155
+ appearance: resolvedAppearance,
171
156
  layout,
172
157
  direction,
173
158
  wrap,
@@ -184,29 +169,53 @@ export function Frame({
184
169
  maxWidth,
185
170
  minHeight,
186
171
  maxHeight,
172
+ position,
173
+ top,
174
+ right,
175
+ bottom,
176
+ left,
177
+ zIndex,
178
+ overflow,
179
+ opacity,
187
180
  radius,
188
181
  bordered,
189
182
  disabled,
190
183
  }),
191
184
  [
192
- tokens, gamut, resolvedFrameElevation,
185
+ tokens, resolvedFrameElevation, resolvedTheme, resolvedAppearance,
193
186
  layout, direction, wrap, reverse, columns, rows,
194
187
  align, justify, padding, gap,
195
188
  width, height, minWidth, maxWidth, minHeight, maxHeight,
189
+ position, top, right, bottom, left, zIndex, overflow, opacity,
196
190
  radius, bordered, disabled,
197
191
  ],
198
192
  );
199
193
 
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.
194
+ // Resolved scheme name: explicit prop > inherited from parent Frame.
195
+ const resolvedScheme = scheme ?? parentFrameCtx?.scheme;
196
+
202
197
  const contextValue = useMemo(
203
- () => ({ elevation: resolvedElevation, tokens }),
204
- [resolvedElevation, tokens],
198
+ () => ({
199
+ elevation: resolvedElevation,
200
+ tokens,
201
+ scheme: resolvedScheme,
202
+ theme: resolvedTheme,
203
+ appearance: resolvedAppearance,
204
+ }),
205
+ [resolvedElevation, tokens, resolvedScheme, resolvedTheme, resolvedAppearance],
205
206
  );
206
207
 
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).
208
+ // When this Frame switches the scheme, override ThemeContext so that
209
+ // useNewtoneTheme().config returns the scheme's config for descendants.
210
+ const schemeThemeCtx = useMemo(() => {
211
+ if (!isSchemeOverride) return null;
212
+ return {
213
+ ...themeCtx,
214
+ config: resolvedConfig,
215
+ activeScheme: scheme ?? themeCtx.activeScheme,
216
+ };
217
+ }, [isSchemeOverride, themeCtx, resolvedConfig, scheme]);
218
+
210
219
  const webOverrides: ViewStyle[] = [];
211
220
  if (styles.gridWebStyle) {
212
221
  webOverrides.push(styles.gridWebStyle as unknown as ViewStyle);
@@ -215,94 +224,83 @@ export function Frame({
215
224
  webOverrides.push({ boxShadow: styles.insetBoxShadow } as unknown as ViewStyle);
216
225
  }
217
226
 
218
- // Normalize user's custom styles into an array for merging.
219
227
  const userStyles = Array.isArray(style) ? style : style ? [style] : [];
220
228
 
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.
229
+
223
230
  const isInteractive = onPress !== undefined || href !== undefined;
224
231
 
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
232
  const { isFocusVisible, focusProps } = useFocusVisible();
229
233
 
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.
234
+ // Focus ring: uses the emphasis background of the resolved theme as the outline color.
233
235
  const focusRingStyle = isFocusVisible && !disabled ? {
234
236
  outlineWidth: 2,
235
237
  outlineStyle: 'solid',
236
- outlineColor: tokens.accent.fill[gamut],
238
+ outlineColor: tokens.colors[resolvedTheme].emphasis.background,
237
239
  outlineOffset: 2,
238
- } as unknown as ViewStyle : undefined; // web-only
240
+ } as unknown as ViewStyle : undefined;
239
241
 
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
242
+ const webFocusProps = isInteractive ? focusProps as any : {};
243
243
 
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
244
  const textStyle = useMemo<TextStyle>(
247
245
  () => ({
248
- color: tokens.textPrimary[gamut],
246
+ color: tokens.colors[resolvedTheme][resolvedAppearance].fontPrimary,
249
247
  fontSize: tokens.typography.fontSizes['05'],
250
248
  fontFamily: tokens.typography.fonts.main.family,
251
249
  lineHeight: tokens.typography.lineHeights['06'],
252
250
  }),
253
- [tokens],
251
+ [tokens, resolvedTheme, resolvedAppearance],
254
252
  );
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
253
  const wrappedChildren = useMemo(
258
254
  () => wrapTextChildren(children, textStyle),
259
255
  [children, textStyle],
260
256
  );
261
257
 
262
- // FrameContext.Provider shares this Frame's theme and elevation with all
263
- // descendants, so nested components automatically pick up the right colors.
258
+ const content = isInteractive ? (
259
+ <Pressable
260
+ ref={ref}
261
+ testID={testID}
262
+ nativeID={nativeID}
263
+ pointerEvents={pointerEvents}
264
+ accessibilityLabel={accessibilityLabel}
265
+ accessibilityHint={accessibilityHint}
266
+ accessibilityState={disabled ? { disabled: true } : undefined}
267
+ onPress={onPress}
268
+ disabled={disabled}
269
+ {...(href ? { href, accessibilityRole: 'link' as const } : { accessibilityRole: 'button' as const })}
270
+ {...webFocusProps}
271
+ style={({ pressed }) => [
272
+ styles.container,
273
+ pressed && !disabled && styles.pressed,
274
+ focusRingStyle,
275
+ ...webOverrides,
276
+ ...userStyles,
277
+ ]}
278
+ >
279
+ {wrappedChildren}
280
+ </Pressable>
281
+ ) : (
282
+ <View
283
+ ref={ref}
284
+ testID={testID}
285
+ nativeID={nativeID}
286
+ pointerEvents={pointerEvents}
287
+ accessibilityLabel={accessibilityLabel}
288
+ accessibilityHint={accessibilityHint}
289
+ style={[styles.container, ...webOverrides, ...userStyles]}
290
+ >
291
+ {wrappedChildren}
292
+ </View>
293
+ );
294
+
295
+ const wrappedContent = schemeThemeCtx ? (
296
+ <_ThemeContext.Provider value={schemeThemeCtx}>
297
+ {content}
298
+ </_ThemeContext.Provider>
299
+ ) : content;
300
+
264
301
  return (
265
302
  <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
- )}
303
+ {wrappedContent}
306
304
  </FrameContext.Provider>
307
305
  );
308
306
  }