@medway-ui/native 1.1.1 → 1.1.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.
package/README.md CHANGED
@@ -1,25 +1,35 @@
1
1
  # @medway-ui/native
2
2
 
3
- React Native components for iOS and Android. Same API as `@medway-ui/core`, built on top of NativeWind + @rn-primitives.
3
+ React Native components for iOS and Android (Expo). Same visual API as `@medway-ui/core`, built on NativeWind + `@rn-primitives`.
4
+
5
+ **Minimum recommended version for npm brownfield setup:** `1.1.2+`
6
+
7
+ ---
4
8
 
5
9
  ## Installation
6
10
 
7
11
  ```bash
8
- npx expo install @medway-ui/native nativewind react-native-reanimated react-native-svg @react-native-community/datetimepicker
9
- ```
12
+ npm install @medway-ui/native @medway-ui/core @medway-ui/icons \
13
+ class-variance-authority clsx tailwind-merge nativewind \
14
+ react-native-reanimated react-native-svg
10
15
 
11
- ## Setup (Expo greenfield)
16
+ # Expo (aligns versions with your SDK)
17
+ npx expo install react-native-reanimated react-native-svg
18
+ ```
12
19
 
13
- Para apps **novos** (100% NativeWind):
20
+ Brownfield apps that already use `babel-plugin-module-resolver` with `@/` aliases pointing at the app `src/` also need:
14
21
 
15
- ```js
16
- // babel.config.js — presets globais
17
- ["babel-preset-expo", { jsxImportSource: "nativewind" }], "nativewind/babel"
22
+ ```bash
23
+ npm install -D babel-plugin-module-resolver
18
24
  ```
19
25
 
20
- ## Setup (Expo brownfield — convive com Restyle / UI legado)
26
+ ---
27
+
28
+ ## Brownfield setup (Expo + legacy UI / Restyle)
21
29
 
22
- Apps existentes: NativeWind **somente** em `@medway-ui/native` + telas em `src/pages/medway-ui/`.
30
+ NativeWind applies **only** to `@medway-ui/native` + screens/components that use the library (e.g. `src/pages/medway-ui/`, partially migrated features).
31
+
32
+ Reference implementation: **test-prep-app** (`metro.config.js`, `babel.config.js`, `tailwind.config.js`, `src/pages/medway-ui/`).
23
33
 
24
34
  ### 1. `metro.config.js`
25
35
 
@@ -29,10 +39,18 @@ const { withMedwayUi } = require("@medway-ui/native/metro");
29
39
 
30
40
  module.exports = withMedwayUi(getDefaultConfig(__dirname), {
31
41
  projectRoot: __dirname,
32
- linkedMedwayUiRoot: "../medway-ui", // omitir se usar npm, não file:
42
+ // linkedMedwayUiRoot: auto-detected (npm or monorepo)
33
43
  });
34
44
  ```
35
45
 
46
+ | Scenario | `linkedMedwayUiRoot` |
47
+ | --- | --- |
48
+ | npm packages (`npm install`) | **Omit** — auto-detects `node_modules/@medway-ui` |
49
+ | Monorepo with local clone | `"../medway-ui"` (optional; also auto-detected) |
50
+ | Symlink `file:../medway-ui/native` | Auto-detected as monorepo |
51
+
52
+ > Do **not** set `resolveNativeSource: false` — the compiled lib still references internal `@/` aliases. The default (`true`) resolves to `native/src` and works with npm.
53
+
36
54
  ### 2. `tailwind.config.js`
37
55
 
38
56
  ```js
@@ -40,77 +58,193 @@ const { createMedwayUiTailwindConfig } = require("@medway-ui/native/tailwind");
40
58
 
41
59
  module.exports = createMedwayUiTailwindConfig({
42
60
  projectRoot: __dirname,
43
- content: ["./src/pages/medway-ui/**/*.{ts,tsx}"],
61
+ content: [
62
+ "./src/pages/medway-ui/**/*.{ts,tsx}",
63
+ // add extra folders that use className + @medway-ui/native:
64
+ // "./src/pages/MyFeature/**/*.{ts,tsx}",
65
+ ],
44
66
  });
45
67
  ```
46
68
 
69
+ `createMedwayUiTailwindConfig` already includes globs for `node_modules/@medway-ui/native/src` and loads tokens from `@medway-ui/core` (Node 22+ compatible).
70
+
47
71
  ### 3. `babel.config.js`
48
72
 
73
+ The app default preset must **not** enable NativeWind globally. Use `createBrownfieldOverride` to scope NativeWind to the library + migrated screens folder.
74
+
75
+ If the app defines `@/` aliases for `./src/*` via `babel-plugin-module-resolver`, you **must** redirect `@/` imports from `@medway-ui/native` files to the library source — otherwise imports like `@/components/Badge` resolve to the app `src/` and break the bundle.
76
+
49
77
  ```js
50
- const { createBrownfieldOverride } = require("@medway-ui/native/babel-brownfield");
78
+ const {
79
+ createBrownfieldOverride,
80
+ createMedwayUiAliasResolver,
81
+ } = require("@medway-ui/native/babel-brownfield");
82
+
83
+ const resolveMedwayUiAlias = createMedwayUiAliasResolver(__dirname);
51
84
 
52
85
  module.exports = function (api) {
53
86
  api.cache(true);
87
+
88
+ const appPlugins = [
89
+ [
90
+ "module-resolver",
91
+ {
92
+ root: ["."],
93
+ extensions: [".ios.ts", ".android.ts", ".ts", ".tsx", ".js", ".jsx", ".json"],
94
+ alias: {
95
+ "@/components": "./src/components",
96
+ "@/pages": "./src/pages",
97
+ // ... other app aliases
98
+ },
99
+ resolvePath(sourcePath, currentFile) {
100
+ return resolveMedwayUiAlias(sourcePath, currentFile);
101
+ },
102
+ },
103
+ ],
104
+ // other app plugins (no reanimated here)
105
+ ];
106
+
54
107
  return {
55
108
  presets: ["babel-preset-expo"],
56
- plugins: [/* plugins do app */],
57
- overrides: [createBrownfieldOverride({ plugins: [/* mesmos plugins */] })],
109
+ plugins: appPlugins,
110
+ overrides: [
111
+ createBrownfieldOverride({
112
+ projectRoot: __dirname,
113
+ appPagesDir: "medway-ui", // src/pages subfolder with NativeWind screens
114
+ plugins: [
115
+ // repeat any plugins needed in the medway-ui scope
116
+ "react-native-reanimated/plugin", // always last
117
+ ],
118
+ }),
119
+ ],
58
120
  };
59
121
  };
60
122
  ```
61
123
 
62
- ### 4. Navigator automático (sem registrar tela a tela)
124
+ Apps **without** `@/` Babel aliases do not need `createMedwayUiAliasResolver`.
63
125
 
64
- `src/pages/medway-ui/navigator.tsx` — **criar uma vez, não editar depois**:
126
+ ### 4. Provider + CSS
127
+
128
+ Wrap the subtree that uses `@medway-ui/native` with `MedwayUiProvider` and import CSS once:
65
129
 
66
130
  ```tsx
67
- import { createMedwayUiNavigator } from "@medway-ui/native/setup";
131
+ import { MedwayUiProvider } from "@medway-ui/native/setup";
132
+ import "@medway-ui/native/setup/global.css";
133
+
134
+ function MyScreen() {
135
+ return (
136
+ <MedwayUiProvider theme="system">
137
+ {/* Button, CardNews, etc. */}
138
+ </MedwayUiProvider>
139
+ );
140
+ }
141
+ ```
142
+
143
+ **Dark mode:** pass `colorScheme="light" | "dark"` when the app already resolves theme (Redux, Zustand, etc.):
144
+
145
+ ```tsx
146
+ <MedwayUiProvider colorScheme={appColorScheme}>
147
+ {children}
148
+ </MedwayUiProvider>
149
+ ```
150
+
151
+ `MedwayUiProvider` includes `PortalHost` (required for Select, Dialog, Popover, Tooltip).
68
152
 
69
- export const { MedwayUiNavigator } = createMedwayUiNavigator(
153
+ > Do **not** import `@medway-ui/core/tokens.css` directly — incompatible with NativeWind v4. Tokens come from `setup/global.css` + `setup/tokens.native.css`.
154
+
155
+ ### 5. Auto navigator (catalog / isolated screens)
156
+
157
+ Create once `src/pages/medway-ui/navigator.tsx`:
158
+
159
+ ```tsx
160
+ import { createMedwayUiNavigator, MedwayUiProvider } from "@medway-ui/native/setup";
161
+ import "@medway-ui/native/setup/global.css";
162
+
163
+ const { MedwayUiNavigator } = createMedwayUiNavigator(
70
164
  require.context(".", true, /^\.\/[^/]+\/index\.tsx$/),
71
165
  );
166
+
167
+ function MedwayUiRoot() {
168
+ return (
169
+ <MedwayUiProvider theme="system">
170
+ <MedwayUiNavigator />
171
+ </MedwayUiProvider>
172
+ );
173
+ }
174
+
175
+ export { MedwayUiRoot, MedwayUiNavigator };
72
176
  ```
73
177
 
74
- Uma rota no app: `<Stack.Screen name="MedwayUi" component={MedwayUiNavigator} />`
178
+ Register in the app stack:
75
179
 
76
- Nova tela = nova pasta `src/pages/medway-ui/MinhaTela/index.tsx` + `navigate("MedwayUi", { screen: "MinhaTela" })`.
180
+ ```tsx
181
+ <Stack.Screen name="MedwayUi" component={MedwayUiRoot} options={{ headerShown: false }} />
182
+ ```
183
+
184
+ New screen = new folder `src/pages/medway-ui/MyScreen/index.tsx` — no manual route registration.
77
185
 
78
- CSS/tokens vêm de `@medway-ui/native/setup/global.css` — **não** importe `@medway-ui/core/tokens.css` diretamente (incompatível com NativeWind v3).
186
+ ### 6. Using components in existing screens (Restyle + Medway UI mix)
187
+
188
+ Pattern used in Content Hub and partially migrated features:
189
+
190
+ ```tsx
191
+ import { Button, CardNews } from "@medway-ui/native";
192
+ import { MedwayUiProvider } from "@medway-ui/native/setup";
193
+ import "@medway-ui/native/setup/global.css";
194
+
195
+ function ContentHubScreen() {
196
+ return (
197
+ <Box> {/* Restyle */}
198
+ <MedwayUiProvider colorScheme={colorScheme}>
199
+ <CardNews>{/* ... */}</CardNews>
200
+ </MedwayUiProvider>
201
+ </Box>
202
+ );
203
+ }
204
+ ```
205
+
206
+ Add the feature folder to `content` in `tailwind.config.js`.
79
207
 
80
208
  ---
81
209
 
82
- ## Setup (Expo greenfield — legado)
210
+ ## Monorepo development (`file:../medway-ui/native`)
83
211
 
84
- ### 1. `tailwind.config.js`
212
+ ```json
213
+ {
214
+ "@medway-ui/core": "file:../medway-ui",
215
+ "@medway-ui/native": "file:../medway-ui/native"
216
+ }
217
+ ```
85
218
 
86
- ```js
87
- const { colors, fontFamily, fontSize, fontWeight, lineHeight, borderRadius, boxShadow } =
88
- require("@medway-ui/core/tokens");
219
+ Metro and Babel auto-detect the local clone. After library changes:
89
220
 
90
- module.exports = {
91
- content: [
92
- "./app/**/*.{ts,tsx}",
93
- "./components/**/*.{ts,tsx}",
94
- "./node_modules/@medway-ui/native/src/**/*.{ts,tsx}",
95
- ],
96
- presets: [require("nativewind/preset")],
97
- theme: {
98
- extend: {
99
- colors,
100
- fontFamily,
101
- fontSize,
102
- fontWeight,
103
- lineHeight,
104
- borderRadius,
105
- boxShadow,
106
- },
107
- },
108
- };
221
+ ```bash
222
+ cd ../medway-ui/native && npm run build:lib # if testing the compiled lib
109
223
  ```
110
224
 
111
- ### 2. `babel.config.js`
225
+ Restart Metro with a clean cache: `npx expo start --clear`.
226
+
227
+ ---
228
+
229
+ ## Troubleshooting
230
+
231
+ | Error | Cause | Fix |
232
+ | --- | --- | --- |
233
+ | `Unable to resolve .../src/components/Badge` | App `@/` alias overrides lib `@/` | Add `createMedwayUiAliasResolver` in Babel |
234
+ | `Unable to resolve @/lib/medwayIcons` | Bug in `@medway-ui/native@1.1.1` | Upgrade to `1.1.2+` |
235
+ | `Unable to resolve ../../../../../src/...` from `lib/module/` | `resolveNativeSource: false` | Remove the option (use default `true`) |
236
+ | Tokens / tailwind fail to load (Node 22+) | `require` of `.ts` inside `node_modules` | Use `@medway-ui/native@1.1.2+` (`createMedwayUiTailwindConfig` uses jiti fallback) |
237
+ | NativeWind styles not applied | File outside brownfield scope | Move to `src/pages/medway-ui/` or add a tailwind glob |
238
+ | Select/Dialog not opening | Missing `PortalHost` | Use `MedwayUiProvider` |
239
+
240
+ ---
241
+
242
+ ## Greenfield setup (100% NativeWind app)
243
+
244
+ For new apps without legacy UI:
112
245
 
113
246
  ```js
247
+ // babel.config.js
114
248
  module.exports = function (api) {
115
249
  api.cache(true);
116
250
  return {
@@ -118,42 +252,38 @@ module.exports = function (api) {
118
252
  ["babel-preset-expo", { jsxImportSource: "nativewind" }],
119
253
  "nativewind/babel",
120
254
  ],
255
+ plugins: ["react-native-reanimated/plugin"],
121
256
  };
122
257
  };
123
258
  ```
124
259
 
125
- ### 3. `metro.config.js`
126
-
127
260
  ```js
261
+ // metro.config.js
128
262
  const { getDefaultConfig } = require("expo/metro-config");
129
- const { withNativeWind } = require("nativewind/metro");
130
-
131
- const config = getDefaultConfig(__dirname);
132
-
133
- module.exports = withNativeWind(config, { input: "./global.css" });
134
- ```
135
-
136
- ### 4. `global.css`
263
+ const { withMedwayUi } = require("@medway-ui/native/metro");
137
264
 
138
- ```css
139
- @tailwind base;
140
- @tailwind components;
141
- @tailwind utilities;
265
+ module.exports = withMedwayUi(getDefaultConfig(__dirname), {
266
+ projectRoot: __dirname,
267
+ });
142
268
  ```
143
269
 
144
- ### 5. Import tokens CSS (design tokens as CSS vars)
145
-
146
- In your root `_layout.tsx`:
270
+ Import `@medway-ui/native/setup/global.css` in the root layout.
147
271
 
148
- ```tsx
149
- import "@medway-ui/core/tokens.css";
150
- import "./global.css";
151
- ```
272
+ ---
152
273
 
153
274
  ## Usage
154
275
 
155
276
  ```tsx
156
- import { Button, Input, Alert, Badge } from "@medway-ui/native";
277
+ import {
278
+ Alert,
279
+ AlertContainer,
280
+ AlertContent,
281
+ AlertTitle,
282
+ AlertDescription,
283
+ Badge,
284
+ Button,
285
+ Input,
286
+ } from "@medway-ui/native";
157
287
 
158
288
  export default function Screen() {
159
289
  return (
@@ -168,15 +298,16 @@ export default function Screen() {
168
298
  size="lg"
169
299
  value={email}
170
300
  onChangeText={setEmail}
301
+ placeholder="email@example.com"
171
302
  />
172
303
 
173
304
  <Alert variant="success" onClose={() => {}}>
174
- <Alert.Container>
175
- <Alert.Content>
176
- <Alert.Title>Concluído</Alert.Title>
177
- <Alert.Description>Operação realizada com sucesso.</Alert.Description>
178
- </Alert.Content>
179
- </Alert.Container>
305
+ <AlertContainer>
306
+ <AlertContent>
307
+ <AlertTitle>Done</AlertTitle>
308
+ <AlertDescription>Operation completed successfully.</AlertDescription>
309
+ </AlertContent>
310
+ </AlertContainer>
180
311
  </Alert>
181
312
 
182
313
  <Badge variant="regular" size="md">9+</Badge>
@@ -187,40 +318,42 @@ export default function Screen() {
187
318
 
188
319
  ## Icons
189
320
 
190
- The `@medway-ui/icons` package already includes a native entry point:
321
+ The `@medway-ui/icons` package includes a native entry point:
191
322
 
192
323
  ```tsx
193
324
  import { Check, CloseMd } from "@medway-ui/icons/native";
194
325
  ```
195
326
 
196
- ## Components — Sprint Status
327
+ ---
328
+
329
+ ## Components — status
197
330
 
198
331
  | Component | Status | Notes |
199
- |---|---|---|
200
- | Button | ✅ Sprint 1 | Full variants, loading, badge, alert dot |
201
- | Input | ✅ Sprint 1 | text, email, password, numeric, floating label |
202
- | Alert | ✅ Sprint 1 | Full sub-components, all variants |
203
- | Badge | ✅ Sprint 1 | All variants, icon-only detection |
204
- | Loading | ✅ Sprint 1 | Reanimated spin animation |
205
- | Checkbox | ✅ Sprint 2 | @rn-primitives/checkbox |
206
- | RadioGroup | ✅ Sprint 2 | @rn-primitives/radio-group |
207
- | Switch | ✅ Sprint 2 | @rn-primitives/switch + Reanimated |
208
- | Select | ✅ Sprint 2 | @rn-primitives/select |
209
- | Label | ✅ Sprint 2 | Pressable + Text |
210
- | Separator | ✅ Sprint 2 | View 1px |
211
- | Dialog | ✅ Sprint 3 | @rn-primitives/dialog |
212
- | AlertDialog | ✅ Sprint 3 | @rn-primitives/alert-dialog |
213
- | Sheet | ✅ Sprint 3 | @rn-primitives/dialog (side-aware layout) |
214
- | Tag | ✅ Sprint 3 | View + Text, 16 variants |
215
- | Toast | ✅ Sprint 3 | Reanimated slide-in, useToast hook |
216
- | Tooltip | ✅ Sprint 3 | @rn-primitives/tooltip |
217
- | Popover | ✅ Sprint 3 | @rn-primitives/popover |
218
- | Tabs | 🔜 Sprint 4 | @rn-primitives/tabs |
219
- | Progress | 🔜 Sprint 4 | @rn-primitives/progress |
220
- | ProgressCircle | 🔜 Sprint 4 | react-native-svg |
221
- | Skeleton | ✅ Sprint 5 | Reanimated shimmer |
222
- | Avatar | ✅ Sprint 5 | @rn-primitives/avatar |
223
- | FallbackImage | ✅ Sprint 5 | react-native-svg |
224
- | CardNews | ✅ Sprint 5 | Card composable for editorial/clinical updates |
225
- | Cards | 🔜 Sprint 5 | CardModule, CardLesson, etc. |
226
- | DatePicker | 🔜 Sprint 6 | @react-native-community/datetimepicker + Modal |
332
+ | --- | --- | --- |
333
+ | Button | ✅ | Variants, loading, badge, alert dot |
334
+ | Input | ✅ | text, email, password, numeric |
335
+ | Alert | ✅ | Sub-components, all variants |
336
+ | Badge | ✅ | Variants, icon-only |
337
+ | Loading | ✅ | Reanimated spin |
338
+ | Checkbox | ✅ | @rn-primitives/checkbox |
339
+ | RadioGroup | ✅ | @rn-primitives/radio-group |
340
+ | Switch | ✅ | @rn-primitives/switch |
341
+ | Select | ✅ | @rn-primitives/select |
342
+ | Label | ✅ | Pressable + Text |
343
+ | Separator | ✅ | View 1px |
344
+ | Dialog | ✅ | @rn-primitives/dialog |
345
+ | AlertDialog | ✅ | @rn-primitives/alert-dialog |
346
+ | Sheet | ✅ | @rn-primitives/dialog (side-aware) |
347
+ | Tag | ✅ | 16+ variants |
348
+ | Toast | ✅ | useToast + toast() |
349
+ | Tooltip | ✅ | @rn-primitives/tooltip |
350
+ | Popover | ✅ | @rn-primitives/popover |
351
+ | Skeleton | ✅ | Shimmer |
352
+ | Avatar | ✅ | @rn-primitives/avatar |
353
+ | FallbackImage | ✅ | react-native-svg |
354
+ | CardNews | ✅ | Composable card (Content Hub) |
355
+ | Tabs | 🔜 | @rn-primitives/tabs |
356
+ | Progress | 🔜 | @rn-primitives/progress |
357
+ | ProgressCircle | 🔜 | react-native-svg |
358
+ | Cards | 🔜 | CardModule, CardLesson, etc. |
359
+ | DatePicker | 🔜 | @react-native-community/datetimepicker |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@medway-ui/native",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Medway UI components for React Native (iOS/Android via Expo)",
5
5
  "author": "Alekcarvalho",
6
6
  "license": "MIT",
@@ -4,7 +4,6 @@ import { PortalHost } from '@rn-primitives/portal';
4
4
  import { useColorScheme } from 'nativewind';
5
5
 
6
6
  import './global.css';
7
- import '@/lib/medwayIcons';
8
7
 
9
8
  type MedwayUiTheme = 'light' | 'dark' | 'system';
10
9
 
@@ -1,3 +1,4 @@
1
+ const fs = require('fs');
1
2
  const path = require('path');
2
3
 
3
4
  const DEFAULT_APP_PAGES_DIR = 'medway-ui';
@@ -15,6 +16,72 @@ function createNativeWindScopeTest(options = {}) {
15
16
  };
16
17
  }
17
18
 
19
+ function createMedwayUiSourceTest() {
20
+ return function isMedwayUiSourceFile(currentFile) {
21
+ return (
22
+ currentFile.includes(`${path.sep}@medway-ui${path.sep}native${path.sep}`) ||
23
+ currentFile.includes(`${path.sep}medway-ui${path.sep}native${path.sep}`)
24
+ );
25
+ };
26
+ }
27
+
28
+ function resolveMedwayUiNativeSrc(projectRoot) {
29
+ const npmNativeSrc = path.join(projectRoot, 'node_modules/@medway-ui/native/src');
30
+
31
+ if (fs.existsSync(npmNativeSrc) && !isNativePackageSymlinkedToMonorepo(projectRoot)) {
32
+ return npmNativeSrc;
33
+ }
34
+
35
+ const monorepoNativeSrc = path.resolve(projectRoot, '../medway-ui/native/src');
36
+ if (fs.existsSync(monorepoNativeSrc)) {
37
+ return monorepoNativeSrc;
38
+ }
39
+
40
+ return npmNativeSrc;
41
+ }
42
+
43
+ function isNativePackageSymlinkedToMonorepo(projectRoot) {
44
+ const nativePath = path.join(projectRoot, 'node_modules/@medway-ui/native');
45
+
46
+ if (!fs.existsSync(nativePath)) {
47
+ return false;
48
+ }
49
+
50
+ try {
51
+ const nativeRealPath = fs.realpathSync(nativePath);
52
+ const monorepoNativePath = fs.realpathSync(
53
+ path.resolve(projectRoot, '../medway-ui/native'),
54
+ );
55
+
56
+ return nativeRealPath === monorepoNativePath;
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+
62
+ function createMedwayUiAliasResolver(projectRoot) {
63
+ const medwayUiNativeSrc = resolveMedwayUiNativeSrc(projectRoot);
64
+ const isMedwayUiSourceFile = createMedwayUiSourceTest();
65
+
66
+ return function resolveMedwayUiAlias(sourcePath, currentFile) {
67
+ if (!isMedwayUiSourceFile(currentFile) || !sourcePath.startsWith('@/')) {
68
+ return undefined;
69
+ }
70
+
71
+ const relativePath = sourcePath.slice(2);
72
+ const extensions = ['', '.tsx', '.ts', '.jsx', '.js', '/index.tsx', '/index.ts'];
73
+
74
+ for (const ext of extensions) {
75
+ const candidate = path.join(medwayUiNativeSrc, `${relativePath}${ext}`);
76
+ if (fs.existsSync(candidate)) {
77
+ return candidate;
78
+ }
79
+ }
80
+
81
+ return undefined;
82
+ };
83
+ }
84
+
18
85
  function loadCssInteropBabelPlugin(projectRoot) {
19
86
  return require(
20
87
  require.resolve('react-native-css-interop/dist/babel-plugin', {
@@ -46,5 +113,8 @@ function createBrownfieldOverride(options = {}) {
46
113
  module.exports = {
47
114
  createBrownfieldOverride,
48
115
  createNativeWindScopeTest,
116
+ createMedwayUiAliasResolver,
117
+ createMedwayUiSourceTest,
118
+ resolveMedwayUiNativeSrc,
49
119
  DEFAULT_APP_PAGES_DIR,
50
120
  };
package/setup/metro.js CHANGED
@@ -29,7 +29,9 @@ function resolveMedwayAlias(moduleName, medwayUiNativeSrc, medwayUiSharedSrc) {
29
29
 
30
30
  return (
31
31
  tryResolveMedwayModule(path.join(medwayUiNativeSrc, relativePath)) ??
32
- tryResolveMedwayModule(path.join(medwayUiSharedSrc, relativePath))
32
+ (medwayUiSharedSrc
33
+ ? tryResolveMedwayModule(path.join(medwayUiSharedSrc, relativePath))
34
+ : null)
33
35
  );
34
36
  }
35
37
 
@@ -92,43 +94,112 @@ function resolveBareModule(moduleName, searchRoots) {
92
94
  return null;
93
95
  }
94
96
 
97
+ /**
98
+ * Detecta monorepo file: (`../medway-ui`) vs pacotes npm (`node_modules/@medway-ui`).
99
+ * Prioriza npm quando o pacote não é symlink para o clone local.
100
+ */
101
+ function isNativePackageSymlinkedToMonorepo(projectRoot) {
102
+ const nativePath = path.join(projectRoot, 'node_modules/@medway-ui/native');
103
+
104
+ if (!fs.existsSync(nativePath)) {
105
+ return false;
106
+ }
107
+
108
+ try {
109
+ const nativeRealPath = fs.realpathSync(nativePath);
110
+ const monorepoNativePath = fs.realpathSync(
111
+ path.resolve(projectRoot, '../medway-ui/native'),
112
+ );
113
+
114
+ return nativeRealPath === monorepoNativePath;
115
+ } catch {
116
+ return false;
117
+ }
118
+ }
119
+
120
+ function detectLinkedMedwayUiRoot(projectRoot) {
121
+ const npmScopedRoot = 'node_modules/@medway-ui';
122
+ const npmNativeSrc = path.resolve(projectRoot, npmScopedRoot, 'native/src');
123
+
124
+ if (fs.existsSync(npmNativeSrc) && !isNativePackageSymlinkedToMonorepo(projectRoot)) {
125
+ return npmScopedRoot;
126
+ }
127
+
128
+ const monorepoRoot = '../medway-ui';
129
+ const monorepoNativeSrc = path.resolve(projectRoot, monorepoRoot, 'native/src');
130
+
131
+ if (fs.existsSync(monorepoNativeSrc)) {
132
+ return monorepoRoot;
133
+ }
134
+
135
+ if (fs.existsSync(npmNativeSrc)) {
136
+ return npmScopedRoot;
137
+ }
138
+
139
+ return monorepoRoot;
140
+ }
141
+
142
+ function resolveMedwayUiPaths(projectRoot, linkedMedwayUiRoot, nativePackageRoot) {
143
+ const medwayUiRoot = path.resolve(projectRoot, linkedMedwayUiRoot);
144
+ const linkedNativeSrc = path.resolve(medwayUiRoot, 'native/src');
145
+ const npmNativeSrc = path.join(nativePackageRoot, 'src');
146
+ const isMonorepoLink = fs.existsSync(linkedNativeSrc);
147
+
148
+ return {
149
+ medwayUiRoot: isMonorepoLink ? medwayUiRoot : nativePackageRoot,
150
+ medwayUiNativeRoot: isMonorepoLink
151
+ ? path.resolve(medwayUiRoot, 'native')
152
+ : nativePackageRoot,
153
+ medwayUiNativeSrc: isMonorepoLink ? linkedNativeSrc : npmNativeSrc,
154
+ medwayUiSharedSrc: isMonorepoLink ? path.resolve(medwayUiRoot, 'src') : null,
155
+ isMonorepoLink,
156
+ };
157
+ }
158
+
95
159
  /**
96
160
  * Metro + NativeWind para @medway-ui/native (monorepo file: ou npm).
97
161
  *
98
162
  * @param {import('expo/metro-config').MetroConfig} config
99
163
  * @param {object} [options]
100
164
  * @param {string} options.projectRoot
101
- * @param {string} [options.linkedMedwayUiRoot='../medway-ui'] - clone local no monorepo
165
+ * @param {string} [options.linkedMedwayUiRoot] - auto-detecta monorepo ou npm quando omitido
102
166
  * @param {boolean} [options.resolveNativeSource=true] - aponta @medway-ui/native para src/
103
167
  */
104
168
  function withMedwayUi(config, options = {}) {
105
169
  const projectRoot = options.projectRoot ?? process.cwd();
106
- const linkedMedwayUiRoot = options.linkedMedwayUiRoot ?? '../medway-ui';
170
+ const linkedMedwayUiRoot =
171
+ options.linkedMedwayUiRoot ?? detectLinkedMedwayUiRoot(projectRoot);
107
172
  const resolveNativeSource = options.resolveNativeSource ?? true;
108
173
 
109
174
  const nativePackageRoot = path.resolve(__dirname, '..');
110
175
  const globalCssInput = path.join(nativePackageRoot, 'setup/global.css');
111
176
 
112
- const medwayUiRoot = path.resolve(projectRoot, linkedMedwayUiRoot);
113
- const medwayUiNativeRoot = path.resolve(medwayUiRoot, 'native');
114
- const medwayUiNativeSrc = path.resolve(medwayUiNativeRoot, 'src');
115
- const medwayUiSharedSrc = path.resolve(medwayUiRoot, 'src');
116
- const escapedMedwayUiRoot = medwayUiRoot.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
117
-
118
- config.watchFolders = [...(config.watchFolders ?? []), medwayUiRoot];
119
-
120
- const existingBlockList = config.resolver.blockList;
121
- const blockListArray = Array.isArray(existingBlockList)
122
- ? existingBlockList
123
- : existingBlockList
124
- ? [existingBlockList]
125
- : [];
126
-
127
- config.resolver.blockList = [
128
- ...blockListArray,
129
- new RegExp(`${escapedMedwayUiRoot}[\\\\/]native[\\\\/]node_modules[\\\\/].*`),
130
- new RegExp(`${escapedMedwayUiRoot}[\\\\/]node_modules[\\\\/].*`),
131
- ];
177
+ const {
178
+ medwayUiRoot,
179
+ medwayUiNativeRoot,
180
+ medwayUiNativeSrc,
181
+ medwayUiSharedSrc,
182
+ isMonorepoLink,
183
+ } = resolveMedwayUiPaths(projectRoot, linkedMedwayUiRoot, nativePackageRoot);
184
+
185
+ if (isMonorepoLink) {
186
+ const escapedMedwayUiRoot = medwayUiRoot.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
187
+
188
+ config.watchFolders = [...(config.watchFolders ?? []), medwayUiRoot];
189
+
190
+ const existingBlockList = config.resolver.blockList;
191
+ const blockListArray = Array.isArray(existingBlockList)
192
+ ? existingBlockList
193
+ : existingBlockList
194
+ ? [existingBlockList]
195
+ : [];
196
+
197
+ config.resolver.blockList = [
198
+ ...blockListArray,
199
+ new RegExp(`${escapedMedwayUiRoot}[\\\\/]native[\\\\/]node_modules[\\\\/].*`),
200
+ new RegExp(`${escapedMedwayUiRoot}[\\\\/]node_modules[\\\\/].*`),
201
+ ];
202
+ }
132
203
 
133
204
  function isMedwayUiOrigin(originModulePath) {
134
205
  return (
@@ -192,4 +263,4 @@ function withMedwayUi(config, options = {}) {
192
263
  });
193
264
  }
194
265
 
195
- module.exports = { withMedwayUi };
266
+ module.exports = { withMedwayUi, detectLinkedMedwayUiRoot, resolveMedwayUiPaths };
package/setup/tailwind.js CHANGED
@@ -1,7 +1,22 @@
1
+ const fs = require('fs');
1
2
  const path = require('path');
2
3
 
3
4
  function loadDesignTokens(projectRoot) {
4
- return require(require.resolve('@medway-ui/core/tokens', { paths: [projectRoot] }));
5
+ const tokensPath = require.resolve('@medway-ui/core/tokens', {
6
+ paths: [projectRoot],
7
+ });
8
+
9
+ try {
10
+ return require(tokensPath);
11
+ } catch (error) {
12
+ // Node 22+ não permite require de .ts dentro de node_modules.
13
+ if (error?.code !== 'ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING') {
14
+ throw error;
15
+ }
16
+
17
+ const jiti = require('jiti')(path.join(projectRoot, 'tailwind.config.js'));
18
+ return jiti(tokensPath);
19
+ }
5
20
  }
6
21
 
7
22
  function resolveNativePackageRoot() {
@@ -30,7 +45,6 @@ function createMedwayUiTailwindConfig(options = {}) {
30
45
  } = loadDesignTokens(projectRoot);
31
46
 
32
47
  const nativeRoot = resolveNativePackageRoot();
33
- const nativeSrc = path.join(nativeRoot, 'src');
34
48
 
35
49
  const libContent = [
36
50
  path.join(nativeRoot, 'src/**/*.{js,jsx,ts,tsx}'),
@@ -38,9 +52,15 @@ function createMedwayUiTailwindConfig(options = {}) {
38
52
  ];
39
53
 
40
54
  if (linkedMedwayUiRoot) {
41
- libContent.push(
42
- path.resolve(projectRoot, linkedMedwayUiRoot, 'native/src/**/*.{ts,tsx}'),
55
+ const linkedNativeSrc = path.resolve(
56
+ projectRoot,
57
+ linkedMedwayUiRoot,
58
+ 'native/src',
43
59
  );
60
+
61
+ if (fs.existsSync(linkedNativeSrc)) {
62
+ libContent.push(path.join(linkedNativeSrc, '**/*.{ts,tsx}'));
63
+ }
44
64
  }
45
65
 
46
66
  return {