@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 +238 -105
- package/package.json +1 -1
- package/setup/MedwayUiProvider.tsx +0 -1
- package/setup/babel-brownfield.js +70 -0
- package/setup/metro.js +95 -24
- package/setup/tailwind.js +24 -4
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
|
|
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
|
-
|
|
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
|
-
|
|
16
|
+
# Expo (aligns versions with your SDK)
|
|
17
|
+
npx expo install react-native-reanimated react-native-svg
|
|
18
|
+
```
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
Brownfield apps that already use `babel-plugin-module-resolver` with `@/` aliases pointing at the app `src/` also need:
|
|
14
21
|
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
["babel-preset-expo", { jsxImportSource: "nativewind" }], "nativewind/babel"
|
|
22
|
+
```bash
|
|
23
|
+
npm install -D babel-plugin-module-resolver
|
|
18
24
|
```
|
|
19
25
|
|
|
20
|
-
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Brownfield setup (Expo + legacy UI / Restyle)
|
|
21
29
|
|
|
22
|
-
|
|
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:
|
|
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: [
|
|
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 {
|
|
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:
|
|
57
|
-
overrides: [
|
|
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
|
-
|
|
124
|
+
Apps **without** `@/` Babel aliases do not need `createMedwayUiAliasResolver`.
|
|
63
125
|
|
|
64
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
178
|
+
Register in the app stack:
|
|
75
179
|
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
210
|
+
## Monorepo development (`file:../medway-ui/native`)
|
|
83
211
|
|
|
84
|
-
|
|
212
|
+
```json
|
|
213
|
+
{
|
|
214
|
+
"@medway-ui/core": "file:../medway-ui",
|
|
215
|
+
"@medway-ui/native": "file:../medway-ui/native"
|
|
216
|
+
}
|
|
217
|
+
```
|
|
85
218
|
|
|
86
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
@tailwind utilities;
|
|
265
|
+
module.exports = withMedwayUi(getDefaultConfig(__dirname), {
|
|
266
|
+
projectRoot: __dirname,
|
|
267
|
+
});
|
|
142
268
|
```
|
|
143
269
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
In your root `_layout.tsx`:
|
|
270
|
+
Import `@medway-ui/native/setup/global.css` in the root layout.
|
|
147
271
|
|
|
148
|
-
|
|
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 {
|
|
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
|
-
<
|
|
175
|
-
<
|
|
176
|
-
<
|
|
177
|
-
<
|
|
178
|
-
</
|
|
179
|
-
</
|
|
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
|
|
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
|
-
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Components — status
|
|
197
330
|
|
|
198
331
|
| Component | Status | Notes |
|
|
199
|
-
|
|
200
|
-
| Button | ✅
|
|
201
|
-
| Input | ✅
|
|
202
|
-
| Alert | ✅
|
|
203
|
-
| Badge | ✅
|
|
204
|
-
| Loading | ✅
|
|
205
|
-
| Checkbox | ✅
|
|
206
|
-
| RadioGroup | ✅
|
|
207
|
-
| Switch | ✅
|
|
208
|
-
| Select | ✅
|
|
209
|
-
| Label | ✅
|
|
210
|
-
| Separator | ✅
|
|
211
|
-
| Dialog | ✅
|
|
212
|
-
| AlertDialog | ✅
|
|
213
|
-
| Sheet | ✅
|
|
214
|
-
| Tag | ✅
|
|
215
|
-
| Toast | ✅
|
|
216
|
-
| Tooltip | ✅
|
|
217
|
-
| Popover | ✅
|
|
218
|
-
|
|
|
219
|
-
|
|
|
220
|
-
|
|
|
221
|
-
|
|
|
222
|
-
|
|
|
223
|
-
|
|
|
224
|
-
|
|
|
225
|
-
| Cards | 🔜
|
|
226
|
-
| DatePicker | 🔜
|
|
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,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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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 {
|