@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/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