@harkenapp/sdk-react-native 0.0.1-alpha.1 → 0.0.1-alpha.2

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 (278) hide show
  1. package/README.md +44 -7
  2. package/app.plugin.cjs +12 -17
  3. package/dist/__mocks__/async-storage.d.ts +16 -0
  4. package/dist/__mocks__/async-storage.d.ts.map +1 -0
  5. package/dist/__mocks__/async-storage.js +39 -0
  6. package/dist/__mocks__/async-storage.js.map +1 -0
  7. package/dist/__mocks__/expo-document-picker.d.ts +26 -0
  8. package/dist/__mocks__/expo-document-picker.d.ts.map +1 -0
  9. package/dist/__mocks__/expo-document-picker.js +25 -0
  10. package/dist/__mocks__/expo-document-picker.js.map +1 -0
  11. package/dist/__mocks__/expo-file-system.d.ts +42 -0
  12. package/dist/__mocks__/expo-file-system.d.ts.map +1 -0
  13. package/dist/__mocks__/expo-file-system.js +37 -0
  14. package/dist/__mocks__/expo-file-system.js.map +1 -0
  15. package/dist/__mocks__/expo-image-picker.d.ts +30 -0
  16. package/dist/__mocks__/expo-image-picker.d.ts.map +1 -0
  17. package/dist/__mocks__/expo-image-picker.js +30 -0
  18. package/dist/__mocks__/expo-image-picker.js.map +1 -0
  19. package/dist/__mocks__/expo-secure-store.d.ts +15 -0
  20. package/dist/__mocks__/expo-secure-store.d.ts.map +1 -0
  21. package/dist/__mocks__/expo-secure-store.js +30 -0
  22. package/dist/__mocks__/expo-secure-store.js.map +1 -0
  23. package/dist/__mocks__/react-native.d.ts +73 -0
  24. package/dist/__mocks__/react-native.d.ts.map +1 -0
  25. package/dist/__mocks__/react-native.js +45 -0
  26. package/dist/__mocks__/react-native.js.map +1 -0
  27. package/dist/api/client.d.ts +8 -8
  28. package/dist/api/client.d.ts.map +1 -1
  29. package/dist/api/client.js +17 -19
  30. package/dist/api/client.js.map +1 -1
  31. package/dist/api/client.test.d.ts +2 -0
  32. package/dist/api/client.test.d.ts.map +1 -0
  33. package/dist/api/client.test.js +417 -0
  34. package/dist/api/client.test.js.map +1 -0
  35. package/dist/api/errors.d.ts +3 -3
  36. package/dist/api/errors.d.ts.map +1 -1
  37. package/dist/api/errors.js +3 -3
  38. package/dist/api/errors.js.map +1 -1
  39. package/dist/api/errors.test.d.ts +2 -0
  40. package/dist/api/errors.test.d.ts.map +1 -0
  41. package/dist/api/errors.test.js +155 -0
  42. package/dist/api/errors.test.js.map +1 -0
  43. package/dist/api/index.d.ts +6 -6
  44. package/dist/api/index.d.ts.map +1 -1
  45. package/dist/api/index.js.map +1 -1
  46. package/dist/api/retry.d.ts +1 -1
  47. package/dist/api/retry.d.ts.map +1 -1
  48. package/dist/api/retry.js.map +1 -1
  49. package/dist/api/retry.test.d.ts +2 -0
  50. package/dist/api/retry.test.d.ts.map +1 -0
  51. package/dist/api/retry.test.js +193 -0
  52. package/dist/api/retry.test.js.map +1 -0
  53. package/dist/attachments/FeedbackSheet.d.ts +36 -13
  54. package/dist/attachments/FeedbackSheet.d.ts.map +1 -1
  55. package/dist/attachments/FeedbackSheet.js +50 -30
  56. package/dist/attachments/FeedbackSheet.js.map +1 -1
  57. package/dist/attachments/index.d.ts +2 -2
  58. package/dist/components/AttachmentGrid.d.ts +12 -4
  59. package/dist/components/AttachmentGrid.d.ts.map +1 -1
  60. package/dist/components/AttachmentGrid.js +44 -34
  61. package/dist/components/AttachmentGrid.js.map +1 -1
  62. package/dist/components/AttachmentPicker.d.ts +3 -3
  63. package/dist/components/AttachmentPicker.d.ts.map +1 -1
  64. package/dist/components/AttachmentPicker.js +34 -36
  65. package/dist/components/AttachmentPicker.js.map +1 -1
  66. package/dist/components/AttachmentPreview.d.ts +10 -4
  67. package/dist/components/AttachmentPreview.d.ts.map +1 -1
  68. package/dist/components/AttachmentPreview.js +48 -34
  69. package/dist/components/AttachmentPreview.js.map +1 -1
  70. package/dist/components/CategorySelector.d.ts +3 -3
  71. package/dist/components/CategorySelector.d.ts.map +1 -1
  72. package/dist/components/CategorySelector.js +21 -27
  73. package/dist/components/CategorySelector.js.map +1 -1
  74. package/dist/components/FeedbackForm.d.ts +3 -3
  75. package/dist/components/FeedbackForm.d.ts.map +1 -1
  76. package/dist/components/FeedbackForm.js +7 -8
  77. package/dist/components/FeedbackForm.js.map +1 -1
  78. package/dist/components/FeedbackSheet.d.ts +34 -11
  79. package/dist/components/FeedbackSheet.d.ts.map +1 -1
  80. package/dist/components/FeedbackSheet.js +46 -28
  81. package/dist/components/FeedbackSheet.js.map +1 -1
  82. package/dist/components/ThemedButton.d.ts +16 -5
  83. package/dist/components/ThemedButton.d.ts.map +1 -1
  84. package/dist/components/ThemedButton.js +38 -29
  85. package/dist/components/ThemedButton.js.map +1 -1
  86. package/dist/components/ThemedText.d.ts +3 -3
  87. package/dist/components/ThemedText.d.ts.map +1 -1
  88. package/dist/components/ThemedText.js +1 -1
  89. package/dist/components/ThemedText.js.map +1 -1
  90. package/dist/components/ThemedTextInput.d.ts +11 -2
  91. package/dist/components/ThemedTextInput.d.ts.map +1 -1
  92. package/dist/components/ThemedTextInput.js +19 -9
  93. package/dist/components/ThemedTextInput.js.map +1 -1
  94. package/dist/components/UploadStatusOverlay.d.ts +11 -3
  95. package/dist/components/UploadStatusOverlay.d.ts.map +1 -1
  96. package/dist/components/UploadStatusOverlay.js +59 -76
  97. package/dist/components/UploadStatusOverlay.js.map +1 -1
  98. package/dist/components/index.d.ts +18 -18
  99. package/dist/components/index.d.ts.map +1 -1
  100. package/dist/components/index.js.map +1 -1
  101. package/dist/context/HarkenContext.d.ts +20 -15
  102. package/dist/context/HarkenContext.d.ts.map +1 -1
  103. package/dist/context/HarkenContext.js +20 -17
  104. package/dist/context/HarkenContext.js.map +1 -1
  105. package/dist/context/index.d.ts +2 -2
  106. package/dist/domain/index.d.ts +2 -2
  107. package/dist/domain/index.d.ts.map +1 -1
  108. package/dist/domain/index.js.map +1 -1
  109. package/dist/hooks/index.d.ts +5 -5
  110. package/dist/hooks/useAnonymousId.js +1 -1
  111. package/dist/hooks/useAnonymousId.test.d.ts +2 -0
  112. package/dist/hooks/useAnonymousId.test.d.ts.map +1 -0
  113. package/dist/hooks/useAnonymousId.test.js +154 -0
  114. package/dist/hooks/useAnonymousId.test.js.map +1 -0
  115. package/dist/hooks/useAttachmentPicker.d.ts +3 -3
  116. package/dist/hooks/useAttachmentPicker.js +7 -7
  117. package/dist/hooks/useAttachmentStatus.d.ts +1 -1
  118. package/dist/hooks/useAttachmentStatus.d.ts.map +1 -1
  119. package/dist/hooks/useAttachmentStatus.js.map +1 -1
  120. package/dist/hooks/useAttachmentUpload.d.ts +2 -2
  121. package/dist/hooks/useAttachmentUpload.d.ts.map +1 -1
  122. package/dist/hooks/useAttachmentUpload.js +5 -5
  123. package/dist/hooks/useAttachmentUpload.js.map +1 -1
  124. package/dist/hooks/useAttachmentUpload.test.d.ts +2 -0
  125. package/dist/hooks/useAttachmentUpload.test.d.ts.map +1 -0
  126. package/dist/hooks/useAttachmentUpload.test.js +542 -0
  127. package/dist/hooks/useAttachmentUpload.test.js.map +1 -0
  128. package/dist/hooks/useFeedback.d.ts +4 -4
  129. package/dist/hooks/useFeedback.d.ts.map +1 -1
  130. package/dist/hooks/useFeedback.js +3 -5
  131. package/dist/hooks/useFeedback.js.map +1 -1
  132. package/dist/hooks/useFeedback.test.d.ts +2 -0
  133. package/dist/hooks/useFeedback.test.d.ts.map +1 -0
  134. package/dist/hooks/useFeedback.test.js +299 -0
  135. package/dist/hooks/useFeedback.test.js.map +1 -0
  136. package/dist/hooks/useHarkenContext.d.ts +1 -1
  137. package/dist/hooks/useHarkenContext.js +1 -1
  138. package/dist/hooks/useHarkenTheme.d.ts +27 -3
  139. package/dist/hooks/useHarkenTheme.d.ts.map +1 -1
  140. package/dist/hooks/useHarkenTheme.js +26 -2
  141. package/dist/hooks/useHarkenTheme.js.map +1 -1
  142. package/dist/index.d.ts +28 -28
  143. package/dist/index.d.ts.map +1 -1
  144. package/dist/index.js.map +1 -1
  145. package/dist/services/index.d.ts +3 -3
  146. package/dist/services/index.d.ts.map +1 -1
  147. package/dist/services/index.js.map +1 -1
  148. package/dist/services/uploadQueueService.d.ts +2 -2
  149. package/dist/services/uploadQueueService.d.ts.map +1 -1
  150. package/dist/services/uploadQueueService.js +16 -17
  151. package/dist/services/uploadQueueService.js.map +1 -1
  152. package/dist/services/uploadQueueService.test.d.ts +2 -0
  153. package/dist/services/uploadQueueService.test.d.ts.map +1 -0
  154. package/dist/services/uploadQueueService.test.js +426 -0
  155. package/dist/services/uploadQueueService.test.js.map +1 -0
  156. package/dist/services/uploadQueueStorage.d.ts +1 -1
  157. package/dist/services/uploadQueueStorage.d.ts.map +1 -1
  158. package/dist/services/uploadQueueStorage.js +4 -4
  159. package/dist/services/uploadQueueStorage.js.map +1 -1
  160. package/dist/services/uploadQueueStorage.test.d.ts +2 -0
  161. package/dist/services/uploadQueueStorage.test.d.ts.map +1 -0
  162. package/dist/services/uploadQueueStorage.test.js +200 -0
  163. package/dist/services/uploadQueueStorage.test.js.map +1 -0
  164. package/dist/storage/IdentityStore.d.ts +1 -1
  165. package/dist/storage/IdentityStore.d.ts.map +1 -1
  166. package/dist/storage/IdentityStore.js.map +1 -1
  167. package/dist/storage/IdentityStore.test.d.ts +2 -0
  168. package/dist/storage/IdentityStore.test.d.ts.map +1 -0
  169. package/dist/storage/IdentityStore.test.js +176 -0
  170. package/dist/storage/IdentityStore.test.js.map +1 -0
  171. package/dist/storage/SecureStoreAdapter.d.ts +1 -1
  172. package/dist/storage/SecureStoreAdapter.test.d.ts +2 -0
  173. package/dist/storage/SecureStoreAdapter.test.d.ts.map +1 -0
  174. package/dist/storage/SecureStoreAdapter.test.js +114 -0
  175. package/dist/storage/SecureStoreAdapter.test.js.map +1 -0
  176. package/dist/storage/defaultStorage.d.ts +1 -1
  177. package/dist/storage/defaultStorage.js +4 -4
  178. package/dist/storage/defaultStorage.test.d.ts +2 -0
  179. package/dist/storage/defaultStorage.test.d.ts.map +1 -0
  180. package/dist/storage/defaultStorage.test.js +159 -0
  181. package/dist/storage/defaultStorage.test.js.map +1 -0
  182. package/dist/storage/index.d.ts +5 -5
  183. package/dist/storage/types.js +1 -1
  184. package/dist/theme/defaults.d.ts +14 -3
  185. package/dist/theme/defaults.d.ts.map +1 -1
  186. package/dist/theme/defaults.js +58 -43
  187. package/dist/theme/defaults.js.map +1 -1
  188. package/dist/theme/index.d.ts +3 -2
  189. package/dist/theme/index.d.ts.map +1 -1
  190. package/dist/theme/index.js +4 -1
  191. package/dist/theme/index.js.map +1 -1
  192. package/dist/theme/resolver.d.ts +16 -0
  193. package/dist/theme/resolver.d.ts.map +1 -0
  194. package/dist/theme/resolver.js +375 -0
  195. package/dist/theme/resolver.js.map +1 -0
  196. package/dist/theme/resolver.test.d.ts +2 -0
  197. package/dist/theme/resolver.test.d.ts.map +1 -0
  198. package/dist/theme/resolver.test.js +344 -0
  199. package/dist/theme/resolver.test.js.map +1 -0
  200. package/dist/theme/types.d.ts +378 -5
  201. package/dist/theme/types.d.ts.map +1 -1
  202. package/dist/types/config.d.ts +4 -4
  203. package/dist/types/index.d.ts +2 -2
  204. package/dist/utils/index.d.ts +1 -1
  205. package/dist/utils/uuid.d.ts.map +1 -1
  206. package/dist/utils/uuid.js +4 -5
  207. package/dist/utils/uuid.js.map +1 -1
  208. package/dist/utils/uuid.test.d.ts +2 -0
  209. package/dist/utils/uuid.test.d.ts.map +1 -0
  210. package/dist/utils/uuid.test.js +78 -0
  211. package/dist/utils/uuid.test.js.map +1 -0
  212. package/package.json +21 -13
  213. package/src/@types/expo-file-system-legacy.d.ts +3 -3
  214. package/src/__mocks__/async-storage.ts +46 -0
  215. package/src/__mocks__/expo-document-picker.ts +41 -0
  216. package/src/__mocks__/expo-file-system.ts +62 -0
  217. package/src/__mocks__/expo-image-picker.ts +48 -0
  218. package/src/__mocks__/expo-secure-store.ts +29 -0
  219. package/src/__mocks__/react-native.ts +46 -0
  220. package/src/api/client.test.ts +515 -0
  221. package/src/api/client.ts +45 -64
  222. package/src/api/errors.test.ts +193 -0
  223. package/src/api/errors.ts +7 -11
  224. package/src/api/index.ts +6 -10
  225. package/src/api/retry.test.ts +251 -0
  226. package/src/api/retry.ts +3 -6
  227. package/src/attachments/FeedbackSheet.tsx +100 -80
  228. package/src/attachments/index.ts +2 -2
  229. package/src/components/AttachmentGrid.tsx +54 -45
  230. package/src/components/AttachmentPicker.tsx +43 -54
  231. package/src/components/AttachmentPreview.tsx +51 -47
  232. package/src/components/CategorySelector.tsx +29 -35
  233. package/src/components/FeedbackForm.tsx +23 -35
  234. package/src/components/FeedbackSheet.tsx +89 -68
  235. package/src/components/ThemedButton.tsx +49 -47
  236. package/src/components/ThemedText.tsx +7 -10
  237. package/src/components/ThemedTextInput.tsx +23 -13
  238. package/src/components/UploadStatusOverlay.tsx +66 -89
  239. package/src/components/index.ts +18 -21
  240. package/src/context/HarkenContext.tsx +29 -28
  241. package/src/context/index.ts +2 -2
  242. package/src/domain/index.ts +2 -5
  243. package/src/domain/upload-queue.ts +5 -5
  244. package/src/hooks/index.ts +5 -5
  245. package/src/hooks/useAnonymousId.test.ts +189 -0
  246. package/src/hooks/useAnonymousId.ts +3 -3
  247. package/src/hooks/useAttachmentPicker.ts +12 -12
  248. package/src/hooks/useAttachmentStatus.ts +12 -16
  249. package/src/hooks/useAttachmentUpload.test.ts +632 -0
  250. package/src/hooks/useAttachmentUpload.ts +45 -54
  251. package/src/hooks/useFeedback.test.ts +376 -0
  252. package/src/hooks/useFeedback.ts +12 -14
  253. package/src/hooks/useHarkenContext.ts +4 -4
  254. package/src/hooks/useHarkenTheme.ts +30 -6
  255. package/src/index.ts +28 -52
  256. package/src/services/index.ts +3 -9
  257. package/src/services/uploadQueueService.test.ts +489 -0
  258. package/src/services/uploadQueueService.ts +40 -56
  259. package/src/services/uploadQueueStorage.test.ts +243 -0
  260. package/src/services/uploadQueueStorage.ts +7 -9
  261. package/src/storage/IdentityStore.test.ts +173 -0
  262. package/src/storage/IdentityStore.ts +4 -5
  263. package/src/storage/SecureStoreAdapter.test.ts +147 -0
  264. package/src/storage/SecureStoreAdapter.ts +1 -1
  265. package/src/storage/defaultStorage.test.ts +159 -0
  266. package/src/storage/defaultStorage.ts +6 -6
  267. package/src/storage/index.ts +5 -5
  268. package/src/storage/types.ts +1 -1
  269. package/src/theme/defaults.ts +75 -46
  270. package/src/theme/index.ts +15 -2
  271. package/src/theme/resolver.test.ts +411 -0
  272. package/src/theme/resolver.ts +446 -0
  273. package/src/theme/types.ts +453 -15
  274. package/src/types/config.ts +4 -4
  275. package/src/types/index.ts +2 -2
  276. package/src/utils/index.ts +1 -1
  277. package/src/utils/uuid.test.ts +85 -0
  278. package/src/utils/uuid.ts +4 -7
@@ -0,0 +1,446 @@
1
+ import type {
2
+ HarkenColors,
3
+ HarkenTypography,
4
+ HarkenSpacing,
5
+ HarkenRadii,
6
+ HarkenSizing,
7
+ HarkenOpacity,
8
+ HarkenTheme,
9
+ PartialHarkenTheme,
10
+ ResolvedHarkenColors,
11
+ ResolvedHarkenSpacing,
12
+ ResolvedHarkenRadii,
13
+ ResolvedHarkenSizing,
14
+ ResolvedHarkenOpacity,
15
+ ResolvedHarkenTheme,
16
+ HarkenComponentTokens,
17
+ } from "./types";
18
+
19
+ // ============================================================================
20
+ // DEFAULT VALUES
21
+ // ============================================================================
22
+
23
+ /** Default sizing values */
24
+ const DEFAULT_SIZING: ResolvedHarkenSizing = {
25
+ buttonMinHeight: 48,
26
+ inputMinHeight: 44,
27
+ tileSize: 80,
28
+ addButtonIconSize: 28,
29
+ pickerIconSize: 44,
30
+ };
31
+
32
+ /** Default opacity values */
33
+ const DEFAULT_OPACITY: ResolvedHarkenOpacity = {
34
+ disabled: 0.6,
35
+ pressed: 0.8,
36
+ };
37
+
38
+ /** Default progress track color (used when no theme-aware value is available) */
39
+ const DEFAULT_PROGRESS_TRACK = "rgba(255, 255, 255, 0.3)";
40
+
41
+ // ============================================================================
42
+ // COLOR RESOLUTION
43
+ // ============================================================================
44
+
45
+ /**
46
+ * Resolves color tokens, applying fallbacks for all component tokens.
47
+ * The resolution order for component tokens is:
48
+ * 1. Explicit component token (e.g., chipBackground)
49
+ * 2. User-provided base token (e.g., surface)
50
+ * 3. Default base token value
51
+ */
52
+ function resolveColors(
53
+ input: Partial<HarkenColors> | undefined,
54
+ defaults: HarkenColors
55
+ ): ResolvedHarkenColors {
56
+ // First resolve base tokens
57
+ const primary = input?.primary ?? defaults.primary;
58
+ const primaryPressed = input?.primaryPressed ?? defaults.primaryPressed;
59
+ const background = input?.background ?? defaults.background;
60
+ // Surface resolution order: explicit surface > explicit backgroundSecondary > defaults.surface > defaults.backgroundSecondary
61
+ const surface =
62
+ input?.surface ??
63
+ input?.backgroundSecondary ??
64
+ defaults.surface ??
65
+ defaults.backgroundSecondary;
66
+ const backgroundSecondary = input?.backgroundSecondary ?? surface;
67
+ const text = input?.text ?? defaults.text;
68
+ const textSecondary = input?.textSecondary ?? defaults.textSecondary;
69
+ const textPlaceholder = input?.textPlaceholder ?? defaults.textPlaceholder;
70
+ const textOnPrimary = input?.textOnPrimary ?? defaults.textOnPrimary;
71
+ const border = input?.border ?? defaults.border;
72
+ const borderFocused = input?.borderFocused ?? defaults.borderFocused;
73
+ const error = input?.error ?? defaults.error;
74
+ const success = input?.success ?? defaults.success;
75
+ const warning = input?.warning ?? defaults.warning;
76
+ const info = input?.info ?? defaults.info;
77
+ const overlay = input?.overlay ?? defaults.overlay;
78
+ const overlayDark = input?.overlayDark ?? defaults.overlayDark;
79
+ const accent1 = input?.accent1 ?? defaults.accent1;
80
+ const accent2 = input?.accent2 ?? defaults.accent2;
81
+ const accent3 = input?.accent3 ?? defaults.accent3;
82
+
83
+ // Now resolve component tokens with fallbacks to base tokens
84
+ return {
85
+ // Base tokens
86
+ primary,
87
+ primaryPressed,
88
+ background,
89
+ surface,
90
+ backgroundSecondary,
91
+ text,
92
+ textSecondary,
93
+ textPlaceholder,
94
+ textOnPrimary,
95
+ border,
96
+ borderFocused,
97
+ error,
98
+ success,
99
+ warning,
100
+ info,
101
+ overlay,
102
+ overlayDark,
103
+ accent1,
104
+ accent2,
105
+ accent3,
106
+
107
+ // Chip tokens
108
+ chipBackground: input?.chipBackground ?? surface,
109
+ chipBackgroundSelected: input?.chipBackgroundSelected ?? primary,
110
+ chipBorder: input?.chipBorder ?? border,
111
+ chipBorderSelected: input?.chipBorderSelected ?? primary,
112
+ chipText: input?.chipText ?? text,
113
+ chipTextSelected: input?.chipTextSelected ?? textOnPrimary,
114
+
115
+ // Input tokens
116
+ inputBackground: input?.inputBackground ?? surface,
117
+ inputBorder: input?.inputBorder ?? border,
118
+ inputBorderFocused: input?.inputBorderFocused ?? borderFocused,
119
+ inputBorderError: input?.inputBorderError ?? error,
120
+ inputText: input?.inputText ?? text,
121
+ inputPlaceholder: input?.inputPlaceholder ?? textPlaceholder,
122
+
123
+ // Button tokens - Primary
124
+ buttonPrimaryBackground: input?.buttonPrimaryBackground ?? primary,
125
+ buttonPrimaryBackgroundPressed: input?.buttonPrimaryBackgroundPressed ?? primaryPressed,
126
+ buttonPrimaryText: input?.buttonPrimaryText ?? textOnPrimary,
127
+
128
+ // Button tokens - Secondary
129
+ buttonSecondaryBackground: input?.buttonSecondaryBackground ?? surface,
130
+ buttonSecondaryBorder: input?.buttonSecondaryBorder ?? border,
131
+ buttonSecondaryText: input?.buttonSecondaryText ?? text,
132
+
133
+ // Button tokens - Ghost
134
+ buttonGhostText: input?.buttonGhostText ?? text,
135
+
136
+ // Add button tokens
137
+ addButtonBackground: input?.addButtonBackground ?? surface,
138
+ addButtonBackgroundPressed: input?.addButtonBackgroundPressed ?? border,
139
+ addButtonBorder: input?.addButtonBorder ?? border,
140
+ addButtonIcon: input?.addButtonIcon ?? textSecondary,
141
+ addButtonText: input?.addButtonText ?? textSecondary,
142
+
143
+ // Tile tokens
144
+ tileBackground: input?.tileBackground ?? surface,
145
+ tileBorder: input?.tileBorder ?? border,
146
+
147
+ // Upload tokens
148
+ uploadOverlay: input?.uploadOverlay ?? overlay,
149
+ uploadOverlayError: input?.uploadOverlayError ?? overlayDark,
150
+ uploadProgressTrack: input?.uploadProgressTrack ?? DEFAULT_PROGRESS_TRACK,
151
+ uploadProgressFill: input?.uploadProgressFill ?? primary,
152
+ uploadBadgeSuccess: input?.uploadBadgeSuccess ?? success,
153
+ uploadText: input?.uploadText ?? textOnPrimary,
154
+
155
+ // Picker tokens
156
+ pickerOverlay: input?.pickerOverlay ?? overlay,
157
+ pickerBackground: input?.pickerBackground ?? background,
158
+ pickerHandle: input?.pickerHandle ?? textSecondary,
159
+ pickerOptionBackground: input?.pickerOptionBackground ?? surface,
160
+ pickerOptionBackgroundPressed: input?.pickerOptionBackgroundPressed ?? border,
161
+ pickerCancelText: input?.pickerCancelText ?? error,
162
+
163
+ // Form tokens
164
+ formBackground: input?.formBackground ?? background,
165
+ };
166
+ }
167
+
168
+ // ============================================================================
169
+ // SPACING RESOLUTION
170
+ // ============================================================================
171
+
172
+ /**
173
+ * Resolves spacing tokens, applying fallbacks for all component tokens.
174
+ */
175
+ function resolveSpacing(
176
+ input: Partial<HarkenSpacing> | undefined,
177
+ defaults: HarkenSpacing
178
+ ): ResolvedHarkenSpacing {
179
+ // Resolve base tokens
180
+ const xs = input?.xs ?? defaults.xs;
181
+ const sm = input?.sm ?? defaults.sm;
182
+ const md = input?.md ?? defaults.md;
183
+ const lg = input?.lg ?? defaults.lg;
184
+ const xl = input?.xl ?? defaults.xl;
185
+ const xxl = input?.xxl ?? defaults.xxl;
186
+
187
+ return {
188
+ // Base tokens
189
+ xs,
190
+ sm,
191
+ md,
192
+ lg,
193
+ xl,
194
+ xxl,
195
+
196
+ // Component tokens with fallbacks
197
+ chipPaddingVertical: input?.chipPaddingVertical ?? sm,
198
+ chipPaddingHorizontal: input?.chipPaddingHorizontal ?? md,
199
+ chipGap: input?.chipGap ?? sm,
200
+ inputPadding: input?.inputPadding ?? md,
201
+ buttonPaddingVertical: input?.buttonPaddingVertical ?? sm,
202
+ buttonPaddingHorizontal: input?.buttonPaddingHorizontal ?? md,
203
+ formPadding: input?.formPadding ?? lg,
204
+ sectionGap: input?.sectionGap ?? lg,
205
+ tileGap: input?.tileGap ?? sm,
206
+ };
207
+ }
208
+
209
+ // ============================================================================
210
+ // RADII RESOLUTION
211
+ // ============================================================================
212
+
213
+ /**
214
+ * Resolves radii tokens, applying fallbacks for all component tokens.
215
+ */
216
+ function resolveRadii(
217
+ input: Partial<HarkenRadii> | undefined,
218
+ defaults: HarkenRadii
219
+ ): ResolvedHarkenRadii {
220
+ // Resolve base tokens
221
+ const none = input?.none ?? defaults.none;
222
+ const sm = input?.sm ?? defaults.sm;
223
+ const md = input?.md ?? defaults.md;
224
+ const lg = input?.lg ?? defaults.lg;
225
+ const xl = input?.xl ?? defaults.xl;
226
+ const full = input?.full ?? defaults.full;
227
+
228
+ return {
229
+ // Base tokens
230
+ none,
231
+ sm,
232
+ md,
233
+ lg,
234
+ xl,
235
+ full,
236
+
237
+ // Component tokens with fallbacks
238
+ chip: input?.chip ?? full,
239
+ input: input?.input ?? md,
240
+ button: input?.button ?? md,
241
+ tile: input?.tile ?? md,
242
+ form: input?.form ?? lg,
243
+ picker: input?.picker ?? xl,
244
+ };
245
+ }
246
+
247
+ // ============================================================================
248
+ // SIZING RESOLUTION
249
+ // ============================================================================
250
+
251
+ /**
252
+ * Resolves sizing tokens.
253
+ */
254
+ function resolveSizing(input: Partial<HarkenSizing> | undefined): ResolvedHarkenSizing {
255
+ return {
256
+ buttonMinHeight: input?.buttonMinHeight ?? DEFAULT_SIZING.buttonMinHeight,
257
+ inputMinHeight: input?.inputMinHeight ?? DEFAULT_SIZING.inputMinHeight,
258
+ tileSize: input?.tileSize ?? DEFAULT_SIZING.tileSize,
259
+ addButtonIconSize: input?.addButtonIconSize ?? DEFAULT_SIZING.addButtonIconSize,
260
+ pickerIconSize: input?.pickerIconSize ?? DEFAULT_SIZING.pickerIconSize,
261
+ };
262
+ }
263
+
264
+ // ============================================================================
265
+ // OPACITY RESOLUTION
266
+ // ============================================================================
267
+
268
+ /**
269
+ * Resolves opacity tokens.
270
+ */
271
+ function resolveOpacity(input: Partial<HarkenOpacity> | undefined): ResolvedHarkenOpacity {
272
+ return {
273
+ disabled: input?.disabled ?? DEFAULT_OPACITY.disabled,
274
+ pressed: input?.pressed ?? DEFAULT_OPACITY.pressed,
275
+ };
276
+ }
277
+
278
+ // ============================================================================
279
+ // TYPOGRAPHY RESOLUTION
280
+ // ============================================================================
281
+
282
+ /**
283
+ * Resolves typography tokens.
284
+ * Typography doesn't have component-specific tokens, just base overrides.
285
+ */
286
+ function resolveTypography(
287
+ input: Partial<HarkenTypography> | undefined,
288
+ defaults: HarkenTypography
289
+ ): HarkenTypography {
290
+ return {
291
+ fontFamily: input?.fontFamily ?? defaults.fontFamily,
292
+ fontFamilyHeading: input?.fontFamilyHeading ?? defaults.fontFamilyHeading,
293
+ titleSize: input?.titleSize ?? defaults.titleSize,
294
+ titleLineHeight: input?.titleLineHeight ?? defaults.titleLineHeight,
295
+ titleWeight: input?.titleWeight ?? defaults.titleWeight,
296
+ bodySize: input?.bodySize ?? defaults.bodySize,
297
+ bodyLineHeight: input?.bodyLineHeight ?? defaults.bodyLineHeight,
298
+ bodyWeight: input?.bodyWeight ?? defaults.bodyWeight,
299
+ labelSize: input?.labelSize ?? defaults.labelSize,
300
+ labelWeight: input?.labelWeight ?? defaults.labelWeight,
301
+ captionSize: input?.captionSize ?? defaults.captionSize,
302
+ captionWeight: input?.captionWeight ?? defaults.captionWeight,
303
+ };
304
+ }
305
+
306
+ // ============================================================================
307
+ // COMPONENT ALIASES
308
+ // ============================================================================
309
+
310
+ /**
311
+ * Builds the structured component token aliases from resolved flat tokens.
312
+ * This provides an ergonomic grouped API for accessing theme values.
313
+ */
314
+ function buildComponentAliases(
315
+ colors: ResolvedHarkenColors,
316
+ spacing: ResolvedHarkenSpacing,
317
+ radii: ResolvedHarkenRadii,
318
+ sizing: ResolvedHarkenSizing
319
+ ): HarkenComponentTokens {
320
+ return {
321
+ chip: {
322
+ background: colors.chipBackground,
323
+ backgroundSelected: colors.chipBackgroundSelected,
324
+ border: colors.chipBorder,
325
+ borderSelected: colors.chipBorderSelected,
326
+ text: colors.chipText,
327
+ textSelected: colors.chipTextSelected,
328
+ paddingVertical: spacing.chipPaddingVertical,
329
+ paddingHorizontal: spacing.chipPaddingHorizontal,
330
+ gap: spacing.chipGap,
331
+ radius: radii.chip,
332
+ },
333
+ input: {
334
+ background: colors.inputBackground,
335
+ border: colors.inputBorder,
336
+ borderFocused: colors.inputBorderFocused,
337
+ borderError: colors.inputBorderError,
338
+ text: colors.inputText,
339
+ placeholder: colors.inputPlaceholder,
340
+ padding: spacing.inputPadding,
341
+ radius: radii.input,
342
+ minHeight: sizing.inputMinHeight,
343
+ },
344
+ button: {
345
+ primary: {
346
+ background: colors.buttonPrimaryBackground,
347
+ backgroundPressed: colors.buttonPrimaryBackgroundPressed,
348
+ text: colors.buttonPrimaryText,
349
+ },
350
+ secondary: {
351
+ background: colors.buttonSecondaryBackground,
352
+ border: colors.buttonSecondaryBorder,
353
+ text: colors.buttonSecondaryText,
354
+ },
355
+ ghost: {
356
+ text: colors.buttonGhostText,
357
+ },
358
+ paddingVertical: spacing.buttonPaddingVertical,
359
+ paddingHorizontal: spacing.buttonPaddingHorizontal,
360
+ radius: radii.button,
361
+ minHeight: sizing.buttonMinHeight,
362
+ },
363
+ addButton: {
364
+ background: colors.addButtonBackground,
365
+ backgroundPressed: colors.addButtonBackgroundPressed,
366
+ border: colors.addButtonBorder,
367
+ icon: colors.addButtonIcon,
368
+ text: colors.addButtonText,
369
+ iconSize: sizing.addButtonIconSize,
370
+ },
371
+ tile: {
372
+ background: colors.tileBackground,
373
+ border: colors.tileBorder,
374
+ radius: radii.tile,
375
+ size: sizing.tileSize,
376
+ gap: spacing.tileGap,
377
+ },
378
+ upload: {
379
+ overlay: colors.uploadOverlay,
380
+ overlayError: colors.uploadOverlayError,
381
+ progressTrack: colors.uploadProgressTrack,
382
+ progressFill: colors.uploadProgressFill,
383
+ badgeSuccess: colors.uploadBadgeSuccess,
384
+ text: colors.uploadText,
385
+ },
386
+ picker: {
387
+ overlay: colors.pickerOverlay,
388
+ background: colors.pickerBackground,
389
+ handle: colors.pickerHandle,
390
+ optionBackground: colors.pickerOptionBackground,
391
+ optionBackgroundPressed: colors.pickerOptionBackgroundPressed,
392
+ cancelText: colors.pickerCancelText,
393
+ radius: radii.picker,
394
+ iconSize: sizing.pickerIconSize,
395
+ },
396
+ form: {
397
+ background: colors.formBackground,
398
+ padding: spacing.formPadding,
399
+ sectionGap: spacing.sectionGap,
400
+ radius: radii.form,
401
+ },
402
+ };
403
+ }
404
+
405
+ // ============================================================================
406
+ // MAIN RESOLVER
407
+ // ============================================================================
408
+
409
+ /**
410
+ * Resolves a theme by merging user overrides with a base theme and applying
411
+ * all fallback rules. Returns a fully-resolved theme where all values are
412
+ * guaranteed to exist.
413
+ *
414
+ * This is the single source of truth for fallback logic. Components should
415
+ * never do their own `??` checks - they receive a resolved theme with all
416
+ * values populated.
417
+ *
418
+ * @param baseTheme - The base theme (light or dark)
419
+ * @param overrides - User-provided partial overrides
420
+ * @returns A fully resolved theme with all component tokens populated
421
+ */
422
+ export function resolveTheme(
423
+ baseTheme: HarkenTheme,
424
+ overrides?: PartialHarkenTheme
425
+ ): ResolvedHarkenTheme {
426
+ // Resolve each category
427
+ const colors = resolveColors(overrides?.colors, baseTheme.colors);
428
+ const typography = resolveTypography(overrides?.typography, baseTheme.typography);
429
+ const spacing = resolveSpacing(overrides?.spacing, baseTheme.spacing);
430
+ const radii = resolveRadii(overrides?.radii, baseTheme.radii);
431
+ const sizing = resolveSizing(overrides?.sizing);
432
+ const opacity = resolveOpacity(overrides?.opacity);
433
+
434
+ // Build component aliases
435
+ const components = buildComponentAliases(colors, spacing, radii, sizing);
436
+
437
+ return {
438
+ colors,
439
+ typography,
440
+ spacing,
441
+ radii,
442
+ sizing,
443
+ opacity,
444
+ components,
445
+ };
446
+ }