react-native-terra-ui 0.7.2 → 0.8.1

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 (252) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/lib/module/components/avatar/Avatar.js +20 -20
  4. package/lib/module/components/avatar/Avatar.js.map +1 -1
  5. package/lib/module/components/button/Button.js +11 -11
  6. package/lib/module/components/button/Button.js.map +1 -1
  7. package/lib/module/components/chip/Chip.js +32 -32
  8. package/lib/module/components/chip/Chip.js.map +1 -1
  9. package/lib/module/components/empty-state/EmptyState.js +3 -3
  10. package/lib/module/components/empty-state/EmptyState.js.map +1 -1
  11. package/lib/module/components/header/variants/LargeTitleHeader/index.js +30 -32
  12. package/lib/module/components/header/variants/LargeTitleHeader/index.js.map +1 -1
  13. package/lib/module/components/header/variants/TitleHeader/index.js +26 -18
  14. package/lib/module/components/header/variants/TitleHeader/index.js.map +1 -1
  15. package/lib/module/components/icon/Icon.js +3 -3
  16. package/lib/module/components/page-indicator/hooks/use-page-indicator-colors.js +2 -2
  17. package/lib/module/components/screen/Screen.js +63 -34
  18. package/lib/module/components/screen/Screen.js.map +1 -1
  19. package/lib/module/components/screen/ScreenContext.js +28 -9
  20. package/lib/module/components/screen/ScreenContext.js.map +1 -1
  21. package/lib/module/components/screen/ScreenFlatList.js +4 -162
  22. package/lib/module/components/screen/ScreenFlatList.js.map +1 -1
  23. package/lib/module/components/screen/ScreenList.js +81 -97
  24. package/lib/module/components/screen/ScreenList.js.map +1 -1
  25. package/lib/module/components/screen/ScreenScrollView.js +64 -115
  26. package/lib/module/components/screen/ScreenScrollView.js.map +1 -1
  27. package/lib/module/components/screen/hooks/use-screen-ref.js +18 -0
  28. package/lib/module/components/screen/hooks/use-screen-ref.js.map +1 -0
  29. package/lib/module/components/screen/hooks/use-screen-scroll-handler.js +19 -0
  30. package/lib/module/components/screen/hooks/use-screen-scroll-handler.js.map +1 -0
  31. package/lib/module/components/screen/index.js.map +1 -1
  32. package/lib/module/components/screen/parts/ScreenFrame.js +47 -0
  33. package/lib/module/components/screen/parts/ScreenFrame.js.map +1 -0
  34. package/lib/module/components/screen/parts/ScreenHeaderAppearance.js +43 -0
  35. package/lib/module/components/screen/parts/ScreenHeaderAppearance.js.map +1 -0
  36. package/lib/module/components/screen/parts/ScreenPlaceholder.js +11 -13
  37. package/lib/module/components/screen/parts/ScreenPlaceholder.js.map +1 -1
  38. package/lib/module/components/screen/parts/ScreenSafeArea.android.js +27 -0
  39. package/lib/module/components/screen/parts/ScreenSafeArea.android.js.map +1 -0
  40. package/lib/module/components/screen/parts/ScreenSafeArea.js +26 -0
  41. package/lib/module/components/screen/parts/ScreenSafeArea.js.map +1 -0
  42. package/lib/module/components/screen/utils.js +18 -9
  43. package/lib/module/components/screen/utils.js.map +1 -1
  44. package/lib/module/components/spinner/Spinner.js +2 -2
  45. package/lib/module/components/text/Text.js +1 -1
  46. package/lib/module/components/toast/Toast.js +13 -13
  47. package/lib/module/components/toast/Toast.js.map +1 -1
  48. package/lib/module/components/toolbar/Toolbar.js +2 -2
  49. package/lib/module/context/ThemeProvider.js +22 -39
  50. package/lib/module/context/ThemeProvider.js.map +1 -1
  51. package/lib/module/context/index.js +1 -1
  52. package/lib/module/context/index.js.map +1 -1
  53. package/lib/module/theme/breakpoints.js +1 -1
  54. package/lib/module/theme/define-config.js +21 -0
  55. package/lib/module/theme/define-config.js.map +1 -0
  56. package/lib/module/theme/diagnostics.js +13 -0
  57. package/lib/module/theme/diagnostics.js.map +1 -0
  58. package/lib/module/theme/errors.js +15 -0
  59. package/lib/module/theme/errors.js.map +1 -0
  60. package/lib/module/theme/index.js +7 -1
  61. package/lib/module/theme/index.js.map +1 -1
  62. package/lib/module/theme/legacy-tokens.js +119 -0
  63. package/lib/module/theme/legacy-tokens.js.map +1 -0
  64. package/lib/module/theme/registry.js +122 -130
  65. package/lib/module/theme/registry.js.map +1 -1
  66. package/lib/module/theme/resolve-config.js +346 -0
  67. package/lib/module/theme/resolve-config.js.map +1 -0
  68. package/lib/module/theme/runtime.js +57 -12
  69. package/lib/module/theme/runtime.js.map +1 -1
  70. package/lib/module/theme/screen-margin.js +6 -14
  71. package/lib/module/theme/screen-margin.js.map +1 -1
  72. package/lib/module/theme/screen-padding.js +17 -0
  73. package/lib/module/theme/screen-padding.js.map +1 -0
  74. package/lib/module/theme/selection-store.js +75 -0
  75. package/lib/module/theme/selection-store.js.map +1 -0
  76. package/lib/module/theme/theme.js +81 -36
  77. package/lib/module/theme/theme.js.map +1 -1
  78. package/lib/module/theme/tokens/canonical-color-keys.js +102 -0
  79. package/lib/module/theme/tokens/canonical-color-keys.js.map +1 -0
  80. package/lib/module/theme/tokens/dark.js +72 -88
  81. package/lib/module/theme/tokens/dark.js.map +1 -1
  82. package/lib/module/theme/tokens/light.js +69 -83
  83. package/lib/module/theme/tokens/light.js.map +1 -1
  84. package/lib/module/theme/tokens/primitives.js +14 -14
  85. package/lib/module/theme/tokens/primitives.js.map +1 -1
  86. package/lib/module/theme/tokens/validate.js +238 -0
  87. package/lib/module/theme/tokens/validate.js.map +1 -0
  88. package/lib/module/theme/unistyles-adapter.js +77 -0
  89. package/lib/module/theme/unistyles-adapter.js.map +1 -0
  90. package/lib/module/utils/accent-generator.js +476 -0
  91. package/lib/module/utils/accent-generator.js.map +1 -0
  92. package/lib/module/utils/accent-utils.js +52 -35
  93. package/lib/module/utils/accent-utils.js.map +1 -1
  94. package/lib/module/utils/color-utils.js +224 -7
  95. package/lib/module/utils/color-utils.js.map +1 -1
  96. package/lib/module/utils/deep-merge.js +1 -1
  97. package/lib/module/utils/deep-merge.js.map +1 -1
  98. package/lib/module/utils/resolve-theme-color.js +2 -2
  99. package/lib/typescript/src/components/chip/types.d.ts +1 -1
  100. package/lib/typescript/src/components/empty-state/EmptyState.d.ts +1 -1
  101. package/lib/typescript/src/components/empty-state/EmptyState.d.ts.map +1 -1
  102. package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts +6 -5
  103. package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts.map +1 -1
  104. package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts +11 -5
  105. package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts.map +1 -1
  106. package/lib/typescript/src/components/icon/Icon.d.ts +2 -2
  107. package/lib/typescript/src/components/screen/Screen.d.ts +45 -16
  108. package/lib/typescript/src/components/screen/Screen.d.ts.map +1 -1
  109. package/lib/typescript/src/components/screen/ScreenContext.d.ts +42 -26
  110. package/lib/typescript/src/components/screen/ScreenContext.d.ts.map +1 -1
  111. package/lib/typescript/src/components/screen/ScreenFlatList.d.ts +5 -46
  112. package/lib/typescript/src/components/screen/ScreenFlatList.d.ts.map +1 -1
  113. package/lib/typescript/src/components/screen/ScreenList.d.ts +62 -50
  114. package/lib/typescript/src/components/screen/ScreenList.d.ts.map +1 -1
  115. package/lib/typescript/src/components/screen/ScreenScrollView.d.ts +18 -58
  116. package/lib/typescript/src/components/screen/ScreenScrollView.d.ts.map +1 -1
  117. package/lib/typescript/src/components/screen/hooks/use-screen-ref.d.ts +5 -0
  118. package/lib/typescript/src/components/screen/hooks/use-screen-ref.d.ts.map +1 -0
  119. package/lib/typescript/src/components/screen/hooks/use-screen-scroll-handler.d.ts +53 -0
  120. package/lib/typescript/src/components/screen/hooks/use-screen-scroll-handler.d.ts.map +1 -0
  121. package/lib/typescript/src/components/screen/index.d.ts +2 -2
  122. package/lib/typescript/src/components/screen/index.d.ts.map +1 -1
  123. package/lib/typescript/src/components/screen/parts/ScreenFrame.d.ts +6 -0
  124. package/lib/typescript/src/components/screen/parts/ScreenFrame.d.ts.map +1 -0
  125. package/lib/typescript/src/components/screen/parts/ScreenHeaderAppearance.d.ts +21 -0
  126. package/lib/typescript/src/components/screen/parts/ScreenHeaderAppearance.d.ts.map +1 -0
  127. package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts +6 -8
  128. package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts.map +1 -1
  129. package/lib/typescript/src/components/screen/parts/ScreenSafeArea.android.d.ts +7 -0
  130. package/lib/typescript/src/components/screen/parts/ScreenSafeArea.android.d.ts.map +1 -0
  131. package/lib/typescript/src/components/screen/parts/ScreenSafeArea.d.ts +6 -0
  132. package/lib/typescript/src/components/screen/parts/ScreenSafeArea.d.ts.map +1 -0
  133. package/lib/typescript/src/components/screen/types.d.ts +27 -32
  134. package/lib/typescript/src/components/screen/types.d.ts.map +1 -1
  135. package/lib/typescript/src/components/screen/utils.d.ts +9 -8
  136. package/lib/typescript/src/components/screen/utils.d.ts.map +1 -1
  137. package/lib/typescript/src/components/text/Text.d.ts +1 -1
  138. package/lib/typescript/src/components/text/Text.d.ts.map +1 -1
  139. package/lib/typescript/src/components/toolbar/Toolbar.d.ts +2 -2
  140. package/lib/typescript/src/context/ThemeProvider.d.ts +29 -12
  141. package/lib/typescript/src/context/ThemeProvider.d.ts.map +1 -1
  142. package/lib/typescript/src/context/index.d.ts +1 -1
  143. package/lib/typescript/src/context/index.d.ts.map +1 -1
  144. package/lib/typescript/src/theme/breakpoints.d.ts +1 -1
  145. package/lib/typescript/src/theme/define-config.d.ts +23 -0
  146. package/lib/typescript/src/theme/define-config.d.ts.map +1 -0
  147. package/lib/typescript/src/theme/diagnostics.d.ts +4 -0
  148. package/lib/typescript/src/theme/diagnostics.d.ts.map +1 -0
  149. package/lib/typescript/src/theme/errors.d.ts +10 -0
  150. package/lib/typescript/src/theme/errors.d.ts.map +1 -0
  151. package/lib/typescript/src/theme/index.d.ts +10 -2
  152. package/lib/typescript/src/theme/index.d.ts.map +1 -1
  153. package/lib/typescript/src/theme/legacy-tokens.d.ts +187 -0
  154. package/lib/typescript/src/theme/legacy-tokens.d.ts.map +1 -0
  155. package/lib/typescript/src/theme/registry.d.ts +12 -17
  156. package/lib/typescript/src/theme/registry.d.ts.map +1 -1
  157. package/lib/typescript/src/theme/resolve-config.d.ts +99 -0
  158. package/lib/typescript/src/theme/resolve-config.d.ts.map +1 -0
  159. package/lib/typescript/src/theme/runtime.d.ts +25 -4
  160. package/lib/typescript/src/theme/runtime.d.ts.map +1 -1
  161. package/lib/typescript/src/theme/screen-margin.d.ts +6 -14
  162. package/lib/typescript/src/theme/screen-margin.d.ts.map +1 -1
  163. package/lib/typescript/src/theme/screen-padding.d.ts +15 -0
  164. package/lib/typescript/src/theme/screen-padding.d.ts.map +1 -0
  165. package/lib/typescript/src/theme/selection-store.d.ts +41 -0
  166. package/lib/typescript/src/theme/selection-store.d.ts.map +1 -0
  167. package/lib/typescript/src/theme/theme.d.ts +54 -1
  168. package/lib/typescript/src/theme/theme.d.ts.map +1 -1
  169. package/lib/typescript/src/theme/tokens/canonical-color-keys.d.ts +95 -0
  170. package/lib/typescript/src/theme/tokens/canonical-color-keys.d.ts.map +1 -0
  171. package/lib/typescript/src/theme/tokens/dark.d.ts +56 -63
  172. package/lib/typescript/src/theme/tokens/dark.d.ts.map +1 -1
  173. package/lib/typescript/src/theme/tokens/light.d.ts +56 -71
  174. package/lib/typescript/src/theme/tokens/light.d.ts.map +1 -1
  175. package/lib/typescript/src/theme/tokens/primitives.d.ts +11 -10
  176. package/lib/typescript/src/theme/tokens/primitives.d.ts.map +1 -1
  177. package/lib/typescript/src/theme/tokens/validate.d.ts +34 -0
  178. package/lib/typescript/src/theme/tokens/validate.d.ts.map +1 -0
  179. package/lib/typescript/src/theme/types.d.ts +166 -86
  180. package/lib/typescript/src/theme/types.d.ts.map +1 -1
  181. package/lib/typescript/src/theme/unistyles-adapter.d.ts +33 -0
  182. package/lib/typescript/src/theme/unistyles-adapter.d.ts.map +1 -0
  183. package/lib/typescript/src/utils/accent-generator.d.ts +103 -0
  184. package/lib/typescript/src/utils/accent-generator.d.ts.map +1 -0
  185. package/lib/typescript/src/utils/accent-utils.d.ts +15 -2
  186. package/lib/typescript/src/utils/accent-utils.d.ts.map +1 -1
  187. package/lib/typescript/src/utils/color-utils.d.ts +59 -2
  188. package/lib/typescript/src/utils/color-utils.d.ts.map +1 -1
  189. package/lib/typescript/src/utils/deep-merge.d.ts +1 -0
  190. package/lib/typescript/src/utils/deep-merge.d.ts.map +1 -1
  191. package/lib/typescript/src/utils/resolve-theme-color.d.ts +2 -2
  192. package/package.json +1 -1
  193. package/src/components/avatar/Avatar.tsx +20 -20
  194. package/src/components/button/Button.tsx +11 -11
  195. package/src/components/chip/Chip.tsx +32 -32
  196. package/src/components/chip/types.ts +1 -1
  197. package/src/components/empty-state/EmptyState.tsx +4 -4
  198. package/src/components/header/variants/LargeTitleHeader/index.tsx +31 -30
  199. package/src/components/header/variants/TitleHeader/index.tsx +33 -22
  200. package/src/components/icon/Icon.tsx +4 -4
  201. package/src/components/page-indicator/hooks/use-page-indicator-colors.ts +2 -2
  202. package/src/components/screen/Screen.tsx +89 -43
  203. package/src/components/screen/ScreenContext.tsx +48 -28
  204. package/src/components/screen/ScreenFlatList.tsx +5 -191
  205. package/src/components/screen/ScreenList.tsx +197 -175
  206. package/src/components/screen/ScreenScrollView.tsx +92 -167
  207. package/src/components/screen/hooks/use-screen-ref.ts +24 -0
  208. package/src/components/screen/hooks/use-screen-scroll-handler.ts +31 -0
  209. package/src/components/screen/index.ts +8 -2
  210. package/src/components/screen/parts/ScreenFrame.tsx +49 -0
  211. package/src/components/screen/parts/ScreenHeaderAppearance.tsx +52 -0
  212. package/src/components/screen/parts/ScreenPlaceholder.tsx +16 -16
  213. package/src/components/screen/parts/ScreenSafeArea.android.tsx +22 -0
  214. package/src/components/screen/parts/ScreenSafeArea.tsx +20 -0
  215. package/src/components/screen/types.ts +29 -32
  216. package/src/components/screen/utils.ts +24 -11
  217. package/src/components/spinner/Spinner.tsx +2 -2
  218. package/src/components/text/Text.tsx +2 -2
  219. package/src/components/toast/Toast.tsx +13 -13
  220. package/src/components/toolbar/Toolbar.tsx +4 -4
  221. package/src/context/ThemeProvider.tsx +47 -51
  222. package/src/context/index.ts +1 -1
  223. package/src/theme/breakpoints.ts +1 -1
  224. package/src/theme/define-config.ts +27 -0
  225. package/src/theme/diagnostics.ts +11 -0
  226. package/src/theme/errors.ts +14 -0
  227. package/src/theme/index.ts +40 -1
  228. package/src/theme/legacy-tokens.ts +227 -0
  229. package/src/theme/registry.ts +119 -137
  230. package/src/theme/resolve-config.ts +406 -0
  231. package/src/theme/runtime.ts +52 -14
  232. package/src/theme/screen-margin.ts +6 -25
  233. package/src/theme/screen-padding.ts +26 -0
  234. package/src/theme/selection-store.ts +76 -0
  235. package/src/theme/theme.ts +104 -39
  236. package/src/theme/tokens/canonical-color-keys.ts +107 -0
  237. package/src/theme/tokens/dark.ts +91 -98
  238. package/src/theme/tokens/light.ts +88 -93
  239. package/src/theme/tokens/primitives.ts +14 -14
  240. package/src/theme/tokens/validate.ts +271 -0
  241. package/src/theme/types.ts +184 -95
  242. package/src/theme/unistyles-adapter.ts +75 -0
  243. package/src/utils/accent-generator.ts +530 -0
  244. package/src/utils/accent-utils.ts +66 -39
  245. package/src/utils/color-utils.ts +225 -8
  246. package/src/utils/deep-merge.ts +1 -1
  247. package/src/utils/resolve-theme-color.ts +2 -2
  248. package/lib/module/components/screen/parts/BottomSafeArea.js +0 -62
  249. package/lib/module/components/screen/parts/BottomSafeArea.js.map +0 -1
  250. package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts +0 -30
  251. package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts.map +0 -1
  252. package/src/components/screen/parts/BottomSafeArea.tsx +0 -59
@@ -1,18 +1,31 @@
1
1
  import { type ComponentType, createElement, isValidElement, type ReactElement } from "react";
2
2
 
3
- import type { ScreenMargins } from "./types";
3
+ import type { ScreenContentPadding, ScreenSafeArea, ScreenSafeAreaEdges } from "./types";
4
4
 
5
- export type ListHeaderComponent = ComponentType | ReactElement | null | undefined;
5
+ export type ListComponent = ComponentType | ReactElement | null | undefined;
6
6
 
7
- /** Renders a `ListHeaderComponent`-shaped prop (element, component, or nullish) into an element. */
8
- export function renderListHeader(ListHeaderComponent: ListHeaderComponent): ReactElement | null {
9
- if (!ListHeaderComponent) return null;
10
- if (isValidElement(ListHeaderComponent)) return ListHeaderComponent;
11
- return createElement(ListHeaderComponent);
7
+ /** Resolves a list header, footer, or empty component to an element. */
8
+ export function renderListComponent(ListComponent: ListComponent): ReactElement | null {
9
+ if (!ListComponent) return null;
10
+ if (isValidElement(ListComponent)) return ListComponent;
11
+ return createElement(ListComponent);
12
12
  }
13
13
 
14
- /** Whether `margins` pads the horizontal axis. */
15
- export const hasXMargin = (margins: ScreenMargins): boolean => margins === "all" || margins === "x";
14
+ /** Whether `padding` pads the horizontal axis. */
15
+ export const hasHorizontalPadding = (padding: ScreenContentPadding): boolean =>
16
+ padding === "all" || padding === "x";
16
17
 
17
- /** Whether `margins` pads the vertical axis (the bottom of the content area). */
18
- export const hasYMargin = (margins: ScreenMargins): boolean => margins === "all" || margins === "y";
18
+ /** Whether `padding` pads the vertical axis (top and bottom). */
19
+ export const hasVerticalPadding = (padding: ScreenContentPadding): boolean =>
20
+ padding === "all" || padding === "y";
21
+
22
+ export function resolveSafeAreaEdges(safeArea: ScreenSafeArea): ScreenSafeAreaEdges {
23
+ const includes = (edge: keyof ScreenSafeAreaEdges) =>
24
+ safeArea === "auto" || (safeArea !== "none" && safeArea.includes(edge));
25
+ return {
26
+ top: includes("top"),
27
+ bottom: includes("bottom"),
28
+ left: includes("left"),
29
+ right: includes("right"),
30
+ };
31
+ }
@@ -70,14 +70,14 @@ const SpinnerIcon = memo(function SpinnerIcon({ size, color, gradientId1, gradie
70
70
  * <Spinner size="md" color="content.accent" />
71
71
  * ```
72
72
  */
73
- export function Spinner({ size = "md", color = "text.accent" }: SpinnerProps) {
73
+ export function Spinner({ size = "md", color = "accent.text" }: SpinnerProps) {
74
74
  const { theme } = useUnistyles();
75
75
  const id = useId();
76
76
  const gradientId1 = `spinner-def-1-${id}`;
77
77
  const gradientId2 = `spinner-def-2-${id}`;
78
78
  const resolvedColor =
79
79
  resolveThemeColor(color, theme) ??
80
- (theme.color as unknown as Record<string, string | undefined>)["text.accent"] ??
80
+ (theme.color as unknown as Record<string, string | undefined>)["accent.text"] ??
81
81
  "";
82
82
 
83
83
  const dim = DIMENSION[size];
@@ -34,7 +34,7 @@ function resolveTextVariant(variant: TextVariant): RoleTextVariant {
34
34
  export interface TextProps extends RNTextProps {
35
35
  /** Typographic role. Defaults to `body-md`. */
36
36
  variant?: TextVariant;
37
- /** Semantic color token (e.g. `content.secondary`, `status.danger.solid`). */
37
+ /** Semantic color token (e.g. `text.secondary`, `danger.soft.fg`). */
38
38
  color?: ColorToken;
39
39
  /** Override the variant's default weight. */
40
40
  weight?: FontWeightToken;
@@ -85,7 +85,7 @@ export const Text = forwardRef<ComponentRef<typeof RNText>, TextProps>(function
85
85
  letterSpacing: v.letterSpacing,
86
86
  color:
87
87
  resolveThemeColor(color, theme) ??
88
- (theme.color as unknown as Record<string, string | undefined>)["text.default"] ??
88
+ (theme.color as unknown as Record<string, string | undefined>)["text.primary"] ??
89
89
  "",
90
90
  };
91
91
  // System font: apply numeric weight. Custom font: family encodes the weight.
@@ -62,17 +62,17 @@ function getToastColors(variant: ToastVariant, theme: TerraTheme): ToastColors {
62
62
  const base = {
63
63
  bg: get("surface.default"),
64
64
  border: get("border.subtle"),
65
- description: get("text.muted"),
65
+ description: get("text.secondary"),
66
66
  };
67
67
 
68
68
  switch (variant) {
69
69
  case "accent":
70
70
  return {
71
71
  ...base,
72
- title: get("action.fg.primary.subtle"),
73
- icon: get("text.accent"),
74
- actionBg: get("action.bg.primary"),
75
- actionFg: get("action.fg.primary"),
72
+ title: get("accent.text"),
73
+ icon: get("accent.text"),
74
+ actionBg: get("accent.solid.bg"),
75
+ actionFg: get("accent.solid.fg"),
76
76
  };
77
77
  case "info":
78
78
  case "success":
@@ -80,18 +80,18 @@ function getToastColors(variant: ToastVariant, theme: TerraTheme): ToastColors {
80
80
  case "danger":
81
81
  return {
82
82
  ...base,
83
- title: get(`status.${variant}.bg`),
84
- icon: get(`status.${variant}.bg`),
85
- actionBg: get(`status.${variant}.bg`),
86
- actionFg: get(`status.${variant}.fg`),
83
+ title: get(`${variant}.text`),
84
+ icon: get(`${variant}.text`),
85
+ actionBg: get(`${variant}.solid.bg`),
86
+ actionFg: get(`${variant}.solid.fg`),
87
87
  };
88
88
  case "default":
89
89
  return {
90
90
  ...base,
91
- title: get("text.default"),
92
- icon: get("text.subtle"),
93
- actionBg: get("action.bg.secondary"),
94
- actionFg: get("action.fg.secondary"),
91
+ title: get("text.primary"),
92
+ icon: get("text.tertiary"),
93
+ actionBg: get("neutral.solid.bg"),
94
+ actionFg: get("neutral.solid.fg"),
95
95
  };
96
96
  }
97
97
  }
@@ -39,7 +39,7 @@ interface ToolbarGroupContextValue {
39
39
  /** Non-null default, so a bare `Toolbar.Button` still paints its own circle. */
40
40
  const ToolbarGroupContext = createContext<ToolbarGroupContextValue>({
41
41
  background: "surface.default",
42
- tint: "text.default",
42
+ tint: "text.primary",
43
43
  isGrouped: false,
44
44
  });
45
45
 
@@ -94,7 +94,7 @@ interface ToolbarButtonBaseProps extends Omit<PressableProps, "children" | "styl
94
94
  iconSize?: number;
95
95
  /**
96
96
  * Foreground token for the icon and label. Defaults to the group's `tint`,
97
- * or `text.default` outside a group.
97
+ * or `text.primary` outside a group.
98
98
  */
99
99
  tint?: ColorToken;
100
100
  /**
@@ -220,7 +220,7 @@ export interface ToolbarGroupProps extends ViewProps {
220
220
  background?: ColorToken;
221
221
  /**
222
222
  * Foreground token inherited by every item's icon and label. Defaults to
223
- * `text.default`; a per-item `tint` still wins.
223
+ * `text.primary`; a per-item `tint` still wins.
224
224
  */
225
225
  tint?: ColorToken;
226
226
 
@@ -244,7 +244,7 @@ export interface ToolbarGroupProps extends ViewProps {
244
244
  * ```
245
245
  */
246
246
  export const ToolbarGroup = forwardRef<ComponentRef<typeof View>, ToolbarGroupProps>(function ToolbarGroup(
247
- { background = "surface.default", tint = "text.default", children, style, ...rest },
247
+ { background = "surface.default", tint = "text.primary", children, style, ...rest },
248
248
  ref
249
249
  ) {
250
250
  const context = useMemo<ToolbarGroupContextValue>(
@@ -1,25 +1,24 @@
1
- import { createContext, type PropsWithChildren, useCallback, useContext, useMemo, useState } from "react";
1
+ import { type PropsWithChildren, useSyncExternalStore } from "react";
2
2
 
3
3
  import { SafeAreaProvider } from "react-native-safe-area-context";
4
4
  import { useUnistyles } from "react-native-unistyles";
5
5
 
6
6
  import { bootstrapTerraUI } from "../theme/registry";
7
- import { applyAccent, applyScheme, getCurrentAccent } from "../theme/runtime";
8
- import { type ResolvedScreenMargin, resolveScreenMargin } from "../theme/screen-margin";
9
- import type { Scheme, TerraTheme } from "../theme/types";
7
+ import {
8
+ applyAccent,
9
+ applyScheme,
10
+ getThemeSelection,
11
+ setThemeMode,
12
+ subscribeThemeSelection,
13
+ } from "../theme/runtime";
14
+ import { type ResolvedScreenPadding, resolveScreenPadding } from "../theme/screen-padding";
15
+ import type { Scheme, TerraTheme, ThemeMode } from "../theme/types";
10
16
 
11
17
  // Ensure the default theme is registered on first import. An app that calls
12
18
  // `configureTerraUI` itself still wins, whichever module the bundler evaluates
13
19
  // first — see the note on `bootstrapTerraUI`.
14
20
  bootstrapTerraUI();
15
21
 
16
- interface ThemeContextValue {
17
- accent: string | undefined;
18
- setAccent: (name: string) => void;
19
- }
20
-
21
- const ThemeContext = createContext<ThemeContextValue | null>(null);
22
-
23
22
  /**
24
23
  * Root provider for Terra UI. Wrap your app once near the root.
25
24
  *
@@ -28,70 +27,67 @@ const ThemeContext = createContext<ThemeContextValue | null>(null);
28
27
  * <RootNavigator />
29
28
  * </TerraUIProvider>
30
29
  */
31
- export const TerraUIProvider = ({ children }: PropsWithChildren) => {
32
- const [accent, setAccentState] = useState<string | undefined>(() => getCurrentAccent());
33
-
34
- const setAccent = useCallback((name: string) => {
35
- applyAccent(name);
36
- setAccentState(name);
37
- }, []);
38
-
39
- const value = useMemo<ThemeContextValue>(() => ({ accent, setAccent }), [accent, setAccent]);
40
-
41
- return (
42
- <ThemeContext.Provider value={value}>
43
- <SafeAreaProvider>{children}</SafeAreaProvider>
44
- </ThemeContext.Provider>
45
- );
46
- };
30
+ export const TerraUIProvider = ({ children }: PropsWithChildren) => (
31
+ <SafeAreaProvider>{children}</SafeAreaProvider>
32
+ );
47
33
 
48
34
  export interface UseThemeResult {
49
35
  /** The active resolved theme. */
50
36
  theme: TerraTheme;
51
- /** Active color scheme. */
37
+ /**
38
+ * The currently resolved color scheme — what Unistyles is rendering right
39
+ * now. Under `mode: "system"` this follows the OS appearance.
40
+ */
52
41
  scheme: Scheme;
53
- /** Switch scheme (disables system-adaptive theming). */
42
+ /** Pin a scheme (stops following the system). Same as `setMode(scheme)`. */
54
43
  setScheme: (scheme: Scheme) => void;
55
- /** Active accent name, if any. */
44
+ /** Applied accent name, or `undefined` for the base themes. */
56
45
  accent: string | undefined;
57
- /** Switch to a registered accent. */
58
- setAccent: (name: string) => void;
46
+ /**
47
+ * Apply a registered accent; `null` restores the base themes. An unknown
48
+ * name warns in development and leaves the accent unchanged.
49
+ */
50
+ setAccent: (name: string | null) => void;
51
+ /**
52
+ * How the active scheme is chosen: `system` follows the OS appearance,
53
+ * `light` / `dark` pin a scheme. A system appearance change updates
54
+ * `scheme`, never `mode`.
55
+ */
56
+ mode: ThemeMode;
57
+ /** Change the theme mode; `setMode("system")` returns to the OS preference. */
58
+ setMode: (mode: ThemeMode) => void;
59
59
  }
60
60
 
61
61
  /**
62
- * Access the active theme and switch scheme / accent / radius.
63
- * Must be used under {@link TerraUIProvider}.
62
+ * Access the active theme and switch scheme / mode / accent. Selection state
63
+ * comes from the runtime store, so changes made imperatively (`applyAccent`,
64
+ * `setThemeMode`) and through this hook are observed by every consumer.
64
65
  */
65
66
  export function useTheme(): UseThemeResult {
66
67
  const { theme, rt } = useUnistyles();
67
- const ctx = useContext(ThemeContext);
68
-
69
- const setScheme = useCallback((scheme: Scheme) => {
70
- applyScheme(scheme);
71
- }, []);
68
+ const selection = useSyncExternalStore(subscribeThemeSelection, getThemeSelection, getThemeSelection);
72
69
 
73
70
  return {
74
71
  theme,
75
72
  scheme: (rt.themeName ?? "light") as Scheme,
76
- setScheme,
77
- accent: ctx?.accent,
78
- setAccent: ctx?.setAccent ?? noopSetAccent,
73
+ setScheme: applyScheme,
74
+ accent: selection.accent,
75
+ setAccent: applyAccent,
76
+ mode: selection.mode,
77
+ setMode: setThemeMode,
79
78
  };
80
79
  }
81
80
 
82
81
  /**
83
- * Resolves `theme.layout.screen.margin` for the current runtime breakpoint.
82
+ * Resolves `theme.layout.screen.padding` for the current runtime breakpoint.
84
83
  * For use outside a `StyleSheet.create` factory (e.g. plain-JS layout maths);
85
- * inside one, call `resolveScreenMargin(theme, rt.breakpoint)` directly so
84
+ * inside one, call `resolveScreenPadding(theme, rt.breakpoint)` directly so
86
85
  * the value stays reactive without a React re-render.
87
86
  */
88
- export function useScreenMargin(): ResolvedScreenMargin {
87
+ export function useScreenPadding(): ResolvedScreenPadding {
89
88
  const { theme, rt } = useUnistyles();
90
- return resolveScreenMargin(theme, rt.breakpoint);
89
+ return resolveScreenPadding(theme, rt.breakpoint);
91
90
  }
92
91
 
93
- const noopSetAccent = (_name: string): void => {
94
- if (__DEV__) {
95
- console.warn("[react-native-terra-ui] setAccent called outside <TerraUIProvider>.");
96
- }
97
- };
92
+ /** @deprecated Use useScreenPadding. */
93
+ export const useScreenMargin = useScreenPadding;
@@ -1,2 +1,2 @@
1
1
  export type { UseThemeResult } from "./ThemeProvider";
2
- export { TerraUIProvider, useScreenMargin, useTheme } from "./ThemeProvider";
2
+ export { TerraUIProvider, useScreenMargin, useScreenPadding, useTheme } from "./ThemeProvider";
@@ -21,7 +21,7 @@ declare module "react-native-unistyles" {
21
21
  }
22
22
 
23
23
  /**
24
- * Resolves a per-breakpoint record (e.g. `theme.layout.screen.margin.x`) to
24
+ * Resolves a per-breakpoint record (e.g. `theme.layout.screen.padding.x`) to
25
25
  * the value for the current runtime breakpoint (`rt.breakpoint`). A
26
26
  * `StyleSheet.create` factory re-runs on breakpoint change, so calling this
27
27
  * inside one keeps the resolved value reactive.
@@ -0,0 +1,27 @@
1
+ import type { TerraConfig } from "./types";
2
+
3
+ /** The literal accent names declared in `T.accents`, or `never` when none are declared. */
4
+ type DeclaredAccentNames<T> = T extends { accents: infer A }
5
+ ? A extends Record<string, unknown>
6
+ ? keyof A & string
7
+ : never
8
+ : never;
9
+
10
+ /**
11
+ * Identity helper for authoring a {@link TerraConfig} with contextual typing.
12
+ * Has no side effects. With literal `accents`, `defaultAccent` must name one of
13
+ * them; a dynamic `Record<string, Accent>` keeps `defaultAccent` as `string`
14
+ * and defers to runtime validation.
15
+ *
16
+ * @example
17
+ * const config = defineTerraConfig({
18
+ * accents: { indigo: { light: "#4f46e5", dark: "#818cf8" } },
19
+ * defaultAccent: "indigo",
20
+ * });
21
+ * configureTerraUI(config);
22
+ */
23
+ export function defineTerraConfig<const T extends TerraConfig>(
24
+ config: T & { defaultAccent?: DeclaredAccentNames<T> }
25
+ ): T {
26
+ return config;
27
+ }
@@ -0,0 +1,11 @@
1
+ import type { ContrastDiagnostic } from "#utils/accent-generator";
2
+
3
+ /** Reports each distinct contrast diagnostic once per resolution. */
4
+ export function warnContrastDiagnostics(diagnostics: readonly ContrastDiagnostic[]): void {
5
+ if (typeof __DEV__ === "undefined" || !__DEV__ || diagnostics.length === 0) return;
6
+
7
+ const messages = new Set(diagnostics.map(({ message }) => message));
8
+ for (const message of messages) {
9
+ console.warn(`[react-native-terra-ui] ${message}`);
10
+ }
11
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Thrown for invalid pure configuration input — before any registry, selection,
3
+ * or native state is touched. `path` points at the offending config entry, e.g.
4
+ * `accents.indigo.dark.seed` or `defaultAccent`.
5
+ */
6
+ export class TerraConfigError extends Error {
7
+ readonly path: string;
8
+
9
+ constructor(path: string, message: string) {
10
+ super(`[react-native-terra-ui] Invalid config at "${path}": ${message}`);
11
+ this.name = "TerraConfigError";
12
+ this.path = path;
13
+ }
14
+ }
@@ -6,6 +6,19 @@
6
6
  // resolved `defaultLightTheme` / `defaultDarkTheme` are the public surface.
7
7
 
8
8
  // ─── Utilities (color resolution + accent/merge/typography helpers) ──────────
9
+ export type {
10
+ AccentGenerationContext,
11
+ AccentGenerationResult,
12
+ AccentSurfaceRole,
13
+ ContrastDiagnostic,
14
+ ContrastDiagnosticKind,
15
+ GeneratedAccentRole,
16
+ } from "#utils/accent-generator";
17
+ export {
18
+ checkAccentContrast,
19
+ generateAccentColors,
20
+ isSupportedAccentSeed,
21
+ } from "#utils/accent-generator";
9
22
  export { normalizeAccent } from "#utils/accent-utils";
10
23
  export { readableOn, shade, withAlpha } from "#utils/color-utils";
11
24
  export type { DeepPartial } from "#utils/deep-merge";
@@ -16,6 +29,8 @@ export { FONT_WEIGHT_VALUE, SYSTEM_FONT } from "#utils/typography";
16
29
  export type { AppBreakpoints, BreakpointName } from "./breakpoints";
17
30
  export { breakpoints, resolveBreakpointValue } from "./breakpoints";
18
31
  // ─── Config + runtime ───────────────────────────────────────────────────────
32
+ export { defineTerraConfig } from "./define-config";
33
+ export { TerraConfigError } from "./errors";
19
34
  export {
20
35
  configureTerraUI,
21
36
  getAccentNames,
@@ -27,38 +42,59 @@ export {
27
42
  getSurfaceElevation,
28
43
  resolveTheme,
29
44
  } from "./registry";
30
- export { applyAccent, applyScheme, getCurrentAccent } from "./runtime";
45
+ export { createTerraThemes } from "./resolve-config";
46
+ export type { ThemeSelection } from "./runtime";
47
+ export {
48
+ applyAccent,
49
+ applyScheme,
50
+ getCurrentAccent,
51
+ getThemeMode,
52
+ getThemeSelection,
53
+ setThemeMode,
54
+ } from "./runtime";
31
55
  export type { ResolvedScreenMargin } from "./screen-margin";
32
56
  export { resolveScreenMargin } from "./screen-margin";
57
+ export type { ResolvedScreenPadding } from "./screen-padding";
58
+ export { resolveScreenPadding } from "./screen-padding";
33
59
  // ─── Default themes ───────────────────────────────────────────────────────────
34
60
  export { defaultDarkTheme, defaultLightTheme } from "./theme";
35
61
  // ─── Types ────────────────────────────────────────────────────────────────────
36
62
  export type {
37
63
  Accent,
64
+ AccentColorToken,
38
65
  AccentOverride,
66
+ AccentSchemeInput,
67
+ AccentSeed,
39
68
  AccentShorthand,
40
69
  ActionColorToken,
41
70
  BackgroundColorToken,
42
71
  BorderColorToken,
43
72
  ButtonDefaults,
73
+ CanonicalThemeColor,
44
74
  ColorToken,
45
75
  ComponentDefaults,
46
76
  ContentColorToken,
77
+ CreateTerraThemesOptions,
47
78
  DefaultRadiusToken,
48
79
  ElevationKey,
49
80
  ElevationScale,
50
81
  ElevationStyle,
51
82
  FontWeightToken,
52
83
  LayoutTokens,
84
+ NeutralColorToken,
53
85
  OpacityTokens,
54
86
  RadiusComponent,
55
87
  RadiusKey,
56
88
  Scheme,
89
+ ScreenPaddingTokens,
57
90
  SpacingKey,
58
91
  StatusColorToken,
59
92
  SurfaceColorToken,
60
93
  SurfaceDefaults,
94
+ TerraColorRegistry,
95
+ TerraColorRegistryCollision,
61
96
  TerraConfig,
97
+ TerraCustomColorName,
62
98
  TerraIconComponent,
63
99
  TerraIconName,
64
100
  TerraIconProps,
@@ -67,11 +103,14 @@ export type {
67
103
  TerraImageProps,
68
104
  TerraSemanticIconName,
69
105
  TerraTheme,
106
+ TerraThemeInput,
70
107
  TerraThemeOverride,
71
108
  TextColorToken,
72
109
  TextVariant,
73
110
  ThemeColor,
111
+ ThemeMode,
74
112
  ThemeRadiusOverride,
113
+ ThemeSpacingOverride,
75
114
  TypeStyle,
76
115
  Typography,
77
116
  } from "./types";