@ankhorage/surface 0.1.4 → 0.1.6

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 (292) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +23 -184
  3. package/dist/components/badge/Badge.js.map +1 -1
  4. package/dist/components/badge/index.js.map +1 -1
  5. package/dist/components/badge/types.js.map +1 -1
  6. package/dist/components/button/Button.js.map +1 -1
  7. package/dist/components/button/index.js.map +1 -1
  8. package/dist/components/button/types.js.map +1 -1
  9. package/dist/components/card/Card.js.map +1 -1
  10. package/dist/components/card/index.js.map +1 -1
  11. package/dist/components/card/types.js.map +1 -1
  12. package/dist/components/checkbox/Checkbox.js.map +1 -1
  13. package/dist/components/checkbox/index.js.map +1 -1
  14. package/dist/components/checkbox/types.js.map +1 -1
  15. package/dist/components/drawer/Drawer.js.map +1 -1
  16. package/dist/components/drawer/index.js.map +1 -1
  17. package/dist/components/drawer/types.js.map +1 -1
  18. package/dist/components/field/Field.js.map +1 -1
  19. package/dist/components/field/index.js.map +1 -1
  20. package/dist/components/field/types.js.map +1 -1
  21. package/dist/components/helper-text/HelperText.js.map +1 -1
  22. package/dist/components/helper-text/index.js.map +1 -1
  23. package/dist/components/helper-text/types.js.map +1 -1
  24. package/dist/components/icon-button/IconButton.js.map +1 -1
  25. package/dist/components/icon-button/index.js.map +1 -1
  26. package/dist/components/icon-button/types.js.map +1 -1
  27. package/dist/components/label/Label.js.map +1 -1
  28. package/dist/components/label/index.js.map +1 -1
  29. package/dist/components/label/types.js.map +1 -1
  30. package/dist/components/list-item/ListItem.js.map +1 -1
  31. package/dist/components/list-item/index.js.map +1 -1
  32. package/dist/components/list-item/types.js.map +1 -1
  33. package/dist/components/menu/Menu.js.map +1 -1
  34. package/dist/components/menu/index.js.map +1 -1
  35. package/dist/components/menu/navigation.js.map +1 -1
  36. package/dist/components/menu/types.js.map +1 -1
  37. package/dist/components/modal/Modal.js.map +1 -1
  38. package/dist/components/modal/index.js.map +1 -1
  39. package/dist/components/modal/types.js.map +1 -1
  40. package/dist/components/radio/Radio.js.map +1 -1
  41. package/dist/components/radio/index.js.map +1 -1
  42. package/dist/components/radio/types.js.map +1 -1
  43. package/dist/components/switch/Switch.js.map +1 -1
  44. package/dist/components/switch/index.js.map +1 -1
  45. package/dist/components/switch/types.js.map +1 -1
  46. package/dist/components/tabs/Tab.js.map +1 -1
  47. package/dist/components/tabs/TabList.js.map +1 -1
  48. package/dist/components/tabs/TabPanel.js.map +1 -1
  49. package/dist/components/tabs/Tabs.js.map +1 -1
  50. package/dist/components/tabs/a11y.js.map +1 -1
  51. package/dist/components/tabs/context.js.map +1 -1
  52. package/dist/components/tabs/index.js.map +1 -1
  53. package/dist/components/tabs/navigation.js.map +1 -1
  54. package/dist/components/tabs/types.js.map +1 -1
  55. package/dist/components/text-input/TextInput.js.map +1 -1
  56. package/dist/components/text-input/index.js.map +1 -1
  57. package/dist/components/text-input/types.js.map +1 -1
  58. package/dist/components/textarea/Textarea.js.map +1 -1
  59. package/dist/components/textarea/index.js.map +1 -1
  60. package/dist/components/textarea/types.js.map +1 -1
  61. package/dist/components/toast/Toast.js.map +1 -1
  62. package/dist/components/toast/ToastProvider.js.map +1 -1
  63. package/dist/components/toast/index.js.map +1 -1
  64. package/dist/components/toast/types.js.map +1 -1
  65. package/dist/components/tooltip/Tooltip.js.map +1 -1
  66. package/dist/components/tooltip/index.js.map +1 -1
  67. package/dist/components/tooltip/types.js.map +1 -1
  68. package/dist/context/FontContext.js.map +1 -1
  69. package/dist/context/TranslationContext.js.map +1 -1
  70. package/dist/core/responsive/ResponsiveProvider.js.map +1 -1
  71. package/dist/core/responsive/breakpoints.js.map +1 -1
  72. package/dist/core/responsive/getBreakpointFromWidth.js.map +1 -1
  73. package/dist/core/responsive/index.js.map +1 -1
  74. package/dist/core/responsive/resolve.js.map +1 -1
  75. package/dist/core/responsive/types.js.map +1 -1
  76. package/dist/core/responsive/useBreakpoint.js.map +1 -1
  77. package/dist/examples/DocsExamples.js.map +1 -1
  78. package/dist/index.js.map +1 -1
  79. package/dist/internal/focus/FocusScope.js.map +1 -1
  80. package/dist/internal/focus/useFocusManager.js.map +1 -1
  81. package/dist/internal/overlay/OverlayProvider.js.map +1 -1
  82. package/dist/internal/overlay/Portal.js.map +1 -1
  83. package/dist/internal/overlay/useOverlayStack.js.map +1 -1
  84. package/dist/internal/resolvers/index.js.map +1 -1
  85. package/dist/internal/resolvers/resolveControlSize.js.map +1 -1
  86. package/dist/internal/resolvers/resolveFieldPresentation.js.map +1 -1
  87. package/dist/internal/resolvers/resolveFieldState.js.map +1 -1
  88. package/dist/internal/resolvers/resolveFocusRingStyles.js.map +1 -1
  89. package/dist/internal/resolvers/resolveIconSize.js.map +1 -1
  90. package/dist/internal/resolvers/resolveIndicatorSize.js.map +1 -1
  91. package/dist/internal/resolvers/resolveInteractiveColors.js.map +1 -1
  92. package/dist/internal/resolvers/resolveInteractiveState.js.map +1 -1
  93. package/dist/internal/resolvers/resolveOverlayAnimation.js.map +1 -1
  94. package/dist/internal/resolvers/resolveOverlayZIndex.js.map +1 -1
  95. package/dist/internal/resolvers/resolveSelectionControlBehavior.js.map +1 -1
  96. package/dist/internal/resolvers/resolveSelectionControlColors.js.map +1 -1
  97. package/dist/internal/resolvers/resolveTextColor.js.map +1 -1
  98. package/dist/internal/resolvers/resolveTextStyles.js.map +1 -1
  99. package/dist/internal/resolvers/resolveTone.js.map +1 -1
  100. package/dist/internal/useControllableState.js.map +1 -1
  101. package/dist/layout/Box.js.map +1 -1
  102. package/dist/layout/Center.js.map +1 -1
  103. package/dist/layout/Container.js.map +1 -1
  104. package/dist/layout/Divider.js.map +1 -1
  105. package/dist/layout/Grid.js.map +1 -1
  106. package/dist/layout/Inline.js.map +1 -1
  107. package/dist/layout/Show.js.map +1 -1
  108. package/dist/layout/Spacer.js.map +1 -1
  109. package/dist/layout/Stack.js.map +1 -1
  110. package/dist/layout/Surface.js.map +1 -1
  111. package/dist/layout/Template.js.map +1 -1
  112. package/dist/layout/helpers.js.map +1 -1
  113. package/dist/layout/index.js.map +1 -1
  114. package/dist/primitives/button-base/ButtonBase.js.map +1 -1
  115. package/dist/primitives/button-base/index.js.map +1 -1
  116. package/dist/primitives/button-base/types.js.map +1 -1
  117. package/dist/primitives/heading/Heading.js.map +1 -1
  118. package/dist/primitives/heading/index.js.map +1 -1
  119. package/dist/primitives/heading/resolveHeadingStyle.js.map +1 -1
  120. package/dist/primitives/heading/types.js.map +1 -1
  121. package/dist/primitives/icon/Icon.js.map +1 -1
  122. package/dist/primitives/icon/index.js.map +1 -1
  123. package/dist/primitives/icon/resolveExpoIconComponent.js.map +1 -1
  124. package/dist/primitives/text/Text.js.map +1 -1
  125. package/dist/primitives/text/index.js.map +1 -1
  126. package/dist/primitives/text/types.js.map +1 -1
  127. package/dist/theme/ThemeContext.js.map +1 -1
  128. package/dist/theme/colorEngine.js.map +1 -1
  129. package/dist/theme/createTheme.js.map +1 -1
  130. package/dist/theme/index.js.map +1 -1
  131. package/dist/theme/resolveToken.js.map +1 -1
  132. package/dist/theme/types.js.map +1 -1
  133. package/dist/utils/deepEqual.js.map +1 -1
  134. package/dist/utils/deepMerge.js.map +1 -1
  135. package/package.json +4 -1
  136. package/src/components/badge/Badge.tsx +47 -0
  137. package/src/components/badge/index.ts +2 -0
  138. package/src/components/badge/types.ts +13 -0
  139. package/src/components/button/Button.tsx +104 -0
  140. package/src/components/button/index.ts +2 -0
  141. package/src/components/button/types.ts +26 -0
  142. package/src/components/card/Card.tsx +81 -0
  143. package/src/components/card/index.ts +2 -0
  144. package/src/components/card/types.ts +11 -0
  145. package/src/components/checkbox/Checkbox.tsx +111 -0
  146. package/src/components/checkbox/index.ts +2 -0
  147. package/src/components/checkbox/types.ts +19 -0
  148. package/src/components/drawer/Drawer.tsx +92 -0
  149. package/src/components/drawer/index.ts +2 -0
  150. package/src/components/drawer/types.ts +10 -0
  151. package/src/components/field/Field.tsx +43 -0
  152. package/src/components/field/index.ts +2 -0
  153. package/src/components/field/types.ts +13 -0
  154. package/src/components/helper-text/HelperText.tsx +12 -0
  155. package/src/components/helper-text/index.ts +2 -0
  156. package/src/components/helper-text/types.ts +9 -0
  157. package/src/components/icon-button/IconButton.tsx +60 -0
  158. package/src/components/icon-button/index.ts +2 -0
  159. package/src/components/icon-button/types.ts +19 -0
  160. package/src/components/label/Label.tsx +17 -0
  161. package/src/components/label/index.ts +2 -0
  162. package/src/components/label/types.ts +10 -0
  163. package/src/components/list-item/ListItem.tsx +72 -0
  164. package/src/components/list-item/index.ts +2 -0
  165. package/src/components/list-item/types.ts +11 -0
  166. package/src/components/menu/Menu.tsx +180 -0
  167. package/src/components/menu/index.ts +2 -0
  168. package/src/components/menu/navigation.test.ts +21 -0
  169. package/src/components/menu/navigation.ts +34 -0
  170. package/src/components/menu/types.ts +16 -0
  171. package/src/components/modal/Modal.tsx +87 -0
  172. package/src/components/modal/index.ts +2 -0
  173. package/src/components/modal/types.ts +9 -0
  174. package/src/components/radio/Radio.tsx +116 -0
  175. package/src/components/radio/index.ts +2 -0
  176. package/src/components/radio/types.ts +19 -0
  177. package/src/components/switch/Switch.tsx +116 -0
  178. package/src/components/switch/index.ts +2 -0
  179. package/src/components/switch/types.ts +19 -0
  180. package/src/components/tabs/Tab.tsx +82 -0
  181. package/src/components/tabs/TabList.tsx +51 -0
  182. package/src/components/tabs/TabPanel.tsx +29 -0
  183. package/src/components/tabs/Tabs.tsx +67 -0
  184. package/src/components/tabs/a11y.test.ts +15 -0
  185. package/src/components/tabs/a11y.ts +15 -0
  186. package/src/components/tabs/context.tsx +31 -0
  187. package/src/components/tabs/index.ts +5 -0
  188. package/src/components/tabs/navigation.test.ts +21 -0
  189. package/src/components/tabs/navigation.ts +32 -0
  190. package/src/components/tabs/types.ts +27 -0
  191. package/src/components/text-input/TextInput.tsx +116 -0
  192. package/src/components/text-input/index.ts +2 -0
  193. package/src/components/text-input/types.ts +32 -0
  194. package/src/components/textarea/Textarea.tsx +15 -0
  195. package/src/components/textarea/index.ts +2 -0
  196. package/src/components/textarea/types.ts +5 -0
  197. package/src/components/toast/Toast.tsx +54 -0
  198. package/src/components/toast/ToastProvider.tsx +114 -0
  199. package/src/components/toast/index.ts +3 -0
  200. package/src/components/toast/types.ts +16 -0
  201. package/src/components/tooltip/Tooltip.tsx +109 -0
  202. package/src/components/tooltip/index.ts +2 -0
  203. package/src/components/tooltip/types.ts +9 -0
  204. package/src/context/FontContext.tsx +59 -0
  205. package/src/context/TranslationContext.tsx +54 -0
  206. package/src/core/responsive/ResponsiveProvider.tsx +31 -0
  207. package/src/core/responsive/breakpoints.ts +9 -0
  208. package/src/core/responsive/getBreakpointFromWidth.test.ts +15 -0
  209. package/src/core/responsive/getBreakpointFromWidth.ts +10 -0
  210. package/src/core/responsive/index.ts +6 -0
  211. package/src/core/responsive/resolve.test.ts +25 -0
  212. package/src/core/responsive/resolve.ts +24 -0
  213. package/src/core/responsive/types.ts +10 -0
  214. package/src/core/responsive/useBreakpoint.ts +9 -0
  215. package/src/examples/DocsExamples.tsx +116 -0
  216. package/src/index.test.ts +64 -0
  217. package/src/index.ts +55 -0
  218. package/src/internal/focus/FocusScope.tsx +66 -0
  219. package/src/internal/focus/useFocusManager.test.ts +44 -0
  220. package/src/internal/focus/useFocusManager.ts +142 -0
  221. package/src/internal/overlay/OverlayProvider.tsx +74 -0
  222. package/src/internal/overlay/Portal.tsx +38 -0
  223. package/src/internal/overlay/useOverlayStack.test.ts +31 -0
  224. package/src/internal/overlay/useOverlayStack.ts +61 -0
  225. package/src/internal/resolvers/index.ts +15 -0
  226. package/src/internal/resolvers/resolveControlSize.test.ts +25 -0
  227. package/src/internal/resolvers/resolveControlSize.ts +45 -0
  228. package/src/internal/resolvers/resolveFieldPresentation.test.ts +31 -0
  229. package/src/internal/resolvers/resolveFieldPresentation.ts +30 -0
  230. package/src/internal/resolvers/resolveFieldState.test.ts +22 -0
  231. package/src/internal/resolvers/resolveFieldState.ts +36 -0
  232. package/src/internal/resolvers/resolveFocusRingStyles.ts +14 -0
  233. package/src/internal/resolvers/resolveIconSize.ts +6 -0
  234. package/src/internal/resolvers/resolveIndicatorSize.test.ts +19 -0
  235. package/src/internal/resolvers/resolveIndicatorSize.ts +47 -0
  236. package/src/internal/resolvers/resolveInteractiveColors.test.ts +57 -0
  237. package/src/internal/resolvers/resolveInteractiveColors.ts +134 -0
  238. package/src/internal/resolvers/resolveInteractiveState.test.ts +14 -0
  239. package/src/internal/resolvers/resolveInteractiveState.ts +15 -0
  240. package/src/internal/resolvers/resolveOverlayAnimation.test.ts +15 -0
  241. package/src/internal/resolvers/resolveOverlayAnimation.ts +24 -0
  242. package/src/internal/resolvers/resolveOverlayZIndex.test.ts +15 -0
  243. package/src/internal/resolvers/resolveOverlayZIndex.ts +13 -0
  244. package/src/internal/resolvers/resolveSelectionControlBehavior.test.ts +52 -0
  245. package/src/internal/resolvers/resolveSelectionControlBehavior.ts +23 -0
  246. package/src/internal/resolvers/resolveSelectionControlColors.test.ts +44 -0
  247. package/src/internal/resolvers/resolveSelectionControlColors.ts +81 -0
  248. package/src/internal/resolvers/resolveTextColor.test.ts +23 -0
  249. package/src/internal/resolvers/resolveTextColor.ts +40 -0
  250. package/src/internal/resolvers/resolveTextStyles.test.ts +27 -0
  251. package/src/internal/resolvers/resolveTextStyles.ts +95 -0
  252. package/src/internal/resolvers/resolveTone.ts +19 -0
  253. package/src/internal/useControllableState.ts +28 -0
  254. package/src/layout/Box.tsx +79 -0
  255. package/src/layout/Center.tsx +22 -0
  256. package/src/layout/Container.tsx +43 -0
  257. package/src/layout/Divider.tsx +26 -0
  258. package/src/layout/Grid.tsx +83 -0
  259. package/src/layout/Inline.tsx +9 -0
  260. package/src/layout/Show.tsx +15 -0
  261. package/src/layout/Spacer.tsx +22 -0
  262. package/src/layout/Stack.tsx +67 -0
  263. package/src/layout/Surface.tsx +70 -0
  264. package/src/layout/Template.tsx +85 -0
  265. package/src/layout/helpers.test.ts +71 -0
  266. package/src/layout/helpers.ts +208 -0
  267. package/src/layout/index.ts +22 -0
  268. package/src/primitives/button-base/ButtonBase.tsx +81 -0
  269. package/src/primitives/button-base/index.ts +2 -0
  270. package/src/primitives/button-base/types.ts +16 -0
  271. package/src/primitives/heading/Heading.tsx +60 -0
  272. package/src/primitives/heading/index.ts +2 -0
  273. package/src/primitives/heading/resolveHeadingStyle.test.ts +31 -0
  274. package/src/primitives/heading/resolveHeadingStyle.ts +17 -0
  275. package/src/primitives/heading/types.ts +13 -0
  276. package/src/primitives/icon/Icon.tsx +40 -0
  277. package/src/primitives/icon/index.ts +2 -0
  278. package/src/primitives/icon/resolveExpoIconComponent.test.ts +29 -0
  279. package/src/primitives/icon/resolveExpoIconComponent.ts +20 -0
  280. package/src/primitives/text/Text.tsx +66 -0
  281. package/src/primitives/text/index.ts +2 -0
  282. package/src/primitives/text/types.ts +18 -0
  283. package/src/theme/ThemeContext.tsx +95 -0
  284. package/src/theme/colorEngine.test.ts +114 -0
  285. package/src/theme/colorEngine.ts +480 -0
  286. package/src/theme/createTheme.ts +121 -0
  287. package/src/theme/index.ts +5 -0
  288. package/src/theme/resolveToken.ts +32 -0
  289. package/src/theme/types.ts +188 -0
  290. package/src/utils/deepEqual.ts +34 -0
  291. package/src/utils/deepMerge.test.ts +117 -0
  292. package/src/utils/deepMerge.ts +29 -0
@@ -0,0 +1,208 @@
1
+ import type { StyleProp, ViewStyle } from 'react-native';
2
+
3
+ import { resolveResponsive } from '../core/responsive/resolve';
4
+ import type { Breakpoint, Responsive } from '../core/responsive/types';
5
+ import { resolveToken } from '../theme/resolveToken';
6
+ import type { AnkhTheme } from '../theme/types';
7
+
8
+ type SpaceToken = keyof AnkhTheme['spacing'];
9
+ type RadiusToken = keyof AnkhTheme['radii'];
10
+ type ColorToken = keyof AnkhTheme['colors'];
11
+
12
+ export type SpaceValue = number | SpaceToken;
13
+ export type RadiusValue = number | RadiusToken;
14
+ export type ColorValue = string | ColorToken;
15
+
16
+ export interface BoxStyleProps {
17
+ p?: Responsive<SpaceValue>;
18
+ px?: Responsive<SpaceValue>;
19
+ py?: Responsive<SpaceValue>;
20
+ pt?: Responsive<SpaceValue>;
21
+ pb?: Responsive<SpaceValue>;
22
+ pl?: Responsive<SpaceValue>;
23
+ pr?: Responsive<SpaceValue>;
24
+ m?: Responsive<SpaceValue>;
25
+ mx?: Responsive<SpaceValue>;
26
+ my?: Responsive<SpaceValue>;
27
+ mt?: Responsive<SpaceValue>;
28
+ mb?: Responsive<SpaceValue>;
29
+ ml?: Responsive<SpaceValue>;
30
+ mr?: Responsive<SpaceValue>;
31
+ bg?: Responsive<ColorValue>;
32
+ radius?: Responsive<RadiusValue>;
33
+ borderWidth?: Responsive<number>;
34
+ borderColor?: Responsive<ColorValue>;
35
+ width?: Responsive<number | string>;
36
+ height?: Responsive<number | string>;
37
+ minWidth?: Responsive<number | string>;
38
+ maxWidth?: Responsive<number | string>;
39
+ minHeight?: Responsive<number | string>;
40
+ maxHeight?: Responsive<number | string>;
41
+ flex?: Responsive<number>;
42
+ flexGrow?: Responsive<number>;
43
+ flexShrink?: Responsive<number>;
44
+ flexBasis?: Responsive<number | string>;
45
+ alignSelf?: Responsive<ViewStyle['alignSelf']>;
46
+ position?: Responsive<ViewStyle['position']>;
47
+ top?: Responsive<number>;
48
+ bottom?: Responsive<number>;
49
+ left?: Responsive<number>;
50
+ right?: Responsive<number>;
51
+ overflow?: Responsive<ViewStyle['overflow']>;
52
+ zIndex?: Responsive<number>;
53
+ opacity?: Responsive<number>;
54
+ style?: StyleProp<ViewStyle>;
55
+ }
56
+
57
+ export function resolveSpacing(
58
+ theme: AnkhTheme,
59
+ value: SpaceValue | undefined,
60
+ ): number | undefined {
61
+ if (value === undefined) return undefined;
62
+ if (typeof value === 'number') return value;
63
+ return theme.spacing[value as SpaceToken];
64
+ }
65
+
66
+ export function resolveRadius(
67
+ theme: AnkhTheme,
68
+ value: RadiusValue | undefined,
69
+ ): number | undefined {
70
+ if (value === undefined) return undefined;
71
+ if (typeof value === 'number') return value;
72
+ return theme.radii[value];
73
+ }
74
+
75
+ export function resolveColor(theme: AnkhTheme, value: ColorValue | undefined): string | undefined {
76
+ if (value === undefined) return undefined;
77
+ if (Object.prototype.hasOwnProperty.call(theme.colors, value)) {
78
+ return theme.colors[value as ColorToken];
79
+ }
80
+ return value as string;
81
+ }
82
+
83
+ export function resolveDimension(
84
+ theme: AnkhTheme,
85
+ value: number | string | undefined,
86
+ ): number | string | undefined {
87
+ if (value === undefined || typeof value === 'number') return value;
88
+ return resolveToken(theme.spacing, value);
89
+ }
90
+
91
+ function assignIfDefined<T, K extends keyof T>(target: T, key: K, value: T[K] | undefined) {
92
+ if (value !== undefined) target[key] = value;
93
+ }
94
+
95
+ export function resolveBoxStyles(
96
+ theme: AnkhTheme,
97
+ breakpoint: Breakpoint,
98
+ props: BoxStyleProps,
99
+ ): ViewStyle {
100
+ const styles: ViewStyle = {
101
+ elevation: 0,
102
+ };
103
+
104
+ const p = resolveSpacing(theme, resolveResponsive(props.p, breakpoint));
105
+ const px = resolveSpacing(theme, resolveResponsive(props.px, breakpoint));
106
+ const py = resolveSpacing(theme, resolveResponsive(props.py, breakpoint));
107
+ const pt = resolveSpacing(theme, resolveResponsive(props.pt, breakpoint));
108
+ const pb = resolveSpacing(theme, resolveResponsive(props.pb, breakpoint));
109
+ const pl = resolveSpacing(theme, resolveResponsive(props.pl, breakpoint));
110
+ const pr = resolveSpacing(theme, resolveResponsive(props.pr, breakpoint));
111
+ const m = resolveSpacing(theme, resolveResponsive(props.m, breakpoint));
112
+ const mx = resolveSpacing(theme, resolveResponsive(props.mx, breakpoint));
113
+ const my = resolveSpacing(theme, resolveResponsive(props.my, breakpoint));
114
+ const mt = resolveSpacing(theme, resolveResponsive(props.mt, breakpoint));
115
+ const mb = resolveSpacing(theme, resolveResponsive(props.mb, breakpoint));
116
+ const ml = resolveSpacing(theme, resolveResponsive(props.ml, breakpoint));
117
+ const mr = resolveSpacing(theme, resolveResponsive(props.mr, breakpoint));
118
+
119
+ assignIfDefined(styles, 'padding', p as ViewStyle['padding']);
120
+ assignIfDefined(styles, 'paddingHorizontal', px as ViewStyle['paddingHorizontal']);
121
+ assignIfDefined(styles, 'paddingVertical', py as ViewStyle['paddingVertical']);
122
+ assignIfDefined(styles, 'paddingTop', pt as ViewStyle['paddingTop']);
123
+ assignIfDefined(styles, 'paddingBottom', pb as ViewStyle['paddingBottom']);
124
+ assignIfDefined(styles, 'paddingLeft', pl as ViewStyle['paddingLeft']);
125
+ assignIfDefined(styles, 'paddingRight', pr as ViewStyle['paddingRight']);
126
+ assignIfDefined(styles, 'margin', m as ViewStyle['margin']);
127
+ assignIfDefined(styles, 'marginHorizontal', mx as ViewStyle['marginHorizontal']);
128
+ assignIfDefined(styles, 'marginVertical', my as ViewStyle['marginVertical']);
129
+ assignIfDefined(styles, 'marginTop', mt as ViewStyle['marginTop']);
130
+ assignIfDefined(styles, 'marginBottom', mb as ViewStyle['marginBottom']);
131
+ assignIfDefined(styles, 'marginLeft', ml as ViewStyle['marginLeft']);
132
+ assignIfDefined(styles, 'marginRight', mr as ViewStyle['marginRight']);
133
+
134
+ assignIfDefined(
135
+ styles,
136
+ 'backgroundColor',
137
+ resolveColor(theme, resolveResponsive(props.bg, breakpoint)),
138
+ );
139
+ assignIfDefined(
140
+ styles,
141
+ 'borderRadius',
142
+ resolveRadius(theme, resolveResponsive(props.radius, breakpoint)) as ViewStyle['borderRadius'],
143
+ );
144
+ assignIfDefined(styles, 'borderWidth', resolveResponsive(props.borderWidth, breakpoint));
145
+ assignIfDefined(
146
+ styles,
147
+ 'borderColor',
148
+ resolveColor(theme, resolveResponsive(props.borderColor, breakpoint)),
149
+ );
150
+
151
+ assignIfDefined(
152
+ styles,
153
+ 'width',
154
+ resolveDimension(theme, resolveResponsive(props.width, breakpoint)) as ViewStyle['width'],
155
+ );
156
+ assignIfDefined(
157
+ styles,
158
+ 'height',
159
+ resolveDimension(theme, resolveResponsive(props.height, breakpoint)) as ViewStyle['height'],
160
+ );
161
+ assignIfDefined(
162
+ styles,
163
+ 'minWidth',
164
+ resolveDimension(theme, resolveResponsive(props.minWidth, breakpoint)) as ViewStyle['minWidth'],
165
+ );
166
+ assignIfDefined(
167
+ styles,
168
+ 'maxWidth',
169
+ resolveDimension(theme, resolveResponsive(props.maxWidth, breakpoint)) as ViewStyle['maxWidth'],
170
+ );
171
+ assignIfDefined(
172
+ styles,
173
+ 'minHeight',
174
+ resolveDimension(
175
+ theme,
176
+ resolveResponsive(props.minHeight, breakpoint),
177
+ ) as ViewStyle['minHeight'],
178
+ );
179
+ assignIfDefined(
180
+ styles,
181
+ 'maxHeight',
182
+ resolveDimension(
183
+ theme,
184
+ resolveResponsive(props.maxHeight, breakpoint),
185
+ ) as ViewStyle['maxHeight'],
186
+ );
187
+
188
+ assignIfDefined(styles, 'flex', resolveResponsive(props.flex, breakpoint));
189
+ assignIfDefined(styles, 'flexGrow', resolveResponsive(props.flexGrow, breakpoint));
190
+ assignIfDefined(styles, 'flexShrink', resolveResponsive(props.flexShrink, breakpoint));
191
+ assignIfDefined(
192
+ styles,
193
+ 'flexBasis',
194
+ resolveResponsive(props.flexBasis, breakpoint) as ViewStyle['flexBasis'],
195
+ );
196
+ assignIfDefined(styles, 'alignSelf', resolveResponsive(props.alignSelf, breakpoint));
197
+
198
+ assignIfDefined(styles, 'position', resolveResponsive(props.position, breakpoint));
199
+ assignIfDefined(styles, 'top', resolveResponsive(props.top, breakpoint));
200
+ assignIfDefined(styles, 'bottom', resolveResponsive(props.bottom, breakpoint));
201
+ assignIfDefined(styles, 'left', resolveResponsive(props.left, breakpoint));
202
+ assignIfDefined(styles, 'right', resolveResponsive(props.right, breakpoint));
203
+ assignIfDefined(styles, 'overflow', resolveResponsive(props.overflow, breakpoint));
204
+ assignIfDefined(styles, 'zIndex', resolveResponsive(props.zIndex, breakpoint));
205
+ assignIfDefined(styles, 'opacity', resolveResponsive(props.opacity, breakpoint));
206
+
207
+ return styles;
208
+ }
@@ -0,0 +1,22 @@
1
+ export type { BoxProps } from './Box';
2
+ export { Box } from './Box';
3
+ export type { CenterProps } from './Center';
4
+ export { Center } from './Center';
5
+ export type { ContainerProps } from './Container';
6
+ export { Container } from './Container';
7
+ export type { DividerProps } from './Divider';
8
+ export { Divider } from './Divider';
9
+ export type { GridProps } from './Grid';
10
+ export { Grid } from './Grid';
11
+ export type { InlineProps } from './Inline';
12
+ export { Inline } from './Inline';
13
+ export type { ShowProps } from './Show';
14
+ export { Show } from './Show';
15
+ export type { SpacerProps } from './Spacer';
16
+ export { Spacer } from './Spacer';
17
+ export type { StackProps } from './Stack';
18
+ export { Stack } from './Stack';
19
+ export type { SurfaceProps, SurfaceVariant } from './Surface';
20
+ export { Surface } from './Surface';
21
+ export type { TemplateProps } from './Template';
22
+ export { Template } from './Template';
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+ import { Platform, Pressable, type PressableStateCallbackType } from 'react-native';
3
+
4
+ import { useResponsiveRuntime } from '../../core/responsive';
5
+ import {
6
+ type InteractionState,
7
+ resolveFocusRingStyles,
8
+ resolveInteractiveState,
9
+ } from '../../internal/resolvers';
10
+ import { resolveBoxStyles } from '../../layout/helpers';
11
+ import { useTheme } from '../../theme/ThemeContext';
12
+ import type { ButtonBaseProps } from './types';
13
+
14
+ function getInteractionState(
15
+ pressableState: PressableStateCallbackType,
16
+ hovered: boolean,
17
+ focused: boolean,
18
+ disabled: boolean,
19
+ ): InteractionState {
20
+ return resolveInteractiveState({
21
+ pressed: pressableState.pressed,
22
+ hovered,
23
+ focused,
24
+ disabled,
25
+ });
26
+ }
27
+
28
+ export function ButtonBase({
29
+ children,
30
+ disabled = false,
31
+ onPress,
32
+ onLongPress,
33
+ accessibilityLabel,
34
+ accessibilityRole = 'button',
35
+ accessibilityState,
36
+ style,
37
+ testID,
38
+ ...props
39
+ }: ButtonBaseProps) {
40
+ const { theme } = useTheme();
41
+ const { breakpoint } = useResponsiveRuntime();
42
+ const [hovered, setHovered] = React.useState(false);
43
+ const [focused, setFocused] = React.useState(false);
44
+ const isWeb = Platform.OS === 'web';
45
+ const resolvedBoxStyles = resolveBoxStyles(theme, breakpoint, props);
46
+
47
+ return (
48
+ <Pressable
49
+ accessibilityLabel={accessibilityLabel}
50
+ accessibilityRole={accessibilityRole}
51
+ accessibilityState={{ ...accessibilityState, disabled }}
52
+ disabled={disabled}
53
+ onBlur={() => setFocused(false)}
54
+ onFocus={() => {
55
+ if (isWeb) {
56
+ setFocused(true);
57
+ }
58
+ }}
59
+ onHoverIn={isWeb ? () => setHovered(true) : undefined}
60
+ onHoverOut={isWeb ? () => setHovered(false) : undefined}
61
+ onLongPress={onLongPress}
62
+ onPress={onPress}
63
+ style={(pressableState) => {
64
+ const state = getInteractionState(pressableState, hovered, focused, disabled);
65
+
66
+ return [
67
+ resolvedBoxStyles,
68
+ resolveFocusRingStyles(theme.semantics.border.focus, state.focused, isWeb),
69
+ style,
70
+ ];
71
+ }}
72
+ testID={testID}
73
+ >
74
+ {(pressableState) => {
75
+ const state = getInteractionState(pressableState, hovered, focused, disabled);
76
+
77
+ return typeof children === 'function' ? children(state) : children;
78
+ }}
79
+ </Pressable>
80
+ );
81
+ }
@@ -0,0 +1,2 @@
1
+ export { ButtonBase } from './ButtonBase';
2
+ export type { ButtonBaseProps } from './types';
@@ -0,0 +1,16 @@
1
+ import type React from 'react';
2
+ import type { AccessibilityRole, AccessibilityState, GestureResponderEvent } from 'react-native';
3
+
4
+ import type { InteractionState } from '../../internal/resolvers/resolveInteractiveState';
5
+ import type { BoxProps } from '../../layout';
6
+
7
+ export interface ButtonBaseProps extends Omit<BoxProps, 'children' | 'pointerEvents'> {
8
+ children?: React.ReactNode | ((state: InteractionState) => React.ReactNode);
9
+ disabled?: boolean;
10
+ onPress?: ((event: GestureResponderEvent) => void) | undefined;
11
+ onLongPress?: ((event: GestureResponderEvent) => void) | undefined;
12
+ accessibilityLabel?: string;
13
+ accessibilityRole?: AccessibilityRole;
14
+ accessibilityState?: AccessibilityState;
15
+ testID?: string;
16
+ }
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import { Text } from 'react-native';
3
+
4
+ import { useTranslationContext } from '../../context/TranslationContext';
5
+ import { useTheme } from '../../theme/ThemeContext';
6
+ import { resolveHeadingTextStyle } from './resolveHeadingStyle';
7
+ import type { HeadingProps } from './types';
8
+
9
+ function resolveHeadingContent({
10
+ text,
11
+ children,
12
+ i18nKey,
13
+ translate,
14
+ }: {
15
+ text: HeadingProps['text'];
16
+ children: HeadingProps['children'];
17
+ i18nKey: HeadingProps['i18nKey'];
18
+ translate: (key: string) => string;
19
+ }) {
20
+ let content = text ?? children;
21
+ if (!i18nKey) {
22
+ return content;
23
+ }
24
+
25
+ try {
26
+ const translated = translate(i18nKey);
27
+ if (translated && translated !== i18nKey) {
28
+ content = translated;
29
+ }
30
+ } catch (error) {
31
+ console.warn('[Heading] Translation error:', error);
32
+ }
33
+
34
+ return content;
35
+ }
36
+
37
+ export const Heading: React.FC<HeadingProps> = ({
38
+ text,
39
+ children,
40
+ level = 2,
41
+ align,
42
+ numberOfLines,
43
+ i18nKey,
44
+ testID,
45
+ }) => {
46
+ const { theme } = useTheme();
47
+ const { t } = useTranslationContext();
48
+ const content = resolveHeadingContent({ text, children, i18nKey, translate: t });
49
+
50
+ return (
51
+ <Text
52
+ accessibilityRole="header"
53
+ testID={testID}
54
+ numberOfLines={numberOfLines}
55
+ style={resolveHeadingTextStyle(theme, level, align)}
56
+ >
57
+ {content}
58
+ </Text>
59
+ );
60
+ };
@@ -0,0 +1,2 @@
1
+ export { Heading } from './Heading';
2
+ export type { HeadingLevel, HeadingProps } from './types';
@@ -0,0 +1,31 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+
3
+ import { createTheme } from '../../theme/createTheme';
4
+ import { resolveHeadingTextStyle } from './resolveHeadingStyle';
5
+
6
+ describe('resolveHeadingTextStyle', () => {
7
+ it('maps heading levels to theme-driven typography tokens', () => {
8
+ const theme = createTheme();
9
+ const style = resolveHeadingTextStyle(theme, 4, 'center');
10
+
11
+ expect(style.fontSize).toBe(18);
12
+ expect(style.lineHeight).toBe(24);
13
+ expect(style.fontWeight).toBe('600');
14
+ expect(style.color).toBe(theme.colors.text);
15
+ expect(style.textAlign).toBe('center');
16
+ });
17
+
18
+ it('uses the themed font family when one is configured', () => {
19
+ const theme = createTheme(undefined, 'light', 'space grotesk');
20
+ const style = resolveHeadingTextStyle(theme, 1);
21
+
22
+ expect(style.fontFamily).toBe('SpaceGrotesk_700Regular');
23
+ });
24
+
25
+ it('shares the default content tone with the text primitive', () => {
26
+ const theme = createTheme();
27
+ const style = resolveHeadingTextStyle(theme, 2);
28
+
29
+ expect(style.color).toBe(theme.semantics.content.default);
30
+ });
31
+ });
@@ -0,0 +1,17 @@
1
+ import type { TextStyle } from 'react-native';
2
+
3
+ import { resolveTextColor } from '../../internal/resolvers/resolveTextColor';
4
+ import { resolveTextStyles } from '../../internal/resolvers/resolveTextStyles';
5
+ import type { AnkhTheme } from '../../theme/types';
6
+ import type { HeadingLevel, HeadingProps } from './types';
7
+
8
+ export function resolveHeadingTextStyle(
9
+ theme: AnkhTheme,
10
+ level: HeadingLevel,
11
+ align?: HeadingProps['align'],
12
+ ): TextStyle {
13
+ return {
14
+ ...resolveTextStyles(theme, { align, level }),
15
+ color: resolveTextColor(theme, 'default'),
16
+ };
17
+ }
@@ -0,0 +1,13 @@
1
+ import type React from 'react';
2
+
3
+ export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
4
+
5
+ export interface HeadingProps {
6
+ text?: string;
7
+ children?: React.ReactNode;
8
+ level?: HeadingLevel;
9
+ align?: 'auto' | 'left' | 'right' | 'center' | 'justify';
10
+ numberOfLines?: number;
11
+ i18nKey?: string;
12
+ testID?: string;
13
+ }
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+ import { type StyleProp, type TextStyle } from 'react-native';
3
+
4
+ import { resolveToken } from '../../theme/resolveToken';
5
+ import { useTheme } from '../../theme/ThemeContext';
6
+ import type { AnkhTheme } from '../../theme/types';
7
+ import { resolveExpoIconComponent } from './resolveExpoIconComponent';
8
+
9
+ export type IconProvider = string;
10
+
11
+ export interface IconProps {
12
+ name: string;
13
+ provider?: IconProvider;
14
+ size?: keyof AnkhTheme['spacing'] | number;
15
+ color?: keyof AnkhTheme['colors'] | string;
16
+ style?: StyleProp<TextStyle>;
17
+ testID?: string;
18
+ }
19
+
20
+ export function Icon({
21
+ name,
22
+ provider = 'Ionicons',
23
+ size = 'm',
24
+ color = 'text',
25
+ style,
26
+ testID,
27
+ }: IconProps) {
28
+ const { theme } = useTheme();
29
+ const IconComponent = resolveExpoIconComponent(provider);
30
+ const resolvedSize = typeof size === 'number' ? size : resolveToken(theme.spacing, size);
31
+ const resolvedColor = resolveToken(theme.colors, color);
32
+
33
+ return React.createElement(IconComponent, {
34
+ color: resolvedColor,
35
+ name,
36
+ size: resolvedSize,
37
+ style,
38
+ testID,
39
+ });
40
+ }
@@ -0,0 +1,2 @@
1
+ export type { IconProps, IconProvider } from './Icon';
2
+ export { Icon } from './Icon';
@@ -0,0 +1,29 @@
1
+ import { describe, expect, mock, test } from 'bun:test';
2
+
3
+ const Ionicons = () => null;
4
+ const MaterialIcons = () => null;
5
+
6
+ describe('resolveExpoIconComponent', () => {
7
+ test('returns the requested Expo icon family when it exists', async () => {
8
+ await mock.module('@expo/vector-icons', () => ({
9
+ Ionicons,
10
+ MaterialIcons,
11
+ }));
12
+
13
+ const { resolveExpoIconComponent } = await import('./resolveExpoIconComponent');
14
+
15
+ expect(resolveExpoIconComponent('Ionicons')).toBe(Ionicons);
16
+ expect(resolveExpoIconComponent('MaterialIcons')).toBe(MaterialIcons);
17
+ });
18
+
19
+ test('falls back to Ionicons when the provider is unknown', async () => {
20
+ await mock.module('@expo/vector-icons', () => ({
21
+ Ionicons,
22
+ MaterialIcons,
23
+ }));
24
+
25
+ const { resolveExpoIconComponent } = await import('./resolveExpoIconComponent');
26
+
27
+ expect(resolveExpoIconComponent('MissingIconFamily')).toBe(Ionicons);
28
+ });
29
+ });
@@ -0,0 +1,20 @@
1
+ import * as ExpoIcons from '@expo/vector-icons';
2
+ import type React from 'react';
3
+ import { type StyleProp, type TextStyle } from 'react-native';
4
+
5
+ export type ExpoIconComponent = React.ElementType<{
6
+ color?: string;
7
+ name?: string;
8
+ size?: number;
9
+ style?: StyleProp<TextStyle>;
10
+ testID?: string;
11
+ }>;
12
+
13
+ export function resolveExpoIconComponent(provider: string): ExpoIconComponent {
14
+ const candidate = (ExpoIcons as Record<string, unknown>)[provider];
15
+ if (typeof candidate === 'function') {
16
+ return candidate as ExpoIconComponent;
17
+ }
18
+
19
+ return ExpoIcons.Ionicons as ExpoIconComponent;
20
+ }
@@ -0,0 +1,66 @@
1
+ import React from 'react';
2
+ import { Text as ReactNativeText } from 'react-native';
3
+
4
+ import { useTranslationContext } from '../../context/TranslationContext';
5
+ import { resolveTextColor, resolveTextStyles } from '../../internal/resolvers';
6
+ import { useTheme } from '../../theme/ThemeContext';
7
+ import type { TextProps } from './types';
8
+
9
+ function resolveTextContent(
10
+ children: TextProps['children'],
11
+ i18nKey: TextProps['i18nKey'],
12
+ translate: (key: string) => string,
13
+ ) {
14
+ if (children !== undefined) {
15
+ return children;
16
+ }
17
+
18
+ if (!i18nKey) {
19
+ return null;
20
+ }
21
+
22
+ try {
23
+ const translated = translate(i18nKey);
24
+ return translated && translated !== i18nKey ? translated : i18nKey;
25
+ } catch (error) {
26
+ console.warn('[Text] Translation error:', error);
27
+ return i18nKey;
28
+ }
29
+ }
30
+
31
+ export function Text({
32
+ children,
33
+ i18nKey,
34
+ variant = 'body',
35
+ tone = 'default',
36
+ color,
37
+ align,
38
+ weight,
39
+ italic = false,
40
+ numberOfLines,
41
+ testID,
42
+ }: TextProps) {
43
+ const { theme } = useTheme();
44
+ const { t } = useTranslationContext();
45
+ const content = resolveTextContent(children, i18nKey, t);
46
+
47
+ return (
48
+ <ReactNativeText
49
+ numberOfLines={numberOfLines}
50
+ testID={testID}
51
+ style={[
52
+ resolveTextStyles(theme, {
53
+ align,
54
+ italic,
55
+ variant,
56
+ weight,
57
+ }),
58
+ {
59
+ color: resolveTextColor(theme, tone, color),
60
+ },
61
+ ]}
62
+ >
63
+ {content}
64
+ </ReactNativeText>
65
+ );
66
+ }
@@ -0,0 +1,2 @@
1
+ export { Text } from './Text';
2
+ export type { TextProps } from './types';
@@ -0,0 +1,18 @@
1
+ import type React from 'react';
2
+ import type { TextStyle } from 'react-native';
3
+
4
+ import type { TextColorValue, TextTone } from '../../internal/resolvers/resolveTextColor';
5
+ import type { TextVariant, TextWeight } from '../../internal/resolvers/resolveTextStyles';
6
+
7
+ export interface TextProps {
8
+ children?: React.ReactNode;
9
+ i18nKey?: string;
10
+ variant?: TextVariant;
11
+ tone?: TextTone;
12
+ color?: TextColorValue;
13
+ align?: TextStyle['textAlign'];
14
+ weight?: TextWeight;
15
+ italic?: boolean;
16
+ numberOfLines?: number;
17
+ testID?: string;
18
+ }