@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,79 @@
1
+ import React from 'react';
2
+ import {
3
+ Platform,
4
+ type StyleProp,
5
+ View,
6
+ type ViewProps as ReactNativeViewProps,
7
+ type ViewStyle,
8
+ } from 'react-native';
9
+
10
+ import { useResponsiveRuntime } from '../core/responsive';
11
+ import { useTheme } from '../theme/ThemeContext';
12
+ import { type BoxStyleProps, resolveBoxStyles } from './helpers';
13
+
14
+ export interface BoxProps extends BoxStyleProps {
15
+ accessibilityLabel?: ReactNativeViewProps['accessibilityLabel'];
16
+ accessibilityRole?: ReactNativeViewProps['accessibilityRole'];
17
+ accessibilityState?: ReactNativeViewProps['accessibilityState'];
18
+ accessible?: ReactNativeViewProps['accessible'];
19
+ children?: React.ReactNode;
20
+ pointerEvents?: ReactNativeViewProps['pointerEvents'];
21
+ testID?: string;
22
+ }
23
+
24
+ function resolvePointerEventsStyle(
25
+ pointerEvents: ReactNativeViewProps['pointerEvents'] | undefined,
26
+ ): StyleProp<ViewStyle> | null {
27
+ if (Platform.OS !== 'web' || pointerEvents === undefined) return null;
28
+
29
+ if (pointerEvents === 'auto' || pointerEvents === 'none') {
30
+ return { pointerEvents };
31
+ }
32
+
33
+ return null;
34
+ }
35
+
36
+ function resolveViewPointerEvents(
37
+ pointerEvents: ReactNativeViewProps['pointerEvents'] | undefined,
38
+ ): ReactNativeViewProps['pointerEvents'] | undefined {
39
+ if (pointerEvents === undefined) return undefined;
40
+ if (Platform.OS !== 'web') return pointerEvents;
41
+
42
+ if (pointerEvents === 'box-none' || pointerEvents === 'box-only') {
43
+ return pointerEvents;
44
+ }
45
+
46
+ return undefined;
47
+ }
48
+
49
+ export function Box({
50
+ accessible,
51
+ accessibilityLabel,
52
+ accessibilityRole,
53
+ accessibilityState,
54
+ children,
55
+ pointerEvents,
56
+ style,
57
+ testID,
58
+ ...props
59
+ }: BoxProps) {
60
+ const { theme } = useTheme();
61
+ const { breakpoint } = useResponsiveRuntime();
62
+ const resolved = resolveBoxStyles(theme, breakpoint, props);
63
+ const pointerEventsStyle = resolvePointerEventsStyle(pointerEvents);
64
+ const viewPointerEvents = resolveViewPointerEvents(pointerEvents);
65
+
66
+ return (
67
+ <View
68
+ accessible={accessible}
69
+ accessibilityLabel={accessibilityLabel}
70
+ accessibilityRole={accessibilityRole}
71
+ accessibilityState={accessibilityState}
72
+ pointerEvents={viewPointerEvents}
73
+ testID={testID}
74
+ style={[resolved, pointerEventsStyle, style]}
75
+ >
76
+ {children}
77
+ </View>
78
+ );
79
+ }
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+
3
+ import { Box, type BoxProps } from './Box';
4
+
5
+ export interface CenterProps extends BoxProps {
6
+ axis?: 'horizontal' | 'vertical' | 'both';
7
+ }
8
+
9
+ export function Center({ axis = 'both', style, ...props }: CenterProps) {
10
+ return (
11
+ <Box
12
+ {...props}
13
+ style={[
14
+ {
15
+ alignItems: axis === 'both' || axis === 'horizontal' ? 'center' : undefined,
16
+ justifyContent: axis === 'both' || axis === 'vertical' ? 'center' : undefined,
17
+ },
18
+ style,
19
+ ]}
20
+ />
21
+ );
22
+ }
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import { View } from 'react-native';
3
+
4
+ import { resolveResponsive, type Responsive, useResponsiveRuntime } from '../core/responsive';
5
+ import { useTheme } from '../theme/ThemeContext';
6
+ import type { AnkhTheme } from '../theme/types';
7
+ import { Box, type BoxProps } from './Box';
8
+ import { resolveSpacing } from './helpers';
9
+
10
+ export interface ContainerProps extends Omit<BoxProps, 'children'> {
11
+ children?: React.ReactNode;
12
+ maxWidth?: Responsive<number>;
13
+ px?: Responsive<number | keyof AnkhTheme['spacing']>;
14
+ }
15
+
16
+ export function Container({
17
+ children,
18
+ maxWidth = { base: Number.MAX_SAFE_INTEGER, lg: 1120 },
19
+ px = { base: 16, md: 24, lg: 32 },
20
+ ...props
21
+ }: ContainerProps) {
22
+ const { theme } = useTheme();
23
+ const { breakpoint } = useResponsiveRuntime();
24
+
25
+ const activeMaxWidth = resolveResponsive(maxWidth, breakpoint);
26
+ const activePx = resolveSpacing(theme, resolveResponsive(px, breakpoint));
27
+
28
+ return (
29
+ <Box {...props} width="100%" style={[{ width: '100%' }, props.style]}>
30
+ <View
31
+ style={{
32
+ width: '100%',
33
+ maxWidth: activeMaxWidth,
34
+ alignSelf: 'center',
35
+ paddingLeft: activePx,
36
+ paddingRight: activePx,
37
+ }}
38
+ >
39
+ {children}
40
+ </View>
41
+ </Box>
42
+ );
43
+ }
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+
3
+ import { Box, type BoxProps } from './Box';
4
+ import type { ColorValue } from './helpers';
5
+
6
+ export interface DividerProps extends Omit<BoxProps, 'bg' | 'height' | 'width'> {
7
+ orientation?: 'horizontal' | 'vertical';
8
+ color?: ColorValue;
9
+ thickness?: number;
10
+ }
11
+
12
+ export function Divider({
13
+ orientation = 'horizontal',
14
+ color = 'border',
15
+ thickness = 1,
16
+ ...props
17
+ }: DividerProps) {
18
+ return (
19
+ <Box
20
+ {...props}
21
+ bg={color}
22
+ height={orientation === 'horizontal' ? thickness : '100%'}
23
+ width={orientation === 'vertical' ? thickness : '100%'}
24
+ />
25
+ );
26
+ }
@@ -0,0 +1,83 @@
1
+ import React from 'react';
2
+ import { View, type ViewStyle } from 'react-native';
3
+
4
+ import { resolveResponsive, type Responsive, useResponsiveRuntime } from '../core/responsive';
5
+ import { useTheme } from '../theme/ThemeContext';
6
+ import { Box, type BoxProps } from './Box';
7
+ import { resolveSpacing, type SpaceValue } from './helpers';
8
+
9
+ export interface GridProps extends Omit<BoxProps, 'children'> {
10
+ children?: React.ReactNode;
11
+ cols: Responsive<number>;
12
+ gap?: Responsive<SpaceValue>;
13
+ rowGap?: Responsive<SpaceValue>;
14
+ colGap?: Responsive<SpaceValue>;
15
+ minItemWidth?: Responsive<number>;
16
+ }
17
+
18
+ export function Grid({
19
+ children,
20
+ cols,
21
+ gap = 0,
22
+ rowGap,
23
+ colGap,
24
+ minItemWidth,
25
+ ...props
26
+ }: GridProps) {
27
+ const { theme } = useTheme();
28
+ const { breakpoint } = useResponsiveRuntime();
29
+
30
+ const activeCols = Math.max(1, Math.floor(resolveResponsive(cols, breakpoint) ?? 1));
31
+ const defaultGap = resolveResponsive(gap, breakpoint) ?? 0;
32
+ const activeRowGap = resolveResponsive(rowGap, breakpoint) ?? defaultGap;
33
+ const activeColGap = resolveResponsive(colGap, breakpoint) ?? defaultGap;
34
+ const rowSpacing = Number(resolveSpacing(theme, activeRowGap) ?? 0);
35
+ const colSpacing = Number(resolveSpacing(theme, activeColGap) ?? 0);
36
+ const activeMinItemWidth = resolveResponsive(minItemWidth, breakpoint);
37
+
38
+ const basisPercent = `${100 / activeCols}%`;
39
+ const nodes = React.Children.toArray(children);
40
+
41
+ return (
42
+ <Box {...props}>
43
+ <View
44
+ style={{
45
+ flexDirection: 'row',
46
+ flexWrap: 'wrap',
47
+ marginTop: -rowSpacing / 2,
48
+ marginLeft: -colSpacing / 2,
49
+ marginRight: -colSpacing / 2,
50
+ }}
51
+ >
52
+ {nodes.map((node, index) => {
53
+ const itemStyle: ViewStyle =
54
+ activeMinItemWidth !== undefined
55
+ ? {
56
+ minWidth: activeMinItemWidth,
57
+ flexBasis: activeMinItemWidth,
58
+ flexGrow: 1,
59
+ }
60
+ : {
61
+ width: basisPercent as ViewStyle['width'],
62
+ flexBasis: basisPercent as ViewStyle['flexBasis'],
63
+ };
64
+
65
+ return (
66
+ <View
67
+ key={String(index)}
68
+ style={{
69
+ paddingTop: rowSpacing / 2,
70
+ paddingBottom: rowSpacing / 2,
71
+ paddingLeft: colSpacing / 2,
72
+ paddingRight: colSpacing / 2,
73
+ ...itemStyle,
74
+ }}
75
+ >
76
+ {node}
77
+ </View>
78
+ );
79
+ })}
80
+ </View>
81
+ </Box>
82
+ );
83
+ }
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+
3
+ import { Stack, type StackProps } from './Stack';
4
+
5
+ export type InlineProps = Omit<StackProps, 'direction'>;
6
+
7
+ export function Inline({ wrap = 'wrap', align = 'center', ...props }: InlineProps) {
8
+ return <Stack {...props} align={align} direction="row" wrap={wrap} />;
9
+ }
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+
3
+ import { resolveResponsive, type Responsive, useResponsiveRuntime } from '../core/responsive';
4
+
5
+ export interface ShowProps {
6
+ when: Responsive<boolean>;
7
+ children: React.ReactNode;
8
+ fallback?: React.ReactNode;
9
+ }
10
+
11
+ export function Show({ when, children, fallback = null }: ShowProps) {
12
+ const { breakpoint } = useResponsiveRuntime();
13
+ const visible = resolveResponsive(when, breakpoint) ?? false;
14
+ return <>{visible ? children : fallback}</>;
15
+ }
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+
3
+ import { Box } from './Box';
4
+ import type { SpaceValue } from './helpers';
5
+
6
+ export interface SpacerProps {
7
+ size?: SpaceValue;
8
+ axis?: 'horizontal' | 'vertical' | 'both';
9
+ testID?: string;
10
+ }
11
+
12
+ export function Spacer({ size = 'm', axis = 'vertical', testID }: SpacerProps) {
13
+ if (axis === 'horizontal') {
14
+ return <Box testID={testID} width={size} />;
15
+ }
16
+
17
+ if (axis === 'both') {
18
+ return <Box testID={testID} height={size} width={size} />;
19
+ }
20
+
21
+ return <Box testID={testID} height={size} />;
22
+ }
@@ -0,0 +1,67 @@
1
+ import React from 'react';
2
+ import { View } from 'react-native';
3
+
4
+ import { resolveResponsive, type Responsive, useResponsiveRuntime } from '../core/responsive';
5
+ import { useTheme } from '../theme/ThemeContext';
6
+ import { Box, type BoxProps } from './Box';
7
+ import { resolveSpacing, type SpaceValue } from './helpers';
8
+
9
+ export interface StackProps extends Omit<BoxProps, 'children'> {
10
+ children?: React.ReactNode;
11
+ direction?: Responsive<'row' | 'column'>;
12
+ gap?: Responsive<SpaceValue>;
13
+ align?: Responsive<'flex-start' | 'center' | 'flex-end' | 'stretch' | 'baseline'>;
14
+ justify?: Responsive<
15
+ 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around' | 'space-evenly'
16
+ >;
17
+ wrap?: Responsive<'nowrap' | 'wrap'>;
18
+ }
19
+
20
+ export function Stack({
21
+ children,
22
+ direction = 'column',
23
+ gap = 0,
24
+ align,
25
+ justify,
26
+ wrap,
27
+ ...props
28
+ }: StackProps) {
29
+ const { theme } = useTheme();
30
+ const { breakpoint } = useResponsiveRuntime();
31
+ const activeDirection = resolveResponsive(direction, breakpoint) ?? 'column';
32
+ const activeGap = resolveSpacing(theme, resolveResponsive(gap, breakpoint)) ?? 0;
33
+ const activeAlign = resolveResponsive(align, breakpoint);
34
+ const activeJustify = resolveResponsive(justify, breakpoint);
35
+ const activeWrap = resolveResponsive(wrap, breakpoint);
36
+
37
+ const nodes = React.Children.toArray(children);
38
+
39
+ return (
40
+ <Box
41
+ {...props}
42
+ style={[
43
+ {
44
+ flexDirection: activeDirection,
45
+ alignItems: activeAlign,
46
+ justifyContent: activeJustify,
47
+ flexWrap: activeWrap,
48
+ },
49
+ props.style,
50
+ ]}
51
+ >
52
+ {nodes.map((node, index) => {
53
+ const spacing =
54
+ index === 0
55
+ ? undefined
56
+ : activeDirection === 'row'
57
+ ? { marginLeft: activeGap }
58
+ : { marginTop: activeGap };
59
+ return (
60
+ <View key={String(index)} style={spacing}>
61
+ {node}
62
+ </View>
63
+ );
64
+ })}
65
+ </Box>
66
+ );
67
+ }
@@ -0,0 +1,70 @@
1
+ import React from 'react';
2
+ import type { ViewStyle } from 'react-native';
3
+
4
+ import { useTheme } from '../theme/ThemeContext';
5
+ import { Box, type BoxProps } from './Box';
6
+
7
+ export type SurfaceVariant = 'default' | 'subtle' | 'raised' | 'outline';
8
+
9
+ export interface SurfaceProps extends Omit<BoxProps, 'bg' | 'borderColor' | 'borderWidth'> {
10
+ variant?: SurfaceVariant;
11
+ }
12
+
13
+ function resolveSurfaceVariantStyles(
14
+ variant: SurfaceVariant,
15
+ borderColor: string,
16
+ backgroundColor: string,
17
+ ): ViewStyle {
18
+ switch (variant) {
19
+ case 'subtle':
20
+ return {
21
+ backgroundColor,
22
+ borderColor: 'transparent',
23
+ borderWidth: 0,
24
+ };
25
+ case 'raised':
26
+ return {
27
+ backgroundColor,
28
+ borderColor: 'transparent',
29
+ borderWidth: 0,
30
+ elevation: 2,
31
+ shadowColor: '#000000',
32
+ shadowOpacity: 0.08,
33
+ shadowOffset: { width: 0, height: 2 },
34
+ shadowRadius: 8,
35
+ };
36
+ case 'outline':
37
+ return {
38
+ backgroundColor: 'transparent',
39
+ borderColor,
40
+ borderWidth: 1,
41
+ };
42
+ case 'default':
43
+ default:
44
+ return {
45
+ backgroundColor,
46
+ borderColor,
47
+ borderWidth: 1,
48
+ };
49
+ }
50
+ }
51
+
52
+ export function Surface({ variant = 'default', radius = 'm', style, ...props }: SurfaceProps) {
53
+ const { theme } = useTheme();
54
+
55
+ const backgroundColor =
56
+ variant === 'subtle'
57
+ ? theme.semantics.surface.subtle
58
+ : variant === 'raised'
59
+ ? theme.semantics.surface.raised
60
+ : theme.semantics.surface.default;
61
+ const borderColor = theme.semantics.border.default;
62
+
63
+ return (
64
+ <Box
65
+ {...props}
66
+ radius={radius}
67
+ style={[resolveSurfaceVariantStyles(variant, borderColor, backgroundColor), style]}
68
+ />
69
+ );
70
+ }
@@ -0,0 +1,85 @@
1
+ import React from 'react';
2
+ import { View } from 'react-native';
3
+
4
+ import {
5
+ type Breakpoint,
6
+ resolveResponsive,
7
+ type Responsive,
8
+ useResponsiveRuntime,
9
+ } from '../core/responsive';
10
+ import { useTheme } from '../theme/ThemeContext';
11
+ import { Box, type BoxProps } from './Box';
12
+ import { resolveSpacing, type SpaceValue } from './helpers';
13
+
14
+ type SlotMap = Record<string, React.ReactNode>;
15
+ type TemplateMap = Partial<Record<Breakpoint, string[][]>>;
16
+ type ColumnsMap = Partial<Record<Breakpoint, number[]>>;
17
+
18
+ export interface TemplateProps extends Omit<BoxProps, 'children'> {
19
+ slots: SlotMap;
20
+ templates: TemplateMap;
21
+ columns?: ColumnsMap;
22
+ gap?: Responsive<SpaceValue>;
23
+ rowGap?: Responsive<SpaceValue>;
24
+ colGap?: Responsive<SpaceValue>;
25
+ }
26
+
27
+ function resolveByBreakpointMap<T>(
28
+ map: Partial<Record<Breakpoint, T>> | undefined,
29
+ breakpoint: Breakpoint,
30
+ ): T | undefined {
31
+ return resolveResponsive(map, breakpoint);
32
+ }
33
+
34
+ export function Template({
35
+ slots,
36
+ templates,
37
+ columns,
38
+ gap = 0,
39
+ rowGap,
40
+ colGap,
41
+ ...props
42
+ }: TemplateProps) {
43
+ const { theme } = useTheme();
44
+ const { breakpoint } = useResponsiveRuntime();
45
+
46
+ const resolvedTemplate = resolveByBreakpointMap(templates, breakpoint) ?? [];
47
+ const resolvedColumns = resolveByBreakpointMap(columns, breakpoint);
48
+ const defaultGap = resolveResponsive(gap, breakpoint) ?? 0;
49
+ const rowSpacing = Number(
50
+ resolveSpacing(theme, resolveResponsive(rowGap, breakpoint) ?? defaultGap) ?? 0,
51
+ );
52
+ const colSpacing = Number(
53
+ resolveSpacing(theme, resolveResponsive(colGap, breakpoint) ?? defaultGap) ?? 0,
54
+ );
55
+
56
+ return (
57
+ <Box {...props}>
58
+ {resolvedTemplate.map((row, rowIndex) => (
59
+ <View
60
+ key={`row-${String(rowIndex)}`}
61
+ style={{
62
+ flexDirection: 'row',
63
+ marginTop: rowIndex === 0 ? 0 : rowSpacing,
64
+ }}
65
+ >
66
+ {row.map((slotId, cellIndex) => {
67
+ const weight = resolvedColumns?.[cellIndex] ?? 1;
68
+ const node = slots[slotId];
69
+ return (
70
+ <View
71
+ key={`${slotId}-${String(rowIndex)}-${String(cellIndex)}`}
72
+ style={{
73
+ flex: weight,
74
+ marginLeft: cellIndex === 0 ? 0 : colSpacing,
75
+ }}
76
+ >
77
+ {node}
78
+ </View>
79
+ );
80
+ })}
81
+ </View>
82
+ ))}
83
+ </Box>
84
+ );
85
+ }
@@ -0,0 +1,71 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import type { AnkhTheme } from '../theme/types';
4
+ import { resolveBoxStyles } from './helpers';
5
+
6
+ const mockTheme = {
7
+ spacing: {
8
+ s: 4,
9
+ m: 8,
10
+ l: 16,
11
+ },
12
+ radii: {
13
+ m: 8,
14
+ },
15
+ colors: {
16
+ primary: '#007AFF',
17
+ surface: '#FFFFFF',
18
+ },
19
+ } as unknown as AnkhTheme;
20
+
21
+ describe('resolveBoxStyles', () => {
22
+ test('resolves padding and margins correctly', () => {
23
+ const styles = resolveBoxStyles(mockTheme, 'base', {
24
+ p: 's',
25
+ m: 8,
26
+ });
27
+ expect(styles.padding).toBe(4);
28
+ expect(styles.margin).toBe(8);
29
+ });
30
+
31
+ test('resolves responsive properties', () => {
32
+ const props = {
33
+ p: { base: 's', lg: 'l' },
34
+ };
35
+ const baseStyles = resolveBoxStyles(mockTheme, 'base', props);
36
+ const lgStyles = resolveBoxStyles(mockTheme, 'lg', props);
37
+ expect(baseStyles.padding).toBe(4);
38
+ expect(lgStyles.padding).toBe(16);
39
+ });
40
+
41
+ test('resolves colors and border styles', () => {
42
+ const styles = resolveBoxStyles(mockTheme, 'base', {
43
+ bg: 'primary',
44
+ radius: 'm',
45
+ borderWidth: 1,
46
+ borderColor: 'surface',
47
+ });
48
+ expect(styles.backgroundColor).toBe('#007AFF');
49
+ expect(styles.borderRadius).toBe(8);
50
+ expect(styles.borderWidth).toBe(1);
51
+ expect(styles.borderColor).toBe('#FFFFFF');
52
+ });
53
+
54
+ test('resolves spacing tokens for dimension props and preserves raw strings', () => {
55
+ const styles = resolveBoxStyles(mockTheme, 'base', {
56
+ width: 'm',
57
+ minHeight: 'l',
58
+ maxWidth: '50%',
59
+ });
60
+
61
+ expect(styles.width).toBe(8);
62
+ expect(styles.minHeight).toBe(16);
63
+ expect(styles.maxWidth).toBe('50%');
64
+ });
65
+
66
+ test('handles undefined props gracefully', () => {
67
+ const styles = resolveBoxStyles(mockTheme, 'base', {});
68
+ expect(styles.padding).toBeUndefined();
69
+ expect(styles.margin).toBeUndefined();
70
+ });
71
+ });