@gaozh1024/rn-kit 0.2.0-beta.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.
- package/README.md +363 -0
- package/TAILWIND_SETUP.md +307 -0
- package/dist/index.d.mts +3465 -0
- package/dist/index.d.ts +3465 -0
- package/dist/index.js +4627 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +4504 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +63 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,4504 @@
|
|
|
1
|
+
// src/utils/cn.ts
|
|
2
|
+
import { clsx } from "clsx";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
4
|
+
function cn(...inputs) {
|
|
5
|
+
return twMerge(clsx(inputs));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/utils/color.ts
|
|
9
|
+
function hexToRgb(hex) {
|
|
10
|
+
const clean = hex.replace("#", "");
|
|
11
|
+
const bigint = parseInt(clean, 16);
|
|
12
|
+
return {
|
|
13
|
+
r: bigint >> 16 & 255,
|
|
14
|
+
g: bigint >> 8 & 255,
|
|
15
|
+
b: bigint & 255
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function rgbToHex(rgb) {
|
|
19
|
+
return "#" + [rgb.r, rgb.g, rgb.b].map((x) => x.toString(16).padStart(2, "0")).join("");
|
|
20
|
+
}
|
|
21
|
+
function adjustBrightness(rgb, factor) {
|
|
22
|
+
const adjust = (c) => Math.max(0, Math.min(255, c + (factor > 0 ? (255 - c) * factor : c * factor)));
|
|
23
|
+
return { r: adjust(rgb.r), g: adjust(rgb.g), b: adjust(rgb.b) };
|
|
24
|
+
}
|
|
25
|
+
function generateColorPalette(baseHex) {
|
|
26
|
+
const rgb = hexToRgb(baseHex);
|
|
27
|
+
const factors = {
|
|
28
|
+
0: 0.95,
|
|
29
|
+
50: 0.9,
|
|
30
|
+
100: 0.75,
|
|
31
|
+
200: 0.5,
|
|
32
|
+
300: 0.3,
|
|
33
|
+
400: 0.1,
|
|
34
|
+
500: 0,
|
|
35
|
+
600: -0.1,
|
|
36
|
+
700: -0.25,
|
|
37
|
+
800: -0.4,
|
|
38
|
+
900: -0.55,
|
|
39
|
+
950: -0.7
|
|
40
|
+
};
|
|
41
|
+
const result = {};
|
|
42
|
+
for (const [level, factor] of Object.entries(factors)) {
|
|
43
|
+
result[parseInt(level)] = rgbToHex(adjustBrightness(rgb, factor));
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/utils/platform.ts
|
|
49
|
+
function isDevelopment() {
|
|
50
|
+
return process.env.NODE_ENV === "development";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/utils/date/index.ts
|
|
54
|
+
function formatDate(date, format = "yyyy-MM-dd") {
|
|
55
|
+
const d = new Date(date);
|
|
56
|
+
const year = d.getFullYear();
|
|
57
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
58
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
59
|
+
return format.replace("yyyy", String(year)).replace("MM", month).replace("dd", day);
|
|
60
|
+
}
|
|
61
|
+
function formatRelativeTime(date) {
|
|
62
|
+
const now = /* @__PURE__ */ new Date();
|
|
63
|
+
const diff = now.getTime() - new Date(date).getTime();
|
|
64
|
+
const minutes = Math.floor(diff / 6e4);
|
|
65
|
+
const hours = Math.floor(diff / 36e5);
|
|
66
|
+
const days = Math.floor(diff / 864e5);
|
|
67
|
+
if (minutes < 1) return "\u521A\u521A";
|
|
68
|
+
if (minutes < 60) return `${minutes}\u5206\u949F\u524D`;
|
|
69
|
+
if (hours < 24) return `${hours}\u5C0F\u65F6\u524D`;
|
|
70
|
+
if (days < 30) return `${days}\u5929\u524D`;
|
|
71
|
+
return formatDate(date);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/utils/string/index.ts
|
|
75
|
+
function truncate(str, length, suffix = "...") {
|
|
76
|
+
if (str.length <= length) return str;
|
|
77
|
+
return str.slice(0, length - suffix.length) + suffix;
|
|
78
|
+
}
|
|
79
|
+
function slugify(str) {
|
|
80
|
+
return str.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-");
|
|
81
|
+
}
|
|
82
|
+
function capitalize(str) {
|
|
83
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/utils/number/index.ts
|
|
87
|
+
function formatNumber(num) {
|
|
88
|
+
return num.toLocaleString("zh-CN");
|
|
89
|
+
}
|
|
90
|
+
function formatCurrency(num, currency = "\xA5") {
|
|
91
|
+
return `${currency}${formatNumber(num)}`;
|
|
92
|
+
}
|
|
93
|
+
function formatPercent(num, decimals = 2) {
|
|
94
|
+
return `${(num * 100).toFixed(decimals)}%`;
|
|
95
|
+
}
|
|
96
|
+
function clamp(num, min, max) {
|
|
97
|
+
return Math.min(Math.max(num, min), max);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/utils/object/index.ts
|
|
101
|
+
function deepMerge(target, source) {
|
|
102
|
+
const result = { ...target };
|
|
103
|
+
for (const key in source) {
|
|
104
|
+
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
|
|
105
|
+
result[key] = deepMerge(result[key] || {}, source[key]);
|
|
106
|
+
} else {
|
|
107
|
+
result[key] = source[key];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
function pick(obj, keys) {
|
|
113
|
+
const result = {};
|
|
114
|
+
for (const key of keys) {
|
|
115
|
+
if (key in obj) result[key] = obj[key];
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
function omit(obj, keys) {
|
|
120
|
+
const result = { ...obj };
|
|
121
|
+
for (const key of keys) delete result[key];
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/utils/validation/index.ts
|
|
126
|
+
function getValidationErrors(error) {
|
|
127
|
+
const errors = {};
|
|
128
|
+
for (const issue of error.issues) {
|
|
129
|
+
const path = issue.path.join(".");
|
|
130
|
+
errors[path] = issue.message;
|
|
131
|
+
}
|
|
132
|
+
return errors;
|
|
133
|
+
}
|
|
134
|
+
function isValidEmail(email) {
|
|
135
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
136
|
+
}
|
|
137
|
+
function isValidPhone(phone) {
|
|
138
|
+
return /^1[3-9]\d{9}$/.test(phone);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/theme/create-theme.ts
|
|
142
|
+
var defaultSpacing = {
|
|
143
|
+
0: 0,
|
|
144
|
+
1: 4,
|
|
145
|
+
2: 8,
|
|
146
|
+
3: 12,
|
|
147
|
+
4: 16,
|
|
148
|
+
5: 20,
|
|
149
|
+
6: 24,
|
|
150
|
+
8: 32,
|
|
151
|
+
10: 40,
|
|
152
|
+
12: 48
|
|
153
|
+
};
|
|
154
|
+
var defaultBorderRadius = {
|
|
155
|
+
none: 0,
|
|
156
|
+
sm: 2,
|
|
157
|
+
md: 6,
|
|
158
|
+
lg: 8,
|
|
159
|
+
xl: 12,
|
|
160
|
+
"2xl": 16,
|
|
161
|
+
"3xl": 24,
|
|
162
|
+
full: 9999
|
|
163
|
+
};
|
|
164
|
+
var defaultColors = {
|
|
165
|
+
background: "#ffffff",
|
|
166
|
+
card: "#ffffff",
|
|
167
|
+
text: "#1f2937",
|
|
168
|
+
border: "#e5e7eb",
|
|
169
|
+
error: "#ef4444",
|
|
170
|
+
success: "#22c55e",
|
|
171
|
+
warning: "#f59e0b",
|
|
172
|
+
info: "#3b82f6"
|
|
173
|
+
};
|
|
174
|
+
function resolveColor(token) {
|
|
175
|
+
return typeof token === "string" ? generateColorPalette(token) : token;
|
|
176
|
+
}
|
|
177
|
+
function createTheme(config) {
|
|
178
|
+
const colors = {};
|
|
179
|
+
for (const [name, value] of Object.entries(defaultColors)) {
|
|
180
|
+
colors[name] = resolveColor(value);
|
|
181
|
+
}
|
|
182
|
+
for (const [name, token] of Object.entries(config.colors)) {
|
|
183
|
+
colors[name] = resolveColor(token);
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
colors,
|
|
187
|
+
spacing: config.spacing ?? defaultSpacing,
|
|
188
|
+
borderRadius: config.borderRadius ?? defaultBorderRadius
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/theme/provider.tsx
|
|
193
|
+
import { createContext, useContext, useState, useMemo } from "react";
|
|
194
|
+
import { jsx } from "nativewind/jsx-runtime";
|
|
195
|
+
var ThemeContext = createContext(null);
|
|
196
|
+
var fallbackTheme = createTheme({
|
|
197
|
+
colors: {
|
|
198
|
+
primary: "#f38b32",
|
|
199
|
+
secondary: "#3b82f6",
|
|
200
|
+
background: "#ffffff",
|
|
201
|
+
card: "#ffffff",
|
|
202
|
+
text: "#1f2937",
|
|
203
|
+
border: "#e5e7eb",
|
|
204
|
+
error: "#ef4444",
|
|
205
|
+
success: "#22c55e",
|
|
206
|
+
warning: "#f59e0b",
|
|
207
|
+
info: "#3b82f6"
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
function ThemeProvider({
|
|
211
|
+
children,
|
|
212
|
+
light,
|
|
213
|
+
dark,
|
|
214
|
+
defaultDark = false,
|
|
215
|
+
isDark: controlledIsDark,
|
|
216
|
+
onThemeChange
|
|
217
|
+
}) {
|
|
218
|
+
const [internalIsDark, setInternalIsDark] = useState(defaultDark);
|
|
219
|
+
const isDark = controlledIsDark ?? internalIsDark;
|
|
220
|
+
const theme = useMemo(() => {
|
|
221
|
+
const config = isDark && dark ? dark : light;
|
|
222
|
+
return createTheme(config);
|
|
223
|
+
}, [isDark, light, dark]);
|
|
224
|
+
const toggleTheme = () => {
|
|
225
|
+
const nextIsDark = !isDark;
|
|
226
|
+
if (controlledIsDark === void 0) {
|
|
227
|
+
setInternalIsDark(nextIsDark);
|
|
228
|
+
}
|
|
229
|
+
onThemeChange?.(nextIsDark);
|
|
230
|
+
};
|
|
231
|
+
return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: { theme, isDark, toggleTheme }, children });
|
|
232
|
+
}
|
|
233
|
+
function useTheme() {
|
|
234
|
+
const context = useContext(ThemeContext);
|
|
235
|
+
if (!context) throw new Error("useTheme must be used within ThemeProvider");
|
|
236
|
+
return context;
|
|
237
|
+
}
|
|
238
|
+
function useOptionalTheme() {
|
|
239
|
+
const context = useContext(ThemeContext);
|
|
240
|
+
return context || {
|
|
241
|
+
theme: fallbackTheme,
|
|
242
|
+
isDark: false,
|
|
243
|
+
toggleTheme: () => {
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/theme/tokens.ts
|
|
249
|
+
import { useMemo as useMemo2 } from "react";
|
|
250
|
+
function getThemeColors(theme, isDark) {
|
|
251
|
+
return {
|
|
252
|
+
primary: theme.colors.primary?.[500] || "#f38b32",
|
|
253
|
+
primarySurface: isDark ? theme.colors.primary?.[900] || "#7c2d12" : theme.colors.primary?.[50] || "#fff7ed",
|
|
254
|
+
background: theme.colors.background?.[500] || (isDark ? "#0a0a0a" : "#ffffff"),
|
|
255
|
+
card: theme.colors.card?.[500] || (isDark ? "#1f2937" : "#ffffff"),
|
|
256
|
+
cardElevated: (isDark ? theme.colors.card?.[800] || theme.colors.card?.[700] : theme.colors.card?.[500]) || (isDark ? "#1f2937" : "#ffffff"),
|
|
257
|
+
text: theme.colors.text?.[500] || (isDark ? "#ffffff" : "#1f2937"),
|
|
258
|
+
textSecondary: isDark ? "#d1d5db" : "#374151",
|
|
259
|
+
textMuted: isDark ? "#9ca3af" : "#6b7280",
|
|
260
|
+
textInverse: "#ffffff",
|
|
261
|
+
border: isDark ? theme.colors.border?.[600] || theme.colors.border?.[500] || "#4b5563" : theme.colors.border?.[500] || "#d1d5db",
|
|
262
|
+
divider: isDark ? theme.colors.border?.[700] || theme.colors.border?.[500] || "#374151" : theme.colors.border?.[500] || "#e5e7eb",
|
|
263
|
+
iconMuted: isDark ? "#9ca3af" : "#6b7280"
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function useThemeColors() {
|
|
267
|
+
const { theme, isDark } = useOptionalTheme();
|
|
268
|
+
return useMemo2(() => getThemeColors(theme, isDark), [theme, isDark]);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/core/index.ts
|
|
272
|
+
import { z } from "zod";
|
|
273
|
+
import { useQuery, useMutation } from "@tanstack/react-query";
|
|
274
|
+
|
|
275
|
+
// src/core/error/types.ts
|
|
276
|
+
var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
277
|
+
ErrorCode2["VALIDATION"] = "VALIDATION";
|
|
278
|
+
ErrorCode2["NETWORK"] = "NETWORK";
|
|
279
|
+
ErrorCode2["UNAUTHORIZED"] = "UNAUTHORIZED";
|
|
280
|
+
ErrorCode2["FORBIDDEN"] = "FORBIDDEN";
|
|
281
|
+
ErrorCode2["SERVER"] = "SERVER";
|
|
282
|
+
ErrorCode2["BUSINESS"] = "BUSINESS";
|
|
283
|
+
ErrorCode2["UNKNOWN"] = "UNKNOWN";
|
|
284
|
+
return ErrorCode2;
|
|
285
|
+
})(ErrorCode || {});
|
|
286
|
+
|
|
287
|
+
// src/core/error/helpers.ts
|
|
288
|
+
function mapHttpStatus(status) {
|
|
289
|
+
if (status === 401) return "UNAUTHORIZED" /* UNAUTHORIZED */;
|
|
290
|
+
if (status === 403) return "FORBIDDEN" /* FORBIDDEN */;
|
|
291
|
+
if (status >= 400 && status < 500) return "VALIDATION" /* VALIDATION */;
|
|
292
|
+
if (status >= 500) return "SERVER" /* SERVER */;
|
|
293
|
+
return "UNKNOWN" /* UNKNOWN */;
|
|
294
|
+
}
|
|
295
|
+
function enhanceError(error) {
|
|
296
|
+
return {
|
|
297
|
+
...error,
|
|
298
|
+
get isValidation() {
|
|
299
|
+
return error.code === "VALIDATION" /* VALIDATION */;
|
|
300
|
+
},
|
|
301
|
+
get isNetwork() {
|
|
302
|
+
return error.code === "NETWORK" /* NETWORK */;
|
|
303
|
+
},
|
|
304
|
+
get isAuth() {
|
|
305
|
+
return error.code === "UNAUTHORIZED" /* UNAUTHORIZED */ || error.code === "FORBIDDEN" /* FORBIDDEN */;
|
|
306
|
+
},
|
|
307
|
+
get isRetryable() {
|
|
308
|
+
return error.retryable ?? error.code === "NETWORK" /* NETWORK */;
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/core/hooks/useAsyncState.ts
|
|
314
|
+
import { useState as useState2, useCallback } from "react";
|
|
315
|
+
function useAsyncState() {
|
|
316
|
+
const [data, setData] = useState2(null);
|
|
317
|
+
const [error, setError] = useState2(null);
|
|
318
|
+
const [loading, setLoading] = useState2(false);
|
|
319
|
+
const execute = useCallback(async (promise) => {
|
|
320
|
+
setLoading(true);
|
|
321
|
+
setError(null);
|
|
322
|
+
try {
|
|
323
|
+
const result = await promise;
|
|
324
|
+
setData(result);
|
|
325
|
+
return result;
|
|
326
|
+
} catch (err) {
|
|
327
|
+
const appError = {
|
|
328
|
+
code: err.code || "UNKNOWN",
|
|
329
|
+
message: err.message || "Unknown error",
|
|
330
|
+
...err
|
|
331
|
+
};
|
|
332
|
+
const enhanced = enhanceError(appError);
|
|
333
|
+
setError(enhanced);
|
|
334
|
+
throw enhanced;
|
|
335
|
+
} finally {
|
|
336
|
+
setLoading(false);
|
|
337
|
+
}
|
|
338
|
+
}, []);
|
|
339
|
+
const reset = useCallback(() => {
|
|
340
|
+
setData(null);
|
|
341
|
+
setError(null);
|
|
342
|
+
setLoading(false);
|
|
343
|
+
}, []);
|
|
344
|
+
return { data, error, loading, execute, reset };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/core/api/create-api.ts
|
|
348
|
+
import { ZodError } from "zod";
|
|
349
|
+
function parseZodError(error) {
|
|
350
|
+
const first = error.errors[0];
|
|
351
|
+
return {
|
|
352
|
+
code: "VALIDATION" /* VALIDATION */,
|
|
353
|
+
message: first?.message || "Validation failed",
|
|
354
|
+
field: first?.path.join(".")
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function isAppError(error) {
|
|
358
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" && "message" in error && typeof error.message === "string";
|
|
359
|
+
}
|
|
360
|
+
function parseNetworkError(error) {
|
|
361
|
+
return {
|
|
362
|
+
code: "NETWORK" /* NETWORK */,
|
|
363
|
+
message: error instanceof Error ? error.message : "Network request failed",
|
|
364
|
+
retryable: true,
|
|
365
|
+
original: error
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
function parseHttpError(response, data) {
|
|
369
|
+
const fallbackMessage = response.statusText || `Request failed with status ${response.status}`;
|
|
370
|
+
const message = typeof data === "object" && data !== null && "message" in data && typeof data.message === "string" ? data.message : fallbackMessage;
|
|
371
|
+
return {
|
|
372
|
+
code: mapHttpStatus(response.status),
|
|
373
|
+
message,
|
|
374
|
+
statusCode: response.status,
|
|
375
|
+
retryable: response.status >= 500,
|
|
376
|
+
original: data
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function parseUnknownError(error) {
|
|
380
|
+
if (error instanceof ZodError) {
|
|
381
|
+
return parseZodError(error);
|
|
382
|
+
}
|
|
383
|
+
if (isAppError(error)) {
|
|
384
|
+
return error;
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
code: "UNKNOWN" /* UNKNOWN */,
|
|
388
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
389
|
+
original: error
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
async function notifyError(error, context, endpoint, config) {
|
|
393
|
+
const enhanced = enhanceError(error);
|
|
394
|
+
await endpoint.onError?.(enhanced, context);
|
|
395
|
+
await config.onError?.(enhanced, context);
|
|
396
|
+
return enhanced;
|
|
397
|
+
}
|
|
398
|
+
function createAPI(config) {
|
|
399
|
+
const endpoints = {};
|
|
400
|
+
const fetcher = config.fetcher ?? fetch;
|
|
401
|
+
for (const [name, ep] of Object.entries(config.endpoints)) {
|
|
402
|
+
endpoints[name] = async (input) => {
|
|
403
|
+
const context = {
|
|
404
|
+
endpointName: name,
|
|
405
|
+
path: ep.path,
|
|
406
|
+
method: ep.method,
|
|
407
|
+
input
|
|
408
|
+
};
|
|
409
|
+
if (ep.input) {
|
|
410
|
+
const result = ep.input.safeParse(input);
|
|
411
|
+
if (!result.success) {
|
|
412
|
+
throw await notifyError(parseZodError(result.error), context, ep, config);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const url = config.baseURL + ep.path;
|
|
416
|
+
let response;
|
|
417
|
+
try {
|
|
418
|
+
response = await fetcher(url, { method: ep.method });
|
|
419
|
+
} catch (error) {
|
|
420
|
+
throw await notifyError(parseNetworkError(error), context, ep, config);
|
|
421
|
+
}
|
|
422
|
+
context.response = response;
|
|
423
|
+
let data = void 0;
|
|
424
|
+
const contentType = response.headers.get("content-type") || "";
|
|
425
|
+
const canParseJson = contentType.includes("application/json");
|
|
426
|
+
if (canParseJson) {
|
|
427
|
+
try {
|
|
428
|
+
data = await response.json();
|
|
429
|
+
} catch {
|
|
430
|
+
data = void 0;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
context.responseData = data;
|
|
434
|
+
if (!response.ok) {
|
|
435
|
+
throw await notifyError(parseHttpError(response, data), context, ep, config);
|
|
436
|
+
}
|
|
437
|
+
const businessError = ep.parseBusinessError?.(data, response) ?? config.parseBusinessError?.(data, response);
|
|
438
|
+
if (businessError) {
|
|
439
|
+
throw await notifyError(businessError, context, ep, config);
|
|
440
|
+
}
|
|
441
|
+
try {
|
|
442
|
+
if (ep.output) {
|
|
443
|
+
return ep.output.parse(data);
|
|
444
|
+
}
|
|
445
|
+
} catch (error) {
|
|
446
|
+
throw await notifyError(parseUnknownError(error), context, ep, config);
|
|
447
|
+
}
|
|
448
|
+
return data;
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
return endpoints;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// src/core/storage/memory-storage.ts
|
|
455
|
+
var MemoryStorage = class {
|
|
456
|
+
constructor() {
|
|
457
|
+
/** 内部存储的 Map 实例 */
|
|
458
|
+
this.memory = /* @__PURE__ */ new Map();
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* 存储键值对
|
|
462
|
+
* @param key - 存储键名
|
|
463
|
+
* @param value - 要存储的字符串值
|
|
464
|
+
* @returns 解析为 void 的 Promise
|
|
465
|
+
*/
|
|
466
|
+
async setItem(key, value) {
|
|
467
|
+
this.memory.set(key, value);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* 根据键名获取值
|
|
471
|
+
* @param key - 存储键名
|
|
472
|
+
* @returns 存储的字符串值,如果不存在则返回 null
|
|
473
|
+
*/
|
|
474
|
+
async getItem(key) {
|
|
475
|
+
return this.memory.get(key) ?? null;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* 删除指定键名的存储项
|
|
479
|
+
* @param key - 要删除的键名
|
|
480
|
+
* @returns 解析为 void 的 Promise
|
|
481
|
+
*/
|
|
482
|
+
async removeItem(key) {
|
|
483
|
+
this.memory.delete(key);
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
var storage = new MemoryStorage();
|
|
487
|
+
|
|
488
|
+
// src/core/hooks/useRequest.ts
|
|
489
|
+
import { useState as useState3, useCallback as useCallback2, useRef, useEffect } from "react";
|
|
490
|
+
function useRequest(service, options = {}) {
|
|
491
|
+
const {
|
|
492
|
+
manual = false,
|
|
493
|
+
deps = [],
|
|
494
|
+
defaultParams,
|
|
495
|
+
onSuccess,
|
|
496
|
+
onError,
|
|
497
|
+
onFinally,
|
|
498
|
+
retryCount = 0,
|
|
499
|
+
retryDelay = 1e3
|
|
500
|
+
} = options;
|
|
501
|
+
const [data, setData] = useState3(void 0);
|
|
502
|
+
const [loading, setLoading] = useState3(!manual);
|
|
503
|
+
const [error, setError] = useState3(void 0);
|
|
504
|
+
const serviceRef = useRef(service);
|
|
505
|
+
const paramsRef = useRef(defaultParams);
|
|
506
|
+
const retryCountRef = useRef(0);
|
|
507
|
+
const canceledRef = useRef(false);
|
|
508
|
+
serviceRef.current = service;
|
|
509
|
+
const run = useCallback2(
|
|
510
|
+
async (...params) => {
|
|
511
|
+
paramsRef.current = params;
|
|
512
|
+
setLoading(true);
|
|
513
|
+
setError(void 0);
|
|
514
|
+
try {
|
|
515
|
+
const result = await serviceRef.current(...params);
|
|
516
|
+
if (!canceledRef.current) {
|
|
517
|
+
setData(result);
|
|
518
|
+
retryCountRef.current = 0;
|
|
519
|
+
onSuccess?.(result, params);
|
|
520
|
+
}
|
|
521
|
+
return result;
|
|
522
|
+
} catch (err) {
|
|
523
|
+
if (!canceledRef.current) {
|
|
524
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
525
|
+
setError(error2);
|
|
526
|
+
onError?.(error2, params);
|
|
527
|
+
if (retryCountRef.current < retryCount) {
|
|
528
|
+
retryCountRef.current++;
|
|
529
|
+
setTimeout(() => {
|
|
530
|
+
if (!canceledRef.current) {
|
|
531
|
+
run(...params);
|
|
532
|
+
}
|
|
533
|
+
}, retryDelay * retryCountRef.current);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
throw err;
|
|
537
|
+
} finally {
|
|
538
|
+
if (!canceledRef.current) {
|
|
539
|
+
setLoading(false);
|
|
540
|
+
onFinally?.(params);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
[onSuccess, onError, onFinally, retryCount, retryDelay]
|
|
545
|
+
);
|
|
546
|
+
const refresh = useCallback2(() => {
|
|
547
|
+
if (paramsRef.current) {
|
|
548
|
+
return run(...paramsRef.current);
|
|
549
|
+
}
|
|
550
|
+
throw new Error("No params to refresh");
|
|
551
|
+
}, [run]);
|
|
552
|
+
const cancel = useCallback2(() => {
|
|
553
|
+
canceledRef.current = true;
|
|
554
|
+
}, []);
|
|
555
|
+
const mutate = useCallback2((newData) => {
|
|
556
|
+
setData((prev) => {
|
|
557
|
+
if (typeof newData === "function") {
|
|
558
|
+
return newData(prev);
|
|
559
|
+
}
|
|
560
|
+
return newData;
|
|
561
|
+
});
|
|
562
|
+
}, []);
|
|
563
|
+
useEffect(() => {
|
|
564
|
+
if (!manual) {
|
|
565
|
+
run(...defaultParams || []);
|
|
566
|
+
}
|
|
567
|
+
return () => {
|
|
568
|
+
canceledRef.current = true;
|
|
569
|
+
};
|
|
570
|
+
}, deps);
|
|
571
|
+
return {
|
|
572
|
+
data,
|
|
573
|
+
loading,
|
|
574
|
+
error,
|
|
575
|
+
run,
|
|
576
|
+
refresh,
|
|
577
|
+
cancel,
|
|
578
|
+
mutate
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// src/core/hooks/usePagination.ts
|
|
583
|
+
import { useState as useState4, useCallback as useCallback3, useRef as useRef2 } from "react";
|
|
584
|
+
function usePagination(service, options = {}) {
|
|
585
|
+
const { defaultCurrent = 1, defaultPageSize = 10 } = options;
|
|
586
|
+
const [data, setData] = useState4([]);
|
|
587
|
+
const [current, setCurrent] = useState4(defaultCurrent);
|
|
588
|
+
const [pageSize] = useState4(defaultPageSize);
|
|
589
|
+
const [total, setTotal] = useState4(0);
|
|
590
|
+
const [loading, setLoading] = useState4(false);
|
|
591
|
+
const [refreshing, setRefreshing] = useState4(false);
|
|
592
|
+
const [loadingMore, setLoadingMore] = useState4(false);
|
|
593
|
+
const [error, setError] = useState4(null);
|
|
594
|
+
const serviceRef = useRef2(service);
|
|
595
|
+
serviceRef.current = service;
|
|
596
|
+
const hasMore = data.length < total;
|
|
597
|
+
const fetch2 = useCallback3(async (params, isRefresh = false) => {
|
|
598
|
+
try {
|
|
599
|
+
const result = await serviceRef.current(params);
|
|
600
|
+
const items = result.data ?? result.list ?? [];
|
|
601
|
+
if (isRefresh) {
|
|
602
|
+
setData(items);
|
|
603
|
+
} else {
|
|
604
|
+
setData((prev) => [...prev, ...items]);
|
|
605
|
+
}
|
|
606
|
+
setTotal(result.total);
|
|
607
|
+
setError(null);
|
|
608
|
+
return result;
|
|
609
|
+
} catch (err) {
|
|
610
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
611
|
+
setError(error2);
|
|
612
|
+
throw err;
|
|
613
|
+
}
|
|
614
|
+
}, []);
|
|
615
|
+
const refresh = useCallback3(async () => {
|
|
616
|
+
setRefreshing(true);
|
|
617
|
+
try {
|
|
618
|
+
await fetch2({ current: 1, pageSize }, true);
|
|
619
|
+
setCurrent(1);
|
|
620
|
+
} finally {
|
|
621
|
+
setRefreshing(false);
|
|
622
|
+
}
|
|
623
|
+
}, [fetch2, pageSize]);
|
|
624
|
+
const loadMore = useCallback3(async () => {
|
|
625
|
+
if (loadingMore || !hasMore) return;
|
|
626
|
+
setLoadingMore(true);
|
|
627
|
+
try {
|
|
628
|
+
const nextPage = current + 1;
|
|
629
|
+
await fetch2({ current: nextPage, pageSize }, false);
|
|
630
|
+
setCurrent(nextPage);
|
|
631
|
+
} finally {
|
|
632
|
+
setLoadingMore(false);
|
|
633
|
+
}
|
|
634
|
+
}, [fetch2, current, pageSize, loadingMore, hasMore]);
|
|
635
|
+
const changePage = useCallback3(
|
|
636
|
+
async (page) => {
|
|
637
|
+
setLoading(true);
|
|
638
|
+
try {
|
|
639
|
+
await fetch2({ current: page, pageSize }, true);
|
|
640
|
+
setCurrent(page);
|
|
641
|
+
} finally {
|
|
642
|
+
setLoading(false);
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
[fetch2, pageSize]
|
|
646
|
+
);
|
|
647
|
+
return {
|
|
648
|
+
data,
|
|
649
|
+
current,
|
|
650
|
+
pageSize,
|
|
651
|
+
total,
|
|
652
|
+
hasMore,
|
|
653
|
+
loading,
|
|
654
|
+
refreshing,
|
|
655
|
+
loadingMore,
|
|
656
|
+
error,
|
|
657
|
+
refresh,
|
|
658
|
+
loadMore,
|
|
659
|
+
changePage
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// src/core/hooks/usePrevious.ts
|
|
664
|
+
import { useRef as useRef3, useEffect as useEffect2 } from "react";
|
|
665
|
+
function usePrevious(value) {
|
|
666
|
+
const ref = useRef3(void 0);
|
|
667
|
+
useEffect2(() => {
|
|
668
|
+
ref.current = value;
|
|
669
|
+
}, [value]);
|
|
670
|
+
return ref.current;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/core/hooks/useSetState.ts
|
|
674
|
+
import { useState as useState5, useCallback as useCallback4 } from "react";
|
|
675
|
+
function useSetState(initialState) {
|
|
676
|
+
const [state, setState] = useState5(initialState);
|
|
677
|
+
const setMergeState = useCallback4((patch) => {
|
|
678
|
+
setState((prev) => ({
|
|
679
|
+
...prev,
|
|
680
|
+
...typeof patch === "function" ? patch(prev) : patch
|
|
681
|
+
}));
|
|
682
|
+
}, []);
|
|
683
|
+
const resetState = useCallback4(() => {
|
|
684
|
+
setState(initialState);
|
|
685
|
+
}, [initialState]);
|
|
686
|
+
return [state, setMergeState, resetState];
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// src/core/hooks/useMemoizedFn.ts
|
|
690
|
+
import { useRef as useRef4, useCallback as useCallback5 } from "react";
|
|
691
|
+
function useMemoizedFn(fn) {
|
|
692
|
+
const fnRef = useRef4(fn);
|
|
693
|
+
fnRef.current = fn;
|
|
694
|
+
const memoizedFn = useCallback5(
|
|
695
|
+
((...args) => {
|
|
696
|
+
return fnRef.current(...args);
|
|
697
|
+
}),
|
|
698
|
+
[]
|
|
699
|
+
);
|
|
700
|
+
return memoizedFn;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// src/core/hooks/useUpdateEffect.ts
|
|
704
|
+
import { useEffect as useEffect3, useRef as useRef5 } from "react";
|
|
705
|
+
function useUpdateEffect(effect, deps) {
|
|
706
|
+
const isFirstRender = useRef5(true);
|
|
707
|
+
useEffect3(() => {
|
|
708
|
+
if (isFirstRender.current) {
|
|
709
|
+
isFirstRender.current = false;
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
return effect();
|
|
713
|
+
}, deps);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// src/core/hooks/useStorage.ts
|
|
717
|
+
import { useState as useState6, useEffect as useEffect4, useCallback as useCallback6 } from "react";
|
|
718
|
+
function useStorage(key, defaultValue) {
|
|
719
|
+
const [value, setValue] = useState6(defaultValue);
|
|
720
|
+
const [, setIsLoaded] = useState6(false);
|
|
721
|
+
useEffect4(() => {
|
|
722
|
+
const loadValue = async () => {
|
|
723
|
+
try {
|
|
724
|
+
const stored = await storage.getItem(key);
|
|
725
|
+
if (stored !== null) {
|
|
726
|
+
setValue(JSON.parse(stored));
|
|
727
|
+
}
|
|
728
|
+
} catch (error) {
|
|
729
|
+
console.warn(`Failed to load storage key "${key}":`, error);
|
|
730
|
+
} finally {
|
|
731
|
+
setIsLoaded(true);
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
loadValue();
|
|
735
|
+
}, [key]);
|
|
736
|
+
const setStoredValue = useCallback6(
|
|
737
|
+
async (newValue) => {
|
|
738
|
+
try {
|
|
739
|
+
const valueToStore = newValue instanceof Function ? newValue(value) : newValue;
|
|
740
|
+
setValue(valueToStore);
|
|
741
|
+
await storage.setItem(key, JSON.stringify(valueToStore));
|
|
742
|
+
} catch (error) {
|
|
743
|
+
console.warn(`Failed to save storage key "${key}":`, error);
|
|
744
|
+
}
|
|
745
|
+
},
|
|
746
|
+
[key, value]
|
|
747
|
+
);
|
|
748
|
+
const removeValue = useCallback6(async () => {
|
|
749
|
+
try {
|
|
750
|
+
setValue(defaultValue);
|
|
751
|
+
await storage.removeItem(key);
|
|
752
|
+
} catch (error) {
|
|
753
|
+
console.warn(`Failed to remove storage key "${key}":`, error);
|
|
754
|
+
}
|
|
755
|
+
}, [key, defaultValue]);
|
|
756
|
+
return [value, setStoredValue, removeValue];
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// src/core/hooks/useRefresh.ts
|
|
760
|
+
import { useState as useState7, useCallback as useCallback7 } from "react";
|
|
761
|
+
function useRefresh(fetcher) {
|
|
762
|
+
const [data, setData] = useState7(void 0);
|
|
763
|
+
const [refreshing, setRefreshing] = useState7(false);
|
|
764
|
+
const [error, setError] = useState7(void 0);
|
|
765
|
+
const refresh = useCallback7(async () => {
|
|
766
|
+
setRefreshing(true);
|
|
767
|
+
setError(void 0);
|
|
768
|
+
try {
|
|
769
|
+
const result = await fetcher();
|
|
770
|
+
setData(result);
|
|
771
|
+
} catch (err) {
|
|
772
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
773
|
+
setError(errorObj);
|
|
774
|
+
} finally {
|
|
775
|
+
setRefreshing(false);
|
|
776
|
+
}
|
|
777
|
+
}, [fetcher]);
|
|
778
|
+
return {
|
|
779
|
+
data,
|
|
780
|
+
refreshing,
|
|
781
|
+
error,
|
|
782
|
+
refresh
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/core/hooks/useInfinite.ts
|
|
787
|
+
import { useState as useState8, useCallback as useCallback8 } from "react";
|
|
788
|
+
function useInfinite(fetcher, options = {}) {
|
|
789
|
+
const { defaultPage = 1, pageSize = 10 } = options;
|
|
790
|
+
const [data, setData] = useState8([]);
|
|
791
|
+
const [page, setPage] = useState8(defaultPage);
|
|
792
|
+
const [loading, setLoading] = useState8(false);
|
|
793
|
+
const [loadingMore, setLoadingMore] = useState8(false);
|
|
794
|
+
const [hasMore, setHasMore] = useState8(true);
|
|
795
|
+
const [error, setError] = useState8(void 0);
|
|
796
|
+
const fetchData = useCallback8(
|
|
797
|
+
async (targetPage, isLoadMore) => {
|
|
798
|
+
if (isLoadMore) {
|
|
799
|
+
setLoadingMore(true);
|
|
800
|
+
} else {
|
|
801
|
+
setLoading(true);
|
|
802
|
+
}
|
|
803
|
+
setError(void 0);
|
|
804
|
+
try {
|
|
805
|
+
const result = await fetcher({ page: targetPage, pageSize });
|
|
806
|
+
const items = result.data ?? result.list ?? [];
|
|
807
|
+
if (isLoadMore) {
|
|
808
|
+
setData((prev) => [...prev, ...items]);
|
|
809
|
+
} else {
|
|
810
|
+
setData(items);
|
|
811
|
+
}
|
|
812
|
+
setHasMore(result.hasMore);
|
|
813
|
+
setPage(targetPage);
|
|
814
|
+
} catch (err) {
|
|
815
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
816
|
+
setError(errorObj);
|
|
817
|
+
} finally {
|
|
818
|
+
setLoading(false);
|
|
819
|
+
setLoadingMore(false);
|
|
820
|
+
}
|
|
821
|
+
},
|
|
822
|
+
[fetcher, pageSize]
|
|
823
|
+
);
|
|
824
|
+
const load = useCallback8(async () => {
|
|
825
|
+
await fetchData(defaultPage, false);
|
|
826
|
+
}, [fetchData, defaultPage]);
|
|
827
|
+
const loadMore = useCallback8(async () => {
|
|
828
|
+
if (loadingMore || !hasMore) return;
|
|
829
|
+
await fetchData(page + 1, true);
|
|
830
|
+
}, [fetchData, page, loadingMore, hasMore]);
|
|
831
|
+
const refresh = useCallback8(async () => {
|
|
832
|
+
await fetchData(defaultPage, false);
|
|
833
|
+
}, [fetchData, defaultPage]);
|
|
834
|
+
const reset = useCallback8(() => {
|
|
835
|
+
setData([]);
|
|
836
|
+
setPage(defaultPage);
|
|
837
|
+
setLoading(false);
|
|
838
|
+
setLoadingMore(false);
|
|
839
|
+
setHasMore(true);
|
|
840
|
+
setError(void 0);
|
|
841
|
+
}, [defaultPage]);
|
|
842
|
+
return {
|
|
843
|
+
data,
|
|
844
|
+
page,
|
|
845
|
+
loading,
|
|
846
|
+
loadingMore,
|
|
847
|
+
hasMore,
|
|
848
|
+
error,
|
|
849
|
+
load,
|
|
850
|
+
loadMore,
|
|
851
|
+
refresh,
|
|
852
|
+
reset
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// src/ui/primitives/AppView.tsx
|
|
857
|
+
import { View } from "react-native";
|
|
858
|
+
|
|
859
|
+
// src/ui/utils/theme-color.ts
|
|
860
|
+
var grayPalette = {
|
|
861
|
+
50: "#f9fafb",
|
|
862
|
+
100: "#f3f4f6",
|
|
863
|
+
200: "#e5e7eb",
|
|
864
|
+
300: "#d1d5db",
|
|
865
|
+
400: "#9ca3af",
|
|
866
|
+
500: "#6b7280",
|
|
867
|
+
600: "#4b5563",
|
|
868
|
+
700: "#374151",
|
|
869
|
+
800: "#1f2937",
|
|
870
|
+
900: "#111827",
|
|
871
|
+
950: "#030712"
|
|
872
|
+
};
|
|
873
|
+
var staticPalettes = {
|
|
874
|
+
gray: grayPalette,
|
|
875
|
+
white: { 500: "#ffffff" },
|
|
876
|
+
black: { 500: "#000000" }
|
|
877
|
+
};
|
|
878
|
+
function resolvePaletteColor(name, shade, theme) {
|
|
879
|
+
const palette = theme.colors[name] ?? staticPalettes[name];
|
|
880
|
+
if (!palette) return void 0;
|
|
881
|
+
return palette[shade] ?? palette[500] ?? palette["500"];
|
|
882
|
+
}
|
|
883
|
+
function resolveNamedColor(color, theme, isDark) {
|
|
884
|
+
if (!color) return void 0;
|
|
885
|
+
switch (color) {
|
|
886
|
+
case "text":
|
|
887
|
+
case "foreground":
|
|
888
|
+
case "default":
|
|
889
|
+
return theme.colors.text?.[500] ?? (isDark ? "#ffffff" : "#1f2937");
|
|
890
|
+
case "muted":
|
|
891
|
+
case "text-muted":
|
|
892
|
+
case "secondary-text":
|
|
893
|
+
return isDark ? "#9ca3af" : "#6b7280";
|
|
894
|
+
case "inverse":
|
|
895
|
+
case "on-primary":
|
|
896
|
+
return "#ffffff";
|
|
897
|
+
case "background":
|
|
898
|
+
case "screen":
|
|
899
|
+
case "page":
|
|
900
|
+
return theme.colors.background?.[500] ?? (isDark ? "#0a0a0a" : "#ffffff");
|
|
901
|
+
case "card":
|
|
902
|
+
case "surface":
|
|
903
|
+
return theme.colors.card?.[500] ?? (isDark ? "#1f2937" : "#ffffff");
|
|
904
|
+
case "border":
|
|
905
|
+
return theme.colors.border?.[500] ?? (isDark ? "#404040" : "#e5e5e5");
|
|
906
|
+
case "danger":
|
|
907
|
+
return theme.colors.error?.[500] ?? "#ef4444";
|
|
908
|
+
case "success":
|
|
909
|
+
case "warning":
|
|
910
|
+
case "error":
|
|
911
|
+
case "info":
|
|
912
|
+
case "primary":
|
|
913
|
+
case "secondary":
|
|
914
|
+
return theme.colors[color]?.[500];
|
|
915
|
+
case "transparent":
|
|
916
|
+
return "transparent";
|
|
917
|
+
}
|
|
918
|
+
if (color.startsWith("#") || color.startsWith("rgb(") || color.startsWith("rgba(")) {
|
|
919
|
+
return color;
|
|
920
|
+
}
|
|
921
|
+
if (theme.colors[color]?.[500]) {
|
|
922
|
+
return theme.colors[color][500];
|
|
923
|
+
}
|
|
924
|
+
if (color.includes("-")) {
|
|
925
|
+
const [name, shade] = color.split("-");
|
|
926
|
+
if (name === "danger") {
|
|
927
|
+
return theme.colors.error?.[shade] ?? theme.colors.error?.[500] ?? "#ef4444";
|
|
928
|
+
}
|
|
929
|
+
return resolvePaletteColor(name, shade, theme);
|
|
930
|
+
}
|
|
931
|
+
return void 0;
|
|
932
|
+
}
|
|
933
|
+
function resolveSurfaceColor(surface, theme, isDark) {
|
|
934
|
+
switch (surface) {
|
|
935
|
+
case "background":
|
|
936
|
+
return theme.colors.background?.[500] ?? (isDark ? "#0a0a0a" : "#ffffff");
|
|
937
|
+
case "card":
|
|
938
|
+
return theme.colors.card?.[500] ?? (isDark ? "#1f2937" : "#ffffff");
|
|
939
|
+
case "muted":
|
|
940
|
+
return isDark ? "#111827" : "#f3f4f6";
|
|
941
|
+
default:
|
|
942
|
+
return void 0;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
function resolveTextTone(tone, theme, isDark) {
|
|
946
|
+
switch (tone) {
|
|
947
|
+
case "default":
|
|
948
|
+
return theme.colors.text?.[500] ?? (isDark ? "#ffffff" : "#1f2937");
|
|
949
|
+
case "muted":
|
|
950
|
+
return isDark ? "#9ca3af" : "#6b7280";
|
|
951
|
+
case "inverse":
|
|
952
|
+
return "#ffffff";
|
|
953
|
+
case "primary":
|
|
954
|
+
case "secondary":
|
|
955
|
+
case "success":
|
|
956
|
+
case "warning":
|
|
957
|
+
case "error":
|
|
958
|
+
return resolveNamedColor(tone, theme, isDark);
|
|
959
|
+
default:
|
|
960
|
+
return void 0;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// src/ui/primitives/AppView.tsx
|
|
965
|
+
import { jsx as jsx2 } from "nativewind/jsx-runtime";
|
|
966
|
+
function AppView({
|
|
967
|
+
flex,
|
|
968
|
+
row,
|
|
969
|
+
center,
|
|
970
|
+
between,
|
|
971
|
+
items,
|
|
972
|
+
justify,
|
|
973
|
+
p,
|
|
974
|
+
px,
|
|
975
|
+
py,
|
|
976
|
+
gap,
|
|
977
|
+
bg,
|
|
978
|
+
surface,
|
|
979
|
+
rounded,
|
|
980
|
+
className,
|
|
981
|
+
children,
|
|
982
|
+
style,
|
|
983
|
+
...props
|
|
984
|
+
}) {
|
|
985
|
+
const { theme, isDark } = useOptionalTheme();
|
|
986
|
+
const resolvedBgColor = resolveSurfaceColor(surface, theme, isDark) ?? resolveNamedColor(bg, theme, isDark);
|
|
987
|
+
const shouldUseClassBg = !!bg && !resolvedBgColor;
|
|
988
|
+
return /* @__PURE__ */ jsx2(
|
|
989
|
+
View,
|
|
990
|
+
{
|
|
991
|
+
className: cn(
|
|
992
|
+
flex === true && "flex-1",
|
|
993
|
+
typeof flex === "number" && `flex-${flex}`,
|
|
994
|
+
row ? "flex-row" : "flex-col",
|
|
995
|
+
center && "items-center justify-center",
|
|
996
|
+
between && "justify-between",
|
|
997
|
+
items && `items-${items}`,
|
|
998
|
+
justify && `justify-${justify}`,
|
|
999
|
+
p !== void 0 && `p-${p}`,
|
|
1000
|
+
px !== void 0 && `px-${px}`,
|
|
1001
|
+
py !== void 0 && `py-${py}`,
|
|
1002
|
+
gap !== void 0 && `gap-${gap}`,
|
|
1003
|
+
shouldUseClassBg && `bg-${bg}`,
|
|
1004
|
+
rounded && `rounded-${rounded}`,
|
|
1005
|
+
className
|
|
1006
|
+
),
|
|
1007
|
+
style: [resolvedBgColor ? { backgroundColor: resolvedBgColor } : void 0, style],
|
|
1008
|
+
...props,
|
|
1009
|
+
children
|
|
1010
|
+
}
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// src/ui/primitives/AppScrollView.tsx
|
|
1015
|
+
import { ScrollView } from "react-native";
|
|
1016
|
+
import { jsx as jsx3 } from "nativewind/jsx-runtime";
|
|
1017
|
+
function AppScrollView({
|
|
1018
|
+
flex,
|
|
1019
|
+
bg,
|
|
1020
|
+
p,
|
|
1021
|
+
px,
|
|
1022
|
+
py,
|
|
1023
|
+
gap,
|
|
1024
|
+
surface,
|
|
1025
|
+
rounded,
|
|
1026
|
+
className,
|
|
1027
|
+
children,
|
|
1028
|
+
style,
|
|
1029
|
+
...props
|
|
1030
|
+
}) {
|
|
1031
|
+
const { theme, isDark } = useOptionalTheme();
|
|
1032
|
+
const resolvedBgColor = resolveSurfaceColor(surface, theme, isDark) ?? resolveNamedColor(bg, theme, isDark);
|
|
1033
|
+
const shouldUseClassBg = !!bg && !resolvedBgColor;
|
|
1034
|
+
return /* @__PURE__ */ jsx3(
|
|
1035
|
+
ScrollView,
|
|
1036
|
+
{
|
|
1037
|
+
className: cn(
|
|
1038
|
+
flex && "flex-1",
|
|
1039
|
+
shouldUseClassBg && `bg-${bg}`,
|
|
1040
|
+
p !== void 0 && `p-${p}`,
|
|
1041
|
+
px !== void 0 && `px-${px}`,
|
|
1042
|
+
py !== void 0 && `py-${py}`,
|
|
1043
|
+
gap !== void 0 && `gap-${gap}`,
|
|
1044
|
+
rounded && `rounded-${rounded}`,
|
|
1045
|
+
className
|
|
1046
|
+
),
|
|
1047
|
+
style: [resolvedBgColor ? { backgroundColor: resolvedBgColor } : void 0, style],
|
|
1048
|
+
...props,
|
|
1049
|
+
children
|
|
1050
|
+
}
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// src/ui/primitives/AppText.tsx
|
|
1055
|
+
import { Text } from "react-native";
|
|
1056
|
+
import { jsx as jsx4 } from "nativewind/jsx-runtime";
|
|
1057
|
+
function AppText({
|
|
1058
|
+
size = "md",
|
|
1059
|
+
weight = "normal",
|
|
1060
|
+
color,
|
|
1061
|
+
tone,
|
|
1062
|
+
className,
|
|
1063
|
+
children,
|
|
1064
|
+
style,
|
|
1065
|
+
...props
|
|
1066
|
+
}) {
|
|
1067
|
+
const { theme, isDark } = useOptionalTheme();
|
|
1068
|
+
const sizeMap3 = {
|
|
1069
|
+
xs: "text-xs",
|
|
1070
|
+
sm: "text-sm",
|
|
1071
|
+
md: "text-base",
|
|
1072
|
+
lg: "text-lg",
|
|
1073
|
+
xl: "text-xl",
|
|
1074
|
+
"2xl": "text-2xl",
|
|
1075
|
+
"3xl": "text-3xl"
|
|
1076
|
+
};
|
|
1077
|
+
const weightMap = {
|
|
1078
|
+
normal: "font-normal",
|
|
1079
|
+
medium: "font-medium",
|
|
1080
|
+
semibold: "font-semibold",
|
|
1081
|
+
bold: "font-bold"
|
|
1082
|
+
};
|
|
1083
|
+
const resolvedColor = resolveTextTone(tone, theme, isDark) ?? resolveNamedColor(color, theme, isDark);
|
|
1084
|
+
const shouldUseClassColor = !!color && !resolvedColor;
|
|
1085
|
+
return /* @__PURE__ */ jsx4(
|
|
1086
|
+
Text,
|
|
1087
|
+
{
|
|
1088
|
+
className: cn(sizeMap3[size], weightMap[weight], shouldUseClassColor && `text-${color}`, className),
|
|
1089
|
+
style: [resolvedColor ? { color: resolvedColor } : void 0, style],
|
|
1090
|
+
...props,
|
|
1091
|
+
children
|
|
1092
|
+
}
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// src/ui/primitives/AppPressable.tsx
|
|
1097
|
+
import * as React2 from "react";
|
|
1098
|
+
import { Pressable } from "react-native";
|
|
1099
|
+
import { jsx as jsx5 } from "nativewind/jsx-runtime";
|
|
1100
|
+
function AppPressable({
|
|
1101
|
+
className,
|
|
1102
|
+
pressedClassName,
|
|
1103
|
+
children,
|
|
1104
|
+
...props
|
|
1105
|
+
}) {
|
|
1106
|
+
const [isPressed, setIsPressed] = React2.useState(false);
|
|
1107
|
+
return /* @__PURE__ */ jsx5(
|
|
1108
|
+
Pressable,
|
|
1109
|
+
{
|
|
1110
|
+
className: cn(className, isPressed && pressedClassName),
|
|
1111
|
+
onPressIn: (e) => {
|
|
1112
|
+
setIsPressed(true);
|
|
1113
|
+
props.onPressIn?.(e);
|
|
1114
|
+
},
|
|
1115
|
+
onPressOut: (e) => {
|
|
1116
|
+
setIsPressed(false);
|
|
1117
|
+
props.onPressOut?.(e);
|
|
1118
|
+
},
|
|
1119
|
+
...props,
|
|
1120
|
+
children
|
|
1121
|
+
}
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// src/ui/layout/Row.tsx
|
|
1126
|
+
import { jsx as jsx6 } from "nativewind/jsx-runtime";
|
|
1127
|
+
var justifyMap = {
|
|
1128
|
+
start: "justify-start",
|
|
1129
|
+
center: "justify-center",
|
|
1130
|
+
end: "justify-end",
|
|
1131
|
+
between: "justify-between",
|
|
1132
|
+
around: "justify-around"
|
|
1133
|
+
};
|
|
1134
|
+
var itemsMap = {
|
|
1135
|
+
start: "items-start",
|
|
1136
|
+
center: "items-center",
|
|
1137
|
+
end: "items-end",
|
|
1138
|
+
stretch: "items-stretch"
|
|
1139
|
+
};
|
|
1140
|
+
function Row({ justify = "start", items = "center", className, ...props }) {
|
|
1141
|
+
return /* @__PURE__ */ jsx6(AppView, { row: true, className: cn(justifyMap[justify], itemsMap[items], className), ...props });
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// src/ui/layout/Col.tsx
|
|
1145
|
+
import { jsx as jsx7 } from "nativewind/jsx-runtime";
|
|
1146
|
+
var justifyMap2 = {
|
|
1147
|
+
start: "justify-start",
|
|
1148
|
+
center: "justify-center",
|
|
1149
|
+
end: "justify-end",
|
|
1150
|
+
between: "justify-between",
|
|
1151
|
+
around: "justify-around"
|
|
1152
|
+
};
|
|
1153
|
+
var itemsMap2 = {
|
|
1154
|
+
start: "items-start",
|
|
1155
|
+
center: "items-center",
|
|
1156
|
+
end: "items-end",
|
|
1157
|
+
stretch: "items-stretch"
|
|
1158
|
+
};
|
|
1159
|
+
function Col({ justify = "start", items = "stretch", className, ...props }) {
|
|
1160
|
+
return /* @__PURE__ */ jsx7(AppView, { className: cn(justifyMap2[justify], itemsMap2[items], className), ...props });
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// src/ui/layout/Center.tsx
|
|
1164
|
+
import { jsx as jsx8 } from "nativewind/jsx-runtime";
|
|
1165
|
+
function Center({ flex = true, ...props }) {
|
|
1166
|
+
return /* @__PURE__ */ jsx8(AppView, { center: true, flex, ...props });
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// src/ui/layout/SafeScreen.tsx
|
|
1170
|
+
import { View as View2, StyleSheet } from "react-native";
|
|
1171
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
1172
|
+
import { jsx as jsx9 } from "nativewind/jsx-runtime";
|
|
1173
|
+
function SafeScreen({
|
|
1174
|
+
top = true,
|
|
1175
|
+
bottom = true,
|
|
1176
|
+
left = false,
|
|
1177
|
+
right = false,
|
|
1178
|
+
bg,
|
|
1179
|
+
flex = true,
|
|
1180
|
+
className,
|
|
1181
|
+
children,
|
|
1182
|
+
style,
|
|
1183
|
+
...props
|
|
1184
|
+
}) {
|
|
1185
|
+
const insets = useSafeAreaInsets();
|
|
1186
|
+
const { theme, isDark } = useOptionalTheme();
|
|
1187
|
+
const resolvedBgColor = resolveNamedColor(bg, theme, isDark);
|
|
1188
|
+
const shouldUseClassBg = !!bg && !resolvedBgColor;
|
|
1189
|
+
return /* @__PURE__ */ jsx9(
|
|
1190
|
+
View2,
|
|
1191
|
+
{
|
|
1192
|
+
className: cn(flex && "flex-1", shouldUseClassBg && `bg-${bg}`, className),
|
|
1193
|
+
style: [
|
|
1194
|
+
flex && styles.flex,
|
|
1195
|
+
resolvedBgColor ? { backgroundColor: resolvedBgColor } : void 0,
|
|
1196
|
+
{
|
|
1197
|
+
paddingTop: top ? insets.top : 0,
|
|
1198
|
+
paddingBottom: bottom ? insets.bottom : 0,
|
|
1199
|
+
paddingLeft: left ? insets.left : 0,
|
|
1200
|
+
paddingRight: right ? insets.right : 0
|
|
1201
|
+
},
|
|
1202
|
+
style
|
|
1203
|
+
],
|
|
1204
|
+
...props,
|
|
1205
|
+
children
|
|
1206
|
+
}
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
var styles = StyleSheet.create({
|
|
1210
|
+
flex: {
|
|
1211
|
+
flex: 1
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
function Page({
|
|
1215
|
+
children,
|
|
1216
|
+
className,
|
|
1217
|
+
...props
|
|
1218
|
+
}) {
|
|
1219
|
+
return /* @__PURE__ */ jsx9(SafeScreen, { flex: true, bg: "background", ...props, className, children });
|
|
1220
|
+
}
|
|
1221
|
+
function SafeBottom({
|
|
1222
|
+
children,
|
|
1223
|
+
className,
|
|
1224
|
+
...props
|
|
1225
|
+
}) {
|
|
1226
|
+
return /* @__PURE__ */ jsx9(SafeScreen, { bottom: true, left: true, right: true, flex: false, ...props, className, children });
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// src/ui/actions/AppButton.tsx
|
|
1230
|
+
import { ActivityIndicator } from "react-native";
|
|
1231
|
+
import { jsx as jsx10 } from "nativewind/jsx-runtime";
|
|
1232
|
+
function AppButton({
|
|
1233
|
+
variant = "solid",
|
|
1234
|
+
size = "md",
|
|
1235
|
+
color = "primary",
|
|
1236
|
+
loading,
|
|
1237
|
+
disabled,
|
|
1238
|
+
onPress,
|
|
1239
|
+
children,
|
|
1240
|
+
className
|
|
1241
|
+
}) {
|
|
1242
|
+
const { theme, isDark } = useOptionalTheme();
|
|
1243
|
+
const isDisabled = disabled || loading;
|
|
1244
|
+
const sizeClasses = { sm: "px-3 py-2", md: "px-4 py-3", lg: "px-6 py-4" };
|
|
1245
|
+
const buttonColors = {
|
|
1246
|
+
primary: theme.colors.primary?.[500] || "#f38b32",
|
|
1247
|
+
secondary: theme.colors.secondary?.[500] || "#3b82f6",
|
|
1248
|
+
danger: theme.colors.error?.[500] || "#ef4444"
|
|
1249
|
+
};
|
|
1250
|
+
const ghostTextColor = isDark ? "#ffffff" : theme.colors.text?.[500] || "#1f2937";
|
|
1251
|
+
const ghostBackgroundColor = isDark ? "rgba(255,255,255,0.04)" : "transparent";
|
|
1252
|
+
const loadingColor = variant === "solid" ? "white" : buttonColors[color];
|
|
1253
|
+
const textColor = variant === "solid" ? "#ffffff" : variant === "ghost" ? ghostTextColor : buttonColors[color];
|
|
1254
|
+
const buttonStyle = variant === "solid" ? { backgroundColor: buttonColors[color] } : variant === "outline" ? { borderWidth: 0.5, borderColor: buttonColors[color], backgroundColor: "transparent" } : { backgroundColor: ghostBackgroundColor };
|
|
1255
|
+
return /* @__PURE__ */ jsx10(
|
|
1256
|
+
AppPressable,
|
|
1257
|
+
{
|
|
1258
|
+
onPress,
|
|
1259
|
+
disabled: isDisabled,
|
|
1260
|
+
className: cn(
|
|
1261
|
+
"flex-row items-center justify-center rounded-lg",
|
|
1262
|
+
sizeClasses[size],
|
|
1263
|
+
isDisabled && "opacity-50",
|
|
1264
|
+
className
|
|
1265
|
+
),
|
|
1266
|
+
style: buttonStyle,
|
|
1267
|
+
children: loading ? /* @__PURE__ */ jsx10(ActivityIndicator, { size: "small", color: loadingColor }) : /* @__PURE__ */ jsx10(AppText, { weight: "semibold", style: { color: textColor }, children })
|
|
1268
|
+
}
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// src/ui/feedback/Toast.tsx
|
|
1273
|
+
import { jsx as jsx11 } from "nativewind/jsx-runtime";
|
|
1274
|
+
var typeStyles = {
|
|
1275
|
+
success: "bg-green-500",
|
|
1276
|
+
error: "bg-red-500",
|
|
1277
|
+
warning: "bg-yellow-500",
|
|
1278
|
+
info: "bg-blue-500"
|
|
1279
|
+
};
|
|
1280
|
+
function Toast({ message, type = "info", visible = true }) {
|
|
1281
|
+
if (!visible) return null;
|
|
1282
|
+
return /* @__PURE__ */ jsx11(AppView, { className: cn("px-4 py-3 rounded-lg", typeStyles[type]), children: /* @__PURE__ */ jsx11(AppText, { color: "white", children: message }) });
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// src/ui/feedback/Alert.tsx
|
|
1286
|
+
import { useCallback as useCallback9 } from "react";
|
|
1287
|
+
import { Modal, TouchableOpacity, StyleSheet as StyleSheet2 } from "react-native";
|
|
1288
|
+
import { jsx as jsx12, jsxs } from "nativewind/jsx-runtime";
|
|
1289
|
+
function Alert({ visible, title, message, buttons, onClose }) {
|
|
1290
|
+
const { theme, isDark } = useTheme();
|
|
1291
|
+
const modalBgColor = isDark ? "#1f2937" : "#ffffff";
|
|
1292
|
+
const textColor = isDark ? "#ffffff" : "#1f2937";
|
|
1293
|
+
const messageColor = isDark ? "#9ca3af" : "#6b7280";
|
|
1294
|
+
const borderColor = isDark ? "#374151" : "#e5e7eb";
|
|
1295
|
+
const cancelButtonBg = isDark ? "#374151" : "#f3f4f6";
|
|
1296
|
+
const cancelButtonText = isDark ? "#ffffff" : "#374151";
|
|
1297
|
+
const destructiveColor = theme.colors.error?.[500] || "#ef4444";
|
|
1298
|
+
const handleButtonPress = useCallback9(
|
|
1299
|
+
(button) => (e) => {
|
|
1300
|
+
e.stopPropagation();
|
|
1301
|
+
button.onPress?.();
|
|
1302
|
+
onClose?.();
|
|
1303
|
+
},
|
|
1304
|
+
[onClose]
|
|
1305
|
+
);
|
|
1306
|
+
const getButtonStyle = (button) => {
|
|
1307
|
+
if (button.style === "destructive") {
|
|
1308
|
+
return {
|
|
1309
|
+
backgroundColor: "transparent",
|
|
1310
|
+
borderWidth: 0.5,
|
|
1311
|
+
borderColor: destructiveColor
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
if (button.style === "cancel") {
|
|
1315
|
+
return {
|
|
1316
|
+
backgroundColor: cancelButtonBg,
|
|
1317
|
+
borderWidth: 0.5,
|
|
1318
|
+
borderColor: isDark ? "#4b5563" : "#d1d5db"
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
return {
|
|
1322
|
+
backgroundColor: theme.colors.primary?.[500] || "#f38b32",
|
|
1323
|
+
borderWidth: 0
|
|
1324
|
+
};
|
|
1325
|
+
};
|
|
1326
|
+
const getButtonTextColor = (button) => {
|
|
1327
|
+
if (button.style === "destructive") {
|
|
1328
|
+
return destructiveColor;
|
|
1329
|
+
}
|
|
1330
|
+
if (button.style === "cancel") {
|
|
1331
|
+
return cancelButtonText;
|
|
1332
|
+
}
|
|
1333
|
+
return "#ffffff";
|
|
1334
|
+
};
|
|
1335
|
+
return /* @__PURE__ */ jsx12(
|
|
1336
|
+
Modal,
|
|
1337
|
+
{
|
|
1338
|
+
visible,
|
|
1339
|
+
transparent: true,
|
|
1340
|
+
animationType: "fade",
|
|
1341
|
+
onRequestClose: onClose,
|
|
1342
|
+
statusBarTranslucent: true,
|
|
1343
|
+
children: /* @__PURE__ */ jsx12(AppView, { className: "flex-1", style: { backgroundColor: "rgba(0,0,0,0.5)" }, center: true, children: /* @__PURE__ */ jsxs(
|
|
1344
|
+
AppView,
|
|
1345
|
+
{
|
|
1346
|
+
className: "rounded-xl mx-8 min-w-[280px]",
|
|
1347
|
+
style: { backgroundColor: modalBgColor },
|
|
1348
|
+
children: [
|
|
1349
|
+
/* @__PURE__ */ jsxs(AppView, { className: "px-6 py-5", children: [
|
|
1350
|
+
/* @__PURE__ */ jsx12(
|
|
1351
|
+
AppText,
|
|
1352
|
+
{
|
|
1353
|
+
size: "lg",
|
|
1354
|
+
weight: "semibold",
|
|
1355
|
+
className: "text-center mb-2",
|
|
1356
|
+
style: { color: textColor },
|
|
1357
|
+
children: title
|
|
1358
|
+
}
|
|
1359
|
+
),
|
|
1360
|
+
message && /* @__PURE__ */ jsx12(AppText, { size: "sm", style: { color: messageColor }, className: "text-center leading-5", children: message })
|
|
1361
|
+
] }),
|
|
1362
|
+
/* @__PURE__ */ jsx12(AppView, { className: "border-t", style: { borderTopColor: borderColor }, children: buttons.length === 1 ? (
|
|
1363
|
+
// 单个按钮
|
|
1364
|
+
/* @__PURE__ */ jsx12(
|
|
1365
|
+
TouchableOpacity,
|
|
1366
|
+
{
|
|
1367
|
+
onPress: handleButtonPress(buttons[0]),
|
|
1368
|
+
className: "py-3 rounded-b-xl",
|
|
1369
|
+
style: [styles2.singleButton, getButtonStyle(buttons[0])],
|
|
1370
|
+
children: /* @__PURE__ */ jsx12(
|
|
1371
|
+
AppText,
|
|
1372
|
+
{
|
|
1373
|
+
weight: "medium",
|
|
1374
|
+
className: "text-center",
|
|
1375
|
+
style: { color: getButtonTextColor(buttons[0]) },
|
|
1376
|
+
children: buttons[0].text
|
|
1377
|
+
}
|
|
1378
|
+
)
|
|
1379
|
+
}
|
|
1380
|
+
)
|
|
1381
|
+
) : buttons.length === 2 ? (
|
|
1382
|
+
// 两个按钮横向排列
|
|
1383
|
+
/* @__PURE__ */ jsx12(AppView, { row: true, style: styles2.twoButtonContainer, children: buttons.map((button, index) => /* @__PURE__ */ jsx12(
|
|
1384
|
+
TouchableOpacity,
|
|
1385
|
+
{
|
|
1386
|
+
onPress: handleButtonPress(button),
|
|
1387
|
+
className: cn(
|
|
1388
|
+
"py-3 flex-1",
|
|
1389
|
+
index === 0 && "rounded-bl-xl",
|
|
1390
|
+
index === 1 && "rounded-br-xl"
|
|
1391
|
+
),
|
|
1392
|
+
style: [
|
|
1393
|
+
styles2.twoButton,
|
|
1394
|
+
index > 0 && { borderLeftColor: borderColor },
|
|
1395
|
+
getButtonStyle(button)
|
|
1396
|
+
],
|
|
1397
|
+
children: /* @__PURE__ */ jsx12(
|
|
1398
|
+
AppText,
|
|
1399
|
+
{
|
|
1400
|
+
weight: "medium",
|
|
1401
|
+
className: "text-center",
|
|
1402
|
+
style: { color: getButtonTextColor(button) },
|
|
1403
|
+
children: button.text
|
|
1404
|
+
}
|
|
1405
|
+
)
|
|
1406
|
+
},
|
|
1407
|
+
index
|
|
1408
|
+
)) })
|
|
1409
|
+
) : (
|
|
1410
|
+
// 多个按钮纵向排列
|
|
1411
|
+
/* @__PURE__ */ jsx12(AppView, { className: "gap-2 pb-4 px-4", children: buttons.map((button, index) => /* @__PURE__ */ jsx12(
|
|
1412
|
+
TouchableOpacity,
|
|
1413
|
+
{
|
|
1414
|
+
onPress: handleButtonPress(button),
|
|
1415
|
+
className: "py-3 rounded-lg",
|
|
1416
|
+
style: getButtonStyle(button),
|
|
1417
|
+
children: /* @__PURE__ */ jsx12(
|
|
1418
|
+
AppText,
|
|
1419
|
+
{
|
|
1420
|
+
weight: "medium",
|
|
1421
|
+
className: "text-center",
|
|
1422
|
+
style: { color: getButtonTextColor(button) },
|
|
1423
|
+
children: button.text
|
|
1424
|
+
}
|
|
1425
|
+
)
|
|
1426
|
+
},
|
|
1427
|
+
index
|
|
1428
|
+
)) })
|
|
1429
|
+
) })
|
|
1430
|
+
]
|
|
1431
|
+
}
|
|
1432
|
+
) })
|
|
1433
|
+
}
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1436
|
+
var styles2 = StyleSheet2.create({
|
|
1437
|
+
singleButton: {
|
|
1438
|
+
borderBottomLeftRadius: 12,
|
|
1439
|
+
borderBottomRightRadius: 12
|
|
1440
|
+
},
|
|
1441
|
+
twoButtonContainer: {
|
|
1442
|
+
borderBottomLeftRadius: 12,
|
|
1443
|
+
borderBottomRightRadius: 12
|
|
1444
|
+
},
|
|
1445
|
+
twoButton: {
|
|
1446
|
+
borderLeftWidth: 0.5
|
|
1447
|
+
}
|
|
1448
|
+
});
|
|
1449
|
+
|
|
1450
|
+
// src/ui/feedback/Loading.tsx
|
|
1451
|
+
import { ActivityIndicator as ActivityIndicator2 } from "react-native";
|
|
1452
|
+
import { jsx as jsx13, jsxs as jsxs2 } from "nativewind/jsx-runtime";
|
|
1453
|
+
function Loading({
|
|
1454
|
+
text,
|
|
1455
|
+
color,
|
|
1456
|
+
overlay = false,
|
|
1457
|
+
visible = true,
|
|
1458
|
+
testID
|
|
1459
|
+
}) {
|
|
1460
|
+
if (!visible) return null;
|
|
1461
|
+
const content = /* @__PURE__ */ jsxs2(AppView, { center: true, gap: 3, testID, children: [
|
|
1462
|
+
/* @__PURE__ */ jsx13(ActivityIndicator2, { size: "large", color }),
|
|
1463
|
+
text && /* @__PURE__ */ jsx13(AppText, { style: color ? { color } : void 0, children: text })
|
|
1464
|
+
] });
|
|
1465
|
+
if (overlay) {
|
|
1466
|
+
return /* @__PURE__ */ jsx13(AppView, { center: true, flex: true, className: "absolute inset-0 bg-black/30", testID, children: content });
|
|
1467
|
+
}
|
|
1468
|
+
return content;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// src/ui/display/Progress.tsx
|
|
1472
|
+
import { jsx as jsx14 } from "nativewind/jsx-runtime";
|
|
1473
|
+
var sizeMap = { xs: "h-1", sm: "h-1.5", md: "h-2", lg: "h-3", xl: "h-4" };
|
|
1474
|
+
var colorMap = {
|
|
1475
|
+
primary: "bg-primary-500",
|
|
1476
|
+
secondary: "bg-secondary-500",
|
|
1477
|
+
success: "bg-success-500",
|
|
1478
|
+
warning: "bg-warning-500",
|
|
1479
|
+
error: "bg-error-500"
|
|
1480
|
+
};
|
|
1481
|
+
function Progress({
|
|
1482
|
+
value,
|
|
1483
|
+
max = 100,
|
|
1484
|
+
size = "md",
|
|
1485
|
+
color = "primary",
|
|
1486
|
+
testID,
|
|
1487
|
+
className,
|
|
1488
|
+
barClassName
|
|
1489
|
+
}) {
|
|
1490
|
+
const { theme, isDark } = useTheme();
|
|
1491
|
+
const percentage = Math.min(Math.max(value / max * 100, 0), 100);
|
|
1492
|
+
const trackBgColor = isDark ? theme.colors.border?.[700] || "#374151" : "#e5e7eb";
|
|
1493
|
+
return /* @__PURE__ */ jsx14(
|
|
1494
|
+
AppView,
|
|
1495
|
+
{
|
|
1496
|
+
className: cn("w-full rounded-full", sizeMap[size], className),
|
|
1497
|
+
style: { backgroundColor: trackBgColor },
|
|
1498
|
+
testID,
|
|
1499
|
+
children: /* @__PURE__ */ jsx14(
|
|
1500
|
+
AppView,
|
|
1501
|
+
{
|
|
1502
|
+
className: cn("rounded-full", sizeMap[size], colorMap[color], barClassName),
|
|
1503
|
+
style: { width: `${percentage}%` }
|
|
1504
|
+
}
|
|
1505
|
+
)
|
|
1506
|
+
}
|
|
1507
|
+
);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
// src/ui/display/Card.tsx
|
|
1511
|
+
import { View as View3 } from "react-native";
|
|
1512
|
+
import { jsx as jsx15 } from "nativewind/jsx-runtime";
|
|
1513
|
+
function Card({
|
|
1514
|
+
children,
|
|
1515
|
+
className,
|
|
1516
|
+
style,
|
|
1517
|
+
noShadow = false,
|
|
1518
|
+
noBorder = false,
|
|
1519
|
+
noRadius = false,
|
|
1520
|
+
...props
|
|
1521
|
+
}) {
|
|
1522
|
+
const colors = useThemeColors();
|
|
1523
|
+
return /* @__PURE__ */ jsx15(
|
|
1524
|
+
View3,
|
|
1525
|
+
{
|
|
1526
|
+
className: cn(
|
|
1527
|
+
!noRadius && "rounded-lg",
|
|
1528
|
+
!noShadow && "shadow-sm",
|
|
1529
|
+
"overflow-hidden",
|
|
1530
|
+
className
|
|
1531
|
+
),
|
|
1532
|
+
style: [
|
|
1533
|
+
{
|
|
1534
|
+
backgroundColor: colors.card,
|
|
1535
|
+
...noBorder ? {} : { borderWidth: 0.5, borderColor: colors.divider }
|
|
1536
|
+
},
|
|
1537
|
+
style
|
|
1538
|
+
],
|
|
1539
|
+
...props,
|
|
1540
|
+
children
|
|
1541
|
+
}
|
|
1542
|
+
);
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// src/ui/display/Icon.tsx
|
|
1546
|
+
import MaterialIcons from "react-native-vector-icons/MaterialIcons";
|
|
1547
|
+
import { jsx as jsx16 } from "nativewind/jsx-runtime";
|
|
1548
|
+
var sizeMap2 = {
|
|
1549
|
+
xs: 16,
|
|
1550
|
+
sm: 20,
|
|
1551
|
+
md: 24,
|
|
1552
|
+
lg: 32,
|
|
1553
|
+
xl: 48
|
|
1554
|
+
};
|
|
1555
|
+
function resolveSize(size = "md") {
|
|
1556
|
+
if (typeof size === "number") return size;
|
|
1557
|
+
return sizeMap2[size] || 24;
|
|
1558
|
+
}
|
|
1559
|
+
function Icon({ name, size = "md", color = "gray-600", style, onPress, testID }) {
|
|
1560
|
+
const { theme, isDark } = useOptionalTheme();
|
|
1561
|
+
const resolvedSize = resolveSize(size);
|
|
1562
|
+
const resolvedColor = resolveNamedColor(color, theme, isDark) ?? color;
|
|
1563
|
+
if (onPress) {
|
|
1564
|
+
return /* @__PURE__ */ jsx16(AppPressable, { onPress, testID, children: /* @__PURE__ */ jsx16(MaterialIcons, { name, size: resolvedSize, color: resolvedColor, style }) });
|
|
1565
|
+
}
|
|
1566
|
+
return /* @__PURE__ */ jsx16(
|
|
1567
|
+
MaterialIcons,
|
|
1568
|
+
{
|
|
1569
|
+
name,
|
|
1570
|
+
size: resolvedSize,
|
|
1571
|
+
color: resolvedColor,
|
|
1572
|
+
style,
|
|
1573
|
+
testID
|
|
1574
|
+
}
|
|
1575
|
+
);
|
|
1576
|
+
}
|
|
1577
|
+
var NavigationIcons = {
|
|
1578
|
+
home: "home",
|
|
1579
|
+
explore: "explore",
|
|
1580
|
+
profile: "person",
|
|
1581
|
+
settings: "settings",
|
|
1582
|
+
back: "arrow-back",
|
|
1583
|
+
forward: "arrow-forward",
|
|
1584
|
+
close: "close",
|
|
1585
|
+
menu: "menu",
|
|
1586
|
+
more: "more-vert"
|
|
1587
|
+
};
|
|
1588
|
+
var ActionIcons = {
|
|
1589
|
+
add: "add",
|
|
1590
|
+
edit: "edit",
|
|
1591
|
+
delete: "delete",
|
|
1592
|
+
search: "search",
|
|
1593
|
+
share: "share",
|
|
1594
|
+
favorite: "favorite",
|
|
1595
|
+
favoriteBorder: "favorite-border",
|
|
1596
|
+
check: "check",
|
|
1597
|
+
checkCircle: "check-circle",
|
|
1598
|
+
close: "close",
|
|
1599
|
+
closeCircle: "cancel",
|
|
1600
|
+
copy: "content-copy",
|
|
1601
|
+
download: "download",
|
|
1602
|
+
upload: "upload"
|
|
1603
|
+
};
|
|
1604
|
+
var StatusIcons = {
|
|
1605
|
+
info: "info",
|
|
1606
|
+
success: "check-circle",
|
|
1607
|
+
warning: "warning",
|
|
1608
|
+
error: "error",
|
|
1609
|
+
help: "help",
|
|
1610
|
+
loading: "refresh"
|
|
1611
|
+
};
|
|
1612
|
+
var FileIcons = {
|
|
1613
|
+
file: "insert-drive-file",
|
|
1614
|
+
image: "image",
|
|
1615
|
+
video: "videocam",
|
|
1616
|
+
audio: "audiotrack",
|
|
1617
|
+
folder: "folder",
|
|
1618
|
+
folderOpen: "folder-open"
|
|
1619
|
+
};
|
|
1620
|
+
|
|
1621
|
+
// src/ui/display/AppImage.tsx
|
|
1622
|
+
import { useState as useState10, useCallback as useCallback10 } from "react";
|
|
1623
|
+
import {
|
|
1624
|
+
Image,
|
|
1625
|
+
ActivityIndicator as ActivityIndicator3,
|
|
1626
|
+
View as View4
|
|
1627
|
+
} from "react-native";
|
|
1628
|
+
import { jsx as jsx17, jsxs as jsxs3 } from "nativewind/jsx-runtime";
|
|
1629
|
+
var radiusMap = {
|
|
1630
|
+
none: 0,
|
|
1631
|
+
sm: 2,
|
|
1632
|
+
md: 6,
|
|
1633
|
+
lg: 8,
|
|
1634
|
+
xl: 12,
|
|
1635
|
+
"2xl": 16,
|
|
1636
|
+
full: 9999
|
|
1637
|
+
};
|
|
1638
|
+
function resolveRadius(radius) {
|
|
1639
|
+
if (typeof radius === "number") return radius;
|
|
1640
|
+
return radiusMap[radius || "none"];
|
|
1641
|
+
}
|
|
1642
|
+
function SkeletonItem() {
|
|
1643
|
+
return /* @__PURE__ */ jsx17(AppView, { flex: true, className: "bg-gray-200 animate-pulse" });
|
|
1644
|
+
}
|
|
1645
|
+
function AppImage({
|
|
1646
|
+
source,
|
|
1647
|
+
width = "100%",
|
|
1648
|
+
height = "auto",
|
|
1649
|
+
borderRadius = "none",
|
|
1650
|
+
placeholder,
|
|
1651
|
+
errorPlaceholder,
|
|
1652
|
+
loadingIndicator = false,
|
|
1653
|
+
showError = false,
|
|
1654
|
+
resizeMode = "cover",
|
|
1655
|
+
onLoad,
|
|
1656
|
+
onError,
|
|
1657
|
+
onPress,
|
|
1658
|
+
onLongPress,
|
|
1659
|
+
className,
|
|
1660
|
+
style
|
|
1661
|
+
}) {
|
|
1662
|
+
const [isLoading, setIsLoading] = useState10(true);
|
|
1663
|
+
const [hasError, setHasError] = useState10(false);
|
|
1664
|
+
const { theme } = useTheme();
|
|
1665
|
+
const resolvedRadius = resolveRadius(borderRadius);
|
|
1666
|
+
const handleLoad = useCallback10(() => {
|
|
1667
|
+
setIsLoading(false);
|
|
1668
|
+
onLoad?.();
|
|
1669
|
+
}, [onLoad]);
|
|
1670
|
+
const handleError = useCallback10(
|
|
1671
|
+
(error) => {
|
|
1672
|
+
setIsLoading(false);
|
|
1673
|
+
setHasError(true);
|
|
1674
|
+
onError?.(error);
|
|
1675
|
+
},
|
|
1676
|
+
[onError]
|
|
1677
|
+
);
|
|
1678
|
+
const imageStyle = [
|
|
1679
|
+
{
|
|
1680
|
+
width: "100%",
|
|
1681
|
+
height: "100%",
|
|
1682
|
+
borderRadius: resolvedRadius
|
|
1683
|
+
},
|
|
1684
|
+
style
|
|
1685
|
+
];
|
|
1686
|
+
const renderLoading = () => {
|
|
1687
|
+
if (!isLoading) return null;
|
|
1688
|
+
if (placeholder) {
|
|
1689
|
+
return /* @__PURE__ */ jsx17(
|
|
1690
|
+
Image,
|
|
1691
|
+
{
|
|
1692
|
+
source: placeholder,
|
|
1693
|
+
style: {
|
|
1694
|
+
position: "absolute",
|
|
1695
|
+
width: "100%",
|
|
1696
|
+
height: "100%",
|
|
1697
|
+
borderRadius: resolvedRadius
|
|
1698
|
+
},
|
|
1699
|
+
resizeMode
|
|
1700
|
+
}
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
if (loadingIndicator) {
|
|
1704
|
+
if (typeof loadingIndicator === "boolean") {
|
|
1705
|
+
return /* @__PURE__ */ jsx17(
|
|
1706
|
+
AppView,
|
|
1707
|
+
{
|
|
1708
|
+
center: true,
|
|
1709
|
+
className: "absolute inset-0 bg-gray-100",
|
|
1710
|
+
style: { borderRadius: resolvedRadius },
|
|
1711
|
+
children: /* @__PURE__ */ jsx17(ActivityIndicator3, { color: theme.colors.primary?.[500] })
|
|
1712
|
+
}
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
return /* @__PURE__ */ jsx17(AppView, { center: true, className: "absolute inset-0", style: { borderRadius: resolvedRadius }, children: loadingIndicator });
|
|
1716
|
+
}
|
|
1717
|
+
return /* @__PURE__ */ jsx17(SkeletonItem, {});
|
|
1718
|
+
};
|
|
1719
|
+
const renderError = () => {
|
|
1720
|
+
if (!hasError) return null;
|
|
1721
|
+
if (errorPlaceholder) {
|
|
1722
|
+
return /* @__PURE__ */ jsx17(
|
|
1723
|
+
Image,
|
|
1724
|
+
{
|
|
1725
|
+
source: errorPlaceholder,
|
|
1726
|
+
style: {
|
|
1727
|
+
position: "absolute",
|
|
1728
|
+
width: "100%",
|
|
1729
|
+
height: "100%",
|
|
1730
|
+
borderRadius: resolvedRadius
|
|
1731
|
+
},
|
|
1732
|
+
resizeMode
|
|
1733
|
+
}
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
if (showError) {
|
|
1737
|
+
return /* @__PURE__ */ jsx17(
|
|
1738
|
+
AppView,
|
|
1739
|
+
{
|
|
1740
|
+
center: true,
|
|
1741
|
+
className: "absolute inset-0 bg-gray-100",
|
|
1742
|
+
style: { borderRadius: resolvedRadius },
|
|
1743
|
+
children: /* @__PURE__ */ jsx17(Icon, { name: "error", size: "lg", color: "error-500" })
|
|
1744
|
+
}
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
return null;
|
|
1748
|
+
};
|
|
1749
|
+
const isNumberWidth = typeof width === "number";
|
|
1750
|
+
const isNumberHeight = typeof height === "number";
|
|
1751
|
+
const content = /* @__PURE__ */ jsxs3(
|
|
1752
|
+
View4,
|
|
1753
|
+
{
|
|
1754
|
+
className: cn("overflow-hidden", className),
|
|
1755
|
+
style: {
|
|
1756
|
+
width: isNumberWidth ? width : "100%",
|
|
1757
|
+
height: isNumberHeight ? height : void 0,
|
|
1758
|
+
aspectRatio: typeof height === "string" && height.startsWith("aspect-") ? Number(height.replace("aspect-", "").split("/")[0]) / Number(height.replace("aspect-", "").split("/")[1] || 1) : void 0,
|
|
1759
|
+
borderRadius: resolvedRadius
|
|
1760
|
+
},
|
|
1761
|
+
children: [
|
|
1762
|
+
renderLoading(),
|
|
1763
|
+
/* @__PURE__ */ jsx17(
|
|
1764
|
+
Image,
|
|
1765
|
+
{
|
|
1766
|
+
source,
|
|
1767
|
+
style: imageStyle,
|
|
1768
|
+
resizeMode,
|
|
1769
|
+
onLoad: handleLoad,
|
|
1770
|
+
onError: handleError
|
|
1771
|
+
}
|
|
1772
|
+
),
|
|
1773
|
+
renderError()
|
|
1774
|
+
]
|
|
1775
|
+
}
|
|
1776
|
+
);
|
|
1777
|
+
if (onPress || onLongPress) {
|
|
1778
|
+
return /* @__PURE__ */ jsx17(AppPressable, { onPress, onLongPress, children: content });
|
|
1779
|
+
}
|
|
1780
|
+
return content;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
// src/ui/display/AppList.tsx
|
|
1784
|
+
import { useState as useState11, useCallback as useCallback11, useMemo as useMemo3 } from "react";
|
|
1785
|
+
import {
|
|
1786
|
+
FlatList,
|
|
1787
|
+
RefreshControl,
|
|
1788
|
+
ActivityIndicator as ActivityIndicator4,
|
|
1789
|
+
StyleSheet as StyleSheet3
|
|
1790
|
+
} from "react-native";
|
|
1791
|
+
import { Fragment, jsx as jsx18, jsxs as jsxs4 } from "nativewind/jsx-runtime";
|
|
1792
|
+
function SkeletonItem2({ render }) {
|
|
1793
|
+
const colors = useThemeColors();
|
|
1794
|
+
if (render) {
|
|
1795
|
+
return render();
|
|
1796
|
+
}
|
|
1797
|
+
return /* @__PURE__ */ jsx18(AppView, { p: 4, gap: 3, testID: "skeleton", children: /* @__PURE__ */ jsxs4(AppView, { row: true, gap: 3, children: [
|
|
1798
|
+
/* @__PURE__ */ jsx18(AppView, { className: "w-16 h-16 rounded-lg", style: { backgroundColor: colors.divider } }),
|
|
1799
|
+
/* @__PURE__ */ jsxs4(AppView, { flex: true, gap: 2, children: [
|
|
1800
|
+
/* @__PURE__ */ jsx18(AppView, { className: "h-4 w-3/4 rounded", style: { backgroundColor: colors.divider } }),
|
|
1801
|
+
/* @__PURE__ */ jsx18(AppView, { className: "h-3 w-1/2 rounded", style: { backgroundColor: colors.divider } })
|
|
1802
|
+
] })
|
|
1803
|
+
] }) });
|
|
1804
|
+
}
|
|
1805
|
+
function EmptyState({
|
|
1806
|
+
title,
|
|
1807
|
+
description,
|
|
1808
|
+
icon
|
|
1809
|
+
}) {
|
|
1810
|
+
const colors = useThemeColors();
|
|
1811
|
+
return /* @__PURE__ */ jsxs4(Center, { py: 20, children: [
|
|
1812
|
+
/* @__PURE__ */ jsx18(Icon, { name: icon || "inbox", size: 64, color: colors.textMuted }),
|
|
1813
|
+
/* @__PURE__ */ jsx18(AppText, { size: "lg", weight: "medium", className: "mt-4", style: { color: colors.text }, children: title || "\u6682\u65E0\u6570\u636E" }),
|
|
1814
|
+
description && /* @__PURE__ */ jsx18(AppText, { size: "sm", className: "mt-2", style: { color: colors.textMuted }, children: description })
|
|
1815
|
+
] });
|
|
1816
|
+
}
|
|
1817
|
+
function ErrorState({ error, onRetry }) {
|
|
1818
|
+
const colors = useThemeColors();
|
|
1819
|
+
return /* @__PURE__ */ jsxs4(Center, { py: 20, children: [
|
|
1820
|
+
/* @__PURE__ */ jsx18(Icon, { name: "error-outline", size: 64, color: "error-300" }),
|
|
1821
|
+
/* @__PURE__ */ jsx18(AppText, { size: "lg", weight: "medium", color: "error-500", className: "mt-4", children: "\u52A0\u8F7D\u5931\u8D25" }),
|
|
1822
|
+
/* @__PURE__ */ jsx18(AppText, { size: "sm", style: { color: colors.textMuted }, className: "mt-2 text-center px-8", children: error.message || "\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5" }),
|
|
1823
|
+
onRetry && /* @__PURE__ */ jsx18(
|
|
1824
|
+
AppPressable,
|
|
1825
|
+
{
|
|
1826
|
+
onPress: onRetry,
|
|
1827
|
+
className: "mt-6 px-4 py-2 rounded-lg",
|
|
1828
|
+
style: [
|
|
1829
|
+
styles3.retryButton,
|
|
1830
|
+
{ backgroundColor: colors.cardElevated, borderColor: colors.border }
|
|
1831
|
+
],
|
|
1832
|
+
children: /* @__PURE__ */ jsx18(AppText, { style: { color: colors.textSecondary }, className: "text-center", children: "\u91CD\u65B0\u52A0\u8F7D" })
|
|
1833
|
+
}
|
|
1834
|
+
)
|
|
1835
|
+
] });
|
|
1836
|
+
}
|
|
1837
|
+
function LoadMoreFooter({ loading }) {
|
|
1838
|
+
if (!loading) return null;
|
|
1839
|
+
return /* @__PURE__ */ jsx18(Center, { py: 4, children: /* @__PURE__ */ jsx18(ActivityIndicator4, { size: "small" }) });
|
|
1840
|
+
}
|
|
1841
|
+
function Divider({ style }) {
|
|
1842
|
+
const colors = useThemeColors();
|
|
1843
|
+
return /* @__PURE__ */ jsx18(
|
|
1844
|
+
AppView,
|
|
1845
|
+
{
|
|
1846
|
+
className: "h-px",
|
|
1847
|
+
style: [{ marginVertical: 0 }, { backgroundColor: colors.divider }, style]
|
|
1848
|
+
}
|
|
1849
|
+
);
|
|
1850
|
+
}
|
|
1851
|
+
function AppList({
|
|
1852
|
+
data,
|
|
1853
|
+
renderItem,
|
|
1854
|
+
keyExtractor,
|
|
1855
|
+
loading = false,
|
|
1856
|
+
refreshing = false,
|
|
1857
|
+
onRefresh,
|
|
1858
|
+
hasMore = false,
|
|
1859
|
+
onEndReached,
|
|
1860
|
+
onEndReachedThreshold = 0.5,
|
|
1861
|
+
error,
|
|
1862
|
+
onRetry,
|
|
1863
|
+
emptyTitle,
|
|
1864
|
+
emptyDescription,
|
|
1865
|
+
emptyIcon,
|
|
1866
|
+
EmptyComponent,
|
|
1867
|
+
divider = false,
|
|
1868
|
+
dividerStyle,
|
|
1869
|
+
skeletonCount = 6,
|
|
1870
|
+
skeletonRender,
|
|
1871
|
+
ListHeaderComponent,
|
|
1872
|
+
ListFooterComponent,
|
|
1873
|
+
contentContainerStyle,
|
|
1874
|
+
style,
|
|
1875
|
+
numColumns,
|
|
1876
|
+
columnWrapperStyle,
|
|
1877
|
+
horizontal,
|
|
1878
|
+
showsVerticalScrollIndicator,
|
|
1879
|
+
showsHorizontalScrollIndicator
|
|
1880
|
+
}) {
|
|
1881
|
+
const { theme } = useTheme();
|
|
1882
|
+
const [isLoadingMore, setIsLoadingMore] = useState11(false);
|
|
1883
|
+
const handleEndReached = useCallback11(async () => {
|
|
1884
|
+
if (isLoadingMore || !hasMore || !onEndReached) return;
|
|
1885
|
+
setIsLoadingMore(true);
|
|
1886
|
+
try {
|
|
1887
|
+
await onEndReached();
|
|
1888
|
+
} finally {
|
|
1889
|
+
setIsLoadingMore(false);
|
|
1890
|
+
}
|
|
1891
|
+
}, [isLoadingMore, hasMore, onEndReached]);
|
|
1892
|
+
const defaultKeyExtractor = useCallback11(
|
|
1893
|
+
(item, index) => {
|
|
1894
|
+
if (keyExtractor) return keyExtractor(item, index);
|
|
1895
|
+
return `item-${index}`;
|
|
1896
|
+
},
|
|
1897
|
+
[keyExtractor]
|
|
1898
|
+
);
|
|
1899
|
+
const wrappedRenderItem = useCallback11(
|
|
1900
|
+
(info) => {
|
|
1901
|
+
return /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1902
|
+
divider && info.index > 0 && /* @__PURE__ */ jsx18(Divider, { style: dividerStyle }),
|
|
1903
|
+
renderItem(info)
|
|
1904
|
+
] });
|
|
1905
|
+
},
|
|
1906
|
+
[renderItem, divider, dividerStyle]
|
|
1907
|
+
);
|
|
1908
|
+
const skeletonData = useMemo3(
|
|
1909
|
+
() => new Array(skeletonCount).fill(null).map((_, i) => ({ _skeletonId: i })),
|
|
1910
|
+
[skeletonCount]
|
|
1911
|
+
);
|
|
1912
|
+
const skeletonRenderItem = useCallback11(
|
|
1913
|
+
() => /* @__PURE__ */ jsx18(SkeletonItem2, { render: skeletonRender }),
|
|
1914
|
+
[skeletonRender]
|
|
1915
|
+
);
|
|
1916
|
+
if (loading && data.length === 0) {
|
|
1917
|
+
return /* @__PURE__ */ jsx18(
|
|
1918
|
+
FlatList,
|
|
1919
|
+
{
|
|
1920
|
+
data: skeletonData,
|
|
1921
|
+
renderItem: skeletonRenderItem,
|
|
1922
|
+
keyExtractor: (_, index) => `skeleton-${index}`,
|
|
1923
|
+
contentContainerStyle,
|
|
1924
|
+
style,
|
|
1925
|
+
showsVerticalScrollIndicator,
|
|
1926
|
+
showsHorizontalScrollIndicator
|
|
1927
|
+
}
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
if (error && data.length === 0) {
|
|
1931
|
+
return /* @__PURE__ */ jsx18(Center, { style, children: /* @__PURE__ */ jsx18(ErrorState, { error, onRetry }) });
|
|
1932
|
+
}
|
|
1933
|
+
const ListEmptyComponent = useMemo3(() => {
|
|
1934
|
+
if (EmptyComponent) return /* @__PURE__ */ jsx18(EmptyComponent, {});
|
|
1935
|
+
return /* @__PURE__ */ jsx18(EmptyState, { title: emptyTitle, description: emptyDescription, icon: emptyIcon });
|
|
1936
|
+
}, [EmptyComponent, emptyTitle, emptyDescription, emptyIcon]);
|
|
1937
|
+
const FooterComponent = useMemo3(() => {
|
|
1938
|
+
return /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1939
|
+
/* @__PURE__ */ jsx18(LoadMoreFooter, { loading: isLoadingMore }),
|
|
1940
|
+
ListFooterComponent
|
|
1941
|
+
] });
|
|
1942
|
+
}, [isLoadingMore, ListFooterComponent]);
|
|
1943
|
+
return /* @__PURE__ */ jsx18(
|
|
1944
|
+
FlatList,
|
|
1945
|
+
{
|
|
1946
|
+
data,
|
|
1947
|
+
renderItem: wrappedRenderItem,
|
|
1948
|
+
keyExtractor: defaultKeyExtractor,
|
|
1949
|
+
refreshControl: onRefresh ? /* @__PURE__ */ jsx18(
|
|
1950
|
+
RefreshControl,
|
|
1951
|
+
{
|
|
1952
|
+
refreshing,
|
|
1953
|
+
onRefresh,
|
|
1954
|
+
tintColor: theme.colors.primary?.[500],
|
|
1955
|
+
colors: [theme.colors.primary?.[500]]
|
|
1956
|
+
}
|
|
1957
|
+
) : void 0,
|
|
1958
|
+
onEndReached: onEndReached ? handleEndReached : void 0,
|
|
1959
|
+
onEndReachedThreshold,
|
|
1960
|
+
ListEmptyComponent,
|
|
1961
|
+
ListHeaderComponent,
|
|
1962
|
+
ListFooterComponent: FooterComponent,
|
|
1963
|
+
contentContainerStyle,
|
|
1964
|
+
style,
|
|
1965
|
+
numColumns,
|
|
1966
|
+
columnWrapperStyle,
|
|
1967
|
+
horizontal,
|
|
1968
|
+
showsVerticalScrollIndicator,
|
|
1969
|
+
showsHorizontalScrollIndicator,
|
|
1970
|
+
removeClippedSubviews: true,
|
|
1971
|
+
maxToRenderPerBatch: 10,
|
|
1972
|
+
windowSize: 10,
|
|
1973
|
+
initialNumToRender: 10
|
|
1974
|
+
}
|
|
1975
|
+
);
|
|
1976
|
+
}
|
|
1977
|
+
var styles3 = StyleSheet3.create({
|
|
1978
|
+
retryButton: {
|
|
1979
|
+
borderWidth: 0.5
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
|
|
1983
|
+
// src/ui/form/AppInput.tsx
|
|
1984
|
+
import { forwardRef, useState as useState12 } from "react";
|
|
1985
|
+
import { TextInput, View as View5, StyleSheet as StyleSheet4 } from "react-native";
|
|
1986
|
+
import { jsx as jsx19, jsxs as jsxs5 } from "nativewind/jsx-runtime";
|
|
1987
|
+
var AppInput = forwardRef(
|
|
1988
|
+
({ label, error, disabled = false, leftIcon, rightIcon, className, style, ...props }, ref) => {
|
|
1989
|
+
const colors = useThemeColors();
|
|
1990
|
+
const [isFocused, setIsFocused] = useState12(false);
|
|
1991
|
+
const errorColor = "#ef4444";
|
|
1992
|
+
const getBorderColor = () => {
|
|
1993
|
+
if (error) return errorColor;
|
|
1994
|
+
if (isFocused) return colors.primary;
|
|
1995
|
+
return colors.border;
|
|
1996
|
+
};
|
|
1997
|
+
return /* @__PURE__ */ jsxs5(AppView, { className: cn("flex-col gap-1", className), children: [
|
|
1998
|
+
label && /* @__PURE__ */ jsx19(AppText, { size: "sm", weight: "medium", style: { color: colors.textSecondary }, children: label }),
|
|
1999
|
+
/* @__PURE__ */ jsxs5(
|
|
2000
|
+
AppView,
|
|
2001
|
+
{
|
|
2002
|
+
row: true,
|
|
2003
|
+
items: "center",
|
|
2004
|
+
className: "rounded-lg px-3",
|
|
2005
|
+
style: [
|
|
2006
|
+
styles4.inputContainer,
|
|
2007
|
+
{
|
|
2008
|
+
backgroundColor: colors.card,
|
|
2009
|
+
borderColor: getBorderColor(),
|
|
2010
|
+
opacity: disabled ? 0.6 : 1
|
|
2011
|
+
}
|
|
2012
|
+
],
|
|
2013
|
+
children: [
|
|
2014
|
+
leftIcon && /* @__PURE__ */ jsx19(View5, { style: styles4.icon, children: leftIcon }),
|
|
2015
|
+
/* @__PURE__ */ jsx19(
|
|
2016
|
+
TextInput,
|
|
2017
|
+
{
|
|
2018
|
+
ref,
|
|
2019
|
+
className: "flex-1 py-3 text-base",
|
|
2020
|
+
style: [styles4.input, { color: colors.text }, style],
|
|
2021
|
+
placeholderTextColor: colors.textMuted,
|
|
2022
|
+
editable: !disabled,
|
|
2023
|
+
onFocus: (e) => {
|
|
2024
|
+
setIsFocused(true);
|
|
2025
|
+
props.onFocus?.(e);
|
|
2026
|
+
},
|
|
2027
|
+
onBlur: (e) => {
|
|
2028
|
+
setIsFocused(false);
|
|
2029
|
+
props.onBlur?.(e);
|
|
2030
|
+
},
|
|
2031
|
+
...props
|
|
2032
|
+
}
|
|
2033
|
+
),
|
|
2034
|
+
rightIcon && /* @__PURE__ */ jsx19(View5, { style: styles4.icon, children: rightIcon })
|
|
2035
|
+
]
|
|
2036
|
+
}
|
|
2037
|
+
),
|
|
2038
|
+
error && /* @__PURE__ */ jsx19(AppText, { size: "xs", style: { color: errorColor }, children: error })
|
|
2039
|
+
] });
|
|
2040
|
+
}
|
|
2041
|
+
);
|
|
2042
|
+
AppInput.displayName = "AppInput";
|
|
2043
|
+
var styles4 = StyleSheet4.create({
|
|
2044
|
+
inputContainer: {
|
|
2045
|
+
borderWidth: 0.5,
|
|
2046
|
+
minHeight: 48
|
|
2047
|
+
},
|
|
2048
|
+
input: {
|
|
2049
|
+
padding: 0,
|
|
2050
|
+
margin: 0
|
|
2051
|
+
},
|
|
2052
|
+
icon: {
|
|
2053
|
+
marginHorizontal: 4
|
|
2054
|
+
}
|
|
2055
|
+
});
|
|
2056
|
+
|
|
2057
|
+
// src/ui/form/Checkbox.tsx
|
|
2058
|
+
import { useState as useState13 } from "react";
|
|
2059
|
+
import { TouchableOpacity as TouchableOpacity2, StyleSheet as StyleSheet5 } from "react-native";
|
|
2060
|
+
import { jsx as jsx20, jsxs as jsxs6 } from "nativewind/jsx-runtime";
|
|
2061
|
+
function Checkbox({
|
|
2062
|
+
checked,
|
|
2063
|
+
defaultChecked,
|
|
2064
|
+
onChange,
|
|
2065
|
+
disabled = false,
|
|
2066
|
+
children,
|
|
2067
|
+
className,
|
|
2068
|
+
testID
|
|
2069
|
+
}) {
|
|
2070
|
+
const colors = useThemeColors();
|
|
2071
|
+
const [internalChecked, setInternalChecked] = useState13(defaultChecked || false);
|
|
2072
|
+
const isChecked = checked !== void 0 ? checked : internalChecked;
|
|
2073
|
+
const toggle = () => {
|
|
2074
|
+
if (disabled) return;
|
|
2075
|
+
const newChecked = !isChecked;
|
|
2076
|
+
if (checked === void 0) {
|
|
2077
|
+
setInternalChecked(newChecked);
|
|
2078
|
+
}
|
|
2079
|
+
onChange?.(newChecked);
|
|
2080
|
+
};
|
|
2081
|
+
const disabledOpacity = 0.4;
|
|
2082
|
+
return /* @__PURE__ */ jsxs6(
|
|
2083
|
+
TouchableOpacity2,
|
|
2084
|
+
{
|
|
2085
|
+
onPress: toggle,
|
|
2086
|
+
disabled,
|
|
2087
|
+
className: cn("flex-row items-center gap-2", className),
|
|
2088
|
+
style: disabled ? { opacity: disabledOpacity } : void 0,
|
|
2089
|
+
testID,
|
|
2090
|
+
activeOpacity: 0.7,
|
|
2091
|
+
children: [
|
|
2092
|
+
/* @__PURE__ */ jsx20(
|
|
2093
|
+
AppView,
|
|
2094
|
+
{
|
|
2095
|
+
className: cn(
|
|
2096
|
+
"w-5 h-5 rounded items-center justify-center",
|
|
2097
|
+
isChecked ? "bg-primary-500" : "bg-white border"
|
|
2098
|
+
),
|
|
2099
|
+
style: [
|
|
2100
|
+
styles5.checkbox,
|
|
2101
|
+
{
|
|
2102
|
+
backgroundColor: isChecked ? colors.primary : colors.cardElevated,
|
|
2103
|
+
borderColor: isChecked ? colors.primary : colors.border
|
|
2104
|
+
}
|
|
2105
|
+
],
|
|
2106
|
+
children: isChecked && /* @__PURE__ */ jsx20(AppView, { testID: `${testID}-icon`, children: /* @__PURE__ */ jsx20(Icon, { name: "check", size: "sm", color: "white" }) })
|
|
2107
|
+
}
|
|
2108
|
+
),
|
|
2109
|
+
children && /* @__PURE__ */ jsx20(AppText, { size: "sm", style: { color: colors.text }, children })
|
|
2110
|
+
]
|
|
2111
|
+
}
|
|
2112
|
+
);
|
|
2113
|
+
}
|
|
2114
|
+
var styles5 = StyleSheet5.create({
|
|
2115
|
+
checkbox: {
|
|
2116
|
+
borderWidth: 0.5
|
|
2117
|
+
}
|
|
2118
|
+
});
|
|
2119
|
+
|
|
2120
|
+
// src/ui/form/CheckboxGroup.tsx
|
|
2121
|
+
import { jsx as jsx21 } from "nativewind/jsx-runtime";
|
|
2122
|
+
function CheckboxGroup({
|
|
2123
|
+
value = [],
|
|
2124
|
+
onChange,
|
|
2125
|
+
options = [],
|
|
2126
|
+
direction = "column",
|
|
2127
|
+
disabled = false
|
|
2128
|
+
}) {
|
|
2129
|
+
const handleChange = (optionValue, checked) => {
|
|
2130
|
+
if (!onChange) return;
|
|
2131
|
+
if (checked) {
|
|
2132
|
+
onChange([...value, optionValue]);
|
|
2133
|
+
} else {
|
|
2134
|
+
onChange(value.filter((v) => v !== optionValue));
|
|
2135
|
+
}
|
|
2136
|
+
};
|
|
2137
|
+
return /* @__PURE__ */ jsx21(AppView, { row: direction === "row", flex: direction === "row", gap: 4, children: options.map((option) => /* @__PURE__ */ jsx21(
|
|
2138
|
+
Checkbox,
|
|
2139
|
+
{
|
|
2140
|
+
checked: value.includes(option.value),
|
|
2141
|
+
onChange: (checked) => handleChange(option.value, checked),
|
|
2142
|
+
disabled: disabled || option.disabled,
|
|
2143
|
+
children: option.label
|
|
2144
|
+
},
|
|
2145
|
+
option.value
|
|
2146
|
+
)) });
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
// src/ui/form/Radio.tsx
|
|
2150
|
+
import { useState as useState14 } from "react";
|
|
2151
|
+
import { TouchableOpacity as TouchableOpacity3, StyleSheet as StyleSheet6 } from "react-native";
|
|
2152
|
+
import { jsx as jsx22, jsxs as jsxs7 } from "nativewind/jsx-runtime";
|
|
2153
|
+
function Radio({
|
|
2154
|
+
checked,
|
|
2155
|
+
defaultChecked,
|
|
2156
|
+
onChange,
|
|
2157
|
+
disabled = false,
|
|
2158
|
+
children,
|
|
2159
|
+
className,
|
|
2160
|
+
testID
|
|
2161
|
+
}) {
|
|
2162
|
+
const colors = useThemeColors();
|
|
2163
|
+
const [internalChecked, setInternalChecked] = useState14(defaultChecked || false);
|
|
2164
|
+
const isChecked = checked !== void 0 ? checked : internalChecked;
|
|
2165
|
+
const toggle = () => {
|
|
2166
|
+
if (disabled) return;
|
|
2167
|
+
const newChecked = !isChecked;
|
|
2168
|
+
if (checked === void 0) {
|
|
2169
|
+
setInternalChecked(newChecked);
|
|
2170
|
+
}
|
|
2171
|
+
onChange?.(newChecked);
|
|
2172
|
+
};
|
|
2173
|
+
const disabledOpacity = 0.4;
|
|
2174
|
+
return /* @__PURE__ */ jsxs7(
|
|
2175
|
+
TouchableOpacity3,
|
|
2176
|
+
{
|
|
2177
|
+
onPress: toggle,
|
|
2178
|
+
disabled,
|
|
2179
|
+
className: cn("flex-row items-center gap-2", className),
|
|
2180
|
+
style: disabled ? { opacity: disabledOpacity } : void 0,
|
|
2181
|
+
testID,
|
|
2182
|
+
activeOpacity: 0.7,
|
|
2183
|
+
children: [
|
|
2184
|
+
/* @__PURE__ */ jsx22(
|
|
2185
|
+
AppView,
|
|
2186
|
+
{
|
|
2187
|
+
className: cn("w-5 h-5 rounded-full items-center justify-center", isChecked && "border-2"),
|
|
2188
|
+
style: [
|
|
2189
|
+
styles6.radio,
|
|
2190
|
+
{
|
|
2191
|
+
backgroundColor: colors.card,
|
|
2192
|
+
borderColor: isChecked ? colors.primary : colors.border,
|
|
2193
|
+
borderWidth: isChecked ? 0.5 : 0.5
|
|
2194
|
+
}
|
|
2195
|
+
],
|
|
2196
|
+
children: isChecked && /* @__PURE__ */ jsx22(
|
|
2197
|
+
AppView,
|
|
2198
|
+
{
|
|
2199
|
+
className: "rounded-full",
|
|
2200
|
+
style: [styles6.inner, { backgroundColor: colors.primary }]
|
|
2201
|
+
}
|
|
2202
|
+
)
|
|
2203
|
+
}
|
|
2204
|
+
),
|
|
2205
|
+
children && /* @__PURE__ */ jsx22(AppText, { size: "sm", style: { color: colors.text }, children })
|
|
2206
|
+
]
|
|
2207
|
+
}
|
|
2208
|
+
);
|
|
2209
|
+
}
|
|
2210
|
+
var styles6 = StyleSheet6.create({
|
|
2211
|
+
radio: {
|
|
2212
|
+
borderWidth: 0.5
|
|
2213
|
+
},
|
|
2214
|
+
inner: {
|
|
2215
|
+
width: 10,
|
|
2216
|
+
height: 10
|
|
2217
|
+
}
|
|
2218
|
+
});
|
|
2219
|
+
|
|
2220
|
+
// src/ui/form/RadioGroup.tsx
|
|
2221
|
+
import { jsx as jsx23 } from "nativewind/jsx-runtime";
|
|
2222
|
+
function RadioGroup({
|
|
2223
|
+
value,
|
|
2224
|
+
onChange,
|
|
2225
|
+
options = [],
|
|
2226
|
+
direction = "column",
|
|
2227
|
+
disabled = false
|
|
2228
|
+
}) {
|
|
2229
|
+
return /* @__PURE__ */ jsx23(AppView, { row: direction === "row", flex: direction === "row", gap: 4, children: options.map((option) => /* @__PURE__ */ jsx23(
|
|
2230
|
+
Radio,
|
|
2231
|
+
{
|
|
2232
|
+
checked: value === option.value,
|
|
2233
|
+
onChange: () => onChange?.(option.value),
|
|
2234
|
+
disabled: disabled || option.disabled,
|
|
2235
|
+
children: option.label
|
|
2236
|
+
},
|
|
2237
|
+
option.value
|
|
2238
|
+
)) });
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
// src/ui/form/Switch.tsx
|
|
2242
|
+
import { useState as useState15 } from "react";
|
|
2243
|
+
import { TouchableOpacity as TouchableOpacity4, StyleSheet as StyleSheet7 } from "react-native";
|
|
2244
|
+
import { jsx as jsx24 } from "nativewind/jsx-runtime";
|
|
2245
|
+
function Switch({
|
|
2246
|
+
checked,
|
|
2247
|
+
defaultChecked,
|
|
2248
|
+
onChange,
|
|
2249
|
+
disabled = false,
|
|
2250
|
+
size = "md",
|
|
2251
|
+
className,
|
|
2252
|
+
testID,
|
|
2253
|
+
style
|
|
2254
|
+
}) {
|
|
2255
|
+
const colors = useThemeColors();
|
|
2256
|
+
const [internalChecked, setInternalChecked] = useState15(defaultChecked || false);
|
|
2257
|
+
const isChecked = checked !== void 0 ? checked : internalChecked;
|
|
2258
|
+
const toggle = () => {
|
|
2259
|
+
if (disabled) return;
|
|
2260
|
+
const newChecked = !isChecked;
|
|
2261
|
+
if (checked === void 0) {
|
|
2262
|
+
setInternalChecked(newChecked);
|
|
2263
|
+
}
|
|
2264
|
+
onChange?.(newChecked);
|
|
2265
|
+
};
|
|
2266
|
+
const uncheckedTrackColor = colors.divider;
|
|
2267
|
+
const checkedTrackColor = colors.primary;
|
|
2268
|
+
const disabledOpacity = 0.4;
|
|
2269
|
+
const sizes = {
|
|
2270
|
+
sm: { width: 36, height: 20, thumb: 16, padding: 2 },
|
|
2271
|
+
md: { width: 48, height: 26, thumb: 22, padding: 2 },
|
|
2272
|
+
lg: { width: 60, height: 32, thumb: 28, padding: 2 }
|
|
2273
|
+
};
|
|
2274
|
+
const config = sizes[size];
|
|
2275
|
+
const thumbPosition = isChecked ? config.width - config.thumb - config.padding : config.padding;
|
|
2276
|
+
return /* @__PURE__ */ jsx24(
|
|
2277
|
+
TouchableOpacity4,
|
|
2278
|
+
{
|
|
2279
|
+
onPress: toggle,
|
|
2280
|
+
disabled,
|
|
2281
|
+
className: cn(className),
|
|
2282
|
+
testID,
|
|
2283
|
+
activeOpacity: disabled ? 1 : 0.8,
|
|
2284
|
+
children: /* @__PURE__ */ jsx24(
|
|
2285
|
+
AppView,
|
|
2286
|
+
{
|
|
2287
|
+
className: "rounded-full",
|
|
2288
|
+
style: [
|
|
2289
|
+
styles7.track,
|
|
2290
|
+
{
|
|
2291
|
+
width: config.width,
|
|
2292
|
+
height: config.height,
|
|
2293
|
+
backgroundColor: isChecked ? checkedTrackColor : uncheckedTrackColor,
|
|
2294
|
+
opacity: disabled ? disabledOpacity : 1
|
|
2295
|
+
},
|
|
2296
|
+
style
|
|
2297
|
+
],
|
|
2298
|
+
children: /* @__PURE__ */ jsx24(
|
|
2299
|
+
AppView,
|
|
2300
|
+
{
|
|
2301
|
+
className: "rounded-full",
|
|
2302
|
+
style: [
|
|
2303
|
+
styles7.thumb,
|
|
2304
|
+
{
|
|
2305
|
+
width: config.thumb,
|
|
2306
|
+
height: config.thumb,
|
|
2307
|
+
backgroundColor: colors.textInverse,
|
|
2308
|
+
transform: [{ translateX: thumbPosition }],
|
|
2309
|
+
shadowColor: "#000000",
|
|
2310
|
+
shadowOffset: { width: 0, height: 1 },
|
|
2311
|
+
shadowOpacity: 0.25,
|
|
2312
|
+
shadowRadius: 1
|
|
2313
|
+
}
|
|
2314
|
+
]
|
|
2315
|
+
}
|
|
2316
|
+
)
|
|
2317
|
+
}
|
|
2318
|
+
)
|
|
2319
|
+
}
|
|
2320
|
+
);
|
|
2321
|
+
}
|
|
2322
|
+
var styles7 = StyleSheet7.create({
|
|
2323
|
+
track: {
|
|
2324
|
+
justifyContent: "center",
|
|
2325
|
+
padding: 2
|
|
2326
|
+
},
|
|
2327
|
+
thumb: {
|
|
2328
|
+
elevation: 2,
|
|
2329
|
+
shadowColor: "#000000",
|
|
2330
|
+
shadowOffset: { width: 0, height: 1 },
|
|
2331
|
+
shadowOpacity: 0.2,
|
|
2332
|
+
shadowRadius: 1
|
|
2333
|
+
}
|
|
2334
|
+
});
|
|
2335
|
+
|
|
2336
|
+
// src/ui/form/Slider.tsx
|
|
2337
|
+
import { useState as useState16, useCallback as useCallback12, useRef as useRef6 } from "react";
|
|
2338
|
+
import {
|
|
2339
|
+
View as View6,
|
|
2340
|
+
PanResponder,
|
|
2341
|
+
StyleSheet as StyleSheet8
|
|
2342
|
+
} from "react-native";
|
|
2343
|
+
|
|
2344
|
+
// src/ui/form/useFormTheme.ts
|
|
2345
|
+
import { useMemo as useMemo4 } from "react";
|
|
2346
|
+
function useFormThemeColors() {
|
|
2347
|
+
const { isDark } = useOptionalTheme();
|
|
2348
|
+
const colors = useThemeColors();
|
|
2349
|
+
return useMemo4(
|
|
2350
|
+
() => ({
|
|
2351
|
+
primary: colors.primary,
|
|
2352
|
+
primarySurface: colors.primarySurface,
|
|
2353
|
+
surface: colors.cardElevated,
|
|
2354
|
+
surfaceMuted: isDark ? colors.divider : "#f3f4f6",
|
|
2355
|
+
headerSurface: isDark ? "#111827" : "#f3f4f6",
|
|
2356
|
+
text: colors.text,
|
|
2357
|
+
textSecondary: colors.textSecondary,
|
|
2358
|
+
textMuted: colors.textMuted,
|
|
2359
|
+
textInverse: colors.textInverse,
|
|
2360
|
+
border: colors.border,
|
|
2361
|
+
divider: colors.divider,
|
|
2362
|
+
icon: colors.textMuted,
|
|
2363
|
+
overlay: "rgba(0,0,0,0.5)"
|
|
2364
|
+
}),
|
|
2365
|
+
[colors, isDark]
|
|
2366
|
+
);
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
// src/ui/form/Slider.tsx
|
|
2370
|
+
import { jsx as jsx25, jsxs as jsxs8 } from "nativewind/jsx-runtime";
|
|
2371
|
+
function Slider({
|
|
2372
|
+
value,
|
|
2373
|
+
defaultValue = 0,
|
|
2374
|
+
min = 0,
|
|
2375
|
+
max = 100,
|
|
2376
|
+
step = 1,
|
|
2377
|
+
disabled = false,
|
|
2378
|
+
showTooltip = false,
|
|
2379
|
+
onChange,
|
|
2380
|
+
onChangeEnd,
|
|
2381
|
+
className
|
|
2382
|
+
}) {
|
|
2383
|
+
const colors = useFormThemeColors();
|
|
2384
|
+
const [internalValue, setInternalValue] = useState16(defaultValue);
|
|
2385
|
+
const [trackWidth, setTrackWidth] = useState16(0);
|
|
2386
|
+
const [isDragging, setIsDragging] = useState16(false);
|
|
2387
|
+
const currentValue = value !== void 0 ? value : internalValue;
|
|
2388
|
+
const disabledOpacity = 0.4;
|
|
2389
|
+
const progress = (currentValue - min) / (max - min) * 100;
|
|
2390
|
+
const getValueFromPosition = useCallback12(
|
|
2391
|
+
(position) => {
|
|
2392
|
+
const percentage = Math.max(0, Math.min(1, position / trackWidth));
|
|
2393
|
+
const rawValue = min + percentage * (max - min);
|
|
2394
|
+
const steppedValue = Math.round(rawValue / step) * step;
|
|
2395
|
+
return Math.min(max, Math.max(min, steppedValue));
|
|
2396
|
+
},
|
|
2397
|
+
[trackWidth, min, max, step]
|
|
2398
|
+
);
|
|
2399
|
+
const setValue = useCallback12(
|
|
2400
|
+
(newValue) => {
|
|
2401
|
+
const clampedValue = Math.min(max, Math.max(min, newValue));
|
|
2402
|
+
if (value === void 0) {
|
|
2403
|
+
setInternalValue(clampedValue);
|
|
2404
|
+
}
|
|
2405
|
+
onChange?.(clampedValue);
|
|
2406
|
+
},
|
|
2407
|
+
[value, min, max, onChange]
|
|
2408
|
+
);
|
|
2409
|
+
const panResponder = useRef6(
|
|
2410
|
+
PanResponder.create({
|
|
2411
|
+
onStartShouldSetPanResponder: () => !disabled,
|
|
2412
|
+
onMoveShouldSetPanResponder: () => !disabled,
|
|
2413
|
+
onPanResponderGrant: () => {
|
|
2414
|
+
setIsDragging(true);
|
|
2415
|
+
},
|
|
2416
|
+
onPanResponderMove: (_, gestureState) => {
|
|
2417
|
+
const position = progress / 100 * trackWidth + gestureState.dx;
|
|
2418
|
+
const newValue = getValueFromPosition(position);
|
|
2419
|
+
setValue(newValue);
|
|
2420
|
+
},
|
|
2421
|
+
onPanResponderRelease: (_, gestureState) => {
|
|
2422
|
+
const position = progress / 100 * trackWidth + gestureState.dx;
|
|
2423
|
+
const newValue = getValueFromPosition(position);
|
|
2424
|
+
setValue(newValue);
|
|
2425
|
+
setIsDragging(false);
|
|
2426
|
+
onChangeEnd?.(newValue);
|
|
2427
|
+
}
|
|
2428
|
+
})
|
|
2429
|
+
).current;
|
|
2430
|
+
const handleTrackPress = useCallback12(
|
|
2431
|
+
(event) => {
|
|
2432
|
+
if (disabled) return;
|
|
2433
|
+
const { locationX } = event.nativeEvent;
|
|
2434
|
+
const newValue = getValueFromPosition(locationX);
|
|
2435
|
+
setValue(newValue);
|
|
2436
|
+
onChangeEnd?.(newValue);
|
|
2437
|
+
},
|
|
2438
|
+
[disabled, getValueFromPosition, setValue, onChangeEnd]
|
|
2439
|
+
);
|
|
2440
|
+
const onLayout = useCallback12((event) => {
|
|
2441
|
+
setTrackWidth(event.nativeEvent.layout.width);
|
|
2442
|
+
}, []);
|
|
2443
|
+
return /* @__PURE__ */ jsxs8(AppView, { className: cn("py-2", className), children: [
|
|
2444
|
+
showTooltip && isDragging && /* @__PURE__ */ jsxs8(
|
|
2445
|
+
AppView,
|
|
2446
|
+
{
|
|
2447
|
+
className: "absolute rounded px-2 py-1 -top-8",
|
|
2448
|
+
style: [
|
|
2449
|
+
styles8.tooltip,
|
|
2450
|
+
{
|
|
2451
|
+
backgroundColor: colors.surfaceMuted,
|
|
2452
|
+
left: `${progress}%`,
|
|
2453
|
+
transform: [{ translateX: -16 }]
|
|
2454
|
+
}
|
|
2455
|
+
],
|
|
2456
|
+
children: [
|
|
2457
|
+
/* @__PURE__ */ jsx25(AppText, { size: "xs", style: { color: colors.text }, children: Math.round(currentValue) }),
|
|
2458
|
+
/* @__PURE__ */ jsx25(
|
|
2459
|
+
AppView,
|
|
2460
|
+
{
|
|
2461
|
+
style: [
|
|
2462
|
+
styles8.tooltipArrow,
|
|
2463
|
+
{
|
|
2464
|
+
borderTopColor: colors.surfaceMuted
|
|
2465
|
+
}
|
|
2466
|
+
]
|
|
2467
|
+
}
|
|
2468
|
+
)
|
|
2469
|
+
]
|
|
2470
|
+
}
|
|
2471
|
+
),
|
|
2472
|
+
/* @__PURE__ */ jsxs8(
|
|
2473
|
+
View6,
|
|
2474
|
+
{
|
|
2475
|
+
onLayout,
|
|
2476
|
+
className: "rounded-full",
|
|
2477
|
+
style: [
|
|
2478
|
+
styles8.track,
|
|
2479
|
+
{ backgroundColor: colors.divider, opacity: disabled ? disabledOpacity : 1 }
|
|
2480
|
+
],
|
|
2481
|
+
onTouchEnd: handleTrackPress,
|
|
2482
|
+
children: [
|
|
2483
|
+
/* @__PURE__ */ jsx25(
|
|
2484
|
+
AppView,
|
|
2485
|
+
{
|
|
2486
|
+
className: "rounded-full",
|
|
2487
|
+
style: [
|
|
2488
|
+
styles8.filledTrack,
|
|
2489
|
+
{
|
|
2490
|
+
backgroundColor: colors.primary,
|
|
2491
|
+
width: `${progress}%`
|
|
2492
|
+
}
|
|
2493
|
+
]
|
|
2494
|
+
}
|
|
2495
|
+
),
|
|
2496
|
+
/* @__PURE__ */ jsx25(
|
|
2497
|
+
AppView,
|
|
2498
|
+
{
|
|
2499
|
+
className: "absolute rounded-full items-center justify-center",
|
|
2500
|
+
style: [
|
|
2501
|
+
styles8.thumb,
|
|
2502
|
+
{
|
|
2503
|
+
backgroundColor: colors.textInverse,
|
|
2504
|
+
left: `${progress}%`,
|
|
2505
|
+
transform: [{ translateX: -12 }],
|
|
2506
|
+
shadowColor: "#000000",
|
|
2507
|
+
shadowOffset: { width: 0, height: 2 },
|
|
2508
|
+
shadowOpacity: 0.25,
|
|
2509
|
+
shadowRadius: 2
|
|
2510
|
+
}
|
|
2511
|
+
],
|
|
2512
|
+
...panResponder.panHandlers,
|
|
2513
|
+
children: /* @__PURE__ */ jsx25(
|
|
2514
|
+
AppView,
|
|
2515
|
+
{
|
|
2516
|
+
className: "rounded-full",
|
|
2517
|
+
style: [
|
|
2518
|
+
styles8.thumbDot,
|
|
2519
|
+
{
|
|
2520
|
+
backgroundColor: colors.primary
|
|
2521
|
+
}
|
|
2522
|
+
]
|
|
2523
|
+
}
|
|
2524
|
+
)
|
|
2525
|
+
}
|
|
2526
|
+
)
|
|
2527
|
+
]
|
|
2528
|
+
}
|
|
2529
|
+
)
|
|
2530
|
+
] });
|
|
2531
|
+
}
|
|
2532
|
+
var styles8 = StyleSheet8.create({
|
|
2533
|
+
track: {
|
|
2534
|
+
height: 6,
|
|
2535
|
+
width: "100%"
|
|
2536
|
+
},
|
|
2537
|
+
filledTrack: {
|
|
2538
|
+
height: 6
|
|
2539
|
+
},
|
|
2540
|
+
thumb: {
|
|
2541
|
+
width: 24,
|
|
2542
|
+
height: 24,
|
|
2543
|
+
top: -9,
|
|
2544
|
+
elevation: 3,
|
|
2545
|
+
shadowColor: "#000000",
|
|
2546
|
+
shadowOffset: { width: 0, height: 2 },
|
|
2547
|
+
shadowOpacity: 0.2,
|
|
2548
|
+
shadowRadius: 2
|
|
2549
|
+
},
|
|
2550
|
+
thumbDot: {
|
|
2551
|
+
width: 8,
|
|
2552
|
+
height: 8
|
|
2553
|
+
},
|
|
2554
|
+
tooltip: {
|
|
2555
|
+
minWidth: 32,
|
|
2556
|
+
alignItems: "center",
|
|
2557
|
+
elevation: 4
|
|
2558
|
+
},
|
|
2559
|
+
tooltipArrow: {
|
|
2560
|
+
position: "absolute",
|
|
2561
|
+
bottom: -4,
|
|
2562
|
+
width: 0,
|
|
2563
|
+
height: 0,
|
|
2564
|
+
borderLeftWidth: 4,
|
|
2565
|
+
borderRightWidth: 4,
|
|
2566
|
+
borderTopWidth: 4,
|
|
2567
|
+
borderLeftColor: "transparent",
|
|
2568
|
+
borderRightColor: "transparent"
|
|
2569
|
+
}
|
|
2570
|
+
});
|
|
2571
|
+
|
|
2572
|
+
// src/ui/form/Select.tsx
|
|
2573
|
+
import { useState as useState17, useCallback as useCallback13, useMemo as useMemo5 } from "react";
|
|
2574
|
+
import {
|
|
2575
|
+
Modal as Modal2,
|
|
2576
|
+
View as View7,
|
|
2577
|
+
TouchableOpacity as TouchableOpacity5,
|
|
2578
|
+
FlatList as FlatList2,
|
|
2579
|
+
TextInput as TextInput2,
|
|
2580
|
+
StyleSheet as StyleSheet9
|
|
2581
|
+
} from "react-native";
|
|
2582
|
+
import { Fragment as Fragment2, jsx as jsx26, jsxs as jsxs9 } from "nativewind/jsx-runtime";
|
|
2583
|
+
function Select({
|
|
2584
|
+
value,
|
|
2585
|
+
onChange,
|
|
2586
|
+
options,
|
|
2587
|
+
placeholder = "\u8BF7\u9009\u62E9",
|
|
2588
|
+
multiple = false,
|
|
2589
|
+
searchable = false,
|
|
2590
|
+
onSearch,
|
|
2591
|
+
disabled = false,
|
|
2592
|
+
clearable = true,
|
|
2593
|
+
className
|
|
2594
|
+
}) {
|
|
2595
|
+
const colors = useFormThemeColors();
|
|
2596
|
+
const [visible, setVisible] = useState17(false);
|
|
2597
|
+
const [searchKeyword, setSearchKeyword] = useState17("");
|
|
2598
|
+
const selectedValues = useMemo5(() => {
|
|
2599
|
+
if (multiple) {
|
|
2600
|
+
return Array.isArray(value) ? value : [];
|
|
2601
|
+
}
|
|
2602
|
+
return value ? [value] : [];
|
|
2603
|
+
}, [value, multiple]);
|
|
2604
|
+
const displayText = useMemo5(() => {
|
|
2605
|
+
if (selectedValues.length === 0) return placeholder;
|
|
2606
|
+
const selectedLabels = options.filter((opt) => selectedValues.includes(opt.value)).map((opt) => opt.label);
|
|
2607
|
+
return selectedLabels.join(", ") || placeholder;
|
|
2608
|
+
}, [selectedValues, options, placeholder]);
|
|
2609
|
+
const filteredOptions = useMemo5(() => {
|
|
2610
|
+
if (!searchable || !searchKeyword) return options;
|
|
2611
|
+
return options.filter((opt) => opt.label.toLowerCase().includes(searchKeyword.toLowerCase()));
|
|
2612
|
+
}, [options, searchable, searchKeyword]);
|
|
2613
|
+
const handleSelect = useCallback13(
|
|
2614
|
+
(optionValue) => {
|
|
2615
|
+
if (multiple) {
|
|
2616
|
+
const currentValues = Array.isArray(value) ? value : [];
|
|
2617
|
+
const newValues = currentValues.includes(optionValue) ? currentValues.filter((v) => v !== optionValue) : [...currentValues, optionValue];
|
|
2618
|
+
onChange?.(newValues);
|
|
2619
|
+
} else {
|
|
2620
|
+
onChange?.(optionValue);
|
|
2621
|
+
setVisible(false);
|
|
2622
|
+
}
|
|
2623
|
+
},
|
|
2624
|
+
[multiple, value, onChange]
|
|
2625
|
+
);
|
|
2626
|
+
const handleClear = useCallback13(
|
|
2627
|
+
(e) => {
|
|
2628
|
+
e.stopPropagation();
|
|
2629
|
+
onChange?.(multiple ? [] : "");
|
|
2630
|
+
},
|
|
2631
|
+
[multiple, onChange]
|
|
2632
|
+
);
|
|
2633
|
+
const handleSearch = useCallback13(
|
|
2634
|
+
(text) => {
|
|
2635
|
+
setSearchKeyword(text);
|
|
2636
|
+
onSearch?.(text);
|
|
2637
|
+
},
|
|
2638
|
+
[onSearch]
|
|
2639
|
+
);
|
|
2640
|
+
const renderOption = useCallback13(
|
|
2641
|
+
({ item }) => {
|
|
2642
|
+
const isSelected = selectedValues.includes(item.value);
|
|
2643
|
+
return /* @__PURE__ */ jsxs9(
|
|
2644
|
+
AppPressable,
|
|
2645
|
+
{
|
|
2646
|
+
className: cn(
|
|
2647
|
+
"flex-row items-center justify-between px-4 py-3",
|
|
2648
|
+
isSelected && "bg-primary-50"
|
|
2649
|
+
),
|
|
2650
|
+
style: [
|
|
2651
|
+
styles9.optionItem,
|
|
2652
|
+
{ borderBottomColor: colors.divider },
|
|
2653
|
+
isSelected && { backgroundColor: colors.primarySurface }
|
|
2654
|
+
],
|
|
2655
|
+
onPress: () => handleSelect(item.value),
|
|
2656
|
+
children: [
|
|
2657
|
+
/* @__PURE__ */ jsx26(AppText, { style: { color: isSelected ? colors.primary : colors.text }, children: item.label }),
|
|
2658
|
+
isSelected && /* @__PURE__ */ jsx26(Icon, { name: "check", size: "sm", color: "primary-500" })
|
|
2659
|
+
]
|
|
2660
|
+
}
|
|
2661
|
+
);
|
|
2662
|
+
},
|
|
2663
|
+
[selectedValues, handleSelect, colors]
|
|
2664
|
+
);
|
|
2665
|
+
return /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
2666
|
+
/* @__PURE__ */ jsxs9(
|
|
2667
|
+
AppPressable,
|
|
2668
|
+
{
|
|
2669
|
+
className: cn(
|
|
2670
|
+
"flex-row items-center justify-between px-4 py-3 rounded-lg",
|
|
2671
|
+
disabled ? "opacity-60" : "",
|
|
2672
|
+
className
|
|
2673
|
+
),
|
|
2674
|
+
style: [styles9.trigger, { backgroundColor: colors.surface, borderColor: colors.border }],
|
|
2675
|
+
disabled,
|
|
2676
|
+
onPress: () => setVisible(true),
|
|
2677
|
+
children: [
|
|
2678
|
+
/* @__PURE__ */ jsx26(
|
|
2679
|
+
AppText,
|
|
2680
|
+
{
|
|
2681
|
+
className: "flex-1",
|
|
2682
|
+
style: { color: selectedValues.length === 0 ? colors.textMuted : colors.text },
|
|
2683
|
+
numberOfLines: 1,
|
|
2684
|
+
children: displayText
|
|
2685
|
+
}
|
|
2686
|
+
),
|
|
2687
|
+
/* @__PURE__ */ jsxs9(View7, { className: "flex-row items-center", children: [
|
|
2688
|
+
clearable && selectedValues.length > 0 && !disabled && /* @__PURE__ */ jsx26(TouchableOpacity5, { onPress: handleClear, className: "mr-2 p-1", children: /* @__PURE__ */ jsx26(Icon, { name: "close", size: "sm", color: colors.icon }) }),
|
|
2689
|
+
/* @__PURE__ */ jsx26(Icon, { name: "keyboard-arrow-down", size: "md", color: colors.icon })
|
|
2690
|
+
] })
|
|
2691
|
+
]
|
|
2692
|
+
}
|
|
2693
|
+
),
|
|
2694
|
+
/* @__PURE__ */ jsx26(
|
|
2695
|
+
Modal2,
|
|
2696
|
+
{
|
|
2697
|
+
visible,
|
|
2698
|
+
transparent: true,
|
|
2699
|
+
animationType: "slide",
|
|
2700
|
+
onRequestClose: () => setVisible(false),
|
|
2701
|
+
children: /* @__PURE__ */ jsx26(AppView, { className: "flex-1", style: { backgroundColor: colors.overlay }, justify: "end", children: /* @__PURE__ */ jsxs9(
|
|
2702
|
+
AppView,
|
|
2703
|
+
{
|
|
2704
|
+
className: "rounded-t-2xl max-h-[70%]",
|
|
2705
|
+
style: { backgroundColor: colors.surface },
|
|
2706
|
+
children: [
|
|
2707
|
+
/* @__PURE__ */ jsxs9(
|
|
2708
|
+
AppView,
|
|
2709
|
+
{
|
|
2710
|
+
row: true,
|
|
2711
|
+
between: true,
|
|
2712
|
+
items: "center",
|
|
2713
|
+
className: "px-4 py-3",
|
|
2714
|
+
style: [styles9.header, { borderBottomColor: colors.divider }],
|
|
2715
|
+
children: [
|
|
2716
|
+
/* @__PURE__ */ jsx26(AppText, { className: "text-lg font-semibold", style: { color: colors.text }, children: multiple ? "\u9009\u62E9\u9009\u9879" : "\u8BF7\u9009\u62E9" }),
|
|
2717
|
+
/* @__PURE__ */ jsx26(TouchableOpacity5, { onPress: () => setVisible(false), children: /* @__PURE__ */ jsx26(Icon, { name: "close", size: "md", color: colors.icon }) })
|
|
2718
|
+
]
|
|
2719
|
+
}
|
|
2720
|
+
),
|
|
2721
|
+
searchable && /* @__PURE__ */ jsx26(
|
|
2722
|
+
AppView,
|
|
2723
|
+
{
|
|
2724
|
+
className: "px-4 py-3",
|
|
2725
|
+
style: [styles9.searchBox, { borderBottomColor: colors.divider }],
|
|
2726
|
+
children: /* @__PURE__ */ jsxs9(
|
|
2727
|
+
AppView,
|
|
2728
|
+
{
|
|
2729
|
+
row: true,
|
|
2730
|
+
items: "center",
|
|
2731
|
+
className: "px-3 py-2 rounded-lg",
|
|
2732
|
+
style: { backgroundColor: colors.surfaceMuted },
|
|
2733
|
+
children: [
|
|
2734
|
+
/* @__PURE__ */ jsx26(View7, { style: { marginRight: 8 }, children: /* @__PURE__ */ jsx26(Icon, { name: "search", size: "sm", color: colors.icon }) }),
|
|
2735
|
+
/* @__PURE__ */ jsx26(
|
|
2736
|
+
TextInput2,
|
|
2737
|
+
{
|
|
2738
|
+
className: "flex-1 text-base",
|
|
2739
|
+
style: { color: colors.text },
|
|
2740
|
+
placeholder: "\u641C\u7D22...",
|
|
2741
|
+
placeholderTextColor: colors.textMuted,
|
|
2742
|
+
value: searchKeyword,
|
|
2743
|
+
onChangeText: handleSearch,
|
|
2744
|
+
autoFocus: true
|
|
2745
|
+
}
|
|
2746
|
+
),
|
|
2747
|
+
searchKeyword.length > 0 && /* @__PURE__ */ jsx26(TouchableOpacity5, { onPress: () => setSearchKeyword(""), children: /* @__PURE__ */ jsx26(Icon, { name: "close", size: "sm", color: colors.icon }) })
|
|
2748
|
+
]
|
|
2749
|
+
}
|
|
2750
|
+
)
|
|
2751
|
+
}
|
|
2752
|
+
),
|
|
2753
|
+
/* @__PURE__ */ jsx26(
|
|
2754
|
+
FlatList2,
|
|
2755
|
+
{
|
|
2756
|
+
data: filteredOptions,
|
|
2757
|
+
keyExtractor: (item) => item.value,
|
|
2758
|
+
renderItem: renderOption,
|
|
2759
|
+
ListEmptyComponent: /* @__PURE__ */ jsx26(AppView, { center: true, className: "py-8", children: /* @__PURE__ */ jsx26(AppText, { style: { color: colors.textMuted }, children: "\u6682\u65E0\u9009\u9879" }) })
|
|
2760
|
+
}
|
|
2761
|
+
),
|
|
2762
|
+
multiple && /* @__PURE__ */ jsxs9(
|
|
2763
|
+
AppView,
|
|
2764
|
+
{
|
|
2765
|
+
row: true,
|
|
2766
|
+
between: true,
|
|
2767
|
+
items: "center",
|
|
2768
|
+
className: "px-4 py-3",
|
|
2769
|
+
style: [styles9.footer, { borderTopColor: colors.divider }],
|
|
2770
|
+
children: [
|
|
2771
|
+
/* @__PURE__ */ jsxs9(AppText, { style: { color: colors.textMuted }, children: [
|
|
2772
|
+
"\u5DF2\u9009\u62E9 ",
|
|
2773
|
+
selectedValues.length,
|
|
2774
|
+
" \u9879"
|
|
2775
|
+
] }),
|
|
2776
|
+
/* @__PURE__ */ jsx26(
|
|
2777
|
+
TouchableOpacity5,
|
|
2778
|
+
{
|
|
2779
|
+
className: "px-4 py-2 rounded-lg",
|
|
2780
|
+
style: { backgroundColor: colors.primary },
|
|
2781
|
+
onPress: () => setVisible(false),
|
|
2782
|
+
children: /* @__PURE__ */ jsx26(AppText, { className: "font-medium", style: { color: colors.textInverse }, children: "\u786E\u5B9A" })
|
|
2783
|
+
}
|
|
2784
|
+
)
|
|
2785
|
+
]
|
|
2786
|
+
}
|
|
2787
|
+
)
|
|
2788
|
+
]
|
|
2789
|
+
}
|
|
2790
|
+
) })
|
|
2791
|
+
}
|
|
2792
|
+
)
|
|
2793
|
+
] });
|
|
2794
|
+
}
|
|
2795
|
+
var styles9 = StyleSheet9.create({
|
|
2796
|
+
trigger: {
|
|
2797
|
+
borderWidth: 0.5
|
|
2798
|
+
},
|
|
2799
|
+
header: {
|
|
2800
|
+
borderBottomWidth: 0.5
|
|
2801
|
+
},
|
|
2802
|
+
searchBox: {
|
|
2803
|
+
borderBottomWidth: 0.5
|
|
2804
|
+
},
|
|
2805
|
+
optionItem: {
|
|
2806
|
+
borderBottomWidth: 0.5
|
|
2807
|
+
},
|
|
2808
|
+
footer: {
|
|
2809
|
+
borderTopWidth: 0.5
|
|
2810
|
+
}
|
|
2811
|
+
});
|
|
2812
|
+
|
|
2813
|
+
// src/ui/form/DatePicker.tsx
|
|
2814
|
+
import { useState as useState18, useCallback as useCallback14, useMemo as useMemo6 } from "react";
|
|
2815
|
+
import { Modal as Modal3, TouchableOpacity as TouchableOpacity6, StyleSheet as StyleSheet10 } from "react-native";
|
|
2816
|
+
import { Fragment as Fragment3, jsx as jsx27, jsxs as jsxs10 } from "nativewind/jsx-runtime";
|
|
2817
|
+
function PickerColumn({
|
|
2818
|
+
title,
|
|
2819
|
+
values,
|
|
2820
|
+
selectedValue,
|
|
2821
|
+
onSelect,
|
|
2822
|
+
isDisabled,
|
|
2823
|
+
formatLabel = (value) => String(value),
|
|
2824
|
+
showDivider = false,
|
|
2825
|
+
colors
|
|
2826
|
+
}) {
|
|
2827
|
+
return /* @__PURE__ */ jsxs10(
|
|
2828
|
+
AppView,
|
|
2829
|
+
{
|
|
2830
|
+
flex: true,
|
|
2831
|
+
style: [
|
|
2832
|
+
showDivider && styles10.column,
|
|
2833
|
+
showDivider ? { borderRightColor: colors.divider } : void 0
|
|
2834
|
+
],
|
|
2835
|
+
children: [
|
|
2836
|
+
/* @__PURE__ */ jsx27(AppView, { center: true, className: "py-2", style: { backgroundColor: colors.headerSurface }, children: /* @__PURE__ */ jsx27(AppText, { className: "text-sm font-medium", style: { color: colors.textMuted }, children: title }) }),
|
|
2837
|
+
/* @__PURE__ */ jsx27(AppView, { className: "flex-1", children: values.map((value) => {
|
|
2838
|
+
const selected = selectedValue === value;
|
|
2839
|
+
const disabled = isDisabled(value);
|
|
2840
|
+
return /* @__PURE__ */ jsx27(
|
|
2841
|
+
TouchableOpacity6,
|
|
2842
|
+
{
|
|
2843
|
+
className: cn("py-2 items-center", selected && "bg-primary-50"),
|
|
2844
|
+
style: selected ? { backgroundColor: colors.primarySurface } : void 0,
|
|
2845
|
+
disabled,
|
|
2846
|
+
onPress: () => onSelect(value),
|
|
2847
|
+
children: /* @__PURE__ */ jsx27(
|
|
2848
|
+
AppText,
|
|
2849
|
+
{
|
|
2850
|
+
className: cn(selected ? "font-semibold" : void 0, disabled && "opacity-30"),
|
|
2851
|
+
style: {
|
|
2852
|
+
color: selected ? colors.primary : colors.textSecondary
|
|
2853
|
+
},
|
|
2854
|
+
children: formatLabel(value)
|
|
2855
|
+
}
|
|
2856
|
+
)
|
|
2857
|
+
},
|
|
2858
|
+
value
|
|
2859
|
+
);
|
|
2860
|
+
}) })
|
|
2861
|
+
]
|
|
2862
|
+
}
|
|
2863
|
+
);
|
|
2864
|
+
}
|
|
2865
|
+
function DatePicker({
|
|
2866
|
+
value,
|
|
2867
|
+
onChange,
|
|
2868
|
+
placeholder = "\u8BF7\u9009\u62E9\u65E5\u671F",
|
|
2869
|
+
disabled = false,
|
|
2870
|
+
format = "yyyy-MM-dd",
|
|
2871
|
+
minDate,
|
|
2872
|
+
maxDate,
|
|
2873
|
+
className
|
|
2874
|
+
}) {
|
|
2875
|
+
const colors = useFormThemeColors();
|
|
2876
|
+
const [visible, setVisible] = useState18(false);
|
|
2877
|
+
const [tempDate, setTempDate] = useState18(value || /* @__PURE__ */ new Date());
|
|
2878
|
+
const displayText = useMemo6(() => {
|
|
2879
|
+
return value ? formatDate(value, format) : placeholder;
|
|
2880
|
+
}, [value, format, placeholder]);
|
|
2881
|
+
const handleConfirm = useCallback14(() => {
|
|
2882
|
+
onChange?.(tempDate);
|
|
2883
|
+
setVisible(false);
|
|
2884
|
+
}, [tempDate, onChange]);
|
|
2885
|
+
const years = useMemo6(() => {
|
|
2886
|
+
const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
|
|
2887
|
+
const arr = [];
|
|
2888
|
+
for (let i = currentYear - 50; i <= currentYear + 50; i++) {
|
|
2889
|
+
arr.push(i);
|
|
2890
|
+
}
|
|
2891
|
+
return arr;
|
|
2892
|
+
}, []);
|
|
2893
|
+
const months = useMemo6(() => {
|
|
2894
|
+
return Array.from({ length: 12 }, (_, i) => i + 1);
|
|
2895
|
+
}, []);
|
|
2896
|
+
const days = useMemo6(() => {
|
|
2897
|
+
const year = tempDate.getFullYear();
|
|
2898
|
+
const month = tempDate.getMonth();
|
|
2899
|
+
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
2900
|
+
return Array.from({ length: daysInMonth }, (_, i) => i + 1);
|
|
2901
|
+
}, [tempDate]);
|
|
2902
|
+
const isDateDisabled = useCallback14(
|
|
2903
|
+
(year, month, day) => {
|
|
2904
|
+
const date = new Date(year, month - 1, day);
|
|
2905
|
+
if (minDate && date < minDate) return true;
|
|
2906
|
+
if (maxDate && date > maxDate) return true;
|
|
2907
|
+
return false;
|
|
2908
|
+
},
|
|
2909
|
+
[minDate, maxDate]
|
|
2910
|
+
);
|
|
2911
|
+
const updateTempDate = useCallback14(
|
|
2912
|
+
(year, month, day) => {
|
|
2913
|
+
const newDate = new Date(tempDate);
|
|
2914
|
+
if (year !== void 0) newDate.setFullYear(year);
|
|
2915
|
+
if (month !== void 0) newDate.setMonth(month - 1);
|
|
2916
|
+
if (day !== void 0) newDate.setDate(day);
|
|
2917
|
+
setTempDate(newDate);
|
|
2918
|
+
},
|
|
2919
|
+
[tempDate]
|
|
2920
|
+
);
|
|
2921
|
+
return /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
2922
|
+
/* @__PURE__ */ jsxs10(
|
|
2923
|
+
AppPressable,
|
|
2924
|
+
{
|
|
2925
|
+
className: cn(
|
|
2926
|
+
"flex-row items-center justify-between px-4 py-3 rounded-lg",
|
|
2927
|
+
disabled ? "opacity-60" : "",
|
|
2928
|
+
className
|
|
2929
|
+
),
|
|
2930
|
+
style: [styles10.trigger, { backgroundColor: colors.surface, borderColor: colors.border }],
|
|
2931
|
+
disabled,
|
|
2932
|
+
onPress: () => {
|
|
2933
|
+
setTempDate(value || /* @__PURE__ */ new Date());
|
|
2934
|
+
setVisible(true);
|
|
2935
|
+
},
|
|
2936
|
+
children: [
|
|
2937
|
+
/* @__PURE__ */ jsx27(
|
|
2938
|
+
AppText,
|
|
2939
|
+
{
|
|
2940
|
+
className: "flex-1",
|
|
2941
|
+
style: { color: value ? colors.text : colors.textMuted },
|
|
2942
|
+
numberOfLines: 1,
|
|
2943
|
+
children: displayText
|
|
2944
|
+
}
|
|
2945
|
+
),
|
|
2946
|
+
/* @__PURE__ */ jsx27(Icon, { name: "calendar-today", size: "md", color: colors.icon })
|
|
2947
|
+
]
|
|
2948
|
+
}
|
|
2949
|
+
),
|
|
2950
|
+
/* @__PURE__ */ jsx27(
|
|
2951
|
+
Modal3,
|
|
2952
|
+
{
|
|
2953
|
+
visible,
|
|
2954
|
+
transparent: true,
|
|
2955
|
+
animationType: "slide",
|
|
2956
|
+
onRequestClose: () => setVisible(false),
|
|
2957
|
+
children: /* @__PURE__ */ jsx27(AppView, { className: "flex-1", style: { backgroundColor: colors.overlay }, justify: "end", children: /* @__PURE__ */ jsxs10(AppView, { className: "rounded-t-2xl", style: { backgroundColor: colors.surface }, children: [
|
|
2958
|
+
/* @__PURE__ */ jsxs10(
|
|
2959
|
+
AppView,
|
|
2960
|
+
{
|
|
2961
|
+
row: true,
|
|
2962
|
+
between: true,
|
|
2963
|
+
items: "center",
|
|
2964
|
+
className: "px-4 py-3",
|
|
2965
|
+
style: [styles10.header, { borderBottomColor: colors.divider }],
|
|
2966
|
+
children: [
|
|
2967
|
+
/* @__PURE__ */ jsx27(TouchableOpacity6, { onPress: () => setVisible(false), children: /* @__PURE__ */ jsx27(AppText, { style: { color: colors.textMuted }, children: "\u53D6\u6D88" }) }),
|
|
2968
|
+
/* @__PURE__ */ jsx27(AppText, { className: "text-lg font-semibold", style: { color: colors.text }, children: "\u9009\u62E9\u65E5\u671F" }),
|
|
2969
|
+
/* @__PURE__ */ jsx27(TouchableOpacity6, { onPress: handleConfirm, children: /* @__PURE__ */ jsx27(AppText, { style: { color: colors.primary }, className: "font-medium", children: "\u786E\u5B9A" }) })
|
|
2970
|
+
]
|
|
2971
|
+
}
|
|
2972
|
+
),
|
|
2973
|
+
/* @__PURE__ */ jsx27(AppView, { center: true, className: "py-4", style: { backgroundColor: colors.headerSurface }, children: /* @__PURE__ */ jsx27(AppText, { className: "text-2xl font-semibold", style: { color: colors.text }, children: formatDate(tempDate, "yyyy\u5E74MM\u6708dd\u65E5") }) }),
|
|
2974
|
+
/* @__PURE__ */ jsxs10(AppView, { row: true, className: "h-48", children: [
|
|
2975
|
+
/* @__PURE__ */ jsx27(
|
|
2976
|
+
PickerColumn,
|
|
2977
|
+
{
|
|
2978
|
+
title: "\u5E74",
|
|
2979
|
+
values: years,
|
|
2980
|
+
selectedValue: tempDate.getFullYear(),
|
|
2981
|
+
onSelect: (year) => updateTempDate(year),
|
|
2982
|
+
isDisabled: (year) => isDateDisabled(year, tempDate.getMonth() + 1, tempDate.getDate()),
|
|
2983
|
+
colors,
|
|
2984
|
+
showDivider: true
|
|
2985
|
+
}
|
|
2986
|
+
),
|
|
2987
|
+
/* @__PURE__ */ jsx27(
|
|
2988
|
+
PickerColumn,
|
|
2989
|
+
{
|
|
2990
|
+
title: "\u6708",
|
|
2991
|
+
values: months,
|
|
2992
|
+
selectedValue: tempDate.getMonth() + 1,
|
|
2993
|
+
onSelect: (month) => updateTempDate(void 0, month),
|
|
2994
|
+
isDisabled: (month) => isDateDisabled(tempDate.getFullYear(), month, tempDate.getDate()),
|
|
2995
|
+
formatLabel: (month) => `${month}\u6708`,
|
|
2996
|
+
colors,
|
|
2997
|
+
showDivider: true
|
|
2998
|
+
}
|
|
2999
|
+
),
|
|
3000
|
+
/* @__PURE__ */ jsx27(
|
|
3001
|
+
PickerColumn,
|
|
3002
|
+
{
|
|
3003
|
+
title: "\u65E5",
|
|
3004
|
+
values: days,
|
|
3005
|
+
selectedValue: tempDate.getDate(),
|
|
3006
|
+
onSelect: (day) => updateTempDate(void 0, void 0, day),
|
|
3007
|
+
isDisabled: (day) => isDateDisabled(tempDate.getFullYear(), tempDate.getMonth() + 1, day),
|
|
3008
|
+
colors
|
|
3009
|
+
}
|
|
3010
|
+
)
|
|
3011
|
+
] }),
|
|
3012
|
+
/* @__PURE__ */ jsxs10(
|
|
3013
|
+
AppView,
|
|
3014
|
+
{
|
|
3015
|
+
row: true,
|
|
3016
|
+
className: "px-4 py-3 gap-2",
|
|
3017
|
+
style: [styles10.footer, { borderTopColor: colors.divider }],
|
|
3018
|
+
children: [
|
|
3019
|
+
/* @__PURE__ */ jsx27(
|
|
3020
|
+
TouchableOpacity6,
|
|
3021
|
+
{
|
|
3022
|
+
className: "flex-1 py-2 items-center rounded-lg",
|
|
3023
|
+
style: { backgroundColor: colors.surfaceMuted },
|
|
3024
|
+
onPress: () => setTempDate(/* @__PURE__ */ new Date()),
|
|
3025
|
+
children: /* @__PURE__ */ jsx27(AppText, { style: { color: colors.text }, children: "\u4ECA\u5929" })
|
|
3026
|
+
}
|
|
3027
|
+
),
|
|
3028
|
+
minDate && /* @__PURE__ */ jsx27(
|
|
3029
|
+
TouchableOpacity6,
|
|
3030
|
+
{
|
|
3031
|
+
className: "flex-1 py-2 items-center rounded-lg",
|
|
3032
|
+
style: { backgroundColor: colors.surfaceMuted },
|
|
3033
|
+
onPress: () => setTempDate(minDate),
|
|
3034
|
+
children: /* @__PURE__ */ jsx27(AppText, { style: { color: colors.text }, children: "\u6700\u65E9" })
|
|
3035
|
+
}
|
|
3036
|
+
),
|
|
3037
|
+
maxDate && /* @__PURE__ */ jsx27(
|
|
3038
|
+
TouchableOpacity6,
|
|
3039
|
+
{
|
|
3040
|
+
className: "flex-1 py-2 items-center rounded-lg",
|
|
3041
|
+
style: { backgroundColor: colors.surfaceMuted },
|
|
3042
|
+
onPress: () => setTempDate(maxDate),
|
|
3043
|
+
children: /* @__PURE__ */ jsx27(AppText, { style: { color: colors.text }, children: "\u6700\u665A" })
|
|
3044
|
+
}
|
|
3045
|
+
)
|
|
3046
|
+
]
|
|
3047
|
+
}
|
|
3048
|
+
)
|
|
3049
|
+
] }) })
|
|
3050
|
+
}
|
|
3051
|
+
)
|
|
3052
|
+
] });
|
|
3053
|
+
}
|
|
3054
|
+
var styles10 = StyleSheet10.create({
|
|
3055
|
+
trigger: {
|
|
3056
|
+
borderWidth: 0.5
|
|
3057
|
+
},
|
|
3058
|
+
header: {
|
|
3059
|
+
borderBottomWidth: 0.5
|
|
3060
|
+
},
|
|
3061
|
+
column: {
|
|
3062
|
+
borderRightWidth: 0.5
|
|
3063
|
+
},
|
|
3064
|
+
footer: {
|
|
3065
|
+
borderTopWidth: 0.5
|
|
3066
|
+
}
|
|
3067
|
+
});
|
|
3068
|
+
|
|
3069
|
+
// src/ui/form/FormItem.tsx
|
|
3070
|
+
import { jsx as jsx28, jsxs as jsxs11 } from "nativewind/jsx-runtime";
|
|
3071
|
+
function FormItem({
|
|
3072
|
+
name: _name,
|
|
3073
|
+
label,
|
|
3074
|
+
error,
|
|
3075
|
+
help,
|
|
3076
|
+
required,
|
|
3077
|
+
children,
|
|
3078
|
+
className,
|
|
3079
|
+
labelClassName
|
|
3080
|
+
}) {
|
|
3081
|
+
const colors = useThemeColors();
|
|
3082
|
+
return /* @__PURE__ */ jsxs11(AppView, { className: cn("mb-4", className), children: [
|
|
3083
|
+
label && /* @__PURE__ */ jsxs11(AppView, { row: true, items: "center", gap: 1, className: cn("mb-2", labelClassName), children: [
|
|
3084
|
+
/* @__PURE__ */ jsx28(AppText, { size: "sm", weight: "medium", style: { color: colors.textSecondary }, children: label }),
|
|
3085
|
+
required && /* @__PURE__ */ jsx28(AppText, { color: "error-500", children: "*" })
|
|
3086
|
+
] }),
|
|
3087
|
+
children,
|
|
3088
|
+
error && /* @__PURE__ */ jsx28(AppText, { size: "sm", color: "error-500", className: "mt-1", children: error }),
|
|
3089
|
+
help && !error && /* @__PURE__ */ jsx28(AppText, { size: "sm", className: "mt-1", style: { color: colors.textMuted }, children: help })
|
|
3090
|
+
] });
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
// src/ui/form/useForm.ts
|
|
3094
|
+
import { useState as useState19, useCallback as useCallback15, useMemo as useMemo7 } from "react";
|
|
3095
|
+
function useForm({
|
|
3096
|
+
schema,
|
|
3097
|
+
defaultValues
|
|
3098
|
+
}) {
|
|
3099
|
+
const [values, setValues] = useState19(defaultValues);
|
|
3100
|
+
const [errors, setErrors] = useState19({});
|
|
3101
|
+
const [isSubmitting, setIsSubmitting] = useState19(false);
|
|
3102
|
+
const isDirty = useMemo7(() => {
|
|
3103
|
+
return JSON.stringify(values) !== JSON.stringify(defaultValues);
|
|
3104
|
+
}, [values, defaultValues]);
|
|
3105
|
+
const isValid = useMemo7(() => {
|
|
3106
|
+
return Object.keys(errors).length === 0;
|
|
3107
|
+
}, [errors]);
|
|
3108
|
+
const setValue = useCallback15((name, value) => {
|
|
3109
|
+
setValues((prev) => ({ ...prev, [name]: value }));
|
|
3110
|
+
setErrors((prev) => {
|
|
3111
|
+
const next = { ...prev };
|
|
3112
|
+
delete next[name];
|
|
3113
|
+
return next;
|
|
3114
|
+
});
|
|
3115
|
+
}, []);
|
|
3116
|
+
const getValue = useCallback15(
|
|
3117
|
+
(name) => {
|
|
3118
|
+
return values[name];
|
|
3119
|
+
},
|
|
3120
|
+
[values]
|
|
3121
|
+
);
|
|
3122
|
+
const validateField = useCallback15(
|
|
3123
|
+
async (name) => {
|
|
3124
|
+
try {
|
|
3125
|
+
const shape = schema.shape;
|
|
3126
|
+
if (shape && shape[name]) {
|
|
3127
|
+
await shape[name].parseAsync(values[name]);
|
|
3128
|
+
setErrors((prev) => {
|
|
3129
|
+
const next = { ...prev };
|
|
3130
|
+
delete next[name];
|
|
3131
|
+
return next;
|
|
3132
|
+
});
|
|
3133
|
+
return true;
|
|
3134
|
+
}
|
|
3135
|
+
return true;
|
|
3136
|
+
} catch (error) {
|
|
3137
|
+
setErrors((prev) => ({
|
|
3138
|
+
...prev,
|
|
3139
|
+
[name]: error.errors?.[0]?.message || "\u9A8C\u8BC1\u5931\u8D25"
|
|
3140
|
+
}));
|
|
3141
|
+
return false;
|
|
3142
|
+
}
|
|
3143
|
+
},
|
|
3144
|
+
[schema, values]
|
|
3145
|
+
);
|
|
3146
|
+
const validate = useCallback15(async () => {
|
|
3147
|
+
try {
|
|
3148
|
+
await schema.parseAsync(values);
|
|
3149
|
+
setErrors({});
|
|
3150
|
+
return true;
|
|
3151
|
+
} catch (error) {
|
|
3152
|
+
const formErrors = {};
|
|
3153
|
+
error.errors?.forEach((err) => {
|
|
3154
|
+
const path = err.path.join(".");
|
|
3155
|
+
formErrors[path] = err.message;
|
|
3156
|
+
});
|
|
3157
|
+
setErrors(formErrors);
|
|
3158
|
+
return false;
|
|
3159
|
+
}
|
|
3160
|
+
}, [schema, values]);
|
|
3161
|
+
const reset = useCallback15(() => {
|
|
3162
|
+
setValues(defaultValues);
|
|
3163
|
+
setErrors({});
|
|
3164
|
+
setIsSubmitting(false);
|
|
3165
|
+
}, [defaultValues]);
|
|
3166
|
+
const handleSubmit = useCallback15(
|
|
3167
|
+
async (onSubmit) => {
|
|
3168
|
+
const valid = await validate();
|
|
3169
|
+
if (!valid) return;
|
|
3170
|
+
setIsSubmitting(true);
|
|
3171
|
+
try {
|
|
3172
|
+
await onSubmit?.(values);
|
|
3173
|
+
} finally {
|
|
3174
|
+
setIsSubmitting(false);
|
|
3175
|
+
}
|
|
3176
|
+
},
|
|
3177
|
+
[validate, values]
|
|
3178
|
+
);
|
|
3179
|
+
return {
|
|
3180
|
+
values,
|
|
3181
|
+
errors,
|
|
3182
|
+
isValid,
|
|
3183
|
+
isDirty,
|
|
3184
|
+
isSubmitting,
|
|
3185
|
+
setValue,
|
|
3186
|
+
getValue,
|
|
3187
|
+
validate,
|
|
3188
|
+
validateField,
|
|
3189
|
+
reset,
|
|
3190
|
+
handleSubmit
|
|
3191
|
+
};
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
// src/ui/hooks/useToggle.ts
|
|
3195
|
+
import { useCallback as useCallback16, useState as useState20 } from "react";
|
|
3196
|
+
function useToggle(defaultValue = false) {
|
|
3197
|
+
const [value, setValue] = useState20(defaultValue);
|
|
3198
|
+
const toggle = useCallback16(() => {
|
|
3199
|
+
setValue((v) => !v);
|
|
3200
|
+
}, []);
|
|
3201
|
+
const set = useCallback16((newValue) => {
|
|
3202
|
+
setValue(newValue);
|
|
3203
|
+
}, []);
|
|
3204
|
+
const setTrue = useCallback16(() => {
|
|
3205
|
+
setValue(true);
|
|
3206
|
+
}, []);
|
|
3207
|
+
const setFalse = useCallback16(() => {
|
|
3208
|
+
setValue(false);
|
|
3209
|
+
}, []);
|
|
3210
|
+
return [value, { toggle, set, setTrue, setFalse }];
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
// src/ui/hooks/useDebounce.ts
|
|
3214
|
+
import { useEffect as useEffect5, useState as useState21 } from "react";
|
|
3215
|
+
function useDebounce(value, delay = 500) {
|
|
3216
|
+
const [debouncedValue, setDebouncedValue] = useState21(value);
|
|
3217
|
+
useEffect5(() => {
|
|
3218
|
+
const timer = setTimeout(() => {
|
|
3219
|
+
setDebouncedValue(value);
|
|
3220
|
+
}, delay);
|
|
3221
|
+
return () => {
|
|
3222
|
+
clearTimeout(timer);
|
|
3223
|
+
};
|
|
3224
|
+
}, [value, delay]);
|
|
3225
|
+
return debouncedValue;
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
// src/ui/hooks/useThrottle.ts
|
|
3229
|
+
import { useEffect as useEffect6, useRef as useRef7, useState as useState22 } from "react";
|
|
3230
|
+
function useThrottle(value, delay = 200) {
|
|
3231
|
+
const [throttledValue, setThrottledValue] = useState22(value);
|
|
3232
|
+
const lastUpdatedRef = useRef7(Date.now());
|
|
3233
|
+
useEffect6(() => {
|
|
3234
|
+
const now = Date.now();
|
|
3235
|
+
const timeElapsed = now - lastUpdatedRef.current;
|
|
3236
|
+
if (timeElapsed >= delay) {
|
|
3237
|
+
lastUpdatedRef.current = now;
|
|
3238
|
+
setThrottledValue(value);
|
|
3239
|
+
return void 0;
|
|
3240
|
+
} else {
|
|
3241
|
+
const timer = setTimeout(() => {
|
|
3242
|
+
lastUpdatedRef.current = Date.now();
|
|
3243
|
+
setThrottledValue(value);
|
|
3244
|
+
}, delay - timeElapsed);
|
|
3245
|
+
return () => {
|
|
3246
|
+
clearTimeout(timer);
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
}, [value, delay]);
|
|
3250
|
+
return throttledValue;
|
|
3251
|
+
}
|
|
3252
|
+
|
|
3253
|
+
// src/ui/hooks/useKeyboard.ts
|
|
3254
|
+
import { useEffect as useEffect7, useState as useState23, useCallback as useCallback17 } from "react";
|
|
3255
|
+
import { Keyboard, Platform } from "react-native";
|
|
3256
|
+
function useKeyboard() {
|
|
3257
|
+
const [visible, setVisible] = useState23(false);
|
|
3258
|
+
const [height, setHeight] = useState23(0);
|
|
3259
|
+
useEffect7(() => {
|
|
3260
|
+
const handleKeyboardWillShow = (event) => {
|
|
3261
|
+
setVisible(true);
|
|
3262
|
+
setHeight(event.endCoordinates.height);
|
|
3263
|
+
};
|
|
3264
|
+
const handleKeyboardDidShow = (event) => {
|
|
3265
|
+
setVisible(true);
|
|
3266
|
+
setHeight(event.endCoordinates.height);
|
|
3267
|
+
};
|
|
3268
|
+
const handleKeyboardWillHide = () => {
|
|
3269
|
+
setVisible(false);
|
|
3270
|
+
setHeight(0);
|
|
3271
|
+
};
|
|
3272
|
+
const handleKeyboardDidHide = () => {
|
|
3273
|
+
setVisible(false);
|
|
3274
|
+
setHeight(0);
|
|
3275
|
+
};
|
|
3276
|
+
const willShowSub = Keyboard.addListener(
|
|
3277
|
+
Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow",
|
|
3278
|
+
Platform.OS === "ios" ? handleKeyboardWillShow : handleKeyboardDidShow
|
|
3279
|
+
);
|
|
3280
|
+
const willHideSub = Keyboard.addListener(
|
|
3281
|
+
Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide",
|
|
3282
|
+
Platform.OS === "ios" ? handleKeyboardWillHide : handleKeyboardDidHide
|
|
3283
|
+
);
|
|
3284
|
+
return () => {
|
|
3285
|
+
willShowSub.remove();
|
|
3286
|
+
willHideSub.remove();
|
|
3287
|
+
};
|
|
3288
|
+
}, []);
|
|
3289
|
+
const dismiss = useCallback17(() => {
|
|
3290
|
+
Keyboard.dismiss();
|
|
3291
|
+
}, []);
|
|
3292
|
+
return { visible, height, dismiss };
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
// src/ui/hooks/useDimensions.ts
|
|
3296
|
+
import { useEffect as useEffect8, useState as useState24 } from "react";
|
|
3297
|
+
import { Dimensions } from "react-native";
|
|
3298
|
+
function useDimensions() {
|
|
3299
|
+
const [dimensions, setDimensions] = useState24(() => {
|
|
3300
|
+
const window = Dimensions.get("window");
|
|
3301
|
+
return {
|
|
3302
|
+
width: window.width,
|
|
3303
|
+
height: window.height,
|
|
3304
|
+
scale: window.scale,
|
|
3305
|
+
fontScale: window.fontScale
|
|
3306
|
+
};
|
|
3307
|
+
});
|
|
3308
|
+
useEffect8(() => {
|
|
3309
|
+
const handleChange = ({ window }) => {
|
|
3310
|
+
setDimensions({
|
|
3311
|
+
width: window.width,
|
|
3312
|
+
height: window.height,
|
|
3313
|
+
scale: window.scale,
|
|
3314
|
+
fontScale: window.fontScale
|
|
3315
|
+
});
|
|
3316
|
+
};
|
|
3317
|
+
const subscription = Dimensions.addEventListener("change", handleChange);
|
|
3318
|
+
return () => {
|
|
3319
|
+
subscription.remove();
|
|
3320
|
+
};
|
|
3321
|
+
}, []);
|
|
3322
|
+
return dimensions;
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
// src/ui/hooks/useOrientation.ts
|
|
3326
|
+
import { useEffect as useEffect9, useState as useState25 } from "react";
|
|
3327
|
+
import { Dimensions as Dimensions2 } from "react-native";
|
|
3328
|
+
function useOrientation() {
|
|
3329
|
+
const getOrientation = () => {
|
|
3330
|
+
const { width, height } = Dimensions2.get("window");
|
|
3331
|
+
return width > height ? "landscape" : "portrait";
|
|
3332
|
+
};
|
|
3333
|
+
const [orientation, setOrientation] = useState25(getOrientation);
|
|
3334
|
+
useEffect9(() => {
|
|
3335
|
+
const handleChange = ({ window }) => {
|
|
3336
|
+
const newOrientation = window.width > window.height ? "landscape" : "portrait";
|
|
3337
|
+
setOrientation(newOrientation);
|
|
3338
|
+
};
|
|
3339
|
+
const subscription = Dimensions2.addEventListener("change", handleChange);
|
|
3340
|
+
return () => {
|
|
3341
|
+
subscription.remove();
|
|
3342
|
+
};
|
|
3343
|
+
}, []);
|
|
3344
|
+
return {
|
|
3345
|
+
orientation,
|
|
3346
|
+
isPortrait: orientation === "portrait",
|
|
3347
|
+
isLandscape: orientation === "landscape"
|
|
3348
|
+
};
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
// src/navigation/provider.tsx
|
|
3352
|
+
import React5 from "react";
|
|
3353
|
+
import {
|
|
3354
|
+
NavigationContainer
|
|
3355
|
+
} from "@react-navigation/native";
|
|
3356
|
+
|
|
3357
|
+
// src/navigation/utils/navigation-theme.ts
|
|
3358
|
+
function createNavigationTheme(pantherTheme, isDark) {
|
|
3359
|
+
const { primary, background, card, text, divider } = getThemeColors(pantherTheme, isDark);
|
|
3360
|
+
return {
|
|
3361
|
+
dark: isDark,
|
|
3362
|
+
colors: {
|
|
3363
|
+
primary,
|
|
3364
|
+
background,
|
|
3365
|
+
card,
|
|
3366
|
+
text,
|
|
3367
|
+
border: divider,
|
|
3368
|
+
notification: pantherTheme.colors.error?.[500] || "#ef4444"
|
|
3369
|
+
},
|
|
3370
|
+
fonts: {
|
|
3371
|
+
regular: { fontFamily: "System", fontWeight: "400" },
|
|
3372
|
+
medium: { fontFamily: "System", fontWeight: "500" },
|
|
3373
|
+
bold: { fontFamily: "System", fontWeight: "700" },
|
|
3374
|
+
heavy: { fontFamily: "System", fontWeight: "900" }
|
|
3375
|
+
}
|
|
3376
|
+
};
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
// src/navigation/provider.tsx
|
|
3380
|
+
import { jsx as jsx29 } from "nativewind/jsx-runtime";
|
|
3381
|
+
function NavigationProvider({
|
|
3382
|
+
children,
|
|
3383
|
+
linking,
|
|
3384
|
+
fallback,
|
|
3385
|
+
onReady,
|
|
3386
|
+
onStateChange,
|
|
3387
|
+
onUnhandledAction,
|
|
3388
|
+
theme: customTheme
|
|
3389
|
+
}) {
|
|
3390
|
+
const { theme, isDark } = useTheme();
|
|
3391
|
+
const navigationTheme = React5.useMemo(
|
|
3392
|
+
() => customTheme || createNavigationTheme(theme, isDark),
|
|
3393
|
+
[customTheme, theme, isDark]
|
|
3394
|
+
);
|
|
3395
|
+
return /* @__PURE__ */ jsx29(
|
|
3396
|
+
NavigationContainer,
|
|
3397
|
+
{
|
|
3398
|
+
theme: navigationTheme,
|
|
3399
|
+
linking,
|
|
3400
|
+
fallback,
|
|
3401
|
+
onReady,
|
|
3402
|
+
onStateChange,
|
|
3403
|
+
onUnhandledAction,
|
|
3404
|
+
children
|
|
3405
|
+
}
|
|
3406
|
+
);
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
// src/navigation/vendor/stack.ts
|
|
3410
|
+
import { createStackNavigator, TransitionPresets } from "@react-navigation/stack";
|
|
3411
|
+
|
|
3412
|
+
// src/navigation/navigators/StackNavigator.tsx
|
|
3413
|
+
import { jsx as jsx30 } from "nativewind/jsx-runtime";
|
|
3414
|
+
var NativeStack = createStackNavigator();
|
|
3415
|
+
var defaultScreenOptions = {
|
|
3416
|
+
headerShown: false,
|
|
3417
|
+
...TransitionPresets.SlideFromRightIOS
|
|
3418
|
+
};
|
|
3419
|
+
function StackNavigator({ initialRouteName, screenOptions, children }) {
|
|
3420
|
+
return /* @__PURE__ */ jsx30(
|
|
3421
|
+
NativeStack.Navigator,
|
|
3422
|
+
{
|
|
3423
|
+
initialRouteName,
|
|
3424
|
+
screenOptions: { ...defaultScreenOptions, ...screenOptions },
|
|
3425
|
+
children
|
|
3426
|
+
}
|
|
3427
|
+
);
|
|
3428
|
+
}
|
|
3429
|
+
StackNavigator.Screen = NativeStack.Screen;
|
|
3430
|
+
StackNavigator.Group = NativeStack.Group;
|
|
3431
|
+
function createStackScreens(routes) {
|
|
3432
|
+
return routes.map((route) => /* @__PURE__ */ jsx30(
|
|
3433
|
+
StackNavigator.Screen,
|
|
3434
|
+
{
|
|
3435
|
+
name: route.name,
|
|
3436
|
+
component: route.component,
|
|
3437
|
+
options: route.options,
|
|
3438
|
+
initialParams: route.initialParams
|
|
3439
|
+
},
|
|
3440
|
+
route.name
|
|
3441
|
+
));
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
// src/navigation/navigators/TabNavigator.tsx
|
|
3445
|
+
import React6 from "react";
|
|
3446
|
+
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
|
|
3447
|
+
|
|
3448
|
+
// src/navigation/components/BottomTabBar.tsx
|
|
3449
|
+
import { View as View8, TouchableOpacity as TouchableOpacity7, StyleSheet as StyleSheet11 } from "react-native";
|
|
3450
|
+
import { useSafeAreaInsets as useSafeAreaInsets2 } from "react-native-safe-area-context";
|
|
3451
|
+
import { jsx as jsx31, jsxs as jsxs12 } from "nativewind/jsx-runtime";
|
|
3452
|
+
var DEFAULT_TAB_BAR_HEIGHT = 65;
|
|
3453
|
+
function BottomTabBar({
|
|
3454
|
+
state,
|
|
3455
|
+
descriptors,
|
|
3456
|
+
navigation,
|
|
3457
|
+
showLabel = true,
|
|
3458
|
+
activeTintColor,
|
|
3459
|
+
inactiveTintColor,
|
|
3460
|
+
height = DEFAULT_TAB_BAR_HEIGHT,
|
|
3461
|
+
activeBackgroundColor,
|
|
3462
|
+
inactiveBackgroundColor,
|
|
3463
|
+
iconStyle,
|
|
3464
|
+
labelStyle,
|
|
3465
|
+
style
|
|
3466
|
+
}) {
|
|
3467
|
+
const colors = useThemeColors();
|
|
3468
|
+
const insets = useSafeAreaInsets2();
|
|
3469
|
+
const activeColor = activeTintColor || colors.primary;
|
|
3470
|
+
const inactiveColor = inactiveTintColor || colors.textMuted;
|
|
3471
|
+
const backgroundColor = style?.backgroundColor || colors.card;
|
|
3472
|
+
const borderTopColor = colors.divider;
|
|
3473
|
+
return /* @__PURE__ */ jsx31(
|
|
3474
|
+
View8,
|
|
3475
|
+
{
|
|
3476
|
+
style: [
|
|
3477
|
+
styles11.container,
|
|
3478
|
+
{ borderTopColor },
|
|
3479
|
+
{ backgroundColor, height: height + insets.bottom, paddingBottom: insets.bottom },
|
|
3480
|
+
style
|
|
3481
|
+
],
|
|
3482
|
+
children: state.routes.map((route, index) => {
|
|
3483
|
+
const { options } = descriptors[route.key];
|
|
3484
|
+
const isFocused = state.index === index;
|
|
3485
|
+
const onPress = () => {
|
|
3486
|
+
const event = navigation.emit({
|
|
3487
|
+
type: "tabPress",
|
|
3488
|
+
target: route.key,
|
|
3489
|
+
canPreventDefault: true
|
|
3490
|
+
});
|
|
3491
|
+
if (!isFocused && !event.defaultPrevented) {
|
|
3492
|
+
navigation.navigate(route.name);
|
|
3493
|
+
}
|
|
3494
|
+
};
|
|
3495
|
+
const onLongPress = () => {
|
|
3496
|
+
navigation.emit({
|
|
3497
|
+
type: "tabLongPress",
|
|
3498
|
+
target: route.key
|
|
3499
|
+
});
|
|
3500
|
+
};
|
|
3501
|
+
const label = options.tabBarLabel !== void 0 ? options.tabBarLabel : options.title !== void 0 ? options.title : route.name;
|
|
3502
|
+
const iconName = options.tabBarIcon ? options.tabBarIcon({
|
|
3503
|
+
focused: isFocused,
|
|
3504
|
+
color: isFocused ? activeColor : inactiveColor,
|
|
3505
|
+
size: 24
|
|
3506
|
+
}) : null;
|
|
3507
|
+
const badge = options.tabBarBadge;
|
|
3508
|
+
return /* @__PURE__ */ jsxs12(
|
|
3509
|
+
TouchableOpacity7,
|
|
3510
|
+
{
|
|
3511
|
+
accessibilityRole: "button",
|
|
3512
|
+
accessibilityState: isFocused ? { selected: true } : {},
|
|
3513
|
+
accessibilityLabel: options.tabBarAccessibilityLabel,
|
|
3514
|
+
testID: options.tabBarTestID,
|
|
3515
|
+
onPress,
|
|
3516
|
+
onLongPress,
|
|
3517
|
+
style: [
|
|
3518
|
+
styles11.tab,
|
|
3519
|
+
{
|
|
3520
|
+
backgroundColor: isFocused ? activeBackgroundColor : inactiveBackgroundColor
|
|
3521
|
+
}
|
|
3522
|
+
],
|
|
3523
|
+
children: [
|
|
3524
|
+
/* @__PURE__ */ jsxs12(View8, { style: [styles11.iconContainer, iconStyle], children: [
|
|
3525
|
+
iconName,
|
|
3526
|
+
badge != null && /* @__PURE__ */ jsx31(View8, { style: [styles11.badge, { backgroundColor: activeColor }], children: /* @__PURE__ */ jsx31(AppText, { style: styles11.badgeText, children: typeof badge === "number" && badge > 99 ? "99+" : badge }) })
|
|
3527
|
+
] }),
|
|
3528
|
+
showLabel && /* @__PURE__ */ jsx31(
|
|
3529
|
+
AppText,
|
|
3530
|
+
{
|
|
3531
|
+
style: [
|
|
3532
|
+
styles11.label,
|
|
3533
|
+
{ color: isFocused ? activeColor : inactiveColor },
|
|
3534
|
+
labelStyle
|
|
3535
|
+
],
|
|
3536
|
+
numberOfLines: 1,
|
|
3537
|
+
children: label
|
|
3538
|
+
}
|
|
3539
|
+
)
|
|
3540
|
+
]
|
|
3541
|
+
},
|
|
3542
|
+
route.key
|
|
3543
|
+
);
|
|
3544
|
+
})
|
|
3545
|
+
}
|
|
3546
|
+
);
|
|
3547
|
+
}
|
|
3548
|
+
var styles11 = StyleSheet11.create({
|
|
3549
|
+
container: {
|
|
3550
|
+
flexDirection: "row",
|
|
3551
|
+
borderTopWidth: 0.5,
|
|
3552
|
+
elevation: 8,
|
|
3553
|
+
shadowColor: "#000",
|
|
3554
|
+
shadowOffset: { width: 0, height: -2 },
|
|
3555
|
+
shadowOpacity: 0.1,
|
|
3556
|
+
shadowRadius: 3
|
|
3557
|
+
},
|
|
3558
|
+
tab: {
|
|
3559
|
+
flex: 1,
|
|
3560
|
+
alignItems: "center",
|
|
3561
|
+
justifyContent: "center",
|
|
3562
|
+
paddingVertical: 6
|
|
3563
|
+
},
|
|
3564
|
+
iconContainer: {
|
|
3565
|
+
position: "relative",
|
|
3566
|
+
marginBottom: 2
|
|
3567
|
+
},
|
|
3568
|
+
badge: {
|
|
3569
|
+
position: "absolute",
|
|
3570
|
+
top: -6,
|
|
3571
|
+
right: -10,
|
|
3572
|
+
minWidth: 18,
|
|
3573
|
+
height: 18,
|
|
3574
|
+
borderRadius: 9,
|
|
3575
|
+
justifyContent: "center",
|
|
3576
|
+
alignItems: "center",
|
|
3577
|
+
paddingHorizontal: 4
|
|
3578
|
+
},
|
|
3579
|
+
badgeText: {
|
|
3580
|
+
color: "#fff",
|
|
3581
|
+
fontSize: 10,
|
|
3582
|
+
fontWeight: "bold"
|
|
3583
|
+
},
|
|
3584
|
+
label: {
|
|
3585
|
+
fontSize: 12,
|
|
3586
|
+
marginTop: 2
|
|
3587
|
+
}
|
|
3588
|
+
});
|
|
3589
|
+
|
|
3590
|
+
// src/navigation/navigators/TabNavigator.tsx
|
|
3591
|
+
import { jsx as jsx32 } from "nativewind/jsx-runtime";
|
|
3592
|
+
var NativeTab = createBottomTabNavigator();
|
|
3593
|
+
var defaultScreenOptions2 = {
|
|
3594
|
+
headerShown: false,
|
|
3595
|
+
tabBarShowLabel: true
|
|
3596
|
+
};
|
|
3597
|
+
function TabNavigator({
|
|
3598
|
+
initialRouteName,
|
|
3599
|
+
tabBarOptions,
|
|
3600
|
+
tabBar,
|
|
3601
|
+
screenOptions,
|
|
3602
|
+
children
|
|
3603
|
+
}) {
|
|
3604
|
+
const mergedScreenOptions = React6.useMemo(() => {
|
|
3605
|
+
const options = { ...defaultScreenOptions2, ...screenOptions };
|
|
3606
|
+
if (tabBarOptions) {
|
|
3607
|
+
if (tabBarOptions.showLabel !== void 0) {
|
|
3608
|
+
options.tabBarShowLabel = tabBarOptions.showLabel;
|
|
3609
|
+
}
|
|
3610
|
+
if (tabBarOptions.activeTintColor) {
|
|
3611
|
+
options.tabBarActiveTintColor = tabBarOptions.activeTintColor;
|
|
3612
|
+
}
|
|
3613
|
+
if (tabBarOptions.inactiveTintColor) {
|
|
3614
|
+
options.tabBarInactiveTintColor = tabBarOptions.inactiveTintColor;
|
|
3615
|
+
}
|
|
3616
|
+
if (tabBarOptions.activeBackgroundColor) {
|
|
3617
|
+
options.tabBarActiveBackgroundColor = tabBarOptions.activeBackgroundColor;
|
|
3618
|
+
}
|
|
3619
|
+
if (tabBarOptions.inactiveBackgroundColor) {
|
|
3620
|
+
options.tabBarInactiveBackgroundColor = tabBarOptions.inactiveBackgroundColor;
|
|
3621
|
+
}
|
|
3622
|
+
if (tabBarOptions.hideOnKeyboard !== void 0) {
|
|
3623
|
+
options.tabBarHideOnKeyboard = tabBarOptions.hideOnKeyboard;
|
|
3624
|
+
}
|
|
3625
|
+
if (tabBarOptions.labelPosition) {
|
|
3626
|
+
options.tabBarLabelPosition = tabBarOptions.labelPosition;
|
|
3627
|
+
}
|
|
3628
|
+
if (tabBarOptions.labelStyle) {
|
|
3629
|
+
options.tabBarLabelStyle = tabBarOptions.labelStyle;
|
|
3630
|
+
}
|
|
3631
|
+
if (tabBarOptions.style) {
|
|
3632
|
+
options.tabBarStyle = tabBarOptions.style;
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
return options;
|
|
3636
|
+
}, [tabBarOptions, screenOptions]);
|
|
3637
|
+
const resolvedTabBar = React6.useMemo(() => {
|
|
3638
|
+
if (tabBar) return tabBar;
|
|
3639
|
+
return (props) => /* @__PURE__ */ jsx32(
|
|
3640
|
+
BottomTabBar,
|
|
3641
|
+
{
|
|
3642
|
+
...props,
|
|
3643
|
+
showLabel: tabBarOptions?.showLabel,
|
|
3644
|
+
activeTintColor: tabBarOptions?.activeTintColor,
|
|
3645
|
+
inactiveTintColor: tabBarOptions?.inactiveTintColor,
|
|
3646
|
+
activeBackgroundColor: tabBarOptions?.activeBackgroundColor,
|
|
3647
|
+
inactiveBackgroundColor: tabBarOptions?.inactiveBackgroundColor,
|
|
3648
|
+
iconStyle: tabBarOptions?.iconStyle,
|
|
3649
|
+
labelStyle: tabBarOptions?.labelStyle,
|
|
3650
|
+
style: tabBarOptions?.style,
|
|
3651
|
+
height: tabBarOptions?.height
|
|
3652
|
+
}
|
|
3653
|
+
);
|
|
3654
|
+
}, [
|
|
3655
|
+
tabBar,
|
|
3656
|
+
tabBarOptions?.showLabel,
|
|
3657
|
+
tabBarOptions?.activeTintColor,
|
|
3658
|
+
tabBarOptions?.inactiveTintColor,
|
|
3659
|
+
tabBarOptions?.activeBackgroundColor,
|
|
3660
|
+
tabBarOptions?.inactiveBackgroundColor,
|
|
3661
|
+
tabBarOptions?.iconStyle,
|
|
3662
|
+
tabBarOptions?.labelStyle,
|
|
3663
|
+
tabBarOptions?.style,
|
|
3664
|
+
tabBarOptions?.height
|
|
3665
|
+
]);
|
|
3666
|
+
return /* @__PURE__ */ jsx32(
|
|
3667
|
+
NativeTab.Navigator,
|
|
3668
|
+
{
|
|
3669
|
+
initialRouteName,
|
|
3670
|
+
screenOptions: mergedScreenOptions,
|
|
3671
|
+
tabBar: resolvedTabBar,
|
|
3672
|
+
children
|
|
3673
|
+
}
|
|
3674
|
+
);
|
|
3675
|
+
}
|
|
3676
|
+
TabNavigator.Screen = NativeTab.Screen;
|
|
3677
|
+
function createTabScreens(routes) {
|
|
3678
|
+
return routes.map((route) => /* @__PURE__ */ jsx32(
|
|
3679
|
+
TabNavigator.Screen,
|
|
3680
|
+
{
|
|
3681
|
+
name: route.name,
|
|
3682
|
+
component: route.component,
|
|
3683
|
+
options: route.options,
|
|
3684
|
+
initialParams: route.initialParams
|
|
3685
|
+
},
|
|
3686
|
+
route.name
|
|
3687
|
+
));
|
|
3688
|
+
}
|
|
3689
|
+
|
|
3690
|
+
// src/navigation/navigators/DrawerNavigator.tsx
|
|
3691
|
+
import React7 from "react";
|
|
3692
|
+
import { createDrawerNavigator } from "@react-navigation/drawer";
|
|
3693
|
+
import { jsx as jsx33 } from "nativewind/jsx-runtime";
|
|
3694
|
+
var NativeDrawer = createDrawerNavigator();
|
|
3695
|
+
function DrawerNavigator({
|
|
3696
|
+
initialRouteName,
|
|
3697
|
+
screenOptions,
|
|
3698
|
+
drawerContent,
|
|
3699
|
+
drawerOptions,
|
|
3700
|
+
children
|
|
3701
|
+
}) {
|
|
3702
|
+
const { theme, isDark } = useTheme();
|
|
3703
|
+
const navigationTheme = createNavigationTheme(theme, isDark);
|
|
3704
|
+
const mergedScreenOptions = React7.useMemo(() => {
|
|
3705
|
+
return {
|
|
3706
|
+
headerShown: false,
|
|
3707
|
+
drawerStyle: {
|
|
3708
|
+
backgroundColor: navigationTheme.colors.card,
|
|
3709
|
+
width: drawerOptions?.drawerWidth || 280
|
|
3710
|
+
},
|
|
3711
|
+
drawerActiveTintColor: theme.colors.primary?.[500] || "#f38b32",
|
|
3712
|
+
drawerInactiveTintColor: navigationTheme.colors.text,
|
|
3713
|
+
drawerLabelStyle: {
|
|
3714
|
+
fontSize: 16
|
|
3715
|
+
},
|
|
3716
|
+
drawerType: drawerOptions?.drawerType,
|
|
3717
|
+
overlayColor: drawerOptions?.overlayColor,
|
|
3718
|
+
edgeWidth: drawerOptions?.edgeWidth,
|
|
3719
|
+
minSwipeDistance: drawerOptions?.minSwipeDistance,
|
|
3720
|
+
...screenOptions
|
|
3721
|
+
};
|
|
3722
|
+
}, [screenOptions, drawerOptions, navigationTheme, theme]);
|
|
3723
|
+
return /* @__PURE__ */ jsx33(
|
|
3724
|
+
NativeDrawer.Navigator,
|
|
3725
|
+
{
|
|
3726
|
+
initialRouteName,
|
|
3727
|
+
screenOptions: mergedScreenOptions,
|
|
3728
|
+
drawerContent,
|
|
3729
|
+
children
|
|
3730
|
+
}
|
|
3731
|
+
);
|
|
3732
|
+
}
|
|
3733
|
+
DrawerNavigator.Screen = NativeDrawer.Screen;
|
|
3734
|
+
function createDrawerScreens(routes) {
|
|
3735
|
+
return routes.map((route) => /* @__PURE__ */ jsx33(
|
|
3736
|
+
DrawerNavigator.Screen,
|
|
3737
|
+
{
|
|
3738
|
+
name: route.name,
|
|
3739
|
+
component: route.component,
|
|
3740
|
+
options: route.options,
|
|
3741
|
+
initialParams: route.initialParams
|
|
3742
|
+
},
|
|
3743
|
+
route.name
|
|
3744
|
+
));
|
|
3745
|
+
}
|
|
3746
|
+
|
|
3747
|
+
// src/navigation/components/AppHeader.tsx
|
|
3748
|
+
import { StyleSheet as StyleSheet12 } from "react-native";
|
|
3749
|
+
import { useSafeAreaInsets as useSafeAreaInsets3 } from "react-native-safe-area-context";
|
|
3750
|
+
import { jsx as jsx34, jsxs as jsxs13 } from "nativewind/jsx-runtime";
|
|
3751
|
+
function AppHeader({
|
|
3752
|
+
title,
|
|
3753
|
+
subtitle,
|
|
3754
|
+
leftIcon = "chevron-left",
|
|
3755
|
+
onLeftPress,
|
|
3756
|
+
rightIcons = [],
|
|
3757
|
+
transparent = false,
|
|
3758
|
+
safeArea = true,
|
|
3759
|
+
style
|
|
3760
|
+
}) {
|
|
3761
|
+
const colors = useThemeColors();
|
|
3762
|
+
const insets = useSafeAreaInsets3();
|
|
3763
|
+
const backgroundColor = transparent ? "transparent" : colors.card;
|
|
3764
|
+
return /* @__PURE__ */ jsx34(
|
|
3765
|
+
AppView,
|
|
3766
|
+
{
|
|
3767
|
+
style: [
|
|
3768
|
+
{
|
|
3769
|
+
backgroundColor,
|
|
3770
|
+
paddingTop: safeArea ? insets.top : 0
|
|
3771
|
+
},
|
|
3772
|
+
style
|
|
3773
|
+
],
|
|
3774
|
+
children: /* @__PURE__ */ jsxs13(AppView, { row: true, items: "center", px: 4, style: styles12.container, children: [
|
|
3775
|
+
/* @__PURE__ */ jsx34(AppView, { style: [styles12.sideContainer, styles12.leftContainer], children: leftIcon && /* @__PURE__ */ jsx34(AppPressable, { onPress: onLeftPress, style: styles12.iconButton, children: /* @__PURE__ */ jsx34(Icon, { name: leftIcon, size: 24, color: colors.text }) }) }),
|
|
3776
|
+
/* @__PURE__ */ jsxs13(AppView, { style: styles12.centerContainer, children: [
|
|
3777
|
+
title && /* @__PURE__ */ jsx34(
|
|
3778
|
+
AppText,
|
|
3779
|
+
{
|
|
3780
|
+
size: "lg",
|
|
3781
|
+
weight: "semibold",
|
|
3782
|
+
style: [styles12.title, { color: colors.text }],
|
|
3783
|
+
numberOfLines: 1,
|
|
3784
|
+
children: title
|
|
3785
|
+
}
|
|
3786
|
+
),
|
|
3787
|
+
subtitle && /* @__PURE__ */ jsx34(
|
|
3788
|
+
AppText,
|
|
3789
|
+
{
|
|
3790
|
+
size: "xs",
|
|
3791
|
+
style: [styles12.subtitle, { color: colors.textMuted }],
|
|
3792
|
+
numberOfLines: 1,
|
|
3793
|
+
children: subtitle
|
|
3794
|
+
}
|
|
3795
|
+
)
|
|
3796
|
+
] }),
|
|
3797
|
+
/* @__PURE__ */ jsx34(AppView, { row: true, items: "center", style: [styles12.sideContainer, styles12.rightContainer], children: rightIcons.map((icon, index) => /* @__PURE__ */ jsx34(AppPressable, { onPress: icon.onPress, style: styles12.iconButton, children: /* @__PURE__ */ jsxs13(AppView, { children: [
|
|
3798
|
+
/* @__PURE__ */ jsx34(Icon, { name: icon.icon, size: 24, color: colors.text }),
|
|
3799
|
+
icon.badge ? /* @__PURE__ */ jsx34(AppView, { style: styles12.badge, children: /* @__PURE__ */ jsx34(AppText, { size: "xs", color: "white", style: styles12.badgeText, children: icon.badge > 99 ? "99+" : icon.badge }) }) : null
|
|
3800
|
+
] }) }, index)) })
|
|
3801
|
+
] })
|
|
3802
|
+
}
|
|
3803
|
+
);
|
|
3804
|
+
}
|
|
3805
|
+
var styles12 = StyleSheet12.create({
|
|
3806
|
+
container: {
|
|
3807
|
+
height: 44
|
|
3808
|
+
// iOS 标准导航栏高度
|
|
3809
|
+
},
|
|
3810
|
+
sideContainer: {
|
|
3811
|
+
width: 70,
|
|
3812
|
+
// 固定宽度,确保标题居中
|
|
3813
|
+
flexDirection: "row",
|
|
3814
|
+
alignItems: "center"
|
|
3815
|
+
},
|
|
3816
|
+
leftContainer: {
|
|
3817
|
+
justifyContent: "flex-start"
|
|
3818
|
+
},
|
|
3819
|
+
rightContainer: {
|
|
3820
|
+
justifyContent: "flex-end"
|
|
3821
|
+
},
|
|
3822
|
+
centerContainer: {
|
|
3823
|
+
flex: 1,
|
|
3824
|
+
alignItems: "center",
|
|
3825
|
+
justifyContent: "center"
|
|
3826
|
+
},
|
|
3827
|
+
title: {
|
|
3828
|
+
textAlign: "center"
|
|
3829
|
+
},
|
|
3830
|
+
subtitle: {
|
|
3831
|
+
textAlign: "center",
|
|
3832
|
+
marginTop: 2
|
|
3833
|
+
},
|
|
3834
|
+
iconButton: {
|
|
3835
|
+
padding: 8
|
|
3836
|
+
},
|
|
3837
|
+
badge: {
|
|
3838
|
+
position: "absolute",
|
|
3839
|
+
top: -4,
|
|
3840
|
+
right: -4,
|
|
3841
|
+
minWidth: 16,
|
|
3842
|
+
height: 16,
|
|
3843
|
+
borderRadius: 8,
|
|
3844
|
+
backgroundColor: "#ef4444",
|
|
3845
|
+
alignItems: "center",
|
|
3846
|
+
justifyContent: "center",
|
|
3847
|
+
paddingHorizontal: 4
|
|
3848
|
+
},
|
|
3849
|
+
badgeText: {
|
|
3850
|
+
fontSize: 10,
|
|
3851
|
+
fontWeight: "bold"
|
|
3852
|
+
}
|
|
3853
|
+
});
|
|
3854
|
+
|
|
3855
|
+
// src/navigation/components/DrawerContent.tsx
|
|
3856
|
+
import { View as View9, TouchableOpacity as TouchableOpacity8, StyleSheet as StyleSheet13 } from "react-native";
|
|
3857
|
+
import { DrawerContentScrollView } from "@react-navigation/drawer";
|
|
3858
|
+
import { jsx as jsx35, jsxs as jsxs14 } from "nativewind/jsx-runtime";
|
|
3859
|
+
function DrawerContent({
|
|
3860
|
+
state,
|
|
3861
|
+
descriptors,
|
|
3862
|
+
navigation,
|
|
3863
|
+
header,
|
|
3864
|
+
footer,
|
|
3865
|
+
items,
|
|
3866
|
+
activeBackgroundColor,
|
|
3867
|
+
activeTintColor,
|
|
3868
|
+
inactiveTintColor
|
|
3869
|
+
}) {
|
|
3870
|
+
const { theme, isDark } = useTheme();
|
|
3871
|
+
const colors = useThemeColors();
|
|
3872
|
+
const activeBgColor = activeBackgroundColor || (isDark ? colors.primarySurface : theme.colors.primary?.[50] || "#fff7ed");
|
|
3873
|
+
const activeColor = activeTintColor || colors.primary;
|
|
3874
|
+
const inactiveColor = inactiveTintColor || (isDark ? "#d1d5db" : "#4b5563");
|
|
3875
|
+
const backgroundColor = colors.card;
|
|
3876
|
+
const dividerColor = colors.divider;
|
|
3877
|
+
const drawerItems = items || state.routes.map((route) => {
|
|
3878
|
+
const descriptor = descriptors[route.key];
|
|
3879
|
+
const options = descriptor.options;
|
|
3880
|
+
return {
|
|
3881
|
+
name: route.name,
|
|
3882
|
+
label: options.drawerLabel || options.title || route.name,
|
|
3883
|
+
icon: options.drawerIcon?.({ size: 24, color: inactiveColor })?.props?.name,
|
|
3884
|
+
badge: options.tabBarBadge
|
|
3885
|
+
};
|
|
3886
|
+
});
|
|
3887
|
+
return /* @__PURE__ */ jsxs14(DrawerContentScrollView, { style: [styles13.container, { backgroundColor }], children: [
|
|
3888
|
+
header && /* @__PURE__ */ jsx35(View9, { style: [styles13.header, { borderBottomColor: dividerColor }], children: header }),
|
|
3889
|
+
/* @__PURE__ */ jsx35(AppView, { className: "py-2", children: drawerItems.map((item) => {
|
|
3890
|
+
const isFocused = state.routes[state.index].name === item.name;
|
|
3891
|
+
const onPress = () => {
|
|
3892
|
+
navigation.navigate(item.name);
|
|
3893
|
+
};
|
|
3894
|
+
return /* @__PURE__ */ jsxs14(
|
|
3895
|
+
TouchableOpacity8,
|
|
3896
|
+
{
|
|
3897
|
+
onPress,
|
|
3898
|
+
style: [styles13.item, isFocused && { backgroundColor: activeBgColor }],
|
|
3899
|
+
children: [
|
|
3900
|
+
item.icon && /* @__PURE__ */ jsx35(View9, { style: styles13.iconContainer, children: /* @__PURE__ */ jsx35(
|
|
3901
|
+
Icon,
|
|
3902
|
+
{
|
|
3903
|
+
name: item.icon,
|
|
3904
|
+
size: "md",
|
|
3905
|
+
color: isFocused ? activeColor : inactiveColor
|
|
3906
|
+
}
|
|
3907
|
+
) }),
|
|
3908
|
+
/* @__PURE__ */ jsx35(
|
|
3909
|
+
AppText,
|
|
3910
|
+
{
|
|
3911
|
+
style: [
|
|
3912
|
+
styles13.label,
|
|
3913
|
+
{ color: isFocused ? activeColor : inactiveColor },
|
|
3914
|
+
isFocused && styles13.activeLabel
|
|
3915
|
+
],
|
|
3916
|
+
numberOfLines: 1,
|
|
3917
|
+
children: item.label
|
|
3918
|
+
}
|
|
3919
|
+
),
|
|
3920
|
+
item.badge != null && /* @__PURE__ */ jsx35(View9, { style: [styles13.badge, { backgroundColor: activeColor }], children: /* @__PURE__ */ jsx35(AppText, { style: styles13.badgeText, children: typeof item.badge === "number" && item.badge > 99 ? "99+" : item.badge }) })
|
|
3921
|
+
]
|
|
3922
|
+
},
|
|
3923
|
+
item.name
|
|
3924
|
+
);
|
|
3925
|
+
}) }),
|
|
3926
|
+
footer && /* @__PURE__ */ jsx35(View9, { style: [styles13.footer, { borderTopColor: dividerColor }], children: footer })
|
|
3927
|
+
] });
|
|
3928
|
+
}
|
|
3929
|
+
var styles13 = StyleSheet13.create({
|
|
3930
|
+
container: {
|
|
3931
|
+
flex: 1
|
|
3932
|
+
},
|
|
3933
|
+
header: {
|
|
3934
|
+
padding: 16,
|
|
3935
|
+
borderBottomWidth: 0.5
|
|
3936
|
+
},
|
|
3937
|
+
item: {
|
|
3938
|
+
flexDirection: "row",
|
|
3939
|
+
alignItems: "center",
|
|
3940
|
+
paddingHorizontal: 16,
|
|
3941
|
+
paddingVertical: 12,
|
|
3942
|
+
marginHorizontal: 8,
|
|
3943
|
+
marginVertical: 2,
|
|
3944
|
+
borderRadius: 8
|
|
3945
|
+
},
|
|
3946
|
+
iconContainer: {
|
|
3947
|
+
width: 32,
|
|
3948
|
+
alignItems: "center",
|
|
3949
|
+
marginRight: 8
|
|
3950
|
+
},
|
|
3951
|
+
label: {
|
|
3952
|
+
flex: 1,
|
|
3953
|
+
fontSize: 16
|
|
3954
|
+
},
|
|
3955
|
+
activeLabel: {
|
|
3956
|
+
fontWeight: "600"
|
|
3957
|
+
},
|
|
3958
|
+
badge: {
|
|
3959
|
+
minWidth: 20,
|
|
3960
|
+
height: 20,
|
|
3961
|
+
borderRadius: 10,
|
|
3962
|
+
justifyContent: "center",
|
|
3963
|
+
alignItems: "center",
|
|
3964
|
+
paddingHorizontal: 6
|
|
3965
|
+
},
|
|
3966
|
+
badgeText: {
|
|
3967
|
+
color: "#fff",
|
|
3968
|
+
fontSize: 12,
|
|
3969
|
+
fontWeight: "bold"
|
|
3970
|
+
},
|
|
3971
|
+
footer: {
|
|
3972
|
+
marginTop: "auto",
|
|
3973
|
+
padding: 16,
|
|
3974
|
+
borderTopWidth: 0.5
|
|
3975
|
+
}
|
|
3976
|
+
});
|
|
3977
|
+
|
|
3978
|
+
// src/navigation/hooks/useNavigation.ts
|
|
3979
|
+
import { useEffect as useEffect10 } from "react";
|
|
3980
|
+
import { useNavigation as useRNNavigation } from "@react-navigation/native";
|
|
3981
|
+
function useNavigation() {
|
|
3982
|
+
return useRNNavigation();
|
|
3983
|
+
}
|
|
3984
|
+
function useStackNavigation() {
|
|
3985
|
+
return useRNNavigation();
|
|
3986
|
+
}
|
|
3987
|
+
function useTabNavigation() {
|
|
3988
|
+
return useRNNavigation();
|
|
3989
|
+
}
|
|
3990
|
+
function useDrawerNavigation() {
|
|
3991
|
+
return useRNNavigation();
|
|
3992
|
+
}
|
|
3993
|
+
function useBackHandler(handler) {
|
|
3994
|
+
const navigation = useRNNavigation();
|
|
3995
|
+
useEffect10(() => {
|
|
3996
|
+
const unsubscribe = navigation.addListener("beforeRemove", (e) => {
|
|
3997
|
+
if (!handler()) {
|
|
3998
|
+
e.preventDefault();
|
|
3999
|
+
}
|
|
4000
|
+
});
|
|
4001
|
+
return unsubscribe;
|
|
4002
|
+
}, [navigation, handler]);
|
|
4003
|
+
}
|
|
4004
|
+
|
|
4005
|
+
// src/navigation/hooks/useRoute.ts
|
|
4006
|
+
import { useRoute as useRNRoute } from "@react-navigation/native";
|
|
4007
|
+
function useRoute() {
|
|
4008
|
+
return useRNRoute();
|
|
4009
|
+
}
|
|
4010
|
+
|
|
4011
|
+
// src/navigation/hooks/useNavigationState.ts
|
|
4012
|
+
import {
|
|
4013
|
+
useNavigationState as useRNNavigationState,
|
|
4014
|
+
useIsFocused,
|
|
4015
|
+
useFocusEffect,
|
|
4016
|
+
useScrollToTop
|
|
4017
|
+
} from "@react-navigation/native";
|
|
4018
|
+
function useNavigationState(selector) {
|
|
4019
|
+
return useRNNavigationState(selector);
|
|
4020
|
+
}
|
|
4021
|
+
|
|
4022
|
+
// src/navigation/index.ts
|
|
4023
|
+
import {
|
|
4024
|
+
NavigationContainer as NavigationContainer2
|
|
4025
|
+
} from "@react-navigation/native";
|
|
4026
|
+
|
|
4027
|
+
// src/overlay/AppProvider.tsx
|
|
4028
|
+
import { SafeAreaProvider } from "react-native-safe-area-context";
|
|
4029
|
+
|
|
4030
|
+
// src/overlay/AppStatusBar.tsx
|
|
4031
|
+
import { StatusBar } from "react-native";
|
|
4032
|
+
import { jsx as jsx36 } from "nativewind/jsx-runtime";
|
|
4033
|
+
function AppStatusBar({
|
|
4034
|
+
barStyle = "auto",
|
|
4035
|
+
backgroundColor,
|
|
4036
|
+
translucent = false,
|
|
4037
|
+
...props
|
|
4038
|
+
}) {
|
|
4039
|
+
const { theme, isDark } = useTheme();
|
|
4040
|
+
const resolvedBarStyle = barStyle === "auto" ? isDark ? "light-content" : "dark-content" : barStyle;
|
|
4041
|
+
const resolvedBackgroundColor = backgroundColor ?? (translucent ? "transparent" : theme.colors.background?.[500] || "#ffffff");
|
|
4042
|
+
return /* @__PURE__ */ jsx36(
|
|
4043
|
+
StatusBar,
|
|
4044
|
+
{
|
|
4045
|
+
barStyle: resolvedBarStyle,
|
|
4046
|
+
backgroundColor: resolvedBackgroundColor,
|
|
4047
|
+
translucent,
|
|
4048
|
+
...props
|
|
4049
|
+
}
|
|
4050
|
+
);
|
|
4051
|
+
}
|
|
4052
|
+
|
|
4053
|
+
// src/overlay/loading/provider.tsx
|
|
4054
|
+
import { useState as useState26, useCallback as useCallback18 } from "react";
|
|
4055
|
+
|
|
4056
|
+
// src/overlay/loading/context.ts
|
|
4057
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
4058
|
+
var LoadingContext = createContext2(null);
|
|
4059
|
+
function useLoadingContext() {
|
|
4060
|
+
const ctx = useContext2(LoadingContext);
|
|
4061
|
+
if (!ctx) throw new Error("useLoading must be used within OverlayProvider");
|
|
4062
|
+
return ctx;
|
|
4063
|
+
}
|
|
4064
|
+
|
|
4065
|
+
// src/overlay/loading/component.tsx
|
|
4066
|
+
import { View as View10, Modal as Modal4, ActivityIndicator as ActivityIndicator5, StyleSheet as StyleSheet14 } from "react-native";
|
|
4067
|
+
import { jsx as jsx37, jsxs as jsxs15 } from "nativewind/jsx-runtime";
|
|
4068
|
+
function LoadingModal({ visible, text }) {
|
|
4069
|
+
return /* @__PURE__ */ jsx37(Modal4, { transparent: true, visible, animationType: "fade", children: /* @__PURE__ */ jsx37(View10, { style: styles14.overlay, children: /* @__PURE__ */ jsxs15(View10, { style: [styles14.loadingBox, { backgroundColor: "rgba(0,0,0,0.8)" }], children: [
|
|
4070
|
+
/* @__PURE__ */ jsx37(ActivityIndicator5, { size: "large", color: "#fff" }),
|
|
4071
|
+
text && /* @__PURE__ */ jsx37(AppText, { className: "text-white mt-3 text-sm", children: text })
|
|
4072
|
+
] }) }) });
|
|
4073
|
+
}
|
|
4074
|
+
var styles14 = StyleSheet14.create({
|
|
4075
|
+
overlay: {
|
|
4076
|
+
flex: 1,
|
|
4077
|
+
backgroundColor: "rgba(0,0,0,0.5)",
|
|
4078
|
+
justifyContent: "center",
|
|
4079
|
+
alignItems: "center"
|
|
4080
|
+
},
|
|
4081
|
+
loadingBox: {
|
|
4082
|
+
padding: 24,
|
|
4083
|
+
borderRadius: 12,
|
|
4084
|
+
alignItems: "center",
|
|
4085
|
+
minWidth: 120
|
|
4086
|
+
}
|
|
4087
|
+
});
|
|
4088
|
+
|
|
4089
|
+
// src/overlay/loading/provider.tsx
|
|
4090
|
+
import { jsx as jsx38, jsxs as jsxs16 } from "nativewind/jsx-runtime";
|
|
4091
|
+
function LoadingProvider({ children }) {
|
|
4092
|
+
const [state, setState] = useState26({ visible: false });
|
|
4093
|
+
const show = useCallback18((text) => {
|
|
4094
|
+
setState({ visible: true, text });
|
|
4095
|
+
}, []);
|
|
4096
|
+
const hide = useCallback18(() => {
|
|
4097
|
+
setState({ visible: false });
|
|
4098
|
+
}, []);
|
|
4099
|
+
return /* @__PURE__ */ jsxs16(LoadingContext.Provider, { value: { show, hide }, children: [
|
|
4100
|
+
children,
|
|
4101
|
+
/* @__PURE__ */ jsx38(LoadingModal, { ...state })
|
|
4102
|
+
] });
|
|
4103
|
+
}
|
|
4104
|
+
|
|
4105
|
+
// src/overlay/toast/provider.tsx
|
|
4106
|
+
import { useState as useState27, useCallback as useCallback19, useRef as useRef9 } from "react";
|
|
4107
|
+
import { View as View11, StyleSheet as StyleSheet15 } from "react-native";
|
|
4108
|
+
|
|
4109
|
+
// src/overlay/toast/context.ts
|
|
4110
|
+
import { createContext as createContext3, useContext as useContext3 } from "react";
|
|
4111
|
+
var ToastContext = createContext3(null);
|
|
4112
|
+
function useToastContext() {
|
|
4113
|
+
const ctx = useContext3(ToastContext);
|
|
4114
|
+
if (!ctx) throw new Error("useToast must be used within OverlayProvider");
|
|
4115
|
+
return ctx;
|
|
4116
|
+
}
|
|
4117
|
+
|
|
4118
|
+
// src/overlay/toast/component.tsx
|
|
4119
|
+
import { useRef as useRef8, useEffect as useEffect11 } from "react";
|
|
4120
|
+
import { Animated } from "react-native";
|
|
4121
|
+
import { jsx as jsx39 } from "nativewind/jsx-runtime";
|
|
4122
|
+
function ToastItemView({ message, type, onHide }) {
|
|
4123
|
+
const fadeAnim = useRef8(new Animated.Value(0)).current;
|
|
4124
|
+
useEffect11(() => {
|
|
4125
|
+
Animated.sequence([
|
|
4126
|
+
Animated.timing(fadeAnim, { toValue: 1, duration: 200, useNativeDriver: true }),
|
|
4127
|
+
Animated.delay(2500),
|
|
4128
|
+
Animated.timing(fadeAnim, { toValue: 0, duration: 200, useNativeDriver: true })
|
|
4129
|
+
]).start(onHide);
|
|
4130
|
+
}, []);
|
|
4131
|
+
const bgColors = {
|
|
4132
|
+
success: "bg-success-500",
|
|
4133
|
+
error: "bg-error-500",
|
|
4134
|
+
warning: "bg-warning-500",
|
|
4135
|
+
info: "bg-primary-500"
|
|
4136
|
+
};
|
|
4137
|
+
return /* @__PURE__ */ jsx39(
|
|
4138
|
+
Animated.View,
|
|
4139
|
+
{
|
|
4140
|
+
style: {
|
|
4141
|
+
opacity: fadeAnim,
|
|
4142
|
+
transform: [
|
|
4143
|
+
{
|
|
4144
|
+
translateY: fadeAnim.interpolate({
|
|
4145
|
+
inputRange: [0, 1],
|
|
4146
|
+
outputRange: [-20, 0]
|
|
4147
|
+
})
|
|
4148
|
+
}
|
|
4149
|
+
]
|
|
4150
|
+
},
|
|
4151
|
+
children: /* @__PURE__ */ jsx39(AppView, { className: `${bgColors[type]} px-4 py-3 rounded-lg mb-2 mx-4 shadow-lg`, children: /* @__PURE__ */ jsx39(AppText, { className: "text-white text-center", children: message }) })
|
|
4152
|
+
}
|
|
4153
|
+
);
|
|
4154
|
+
}
|
|
4155
|
+
|
|
4156
|
+
// src/overlay/toast/provider.tsx
|
|
4157
|
+
import { jsx as jsx40, jsxs as jsxs17 } from "nativewind/jsx-runtime";
|
|
4158
|
+
function ToastProvider({ children }) {
|
|
4159
|
+
const [toasts, setToasts] = useState27([]);
|
|
4160
|
+
const timersRef = useRef9(/* @__PURE__ */ new Map());
|
|
4161
|
+
const remove = useCallback19((id) => {
|
|
4162
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
4163
|
+
const timer = timersRef.current.get(id);
|
|
4164
|
+
if (timer) {
|
|
4165
|
+
clearTimeout(timer);
|
|
4166
|
+
timersRef.current.delete(id);
|
|
4167
|
+
}
|
|
4168
|
+
}, []);
|
|
4169
|
+
const show = useCallback19(
|
|
4170
|
+
(message, type = "info", duration = 3e3) => {
|
|
4171
|
+
const id = Math.random().toString(36).substring(7);
|
|
4172
|
+
const toast = { id, message, type, duration };
|
|
4173
|
+
setToasts((prev) => [...prev, toast]);
|
|
4174
|
+
const timer = setTimeout(() => remove(id), duration);
|
|
4175
|
+
timersRef.current.set(id, timer);
|
|
4176
|
+
},
|
|
4177
|
+
[remove]
|
|
4178
|
+
);
|
|
4179
|
+
const success = useCallback19(
|
|
4180
|
+
(message, duration) => show(message, "success", duration),
|
|
4181
|
+
[show]
|
|
4182
|
+
);
|
|
4183
|
+
const error = useCallback19(
|
|
4184
|
+
(message, duration) => show(message, "error", duration),
|
|
4185
|
+
[show]
|
|
4186
|
+
);
|
|
4187
|
+
const info = useCallback19(
|
|
4188
|
+
(message, duration) => show(message, "info", duration),
|
|
4189
|
+
[show]
|
|
4190
|
+
);
|
|
4191
|
+
const warning = useCallback19(
|
|
4192
|
+
(message, duration) => show(message, "warning", duration),
|
|
4193
|
+
[show]
|
|
4194
|
+
);
|
|
4195
|
+
return /* @__PURE__ */ jsxs17(ToastContext.Provider, { value: { show, success, error, info, warning }, children: [
|
|
4196
|
+
children,
|
|
4197
|
+
/* @__PURE__ */ jsx40(View11, { style: styles15.toastContainer, pointerEvents: "none", children: toasts.map((toast) => /* @__PURE__ */ jsx40(ToastItemView, { ...toast, onHide: () => remove(toast.id) }, toast.id)) })
|
|
4198
|
+
] });
|
|
4199
|
+
}
|
|
4200
|
+
var styles15 = StyleSheet15.create({
|
|
4201
|
+
toastContainer: {
|
|
4202
|
+
position: "absolute",
|
|
4203
|
+
top: 60,
|
|
4204
|
+
left: 0,
|
|
4205
|
+
right: 0,
|
|
4206
|
+
zIndex: 9999
|
|
4207
|
+
}
|
|
4208
|
+
});
|
|
4209
|
+
|
|
4210
|
+
// src/overlay/alert/provider.tsx
|
|
4211
|
+
import { useState as useState28, useCallback as useCallback20 } from "react";
|
|
4212
|
+
|
|
4213
|
+
// src/overlay/alert/context.ts
|
|
4214
|
+
import { createContext as createContext4, useContext as useContext4 } from "react";
|
|
4215
|
+
var AlertContext = createContext4(null);
|
|
4216
|
+
function useAlertContext() {
|
|
4217
|
+
const ctx = useContext4(AlertContext);
|
|
4218
|
+
if (!ctx) throw new Error("useAlert must be used within OverlayProvider");
|
|
4219
|
+
return ctx;
|
|
4220
|
+
}
|
|
4221
|
+
|
|
4222
|
+
// src/overlay/alert/component.tsx
|
|
4223
|
+
import { View as View12, Modal as Modal5, StyleSheet as StyleSheet16 } from "react-native";
|
|
4224
|
+
import { jsx as jsx41, jsxs as jsxs18 } from "nativewind/jsx-runtime";
|
|
4225
|
+
function AlertModal({
|
|
4226
|
+
visible,
|
|
4227
|
+
title,
|
|
4228
|
+
message,
|
|
4229
|
+
confirmText,
|
|
4230
|
+
cancelText,
|
|
4231
|
+
showCancel,
|
|
4232
|
+
onConfirm,
|
|
4233
|
+
onCancel
|
|
4234
|
+
}) {
|
|
4235
|
+
if (!visible) return null;
|
|
4236
|
+
return /* @__PURE__ */ jsx41(Modal5, { transparent: true, visible: true, animationType: "fade", children: /* @__PURE__ */ jsx41(View12, { style: styles16.overlay, children: /* @__PURE__ */ jsxs18(View12, { style: styles16.alertBox, children: [
|
|
4237
|
+
title && /* @__PURE__ */ jsx41(AppText, { className: "text-lg font-semibold text-center mb-2", children: title }),
|
|
4238
|
+
message && /* @__PURE__ */ jsx41(AppText, { className: "text-gray-600 text-center mb-4", children: message }),
|
|
4239
|
+
/* @__PURE__ */ jsxs18(AppView, { row: true, gap: 3, className: "mt-2", children: [
|
|
4240
|
+
showCancel && /* @__PURE__ */ jsx41(AppPressable, { onPress: onCancel, className: "flex-1 py-3 bg-gray-100 rounded-lg", children: /* @__PURE__ */ jsx41(AppText, { className: "text-center text-gray-700", children: cancelText || "\u53D6\u6D88" }) }),
|
|
4241
|
+
/* @__PURE__ */ jsx41(AppPressable, { onPress: onConfirm, className: "flex-1 py-3 bg-primary-500 rounded-lg", children: /* @__PURE__ */ jsx41(AppText, { className: "text-center text-white", children: confirmText || "\u786E\u5B9A" }) })
|
|
4242
|
+
] })
|
|
4243
|
+
] }) }) });
|
|
4244
|
+
}
|
|
4245
|
+
var styles16 = StyleSheet16.create({
|
|
4246
|
+
overlay: {
|
|
4247
|
+
flex: 1,
|
|
4248
|
+
backgroundColor: "rgba(0,0,0,0.5)",
|
|
4249
|
+
justifyContent: "center",
|
|
4250
|
+
alignItems: "center"
|
|
4251
|
+
},
|
|
4252
|
+
alertBox: {
|
|
4253
|
+
backgroundColor: "white",
|
|
4254
|
+
borderRadius: 12,
|
|
4255
|
+
padding: 24,
|
|
4256
|
+
margin: 32,
|
|
4257
|
+
minWidth: 280,
|
|
4258
|
+
shadowColor: "#000",
|
|
4259
|
+
shadowOffset: { width: 0, height: 2 },
|
|
4260
|
+
shadowOpacity: 0.25,
|
|
4261
|
+
shadowRadius: 4,
|
|
4262
|
+
elevation: 5
|
|
4263
|
+
}
|
|
4264
|
+
});
|
|
4265
|
+
|
|
4266
|
+
// src/overlay/alert/provider.tsx
|
|
4267
|
+
import { jsx as jsx42, jsxs as jsxs19 } from "nativewind/jsx-runtime";
|
|
4268
|
+
function AlertProvider({ children }) {
|
|
4269
|
+
const [alert, setAlert] = useState28(null);
|
|
4270
|
+
const showAlert = useCallback20((options) => {
|
|
4271
|
+
setAlert({ ...options, visible: true });
|
|
4272
|
+
}, []);
|
|
4273
|
+
const confirm = useCallback20(
|
|
4274
|
+
(options) => {
|
|
4275
|
+
showAlert({ ...options, showCancel: true });
|
|
4276
|
+
},
|
|
4277
|
+
[showAlert]
|
|
4278
|
+
);
|
|
4279
|
+
const hide = useCallback20(() => {
|
|
4280
|
+
setAlert(null);
|
|
4281
|
+
}, []);
|
|
4282
|
+
const handleConfirm = useCallback20(() => {
|
|
4283
|
+
alert?.onConfirm?.();
|
|
4284
|
+
hide();
|
|
4285
|
+
}, [alert, hide]);
|
|
4286
|
+
const handleCancel = useCallback20(() => {
|
|
4287
|
+
alert?.onCancel?.();
|
|
4288
|
+
hide();
|
|
4289
|
+
}, [alert, hide]);
|
|
4290
|
+
return /* @__PURE__ */ jsxs19(AlertContext.Provider, { value: { alert: showAlert, confirm }, children: [
|
|
4291
|
+
children,
|
|
4292
|
+
/* @__PURE__ */ jsx42(
|
|
4293
|
+
AlertModal,
|
|
4294
|
+
{
|
|
4295
|
+
visible: alert?.visible ?? false,
|
|
4296
|
+
title: alert?.title,
|
|
4297
|
+
message: alert?.message,
|
|
4298
|
+
confirmText: alert?.confirmText,
|
|
4299
|
+
cancelText: alert?.cancelText,
|
|
4300
|
+
showCancel: alert?.showCancel,
|
|
4301
|
+
onConfirm: handleConfirm,
|
|
4302
|
+
onCancel: handleCancel
|
|
4303
|
+
}
|
|
4304
|
+
)
|
|
4305
|
+
] });
|
|
4306
|
+
}
|
|
4307
|
+
|
|
4308
|
+
// src/overlay/provider.tsx
|
|
4309
|
+
import { jsx as jsx43 } from "nativewind/jsx-runtime";
|
|
4310
|
+
function OverlayProvider({ children }) {
|
|
4311
|
+
return /* @__PURE__ */ jsx43(LoadingProvider, { children: /* @__PURE__ */ jsx43(ToastProvider, { children: /* @__PURE__ */ jsx43(AlertProvider, { children }) }) });
|
|
4312
|
+
}
|
|
4313
|
+
|
|
4314
|
+
// src/overlay/AppProvider.tsx
|
|
4315
|
+
import { Fragment as Fragment4, jsx as jsx44, jsxs as jsxs20 } from "nativewind/jsx-runtime";
|
|
4316
|
+
var defaultLightTheme = {
|
|
4317
|
+
colors: {
|
|
4318
|
+
primary: "#f38b32",
|
|
4319
|
+
secondary: "#3b82f6",
|
|
4320
|
+
success: "#22c55e",
|
|
4321
|
+
warning: "#f59e0b",
|
|
4322
|
+
error: "#ef4444",
|
|
4323
|
+
info: "#3b82f6"
|
|
4324
|
+
}
|
|
4325
|
+
};
|
|
4326
|
+
var defaultDarkTheme = {
|
|
4327
|
+
colors: {
|
|
4328
|
+
primary: "#f38b32",
|
|
4329
|
+
secondary: "#60a5fa",
|
|
4330
|
+
success: "#4ade80",
|
|
4331
|
+
warning: "#fbbf24",
|
|
4332
|
+
error: "#f87171",
|
|
4333
|
+
info: "#60a5fa"
|
|
4334
|
+
}
|
|
4335
|
+
};
|
|
4336
|
+
function AppProvider({
|
|
4337
|
+
children,
|
|
4338
|
+
enableNavigation = true,
|
|
4339
|
+
enableOverlay = true,
|
|
4340
|
+
enableTheme = true,
|
|
4341
|
+
enableStatusBar = true,
|
|
4342
|
+
enableSafeArea = true,
|
|
4343
|
+
lightTheme = defaultLightTheme,
|
|
4344
|
+
darkTheme = defaultDarkTheme,
|
|
4345
|
+
defaultDark = false,
|
|
4346
|
+
isDark,
|
|
4347
|
+
statusBarProps,
|
|
4348
|
+
...navigationProps
|
|
4349
|
+
}) {
|
|
4350
|
+
let content = children;
|
|
4351
|
+
if (enableOverlay) {
|
|
4352
|
+
content = /* @__PURE__ */ jsx44(OverlayProvider, { children: content });
|
|
4353
|
+
}
|
|
4354
|
+
if (enableNavigation) {
|
|
4355
|
+
content = /* @__PURE__ */ jsx44(NavigationProvider, { ...navigationProps, children: content });
|
|
4356
|
+
}
|
|
4357
|
+
if (enableTheme) {
|
|
4358
|
+
content = /* @__PURE__ */ jsx44(ThemeProvider, { light: lightTheme, dark: darkTheme, defaultDark, isDark, children: /* @__PURE__ */ jsxs20(Fragment4, { children: [
|
|
4359
|
+
enableStatusBar && /* @__PURE__ */ jsx44(AppStatusBar, { testID: "status-bar", ...statusBarProps }),
|
|
4360
|
+
content
|
|
4361
|
+
] }) });
|
|
4362
|
+
}
|
|
4363
|
+
if (enableSafeArea) {
|
|
4364
|
+
content = /* @__PURE__ */ jsx44(SafeAreaProvider, { children: content });
|
|
4365
|
+
}
|
|
4366
|
+
return /* @__PURE__ */ jsx44(Fragment4, { children: content });
|
|
4367
|
+
}
|
|
4368
|
+
|
|
4369
|
+
// src/overlay/loading/hooks.ts
|
|
4370
|
+
function useLoading() {
|
|
4371
|
+
return useLoadingContext();
|
|
4372
|
+
}
|
|
4373
|
+
|
|
4374
|
+
// src/overlay/toast/hooks.ts
|
|
4375
|
+
function useToast() {
|
|
4376
|
+
return useToastContext();
|
|
4377
|
+
}
|
|
4378
|
+
|
|
4379
|
+
// src/overlay/alert/hooks.ts
|
|
4380
|
+
function useAlert() {
|
|
4381
|
+
return useAlertContext();
|
|
4382
|
+
}
|
|
4383
|
+
|
|
4384
|
+
// src/index.ts
|
|
4385
|
+
import { SafeAreaProvider as SafeAreaProvider2 } from "react-native-safe-area-context";
|
|
4386
|
+
export {
|
|
4387
|
+
ActionIcons,
|
|
4388
|
+
Alert,
|
|
4389
|
+
AppButton,
|
|
4390
|
+
AppHeader,
|
|
4391
|
+
AppImage,
|
|
4392
|
+
AppInput,
|
|
4393
|
+
AppList,
|
|
4394
|
+
AppPressable,
|
|
4395
|
+
AppProvider,
|
|
4396
|
+
AppScrollView,
|
|
4397
|
+
AppStatusBar,
|
|
4398
|
+
AppText,
|
|
4399
|
+
AppView,
|
|
4400
|
+
BottomTabBar,
|
|
4401
|
+
Card,
|
|
4402
|
+
Center,
|
|
4403
|
+
Checkbox,
|
|
4404
|
+
CheckboxGroup,
|
|
4405
|
+
Col,
|
|
4406
|
+
DatePicker,
|
|
4407
|
+
DrawerContent,
|
|
4408
|
+
DrawerNavigator,
|
|
4409
|
+
ErrorCode,
|
|
4410
|
+
FileIcons,
|
|
4411
|
+
FormItem,
|
|
4412
|
+
Icon,
|
|
4413
|
+
Loading,
|
|
4414
|
+
MemoryStorage,
|
|
4415
|
+
NavigationContainer2 as NavigationContainer,
|
|
4416
|
+
NavigationIcons,
|
|
4417
|
+
NavigationProvider,
|
|
4418
|
+
OverlayProvider,
|
|
4419
|
+
Page,
|
|
4420
|
+
Progress,
|
|
4421
|
+
Radio,
|
|
4422
|
+
RadioGroup,
|
|
4423
|
+
Row,
|
|
4424
|
+
SafeAreaProvider2 as SafeAreaProvider,
|
|
4425
|
+
SafeBottom,
|
|
4426
|
+
SafeScreen,
|
|
4427
|
+
Select,
|
|
4428
|
+
Slider,
|
|
4429
|
+
StackNavigator,
|
|
4430
|
+
StatusIcons,
|
|
4431
|
+
Switch,
|
|
4432
|
+
TabNavigator,
|
|
4433
|
+
ThemeProvider,
|
|
4434
|
+
Toast,
|
|
4435
|
+
adjustBrightness,
|
|
4436
|
+
capitalize,
|
|
4437
|
+
clamp,
|
|
4438
|
+
clsx,
|
|
4439
|
+
cn,
|
|
4440
|
+
createAPI,
|
|
4441
|
+
createDrawerScreens,
|
|
4442
|
+
createNavigationTheme,
|
|
4443
|
+
createStackScreens,
|
|
4444
|
+
createTabScreens,
|
|
4445
|
+
createTheme,
|
|
4446
|
+
deepMerge,
|
|
4447
|
+
enhanceError,
|
|
4448
|
+
formatCurrency,
|
|
4449
|
+
formatDate,
|
|
4450
|
+
formatNumber,
|
|
4451
|
+
formatPercent,
|
|
4452
|
+
formatRelativeTime,
|
|
4453
|
+
generateColorPalette,
|
|
4454
|
+
getThemeColors,
|
|
4455
|
+
getValidationErrors,
|
|
4456
|
+
hexToRgb,
|
|
4457
|
+
isDevelopment,
|
|
4458
|
+
isValidEmail,
|
|
4459
|
+
isValidPhone,
|
|
4460
|
+
mapHttpStatus,
|
|
4461
|
+
omit,
|
|
4462
|
+
pick,
|
|
4463
|
+
rgbToHex,
|
|
4464
|
+
slugify,
|
|
4465
|
+
storage,
|
|
4466
|
+
truncate,
|
|
4467
|
+
twMerge,
|
|
4468
|
+
useAlert,
|
|
4469
|
+
useAsyncState,
|
|
4470
|
+
useBackHandler,
|
|
4471
|
+
useDebounce,
|
|
4472
|
+
useDimensions,
|
|
4473
|
+
useDrawerNavigation,
|
|
4474
|
+
useFocusEffect,
|
|
4475
|
+
useForm,
|
|
4476
|
+
useInfinite,
|
|
4477
|
+
useIsFocused,
|
|
4478
|
+
useKeyboard,
|
|
4479
|
+
useLoading,
|
|
4480
|
+
useMemoizedFn,
|
|
4481
|
+
useMutation,
|
|
4482
|
+
useNavigation,
|
|
4483
|
+
useNavigationState,
|
|
4484
|
+
useOrientation,
|
|
4485
|
+
usePagination,
|
|
4486
|
+
usePrevious,
|
|
4487
|
+
useQuery,
|
|
4488
|
+
useRefresh,
|
|
4489
|
+
useRequest,
|
|
4490
|
+
useRoute,
|
|
4491
|
+
useScrollToTop,
|
|
4492
|
+
useSetState,
|
|
4493
|
+
useStackNavigation,
|
|
4494
|
+
useStorage,
|
|
4495
|
+
useTabNavigation,
|
|
4496
|
+
useTheme,
|
|
4497
|
+
useThemeColors,
|
|
4498
|
+
useThrottle,
|
|
4499
|
+
useToast,
|
|
4500
|
+
useToggle,
|
|
4501
|
+
useUpdateEffect,
|
|
4502
|
+
z
|
|
4503
|
+
};
|
|
4504
|
+
//# sourceMappingURL=index.mjs.map
|