@medway-ui/native 1.1.2 → 1.3.0

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 (244) hide show
  1. package/lib/commonjs/components/Alert.js +5 -4
  2. package/lib/commonjs/components/Alert.js.map +1 -1
  3. package/lib/commonjs/components/Avatar.js +2 -1
  4. package/lib/commonjs/components/Avatar.js.map +1 -1
  5. package/lib/commonjs/components/Badge.js +2 -1
  6. package/lib/commonjs/components/Badge.js.map +1 -1
  7. package/lib/commonjs/components/Button.js +3 -2
  8. package/lib/commonjs/components/Button.js.map +1 -1
  9. package/lib/commonjs/components/CardNews.js +7 -6
  10. package/lib/commonjs/components/CardNews.js.map +1 -1
  11. package/lib/commonjs/components/Carousel.js +122 -0
  12. package/lib/commonjs/components/Carousel.js.map +1 -0
  13. package/lib/commonjs/components/Checkbox.js +11 -2
  14. package/lib/commonjs/components/Checkbox.js.map +1 -1
  15. package/lib/commonjs/components/DropdownMenu.js +225 -0
  16. package/lib/commonjs/components/DropdownMenu.js.map +1 -0
  17. package/lib/commonjs/components/EmptyState.js +19 -20
  18. package/lib/commonjs/components/EmptyState.js.map +1 -1
  19. package/lib/commonjs/components/Feed.js +82 -0
  20. package/lib/commonjs/components/Feed.js.map +1 -0
  21. package/lib/commonjs/components/FeedNotice.js +76 -0
  22. package/lib/commonjs/components/FeedNotice.js.map +1 -0
  23. package/lib/commonjs/components/Input.js +21 -9
  24. package/lib/commonjs/components/Input.js.map +1 -1
  25. package/lib/commonjs/components/InvitePopover.js +196 -0
  26. package/lib/commonjs/components/InvitePopover.js.map +1 -0
  27. package/lib/commonjs/components/Label.js +2 -1
  28. package/lib/commonjs/components/Label.js.map +1 -1
  29. package/lib/commonjs/components/LeaderboardCard.js +58 -0
  30. package/lib/commonjs/components/LeaderboardCard.js.map +1 -0
  31. package/lib/commonjs/components/MemberRow.js +131 -0
  32. package/lib/commonjs/components/MemberRow.js.map +1 -0
  33. package/lib/commonjs/components/PageHeader.js +3 -2
  34. package/lib/commonjs/components/PageHeader.js.map +1 -1
  35. package/lib/commonjs/components/PostForm.js +182 -0
  36. package/lib/commonjs/components/PostForm.js.map +1 -0
  37. package/lib/commonjs/components/PrizeCollection.js +135 -0
  38. package/lib/commonjs/components/PrizeCollection.js.map +1 -0
  39. package/lib/commonjs/components/RadioGroup.js +22 -0
  40. package/lib/commonjs/components/RadioGroup.js.map +1 -1
  41. package/lib/commonjs/components/SectionHeader.js +36 -0
  42. package/lib/commonjs/components/SectionHeader.js.map +1 -0
  43. package/lib/commonjs/components/Switch.js +33 -19
  44. package/lib/commonjs/components/Switch.js.map +1 -1
  45. package/lib/commonjs/components/Tabs.js +156 -0
  46. package/lib/commonjs/components/Tabs.js.map +1 -0
  47. package/lib/commonjs/components/Tag.js +2 -1
  48. package/lib/commonjs/components/Tag.js.map +1 -1
  49. package/lib/commonjs/components/Text.js +60 -0
  50. package/lib/commonjs/components/Text.js.map +1 -0
  51. package/lib/commonjs/components/Toast.js +3 -2
  52. package/lib/commonjs/components/Toast.js.map +1 -1
  53. package/lib/commonjs/components/Tooltip.js +2 -2
  54. package/lib/commonjs/components/Tooltip.js.map +1 -1
  55. package/lib/commonjs/editor/NoteEditor.js +710 -0
  56. package/lib/commonjs/editor/NoteEditor.js.map +1 -0
  57. package/lib/commonjs/editor/editorHtml.generated.js +21 -0
  58. package/lib/commonjs/editor/editorHtml.generated.js.map +1 -0
  59. package/lib/commonjs/editor/index.js +33 -0
  60. package/lib/commonjs/editor/index.js.map +1 -0
  61. package/lib/commonjs/editor/protocol.js +88 -0
  62. package/lib/commonjs/editor/protocol.js.map +1 -0
  63. package/lib/commonjs/hooks/useThemeColor.js +66 -29
  64. package/lib/commonjs/hooks/useThemeColor.js.map +1 -1
  65. package/lib/commonjs/index.js +319 -1
  66. package/lib/commonjs/index.js.map +1 -1
  67. package/lib/commonjs/lib/overlay-host.js +101 -0
  68. package/lib/commonjs/lib/overlay-host.js.map +1 -0
  69. package/lib/commonjs/lib/typography.js +26 -0
  70. package/lib/commonjs/lib/typography.js.map +1 -0
  71. package/lib/commonjs/nativewind-env.d.js +0 -1
  72. package/lib/commonjs/nativewind-env.d.js.map +1 -1
  73. package/lib/module/components/Alert.js +3 -2
  74. package/lib/module/components/Alert.js.map +1 -1
  75. package/lib/module/components/Avatar.js +2 -1
  76. package/lib/module/components/Avatar.js.map +1 -1
  77. package/lib/module/components/Badge.js +2 -1
  78. package/lib/module/components/Badge.js.map +1 -1
  79. package/lib/module/components/Button.js +2 -1
  80. package/lib/module/components/Button.js.map +1 -1
  81. package/lib/module/components/CardNews.js +2 -1
  82. package/lib/module/components/CardNews.js.map +1 -1
  83. package/lib/module/components/Carousel.js +117 -0
  84. package/lib/module/components/Carousel.js.map +1 -0
  85. package/lib/module/components/Checkbox.js +12 -2
  86. package/lib/module/components/Checkbox.js.map +1 -1
  87. package/lib/module/components/DropdownMenu.js +216 -0
  88. package/lib/module/components/DropdownMenu.js.map +1 -0
  89. package/lib/module/components/EmptyState.js +19 -20
  90. package/lib/module/components/EmptyState.js.map +1 -1
  91. package/lib/module/components/Feed.js +77 -0
  92. package/lib/module/components/Feed.js.map +1 -0
  93. package/lib/module/components/FeedNotice.js +73 -0
  94. package/lib/module/components/FeedNotice.js.map +1 -0
  95. package/lib/module/components/Input.js +21 -9
  96. package/lib/module/components/Input.js.map +1 -1
  97. package/lib/module/components/InvitePopover.js +183 -0
  98. package/lib/module/components/InvitePopover.js.map +1 -0
  99. package/lib/module/components/Label.js +2 -1
  100. package/lib/module/components/Label.js.map +1 -1
  101. package/lib/module/components/LeaderboardCard.js +53 -0
  102. package/lib/module/components/LeaderboardCard.js.map +1 -0
  103. package/lib/module/components/MemberRow.js +127 -0
  104. package/lib/module/components/MemberRow.js.map +1 -0
  105. package/lib/module/components/PageHeader.js +2 -1
  106. package/lib/module/components/PageHeader.js.map +1 -1
  107. package/lib/module/components/PostForm.js +174 -0
  108. package/lib/module/components/PostForm.js.map +1 -0
  109. package/lib/module/components/PrizeCollection.js +127 -0
  110. package/lib/module/components/PrizeCollection.js.map +1 -0
  111. package/lib/module/components/RadioGroup.js +21 -1
  112. package/lib/module/components/RadioGroup.js.map +1 -1
  113. package/lib/module/components/SectionHeader.js +33 -0
  114. package/lib/module/components/SectionHeader.js.map +1 -0
  115. package/lib/module/components/Switch.js +34 -20
  116. package/lib/module/components/Switch.js.map +1 -1
  117. package/lib/module/components/Tabs.js +148 -0
  118. package/lib/module/components/Tabs.js.map +1 -0
  119. package/lib/module/components/Tag.js +2 -1
  120. package/lib/module/components/Tag.js.map +1 -1
  121. package/lib/module/components/Text.js +58 -0
  122. package/lib/module/components/Text.js.map +1 -0
  123. package/lib/module/components/Toast.js +2 -1
  124. package/lib/module/components/Toast.js.map +1 -1
  125. package/lib/module/components/Tooltip.js +1 -1
  126. package/lib/module/components/Tooltip.js.map +1 -1
  127. package/lib/module/editor/NoteEditor.js +705 -0
  128. package/lib/module/editor/NoteEditor.js.map +1 -0
  129. package/lib/module/editor/editorHtml.generated.js +17 -0
  130. package/lib/module/editor/editorHtml.generated.js.map +1 -0
  131. package/lib/module/editor/index.js +5 -0
  132. package/lib/module/editor/index.js.map +1 -0
  133. package/lib/module/editor/protocol.js +83 -0
  134. package/lib/module/editor/protocol.js.map +1 -0
  135. package/lib/module/hooks/useThemeColor.js +66 -31
  136. package/lib/module/hooks/useThemeColor.js.map +1 -1
  137. package/lib/module/index.js +33 -3
  138. package/lib/module/index.js.map +1 -1
  139. package/lib/module/lib/overlay-host.js +96 -0
  140. package/lib/module/lib/overlay-host.js.map +1 -0
  141. package/lib/module/lib/typography.js +22 -0
  142. package/lib/module/lib/typography.js.map +1 -0
  143. package/lib/module/nativewind-env.d.js +0 -1
  144. package/lib/module/nativewind-env.d.js.map +1 -1
  145. package/lib/typescript/components/Alert.d.ts.map +1 -1
  146. package/lib/typescript/components/Avatar.d.ts.map +1 -1
  147. package/lib/typescript/components/Badge.d.ts.map +1 -1
  148. package/lib/typescript/components/Button.d.ts.map +1 -1
  149. package/lib/typescript/components/CardNews.d.ts.map +1 -1
  150. package/lib/typescript/components/Carousel.d.ts +29 -0
  151. package/lib/typescript/components/Carousel.d.ts.map +1 -0
  152. package/lib/typescript/components/Checkbox.d.ts.map +1 -1
  153. package/lib/typescript/components/DropdownMenu.d.ts +39 -0
  154. package/lib/typescript/components/DropdownMenu.d.ts.map +1 -0
  155. package/lib/typescript/components/EmptyState.d.ts +1 -1
  156. package/lib/typescript/components/EmptyState.d.ts.map +1 -1
  157. package/lib/typescript/components/Feed.d.ts +22 -0
  158. package/lib/typescript/components/Feed.d.ts.map +1 -0
  159. package/lib/typescript/components/FeedNotice.d.ts +17 -0
  160. package/lib/typescript/components/FeedNotice.d.ts.map +1 -0
  161. package/lib/typescript/components/Input.d.ts +3 -2
  162. package/lib/typescript/components/Input.d.ts.map +1 -1
  163. package/lib/typescript/components/InvitePopover.d.ts +46 -0
  164. package/lib/typescript/components/InvitePopover.d.ts.map +1 -0
  165. package/lib/typescript/components/Label.d.ts.map +1 -1
  166. package/lib/typescript/components/LeaderboardCard.d.ts +16 -0
  167. package/lib/typescript/components/LeaderboardCard.d.ts.map +1 -0
  168. package/lib/typescript/components/MemberRow.d.ts +40 -0
  169. package/lib/typescript/components/MemberRow.d.ts.map +1 -0
  170. package/lib/typescript/components/PageHeader.d.ts.map +1 -1
  171. package/lib/typescript/components/PostForm.d.ts +50 -0
  172. package/lib/typescript/components/PostForm.d.ts.map +1 -0
  173. package/lib/typescript/components/PrizeCollection.d.ts +35 -0
  174. package/lib/typescript/components/PrizeCollection.d.ts.map +1 -0
  175. package/lib/typescript/components/RadioGroup.d.ts +10 -2
  176. package/lib/typescript/components/RadioGroup.d.ts.map +1 -1
  177. package/lib/typescript/components/SectionHeader.d.ts +12 -0
  178. package/lib/typescript/components/SectionHeader.d.ts.map +1 -0
  179. package/lib/typescript/components/Switch.d.ts.map +1 -1
  180. package/lib/typescript/components/Tabs.d.ts +32 -0
  181. package/lib/typescript/components/Tabs.d.ts.map +1 -0
  182. package/lib/typescript/components/Tag.d.ts.map +1 -1
  183. package/lib/typescript/components/Text.d.ts +23 -0
  184. package/lib/typescript/components/Text.d.ts.map +1 -0
  185. package/lib/typescript/components/Toast.d.ts +1 -1
  186. package/lib/typescript/components/Toast.d.ts.map +1 -1
  187. package/lib/typescript/components/Tooltip.d.ts.map +1 -1
  188. package/lib/typescript/editor/NoteEditor.d.ts +82 -0
  189. package/lib/typescript/editor/NoteEditor.d.ts.map +1 -0
  190. package/lib/typescript/editor/editorHtml.generated.d.ts +13 -0
  191. package/lib/typescript/editor/editorHtml.generated.d.ts.map +1 -0
  192. package/lib/typescript/editor/index.d.ts +6 -0
  193. package/lib/typescript/editor/index.d.ts.map +1 -0
  194. package/lib/typescript/editor/protocol.d.ts +153 -0
  195. package/lib/typescript/editor/protocol.d.ts.map +1 -0
  196. package/lib/typescript/hooks/useThemeColor.d.ts +4 -2
  197. package/lib/typescript/hooks/useThemeColor.d.ts.map +1 -1
  198. package/lib/typescript/index.d.ts +28 -4
  199. package/lib/typescript/index.d.ts.map +1 -1
  200. package/lib/typescript/lib/overlay-host.d.ts +23 -0
  201. package/lib/typescript/lib/overlay-host.d.ts.map +1 -0
  202. package/lib/typescript/lib/typography.d.ts +12 -0
  203. package/lib/typescript/lib/typography.d.ts.map +1 -0
  204. package/package.json +36 -3
  205. package/setup/MedwayUiProvider.tsx +15 -3
  206. package/setup/metro.js +27 -5
  207. package/setup/tailwind.js +52 -4
  208. package/setup/tokens.native.css +8 -3
  209. package/src/components/Alert.tsx +4 -2
  210. package/src/components/Avatar.tsx +3 -1
  211. package/src/components/Badge.tsx +3 -1
  212. package/src/components/Button.tsx +3 -7
  213. package/src/components/CardNews.tsx +2 -1
  214. package/src/components/Carousel.tsx +152 -0
  215. package/src/components/Checkbox.tsx +14 -4
  216. package/src/components/DropdownMenu.tsx +312 -0
  217. package/src/components/EmptyState.tsx +21 -23
  218. package/src/components/Feed.tsx +96 -0
  219. package/src/components/FeedNotice.tsx +112 -0
  220. package/src/components/Input.tsx +11 -6
  221. package/src/components/InvitePopover.tsx +240 -0
  222. package/src/components/Label.tsx +3 -1
  223. package/src/components/LeaderboardCard.tsx +70 -0
  224. package/src/components/MemberRow.tsx +157 -0
  225. package/src/components/PageHeader.tsx +3 -1
  226. package/src/components/PostForm.tsx +250 -0
  227. package/src/components/PrizeCollection.tsx +190 -0
  228. package/src/components/RadioGroup.tsx +32 -3
  229. package/src/components/SectionHeader.tsx +45 -0
  230. package/src/components/Switch.tsx +65 -59
  231. package/src/components/Tabs.tsx +182 -0
  232. package/src/components/Tag.tsx +3 -1
  233. package/src/components/Text.tsx +66 -0
  234. package/src/components/Toast.tsx +3 -1
  235. package/src/components/Tooltip.tsx +2 -1
  236. package/src/editor/NoteEditor.tsx +802 -0
  237. package/src/editor/editorHtml.generated.ts +14 -0
  238. package/src/editor/index.ts +13 -0
  239. package/src/editor/protocol.ts +184 -0
  240. package/src/hooks/useThemeColor.ts +77 -32
  241. package/src/index.ts +153 -6
  242. package/src/lib/overlay-host.tsx +102 -0
  243. package/src/lib/typography.ts +21 -0
  244. package/src/nativewind-env.d.ts +2 -0
@@ -0,0 +1,13 @@
1
+ export { default as NoteEditor } from "./NoteEditor";
2
+ export type { NoteEditorHandle } from "./NoteEditor";
3
+ export type { WebViewMessageEvent } from "react-native-webview";
4
+
5
+ export {
6
+ EDITOR_PROTOCOL_VERSION,
7
+ EDITOR_RECEIVE_HANDLER,
8
+ isEditorMessage,
9
+ } from "./protocol";
10
+ export type {
11
+ EditorToNativeMessage,
12
+ NativeToEditorMessage,
13
+ } from "./protocol";
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Transport between React Native and the Tiptap editor inside the WebView. Both
3
+ * sides compile against this file — the bundle imports it through the `@/`
4
+ * alias in `editor-bundle/vite.config.ts`.
5
+ *
6
+ * Invariants:
7
+ * - The document never crosses the bridge on keystrokes: the editor emits
8
+ * `change` with a revision counter, and the native side asks with
9
+ * `getDocument`.
10
+ * - Every message is validated on arrival (`isEditorMessage`: envelope plus the
11
+ * required fields of its variant) — the WebView renders student content, so
12
+ * its output is untrusted input. Documents get a second pass through
13
+ * `normalizeDocument` before reaching the host.
14
+ */
15
+
16
+ import type {
17
+ EditorCommand,
18
+ EditorDocument,
19
+ EditorSelectionState,
20
+ EditorTheme,
21
+ SlashCommandId,
22
+ SlashMenuState,
23
+ } from '@medway-ui/notes-core';
24
+
25
+ /**
26
+ * Bumped whenever the message shapes below change. The native side refuses to
27
+ * talk to a bundle that reports a different version, which turns a stale
28
+ * `editorHtml.generated.ts` into a clear error instead of silent breakage.
29
+ */
30
+ export const EDITOR_PROTOCOL_VERSION = 3;
31
+
32
+ /** Name of the global the WebView bundle installs to receive native messages. */
33
+ export const EDITOR_RECEIVE_HANDLER = '__medwayNoteEditorReceive';
34
+
35
+ export type NativeToEditorMessage =
36
+ /**
37
+ * `reset` rebuilds the editor instance instead of replacing the running one's
38
+ * content — required when opening a *different* note, since replacing content
39
+ * carries the previous note's undo stack over.
40
+ *
41
+ * `doc` accepts HTML as well as JSON; parse (`generateJSON`) and whitelist
42
+ * (`normalizeDocument`) happen bundle-side, the only side with a DOM.
43
+ *
44
+ * `focusPosition` defaults to `"end"`. `imageDeleteGraceMs` seeds the bundle's
45
+ * image-removal bookkeeping; read once, not reactive.
46
+ */
47
+ | {
48
+ type: 'setContent';
49
+ doc: EditorDocument | string;
50
+ focus: boolean;
51
+ focusPosition?: 'start' | 'end';
52
+ reset?: boolean;
53
+ imageDeleteGraceMs?: number;
54
+ /**
55
+ * Read only on the `reset` path. Set here instead of creating editable
56
+ * and following up with `setEditable`, which would let a read-only note
57
+ * flash as editable for one frame. Defaults to `true`.
58
+ */
59
+ editable?: boolean;
60
+ }
61
+ | { type: 'setEditable'; editable: boolean }
62
+ | { type: 'setTheme'; theme: EditorTheme }
63
+ /**
64
+ * `embedded` means the editor is inside a native scroll container: it stops
65
+ * filling the viewport and starts reporting `resize`, so React Native can
66
+ * size the WebView to the document instead of nesting two scrollers.
67
+ */
68
+ | { type: 'setLayout'; embedded: boolean }
69
+ | { type: 'scrollCaretIntoView' }
70
+ | { type: 'command'; command: EditorCommand }
71
+ | { type: 'getDocument'; requestId: string }
72
+ | { type: 'runSlashCommand'; commandId: SlashCommandId }
73
+ | { type: 'dismissSlash' }
74
+ | { type: 'focus'; position: 'start' | 'end' }
75
+ | { type: 'blur' }
76
+ /**
77
+ * Inserts an image node at the current selection, once the host's
78
+ * `onImageUpload` resolved with a `src`. The bundle also starts tracking that
79
+ * `src` as "uploaded this session" — see `imageRemoved` below.
80
+ */
81
+ | { type: 'insertImage'; src: string; alt?: string | null };
82
+
83
+ export type EditorToNativeMessage =
84
+ | { type: 'ready'; protocol: number }
85
+ | { type: 'state'; state: EditorSelectionState }
86
+ /** Content changed. `revision` increases monotonically, never resets. */
87
+ | { type: 'change'; revision: number }
88
+ | {
89
+ type: 'document';
90
+ requestId: string;
91
+ revision: number;
92
+ doc: EditorDocument;
93
+ }
94
+ | { type: 'slash'; slash: SlashMenuState }
95
+ /**
96
+ * Height of the rendered document, in CSS pixels. Only meaningful when
97
+ * embedded: the WebView does not scroll there, so React Native has to size it
98
+ * to its content.
99
+ */
100
+ | { type: 'resize'; height: number }
101
+ /** A link inside the note was tapped; the native side decides what to open. */
102
+ | { type: 'linkPress'; href: string }
103
+ /**
104
+ * Backspace with the selection collapsed at the first cursor position of the
105
+ * document. `NoteEditor` uses this to hand focus back to its title field —
106
+ * the inverse of the title's own Enter handler.
107
+ */
108
+ | { type: 'backspaceAtDocStart' }
109
+ /**
110
+ * A `src` that `insertImage` added this session left the document and stayed
111
+ * out past the grace period (`imageDeleteGraceMs`).
112
+ */
113
+ | { type: 'imageRemoved'; src: string }
114
+ | { type: 'error'; message: string };
115
+
116
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
117
+ typeof value === 'object' && value !== null && !Array.isArray(value);
118
+
119
+ const isString = (value: unknown): value is string => typeof value === 'string';
120
+
121
+ /** Rejects `NaN` and the infinities, which `JSON.parse` can still produce. */
122
+ const isFiniteNumber = (value: unknown): value is number =>
123
+ typeof value === 'number' && Number.isFinite(value);
124
+
125
+ /*
126
+ * `Record<Union, …>` rather than an array so it is exhaustive: adding a variant
127
+ * to `EditorToNativeMessage` without listing it here stops compiling, instead
128
+ * of letting `isEditorMessage` reject the new type at runtime — which would
129
+ * look like the message never arrived, not like a build error.
130
+ *
131
+ * Each entry checks the fields its own variant declares, so narrowing to the
132
+ * union is backed by the payload and not by `type` alone: a `document` without
133
+ * `doc`/`requestId`, or a `ready` without `protocol`, is rejected here instead
134
+ * of blowing up at the use site.
135
+ *
136
+ * `state`, `slash` and `doc` are checked as objects rather than field by field:
137
+ * their shapes belong to `@medway-ui/notes-core` and are validated there — for
138
+ * `doc`, by the `normalizeDocument` pass the native side runs before handing a
139
+ * document to the host.
140
+ */
141
+ const EDITOR_MESSAGE_VALIDATORS: Record<
142
+ EditorToNativeMessage['type'],
143
+ (message: Record<string, unknown>) => boolean
144
+ > = {
145
+ ready: (message) => isFiniteNumber(message.protocol),
146
+ state: (message) => isRecord(message.state),
147
+ change: (message) => isFiniteNumber(message.revision),
148
+ document: (message) =>
149
+ isString(message.requestId) &&
150
+ isFiniteNumber(message.revision) &&
151
+ isRecord(message.doc),
152
+ slash: (message) => isRecord(message.slash),
153
+ resize: (message) => isFiniteNumber(message.height),
154
+ linkPress: (message) => isString(message.href),
155
+ backspaceAtDocStart: () => true,
156
+ imageRemoved: (message) => isString(message.src),
157
+ error: (message) => isString(message.message),
158
+ };
159
+
160
+ /**
161
+ * Guard for anything arriving from the WebView: checks the envelope *and* the
162
+ * required fields of the variant its `type` names. Establishes the shape, not
163
+ * the contents — a `document` payload's `doc` still goes through
164
+ * `normalizeDocument` before the native side hands it to the host.
165
+ */
166
+ export const isEditorMessage = (
167
+ value: unknown
168
+ ): value is EditorToNativeMessage => {
169
+ if (!isRecord(value)) return false;
170
+
171
+ const { type } = value;
172
+ if (typeof type !== 'string') return false;
173
+
174
+ // `hasOwnProperty`, not `in`: `in` walks the prototype chain, so a `type` of
175
+ // `"toString"` or `"constructor"` matches any plain object literal.
176
+ // `Object.hasOwn` would read better but is ES2022 and `lib` here is ES2020.
177
+ if (!Object.prototype.hasOwnProperty.call(EDITOR_MESSAGE_VALIDATORS, type)) {
178
+ return false;
179
+ }
180
+
181
+ return EDITOR_MESSAGE_VALIDATORS[type as EditorToNativeMessage['type']](
182
+ value
183
+ );
184
+ };
@@ -1,46 +1,77 @@
1
- import { useColorScheme, useUnstableNativeVariable } from "nativewind";
1
+ import { useColorScheme, useUnstableNativeVariable } from 'nativewind';
2
2
 
3
3
  /** Token sem prefixo `--color-` (ex.: `gray-foreground`, `primary-background-fill`). */
4
4
  type ThemeColorToken = string;
5
5
 
6
6
  /** Fallback quando NativeWind não resolve cadeias `var()` — light mode. */
7
7
  const LIGHT_COLOR_ALIAS_MAP: Record<string, string> = {
8
- "content-title": "primary",
9
- "content-paragraph": "gray-600",
10
- "gray-foreground": "gray-600",
11
- "primary-foreground": "primary",
12
- "secondary-foreground": "secondary-darken-3",
13
- "surface-card": "white",
14
- "gray-foreground-muted": "gray-450",
15
- "gray-stroke-muted": "gray-100",
16
- "gray-background-fill-muted": "gray-400",
17
- "gray-background-soft-muted": "gray-30",
18
- "primary-foreground-onfill": "white",
19
- "primary-background-fill": "primary",
8
+ 'content-title': 'primary',
9
+ 'content-paragraph': 'gray-600',
10
+ 'gray-foreground': 'gray-600',
11
+ 'primary-foreground': 'primary',
12
+ 'secondary-foreground': 'secondary-darken-3',
13
+ 'secondary-stroke-emphasis': 'secondary-darken-1',
14
+ 'surface-card': 'white',
15
+ 'gray-foreground-muted': 'gray-450',
16
+ 'gray-stroke-muted': 'gray-100',
17
+ 'gray-background-fill-muted': 'gray-400',
18
+ 'gray-background-soft-muted': 'gray-30',
19
+ 'primary-foreground-onfill': 'white',
20
+ 'primary-background-fill': 'primary',
21
+ 'red-foreground': 'red-darken-2',
22
+ 'go-foreground': 'go-darken-1',
20
23
  };
21
24
 
22
25
  /** Fallback — dark mode (espelha tokens.css `.dark`). */
23
26
  const DARK_COLOR_ALIAS_MAP: Record<string, string> = {
24
- "content-title": "white",
25
- "content-paragraph": "gray-150",
26
- "gray-foreground": "gray-100",
27
- "primary-foreground": "gray-100",
28
- "secondary-foreground": "secondary-lighten-1",
29
- "surface-card": "gray-850",
30
- "gray-foreground-muted": "gray-150",
31
- "gray-stroke-muted": "gray-700",
32
- "gray-background-fill-muted": "gray-300",
33
- "gray-background-soft-muted": "gray-700",
34
- "primary-foreground-onfill": "primary",
35
- "primary-background-fill": "gray-70",
27
+ 'content-title': 'white',
28
+ 'content-paragraph': 'gray-150',
29
+ 'gray-foreground': 'gray-100',
30
+ 'primary-foreground': 'gray-100',
31
+ 'secondary-foreground': 'secondary-lighten-1',
32
+ 'secondary-stroke-emphasis': 'secondary',
33
+ 'surface-card': 'gray-850',
34
+ 'gray-foreground-muted': 'gray-150',
35
+ 'gray-stroke-muted': 'gray-700',
36
+ 'gray-background-fill-muted': 'gray-300',
37
+ 'gray-background-soft-muted': 'gray-700',
38
+ 'primary-foreground-onfill': 'primary',
39
+ 'primary-background-fill': 'gray-70',
40
+ 'red-foreground': 'red-lighten-3',
41
+ 'go-foreground': 'go-lighten-5',
36
42
  };
37
43
 
44
+ /** Fallback estático quando NativeWind não resolve `var(--color-*)` encadeado. */
45
+ const THEME_COLOR_FALLBACKS: Record<string, string> = {
46
+ white: '#FFFFFF',
47
+ primary: 'rgba(0, 32, 91, 1)',
48
+ 'gray-600': 'rgba(49, 57, 73, 1)',
49
+ 'gray-450': 'rgba(90, 105, 135, 1)',
50
+ 'gray-400': 'rgba(122, 133, 151, 1)',
51
+ 'gray-100': 'rgba(231, 233, 239, 1)',
52
+ 'gray-30': 'rgba(249, 250, 251, 1)',
53
+ 'gray-150': 'rgba(206, 211, 222, 1)',
54
+ 'gray-300': 'rgba(133, 146, 173, 1)',
55
+ 'gray-700': 'rgba(33, 38, 49, 1)',
56
+ 'gray-850': 'rgba(16, 19, 24, 1)',
57
+ 'gray-70': 'rgba(240, 241, 245, 1)',
58
+ 'red-darken-2': 'rgba(182, 27, 45, 1)',
59
+ 'red-lighten-3': 'rgba(231, 131, 142, 1)',
60
+ 'go-darken-1': 'rgba(141, 22, 77, 1)',
61
+ 'go-lighten-5': 'rgba(237, 187, 192, 1)',
62
+ 'secondary-darken-1': 'rgba(7, 171, 149, 1)',
63
+ 'secondary-darken-3': 'rgba(13, 104, 92, 1)',
64
+ 'secondary-lighten-1': 'rgba(22, 208, 118, 1)',
65
+ };
66
+
67
+ const DEFAULT_COLOR = '#3B4958';
68
+
38
69
  function toCssVariable(token: ThemeColorToken): string {
39
- return token.startsWith("--") ? token : `--color-${token}`;
70
+ return token.startsWith('--') ? token : `--color-${token}`;
40
71
  }
41
72
 
42
73
  function toTokenName(token: ThemeColorToken): string {
43
- return token.startsWith("--color-") ? token.slice("--color-".length) : token;
74
+ return token.startsWith('--color-') ? token.slice('--color-'.length) : token;
44
75
  }
45
76
 
46
77
  function toResolvedCssVariable(
@@ -52,17 +83,31 @@ function toResolvedCssVariable(
52
83
  }
53
84
 
54
85
  function isResolvedColor(value: unknown): value is string {
55
- return typeof value === "string" && !value.startsWith("var(");
86
+ return typeof value === 'string' && !value.startsWith('var(');
87
+ }
88
+
89
+ function getFallbackColor(
90
+ token: ThemeColorToken,
91
+ aliasMap: Record<string, string>
92
+ ): string {
93
+ const tokenName = toTokenName(token);
94
+ const aliasToken = aliasMap[tokenName] ?? tokenName;
95
+
96
+ return (
97
+ THEME_COLOR_FALLBACKS[tokenName] ??
98
+ THEME_COLOR_FALLBACKS[aliasToken] ??
99
+ DEFAULT_COLOR
100
+ );
56
101
  }
57
102
 
58
103
  /**
59
104
  * Resolve uma cor semântica do design system (tokens.native.css) em runtime.
60
105
  * Use quando a prop `color` for obrigatória (SVG, stroke Reanimated, etc.).
61
106
  */
62
- function useThemeColor(token: ThemeColorToken): string | undefined {
107
+ function useThemeColor(token: ThemeColorToken): string {
63
108
  const { colorScheme } = useColorScheme();
64
109
  const aliasMap =
65
- colorScheme === "dark" ? DARK_COLOR_ALIAS_MAP : LIGHT_COLOR_ALIAS_MAP;
110
+ colorScheme === 'dark' ? DARK_COLOR_ALIAS_MAP : LIGHT_COLOR_ALIAS_MAP;
66
111
 
67
112
  const value = useUnstableNativeVariable(toCssVariable(token));
68
113
  const resolvedValue = useUnstableNativeVariable(
@@ -72,8 +117,8 @@ function useThemeColor(token: ThemeColorToken): string | undefined {
72
117
  if (isResolvedColor(value)) return value;
73
118
  if (isResolvedColor(resolvedValue)) return resolvedValue;
74
119
 
75
- return undefined;
120
+ return getFallbackColor(token, aliasMap);
76
121
  }
77
122
 
78
- export { useThemeColor, toCssVariable };
123
+ export { useThemeColor, toCssVariable, getFallbackColor, DEFAULT_COLOR };
79
124
  export type { ThemeColorToken };
package/src/index.ts CHANGED
@@ -67,6 +67,125 @@ export type { EmptyStateProps } from "@/components/EmptyState";
67
67
  export { PageHeader } from "@/components/PageHeader";
68
68
  export type { PageHeaderProps } from "@/components/PageHeader";
69
69
 
70
+ // Carousel
71
+ export {
72
+ Carousel,
73
+ CarouselMediaSlide,
74
+ CarouselMetricSlide,
75
+ } from "@/components/Carousel";
76
+ export type {
77
+ CarouselMediaSlideProps,
78
+ CarouselMetricSlideProps,
79
+ CarouselProps,
80
+ } from "@/components/Carousel";
81
+
82
+ // DropdownMenu
83
+ export {
84
+ DropdownMenu,
85
+ DropdownMenuContent,
86
+ DropdownMenuGroup,
87
+ DropdownMenuItem,
88
+ DropdownMenuSeparator,
89
+ DropdownMenuTrigger,
90
+ } from "@/components/DropdownMenu";
91
+ export type {
92
+ DropdownMenuContentProps,
93
+ DropdownMenuItemProps,
94
+ DropdownMenuProps,
95
+ DropdownMenuTriggerProps,
96
+ } from "@/components/DropdownMenu";
97
+
98
+ // Feed
99
+ export { Feed, FeedComposer, FeedTitle } from "@/components/Feed";
100
+ export type {
101
+ FeedComposerProps,
102
+ FeedProps,
103
+ FeedTitleProps,
104
+ } from "@/components/Feed";
105
+
106
+ // FeedNotice
107
+ export { FeedNotice } from "@/components/FeedNotice";
108
+ export type { FeedNoticeProps } from "@/components/FeedNotice";
109
+
110
+ // InvitePopover
111
+ export {
112
+ InvitePopover,
113
+ InvitePopoverCard,
114
+ InvitePopoverClose,
115
+ InvitePopoverContent,
116
+ InvitePopoverDivider,
117
+ InvitePopoverEmailRow,
118
+ InvitePopoverSendButton,
119
+ InvitePopoverShareButton,
120
+ InvitePopoverTitle,
121
+ InvitePopoverTrigger,
122
+ } from "@/components/InvitePopover";
123
+ export type {
124
+ InvitePopoverActionProps,
125
+ InvitePopoverContentProps,
126
+ InvitePopoverEmailRowProps,
127
+ InvitePopoverProps,
128
+ InvitePopoverTriggerProps,
129
+ } from "@/components/InvitePopover";
130
+
131
+ // LeaderboardCard
132
+ export {
133
+ LeaderboardCard,
134
+ LeaderboardCardHeader,
135
+ LeaderboardCardList,
136
+ } from "@/components/LeaderboardCard";
137
+ export type {
138
+ LeaderboardCardHeaderProps,
139
+ LeaderboardCardProps,
140
+ } from "@/components/LeaderboardCard";
141
+
142
+ // MemberRow
143
+ export { MemberRow, memberRowVariants, rankIndicatorVariants } from "@/components/MemberRow";
144
+ export type { MemberRowProps } from "@/components/MemberRow";
145
+
146
+ // PostForm
147
+ export {
148
+ PostForm,
149
+ PostFormCheckboxOption,
150
+ PostFormDateTimeField,
151
+ PostFormDescription,
152
+ PostFormHeader,
153
+ PostFormMediaPicker,
154
+ } from "@/components/PostForm";
155
+ export type {
156
+ PostFormCheckboxOptionProps,
157
+ PostFormDateTimeFieldProps,
158
+ PostFormDescriptionProps,
159
+ PostFormHeaderProps,
160
+ PostFormMediaPickerProps,
161
+ PostFormProps,
162
+ } from "@/components/PostForm";
163
+
164
+ // PrizeCollection
165
+ export {
166
+ PrizeCollection,
167
+ PrizeCollectionStats,
168
+ PrizeCollectionStatsItem,
169
+ PrizeItem,
170
+ PrizeRow,
171
+ PrizeShelf,
172
+ } from "@/components/PrizeCollection";
173
+ export type {
174
+ PrizeCollectionProps,
175
+ PrizeCollectionStatsItemProps,
176
+ PrizeCollectionStatsProps,
177
+ PrizeItemProps,
178
+ PrizeShelfProps,
179
+ } from "@/components/PrizeCollection";
180
+
181
+ // Text (Montserrat weight → fontFamily for RN)
182
+ export { Text, resolveMontserratFamily } from "@/components/Text";
183
+ export type { TextProps } from "@/components/Text";
184
+
185
+ // SectionHeader
186
+ export { SectionHeader } from "@/components/SectionHeader";
187
+ export type { SectionHeaderProps } from "@/components/SectionHeader";
188
+
70
189
  // Button
71
190
  export { Button, buttonVariants, buttonTextVariants } from "@/components/Button";
72
191
  export type { ButtonProps } from "@/components/Button";
@@ -107,9 +226,19 @@ export type { LabelProps } from "@/components/Label";
107
226
  export { Loading } from "@/components/Loading";
108
227
  export type { LoadingProps } from "@/components/Loading";
109
228
 
229
+ // Skeleton
230
+ export { Skeleton } from "@/components/Skeleton";
231
+ export type { SkeletonProps } from "@/components/Skeleton";
232
+
110
233
  // RadioGroup
111
- export { RadioGroup, RadioGroupItem, radioGroupVariants } from "@/components/RadioGroup";
112
- export type { RadioGroupItemProps } from "@/components/RadioGroup";
234
+ export {
235
+ RadioGroup,
236
+ RadioGroupItem,
237
+ RadioGroupList,
238
+ RadioGroupListItem,
239
+ radioGroupVariants,
240
+ } from "@/components/RadioGroup";
241
+ export type { RadioGroupItemProps, RadioGroupListProps } from "@/components/RadioGroup";
113
242
 
114
243
  // Select
115
244
  export {
@@ -134,10 +263,6 @@ export type { SelectProps, SelectItemProps, SelectSize } from "@/components/Sele
134
263
  export { Separator } from "@/components/Separator";
135
264
  export type { SeparatorProps } from "@/components/Separator";
136
265
 
137
- // Skeleton
138
- export { Skeleton } from "@/components/Skeleton";
139
- export type { SkeletonProps } from "@/components/Skeleton";
140
-
141
266
  // Switch
142
267
  export { Switch, switchVariants } from "@/components/Switch";
143
268
  export type { SwitchProps } from "@/components/Switch";
@@ -221,6 +346,22 @@ export type {
221
346
  export { Tag, tagVariants, tagTextVariants } from "@/components/Tag";
222
347
  export type { TagProps } from "@/components/Tag";
223
348
 
349
+ // Tabs
350
+ export {
351
+ Tabs,
352
+ TabsContent,
353
+ TabsList,
354
+ TabsTrigger,
355
+ tabsTriggerVariants,
356
+ } from "@/components/Tabs";
357
+ export type {
358
+ TabsContentProps,
359
+ TabsListProps,
360
+ TabsProps,
361
+ TabsTriggerProps,
362
+ TabsVariant,
363
+ } from "@/components/Tabs";
364
+
224
365
  // Toast
225
366
  export {
226
367
  Toast,
@@ -251,3 +392,9 @@ export type { ThemeColorToken } from "@/hooks/useThemeColor";
251
392
  // Theme-aware icon helper
252
393
  export { ThemedIcon } from "@/components/ThemedIcon";
253
394
  export type { ThemedIconProps } from "@/components/ThemedIcon";
395
+
396
+ // NoteEditor is deliberately absent from this barrel: it pulls in
397
+ // `react-native-webview` (an optional peer) and the 428 KB inlined editor HTML,
398
+ // which Metro does not reliably tree-shake — every consumer of this package
399
+ // would carry both. It ships as the `@medway-ui/native/note-editor` subpath
400
+ // instead. Message types alone live at `@medway-ui/native/editor/protocol`.
@@ -0,0 +1,102 @@
1
+ import React, {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useMemo,
6
+ useRef,
7
+ useState,
8
+ type ReactNode,
9
+ } from 'react';
10
+ import { StyleSheet, View } from 'react-native';
11
+
12
+ type HostOrigin = { x: number; y: number };
13
+
14
+ type OverlayHostContextValue = {
15
+ mount: (id: string, node: ReactNode) => void;
16
+ unmount: (id: string) => void;
17
+ /** Mede a origem do host na janela (para converter pageX/pageY → coords do host). */
18
+ measureHostOrigin: (callback: (origin: HostOrigin) => void) => void;
19
+ };
20
+
21
+ const OverlayHostContext = createContext<OverlayHostContextValue | null>(null);
22
+
23
+ /**
24
+ * Host de overlay no topo da árvore Medway UI.
25
+ * Usado pelo DropdownMenu porque o Portal zustand do rn-primitives
26
+ * não renderiza de forma confiável dentro da stack (RNSScreen) deste app.
27
+ */
28
+ function OverlayHostProvider({ children }: { children: ReactNode }) {
29
+ const hostRef = useRef<React.ElementRef<typeof View>>(null);
30
+ const [nodes, setNodes] = useState<Record<string, ReactNode>>({});
31
+
32
+ const mount = useCallback((id: string, node: ReactNode) => {
33
+ setNodes((prev) => {
34
+ if (prev[id] === node) {
35
+ return prev;
36
+ }
37
+ return { ...prev, [id]: node };
38
+ });
39
+ }, []);
40
+
41
+ const unmount = useCallback((id: string) => {
42
+ setNodes((prev) => {
43
+ if (!(id in prev)) {
44
+ return prev;
45
+ }
46
+ const next = { ...prev };
47
+ delete next[id];
48
+ return next;
49
+ });
50
+ }, []);
51
+
52
+ const measureHostOrigin = useCallback((callback: (origin: HostOrigin) => void) => {
53
+ const node = hostRef.current;
54
+ if (!node) {
55
+ callback({ x: 0, y: 0 });
56
+ return;
57
+ }
58
+ node.measureInWindow((x, y) => {
59
+ callback({ x, y });
60
+ });
61
+ }, []);
62
+
63
+ const value = useMemo(
64
+ () => ({ mount, unmount, measureHostOrigin }),
65
+ [mount, unmount, measureHostOrigin]
66
+ );
67
+
68
+ return (
69
+ <OverlayHostContext.Provider value={value}>
70
+ <View style={styles.root}>
71
+ {children}
72
+ <View ref={hostRef} style={styles.host} pointerEvents="box-none" collapsable={false}>
73
+ {Object.entries(nodes).map(([id, node]) => (
74
+ <React.Fragment key={id}>{node}</React.Fragment>
75
+ ))}
76
+ </View>
77
+ </View>
78
+ </OverlayHostContext.Provider>
79
+ );
80
+ }
81
+
82
+ function useOverlayHost() {
83
+ const ctx = useContext(OverlayHostContext);
84
+ if (!ctx) {
85
+ throw new Error('useOverlayHost must be used within OverlayHostProvider (MedwayUiProvider)');
86
+ }
87
+ return ctx;
88
+ }
89
+
90
+ const styles = StyleSheet.create({
91
+ root: {
92
+ flex: 1,
93
+ },
94
+ host: {
95
+ ...StyleSheet.absoluteFill,
96
+ zIndex: 99999,
97
+ elevation: 99999,
98
+ },
99
+ });
100
+
101
+ export { OverlayHostProvider, useOverlayHost };
102
+ export type { HostOrigin };
@@ -0,0 +1,21 @@
1
+ import type { TextStyle } from 'react-native';
2
+
3
+ /** Montserrat files registered by the host app (ex.: App.js useFonts). */
4
+ export const fontFamilyRegular = 'MontserratRegular' as const;
5
+ export const fontFamilyMedium = 'MontserratMedium' as const;
6
+ export const fontFamilySemibold = 'MontserratSemiBold' as const;
7
+
8
+ /** Figma body-sm/medium */
9
+ export const bodySmMediumStyle: TextStyle = {
10
+ fontFamily: fontFamilyMedium,
11
+ };
12
+
13
+ /** Figma body-xs/medium */
14
+ export const bodyXsMediumStyle: TextStyle = {
15
+ fontFamily: fontFamilyMedium,
16
+ };
17
+
18
+ /** Figma body-sm/semibold */
19
+ export const bodySmSemiboldStyle: TextStyle = {
20
+ fontFamily: fontFamilySemibold,
21
+ };
@@ -1 +1,3 @@
1
1
  /// <reference types="nativewind/types" />
2
+
3
+ declare module "*.css";