@gaozh1024/rn-kit 0.3.4 → 0.4.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.js CHANGED
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  ActionIcons: () => ActionIcons,
34
34
  Alert: () => Alert,
35
35
  AppButton: () => AppButton,
36
+ AppErrorBoundary: () => AppErrorBoundary,
36
37
  AppFocusedStatusBar: () => AppFocusedStatusBar,
37
38
  AppHeader: () => AppHeader,
38
39
  AppImage: () => AppImage,
@@ -60,13 +61,18 @@ __export(index_exports, {
60
61
  FormItem: () => FormItem,
61
62
  GradientView: () => GradientView,
62
63
  Icon: () => Icon,
64
+ KeyboardDismissView: () => KeyboardDismissView,
65
+ LOG_LEVEL_WEIGHT: () => LOG_LEVEL_WEIGHT,
63
66
  Loading: () => Loading,
67
+ LogOverlay: () => LogOverlay,
68
+ LoggerProvider: () => LoggerProvider,
64
69
  MemoryStorage: () => MemoryStorage,
65
70
  NavigationContainer: () => import_native6.NavigationContainer,
66
71
  NavigationIcons: () => NavigationIcons,
67
72
  NavigationProvider: () => NavigationProvider,
68
73
  OverlayProvider: () => OverlayProvider,
69
74
  PageDrawer: () => PageDrawer,
75
+ Picker: () => Picker,
70
76
  Progress: () => Progress,
71
77
  Radio: () => Radio,
72
78
  RadioGroup: () => RadioGroup,
@@ -88,7 +94,10 @@ __export(index_exports, {
88
94
  clsx: () => import_clsx.clsx,
89
95
  cn: () => cn,
90
96
  createAPI: () => createAPI,
97
+ createApiLoggerTransport: () => createApiLoggerTransport,
98
+ createConsoleTransport: () => createConsoleTransport,
91
99
  createDrawerScreens: () => createDrawerScreens,
100
+ createLogEntry: () => createLogEntry,
92
101
  createNavigationTheme: () => createNavigationTheme,
93
102
  createStackScreens: () => createStackScreens,
94
103
  createTabScreens: () => createTabScreens,
@@ -97,10 +106,12 @@ __export(index_exports, {
97
106
  enhanceError: () => enhanceError,
98
107
  formatCurrency: () => formatCurrency,
99
108
  formatDate: () => formatDate,
109
+ formatLogTime: () => formatLogTime,
100
110
  formatNumber: () => formatNumber,
101
111
  formatPercent: () => formatPercent,
102
112
  formatRelativeTime: () => formatRelativeTime,
103
113
  generateColorPalette: () => generateColorPalette,
114
+ getGlobalLogger: () => getGlobalLogger,
104
115
  getThemeColors: () => getThemeColors,
105
116
  getValidationErrors: () => getValidationErrors,
106
117
  hexToRgb: () => hexToRgb,
@@ -108,11 +119,16 @@ __export(index_exports, {
108
119
  isValidEmail: () => isValidEmail,
109
120
  isValidPhone: () => isValidPhone,
110
121
  mapHttpStatus: () => mapHttpStatus,
122
+ normalizeConsoleArgs: () => normalizeConsoleArgs,
111
123
  omit: () => omit,
112
124
  pick: () => pick,
113
125
  rgbToHex: () => rgbToHex,
126
+ serializeLogEntries: () => serializeLogEntries,
127
+ setGlobalLogger: () => setGlobalLogger,
128
+ shouldLog: () => shouldLog,
114
129
  slugify: () => slugify,
115
130
  storage: () => storage,
131
+ stringifyLogData: () => stringifyLogData,
116
132
  truncate: () => truncate,
117
133
  twMerge: () => import_tailwind_merge.twMerge,
118
134
  useAlert: () => useAlert,
@@ -127,6 +143,7 @@ __export(index_exports, {
127
143
  useIsFocused: () => import_native5.useIsFocused,
128
144
  useKeyboard: () => useKeyboard,
129
145
  useLoading: () => useLoading,
146
+ useLogger: () => useLogger,
130
147
  useMemoizedFn: () => useMemoizedFn,
131
148
  useMutation: () => import_react_query.useMutation,
132
149
  useNavigation: () => useNavigation,
@@ -406,6 +423,11 @@ var import_react2 = require("react");
406
423
  function getThemeColors(theme, isDark) {
407
424
  return {
408
425
  primary: theme.colors.primary?.[500] || "#f38b32",
426
+ success: theme.colors.success?.[500] || "#22c55e",
427
+ warning: theme.colors.warning?.[500] || "#f59e0b",
428
+ error: theme.colors.error?.[500] || "#ef4444",
429
+ info: theme.colors.info?.[500] || theme.colors.secondary?.[500] || "#3b82f6",
430
+ muted: isDark ? "#9ca3af" : "#6b7280",
409
431
  primarySurface: isDark ? theme.colors.primary?.[900] || "#7c2d12" : theme.colors.primary?.[50] || "#fff7ed",
410
432
  background: theme.colors.background?.[500] || (isDark ? "#0a0a0a" : "#ffffff"),
411
433
  card: theme.colors.card?.[500] || (isDark ? "#1f2937" : "#ffffff"),
@@ -502,6 +524,189 @@ function useAsyncState() {
502
524
 
503
525
  // src/core/api/create-api.ts
504
526
  var import_zod = require("zod");
527
+
528
+ // src/core/logger/utils.ts
529
+ var LOG_LEVEL_WEIGHT = {
530
+ debug: 10,
531
+ info: 20,
532
+ warn: 30,
533
+ error: 40
534
+ };
535
+ function shouldLog(currentLevel, nextLevel) {
536
+ return LOG_LEVEL_WEIGHT[nextLevel] >= LOG_LEVEL_WEIGHT[currentLevel];
537
+ }
538
+ function createLogEntry(input) {
539
+ return {
540
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
541
+ level: input.level,
542
+ message: input.message,
543
+ data: input.data,
544
+ namespace: input.namespace,
545
+ timestamp: Date.now(),
546
+ source: input.source ?? "logger"
547
+ };
548
+ }
549
+ function formatLogTime(timestamp) {
550
+ const date = new Date(timestamp);
551
+ const hh = String(date.getHours()).padStart(2, "0");
552
+ const mm = String(date.getMinutes()).padStart(2, "0");
553
+ const ss = String(date.getSeconds()).padStart(2, "0");
554
+ return `${hh}:${mm}:${ss}`;
555
+ }
556
+ function stringifyLogData(data) {
557
+ if (data === void 0) return "";
558
+ if (typeof data === "string") return data;
559
+ try {
560
+ return JSON.stringify(data, null, 2);
561
+ } catch {
562
+ return String(data);
563
+ }
564
+ }
565
+ function normalizeConsoleArgs(args) {
566
+ if (args.length === 0) {
567
+ return { message: "", data: void 0 };
568
+ }
569
+ const [first, ...rest] = args;
570
+ if (typeof first === "string") {
571
+ return {
572
+ message: first,
573
+ data: rest.length === 0 ? void 0 : rest.length === 1 ? rest[0] : rest
574
+ };
575
+ }
576
+ return {
577
+ message: stringifyLogData(first),
578
+ data: args.length === 1 ? first : args
579
+ };
580
+ }
581
+ function serializeLogEntries(entries) {
582
+ return JSON.stringify(
583
+ entries.map((entry) => ({
584
+ id: entry.id,
585
+ level: entry.level,
586
+ message: entry.message,
587
+ namespace: entry.namespace,
588
+ timestamp: entry.timestamp,
589
+ source: entry.source,
590
+ data: entry.data
591
+ })),
592
+ null,
593
+ 2
594
+ );
595
+ }
596
+
597
+ // src/core/logger/transports.ts
598
+ var ANSI_COLORS = {
599
+ debug: "\x1B[90m",
600
+ info: "\x1B[36m",
601
+ warn: "\x1B[33m",
602
+ error: "\x1B[31m",
603
+ reset: "\x1B[0m"
604
+ };
605
+ function supportsAnsiColors() {
606
+ return typeof process !== "undefined" && Boolean(process.stdout?.isTTY);
607
+ }
608
+ function resolveConsoleMethod(consoleRef, entry) {
609
+ const target = consoleRef ?? console;
610
+ if (entry.level === "debug" && typeof target.debug === "function") return target.debug.bind(target);
611
+ if (entry.level === "info" && typeof target.info === "function") return target.info.bind(target);
612
+ if (entry.level === "warn" && typeof target.warn === "function") return target.warn.bind(target);
613
+ if (entry.level === "error" && typeof target.error === "function") return target.error.bind(target);
614
+ return target.log.bind(target);
615
+ }
616
+ function createConsoleTransport(options = {}) {
617
+ const useAnsiColors = options.useAnsiColors ?? supportsAnsiColors();
618
+ return (entry) => {
619
+ if (entry.source === "console") return;
620
+ const method = resolveConsoleMethod(options.consoleRef, entry);
621
+ const time = formatLogTime(entry.timestamp);
622
+ const basePrefix = `[${time}] [${entry.level.toUpperCase()}]${entry.namespace ? ` [${entry.namespace}]` : ""}`;
623
+ const prefix = useAnsiColors ? `${ANSI_COLORS[entry.level]}${basePrefix}${ANSI_COLORS.reset}` : basePrefix;
624
+ if (entry.data === void 0) {
625
+ method(`${prefix} ${entry.message}`);
626
+ return;
627
+ }
628
+ method(`${prefix} ${entry.message}`, entry.data);
629
+ };
630
+ }
631
+
632
+ // src/core/logger/registry.ts
633
+ var globalLogger = null;
634
+ function setGlobalLogger(logger) {
635
+ globalLogger = logger;
636
+ }
637
+ function getGlobalLogger() {
638
+ return globalLogger;
639
+ }
640
+
641
+ // src/core/api/observability.ts
642
+ function defaultSanitize(value, sanitize, stage, field) {
643
+ if (!sanitize) return value;
644
+ return sanitize(value, { stage, field });
645
+ }
646
+ function resolveMessage(event) {
647
+ if (event.stage === "request") {
648
+ return `API Request ${event.method} ${event.path}`;
649
+ }
650
+ if (event.stage === "response") {
651
+ return `API Response ${event.method} ${event.path} ${event.statusCode ?? "-"}`;
652
+ }
653
+ return `API Error ${event.method} ${event.path}`;
654
+ }
655
+ function resolveLevel(error) {
656
+ if (!error) return "info";
657
+ if (error.code === "VALIDATION" || error.code === "BUSINESS") return "warn";
658
+ return "error";
659
+ }
660
+ function createApiLoggerTransport(config = {}) {
661
+ return (event) => {
662
+ const logger = config.logger ?? getGlobalLogger();
663
+ if (!logger) return;
664
+ const data = {
665
+ endpointName: event.endpointName,
666
+ method: event.method,
667
+ path: event.path,
668
+ url: event.url,
669
+ statusCode: event.statusCode,
670
+ durationMs: event.durationMs,
671
+ input: config.includeInput || event.stage !== "request" ? defaultSanitize(event.input, config.sanitize, event.stage, "input") : void 0,
672
+ responseData: config.includeResponseData ? defaultSanitize(event.responseData, config.sanitize, event.stage, "responseData") : void 0,
673
+ error: event.error ? defaultSanitize(
674
+ {
675
+ code: event.error.code,
676
+ message: event.error.message,
677
+ statusCode: event.error.statusCode,
678
+ retryable: event.error.retryable
679
+ },
680
+ config.sanitize,
681
+ event.stage,
682
+ "error"
683
+ ) : void 0
684
+ };
685
+ const namespace = config.namespace ?? "api";
686
+ const message = resolveMessage(event);
687
+ if (event.stage === "request") {
688
+ logger.debug(message, data, namespace);
689
+ return;
690
+ }
691
+ if (event.stage === "response") {
692
+ logger.info(message, data, namespace);
693
+ return;
694
+ }
695
+ logger[resolveLevel(event.error)](message, data, namespace);
696
+ };
697
+ }
698
+ function resolveApiObservability(config) {
699
+ const enabled = config?.enabled ?? isDevelopment();
700
+ if (!enabled) {
701
+ return { enabled, transports: [] };
702
+ }
703
+ return {
704
+ enabled,
705
+ transports: [createApiLoggerTransport(config), ...config?.transports ?? []]
706
+ };
707
+ }
708
+
709
+ // src/core/api/create-api.ts
505
710
  function parseZodError(error) {
506
711
  const first = error.errors[0];
507
712
  return {
@@ -554,6 +759,7 @@ async function notifyError(error, context, endpoint, config) {
554
759
  function createAPI(config) {
555
760
  const endpoints = {};
556
761
  const fetcher = config.fetcher ?? fetch;
762
+ const observability = resolveApiObservability(config.observability);
557
763
  for (const [name, ep] of Object.entries(config.endpoints)) {
558
764
  endpoints[name] = async (input) => {
559
765
  const context = {
@@ -569,11 +775,43 @@ function createAPI(config) {
569
775
  }
570
776
  }
571
777
  const url = config.baseURL + ep.path;
778
+ const startAt = Date.now();
779
+ if (observability.enabled) {
780
+ await Promise.all(
781
+ observability.transports.map(
782
+ (transport) => transport({
783
+ stage: "request",
784
+ endpointName: name,
785
+ path: ep.path,
786
+ method: ep.method,
787
+ url,
788
+ input
789
+ })
790
+ )
791
+ );
792
+ }
572
793
  let response;
573
794
  try {
574
795
  response = await fetcher(url, { method: ep.method });
575
796
  } catch (error) {
576
- throw await notifyError(parseNetworkError(error), context, ep, config);
797
+ const enhanced = await notifyError(parseNetworkError(error), context, ep, config);
798
+ if (observability.enabled) {
799
+ await Promise.all(
800
+ observability.transports.map(
801
+ (transport) => transport({
802
+ stage: "error",
803
+ endpointName: name,
804
+ path: ep.path,
805
+ method: ep.method,
806
+ url,
807
+ input,
808
+ durationMs: Date.now() - startAt,
809
+ error: enhanced
810
+ })
811
+ )
812
+ );
813
+ }
814
+ throw enhanced;
577
815
  }
578
816
  context.response = response;
579
817
  let data = void 0;
@@ -588,18 +826,115 @@ function createAPI(config) {
588
826
  }
589
827
  context.responseData = data;
590
828
  if (!response.ok) {
591
- throw await notifyError(parseHttpError(response, data), context, ep, config);
829
+ const enhanced = await notifyError(parseHttpError(response, data), context, ep, config);
830
+ if (observability.enabled) {
831
+ await Promise.all(
832
+ observability.transports.map(
833
+ (transport) => transport({
834
+ stage: "error",
835
+ endpointName: name,
836
+ path: ep.path,
837
+ method: ep.method,
838
+ url,
839
+ input,
840
+ response,
841
+ responseData: data,
842
+ statusCode: response.status,
843
+ durationMs: Date.now() - startAt,
844
+ error: enhanced
845
+ })
846
+ )
847
+ );
848
+ }
849
+ throw enhanced;
592
850
  }
593
851
  const businessError = ep.parseBusinessError?.(data, response) ?? config.parseBusinessError?.(data, response);
594
852
  if (businessError) {
595
- throw await notifyError(businessError, context, ep, config);
853
+ const enhanced = await notifyError(businessError, context, ep, config);
854
+ if (observability.enabled) {
855
+ await Promise.all(
856
+ observability.transports.map(
857
+ (transport) => transport({
858
+ stage: "error",
859
+ endpointName: name,
860
+ path: ep.path,
861
+ method: ep.method,
862
+ url,
863
+ input,
864
+ response,
865
+ responseData: data,
866
+ statusCode: response.status,
867
+ durationMs: Date.now() - startAt,
868
+ error: enhanced
869
+ })
870
+ )
871
+ );
872
+ }
873
+ throw enhanced;
596
874
  }
597
875
  try {
598
876
  if (ep.output) {
599
- return ep.output.parse(data);
877
+ const parsed = ep.output.parse(data);
878
+ if (observability.enabled) {
879
+ await Promise.all(
880
+ observability.transports.map(
881
+ (transport) => transport({
882
+ stage: "response",
883
+ endpointName: name,
884
+ path: ep.path,
885
+ method: ep.method,
886
+ url,
887
+ input,
888
+ response,
889
+ responseData: parsed,
890
+ statusCode: response.status,
891
+ durationMs: Date.now() - startAt
892
+ })
893
+ )
894
+ );
895
+ }
896
+ return parsed;
600
897
  }
601
898
  } catch (error) {
602
- throw await notifyError(parseUnknownError(error), context, ep, config);
899
+ const enhanced = await notifyError(parseUnknownError(error), context, ep, config);
900
+ if (observability.enabled) {
901
+ await Promise.all(
902
+ observability.transports.map(
903
+ (transport) => transport({
904
+ stage: "error",
905
+ endpointName: name,
906
+ path: ep.path,
907
+ method: ep.method,
908
+ url,
909
+ input,
910
+ response,
911
+ responseData: data,
912
+ statusCode: response.status,
913
+ durationMs: Date.now() - startAt,
914
+ error: enhanced
915
+ })
916
+ )
917
+ );
918
+ }
919
+ throw enhanced;
920
+ }
921
+ if (observability.enabled) {
922
+ await Promise.all(
923
+ observability.transports.map(
924
+ (transport) => transport({
925
+ stage: "response",
926
+ endpointName: name,
927
+ path: ep.path,
928
+ method: ep.method,
929
+ url,
930
+ input,
931
+ response,
932
+ responseData: data,
933
+ statusCode: response.status,
934
+ durationMs: Date.now() - startAt
935
+ })
936
+ )
937
+ );
603
938
  }
604
939
  return data;
605
940
  };
@@ -1205,6 +1540,7 @@ function AppScrollView({
1205
1540
  surface,
1206
1541
  rounded,
1207
1542
  className,
1543
+ dismissKeyboardOnPressOutside = false,
1208
1544
  children,
1209
1545
  style,
1210
1546
  ...props
@@ -1212,7 +1548,7 @@ function AppScrollView({
1212
1548
  const { theme, isDark } = useOptionalTheme();
1213
1549
  const resolvedBgColor = resolveSurfaceColor(surface, theme, isDark) ?? resolveNamedColor(bg, theme, isDark);
1214
1550
  const shouldUseClassBg = !!bg && !resolvedBgColor;
1215
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1551
+ const content = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1216
1552
  import_react_native2.ScrollView,
1217
1553
  {
1218
1554
  className: cn(
@@ -1225,11 +1561,16 @@ function AppScrollView({
1225
1561
  rounded && `rounded-${rounded}`,
1226
1562
  className
1227
1563
  ),
1228
- style: [resolvedBgColor ? { backgroundColor: resolvedBgColor } : void 0, style],
1229
1564
  ...props,
1565
+ style: [resolvedBgColor ? { backgroundColor: resolvedBgColor } : void 0, style],
1566
+ keyboardShouldPersistTaps: dismissKeyboardOnPressOutside ? props.keyboardShouldPersistTaps ?? "handled" : props.keyboardShouldPersistTaps,
1230
1567
  children
1231
1568
  }
1232
1569
  );
1570
+ if (!dismissKeyboardOnPressOutside) {
1571
+ return content;
1572
+ }
1573
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.TouchableWithoutFeedback, { onPress: import_react_native2.Keyboard.dismiss, accessible: false, children: content });
1233
1574
  }
1234
1575
 
1235
1576
  // src/ui/primitives/AppText.tsx
@@ -1312,8 +1653,19 @@ function AppPressable({
1312
1653
  );
1313
1654
  }
1314
1655
 
1315
- // src/ui/layout/Row.tsx
1656
+ // src/ui/primitives/KeyboardDismissView.tsx
1657
+ var import_react_native5 = require("react-native");
1316
1658
  var import_jsx_runtime6 = require("nativewind/jsx-runtime");
1659
+ function KeyboardDismissView({ enabled = true, children, ...props }) {
1660
+ const content = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AppView, { ...props, children });
1661
+ if (!enabled) {
1662
+ return content;
1663
+ }
1664
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.TouchableWithoutFeedback, { onPress: import_react_native5.Keyboard.dismiss, accessible: false, children: content });
1665
+ }
1666
+
1667
+ // src/ui/layout/Row.tsx
1668
+ var import_jsx_runtime7 = require("nativewind/jsx-runtime");
1317
1669
  var justifyMap = {
1318
1670
  start: "justify-start",
1319
1671
  center: "justify-center",
@@ -1328,11 +1680,11 @@ var itemsMap = {
1328
1680
  stretch: "items-stretch"
1329
1681
  };
1330
1682
  function Row({ justify = "start", items = "center", className, ...props }) {
1331
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AppView, { row: true, className: cn(justifyMap[justify], itemsMap[items], className), ...props });
1683
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AppView, { row: true, className: cn(justifyMap[justify], itemsMap[items], className), ...props });
1332
1684
  }
1333
1685
 
1334
1686
  // src/ui/layout/Col.tsx
1335
- var import_jsx_runtime7 = require("nativewind/jsx-runtime");
1687
+ var import_jsx_runtime8 = require("nativewind/jsx-runtime");
1336
1688
  var justifyMap2 = {
1337
1689
  start: "justify-start",
1338
1690
  center: "justify-center",
@@ -1347,19 +1699,19 @@ var itemsMap2 = {
1347
1699
  stretch: "items-stretch"
1348
1700
  };
1349
1701
  function Col({ justify = "start", items = "stretch", className, ...props }) {
1350
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AppView, { className: cn(justifyMap2[justify], itemsMap2[items], className), ...props });
1702
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AppView, { className: cn(justifyMap2[justify], itemsMap2[items], className), ...props });
1351
1703
  }
1352
1704
 
1353
1705
  // src/ui/layout/Center.tsx
1354
- var import_jsx_runtime8 = require("nativewind/jsx-runtime");
1706
+ var import_jsx_runtime9 = require("nativewind/jsx-runtime");
1355
1707
  function Center({ flex = true, ...props }) {
1356
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AppView, { center: true, flex, ...props });
1708
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AppView, { center: true, flex, ...props });
1357
1709
  }
1358
1710
 
1359
1711
  // src/ui/layout/SafeScreen.tsx
1360
- var import_react_native5 = require("react-native");
1712
+ var import_react_native6 = require("react-native");
1361
1713
  var import_react_native_safe_area_context = require("react-native-safe-area-context");
1362
- var import_jsx_runtime9 = require("nativewind/jsx-runtime");
1714
+ var import_jsx_runtime10 = require("nativewind/jsx-runtime");
1363
1715
  function SafeScreen({
1364
1716
  top = true,
1365
1717
  bottom = true,
@@ -1368,6 +1720,7 @@ function SafeScreen({
1368
1720
  bg,
1369
1721
  surface,
1370
1722
  flex = true,
1723
+ dismissKeyboardOnPressOutside = false,
1371
1724
  className,
1372
1725
  children,
1373
1726
  style,
@@ -1377,8 +1730,8 @@ function SafeScreen({
1377
1730
  const { theme, isDark } = useOptionalTheme();
1378
1731
  const resolvedBgColor = resolveSurfaceColor(surface, theme, isDark) ?? resolveNamedColor(bg, theme, isDark);
1379
1732
  const shouldUseClassBg = !!bg && !resolvedBgColor;
1380
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1381
- import_react_native5.View,
1733
+ const content = /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1734
+ import_react_native6.View,
1382
1735
  {
1383
1736
  className: cn(flex && "flex-1", shouldUseClassBg && `bg-${bg}`, className),
1384
1737
  style: [
@@ -1396,8 +1749,12 @@ function SafeScreen({
1396
1749
  children
1397
1750
  }
1398
1751
  );
1752
+ if (!dismissKeyboardOnPressOutside) {
1753
+ return content;
1754
+ }
1755
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native6.TouchableWithoutFeedback, { onPress: import_react_native6.Keyboard.dismiss, accessible: false, children: content });
1399
1756
  }
1400
- var styles = import_react_native5.StyleSheet.create({
1757
+ var styles = import_react_native6.StyleSheet.create({
1401
1758
  flex: {
1402
1759
  flex: 1
1403
1760
  }
@@ -1407,19 +1764,20 @@ function AppScreen({
1407
1764
  className,
1408
1765
  ...props
1409
1766
  }) {
1410
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SafeScreen, { flex: true, surface: "background", ...props, className, children });
1767
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SafeScreen, { flex: true, surface: "background", ...props, className, children });
1411
1768
  }
1412
1769
  function SafeBottom({
1413
1770
  children,
1414
1771
  className,
1415
1772
  ...props
1416
1773
  }) {
1417
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SafeScreen, { bottom: true, left: true, right: true, flex: false, ...props, className, children });
1774
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SafeScreen, { bottom: true, left: true, right: true, flex: false, ...props, className, children });
1418
1775
  }
1419
1776
 
1420
1777
  // src/ui/actions/AppButton.tsx
1421
- var import_react_native6 = require("react-native");
1422
- var import_jsx_runtime10 = require("nativewind/jsx-runtime");
1778
+ var import_react13 = require("react");
1779
+ var import_react_native7 = require("react-native");
1780
+ var import_jsx_runtime11 = require("nativewind/jsx-runtime");
1423
1781
  function AppButton({
1424
1782
  variant = "solid",
1425
1783
  size = "md",
@@ -1427,6 +1785,7 @@ function AppButton({
1427
1785
  loading,
1428
1786
  disabled,
1429
1787
  onPress,
1788
+ dismissKeyboardOnPress = true,
1430
1789
  children,
1431
1790
  className
1432
1791
  }) {
@@ -1447,11 +1806,17 @@ function AppButton({
1447
1806
  const ghostBackgroundColor = isDark ? "rgba(255,255,255,0.04)" : "transparent";
1448
1807
  const loadingColor = variant === "solid" ? "white" : buttonColors[color];
1449
1808
  const textColor = variant === "solid" ? "#ffffff" : variant === "ghost" ? ghostTextColor : buttonColors[color];
1809
+ const handlePress = (0, import_react13.useCallback)(() => {
1810
+ if (dismissKeyboardOnPress) {
1811
+ import_react_native7.Keyboard.dismiss();
1812
+ }
1813
+ onPress?.();
1814
+ }, [dismissKeyboardOnPress, onPress]);
1450
1815
  const buttonStyle = variant === "solid" ? { backgroundColor: buttonColors[color] } : variant === "outline" ? { borderWidth: 0.5, borderColor: buttonColors[color], backgroundColor: "transparent" } : { backgroundColor: ghostBackgroundColor };
1451
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1816
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1452
1817
  AppPressable,
1453
1818
  {
1454
- onPress,
1819
+ onPress: onPress ? handlePress : void 0,
1455
1820
  disabled: isDisabled,
1456
1821
  className: cn(
1457
1822
  "flex-row items-center justify-center rounded-lg",
@@ -1460,30 +1825,61 @@ function AppButton({
1460
1825
  className
1461
1826
  ),
1462
1827
  style: buttonStyle,
1463
- children: loading ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native6.ActivityIndicator, { size: "small", color: loadingColor }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AppText, { weight: "semibold", style: { color: textColor }, children })
1828
+ children: loading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native7.ActivityIndicator, { size: "small", color: loadingColor }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AppText, { weight: "semibold", style: { color: textColor }, children })
1464
1829
  }
1465
1830
  );
1466
1831
  }
1467
1832
 
1468
1833
  // src/ui/feedback/Toast.tsx
1469
- var import_jsx_runtime11 = require("nativewind/jsx-runtime");
1470
- var typeStyles = {
1471
- success: "bg-green-500",
1472
- error: "bg-red-500",
1473
- warning: "bg-yellow-500",
1474
- info: "bg-blue-500"
1475
- };
1476
- function Toast({ message, type = "info", visible = true }) {
1834
+ var import_jsx_runtime12 = require("nativewind/jsx-runtime");
1835
+ function Toast({ message, type = "info", visible = true, testID }) {
1836
+ const { theme } = useOptionalTheme();
1477
1837
  if (!visible) return null;
1478
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AppView, { className: cn("px-4 py-3 rounded-lg", typeStyles[type]), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AppText, { color: "white", children: message }) });
1838
+ const palette = {
1839
+ success: {
1840
+ backgroundColor: theme.colors.success?.[500] || "#22c55e",
1841
+ textColor: "#ffffff"
1842
+ },
1843
+ error: {
1844
+ backgroundColor: theme.colors.error?.[500] || "#ef4444",
1845
+ textColor: "#ffffff"
1846
+ },
1847
+ warning: {
1848
+ backgroundColor: theme.colors.warning?.[500] || "#f59e0b",
1849
+ textColor: "#111827"
1850
+ },
1851
+ info: {
1852
+ backgroundColor: theme.colors.info?.[500] || theme.colors.primary?.[500] || "#3b82f6",
1853
+ textColor: "#ffffff"
1854
+ }
1855
+ };
1856
+ const currentPalette = palette[type];
1857
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1858
+ AppView,
1859
+ {
1860
+ testID,
1861
+ className: "px-4 py-3 rounded-lg",
1862
+ style: { backgroundColor: currentPalette.backgroundColor },
1863
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AppText, { style: { color: currentPalette.textColor }, children: message })
1864
+ }
1865
+ );
1479
1866
  }
1480
1867
 
1481
1868
  // src/ui/feedback/Alert.tsx
1482
- var import_react13 = require("react");
1483
- var import_react_native7 = require("react-native");
1484
- var import_jsx_runtime12 = require("nativewind/jsx-runtime");
1869
+ var import_react14 = require("react");
1870
+ var import_react_native8 = require("react-native");
1871
+ var import_jsx_runtime13 = require("nativewind/jsx-runtime");
1872
+ function createAnimatedValue(value) {
1873
+ const AnimatedValue = import_react_native8.Animated.Value;
1874
+ try {
1875
+ return new AnimatedValue(value);
1876
+ } catch {
1877
+ return AnimatedValue(value);
1878
+ }
1879
+ }
1485
1880
  function Alert({ visible, title, message, buttons, onClose }) {
1486
1881
  const { theme, isDark } = useTheme();
1882
+ const progress = (0, import_react14.useRef)(createAnimatedValue(0)).current;
1487
1883
  const modalBgColor = isDark ? "#1f2937" : "#ffffff";
1488
1884
  const textColor = isDark ? "#ffffff" : "#1f2937";
1489
1885
  const messageColor = isDark ? "#9ca3af" : "#6b7280";
@@ -1491,7 +1887,19 @@ function Alert({ visible, title, message, buttons, onClose }) {
1491
1887
  const cancelButtonBg = isDark ? "#374151" : "#f3f4f6";
1492
1888
  const cancelButtonText = isDark ? "#ffffff" : "#374151";
1493
1889
  const destructiveColor = theme.colors.error?.[500] || "#ef4444";
1494
- const handleButtonPress = (0, import_react13.useCallback)(
1890
+ (0, import_react14.useEffect)(() => {
1891
+ if (!visible) {
1892
+ progress.setValue(0);
1893
+ return;
1894
+ }
1895
+ progress.setValue(0);
1896
+ import_react_native8.Animated.timing(progress, {
1897
+ toValue: 1,
1898
+ duration: 220,
1899
+ useNativeDriver: true
1900
+ }).start();
1901
+ }, [progress, visible]);
1902
+ const handleButtonPress = (0, import_react14.useCallback)(
1495
1903
  (button) => (e) => {
1496
1904
  e.stopPropagation();
1497
1905
  button.onPress?.();
@@ -1528,108 +1936,141 @@ function Alert({ visible, title, message, buttons, onClose }) {
1528
1936
  }
1529
1937
  return "#ffffff";
1530
1938
  };
1531
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1532
- import_react_native7.Modal,
1939
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1940
+ import_react_native8.Modal,
1533
1941
  {
1534
1942
  visible,
1535
1943
  transparent: true,
1536
- animationType: "fade",
1944
+ animationType: "none",
1537
1945
  onRequestClose: onClose,
1538
1946
  statusBarTranslucent: true,
1539
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AppView, { className: "flex-1", style: { backgroundColor: "rgba(0,0,0,0.5)" }, center: true, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1540
- AppView,
1541
- {
1542
- className: "rounded-xl mx-8 min-w-[280px]",
1543
- style: { backgroundColor: modalBgColor },
1544
- children: [
1545
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(AppView, { className: "px-6 py-5", children: [
1546
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1547
- AppText,
1548
- {
1549
- size: "lg",
1550
- weight: "semibold",
1551
- className: "text-center mb-2",
1552
- style: { color: textColor },
1553
- children: title
1554
- }
1555
- ),
1556
- message && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AppText, { size: "sm", style: { color: messageColor }, className: "text-center leading-5", children: message })
1557
- ] }),
1558
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AppView, { className: "border-t", style: { borderTopColor: borderColor }, children: buttons.length === 1 ? (
1559
- // 单个按钮
1560
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1561
- import_react_native7.TouchableOpacity,
1562
- {
1563
- onPress: handleButtonPress(buttons[0]),
1564
- className: "py-3 rounded-b-xl",
1565
- style: [styles2.singleButton, getButtonStyle(buttons[0])],
1566
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1567
- AppText,
1568
- {
1569
- weight: "medium",
1570
- className: "text-center",
1571
- style: { color: getButtonTextColor(buttons[0]) },
1572
- children: buttons[0].text
1573
- }
1574
- )
1575
- }
1576
- )
1577
- ) : buttons.length === 2 ? (
1578
- // 两个按钮横向排列
1579
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AppView, { row: true, style: styles2.twoButtonContainer, children: buttons.map((button, index) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1580
- import_react_native7.TouchableOpacity,
1581
- {
1582
- onPress: handleButtonPress(button),
1583
- className: cn(
1584
- "py-3 flex-1",
1585
- index === 0 && "rounded-bl-xl",
1586
- index === 1 && "rounded-br-xl"
1587
- ),
1588
- style: [
1589
- styles2.twoButton,
1590
- index > 0 && { borderLeftColor: borderColor },
1591
- getButtonStyle(button)
1592
- ],
1593
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1594
- AppText,
1595
- {
1596
- weight: "medium",
1597
- className: "text-center",
1598
- style: { color: getButtonTextColor(button) },
1599
- children: button.text
1600
- }
1601
- )
1602
- },
1603
- index
1604
- )) })
1605
- ) : (
1606
- // 多个按钮纵向排列
1607
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AppView, { className: "gap-2 pb-4 px-4", children: buttons.map((button, index) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1608
- import_react_native7.TouchableOpacity,
1609
- {
1610
- onPress: handleButtonPress(button),
1611
- className: "py-3 rounded-lg",
1612
- style: getButtonStyle(button),
1613
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1614
- AppText,
1615
- {
1616
- weight: "medium",
1617
- className: "text-center",
1618
- style: { color: getButtonTextColor(button) },
1619
- children: button.text
1620
- }
1621
- )
1622
- },
1623
- index
1624
- )) })
1625
- ) })
1626
- ]
1627
- }
1628
- ) })
1947
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(AppView, { className: "flex-1", center: true, children: [
1948
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1949
+ import_react_native8.Animated.View,
1950
+ {
1951
+ style: [
1952
+ import_react_native8.StyleSheet.absoluteFillObject,
1953
+ {
1954
+ backgroundColor: "rgba(0,0,0,0.5)",
1955
+ opacity: progress
1956
+ }
1957
+ ]
1958
+ }
1959
+ ),
1960
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1961
+ import_react_native8.Animated.View,
1962
+ {
1963
+ className: "rounded-xl mx-8 min-w-[280px]",
1964
+ style: [
1965
+ {
1966
+ backgroundColor: modalBgColor,
1967
+ opacity: progress,
1968
+ transform: [
1969
+ {
1970
+ translateY: progress.interpolate({
1971
+ inputRange: [0, 1],
1972
+ outputRange: [16, 0]
1973
+ })
1974
+ },
1975
+ {
1976
+ scale: progress.interpolate({
1977
+ inputRange: [0, 1],
1978
+ outputRange: [0.96, 1]
1979
+ })
1980
+ }
1981
+ ]
1982
+ }
1983
+ ],
1984
+ children: [
1985
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(AppView, { className: "px-6 py-5", children: [
1986
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1987
+ AppText,
1988
+ {
1989
+ size: "lg",
1990
+ weight: "semibold",
1991
+ className: "text-center mb-2",
1992
+ style: { color: textColor },
1993
+ children: title
1994
+ }
1995
+ ),
1996
+ message && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(AppText, { size: "sm", style: { color: messageColor }, className: "text-center leading-5", children: message })
1997
+ ] }),
1998
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(AppView, { className: "border-t", style: { borderTopColor: borderColor }, children: buttons.length === 1 ? (
1999
+ // 单个按钮
2000
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2001
+ import_react_native8.TouchableOpacity,
2002
+ {
2003
+ onPress: handleButtonPress(buttons[0]),
2004
+ className: "py-3 rounded-b-xl",
2005
+ style: [styles2.singleButton, getButtonStyle(buttons[0])],
2006
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2007
+ AppText,
2008
+ {
2009
+ weight: "medium",
2010
+ className: "text-center",
2011
+ style: { color: getButtonTextColor(buttons[0]) },
2012
+ children: buttons[0].text
2013
+ }
2014
+ )
2015
+ }
2016
+ )
2017
+ ) : buttons.length === 2 ? (
2018
+ // 两个按钮横向排列
2019
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(AppView, { row: true, style: styles2.twoButtonContainer, children: buttons.map((button, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2020
+ import_react_native8.TouchableOpacity,
2021
+ {
2022
+ onPress: handleButtonPress(button),
2023
+ className: cn(
2024
+ "py-3 flex-1",
2025
+ index === 0 && "rounded-bl-xl",
2026
+ index === 1 && "rounded-br-xl"
2027
+ ),
2028
+ style: [
2029
+ styles2.twoButton,
2030
+ index > 0 && { borderLeftColor: borderColor },
2031
+ getButtonStyle(button)
2032
+ ],
2033
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2034
+ AppText,
2035
+ {
2036
+ weight: "medium",
2037
+ className: "text-center",
2038
+ style: { color: getButtonTextColor(button) },
2039
+ children: button.text
2040
+ }
2041
+ )
2042
+ },
2043
+ index
2044
+ )) })
2045
+ ) : (
2046
+ // 多个按钮纵向排列
2047
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(AppView, { className: "gap-2 pb-4 px-4", children: buttons.map((button, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2048
+ import_react_native8.TouchableOpacity,
2049
+ {
2050
+ onPress: handleButtonPress(button),
2051
+ className: "py-3 rounded-lg",
2052
+ style: getButtonStyle(button),
2053
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2054
+ AppText,
2055
+ {
2056
+ weight: "medium",
2057
+ className: "text-center",
2058
+ style: { color: getButtonTextColor(button) },
2059
+ children: button.text
2060
+ }
2061
+ )
2062
+ },
2063
+ index
2064
+ )) })
2065
+ ) })
2066
+ ]
2067
+ }
2068
+ )
2069
+ ] })
1629
2070
  }
1630
2071
  );
1631
2072
  }
1632
- var styles2 = import_react_native7.StyleSheet.create({
2073
+ var styles2 = import_react_native8.StyleSheet.create({
1633
2074
  singleButton: {
1634
2075
  borderBottomLeftRadius: 12,
1635
2076
  borderBottomRightRadius: 12
@@ -1644,19 +2085,8 @@ var styles2 = import_react_native7.StyleSheet.create({
1644
2085
  });
1645
2086
 
1646
2087
  // src/ui/feedback/Loading.tsx
1647
- var import_react_native8 = require("react-native");
1648
- var import_jsx_runtime13 = require("nativewind/jsx-runtime");
1649
- function Loading({ text, color, overlay = false, visible = true, testID }) {
1650
- if (!visible) return null;
1651
- const content = /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(AppView, { center: true, gap: 3, testID, children: [
1652
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native8.ActivityIndicator, { size: "large", color }),
1653
- text && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(AppText, { style: color ? { color } : void 0, children: text })
1654
- ] });
1655
- if (overlay) {
1656
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(AppView, { center: true, flex: true, className: "absolute inset-0 bg-black/30", testID, children: content });
1657
- }
1658
- return content;
1659
- }
2088
+ var import_react_native14 = require("react-native");
2089
+ var import_react18 = require("react");
1660
2090
 
1661
2091
  // src/ui/display/Progress.tsx
1662
2092
  var import_jsx_runtime14 = require("nativewind/jsx-runtime");
@@ -1850,7 +2280,7 @@ var FileIcons = {
1850
2280
  };
1851
2281
 
1852
2282
  // src/ui/display/AppImage.tsx
1853
- var import_react14 = require("react");
2283
+ var import_react15 = require("react");
1854
2284
  var import_react_native11 = require("react-native");
1855
2285
  var import_jsx_runtime17 = require("nativewind/jsx-runtime");
1856
2286
  var radiusMap = {
@@ -1886,15 +2316,15 @@ function AppImage({
1886
2316
  className,
1887
2317
  style
1888
2318
  }) {
1889
- const [isLoading, setIsLoading] = (0, import_react14.useState)(true);
1890
- const [hasError, setHasError] = (0, import_react14.useState)(false);
2319
+ const [isLoading, setIsLoading] = (0, import_react15.useState)(true);
2320
+ const [hasError, setHasError] = (0, import_react15.useState)(false);
1891
2321
  const { theme } = useTheme();
1892
2322
  const resolvedRadius = resolveRadius(borderRadius);
1893
- const handleLoad = (0, import_react14.useCallback)(() => {
2323
+ const handleLoad = (0, import_react15.useCallback)(() => {
1894
2324
  setIsLoading(false);
1895
2325
  onLoad?.();
1896
2326
  }, [onLoad]);
1897
- const handleError = (0, import_react14.useCallback)(
2327
+ const handleError = (0, import_react15.useCallback)(
1898
2328
  (error) => {
1899
2329
  setIsLoading(false);
1900
2330
  setHasError(true);
@@ -2008,9 +2438,18 @@ function AppImage({
2008
2438
  }
2009
2439
 
2010
2440
  // src/ui/display/AppList.tsx
2011
- var import_react15 = require("react");
2441
+ var import_react16 = __toESM(require("react"));
2012
2442
  var import_react_native12 = require("react-native");
2013
2443
  var import_jsx_runtime18 = require("nativewind/jsx-runtime");
2444
+ function renderListSlot(slot) {
2445
+ if (!slot) return null;
2446
+ if (import_react16.default.isValidElement(slot)) return slot;
2447
+ if (typeof slot === "function") {
2448
+ const SlotComponent = slot;
2449
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SlotComponent, {});
2450
+ }
2451
+ return null;
2452
+ }
2014
2453
  function SkeletonItem2({ render }) {
2015
2454
  const colors = useThemeColors();
2016
2455
  if (render) {
@@ -2110,8 +2549,8 @@ function AppList({
2110
2549
  showsHorizontalScrollIndicator
2111
2550
  }) {
2112
2551
  const { theme } = useTheme();
2113
- const [isLoadingMore, setIsLoadingMore] = (0, import_react15.useState)(false);
2114
- const handleEndReached = (0, import_react15.useCallback)(async () => {
2552
+ const [isLoadingMore, setIsLoadingMore] = (0, import_react16.useState)(false);
2553
+ const handleEndReached = (0, import_react16.useCallback)(async () => {
2115
2554
  if (isLoadingMore || !hasMore || !onEndReached) return;
2116
2555
  setIsLoadingMore(true);
2117
2556
  try {
@@ -2120,14 +2559,14 @@ function AppList({
2120
2559
  setIsLoadingMore(false);
2121
2560
  }
2122
2561
  }, [isLoadingMore, hasMore, onEndReached]);
2123
- const defaultKeyExtractor = (0, import_react15.useCallback)(
2562
+ const defaultKeyExtractor = (0, import_react16.useCallback)(
2124
2563
  (item, index) => {
2125
2564
  if (keyExtractor) return keyExtractor(item, index);
2126
2565
  return `item-${index}`;
2127
2566
  },
2128
2567
  [keyExtractor]
2129
2568
  );
2130
- const wrappedRenderItem = (0, import_react15.useCallback)(
2569
+ const wrappedRenderItem = (0, import_react16.useCallback)(
2131
2570
  (info) => {
2132
2571
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
2133
2572
  divider && info.index > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Divider, { style: dividerStyle }),
@@ -2136,14 +2575,18 @@ function AppList({
2136
2575
  },
2137
2576
  [renderItem, divider, dividerStyle]
2138
2577
  );
2139
- const skeletonData = (0, import_react15.useMemo)(
2578
+ const skeletonData = (0, import_react16.useMemo)(
2140
2579
  () => new Array(skeletonCount).fill(null).map((_, i) => ({ _skeletonId: i })),
2141
2580
  [skeletonCount]
2142
2581
  );
2143
- const skeletonRenderItem = (0, import_react15.useCallback)(
2582
+ const skeletonRenderItem = (0, import_react16.useCallback)(
2144
2583
  () => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SkeletonItem2, { render: skeletonRender }),
2145
2584
  [skeletonRender]
2146
2585
  );
2586
+ const flatListKey = (0, import_react16.useMemo)(() => {
2587
+ if (horizontal) return "app-list-horizontal";
2588
+ return `app-list-columns-${numColumns ?? 1}`;
2589
+ }, [horizontal, numColumns]);
2147
2590
  if (loading && data.length === 0) {
2148
2591
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2149
2592
  import_react_native12.FlatList,
@@ -2155,7 +2598,8 @@ function AppList({
2155
2598
  style,
2156
2599
  showsVerticalScrollIndicator,
2157
2600
  showsHorizontalScrollIndicator
2158
- }
2601
+ },
2602
+ `${flatListKey}-skeleton`
2159
2603
  );
2160
2604
  }
2161
2605
  if (error && data.length === 0) {
@@ -2170,16 +2614,20 @@ function AppList({
2170
2614
  }
2171
2615
  ) });
2172
2616
  }
2173
- const ListEmptyComponent = (0, import_react15.useMemo)(() => {
2174
- if (EmptyComponent) return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(EmptyComponent, {});
2617
+ const ListEmptyComponent = (0, import_react16.useMemo)(() => {
2618
+ if (EmptyComponent) return renderListSlot(EmptyComponent);
2175
2619
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(EmptyState, { title: emptyTitle, description: emptyDescription, icon: emptyIcon });
2176
2620
  }, [EmptyComponent, emptyTitle, emptyDescription, emptyIcon]);
2177
- const FooterComponent = (0, import_react15.useMemo)(() => {
2621
+ const FooterComponent = (0, import_react16.useMemo)(() => {
2178
2622
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
2179
2623
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(LoadMoreFooter, { loading: isLoadingMore }),
2180
- ListFooterComponent
2624
+ renderListSlot(ListFooterComponent)
2181
2625
  ] });
2182
2626
  }, [isLoadingMore, ListFooterComponent]);
2627
+ const HeaderComponent = (0, import_react16.useMemo)(
2628
+ () => renderListSlot(ListHeaderComponent),
2629
+ [ListHeaderComponent]
2630
+ );
2183
2631
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2184
2632
  import_react_native12.FlatList,
2185
2633
  {
@@ -2198,7 +2646,7 @@ function AppList({
2198
2646
  onEndReached: onEndReached ? handleEndReached : void 0,
2199
2647
  onEndReachedThreshold,
2200
2648
  ListEmptyComponent,
2201
- ListHeaderComponent,
2649
+ ListHeaderComponent: HeaderComponent,
2202
2650
  ListFooterComponent: FooterComponent,
2203
2651
  contentContainerStyle,
2204
2652
  style,
@@ -2211,7 +2659,8 @@ function AppList({
2211
2659
  maxToRenderPerBatch: 10,
2212
2660
  windowSize: 10,
2213
2661
  initialNumToRender: 10
2214
- }
2662
+ },
2663
+ flatListKey
2215
2664
  );
2216
2665
  }
2217
2666
  var styles3 = import_react_native12.StyleSheet.create({
@@ -2221,7 +2670,7 @@ var styles3 = import_react_native12.StyleSheet.create({
2221
2670
  });
2222
2671
 
2223
2672
  // src/ui/display/PageDrawer.tsx
2224
- var import_react16 = __toESM(require("react"));
2673
+ var import_react17 = __toESM(require("react"));
2225
2674
  var import_react_native13 = require("react-native");
2226
2675
  var import_jsx_runtime19 = require("nativewind/jsx-runtime");
2227
2676
  function PageDrawer({
@@ -2242,13 +2691,12 @@ function PageDrawer({
2242
2691
  backdropTestID = "page-drawer-backdrop"
2243
2692
  }) {
2244
2693
  const colors = useThemeColors();
2245
- const [translateX, setTranslateX] = import_react16.default.useState(0);
2246
- if (!visible) return null;
2247
- const handleClose = import_react16.default.useCallback(() => {
2694
+ const [translateX, setTranslateX] = import_react17.default.useState(0);
2695
+ const handleClose = import_react17.default.useCallback(() => {
2248
2696
  setTranslateX(0);
2249
2697
  onClose?.();
2250
2698
  }, [onClose]);
2251
- import_react16.default.useEffect(() => {
2699
+ import_react17.default.useEffect(() => {
2252
2700
  if (!visible) return;
2253
2701
  const subscription = import_react_native13.BackHandler.addEventListener("hardwareBackPress", () => {
2254
2702
  handleClose();
@@ -2256,7 +2704,7 @@ function PageDrawer({
2256
2704
  });
2257
2705
  return () => subscription.remove();
2258
2706
  }, [handleClose, visible]);
2259
- const panResponder = import_react16.default.useMemo(
2707
+ const panResponder = import_react17.default.useMemo(
2260
2708
  () => import_react_native13.PanResponder.create({
2261
2709
  onMoveShouldSetPanResponder: (_event, gestureState) => {
2262
2710
  if (!swipeEnabled) return false;
@@ -2287,6 +2735,7 @@ function PageDrawer({
2287
2735
  }),
2288
2736
  [handleClose, placement, swipeEnabled, swipeThreshold, width]
2289
2737
  );
2738
+ if (!visible) return null;
2290
2739
  const drawerContent = /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
2291
2740
  AppView,
2292
2741
  {
@@ -2383,23 +2832,66 @@ function GradientView({
2383
2832
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_expo_linear_gradient.LinearGradient, { colors: [...colors], start, end, style, ...props, children });
2384
2833
  }
2385
2834
 
2386
- // src/ui/form/AppInput.tsx
2387
- var import_react17 = require("react");
2388
- var import_react_native14 = require("react-native");
2835
+ // src/ui/feedback/Loading.tsx
2389
2836
  var import_jsx_runtime21 = require("nativewind/jsx-runtime");
2390
- var AppInput = (0, import_react17.forwardRef)(
2837
+ var LOADING_CLOSE_DELAY = 3e4;
2838
+ function Loading({
2839
+ text,
2840
+ color,
2841
+ overlay = false,
2842
+ visible = true,
2843
+ testID,
2844
+ onClose
2845
+ }) {
2846
+ const [showCloseButton, setShowCloseButton] = (0, import_react18.useState)(false);
2847
+ (0, import_react18.useEffect)(() => {
2848
+ if (!visible) {
2849
+ setShowCloseButton(false);
2850
+ return;
2851
+ }
2852
+ setShowCloseButton(false);
2853
+ const timer = setTimeout(() => {
2854
+ setShowCloseButton(true);
2855
+ }, LOADING_CLOSE_DELAY);
2856
+ return () => clearTimeout(timer);
2857
+ }, [visible]);
2858
+ if (!visible) return null;
2859
+ const content = /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(AppView, { center: true, gap: 3, testID, children: [
2860
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native14.ActivityIndicator, { size: "large", color }),
2861
+ text && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(AppText, { style: color ? { color } : void 0, children: text }),
2862
+ showCloseButton && onClose && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2863
+ AppPressable,
2864
+ {
2865
+ testID: testID ? `${testID}-close` : "loading-close",
2866
+ className: "mt-1 p-1",
2867
+ onPress: onClose,
2868
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Icon, { name: "close", size: "md", color: color || "white" })
2869
+ }
2870
+ )
2871
+ ] });
2872
+ if (overlay) {
2873
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(AppView, { center: true, flex: true, className: "absolute inset-0 bg-black/30", testID, children: content });
2874
+ }
2875
+ return content;
2876
+ }
2877
+
2878
+ // src/ui/form/AppInput.tsx
2879
+ var import_react19 = require("react");
2880
+ var import_react_native15 = require("react-native");
2881
+ var import_jsx_runtime22 = require("nativewind/jsx-runtime");
2882
+ var AppInput = (0, import_react19.forwardRef)(
2391
2883
  ({ label, error, disabled = false, leftIcon, rightIcon, className, style, ...props }, ref) => {
2392
2884
  const colors = useThemeColors();
2393
- const [isFocused, setIsFocused] = (0, import_react17.useState)(false);
2885
+ const [isFocused, setIsFocused] = (0, import_react19.useState)(false);
2394
2886
  const errorColor = "#ef4444";
2395
2887
  const getBorderColor = () => {
2396
2888
  if (error) return errorColor;
2397
2889
  if (isFocused) return colors.primary;
2398
2890
  return colors.border;
2399
2891
  };
2400
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(AppView, { className: cn("flex-col gap-1", className), children: [
2401
- label && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(AppText, { size: "sm", weight: "medium", style: { color: colors.textSecondary }, children: label }),
2402
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2892
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(AppView, { className: cn("flex-col gap-1", className), children: [
2893
+ label && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(AppText, { size: "sm", weight: "medium", style: { color: colors.textSecondary }, children: label }),
2894
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
2403
2895
  AppView,
2404
2896
  {
2405
2897
  row: true,
@@ -2414,9 +2906,9 @@ var AppInput = (0, import_react17.forwardRef)(
2414
2906
  }
2415
2907
  ],
2416
2908
  children: [
2417
- leftIcon && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native14.View, { style: styles5.icon, children: leftIcon }),
2418
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2419
- import_react_native14.TextInput,
2909
+ leftIcon && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native15.View, { style: styles5.icon, children: leftIcon }),
2910
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2911
+ import_react_native15.TextInput,
2420
2912
  {
2421
2913
  ref,
2422
2914
  className: "flex-1 py-3 text-base",
@@ -2434,18 +2926,18 @@ var AppInput = (0, import_react17.forwardRef)(
2434
2926
  ...props
2435
2927
  }
2436
2928
  ),
2437
- rightIcon && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native14.View, { style: styles5.icon, children: rightIcon })
2929
+ rightIcon && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native15.View, { style: styles5.icon, children: rightIcon })
2438
2930
  ]
2439
2931
  }
2440
2932
  ),
2441
- error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(AppText, { size: "xs", style: { color: errorColor }, children: error })
2933
+ error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(AppText, { size: "xs", style: { color: errorColor }, children: error })
2442
2934
  ] });
2443
2935
  }
2444
2936
  );
2445
2937
  AppInput.displayName = "AppInput";
2446
2938
  var AppTextInput = AppInput;
2447
2939
  AppTextInput.displayName = "AppTextInput";
2448
- var styles5 = import_react_native14.StyleSheet.create({
2940
+ var styles5 = import_react_native15.StyleSheet.create({
2449
2941
  inputContainer: {
2450
2942
  borderWidth: 0.5,
2451
2943
  minHeight: 48
@@ -2460,9 +2952,9 @@ var styles5 = import_react_native14.StyleSheet.create({
2460
2952
  });
2461
2953
 
2462
2954
  // src/ui/form/Checkbox.tsx
2463
- var import_react18 = require("react");
2464
- var import_react_native15 = require("react-native");
2465
- var import_jsx_runtime22 = require("nativewind/jsx-runtime");
2955
+ var import_react20 = require("react");
2956
+ var import_react_native16 = require("react-native");
2957
+ var import_jsx_runtime23 = require("nativewind/jsx-runtime");
2466
2958
  function Checkbox({
2467
2959
  checked,
2468
2960
  defaultChecked,
@@ -2473,7 +2965,7 @@ function Checkbox({
2473
2965
  testID
2474
2966
  }) {
2475
2967
  const colors = useThemeColors();
2476
- const [internalChecked, setInternalChecked] = (0, import_react18.useState)(defaultChecked || false);
2968
+ const [internalChecked, setInternalChecked] = (0, import_react20.useState)(defaultChecked || false);
2477
2969
  const isChecked = checked !== void 0 ? checked : internalChecked;
2478
2970
  const toggle = () => {
2479
2971
  if (disabled) return;
@@ -2484,8 +2976,8 @@ function Checkbox({
2484
2976
  onChange?.(newChecked);
2485
2977
  };
2486
2978
  const disabledOpacity = 0.4;
2487
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
2488
- import_react_native15.TouchableOpacity,
2979
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
2980
+ import_react_native16.TouchableOpacity,
2489
2981
  {
2490
2982
  onPress: toggle,
2491
2983
  disabled,
@@ -2494,7 +2986,7 @@ function Checkbox({
2494
2986
  testID,
2495
2987
  activeOpacity: 0.7,
2496
2988
  children: [
2497
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2989
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2498
2990
  AppView,
2499
2991
  {
2500
2992
  className: cn(
@@ -2508,17 +3000,27 @@ function Checkbox({
2508
3000
  borderColor: isChecked ? colors.primary : colors.border
2509
3001
  }
2510
3002
  ],
2511
- children: isChecked && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(AppView, { testID: `${testID}-icon`, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: "check", size: "sm", color: "white" }) })
3003
+ children: isChecked && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(AppView, { pointerEvents: "none", style: styles6.iconContainer, testID: `${testID}-icon`, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { name: "check", size: 14, color: "white", style: styles6.icon }) })
2512
3004
  }
2513
3005
  ),
2514
- children && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(AppText, { size: "sm", style: { color: colors.text }, children })
3006
+ children && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(AppText, { size: "sm", style: { color: colors.text }, children })
2515
3007
  ]
2516
3008
  }
2517
3009
  );
2518
3010
  }
2519
- var styles6 = import_react_native15.StyleSheet.create({
3011
+ var styles6 = import_react_native16.StyleSheet.create({
2520
3012
  checkbox: {
2521
3013
  borderWidth: 0.5
3014
+ },
3015
+ iconContainer: {
3016
+ ...import_react_native16.StyleSheet.absoluteFillObject,
3017
+ alignItems: "center",
3018
+ justifyContent: "center"
3019
+ },
3020
+ icon: {
3021
+ lineHeight: 14,
3022
+ includeFontPadding: false,
3023
+ textAlignVertical: "center"
2522
3024
  }
2523
3025
  });
2524
3026
 
@@ -2534,7 +3036,7 @@ var isGroupOptionDisabled = (groupDisabled, optionDisabled) => {
2534
3036
  };
2535
3037
 
2536
3038
  // src/ui/form/CheckboxGroup.tsx
2537
- var import_jsx_runtime23 = require("nativewind/jsx-runtime");
3039
+ var import_jsx_runtime24 = require("nativewind/jsx-runtime");
2538
3040
  function CheckboxGroup({
2539
3041
  value = [],
2540
3042
  onChange,
@@ -2547,7 +3049,7 @@ function CheckboxGroup({
2547
3049
  onChange(toggleGroupValue(value, optionValue, checked));
2548
3050
  };
2549
3051
  const isRow = direction === "row";
2550
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(AppView, { row: isRow, flex: isRow, gap: 4, children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
3052
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(AppView, { row: isRow, flex: isRow, gap: 4, children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2551
3053
  Checkbox,
2552
3054
  {
2553
3055
  checked: value.includes(option.value),
@@ -2560,9 +3062,9 @@ function CheckboxGroup({
2560
3062
  }
2561
3063
 
2562
3064
  // src/ui/form/Radio.tsx
2563
- var import_react19 = require("react");
2564
- var import_react_native16 = require("react-native");
2565
- var import_jsx_runtime24 = require("nativewind/jsx-runtime");
3065
+ var import_react21 = require("react");
3066
+ var import_react_native17 = require("react-native");
3067
+ var import_jsx_runtime25 = require("nativewind/jsx-runtime");
2566
3068
  function Radio({
2567
3069
  checked,
2568
3070
  defaultChecked,
@@ -2573,7 +3075,7 @@ function Radio({
2573
3075
  testID
2574
3076
  }) {
2575
3077
  const colors = useThemeColors();
2576
- const [internalChecked, setInternalChecked] = (0, import_react19.useState)(defaultChecked || false);
3078
+ const [internalChecked, setInternalChecked] = (0, import_react21.useState)(defaultChecked || false);
2577
3079
  const isChecked = checked !== void 0 ? checked : internalChecked;
2578
3080
  const toggle = () => {
2579
3081
  if (disabled) return;
@@ -2584,8 +3086,8 @@ function Radio({
2584
3086
  onChange?.(newChecked);
2585
3087
  };
2586
3088
  const disabledOpacity = 0.4;
2587
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2588
- import_react_native16.TouchableOpacity,
3089
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
3090
+ import_react_native17.TouchableOpacity,
2589
3091
  {
2590
3092
  onPress: toggle,
2591
3093
  disabled,
@@ -2594,7 +3096,7 @@ function Radio({
2594
3096
  testID,
2595
3097
  activeOpacity: 0.7,
2596
3098
  children: [
2597
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3099
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2598
3100
  AppView,
2599
3101
  {
2600
3102
  className: cn("w-5 h-5 rounded-full items-center justify-center", isChecked && "border-2"),
@@ -2606,7 +3108,7 @@ function Radio({
2606
3108
  borderWidth: isChecked ? 0.5 : 0.5
2607
3109
  }
2608
3110
  ],
2609
- children: isChecked && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3111
+ children: isChecked && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2610
3112
  AppView,
2611
3113
  {
2612
3114
  className: "rounded-full",
@@ -2615,12 +3117,12 @@ function Radio({
2615
3117
  )
2616
3118
  }
2617
3119
  ),
2618
- children && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(AppText, { size: "sm", style: { color: colors.text }, children })
3120
+ children && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(AppText, { size: "sm", style: { color: colors.text }, children })
2619
3121
  ]
2620
3122
  }
2621
3123
  );
2622
3124
  }
2623
- var styles7 = import_react_native16.StyleSheet.create({
3125
+ var styles7 = import_react_native17.StyleSheet.create({
2624
3126
  radio: {
2625
3127
  borderWidth: 0.5
2626
3128
  },
@@ -2631,7 +3133,7 @@ var styles7 = import_react_native16.StyleSheet.create({
2631
3133
  });
2632
3134
 
2633
3135
  // src/ui/form/RadioGroup.tsx
2634
- var import_jsx_runtime25 = require("nativewind/jsx-runtime");
3136
+ var import_jsx_runtime26 = require("nativewind/jsx-runtime");
2635
3137
  function RadioGroup({
2636
3138
  value,
2637
3139
  onChange,
@@ -2640,7 +3142,7 @@ function RadioGroup({
2640
3142
  disabled = false
2641
3143
  }) {
2642
3144
  const isRow = direction === "row";
2643
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(AppView, { row: isRow, flex: isRow, gap: 4, children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
3145
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(AppView, { row: isRow, flex: isRow, gap: 4, children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2644
3146
  Radio,
2645
3147
  {
2646
3148
  checked: value === option.value,
@@ -2653,9 +3155,17 @@ function RadioGroup({
2653
3155
  }
2654
3156
 
2655
3157
  // src/ui/form/Switch.tsx
2656
- var import_react20 = require("react");
2657
- var import_react_native17 = require("react-native");
2658
- var import_jsx_runtime26 = require("nativewind/jsx-runtime");
3158
+ var import_react22 = require("react");
3159
+ var import_react_native18 = require("react-native");
3160
+ var import_jsx_runtime27 = require("nativewind/jsx-runtime");
3161
+ function createAnimatedValue2(value) {
3162
+ const AnimatedValue = import_react_native18.Animated.Value;
3163
+ try {
3164
+ return new AnimatedValue(value);
3165
+ } catch {
3166
+ return AnimatedValue(value);
3167
+ }
3168
+ }
2659
3169
  function Switch({
2660
3170
  checked,
2661
3171
  defaultChecked,
@@ -2667,35 +3177,79 @@ function Switch({
2667
3177
  style
2668
3178
  }) {
2669
3179
  const colors = useThemeColors();
2670
- const [internalChecked, setInternalChecked] = (0, import_react20.useState)(defaultChecked || false);
3180
+ const [internalChecked, setInternalChecked] = (0, import_react22.useState)(defaultChecked || false);
3181
+ const [isInteractionLocked, setIsInteractionLocked] = (0, import_react22.useState)(false);
3182
+ const isFirstRender = (0, import_react22.useRef)(true);
3183
+ const unlockTimerRef = (0, import_react22.useRef)(null);
2671
3184
  const isChecked = checked !== void 0 ? checked : internalChecked;
3185
+ const sizes = {
3186
+ sm: { width: 36, height: 20, thumb: 16, padding: 2 },
3187
+ md: { width: 48, height: 26, thumb: 22, padding: 2 },
3188
+ lg: { width: 60, height: 32, thumb: 28, padding: 2 }
3189
+ };
3190
+ const config = sizes[size];
3191
+ const maxTranslateX = config.width - config.thumb - config.padding * 2;
3192
+ const thumbTranslateX = (0, import_react22.useRef)(createAnimatedValue2(isChecked ? maxTranslateX : 0)).current;
3193
+ const clearUnlockTimer = () => {
3194
+ if (!unlockTimerRef.current) return;
3195
+ clearTimeout(unlockTimerRef.current);
3196
+ unlockTimerRef.current = null;
3197
+ };
3198
+ const animateThumb = (nextChecked, shouldUnlock = true) => {
3199
+ import_react_native18.Animated.timing(thumbTranslateX, {
3200
+ toValue: nextChecked ? maxTranslateX : 0,
3201
+ duration: 180,
3202
+ useNativeDriver: true
3203
+ }).start((result) => {
3204
+ if (result?.finished ?? true) {
3205
+ thumbTranslateX.setValue(nextChecked ? maxTranslateX : 0);
3206
+ }
3207
+ if (shouldUnlock) {
3208
+ clearUnlockTimer();
3209
+ setIsInteractionLocked(false);
3210
+ }
3211
+ });
3212
+ };
3213
+ (0, import_react22.useEffect)(() => {
3214
+ if (isFirstRender.current) {
3215
+ thumbTranslateX.setValue(isChecked ? maxTranslateX : 0);
3216
+ isFirstRender.current = false;
3217
+ return;
3218
+ }
3219
+ animateThumb(isChecked);
3220
+ }, [isChecked, maxTranslateX, thumbTranslateX]);
3221
+ (0, import_react22.useEffect)(() => {
3222
+ return () => {
3223
+ clearUnlockTimer();
3224
+ };
3225
+ }, []);
2672
3226
  const toggle = () => {
2673
- if (disabled) return;
3227
+ if (disabled || isInteractionLocked) return;
2674
3228
  const newChecked = !isChecked;
3229
+ setIsInteractionLocked(true);
2675
3230
  if (checked === void 0) {
2676
3231
  setInternalChecked(newChecked);
3232
+ } else {
3233
+ animateThumb(newChecked, false);
3234
+ unlockTimerRef.current = setTimeout(() => {
3235
+ unlockTimerRef.current = null;
3236
+ setIsInteractionLocked(false);
3237
+ }, 220);
2677
3238
  }
2678
3239
  onChange?.(newChecked);
2679
3240
  };
2680
- const uncheckedTrackColor = colors.divider;
2681
- const checkedTrackColor = colors.primary;
2682
- const disabledOpacity = 0.4;
2683
- const sizes = {
2684
- sm: { width: 36, height: 20, thumb: 16, padding: 2 },
2685
- md: { width: 48, height: 26, thumb: 22, padding: 2 },
2686
- lg: { width: 60, height: 32, thumb: 28, padding: 2 }
2687
- };
2688
- const config = sizes[size];
2689
- const thumbPosition = isChecked ? config.width - config.thumb - config.padding : config.padding;
2690
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2691
- import_react_native17.TouchableOpacity,
3241
+ const trackBackgroundColor = disabled ? isChecked ? colors.primarySurface : colors.divider : isChecked ? colors.primary : colors.divider;
3242
+ const trackBorderColor = disabled ? isChecked ? colors.primarySurface : colors.border : isChecked ? colors.primary : colors.border;
3243
+ const thumbBackgroundColor = disabled ? colors.card : colors.textInverse;
3244
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3245
+ import_react_native18.TouchableOpacity,
2692
3246
  {
2693
3247
  onPress: toggle,
2694
- disabled,
3248
+ disabled: disabled || isInteractionLocked,
2695
3249
  className: cn(className),
2696
3250
  testID,
2697
- activeOpacity: disabled ? 1 : 0.8,
2698
- children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
3251
+ activeOpacity: disabled || isInteractionLocked ? 1 : 0.8,
3252
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2699
3253
  AppView,
2700
3254
  {
2701
3255
  className: "rounded-full",
@@ -2704,22 +3258,22 @@ function Switch({
2704
3258
  {
2705
3259
  width: config.width,
2706
3260
  height: config.height,
2707
- backgroundColor: isChecked ? checkedTrackColor : uncheckedTrackColor,
2708
- opacity: disabled ? disabledOpacity : 1
3261
+ backgroundColor: trackBackgroundColor,
3262
+ borderColor: trackBorderColor
2709
3263
  },
2710
3264
  style
2711
3265
  ],
2712
- children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2713
- AppView,
3266
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3267
+ import_react_native18.Animated.View,
2714
3268
  {
2715
- className: "rounded-full",
2716
3269
  style: [
2717
3270
  styles8.thumb,
2718
3271
  {
2719
3272
  width: config.thumb,
2720
3273
  height: config.thumb,
2721
- backgroundColor: colors.textInverse,
2722
- transform: [{ translateX: thumbPosition }],
3274
+ borderRadius: config.thumb / 2,
3275
+ backgroundColor: thumbBackgroundColor,
3276
+ transform: [{ translateX: thumbTranslateX }],
2723
3277
  shadowColor: "#000000",
2724
3278
  shadowOffset: { width: 0, height: 1 },
2725
3279
  shadowOpacity: 0.25,
@@ -2733,10 +3287,11 @@ function Switch({
2733
3287
  }
2734
3288
  );
2735
3289
  }
2736
- var styles8 = import_react_native17.StyleSheet.create({
3290
+ var styles8 = import_react_native18.StyleSheet.create({
2737
3291
  track: {
2738
3292
  justifyContent: "center",
2739
- padding: 2
3293
+ padding: 2,
3294
+ borderWidth: 0.5
2740
3295
  },
2741
3296
  thumb: {
2742
3297
  elevation: 2,
@@ -2748,15 +3303,15 @@ var styles8 = import_react_native17.StyleSheet.create({
2748
3303
  });
2749
3304
 
2750
3305
  // src/ui/form/Slider.tsx
2751
- var import_react22 = require("react");
2752
- var import_react_native18 = require("react-native");
3306
+ var import_react24 = require("react");
3307
+ var import_react_native19 = require("react-native");
2753
3308
 
2754
3309
  // src/ui/form/useFormTheme.ts
2755
- var import_react21 = require("react");
3310
+ var import_react23 = require("react");
2756
3311
  function useFormThemeColors() {
2757
3312
  const { isDark } = useOptionalTheme();
2758
3313
  const colors = useThemeColors();
2759
- return (0, import_react21.useMemo)(
3314
+ return (0, import_react23.useMemo)(
2760
3315
  () => ({
2761
3316
  primary: colors.primary,
2762
3317
  primarySurface: colors.primarySurface,
@@ -2777,7 +3332,7 @@ function useFormThemeColors() {
2777
3332
  }
2778
3333
 
2779
3334
  // src/ui/form/Slider.tsx
2780
- var import_jsx_runtime27 = require("nativewind/jsx-runtime");
3335
+ var import_jsx_runtime28 = require("nativewind/jsx-runtime");
2781
3336
  function Slider({
2782
3337
  value,
2783
3338
  defaultValue = 0,
@@ -2791,53 +3346,82 @@ function Slider({
2791
3346
  className
2792
3347
  }) {
2793
3348
  const colors = useFormThemeColors();
2794
- const [internalValue, setInternalValue] = (0, import_react22.useState)(defaultValue);
2795
- const [trackWidth, setTrackWidth] = (0, import_react22.useState)(0);
2796
- const [isDragging, setIsDragging] = (0, import_react22.useState)(false);
3349
+ const [internalValue, setInternalValue] = (0, import_react24.useState)(defaultValue);
3350
+ const [isDragging, setIsDragging] = (0, import_react24.useState)(false);
3351
+ const trackWidthRef = (0, import_react24.useRef)(0);
3352
+ const currentValueRef = (0, import_react24.useRef)(value ?? defaultValue);
3353
+ const dragStartValueRef = (0, import_react24.useRef)(value ?? defaultValue);
2797
3354
  const currentValue = value !== void 0 ? value : internalValue;
3355
+ const range = max - min;
2798
3356
  const disabledOpacity = 0.4;
2799
- const progress = (currentValue - min) / (max - min) * 100;
2800
- const getValueFromPosition = (0, import_react22.useCallback)(
3357
+ const progress = range <= 0 ? 0 : (currentValue - min) / range * 100;
3358
+ (0, import_react24.useEffect)(() => {
3359
+ currentValueRef.current = currentValue;
3360
+ }, [currentValue]);
3361
+ const clampValue = (0, import_react24.useCallback)(
3362
+ (nextValue) => {
3363
+ if (!Number.isFinite(nextValue)) return currentValueRef.current;
3364
+ return Math.min(max, Math.max(min, nextValue));
3365
+ },
3366
+ [max, min]
3367
+ );
3368
+ const valueToPosition = (0, import_react24.useCallback)(
3369
+ (nextValue) => {
3370
+ if (trackWidthRef.current <= 0 || range <= 0) return 0;
3371
+ return (clampValue(nextValue) - min) / range * trackWidthRef.current;
3372
+ },
3373
+ [clampValue, min, range]
3374
+ );
3375
+ const getValueFromPosition = (0, import_react24.useCallback)(
2801
3376
  (position) => {
2802
- const percentage = Math.max(0, Math.min(1, position / trackWidth));
2803
- const rawValue = min + percentage * (max - min);
2804
- const steppedValue = Math.round(rawValue / step) * step;
2805
- return Math.min(max, Math.max(min, steppedValue));
3377
+ if (trackWidthRef.current <= 0 || range <= 0 || !Number.isFinite(position)) {
3378
+ return clampValue(currentValueRef.current);
3379
+ }
3380
+ const percentage = Math.max(0, Math.min(1, position / trackWidthRef.current));
3381
+ const rawValue = min + percentage * range;
3382
+ const steppedValue = step > 0 ? Math.round((rawValue - min) / step) * step + min : rawValue;
3383
+ return clampValue(steppedValue);
2806
3384
  },
2807
- [trackWidth, min, max, step]
3385
+ [clampValue, min, range, step]
2808
3386
  );
2809
- const setValue = (0, import_react22.useCallback)(
3387
+ const setValue = (0, import_react24.useCallback)(
2810
3388
  (newValue) => {
2811
- const clampedValue = Math.min(max, Math.max(min, newValue));
3389
+ const clampedValue = clampValue(newValue);
2812
3390
  if (value === void 0) {
2813
3391
  setInternalValue(clampedValue);
2814
3392
  }
3393
+ currentValueRef.current = clampedValue;
2815
3394
  onChange?.(clampedValue);
2816
3395
  },
2817
- [value, min, max, onChange]
3396
+ [clampValue, onChange, value]
2818
3397
  );
2819
- const panResponder = (0, import_react22.useRef)(
2820
- import_react_native18.PanResponder.create({
3398
+ const panResponder = (0, import_react24.useMemo)(
3399
+ () => import_react_native19.PanResponder.create({
2821
3400
  onStartShouldSetPanResponder: () => !disabled,
2822
3401
  onMoveShouldSetPanResponder: () => !disabled,
2823
3402
  onPanResponderGrant: () => {
3403
+ dragStartValueRef.current = currentValueRef.current;
2824
3404
  setIsDragging(true);
2825
3405
  },
2826
3406
  onPanResponderMove: (_, gestureState) => {
2827
- const position = progress / 100 * trackWidth + gestureState.dx;
3407
+ const position = valueToPosition(dragStartValueRef.current) + gestureState.dx;
2828
3408
  const newValue = getValueFromPosition(position);
2829
3409
  setValue(newValue);
2830
3410
  },
2831
3411
  onPanResponderRelease: (_, gestureState) => {
2832
- const position = progress / 100 * trackWidth + gestureState.dx;
3412
+ const position = valueToPosition(dragStartValueRef.current) + gestureState.dx;
2833
3413
  const newValue = getValueFromPosition(position);
2834
3414
  setValue(newValue);
2835
3415
  setIsDragging(false);
2836
3416
  onChangeEnd?.(newValue);
3417
+ },
3418
+ onPanResponderTerminate: () => {
3419
+ setIsDragging(false);
2837
3420
  }
2838
- })
2839
- ).current;
2840
- const handleTrackPress = (0, import_react22.useCallback)(
3421
+ }),
3422
+ [disabled, getValueFromPosition, onChangeEnd, setValue, valueToPosition]
3423
+ );
3424
+ const handleTrackPress = (0, import_react24.useCallback)(
2841
3425
  (event) => {
2842
3426
  if (disabled) return;
2843
3427
  const { locationX } = event.nativeEvent;
@@ -2847,11 +3431,12 @@ function Slider({
2847
3431
  },
2848
3432
  [disabled, getValueFromPosition, setValue, onChangeEnd]
2849
3433
  );
2850
- const onLayout = (0, import_react22.useCallback)((event) => {
2851
- setTrackWidth(event.nativeEvent.layout.width);
3434
+ const onLayout = (0, import_react24.useCallback)((event) => {
3435
+ const width = event.nativeEvent.layout.width;
3436
+ trackWidthRef.current = width;
2852
3437
  }, []);
2853
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(AppView, { className: cn("py-2", className), children: [
2854
- showTooltip && isDragging && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
3438
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(AppView, { className: cn("py-2", className), children: [
3439
+ showTooltip && isDragging && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
2855
3440
  AppView,
2856
3441
  {
2857
3442
  className: "absolute rounded px-2 py-1 -top-8",
@@ -2864,8 +3449,8 @@ function Slider({
2864
3449
  }
2865
3450
  ],
2866
3451
  children: [
2867
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(AppText, { size: "xs", style: { color: colors.text }, children: Math.round(currentValue) }),
2868
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3452
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(AppText, { size: "xs", style: { color: colors.text }, children: Math.round(currentValue) }),
3453
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2869
3454
  AppView,
2870
3455
  {
2871
3456
  style: [
@@ -2879,8 +3464,8 @@ function Slider({
2879
3464
  ]
2880
3465
  }
2881
3466
  ),
2882
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
2883
- import_react_native18.View,
3467
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
3468
+ import_react_native19.View,
2884
3469
  {
2885
3470
  onLayout,
2886
3471
  className: "rounded-full",
@@ -2890,7 +3475,7 @@ function Slider({
2890
3475
  ],
2891
3476
  onTouchEnd: handleTrackPress,
2892
3477
  children: [
2893
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3478
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2894
3479
  AppView,
2895
3480
  {
2896
3481
  className: "rounded-full",
@@ -2903,7 +3488,7 @@ function Slider({
2903
3488
  ]
2904
3489
  }
2905
3490
  ),
2906
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3491
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2907
3492
  AppView,
2908
3493
  {
2909
3494
  className: "absolute rounded-full items-center justify-center",
@@ -2920,7 +3505,7 @@ function Slider({
2920
3505
  }
2921
3506
  ],
2922
3507
  ...panResponder.panHandlers,
2923
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3508
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2924
3509
  AppView,
2925
3510
  {
2926
3511
  className: "rounded-full",
@@ -2939,7 +3524,7 @@ function Slider({
2939
3524
  )
2940
3525
  ] });
2941
3526
  }
2942
- var styles9 = import_react_native18.StyleSheet.create({
3527
+ var styles9 = import_react_native19.StyleSheet.create({
2943
3528
  track: {
2944
3529
  height: 6,
2945
3530
  width: "100%"
@@ -2980,9 +3565,204 @@ var styles9 = import_react_native18.StyleSheet.create({
2980
3565
  });
2981
3566
 
2982
3567
  // src/ui/form/Select.tsx
2983
- var import_react23 = require("react");
2984
- var import_react_native19 = require("react-native");
2985
- var import_jsx_runtime28 = require("nativewind/jsx-runtime");
3568
+ var import_react26 = require("react");
3569
+ var import_react_native21 = require("react-native");
3570
+
3571
+ // src/ui/form/BottomSheetModal.tsx
3572
+ var import_react25 = require("react");
3573
+ var import_react_native20 = require("react-native");
3574
+ var import_jsx_runtime29 = require("nativewind/jsx-runtime");
3575
+ var SHEET_OPEN_DURATION = 220;
3576
+ var SHEET_CLOSE_DURATION = 180;
3577
+ var OVERLAY_OPEN_DURATION = 180;
3578
+ var OVERLAY_CLOSE_DURATION = 160;
3579
+ var SHEET_INITIAL_OFFSET = 24;
3580
+ var SHEET_CLOSED_OFFSET = 240;
3581
+ var SHEET_DRAG_CLOSE_THRESHOLD = 72;
3582
+ var SHEET_DRAG_VELOCITY_THRESHOLD = 1;
3583
+ function createAnimatedValue3(value) {
3584
+ const AnimatedValue = import_react_native20.Animated.Value;
3585
+ try {
3586
+ return new AnimatedValue(value);
3587
+ } catch {
3588
+ return AnimatedValue(value);
3589
+ }
3590
+ }
3591
+ function startAnimations(animations, onComplete) {
3592
+ if (animations.length === 0) {
3593
+ onComplete?.();
3594
+ return;
3595
+ }
3596
+ let completed = 0;
3597
+ animations.forEach((animation) => {
3598
+ animation.start(() => {
3599
+ completed += 1;
3600
+ if (completed >= animations.length) {
3601
+ onComplete?.();
3602
+ }
3603
+ });
3604
+ });
3605
+ }
3606
+ function BottomSheetModal({
3607
+ visible,
3608
+ onRequestClose,
3609
+ overlayColor,
3610
+ surfaceColor,
3611
+ children,
3612
+ closeOnBackdropPress = false,
3613
+ maxHeight = "70%",
3614
+ showHandle = true,
3615
+ contentClassName,
3616
+ contentStyle,
3617
+ swipeToClose = true,
3618
+ backdropTestID = "bottom-sheet-backdrop",
3619
+ handleTestID = "bottom-sheet-handle"
3620
+ }) {
3621
+ const [renderVisible, setRenderVisible] = (0, import_react25.useState)(visible);
3622
+ const overlayOpacity = (0, import_react25.useRef)(createAnimatedValue3(0)).current;
3623
+ const sheetTranslateY = (0, import_react25.useRef)(createAnimatedValue3(SHEET_INITIAL_OFFSET)).current;
3624
+ const isDraggingRef = (0, import_react25.useRef)(false);
3625
+ const handlePanResponder = (0, import_react25.useMemo)(
3626
+ () => import_react_native20.PanResponder.create({
3627
+ onMoveShouldSetPanResponder: (_event, gestureState) => {
3628
+ if (!visible || !swipeToClose) return false;
3629
+ const isVertical = Math.abs(gestureState.dy) > Math.abs(gestureState.dx);
3630
+ return isVertical && gestureState.dy > 6;
3631
+ },
3632
+ onPanResponderGrant: () => {
3633
+ isDraggingRef.current = true;
3634
+ },
3635
+ onPanResponderMove: (_event, gestureState) => {
3636
+ if (!visible || !swipeToClose) return;
3637
+ sheetTranslateY.setValue(Math.max(0, Math.min(SHEET_CLOSED_OFFSET, gestureState.dy)));
3638
+ },
3639
+ onPanResponderRelease: (_event, gestureState) => {
3640
+ isDraggingRef.current = false;
3641
+ if (!visible || !swipeToClose) {
3642
+ sheetTranslateY.setValue(0);
3643
+ return;
3644
+ }
3645
+ const shouldClose = gestureState.dy >= SHEET_DRAG_CLOSE_THRESHOLD || gestureState.vy >= SHEET_DRAG_VELOCITY_THRESHOLD;
3646
+ if (shouldClose) {
3647
+ onRequestClose();
3648
+ return;
3649
+ }
3650
+ import_react_native20.Animated.timing(sheetTranslateY, {
3651
+ toValue: 0,
3652
+ duration: SHEET_OPEN_DURATION,
3653
+ useNativeDriver: true
3654
+ }).start();
3655
+ },
3656
+ onPanResponderTerminate: () => {
3657
+ isDraggingRef.current = false;
3658
+ import_react_native20.Animated.timing(sheetTranslateY, {
3659
+ toValue: 0,
3660
+ duration: SHEET_OPEN_DURATION,
3661
+ useNativeDriver: true
3662
+ }).start();
3663
+ }
3664
+ }),
3665
+ [onRequestClose, sheetTranslateY, swipeToClose, visible]
3666
+ );
3667
+ (0, import_react25.useEffect)(() => {
3668
+ if (visible) {
3669
+ setRenderVisible(true);
3670
+ overlayOpacity.setValue(0);
3671
+ sheetTranslateY.setValue(SHEET_INITIAL_OFFSET);
3672
+ startAnimations([
3673
+ import_react_native20.Animated.timing(overlayOpacity, {
3674
+ toValue: 1,
3675
+ duration: OVERLAY_OPEN_DURATION,
3676
+ useNativeDriver: true
3677
+ }),
3678
+ import_react_native20.Animated.timing(sheetTranslateY, {
3679
+ toValue: 0,
3680
+ duration: SHEET_OPEN_DURATION,
3681
+ useNativeDriver: true
3682
+ })
3683
+ ]);
3684
+ return;
3685
+ }
3686
+ if (!renderVisible) return;
3687
+ isDraggingRef.current = false;
3688
+ startAnimations(
3689
+ [
3690
+ import_react_native20.Animated.timing(overlayOpacity, {
3691
+ toValue: 0,
3692
+ duration: OVERLAY_CLOSE_DURATION,
3693
+ useNativeDriver: true
3694
+ }),
3695
+ import_react_native20.Animated.timing(sheetTranslateY, {
3696
+ toValue: SHEET_CLOSED_OFFSET,
3697
+ duration: SHEET_CLOSE_DURATION,
3698
+ useNativeDriver: true
3699
+ })
3700
+ ],
3701
+ () => {
3702
+ setRenderVisible(false);
3703
+ }
3704
+ );
3705
+ }, [overlayOpacity, renderVisible, sheetTranslateY, visible]);
3706
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_react_native20.Modal, { visible: renderVisible, transparent: true, animationType: "none", onRequestClose, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(AppView, { flex: true, justify: "end", children: [
3707
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3708
+ import_react_native20.Animated.View,
3709
+ {
3710
+ pointerEvents: "none",
3711
+ style: [import_react_native20.StyleSheet.absoluteFillObject, { backgroundColor: overlayColor, opacity: overlayOpacity }]
3712
+ }
3713
+ ),
3714
+ closeOnBackdropPress && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(AppPressable, { testID: backdropTestID, className: "flex-1", onPress: onRequestClose }),
3715
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
3716
+ import_react_native20.Animated.View,
3717
+ {
3718
+ className: contentClassName,
3719
+ style: [
3720
+ styles10.sheet,
3721
+ {
3722
+ backgroundColor: surfaceColor,
3723
+ maxHeight,
3724
+ transform: [{ translateY: sheetTranslateY }]
3725
+ },
3726
+ contentStyle
3727
+ ],
3728
+ children: [
3729
+ showHandle && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3730
+ AppView,
3731
+ {
3732
+ testID: handleTestID,
3733
+ center: true,
3734
+ className: "pt-2 pb-1",
3735
+ ...swipeToClose ? handlePanResponder.panHandlers : void 0,
3736
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(AppView, { style: styles10.handle })
3737
+ }
3738
+ ),
3739
+ children
3740
+ ]
3741
+ }
3742
+ )
3743
+ ] }) });
3744
+ }
3745
+ var styles10 = import_react_native20.StyleSheet.create({
3746
+ handle: {
3747
+ width: 36,
3748
+ height: 4,
3749
+ borderRadius: 999,
3750
+ backgroundColor: "rgba(156,163,175,0.7)"
3751
+ },
3752
+ sheet: {
3753
+ borderTopLeftRadius: 24,
3754
+ borderTopRightRadius: 24,
3755
+ overflow: "hidden",
3756
+ shadowColor: "#000000",
3757
+ shadowOffset: { width: 0, height: -4 },
3758
+ shadowOpacity: 0.12,
3759
+ shadowRadius: 16,
3760
+ elevation: 12
3761
+ }
3762
+ });
3763
+
3764
+ // src/ui/form/Select.tsx
3765
+ var import_jsx_runtime30 = require("nativewind/jsx-runtime");
2986
3766
  function formatSelectedCountText(template, count) {
2987
3767
  return template.replace("{{count}}", String(count));
2988
3768
  }
@@ -3005,24 +3785,24 @@ function Select({
3005
3785
  className
3006
3786
  }) {
3007
3787
  const colors = useFormThemeColors();
3008
- const [visible, setVisible] = (0, import_react23.useState)(false);
3009
- const [searchKeyword, setSearchKeyword] = (0, import_react23.useState)("");
3010
- const selectedValues = (0, import_react23.useMemo)(() => {
3788
+ const [visible, setVisible] = (0, import_react26.useState)(false);
3789
+ const [searchKeyword, setSearchKeyword] = (0, import_react26.useState)("");
3790
+ const selectedValues = (0, import_react26.useMemo)(() => {
3011
3791
  if (multiple) {
3012
3792
  return Array.isArray(value) ? value : [];
3013
3793
  }
3014
3794
  return value ? [value] : [];
3015
3795
  }, [value, multiple]);
3016
- const displayText = (0, import_react23.useMemo)(() => {
3796
+ const displayText = (0, import_react26.useMemo)(() => {
3017
3797
  if (selectedValues.length === 0) return placeholder;
3018
3798
  const selectedLabels = options.filter((opt) => selectedValues.includes(opt.value)).map((opt) => opt.label);
3019
3799
  return selectedLabels.join(", ") || placeholder;
3020
3800
  }, [selectedValues, options, placeholder]);
3021
- const filteredOptions = (0, import_react23.useMemo)(() => {
3801
+ const filteredOptions = (0, import_react26.useMemo)(() => {
3022
3802
  if (!searchable || !searchKeyword) return options;
3023
3803
  return options.filter((opt) => opt.label.toLowerCase().includes(searchKeyword.toLowerCase()));
3024
3804
  }, [options, searchable, searchKeyword]);
3025
- const handleSelect = (0, import_react23.useCallback)(
3805
+ const handleSelect = (0, import_react26.useCallback)(
3026
3806
  (optionValue) => {
3027
3807
  if (multiple) {
3028
3808
  const currentValues = Array.isArray(value) ? value : [];
@@ -3033,26 +3813,26 @@ function Select({
3033
3813
  setVisible(false);
3034
3814
  }
3035
3815
  },
3036
- [multiple, value, onChange]
3816
+ [multiple, onChange, value]
3037
3817
  );
3038
- const handleClear = (0, import_react23.useCallback)(
3818
+ const handleClear = (0, import_react26.useCallback)(
3039
3819
  (e) => {
3040
3820
  e.stopPropagation();
3041
3821
  onChange?.(multiple ? [] : "");
3042
3822
  },
3043
3823
  [multiple, onChange]
3044
3824
  );
3045
- const handleSearch = (0, import_react23.useCallback)(
3825
+ const handleSearch = (0, import_react26.useCallback)(
3046
3826
  (text) => {
3047
3827
  setSearchKeyword(text);
3048
3828
  onSearch?.(text);
3049
3829
  },
3050
3830
  [onSearch]
3051
3831
  );
3052
- const renderOption = (0, import_react23.useCallback)(
3832
+ const renderOption = (0, import_react26.useCallback)(
3053
3833
  ({ item }) => {
3054
3834
  const isSelected = selectedValues.includes(item.value);
3055
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
3835
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
3056
3836
  AppPressable,
3057
3837
  {
3058
3838
  className: cn(
@@ -3060,22 +3840,22 @@ function Select({
3060
3840
  isSelected && "bg-primary-50"
3061
3841
  ),
3062
3842
  style: [
3063
- styles10.optionItem,
3843
+ styles11.optionItem,
3064
3844
  { borderBottomColor: colors.divider },
3065
3845
  isSelected && { backgroundColor: colors.primarySurface }
3066
3846
  ],
3067
3847
  onPress: () => handleSelect(item.value),
3068
3848
  children: [
3069
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(AppText, { style: { color: isSelected ? colors.primary : colors.text }, children: item.label }),
3070
- isSelected && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Icon, { name: "check", size: "sm", color: "primary-500" })
3849
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(AppText, { style: { color: isSelected ? colors.primary : colors.text }, children: item.label }),
3850
+ isSelected && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Icon, { name: "check", size: "sm", color: "primary-500" })
3071
3851
  ]
3072
3852
  }
3073
3853
  );
3074
3854
  },
3075
3855
  [selectedValues, handleSelect, colors]
3076
3856
  );
3077
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
3078
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
3857
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
3858
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
3079
3859
  AppPressable,
3080
3860
  {
3081
3861
  className: cn(
@@ -3083,11 +3863,11 @@ function Select({
3083
3863
  disabled ? "opacity-60" : "",
3084
3864
  className
3085
3865
  ),
3086
- style: [styles10.trigger, { backgroundColor: colors.surface, borderColor: colors.border }],
3866
+ style: [styles11.trigger, { backgroundColor: colors.surface, borderColor: colors.border }],
3087
3867
  disabled,
3088
3868
  onPress: () => setVisible(true),
3089
3869
  children: [
3090
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3870
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3091
3871
  AppText,
3092
3872
  {
3093
3873
  className: "flex-1",
@@ -3096,111 +3876,106 @@ function Select({
3096
3876
  children: displayText
3097
3877
  }
3098
3878
  ),
3099
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_react_native19.View, { className: "flex-row items-center", children: [
3100
- clearable && selectedValues.length > 0 && !disabled && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react_native19.TouchableOpacity, { onPress: handleClear, className: "mr-2 p-1", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Icon, { name: "close", size: "sm", color: colors.icon }) }),
3101
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Icon, { name: "keyboard-arrow-down", size: "md", color: colors.icon })
3879
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_react_native21.View, { className: "flex-row items-center", children: [
3880
+ clearable && selectedValues.length > 0 && !disabled && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_native21.TouchableOpacity, { onPress: handleClear, className: "mr-2 p-1", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Icon, { name: "close", size: "sm", color: colors.icon }) }),
3881
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Icon, { name: "keyboard-arrow-down", size: "md", color: colors.icon })
3102
3882
  ] })
3103
3883
  ]
3104
3884
  }
3105
3885
  ),
3106
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3107
- import_react_native19.Modal,
3886
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3887
+ BottomSheetModal,
3108
3888
  {
3109
3889
  visible,
3110
- transparent: true,
3111
- animationType: "slide",
3112
3890
  onRequestClose: () => setVisible(false),
3113
- children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(AppView, { className: "flex-1", style: { backgroundColor: colors.overlay }, justify: "end", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
3114
- AppView,
3115
- {
3116
- className: "rounded-t-2xl max-h-[70%]",
3117
- style: { backgroundColor: colors.surface },
3118
- children: [
3119
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
3120
- AppView,
3121
- {
3122
- row: true,
3123
- between: true,
3124
- items: "center",
3125
- className: "px-4 py-3",
3126
- style: [styles10.header, { borderBottomColor: colors.divider }],
3127
- children: [
3128
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(AppText, { className: "text-lg font-semibold", style: { color: colors.text }, children: multiple ? multipleSelectTitle : singleSelectTitle }),
3129
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react_native19.TouchableOpacity, { onPress: () => setVisible(false), children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Icon, { name: "close", size: "md", color: colors.icon }) })
3130
- ]
3131
- }
3132
- ),
3133
- searchable && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3134
- AppView,
3135
- {
3136
- className: "px-4 py-3",
3137
- style: [styles10.searchBox, { borderBottomColor: colors.divider }],
3138
- children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
3139
- AppView,
3140
- {
3141
- row: true,
3142
- items: "center",
3143
- className: "px-3 py-2 rounded-lg",
3144
- style: { backgroundColor: colors.surfaceMuted },
3145
- children: [
3146
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react_native19.View, { style: { marginRight: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Icon, { name: "search", size: "sm", color: colors.icon }) }),
3147
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3148
- import_react_native19.TextInput,
3149
- {
3150
- className: "flex-1 text-base",
3151
- style: { color: colors.text },
3152
- placeholder: searchPlaceholder,
3153
- placeholderTextColor: colors.textMuted,
3154
- value: searchKeyword,
3155
- onChangeText: handleSearch,
3156
- autoFocus: true
3157
- }
3158
- ),
3159
- searchKeyword.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react_native19.TouchableOpacity, { onPress: () => setSearchKeyword(""), children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Icon, { name: "close", size: "sm", color: colors.icon }) })
3160
- ]
3161
- }
3162
- )
3163
- }
3164
- ),
3165
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3166
- import_react_native19.FlatList,
3167
- {
3168
- data: filteredOptions,
3169
- keyExtractor: (item) => item.value,
3170
- renderItem: renderOption,
3171
- ListEmptyComponent: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(AppView, { center: true, className: "py-8", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(AppText, { style: { color: colors.textMuted }, children: emptyText }) })
3172
- }
3173
- ),
3174
- multiple && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
3891
+ overlayColor: colors.overlay,
3892
+ surfaceColor: colors.surface,
3893
+ closeOnBackdropPress: true,
3894
+ contentClassName: "max-h-[70%]",
3895
+ children: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
3896
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
3897
+ AppView,
3898
+ {
3899
+ row: true,
3900
+ between: true,
3901
+ items: "center",
3902
+ className: "px-4 py-3",
3903
+ style: [styles11.header, { borderBottomColor: colors.divider }],
3904
+ children: [
3905
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(AppText, { className: "text-lg font-semibold", style: { color: colors.text }, children: multiple ? multipleSelectTitle : singleSelectTitle }),
3906
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_native21.TouchableOpacity, { onPress: () => setVisible(false), children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Icon, { name: "close", size: "md", color: colors.icon }) })
3907
+ ]
3908
+ }
3909
+ ),
3910
+ searchable && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3911
+ AppView,
3912
+ {
3913
+ className: "px-4 py-3",
3914
+ style: [styles11.searchBox, { borderBottomColor: colors.divider }],
3915
+ children: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
3175
3916
  AppView,
3176
3917
  {
3177
3918
  row: true,
3178
- between: true,
3179
3919
  items: "center",
3180
- className: "px-4 py-3",
3181
- style: [styles10.footer, { borderTopColor: colors.divider }],
3920
+ className: "px-3 py-2 rounded-lg",
3921
+ style: { backgroundColor: colors.surfaceMuted },
3182
3922
  children: [
3183
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(AppText, { style: { color: colors.textMuted }, children: formatSelectedCountText(selectedCountText, selectedValues.length) }),
3184
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3185
- import_react_native19.TouchableOpacity,
3923
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_native21.View, { style: { marginRight: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Icon, { name: "search", size: "sm", color: colors.icon }) }),
3924
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3925
+ import_react_native21.TextInput,
3186
3926
  {
3187
- className: "px-4 py-2 rounded-lg",
3188
- style: { backgroundColor: colors.primary },
3189
- onPress: () => setVisible(false),
3190
- children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(AppText, { className: "font-medium", style: { color: colors.textInverse }, children: confirmText })
3927
+ className: "flex-1 text-base",
3928
+ style: { color: colors.text },
3929
+ placeholder: searchPlaceholder,
3930
+ placeholderTextColor: colors.textMuted,
3931
+ value: searchKeyword,
3932
+ onChangeText: handleSearch,
3933
+ autoFocus: true
3191
3934
  }
3192
- )
3935
+ ),
3936
+ searchKeyword.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_native21.TouchableOpacity, { onPress: () => setSearchKeyword(""), children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Icon, { name: "close", size: "sm", color: colors.icon }) })
3193
3937
  ]
3194
3938
  }
3195
3939
  )
3196
- ]
3197
- }
3198
- ) })
3940
+ }
3941
+ ),
3942
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3943
+ import_react_native21.FlatList,
3944
+ {
3945
+ data: filteredOptions,
3946
+ keyExtractor: (item, index) => `${item.value}-${index}`,
3947
+ renderItem: renderOption,
3948
+ ListEmptyComponent: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(AppView, { center: true, className: "py-8", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(AppText, { style: { color: colors.textMuted }, children: emptyText }) })
3949
+ }
3950
+ ),
3951
+ multiple && /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
3952
+ AppView,
3953
+ {
3954
+ row: true,
3955
+ between: true,
3956
+ items: "center",
3957
+ className: "px-4 py-3",
3958
+ style: [styles11.footer, { borderTopColor: colors.divider }],
3959
+ children: [
3960
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(AppText, { style: { color: colors.textMuted }, children: formatSelectedCountText(selectedCountText, selectedValues.length) }),
3961
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3962
+ import_react_native21.TouchableOpacity,
3963
+ {
3964
+ className: "px-4 py-2 rounded-lg",
3965
+ style: { backgroundColor: colors.primary },
3966
+ onPress: () => setVisible(false),
3967
+ children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(AppText, { className: "font-medium", style: { color: colors.textInverse }, children: confirmText })
3968
+ }
3969
+ )
3970
+ ]
3971
+ }
3972
+ )
3973
+ ] })
3199
3974
  }
3200
3975
  )
3201
3976
  ] });
3202
3977
  }
3203
- var styles10 = import_react_native19.StyleSheet.create({
3978
+ var styles11 = import_react_native21.StyleSheet.create({
3204
3979
  trigger: {
3205
3980
  borderWidth: 0.5
3206
3981
  },
@@ -3218,126 +3993,302 @@ var styles10 = import_react_native19.StyleSheet.create({
3218
3993
  }
3219
3994
  });
3220
3995
 
3221
- // src/ui/form/DatePicker.tsx
3222
- var import_react24 = require("react");
3223
- var import_react_native20 = require("react-native");
3224
- var import_jsx_runtime29 = require("nativewind/jsx-runtime");
3225
- function PickerColumn({
3226
- title,
3227
- values,
3996
+ // src/ui/form/Picker.tsx
3997
+ var import_react27 = require("react");
3998
+ var import_react_native22 = require("react-native");
3999
+ var import_jsx_runtime31 = require("nativewind/jsx-runtime");
4000
+ function findFirstEnabledValue(column) {
4001
+ return column.options.find((option) => !option.disabled)?.value;
4002
+ }
4003
+ function normalizeValues(columns, values) {
4004
+ return columns.map((column, index) => {
4005
+ const candidate = values?.[index];
4006
+ const matched = column.options.find((option) => option.value === candidate && !option.disabled);
4007
+ return matched?.value ?? findFirstEnabledValue(column) ?? column.options[0]?.value ?? "";
4008
+ });
4009
+ }
4010
+ function WheelPickerColumn({
4011
+ colors,
4012
+ column,
4013
+ onChange,
4014
+ rowHeight,
3228
4015
  selectedValue,
3229
- onSelect,
3230
- isDisabled,
3231
- formatLabel = (value) => String(value),
3232
4016
  showDivider = false,
3233
- colors
4017
+ visibleRows
3234
4018
  }) {
3235
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
4019
+ const scrollRef = (0, import_react27.useRef)(null);
4020
+ const paddingRows = Math.floor(visibleRows / 2);
4021
+ const selectedIndex = Math.max(
4022
+ 0,
4023
+ column.options.findIndex((option) => option.value === selectedValue)
4024
+ );
4025
+ const scrollToIndex = (0, import_react27.useCallback)(
4026
+ (index, animated) => {
4027
+ scrollRef.current?.scrollTo?.({ y: index * rowHeight, animated });
4028
+ },
4029
+ [rowHeight]
4030
+ );
4031
+ const selectNearestEnabled = (0, import_react27.useCallback)(
4032
+ (targetIndex) => {
4033
+ if (column.options.length === 0) return;
4034
+ const maxIndex = column.options.length - 1;
4035
+ const clampedIndex = Math.max(0, Math.min(maxIndex, targetIndex));
4036
+ const exactOption = column.options[clampedIndex];
4037
+ if (exactOption && !exactOption.disabled) {
4038
+ onChange(exactOption.value);
4039
+ scrollToIndex(clampedIndex, true);
4040
+ return;
4041
+ }
4042
+ for (let distance = 1; distance <= maxIndex; distance += 1) {
4043
+ const prevIndex = clampedIndex - distance;
4044
+ if (prevIndex >= 0) {
4045
+ const prevOption = column.options[prevIndex];
4046
+ if (prevOption && !prevOption.disabled) {
4047
+ onChange(prevOption.value);
4048
+ scrollToIndex(prevIndex, true);
4049
+ return;
4050
+ }
4051
+ }
4052
+ const nextIndex = clampedIndex + distance;
4053
+ if (nextIndex <= maxIndex) {
4054
+ const nextOption = column.options[nextIndex];
4055
+ if (nextOption && !nextOption.disabled) {
4056
+ onChange(nextOption.value);
4057
+ scrollToIndex(nextIndex, true);
4058
+ return;
4059
+ }
4060
+ }
4061
+ }
4062
+ },
4063
+ [column.options, onChange, scrollToIndex]
4064
+ );
4065
+ const handleScrollEnd = (0, import_react27.useCallback)(
4066
+ (event) => {
4067
+ const offsetY = event.nativeEvent.contentOffset?.y ?? 0;
4068
+ selectNearestEnabled(Math.round(offsetY / rowHeight));
4069
+ },
4070
+ [rowHeight, selectNearestEnabled]
4071
+ );
4072
+ (0, import_react27.useEffect)(() => {
4073
+ scrollToIndex(selectedIndex, false);
4074
+ }, [scrollToIndex, selectedIndex]);
4075
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
3236
4076
  AppView,
3237
4077
  {
3238
4078
  flex: true,
3239
4079
  style: [
3240
- showDivider && styles11.column,
4080
+ showDivider ? styles12.columnDivider : void 0,
3241
4081
  showDivider ? { borderRightColor: colors.divider } : void 0
3242
4082
  ],
3243
4083
  children: [
3244
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(AppView, { center: true, className: "py-2", style: { backgroundColor: colors.headerSurface }, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(AppText, { className: "text-sm font-medium", style: { color: colors.textMuted }, children: title }) }),
3245
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(AppView, { className: "flex-1", children: values.map((value) => {
3246
- const selected = selectedValue === value;
3247
- const disabled = isDisabled(value);
3248
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3249
- import_react_native20.TouchableOpacity,
3250
- {
3251
- className: cn("py-2 items-center", selected && "bg-primary-50"),
3252
- style: selected ? { backgroundColor: colors.primarySurface } : void 0,
3253
- disabled,
3254
- onPress: () => onSelect(value),
3255
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3256
- AppText,
4084
+ column.title && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(AppView, { center: true, className: "py-2", style: { backgroundColor: colors.headerSurface }, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(AppText, { className: "text-sm font-medium", style: { color: colors.textMuted }, children: column.title }) }),
4085
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
4086
+ AppView,
4087
+ {
4088
+ style: [
4089
+ styles12.wheelViewport,
4090
+ {
4091
+ height: rowHeight * visibleRows,
4092
+ backgroundColor: colors.surface
4093
+ }
4094
+ ],
4095
+ children: [
4096
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4097
+ import_react_native22.ScrollView,
3257
4098
  {
3258
- className: cn(selected ? "font-semibold" : void 0, disabled && "opacity-30"),
3259
- style: {
3260
- color: selected ? colors.primary : colors.textSecondary
3261
- },
3262
- children: formatLabel(value)
4099
+ ref: scrollRef,
4100
+ showsVerticalScrollIndicator: false,
4101
+ snapToInterval: rowHeight,
4102
+ decelerationRate: "fast",
4103
+ onMomentumScrollEnd: handleScrollEnd,
4104
+ onScrollEndDrag: handleScrollEnd,
4105
+ contentContainerStyle: { paddingVertical: rowHeight * paddingRows },
4106
+ children: column.options.map((option, index) => {
4107
+ const selected = option.value === selectedValue;
4108
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4109
+ import_react_native22.TouchableOpacity,
4110
+ {
4111
+ disabled: option.disabled,
4112
+ onPress: () => {
4113
+ if (option.disabled) return;
4114
+ onChange(option.value);
4115
+ scrollToIndex(index, true);
4116
+ },
4117
+ style: [
4118
+ styles12.optionButton,
4119
+ {
4120
+ height: rowHeight,
4121
+ opacity: option.disabled ? 0.35 : selected ? 1 : 0.72
4122
+ }
4123
+ ],
4124
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4125
+ AppText,
4126
+ {
4127
+ className: cn(selected ? "font-semibold" : void 0),
4128
+ style: { color: selected ? colors.primary : colors.text },
4129
+ children: option.label
4130
+ }
4131
+ )
4132
+ },
4133
+ `${column.key}-${String(option.value)}-${index}`
4134
+ );
4135
+ })
4136
+ }
4137
+ ),
4138
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4139
+ AppView,
4140
+ {
4141
+ pointerEvents: "none",
4142
+ style: [
4143
+ styles12.fadeMask,
4144
+ {
4145
+ top: 0,
4146
+ height: rowHeight * paddingRows
4147
+ }
4148
+ ],
4149
+ children: [0.92, 0.72, 0.48, 0.24].map((opacity) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4150
+ AppView,
4151
+ {
4152
+ flex: true,
4153
+ style: { backgroundColor: colors.surface, opacity }
4154
+ },
4155
+ `top-${opacity}`
4156
+ ))
4157
+ }
4158
+ ),
4159
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4160
+ AppView,
4161
+ {
4162
+ pointerEvents: "none",
4163
+ style: [
4164
+ styles12.fadeMask,
4165
+ {
4166
+ bottom: 0,
4167
+ height: rowHeight * paddingRows
4168
+ }
4169
+ ],
4170
+ children: [0.24, 0.48, 0.72, 0.92].map((opacity) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4171
+ AppView,
4172
+ {
4173
+ flex: true,
4174
+ style: { backgroundColor: colors.surface, opacity }
4175
+ },
4176
+ `bottom-${opacity}`
4177
+ ))
4178
+ }
4179
+ ),
4180
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4181
+ AppView,
4182
+ {
4183
+ pointerEvents: "none",
4184
+ style: [
4185
+ styles12.selectionOverlay,
4186
+ {
4187
+ top: rowHeight * paddingRows,
4188
+ height: rowHeight,
4189
+ borderColor: colors.divider,
4190
+ backgroundColor: colors.primarySurface
4191
+ }
4192
+ ]
3263
4193
  }
3264
4194
  )
3265
- },
3266
- value
3267
- );
3268
- }) })
4195
+ ]
4196
+ }
4197
+ )
3269
4198
  ]
3270
4199
  }
3271
4200
  );
3272
4201
  }
3273
- function DatePicker({
4202
+ function Picker({
3274
4203
  value,
3275
4204
  onChange,
3276
- placeholder = "\u8BF7\u9009\u62E9\u65E5\u671F",
4205
+ columns,
4206
+ placeholder = "\u8BF7\u9009\u62E9",
3277
4207
  disabled = false,
3278
- format = "yyyy-MM-dd",
3279
- minDate,
3280
- maxDate,
3281
4208
  className,
4209
+ pickerTitle = "\u8BF7\u9009\u62E9",
3282
4210
  cancelText = "\u53D6\u6D88",
3283
4211
  confirmText = "\u786E\u5B9A",
3284
- pickerTitle = "\u9009\u62E9\u65E5\u671F",
3285
- pickerDateFormat = "yyyy\u5E74MM\u6708dd\u65E5",
3286
- yearLabel = "\u5E74",
3287
- monthLabel = "\u6708",
3288
- dayLabel = "\u65E5",
3289
- todayText = "\u4ECA\u5929",
3290
- minDateText = "\u6700\u65E9",
3291
- maxDateText = "\u6700\u665A"
4212
+ triggerIconName = "keyboard-arrow-down",
4213
+ renderDisplayText,
4214
+ renderFooter,
4215
+ tempValue,
4216
+ defaultTempValue,
4217
+ onTempChange,
4218
+ onOpen,
4219
+ rowHeight = 40,
4220
+ visibleRows = 5
3292
4221
  }) {
3293
4222
  const colors = useFormThemeColors();
3294
- const [visible, setVisible] = (0, import_react24.useState)(false);
3295
- const [tempDate, setTempDate] = (0, import_react24.useState)(value || /* @__PURE__ */ new Date());
3296
- const displayText = (0, import_react24.useMemo)(() => {
3297
- return value ? formatDate(value, format) : placeholder;
3298
- }, [value, format, placeholder]);
3299
- const handleConfirm = (0, import_react24.useCallback)(() => {
3300
- onChange?.(tempDate);
3301
- setVisible(false);
3302
- }, [tempDate, onChange]);
3303
- const years = (0, import_react24.useMemo)(() => {
3304
- const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
3305
- const arr = [];
3306
- for (let i = currentYear - 50; i <= currentYear + 50; i++) {
3307
- arr.push(i);
4223
+ const [visible, setVisible] = (0, import_react27.useState)(false);
4224
+ const [internalTempValues, setInternalTempValues] = (0, import_react27.useState)(
4225
+ normalizeValues(columns, defaultTempValue ?? value)
4226
+ );
4227
+ const isControlledTemp = tempValue !== void 0;
4228
+ const tempValues = (0, import_react27.useMemo)(
4229
+ () => isControlledTemp ? normalizeValues(columns, tempValue) : internalTempValues,
4230
+ [columns, internalTempValues, isControlledTemp, tempValue]
4231
+ );
4232
+ (0, import_react27.useEffect)(() => {
4233
+ if (!isControlledTemp) {
4234
+ setInternalTempValues((previous) => normalizeValues(columns, previous));
3308
4235
  }
3309
- return arr;
3310
- }, []);
3311
- const months = (0, import_react24.useMemo)(() => {
3312
- return Array.from({ length: 12 }, (_, i) => i + 1);
3313
- }, []);
3314
- const days = (0, import_react24.useMemo)(() => {
3315
- const year = tempDate.getFullYear();
3316
- const month = tempDate.getMonth();
3317
- const daysInMonth = new Date(year, month + 1, 0).getDate();
3318
- return Array.from({ length: daysInMonth }, (_, i) => i + 1);
3319
- }, [tempDate]);
3320
- const isDateDisabled = (0, import_react24.useCallback)(
3321
- (year, month, day) => {
3322
- const date = new Date(year, month - 1, day);
3323
- if (minDate && date < minDate) return true;
3324
- if (maxDate && date > maxDate) return true;
3325
- return false;
4236
+ }, [columns, isControlledTemp]);
4237
+ const selectedOptions = (0, import_react27.useMemo)(
4238
+ () => columns.map(
4239
+ (column, index) => column.options.find((option) => option.value === value?.[index] && !option.disabled)
4240
+ ),
4241
+ [columns, value]
4242
+ );
4243
+ const displayText = (0, import_react27.useMemo)(() => {
4244
+ if (!value || value.length === 0) return placeholder;
4245
+ if (renderDisplayText) return renderDisplayText(selectedOptions);
4246
+ const labels = selectedOptions.map((option) => option?.label).filter(Boolean);
4247
+ return labels.length > 0 ? labels.join(" / ") : placeholder;
4248
+ }, [placeholder, renderDisplayText, selectedOptions, value]);
4249
+ const setTempValues = (0, import_react27.useCallback)(
4250
+ (nextValues) => {
4251
+ const normalized = normalizeValues(columns, nextValues);
4252
+ if (isControlledTemp) {
4253
+ onTempChange?.(normalized);
4254
+ return;
4255
+ }
4256
+ setInternalTempValues(normalized);
4257
+ onTempChange?.(normalized);
3326
4258
  },
3327
- [minDate, maxDate]
4259
+ [columns, isControlledTemp, onTempChange]
3328
4260
  );
3329
- const updateTempDate = (0, import_react24.useCallback)(
3330
- (year, month, day) => {
3331
- const newDate = new Date(tempDate);
3332
- if (year !== void 0) newDate.setFullYear(year);
3333
- if (month !== void 0) newDate.setMonth(month - 1);
3334
- if (day !== void 0) newDate.setDate(day);
3335
- setTempDate(newDate);
4261
+ const updateColumnValue = (0, import_react27.useCallback)(
4262
+ (columnIndex, nextValue) => {
4263
+ const nextValues = [...tempValues];
4264
+ nextValues[columnIndex] = nextValue;
4265
+ setTempValues(nextValues);
3336
4266
  },
3337
- [tempDate]
4267
+ [setTempValues, tempValues]
3338
4268
  );
3339
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
3340
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
4269
+ const openModal = (0, import_react27.useCallback)(() => {
4270
+ const normalized = normalizeValues(columns, tempValue ?? value ?? defaultTempValue);
4271
+ if (!isControlledTemp) {
4272
+ setInternalTempValues(normalized);
4273
+ }
4274
+ onTempChange?.(normalized);
4275
+ onOpen?.();
4276
+ setVisible(true);
4277
+ }, [
4278
+ columns,
4279
+ defaultTempValue,
4280
+ isControlledTemp,
4281
+ onOpen,
4282
+ onTempChange,
4283
+ tempValue,
4284
+ value
4285
+ ]);
4286
+ const handleConfirm = (0, import_react27.useCallback)(() => {
4287
+ onChange?.(tempValues);
4288
+ setVisible(false);
4289
+ }, [onChange, tempValues]);
4290
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
4291
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
3341
4292
  AppPressable,
3342
4293
  {
3343
4294
  className: cn(
@@ -3345,147 +4296,252 @@ function DatePicker({
3345
4296
  disabled ? "opacity-60" : "",
3346
4297
  className
3347
4298
  ),
3348
- style: [styles11.trigger, { backgroundColor: colors.surface, borderColor: colors.border }],
4299
+ style: [styles12.trigger, { backgroundColor: colors.surface, borderColor: colors.border }],
3349
4300
  disabled,
3350
- onPress: () => {
3351
- setTempDate(value || /* @__PURE__ */ new Date());
3352
- setVisible(true);
3353
- },
4301
+ onPress: openModal,
3354
4302
  children: [
3355
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
4303
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
3356
4304
  AppText,
3357
4305
  {
3358
4306
  className: "flex-1",
3359
- style: { color: value ? colors.text : colors.textMuted },
4307
+ style: { color: value && value.length > 0 ? colors.text : colors.textMuted },
3360
4308
  numberOfLines: 1,
3361
4309
  children: displayText
3362
4310
  }
3363
4311
  ),
3364
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(Icon, { name: "calendar-today", size: "md", color: colors.icon })
4312
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Icon, { name: triggerIconName, size: "md", color: colors.icon })
3365
4313
  ]
3366
4314
  }
3367
4315
  ),
3368
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3369
- import_react_native20.Modal,
4316
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4317
+ BottomSheetModal,
3370
4318
  {
3371
4319
  visible,
3372
- transparent: true,
3373
- animationType: "slide",
3374
4320
  onRequestClose: () => setVisible(false),
3375
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(AppView, { className: "flex-1", style: { backgroundColor: colors.overlay }, justify: "end", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(AppView, { className: "rounded-t-2xl", style: { backgroundColor: colors.surface }, children: [
3376
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
4321
+ overlayColor: colors.overlay,
4322
+ surfaceColor: colors.surface,
4323
+ closeOnBackdropPress: true,
4324
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
4325
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
3377
4326
  AppView,
3378
4327
  {
3379
4328
  row: true,
3380
4329
  between: true,
3381
4330
  items: "center",
3382
4331
  className: "px-4 py-3",
3383
- style: [styles11.header, { borderBottomColor: colors.divider }],
4332
+ style: [styles12.header, { borderBottomColor: colors.divider }],
3384
4333
  children: [
3385
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_react_native20.TouchableOpacity, { onPress: () => setVisible(false), children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(AppText, { style: { color: colors.textMuted }, children: cancelText }) }),
3386
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(AppText, { className: "text-lg font-semibold", style: { color: colors.text }, children: pickerTitle }),
3387
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_react_native20.TouchableOpacity, { onPress: handleConfirm, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(AppText, { style: { color: colors.primary }, className: "font-medium", children: confirmText }) })
4334
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_native22.TouchableOpacity, { onPress: () => setVisible(false), children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(AppText, { style: { color: colors.textMuted }, children: cancelText }) }),
4335
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(AppText, { className: "text-lg font-semibold", style: { color: colors.text }, children: pickerTitle }),
4336
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_native22.TouchableOpacity, { onPress: handleConfirm, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(AppText, { className: "font-medium", style: { color: colors.primary }, children: confirmText }) })
3388
4337
  ]
3389
4338
  }
3390
4339
  ),
3391
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(AppView, { center: true, className: "py-4", style: { backgroundColor: colors.headerSurface }, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(AppText, { className: "text-2xl font-semibold", style: { color: colors.text }, children: formatDate(tempDate, pickerDateFormat) }) }),
3392
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(AppView, { row: true, className: "h-48", children: [
3393
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3394
- PickerColumn,
3395
- {
3396
- title: yearLabel,
3397
- values: years,
3398
- selectedValue: tempDate.getFullYear(),
3399
- onSelect: (year) => updateTempDate(year),
3400
- isDisabled: (year) => isDateDisabled(year, tempDate.getMonth() + 1, tempDate.getDate()),
3401
- colors,
3402
- showDivider: true
3403
- }
3404
- ),
3405
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3406
- PickerColumn,
3407
- {
3408
- title: monthLabel,
3409
- values: months,
3410
- selectedValue: tempDate.getMonth() + 1,
3411
- onSelect: (month) => updateTempDate(void 0, month),
3412
- isDisabled: (month) => isDateDisabled(tempDate.getFullYear(), month, tempDate.getDate()),
3413
- formatLabel: (month) => `${month}\u6708`,
3414
- colors,
3415
- showDivider: true
3416
- }
3417
- ),
3418
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3419
- PickerColumn,
3420
- {
3421
- title: dayLabel,
3422
- values: days,
3423
- selectedValue: tempDate.getDate(),
3424
- onSelect: (day) => updateTempDate(void 0, void 0, day),
3425
- isDisabled: (day) => isDateDisabled(tempDate.getFullYear(), tempDate.getMonth() + 1, day),
3426
- colors
3427
- }
3428
- )
3429
- ] }),
3430
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
4340
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(AppView, { row: true, children: columns.map((column, index) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4341
+ WheelPickerColumn,
4342
+ {
4343
+ colors,
4344
+ column,
4345
+ onChange: (nextValue) => updateColumnValue(index, nextValue),
4346
+ rowHeight,
4347
+ selectedValue: tempValues[index],
4348
+ showDivider: index < columns.length - 1,
4349
+ visibleRows
4350
+ },
4351
+ column.key
4352
+ )) }),
4353
+ renderFooter && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
3431
4354
  AppView,
3432
4355
  {
3433
- row: true,
3434
- className: "px-4 py-3 gap-2",
3435
- style: [styles11.footer, { borderTopColor: colors.divider }],
3436
- children: [
3437
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3438
- import_react_native20.TouchableOpacity,
3439
- {
3440
- className: "flex-1 py-2 items-center rounded-lg",
3441
- style: { backgroundColor: colors.surfaceMuted },
3442
- onPress: () => setTempDate(/* @__PURE__ */ new Date()),
3443
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(AppText, { style: { color: colors.text }, children: todayText })
3444
- }
3445
- ),
3446
- minDate && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3447
- import_react_native20.TouchableOpacity,
3448
- {
3449
- className: "flex-1 py-2 items-center rounded-lg",
3450
- style: { backgroundColor: colors.surfaceMuted },
3451
- onPress: () => setTempDate(minDate),
3452
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(AppText, { style: { color: colors.text }, children: minDateText })
3453
- }
3454
- ),
3455
- maxDate && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3456
- import_react_native20.TouchableOpacity,
3457
- {
3458
- className: "flex-1 py-2 items-center rounded-lg",
3459
- style: { backgroundColor: colors.surfaceMuted },
3460
- onPress: () => setTempDate(maxDate),
3461
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(AppText, { style: { color: colors.text }, children: maxDateText })
3462
- }
3463
- )
3464
- ]
4356
+ className: "px-4 py-3",
4357
+ style: [styles12.footer, { borderTopColor: colors.divider }],
4358
+ children: renderFooter({ close: () => setVisible(false), setTempValues, tempValues })
3465
4359
  }
3466
4360
  )
3467
- ] }) })
4361
+ ] })
3468
4362
  }
3469
4363
  )
3470
4364
  ] });
3471
4365
  }
3472
- var styles11 = import_react_native20.StyleSheet.create({
3473
- trigger: {
3474
- borderWidth: 0.5
3475
- },
3476
- header: {
3477
- borderBottomWidth: 0.5
3478
- },
3479
- column: {
3480
- borderRightWidth: 0.5
3481
- },
3482
- footer: {
3483
- borderTopWidth: 0.5
3484
- }
3485
- });
4366
+ var styles12 = import_react_native22.StyleSheet.create({
4367
+ trigger: {
4368
+ borderWidth: 0.5
4369
+ },
4370
+ header: {
4371
+ borderBottomWidth: 0.5
4372
+ },
4373
+ footer: {
4374
+ borderTopWidth: 0.5
4375
+ },
4376
+ columnDivider: {
4377
+ borderRightWidth: 0.5
4378
+ },
4379
+ wheelViewport: {
4380
+ position: "relative",
4381
+ overflow: "hidden"
4382
+ },
4383
+ fadeMask: {
4384
+ position: "absolute",
4385
+ left: 0,
4386
+ right: 0
4387
+ },
4388
+ selectionOverlay: {
4389
+ position: "absolute",
4390
+ left: 8,
4391
+ right: 8,
4392
+ borderRadius: 12,
4393
+ borderWidth: 0.5
4394
+ },
4395
+ optionButton: {
4396
+ alignItems: "center",
4397
+ justifyContent: "center"
4398
+ }
4399
+ });
4400
+
4401
+ // src/ui/form/DatePicker.tsx
4402
+ var import_react28 = require("react");
4403
+ var import_react_native23 = require("react-native");
4404
+ var import_jsx_runtime32 = require("nativewind/jsx-runtime");
4405
+ function createSafeDate(year, month, day) {
4406
+ const daysInMonth = new Date(year, month, 0).getDate();
4407
+ return new Date(year, month - 1, Math.min(day, daysInMonth));
4408
+ }
4409
+ function getDateValues(date) {
4410
+ return [date.getFullYear(), date.getMonth() + 1, date.getDate()];
4411
+ }
4412
+ function DatePicker({
4413
+ value,
4414
+ onChange,
4415
+ placeholder = "\u8BF7\u9009\u62E9\u65E5\u671F",
4416
+ disabled = false,
4417
+ format = "yyyy-MM-dd",
4418
+ minDate,
4419
+ maxDate,
4420
+ className,
4421
+ cancelText = "\u53D6\u6D88",
4422
+ confirmText = "\u786E\u5B9A",
4423
+ pickerTitle = "\u9009\u62E9\u65E5\u671F",
4424
+ pickerDateFormat: _pickerDateFormat = "yyyy\u5E74MM\u6708dd\u65E5",
4425
+ yearLabel = "\u5E74",
4426
+ monthLabel = "\u6708",
4427
+ dayLabel = "\u65E5",
4428
+ todayText = "\u4ECA\u5929",
4429
+ minDateText = "\u6700\u65E9",
4430
+ maxDateText = "\u6700\u665A"
4431
+ }) {
4432
+ const colors = useFormThemeColors();
4433
+ const [tempDate, setTempDate] = (0, import_react28.useState)(value || /* @__PURE__ */ new Date());
4434
+ (0, import_react28.useEffect)(() => {
4435
+ if (value) setTempDate(value);
4436
+ }, [value]);
4437
+ const years = (0, import_react28.useMemo)(() => {
4438
+ const baseYear = value?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear();
4439
+ const startYear = minDate?.getFullYear() ?? baseYear - 50;
4440
+ const endYear = maxDate?.getFullYear() ?? baseYear + 50;
4441
+ return Array.from({ length: endYear - startYear + 1 }, (_, index) => startYear + index);
4442
+ }, [maxDate, minDate, value]);
4443
+ const months = (0, import_react28.useMemo)(() => Array.from({ length: 12 }, (_, index) => index + 1), []);
4444
+ const days = (0, import_react28.useMemo)(() => {
4445
+ const daysInMonth = new Date(tempDate.getFullYear(), tempDate.getMonth() + 1, 0).getDate();
4446
+ return Array.from({ length: daysInMonth }, (_, index) => index + 1);
4447
+ }, [tempDate]);
4448
+ const isDateDisabled = (0, import_react28.useCallback)(
4449
+ (year, month, day) => {
4450
+ const date = createSafeDate(year, month, day);
4451
+ if (minDate && date < minDate) return true;
4452
+ if (maxDate && date > maxDate) return true;
4453
+ return false;
4454
+ },
4455
+ [maxDate, minDate]
4456
+ );
4457
+ const columns = (0, import_react28.useMemo)(
4458
+ () => [
4459
+ {
4460
+ key: "year",
4461
+ title: yearLabel,
4462
+ options: years.map((year) => ({
4463
+ label: String(year),
4464
+ value: year,
4465
+ disabled: isDateDisabled(year, tempDate.getMonth() + 1, tempDate.getDate())
4466
+ }))
4467
+ },
4468
+ {
4469
+ key: "month",
4470
+ title: monthLabel,
4471
+ options: months.map((month) => ({
4472
+ label: `${month}\u6708`,
4473
+ value: month,
4474
+ disabled: isDateDisabled(tempDate.getFullYear(), month, tempDate.getDate())
4475
+ }))
4476
+ },
4477
+ {
4478
+ key: "day",
4479
+ title: dayLabel,
4480
+ options: days.map((day) => ({
4481
+ label: `${day}\u65E5`,
4482
+ value: day,
4483
+ disabled: isDateDisabled(tempDate.getFullYear(), tempDate.getMonth() + 1, day)
4484
+ }))
4485
+ }
4486
+ ],
4487
+ [dayLabel, days, isDateDisabled, monthLabel, months, tempDate, yearLabel, years]
4488
+ );
4489
+ const handleTempChange = (0, import_react28.useCallback)((nextValues) => {
4490
+ const [nextYear, nextMonth, nextDay] = nextValues;
4491
+ if (typeof nextYear !== "number" || typeof nextMonth !== "number" || typeof nextDay !== "number") {
4492
+ return;
4493
+ }
4494
+ setTempDate(createSafeDate(nextYear, nextMonth, nextDay));
4495
+ }, []);
4496
+ const handleChange = (0, import_react28.useCallback)(
4497
+ (nextValues) => {
4498
+ const [nextYear, nextMonth, nextDay] = nextValues;
4499
+ if (typeof nextYear !== "number" || typeof nextMonth !== "number" || typeof nextDay !== "number") {
4500
+ return;
4501
+ }
4502
+ onChange?.(createSafeDate(nextYear, nextMonth, nextDay));
4503
+ },
4504
+ [onChange]
4505
+ );
4506
+ const quickActions = (0, import_react28.useMemo)(() => {
4507
+ const actions = [{ label: todayText, date: /* @__PURE__ */ new Date() }];
4508
+ if (minDate) actions.push({ label: minDateText, date: minDate });
4509
+ if (maxDate) actions.push({ label: maxDateText, date: maxDate });
4510
+ return actions;
4511
+ }, [maxDate, maxDateText, minDate, minDateText, todayText]);
4512
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
4513
+ Picker,
4514
+ {
4515
+ value: value ? getDateValues(value) : void 0,
4516
+ tempValue: getDateValues(tempDate),
4517
+ onTempChange: handleTempChange,
4518
+ onChange: handleChange,
4519
+ onOpen: () => setTempDate(value || /* @__PURE__ */ new Date()),
4520
+ columns,
4521
+ placeholder,
4522
+ disabled,
4523
+ className,
4524
+ pickerTitle,
4525
+ cancelText,
4526
+ confirmText,
4527
+ triggerIconName: "calendar-today",
4528
+ renderDisplayText: () => value ? formatDate(value, format) : placeholder,
4529
+ renderFooter: () => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(AppView, { row: true, className: "gap-2", children: quickActions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
4530
+ import_react_native23.TouchableOpacity,
4531
+ {
4532
+ className: "flex-1 py-2 items-center rounded-lg",
4533
+ style: { backgroundColor: colors.surfaceMuted },
4534
+ onPress: () => setTempDate(action.date),
4535
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(AppText, { style: { color: colors.text }, children: action.label })
4536
+ },
4537
+ action.label
4538
+ )) })
4539
+ }
4540
+ );
4541
+ }
3486
4542
 
3487
4543
  // src/ui/form/FormItem.tsx
3488
- var import_jsx_runtime30 = require("nativewind/jsx-runtime");
4544
+ var import_jsx_runtime33 = require("nativewind/jsx-runtime");
3489
4545
  function FormItem({
3490
4546
  name: _name,
3491
4547
  label,
@@ -3497,19 +4553,19 @@ function FormItem({
3497
4553
  labelClassName
3498
4554
  }) {
3499
4555
  const colors = useThemeColors();
3500
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(AppView, { className: cn("mb-4", className), children: [
3501
- label && /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(AppView, { row: true, items: "center", gap: 1, className: cn("mb-2", labelClassName), children: [
3502
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(AppText, { size: "sm", weight: "medium", style: { color: colors.textSecondary }, children: label }),
3503
- required && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(AppText, { color: "error-500", children: "*" })
4556
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(AppView, { className: cn("mb-4", className), children: [
4557
+ label && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(AppView, { row: true, items: "center", gap: 1, className: cn("mb-2", labelClassName), children: [
4558
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(AppText, { size: "sm", weight: "medium", style: { color: colors.textSecondary }, children: label }),
4559
+ required && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(AppText, { color: "error-500", children: "*" })
3504
4560
  ] }),
3505
4561
  children,
3506
- error && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(AppText, { size: "sm", color: "error-500", className: "mt-1", children: error }),
3507
- help && !error && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(AppText, { size: "sm", className: "mt-1", style: { color: colors.textMuted }, children: help })
4562
+ error && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(AppText, { size: "sm", color: "error-500", className: "mt-1", children: error }),
4563
+ help && !error && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(AppText, { size: "sm", className: "mt-1", style: { color: colors.textMuted }, children: help })
3508
4564
  ] });
3509
4565
  }
3510
4566
 
3511
4567
  // src/ui/form/useForm.ts
3512
- var import_react25 = require("react");
4568
+ var import_react29 = require("react");
3513
4569
  var getIssuePath = (issue) => issue.path.map(String).join(".");
3514
4570
  var getFieldError = (issues, name) => {
3515
4571
  const exactIssue = issues.find((issue) => getIssuePath(issue) === name);
@@ -3525,16 +4581,16 @@ var buildFormErrors = (issues) => {
3525
4581
  }, {});
3526
4582
  };
3527
4583
  function useForm({ schema, defaultValues }) {
3528
- const [values, setValues] = (0, import_react25.useState)(defaultValues);
3529
- const [errors, setErrors] = (0, import_react25.useState)({});
3530
- const [isSubmitting, setIsSubmitting] = (0, import_react25.useState)(false);
3531
- const isDirty = (0, import_react25.useMemo)(() => {
4584
+ const [values, setValues] = (0, import_react29.useState)(defaultValues);
4585
+ const [errors, setErrors] = (0, import_react29.useState)({});
4586
+ const [isSubmitting, setIsSubmitting] = (0, import_react29.useState)(false);
4587
+ const isDirty = (0, import_react29.useMemo)(() => {
3532
4588
  return JSON.stringify(values) !== JSON.stringify(defaultValues);
3533
4589
  }, [values, defaultValues]);
3534
- const isValid = (0, import_react25.useMemo)(() => {
4590
+ const isValid = (0, import_react29.useMemo)(() => {
3535
4591
  return Object.keys(errors).length === 0;
3536
4592
  }, [errors]);
3537
- const clearFieldError = (0, import_react25.useCallback)((name) => {
4593
+ const clearFieldError = (0, import_react29.useCallback)((name) => {
3538
4594
  setErrors((prev) => {
3539
4595
  if (!(name in prev)) return prev;
3540
4596
  const next = { ...prev };
@@ -3542,20 +4598,20 @@ function useForm({ schema, defaultValues }) {
3542
4598
  return next;
3543
4599
  });
3544
4600
  }, []);
3545
- const setValue = (0, import_react25.useCallback)(
4601
+ const setValue = (0, import_react29.useCallback)(
3546
4602
  (name, value) => {
3547
4603
  setValues((prev) => ({ ...prev, [name]: value }));
3548
4604
  clearFieldError(name);
3549
4605
  },
3550
4606
  [clearFieldError]
3551
4607
  );
3552
- const getValue = (0, import_react25.useCallback)(
4608
+ const getValue = (0, import_react29.useCallback)(
3553
4609
  (name) => {
3554
4610
  return values[name];
3555
4611
  },
3556
4612
  [values]
3557
4613
  );
3558
- const validateField = (0, import_react25.useCallback)(
4614
+ const validateField = (0, import_react29.useCallback)(
3559
4615
  async (name) => {
3560
4616
  const fieldName = name;
3561
4617
  const result = await schema.safeParseAsync(values);
@@ -3576,7 +4632,7 @@ function useForm({ schema, defaultValues }) {
3576
4632
  },
3577
4633
  [schema, values, clearFieldError]
3578
4634
  );
3579
- const validate = (0, import_react25.useCallback)(async () => {
4635
+ const validate = (0, import_react29.useCallback)(async () => {
3580
4636
  const result = await schema.safeParseAsync(values);
3581
4637
  if (result.success) {
3582
4638
  setErrors({});
@@ -3585,12 +4641,12 @@ function useForm({ schema, defaultValues }) {
3585
4641
  setErrors(buildFormErrors(result.error.issues));
3586
4642
  return false;
3587
4643
  }, [schema, values]);
3588
- const reset = (0, import_react25.useCallback)(() => {
4644
+ const reset = (0, import_react29.useCallback)(() => {
3589
4645
  setValues(defaultValues);
3590
4646
  setErrors({});
3591
4647
  setIsSubmitting(false);
3592
4648
  }, [defaultValues]);
3593
- const handleSubmit = (0, import_react25.useCallback)(
4649
+ const handleSubmit = (0, import_react29.useCallback)(
3594
4650
  async (onSubmit) => {
3595
4651
  const valid = await validate();
3596
4652
  if (!valid) return;
@@ -3619,38 +4675,38 @@ function useForm({ schema, defaultValues }) {
3619
4675
  }
3620
4676
 
3621
4677
  // src/ui/hooks/useToggle.ts
3622
- var import_react26 = require("react");
4678
+ var import_react30 = require("react");
3623
4679
  function useToggle(defaultValue = false) {
3624
- const [value, setValue] = (0, import_react26.useState)(defaultValue);
3625
- const toggle = (0, import_react26.useCallback)(() => {
4680
+ const [value, setValue] = (0, import_react30.useState)(defaultValue);
4681
+ const toggle = (0, import_react30.useCallback)(() => {
3626
4682
  setValue((v) => !v);
3627
4683
  }, []);
3628
- const set = (0, import_react26.useCallback)((newValue) => {
4684
+ const set = (0, import_react30.useCallback)((newValue) => {
3629
4685
  setValue(newValue);
3630
4686
  }, []);
3631
- const setTrue = (0, import_react26.useCallback)(() => {
4687
+ const setTrue = (0, import_react30.useCallback)(() => {
3632
4688
  setValue(true);
3633
4689
  }, []);
3634
- const setFalse = (0, import_react26.useCallback)(() => {
4690
+ const setFalse = (0, import_react30.useCallback)(() => {
3635
4691
  setValue(false);
3636
4692
  }, []);
3637
4693
  return [value, { toggle, set, setTrue, setFalse }];
3638
4694
  }
3639
4695
 
3640
4696
  // src/ui/hooks/usePageDrawer.ts
3641
- var import_react27 = require("react");
4697
+ var import_react31 = require("react");
3642
4698
  function usePageDrawer(defaultVisible = false) {
3643
- const [visible, setVisibleState] = (0, import_react27.useState)(defaultVisible);
3644
- const open = (0, import_react27.useCallback)(() => {
4699
+ const [visible, setVisibleState] = (0, import_react31.useState)(defaultVisible);
4700
+ const open = (0, import_react31.useCallback)(() => {
3645
4701
  setVisibleState(true);
3646
4702
  }, []);
3647
- const close = (0, import_react27.useCallback)(() => {
4703
+ const close = (0, import_react31.useCallback)(() => {
3648
4704
  setVisibleState(false);
3649
4705
  }, []);
3650
- const toggle = (0, import_react27.useCallback)(() => {
4706
+ const toggle = (0, import_react31.useCallback)(() => {
3651
4707
  setVisibleState((current) => !current);
3652
4708
  }, []);
3653
- const setVisible = (0, import_react27.useCallback)((nextVisible) => {
4709
+ const setVisible = (0, import_react31.useCallback)((nextVisible) => {
3654
4710
  setVisibleState(nextVisible);
3655
4711
  }, []);
3656
4712
  return {
@@ -3663,10 +4719,10 @@ function usePageDrawer(defaultVisible = false) {
3663
4719
  }
3664
4720
 
3665
4721
  // src/ui/hooks/useDebounce.ts
3666
- var import_react28 = require("react");
4722
+ var import_react32 = require("react");
3667
4723
  function useDebounce(value, delay = 500) {
3668
- const [debouncedValue, setDebouncedValue] = (0, import_react28.useState)(value);
3669
- (0, import_react28.useEffect)(() => {
4724
+ const [debouncedValue, setDebouncedValue] = (0, import_react32.useState)(value);
4725
+ (0, import_react32.useEffect)(() => {
3670
4726
  const timer = setTimeout(() => {
3671
4727
  setDebouncedValue(value);
3672
4728
  }, delay);
@@ -3678,11 +4734,11 @@ function useDebounce(value, delay = 500) {
3678
4734
  }
3679
4735
 
3680
4736
  // src/ui/hooks/useThrottle.ts
3681
- var import_react29 = require("react");
4737
+ var import_react33 = require("react");
3682
4738
  function useThrottle(value, delay = 200) {
3683
- const [throttledValue, setThrottledValue] = (0, import_react29.useState)(value);
3684
- const lastUpdatedRef = (0, import_react29.useRef)(Date.now());
3685
- (0, import_react29.useEffect)(() => {
4739
+ const [throttledValue, setThrottledValue] = (0, import_react33.useState)(value);
4740
+ const lastUpdatedRef = (0, import_react33.useRef)(Date.now());
4741
+ (0, import_react33.useEffect)(() => {
3686
4742
  const now = Date.now();
3687
4743
  const timeElapsed = now - lastUpdatedRef.current;
3688
4744
  if (timeElapsed >= delay) {
@@ -3703,12 +4759,12 @@ function useThrottle(value, delay = 200) {
3703
4759
  }
3704
4760
 
3705
4761
  // src/ui/hooks/useKeyboard.ts
3706
- var import_react30 = require("react");
3707
- var import_react_native21 = require("react-native");
4762
+ var import_react34 = require("react");
4763
+ var import_react_native24 = require("react-native");
3708
4764
  function useKeyboard() {
3709
- const [visible, setVisible] = (0, import_react30.useState)(false);
3710
- const [height, setHeight] = (0, import_react30.useState)(0);
3711
- (0, import_react30.useEffect)(() => {
4765
+ const [visible, setVisible] = (0, import_react34.useState)(false);
4766
+ const [height, setHeight] = (0, import_react34.useState)(0);
4767
+ (0, import_react34.useEffect)(() => {
3712
4768
  const handleKeyboardWillShow = (event) => {
3713
4769
  setVisible(true);
3714
4770
  setHeight(event.endCoordinates.height);
@@ -3725,31 +4781,31 @@ function useKeyboard() {
3725
4781
  setVisible(false);
3726
4782
  setHeight(0);
3727
4783
  };
3728
- const willShowSub = import_react_native21.Keyboard.addListener(
3729
- import_react_native21.Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow",
3730
- import_react_native21.Platform.OS === "ios" ? handleKeyboardWillShow : handleKeyboardDidShow
4784
+ const willShowSub = import_react_native24.Keyboard.addListener(
4785
+ import_react_native24.Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow",
4786
+ import_react_native24.Platform.OS === "ios" ? handleKeyboardWillShow : handleKeyboardDidShow
3731
4787
  );
3732
- const willHideSub = import_react_native21.Keyboard.addListener(
3733
- import_react_native21.Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide",
3734
- import_react_native21.Platform.OS === "ios" ? handleKeyboardWillHide : handleKeyboardDidHide
4788
+ const willHideSub = import_react_native24.Keyboard.addListener(
4789
+ import_react_native24.Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide",
4790
+ import_react_native24.Platform.OS === "ios" ? handleKeyboardWillHide : handleKeyboardDidHide
3735
4791
  );
3736
4792
  return () => {
3737
4793
  willShowSub.remove();
3738
4794
  willHideSub.remove();
3739
4795
  };
3740
4796
  }, []);
3741
- const dismiss = (0, import_react30.useCallback)(() => {
3742
- import_react_native21.Keyboard.dismiss();
4797
+ const dismiss = (0, import_react34.useCallback)(() => {
4798
+ import_react_native24.Keyboard.dismiss();
3743
4799
  }, []);
3744
4800
  return { visible, height, dismiss };
3745
4801
  }
3746
4802
 
3747
4803
  // src/ui/hooks/useDimensions.ts
3748
- var import_react31 = require("react");
3749
- var import_react_native22 = require("react-native");
4804
+ var import_react35 = require("react");
4805
+ var import_react_native25 = require("react-native");
3750
4806
  function useDimensions() {
3751
- const [dimensions, setDimensions] = (0, import_react31.useState)(() => {
3752
- const window = import_react_native22.Dimensions.get("window");
4807
+ const [dimensions, setDimensions] = (0, import_react35.useState)(() => {
4808
+ const window = import_react_native25.Dimensions.get("window");
3753
4809
  return {
3754
4810
  width: window.width,
3755
4811
  height: window.height,
@@ -3757,7 +4813,7 @@ function useDimensions() {
3757
4813
  fontScale: window.fontScale
3758
4814
  };
3759
4815
  });
3760
- (0, import_react31.useEffect)(() => {
4816
+ (0, import_react35.useEffect)(() => {
3761
4817
  const handleChange = ({ window }) => {
3762
4818
  setDimensions({
3763
4819
  width: window.width,
@@ -3766,7 +4822,7 @@ function useDimensions() {
3766
4822
  fontScale: window.fontScale
3767
4823
  });
3768
4824
  };
3769
- const subscription = import_react_native22.Dimensions.addEventListener("change", handleChange);
4825
+ const subscription = import_react_native25.Dimensions.addEventListener("change", handleChange);
3770
4826
  return () => {
3771
4827
  subscription.remove();
3772
4828
  };
@@ -3775,20 +4831,20 @@ function useDimensions() {
3775
4831
  }
3776
4832
 
3777
4833
  // src/ui/hooks/useOrientation.ts
3778
- var import_react32 = require("react");
3779
- var import_react_native23 = require("react-native");
4834
+ var import_react36 = require("react");
4835
+ var import_react_native26 = require("react-native");
3780
4836
  function useOrientation() {
3781
4837
  const getOrientation = () => {
3782
- const { width, height } = import_react_native23.Dimensions.get("window");
4838
+ const { width, height } = import_react_native26.Dimensions.get("window");
3783
4839
  return width > height ? "landscape" : "portrait";
3784
4840
  };
3785
- const [orientation, setOrientation] = (0, import_react32.useState)(getOrientation);
3786
- (0, import_react32.useEffect)(() => {
4841
+ const [orientation, setOrientation] = (0, import_react36.useState)(getOrientation);
4842
+ (0, import_react36.useEffect)(() => {
3787
4843
  const handleChange = ({ window }) => {
3788
4844
  const newOrientation = window.width > window.height ? "landscape" : "portrait";
3789
4845
  setOrientation(newOrientation);
3790
4846
  };
3791
- const subscription = import_react_native23.Dimensions.addEventListener("change", handleChange);
4847
+ const subscription = import_react_native26.Dimensions.addEventListener("change", handleChange);
3792
4848
  return () => {
3793
4849
  subscription.remove();
3794
4850
  };
@@ -3801,7 +4857,7 @@ function useOrientation() {
3801
4857
  }
3802
4858
 
3803
4859
  // src/navigation/provider.tsx
3804
- var import_react33 = __toESM(require("react"));
4860
+ var import_react37 = __toESM(require("react"));
3805
4861
  var import_native = require("@react-navigation/native");
3806
4862
 
3807
4863
  // src/navigation/utils/navigation-theme.ts
@@ -3827,7 +4883,7 @@ function createNavigationTheme(pantherTheme, isDark) {
3827
4883
  }
3828
4884
 
3829
4885
  // src/navigation/provider.tsx
3830
- var import_jsx_runtime31 = require("nativewind/jsx-runtime");
4886
+ var import_jsx_runtime34 = require("nativewind/jsx-runtime");
3831
4887
  function NavigationProvider({
3832
4888
  children,
3833
4889
  linking,
@@ -3838,11 +4894,11 @@ function NavigationProvider({
3838
4894
  theme: customTheme
3839
4895
  }) {
3840
4896
  const { theme, isDark } = useTheme();
3841
- const navigationTheme = import_react33.default.useMemo(
4897
+ const navigationTheme = import_react37.default.useMemo(
3842
4898
  () => customTheme || createNavigationTheme(theme, isDark),
3843
4899
  [customTheme, theme, isDark]
3844
4900
  );
3845
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4901
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
3846
4902
  import_native.NavigationContainer,
3847
4903
  {
3848
4904
  theme: navigationTheme,
@@ -3860,14 +4916,14 @@ function NavigationProvider({
3860
4916
  var import_stack = require("@react-navigation/stack");
3861
4917
 
3862
4918
  // src/navigation/navigators/StackNavigator.tsx
3863
- var import_jsx_runtime32 = require("nativewind/jsx-runtime");
4919
+ var import_jsx_runtime35 = require("nativewind/jsx-runtime");
3864
4920
  var NativeStack = (0, import_stack.createStackNavigator)();
3865
4921
  var defaultScreenOptions = {
3866
4922
  headerShown: false,
3867
4923
  ...import_stack.TransitionPresets.SlideFromRightIOS
3868
4924
  };
3869
4925
  function StackNavigator({ initialRouteName, screenOptions, children }) {
3870
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
4926
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3871
4927
  NativeStack.Navigator,
3872
4928
  {
3873
4929
  initialRouteName,
@@ -3879,7 +4935,7 @@ function StackNavigator({ initialRouteName, screenOptions, children }) {
3879
4935
  StackNavigator.Screen = NativeStack.Screen;
3880
4936
  StackNavigator.Group = NativeStack.Group;
3881
4937
  function createStackScreens(routes) {
3882
- return routes.map((route) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
4938
+ return routes.map((route) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3883
4939
  StackNavigator.Screen,
3884
4940
  {
3885
4941
  name: route.name,
@@ -3892,13 +4948,13 @@ function createStackScreens(routes) {
3892
4948
  }
3893
4949
 
3894
4950
  // src/navigation/navigators/TabNavigator.tsx
3895
- var import_react34 = __toESM(require("react"));
4951
+ var import_react38 = __toESM(require("react"));
3896
4952
  var import_bottom_tabs = require("@react-navigation/bottom-tabs");
3897
4953
 
3898
4954
  // src/navigation/components/BottomTabBar.tsx
3899
- var import_react_native24 = require("react-native");
4955
+ var import_react_native27 = require("react-native");
3900
4956
  var import_react_native_safe_area_context2 = require("react-native-safe-area-context");
3901
- var import_jsx_runtime33 = require("nativewind/jsx-runtime");
4957
+ var import_jsx_runtime36 = require("nativewind/jsx-runtime");
3902
4958
  var DEFAULT_TAB_BAR_HEIGHT = 65;
3903
4959
  function BottomTabBar({
3904
4960
  state,
@@ -3920,11 +4976,11 @@ function BottomTabBar({
3920
4976
  const inactiveColor = inactiveTintColor || colors.textMuted;
3921
4977
  const backgroundColor = style?.backgroundColor || colors.card;
3922
4978
  const borderTopColor = colors.divider;
3923
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
3924
- import_react_native24.View,
4979
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4980
+ import_react_native27.View,
3925
4981
  {
3926
4982
  style: [
3927
- styles12.container,
4983
+ styles13.container,
3928
4984
  { borderTopColor },
3929
4985
  { backgroundColor, height: height + insets.bottom, paddingBottom: insets.bottom },
3930
4986
  style
@@ -3955,8 +5011,8 @@ function BottomTabBar({
3955
5011
  size: 24
3956
5012
  }) : null;
3957
5013
  const badge = options.tabBarBadge;
3958
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
3959
- import_react_native24.TouchableOpacity,
5014
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
5015
+ import_react_native27.TouchableOpacity,
3960
5016
  {
3961
5017
  accessibilityRole: "button",
3962
5018
  accessibilityState: isFocused ? { selected: true } : {},
@@ -3965,21 +5021,21 @@ function BottomTabBar({
3965
5021
  onPress,
3966
5022
  onLongPress,
3967
5023
  style: [
3968
- styles12.tab,
5024
+ styles13.tab,
3969
5025
  {
3970
5026
  backgroundColor: isFocused ? activeBackgroundColor : inactiveBackgroundColor
3971
5027
  }
3972
5028
  ],
3973
5029
  children: [
3974
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_react_native24.View, { style: [styles12.iconContainer, iconStyle], children: [
5030
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_react_native27.View, { style: [styles13.iconContainer, iconStyle], children: [
3975
5031
  iconName,
3976
- badge != null && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_react_native24.View, { style: [styles12.badge, { backgroundColor: activeColor }], children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(AppText, { style: styles12.badgeText, children: typeof badge === "number" && badge > 99 ? "99+" : badge }) })
5032
+ badge != null && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_react_native27.View, { style: [styles13.badge, { backgroundColor: activeColor }], children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(AppText, { style: styles13.badgeText, children: typeof badge === "number" && badge > 99 ? "99+" : badge }) })
3977
5033
  ] }),
3978
- showLabel && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
5034
+ showLabel && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
3979
5035
  AppText,
3980
5036
  {
3981
5037
  style: [
3982
- styles12.label,
5038
+ styles13.label,
3983
5039
  { color: isFocused ? activeColor : inactiveColor },
3984
5040
  labelStyle
3985
5041
  ],
@@ -3995,7 +5051,7 @@ function BottomTabBar({
3995
5051
  }
3996
5052
  );
3997
5053
  }
3998
- var styles12 = import_react_native24.StyleSheet.create({
5054
+ var styles13 = import_react_native27.StyleSheet.create({
3999
5055
  container: {
4000
5056
  flexDirection: "row",
4001
5057
  borderTopWidth: 0.5,
@@ -4038,7 +5094,7 @@ var styles12 = import_react_native24.StyleSheet.create({
4038
5094
  });
4039
5095
 
4040
5096
  // src/navigation/navigators/TabNavigator.tsx
4041
- var import_jsx_runtime34 = require("nativewind/jsx-runtime");
5097
+ var import_jsx_runtime37 = require("nativewind/jsx-runtime");
4042
5098
  var NativeTab = (0, import_bottom_tabs.createBottomTabNavigator)();
4043
5099
  var defaultScreenOptions2 = {
4044
5100
  headerShown: false,
@@ -4051,7 +5107,7 @@ function TabNavigator({
4051
5107
  screenOptions,
4052
5108
  children
4053
5109
  }) {
4054
- const mergedScreenOptions = import_react34.default.useMemo(() => {
5110
+ const mergedScreenOptions = import_react38.default.useMemo(() => {
4055
5111
  const options = { ...defaultScreenOptions2, ...screenOptions };
4056
5112
  if (tabBarOptions) {
4057
5113
  if (tabBarOptions.showLabel !== void 0) {
@@ -4084,9 +5140,9 @@ function TabNavigator({
4084
5140
  }
4085
5141
  return options;
4086
5142
  }, [tabBarOptions, screenOptions]);
4087
- const resolvedTabBar = import_react34.default.useMemo(() => {
5143
+ const resolvedTabBar = import_react38.default.useMemo(() => {
4088
5144
  if (tabBar) return tabBar;
4089
- return (props) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
5145
+ return (props) => /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4090
5146
  BottomTabBar,
4091
5147
  {
4092
5148
  ...props,
@@ -4113,7 +5169,7 @@ function TabNavigator({
4113
5169
  tabBarOptions?.style,
4114
5170
  tabBarOptions?.height
4115
5171
  ]);
4116
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
5172
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4117
5173
  NativeTab.Navigator,
4118
5174
  {
4119
5175
  initialRouteName,
@@ -4125,7 +5181,7 @@ function TabNavigator({
4125
5181
  }
4126
5182
  TabNavigator.Screen = NativeTab.Screen;
4127
5183
  function createTabScreens(routes) {
4128
- return routes.map((route) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
5184
+ return routes.map((route) => /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4129
5185
  TabNavigator.Screen,
4130
5186
  {
4131
5187
  name: route.name,
@@ -4138,9 +5194,9 @@ function createTabScreens(routes) {
4138
5194
  }
4139
5195
 
4140
5196
  // src/navigation/navigators/DrawerNavigator.tsx
4141
- var import_react35 = __toESM(require("react"));
5197
+ var import_react39 = __toESM(require("react"));
4142
5198
  var import_drawer = require("@react-navigation/drawer");
4143
- var import_jsx_runtime35 = require("nativewind/jsx-runtime");
5199
+ var import_jsx_runtime38 = require("nativewind/jsx-runtime");
4144
5200
  var NativeDrawer = (0, import_drawer.createDrawerNavigator)();
4145
5201
  function DrawerNavigator({
4146
5202
  initialRouteName,
@@ -4151,7 +5207,7 @@ function DrawerNavigator({
4151
5207
  }) {
4152
5208
  const { theme, isDark } = useTheme();
4153
5209
  const navigationTheme = createNavigationTheme(theme, isDark);
4154
- const mergedScreenOptions = import_react35.default.useMemo(() => {
5210
+ const mergedScreenOptions = import_react39.default.useMemo(() => {
4155
5211
  return {
4156
5212
  headerShown: false,
4157
5213
  drawerStyle: {
@@ -4170,7 +5226,7 @@ function DrawerNavigator({
4170
5226
  ...screenOptions
4171
5227
  };
4172
5228
  }, [screenOptions, drawerOptions, navigationTheme, theme]);
4173
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
5229
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
4174
5230
  NativeDrawer.Navigator,
4175
5231
  {
4176
5232
  initialRouteName,
@@ -4182,7 +5238,7 @@ function DrawerNavigator({
4182
5238
  }
4183
5239
  DrawerNavigator.Screen = NativeDrawer.Screen;
4184
5240
  function createDrawerScreens(routes) {
4185
- return routes.map((route) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
5241
+ return routes.map((route) => /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
4186
5242
  DrawerNavigator.Screen,
4187
5243
  {
4188
5244
  name: route.name,
@@ -4195,16 +5251,16 @@ function createDrawerScreens(routes) {
4195
5251
  }
4196
5252
 
4197
5253
  // src/navigation/components/AppHeader.tsx
4198
- var import_react_native30 = require("react-native");
5254
+ var import_react_native33 = require("react-native");
4199
5255
  var import_react_native_safe_area_context4 = require("react-native-safe-area-context");
4200
5256
 
4201
5257
  // src/overlay/AppProvider.tsx
4202
5258
  var import_react_native_safe_area_context3 = require("react-native-safe-area-context");
4203
5259
 
4204
5260
  // src/overlay/AppStatusBar.tsx
4205
- var import_react_native25 = require("react-native");
5261
+ var import_react_native28 = require("react-native");
4206
5262
  var import_native2 = require("@react-navigation/native");
4207
- var import_jsx_runtime36 = require("nativewind/jsx-runtime");
5263
+ var import_jsx_runtime39 = require("nativewind/jsx-runtime");
4208
5264
  function AppStatusBar({
4209
5265
  barStyle = "auto",
4210
5266
  backgroundColor,
@@ -4214,8 +5270,8 @@ function AppStatusBar({
4214
5270
  const { theme, isDark } = useTheme();
4215
5271
  const resolvedBarStyle = barStyle === "auto" ? isDark ? "light-content" : "dark-content" : barStyle;
4216
5272
  const resolvedBackgroundColor = backgroundColor ?? (translucent ? "transparent" : theme.colors.background?.[500] || "#ffffff");
4217
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4218
- import_react_native25.StatusBar,
5273
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
5274
+ import_react_native28.StatusBar,
4219
5275
  {
4220
5276
  barStyle: resolvedBarStyle,
4221
5277
  backgroundColor: resolvedBackgroundColor,
@@ -4227,31 +5283,58 @@ function AppStatusBar({
4227
5283
  function AppFocusedStatusBar(props) {
4228
5284
  const isFocused = (0, import_native2.useIsFocused)();
4229
5285
  if (!isFocused) return null;
4230
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(AppStatusBar, { ...props });
5286
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AppStatusBar, { ...props });
4231
5287
  }
4232
5288
 
4233
5289
  // src/overlay/loading/provider.tsx
4234
- var import_react37 = require("react");
5290
+ var import_react42 = require("react");
4235
5291
 
4236
5292
  // src/overlay/loading/context.ts
4237
- var import_react36 = require("react");
4238
- var LoadingContext = (0, import_react36.createContext)(null);
5293
+ var import_react40 = require("react");
5294
+ var LoadingContext = (0, import_react40.createContext)(null);
4239
5295
  function useLoadingContext() {
4240
- const ctx = (0, import_react36.useContext)(LoadingContext);
5296
+ const ctx = (0, import_react40.useContext)(LoadingContext);
4241
5297
  if (!ctx) throw new Error("useLoading must be used within OverlayProvider");
4242
5298
  return ctx;
4243
5299
  }
4244
5300
 
4245
5301
  // src/overlay/loading/component.tsx
4246
- var import_react_native26 = require("react-native");
4247
- var import_jsx_runtime37 = require("nativewind/jsx-runtime");
4248
- function LoadingModal({ visible, text }) {
4249
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_react_native26.Modal, { transparent: true, visible, animationType: "fade", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_react_native26.View, { style: styles13.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_react_native26.View, { style: [styles13.loadingBox, { backgroundColor: "rgba(0,0,0,0.8)" }], children: [
4250
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_react_native26.ActivityIndicator, { size: "large", color: "#fff" }),
4251
- text && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(AppText, { className: "text-white mt-3 text-sm", children: text })
5302
+ var import_react41 = require("react");
5303
+ var import_react_native29 = require("react-native");
5304
+ var import_jsx_runtime40 = require("nativewind/jsx-runtime");
5305
+ var LOADING_CLOSE_DELAY2 = 3e4;
5306
+ function LoadingModal({
5307
+ visible,
5308
+ text,
5309
+ onRequestClose
5310
+ }) {
5311
+ const [showCloseButton, setShowCloseButton] = (0, import_react41.useState)(false);
5312
+ (0, import_react41.useEffect)(() => {
5313
+ if (!visible) {
5314
+ setShowCloseButton(false);
5315
+ return;
5316
+ }
5317
+ setShowCloseButton(false);
5318
+ const timer = setTimeout(() => {
5319
+ setShowCloseButton(true);
5320
+ }, LOADING_CLOSE_DELAY2);
5321
+ return () => clearTimeout(timer);
5322
+ }, [visible]);
5323
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_react_native29.Modal, { transparent: true, visible, animationType: "fade", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_react_native29.View, { style: styles14.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_react_native29.View, { style: [styles14.loadingBox, { backgroundColor: "rgba(0,0,0,0.8)" }], children: [
5324
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_react_native29.ActivityIndicator, { size: "large", color: "#fff" }),
5325
+ text && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(AppText, { className: "text-white mt-3 text-sm", children: text }),
5326
+ showCloseButton && onRequestClose && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
5327
+ AppPressable,
5328
+ {
5329
+ testID: "loading-close",
5330
+ className: "mt-3 p-1",
5331
+ onPress: onRequestClose,
5332
+ children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Icon, { name: "close", size: "md", color: "#ffffff" })
5333
+ }
5334
+ )
4252
5335
  ] }) }) });
4253
5336
  }
4254
- var styles13 = import_react_native26.StyleSheet.create({
5337
+ var styles14 = import_react_native29.StyleSheet.create({
4255
5338
  overlay: {
4256
5339
  flex: 1,
4257
5340
  backgroundColor: "rgba(0,0,0,0.5)",
@@ -4267,55 +5350,112 @@ var styles13 = import_react_native26.StyleSheet.create({
4267
5350
  });
4268
5351
 
4269
5352
  // src/overlay/loading/provider.tsx
4270
- var import_jsx_runtime38 = require("nativewind/jsx-runtime");
5353
+ var import_jsx_runtime41 = require("nativewind/jsx-runtime");
5354
+ var MIN_VISIBLE_DURATION = 500;
4271
5355
  function LoadingProvider({ children }) {
4272
- const [state, setState] = (0, import_react37.useState)({ visible: false });
4273
- const show = (0, import_react37.useCallback)((text) => {
4274
- setState({ visible: true, text });
5356
+ const [state, setState] = (0, import_react42.useState)({ visible: false });
5357
+ const shownAtRef = (0, import_react42.useRef)(0);
5358
+ const pendingCountRef = (0, import_react42.useRef)(0);
5359
+ const hideTimerRef = (0, import_react42.useRef)(null);
5360
+ const clearHideTimer = (0, import_react42.useCallback)(() => {
5361
+ if (!hideTimerRef.current) return;
5362
+ clearTimeout(hideTimerRef.current);
5363
+ hideTimerRef.current = null;
4275
5364
  }, []);
4276
- const hide = (0, import_react37.useCallback)(() => {
4277
- setState({ visible: false });
4278
- }, []);
4279
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(LoadingContext.Provider, { value: { show, hide }, children: [
5365
+ const show = (0, import_react42.useCallback)((text) => {
5366
+ pendingCountRef.current += 1;
5367
+ clearHideTimer();
5368
+ setState((previous) => {
5369
+ if (!previous.visible) {
5370
+ shownAtRef.current = Date.now();
5371
+ }
5372
+ return { visible: true, text };
5373
+ });
5374
+ }, [clearHideTimer]);
5375
+ const hide = (0, import_react42.useCallback)(() => {
5376
+ pendingCountRef.current = Math.max(0, pendingCountRef.current - 1);
5377
+ if (pendingCountRef.current > 0) return;
5378
+ const elapsed = Date.now() - shownAtRef.current;
5379
+ const remaining = Math.max(0, MIN_VISIBLE_DURATION - elapsed);
5380
+ clearHideTimer();
5381
+ if (remaining === 0) {
5382
+ setState({ visible: false });
5383
+ return;
5384
+ }
5385
+ hideTimerRef.current = setTimeout(() => {
5386
+ hideTimerRef.current = null;
5387
+ setState({ visible: false });
5388
+ }, remaining);
5389
+ }, [clearHideTimer]);
5390
+ (0, import_react42.useEffect)(() => () => clearHideTimer(), [clearHideTimer]);
5391
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(LoadingContext.Provider, { value: { show, hide }, children: [
4280
5392
  children,
4281
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(LoadingModal, { ...state })
5393
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(LoadingModal, { ...state, onRequestClose: hide })
4282
5394
  ] });
4283
5395
  }
4284
5396
 
4285
5397
  // src/overlay/toast/provider.tsx
4286
- var import_react40 = require("react");
4287
- var import_react_native28 = require("react-native");
5398
+ var import_react45 = require("react");
5399
+ var import_react_native31 = require("react-native");
4288
5400
 
4289
5401
  // src/overlay/toast/context.ts
4290
- var import_react38 = require("react");
4291
- var ToastContext = (0, import_react38.createContext)(null);
5402
+ var import_react43 = require("react");
5403
+ var ToastContext = (0, import_react43.createContext)(null);
4292
5404
  function useToastContext() {
4293
- const ctx = (0, import_react38.useContext)(ToastContext);
5405
+ const ctx = (0, import_react43.useContext)(ToastContext);
4294
5406
  if (!ctx) throw new Error("useToast must be used within OverlayProvider");
4295
5407
  return ctx;
4296
5408
  }
4297
5409
 
4298
5410
  // src/overlay/toast/component.tsx
4299
- var import_react39 = require("react");
4300
- var import_react_native27 = require("react-native");
4301
- var import_jsx_runtime39 = require("nativewind/jsx-runtime");
5411
+ var import_react44 = require("react");
5412
+ var import_react_native30 = require("react-native");
5413
+ var import_jsx_runtime42 = require("nativewind/jsx-runtime");
5414
+ function createAnimatedValue4(value) {
5415
+ const AnimatedValue = import_react_native30.Animated.Value;
5416
+ try {
5417
+ return new AnimatedValue(value);
5418
+ } catch {
5419
+ return AnimatedValue(value);
5420
+ }
5421
+ }
4302
5422
  function ToastItemView({ message, type, onHide }) {
4303
- const fadeAnim = (0, import_react39.useRef)(new import_react_native27.Animated.Value(0)).current;
4304
- (0, import_react39.useEffect)(() => {
4305
- import_react_native27.Animated.sequence([
4306
- import_react_native27.Animated.timing(fadeAnim, { toValue: 1, duration: 200, useNativeDriver: true }),
4307
- import_react_native27.Animated.delay(2500),
4308
- import_react_native27.Animated.timing(fadeAnim, { toValue: 0, duration: 200, useNativeDriver: true })
5423
+ const fadeAnim = (0, import_react44.useRef)(createAnimatedValue4(0)).current;
5424
+ const { theme } = useOptionalTheme();
5425
+ (0, import_react44.useEffect)(() => {
5426
+ import_react_native30.Animated.sequence([
5427
+ import_react_native30.Animated.timing(fadeAnim, { toValue: 1, duration: 200, useNativeDriver: true }),
5428
+ import_react_native30.Animated.delay(2500),
5429
+ import_react_native30.Animated.timing(fadeAnim, { toValue: 0, duration: 200, useNativeDriver: true })
4309
5430
  ]).start(onHide);
4310
5431
  }, []);
4311
- const bgColors = {
4312
- success: "bg-success-500",
4313
- error: "bg-error-500",
4314
- warning: "bg-warning-500",
4315
- info: "bg-primary-500"
5432
+ const palette = {
5433
+ success: {
5434
+ backgroundColor: theme.colors.success?.[500] || "#22c55e",
5435
+ textColor: "#ffffff"
5436
+ },
5437
+ error: {
5438
+ backgroundColor: theme.colors.error?.[500] || "#ef4444",
5439
+ textColor: "#ffffff"
5440
+ },
5441
+ warning: {
5442
+ backgroundColor: theme.colors.warning?.[500] || "#f59e0b",
5443
+ textColor: "#111827"
5444
+ },
5445
+ info: {
5446
+ backgroundColor: theme.colors.info?.[500] || theme.colors.primary?.[500] || "#3b82f6",
5447
+ textColor: "#ffffff"
5448
+ }
4316
5449
  };
4317
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
4318
- import_react_native27.Animated.View,
5450
+ const currentPalette = palette[type];
5451
+ const bgStyles = {
5452
+ success: "bg-green-500",
5453
+ error: "bg-red-500",
5454
+ warning: "bg-yellow-500",
5455
+ info: "bg-blue-500"
5456
+ };
5457
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
5458
+ import_react_native30.Animated.View,
4319
5459
  {
4320
5460
  style: {
4321
5461
  opacity: fadeAnim,
@@ -4328,17 +5468,25 @@ function ToastItemView({ message, type, onHide }) {
4328
5468
  }
4329
5469
  ]
4330
5470
  },
4331
- children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AppView, { className: `${bgColors[type]} px-4 py-3 rounded-lg mb-2 mx-4 shadow-lg`, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AppText, { className: "text-white text-center", children: message }) })
5471
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
5472
+ AppView,
5473
+ {
5474
+ testID: `toast-item-${type}`,
5475
+ className: `${bgStyles[type]} px-4 py-3 rounded-lg mb-2 mx-4 shadow-lg`,
5476
+ style: { backgroundColor: currentPalette.backgroundColor },
5477
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(AppText, { className: "text-center", style: { color: currentPalette.textColor }, children: message })
5478
+ }
5479
+ )
4332
5480
  }
4333
5481
  );
4334
5482
  }
4335
5483
 
4336
5484
  // src/overlay/toast/provider.tsx
4337
- var import_jsx_runtime40 = require("nativewind/jsx-runtime");
5485
+ var import_jsx_runtime43 = require("nativewind/jsx-runtime");
4338
5486
  function ToastProvider({ children }) {
4339
- const [toasts, setToasts] = (0, import_react40.useState)([]);
4340
- const timersRef = (0, import_react40.useRef)(/* @__PURE__ */ new Map());
4341
- const remove = (0, import_react40.useCallback)((id) => {
5487
+ const [toasts, setToasts] = (0, import_react45.useState)([]);
5488
+ const timersRef = (0, import_react45.useRef)(/* @__PURE__ */ new Map());
5489
+ const remove = (0, import_react45.useCallback)((id) => {
4342
5490
  setToasts((prev) => prev.filter((t) => t.id !== id));
4343
5491
  const timer = timersRef.current.get(id);
4344
5492
  if (timer) {
@@ -4346,7 +5494,7 @@ function ToastProvider({ children }) {
4346
5494
  timersRef.current.delete(id);
4347
5495
  }
4348
5496
  }, []);
4349
- const show = (0, import_react40.useCallback)(
5497
+ const show = (0, import_react45.useCallback)(
4350
5498
  (message, type = "info", duration = 3e3) => {
4351
5499
  const id = Math.random().toString(36).substring(7);
4352
5500
  const toast = { id, message, type, duration };
@@ -4356,28 +5504,28 @@ function ToastProvider({ children }) {
4356
5504
  },
4357
5505
  [remove]
4358
5506
  );
4359
- const success = (0, import_react40.useCallback)(
5507
+ const success = (0, import_react45.useCallback)(
4360
5508
  (message, duration) => show(message, "success", duration),
4361
5509
  [show]
4362
5510
  );
4363
- const error = (0, import_react40.useCallback)(
5511
+ const error = (0, import_react45.useCallback)(
4364
5512
  (message, duration) => show(message, "error", duration),
4365
5513
  [show]
4366
5514
  );
4367
- const info = (0, import_react40.useCallback)(
5515
+ const info = (0, import_react45.useCallback)(
4368
5516
  (message, duration) => show(message, "info", duration),
4369
5517
  [show]
4370
5518
  );
4371
- const warning = (0, import_react40.useCallback)(
5519
+ const warning = (0, import_react45.useCallback)(
4372
5520
  (message, duration) => show(message, "warning", duration),
4373
5521
  [show]
4374
5522
  );
4375
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(ToastContext.Provider, { value: { show, success, error, info, warning }, children: [
5523
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(ToastContext.Provider, { value: { show, success, error, info, warning }, children: [
4376
5524
  children,
4377
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_react_native28.View, { style: styles14.toastContainer, pointerEvents: "none", children: toasts.map((toast) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ToastItemView, { ...toast, onHide: () => remove(toast.id) }, toast.id)) })
5525
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_react_native31.View, { style: styles15.toastContainer, pointerEvents: "none", children: toasts.map((toast) => /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(ToastItemView, { ...toast, onHide: () => remove(toast.id) }, toast.id)) })
4378
5526
  ] });
4379
5527
  }
4380
- var styles14 = import_react_native28.StyleSheet.create({
5528
+ var styles15 = import_react_native31.StyleSheet.create({
4381
5529
  toastContainer: {
4382
5530
  position: "absolute",
4383
5531
  top: 60,
@@ -4388,20 +5536,29 @@ var styles14 = import_react_native28.StyleSheet.create({
4388
5536
  });
4389
5537
 
4390
5538
  // src/overlay/alert/provider.tsx
4391
- var import_react42 = require("react");
5539
+ var import_react48 = require("react");
4392
5540
 
4393
5541
  // src/overlay/alert/context.ts
4394
- var import_react41 = require("react");
4395
- var AlertContext = (0, import_react41.createContext)(null);
5542
+ var import_react46 = require("react");
5543
+ var AlertContext = (0, import_react46.createContext)(null);
4396
5544
  function useAlertContext() {
4397
- const ctx = (0, import_react41.useContext)(AlertContext);
5545
+ const ctx = (0, import_react46.useContext)(AlertContext);
4398
5546
  if (!ctx) throw new Error("useAlert must be used within OverlayProvider");
4399
5547
  return ctx;
4400
5548
  }
4401
5549
 
4402
5550
  // src/overlay/alert/component.tsx
4403
- var import_react_native29 = require("react-native");
4404
- var import_jsx_runtime41 = require("nativewind/jsx-runtime");
5551
+ var import_react47 = require("react");
5552
+ var import_react_native32 = require("react-native");
5553
+ var import_jsx_runtime44 = require("nativewind/jsx-runtime");
5554
+ function createAnimatedValue5(value) {
5555
+ const AnimatedValue = import_react_native32.Animated.Value;
5556
+ try {
5557
+ return new AnimatedValue(value);
5558
+ } catch {
5559
+ return AnimatedValue(value);
5560
+ }
5561
+ }
4405
5562
  function AlertModal({
4406
5563
  visible,
4407
5564
  title,
@@ -4412,23 +5569,67 @@ function AlertModal({
4412
5569
  onConfirm,
4413
5570
  onCancel
4414
5571
  }) {
5572
+ const progress = (0, import_react47.useRef)(createAnimatedValue5(0)).current;
5573
+ (0, import_react47.useEffect)(() => {
5574
+ if (!visible) {
5575
+ progress.setValue(0);
5576
+ return;
5577
+ }
5578
+ progress.setValue(0);
5579
+ import_react_native32.Animated.timing(progress, {
5580
+ toValue: 1,
5581
+ duration: 220,
5582
+ useNativeDriver: true
5583
+ }).start();
5584
+ }, [progress, visible]);
4415
5585
  if (!visible) return null;
4416
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_react_native29.Modal, { transparent: true, visible: true, animationType: "fade", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_react_native29.View, { style: styles15.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_react_native29.View, { style: styles15.alertBox, children: [
4417
- title && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(AppText, { className: "text-lg font-semibold text-center mb-2", children: title }),
4418
- message && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(AppText, { className: "text-gray-600 text-center mb-4", children: message }),
4419
- /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(AppView, { row: true, gap: 3, className: "mt-2", children: [
4420
- showCancel && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(AppPressable, { onPress: onCancel, className: "flex-1 py-3 bg-gray-100 rounded-lg", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(AppText, { className: "text-center text-gray-700", children: cancelText || "\u53D6\u6D88" }) }),
4421
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(AppPressable, { onPress: onConfirm, className: "flex-1 py-3 bg-primary-500 rounded-lg", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(AppText, { className: "text-center text-white", children: confirmText || "\u786E\u5B9A" }) })
4422
- ] })
4423
- ] }) }) });
5586
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_react_native32.Modal, { transparent: true, visible: true, animationType: "none", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_react_native32.View, { style: styles16.container, children: [
5587
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_react_native32.Animated.View, { style: [styles16.overlay, { opacity: progress }] }),
5588
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
5589
+ import_react_native32.Animated.View,
5590
+ {
5591
+ style: [
5592
+ styles16.alertBox,
5593
+ {
5594
+ opacity: progress,
5595
+ transform: [
5596
+ {
5597
+ translateY: progress.interpolate({
5598
+ inputRange: [0, 1],
5599
+ outputRange: [16, 0]
5600
+ })
5601
+ },
5602
+ {
5603
+ scale: progress.interpolate({
5604
+ inputRange: [0, 1],
5605
+ outputRange: [0.96, 1]
5606
+ })
5607
+ }
5608
+ ]
5609
+ }
5610
+ ],
5611
+ children: [
5612
+ title && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(AppText, { className: "text-lg font-semibold text-center mb-2", children: title }),
5613
+ message && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(AppText, { className: "text-gray-600 text-center mb-4", children: message }),
5614
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(AppView, { row: true, gap: 3, className: "mt-2", children: [
5615
+ showCancel && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(AppPressable, { onPress: onCancel, className: "flex-1 py-3 bg-gray-100 rounded-lg", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(AppText, { className: "text-center text-gray-700", children: cancelText || "\u53D6\u6D88" }) }),
5616
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(AppPressable, { onPress: onConfirm, className: "flex-1 py-3 bg-primary-500 rounded-lg", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(AppText, { className: "text-center text-white", children: confirmText || "\u786E\u5B9A" }) })
5617
+ ] })
5618
+ ]
5619
+ }
5620
+ )
5621
+ ] }) });
4424
5622
  }
4425
- var styles15 = import_react_native29.StyleSheet.create({
4426
- overlay: {
5623
+ var styles16 = import_react_native32.StyleSheet.create({
5624
+ container: {
4427
5625
  flex: 1,
4428
- backgroundColor: "rgba(0,0,0,0.5)",
4429
5626
  justifyContent: "center",
4430
5627
  alignItems: "center"
4431
5628
  },
5629
+ overlay: {
5630
+ ...import_react_native32.StyleSheet.absoluteFillObject,
5631
+ backgroundColor: "rgba(0,0,0,0.5)"
5632
+ },
4432
5633
  alertBox: {
4433
5634
  backgroundColor: "white",
4434
5635
  borderRadius: 12,
@@ -4444,32 +5645,32 @@ var styles15 = import_react_native29.StyleSheet.create({
4444
5645
  });
4445
5646
 
4446
5647
  // src/overlay/alert/provider.tsx
4447
- var import_jsx_runtime42 = require("nativewind/jsx-runtime");
5648
+ var import_jsx_runtime45 = require("nativewind/jsx-runtime");
4448
5649
  function AlertProvider({ children }) {
4449
- const [alert, setAlert] = (0, import_react42.useState)(null);
4450
- const showAlert = (0, import_react42.useCallback)((options) => {
5650
+ const [alert, setAlert] = (0, import_react48.useState)(null);
5651
+ const showAlert = (0, import_react48.useCallback)((options) => {
4451
5652
  setAlert({ ...options, visible: true });
4452
5653
  }, []);
4453
- const confirm = (0, import_react42.useCallback)(
5654
+ const confirm = (0, import_react48.useCallback)(
4454
5655
  (options) => {
4455
5656
  showAlert({ ...options, showCancel: true });
4456
5657
  },
4457
5658
  [showAlert]
4458
5659
  );
4459
- const hide = (0, import_react42.useCallback)(() => {
5660
+ const hide = (0, import_react48.useCallback)(() => {
4460
5661
  setAlert(null);
4461
5662
  }, []);
4462
- const handleConfirm = (0, import_react42.useCallback)(() => {
5663
+ const handleConfirm = (0, import_react48.useCallback)(() => {
4463
5664
  alert?.onConfirm?.();
4464
5665
  hide();
4465
5666
  }, [alert, hide]);
4466
- const handleCancel = (0, import_react42.useCallback)(() => {
5667
+ const handleCancel = (0, import_react48.useCallback)(() => {
4467
5668
  alert?.onCancel?.();
4468
5669
  hide();
4469
5670
  }, [alert, hide]);
4470
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(AlertContext.Provider, { value: { alert: showAlert, confirm }, children: [
5671
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(AlertContext.Provider, { value: { alert: showAlert, confirm }, children: [
4471
5672
  children,
4472
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
5673
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
4473
5674
  AlertModal,
4474
5675
  {
4475
5676
  visible: alert?.visible ?? false,
@@ -4485,14 +5686,378 @@ function AlertProvider({ children }) {
4485
5686
  ] });
4486
5687
  }
4487
5688
 
5689
+ // src/overlay/logger/provider.tsx
5690
+ var import_react51 = require("react");
5691
+
5692
+ // src/overlay/logger/context.ts
5693
+ var import_react49 = require("react");
5694
+ var LoggerContext = (0, import_react49.createContext)(null);
5695
+ function useLoggerContext() {
5696
+ const ctx = (0, import_react49.useContext)(LoggerContext);
5697
+ if (!ctx) throw new Error("useLogger must be used within LoggerProvider");
5698
+ return ctx;
5699
+ }
5700
+
5701
+ // src/overlay/logger/component.tsx
5702
+ var import_react50 = require("react");
5703
+ var import_jsx_runtime46 = require("nativewind/jsx-runtime");
5704
+ var FILTERS = ["all", "error", "warn", "info", "debug"];
5705
+ var ALL_NAMESPACE = "all";
5706
+ var DEFAULT_SEARCH_PLACEHOLDER = "\u641C\u7D22\u65E5\u5FD7";
5707
+ function withAlpha(color, alpha = "20") {
5708
+ return color.startsWith("#") && color.length === 7 ? `${color}${alpha}` : color;
5709
+ }
5710
+ function matchesSearch(entry, keyword) {
5711
+ if (!keyword.trim()) return true;
5712
+ const normalized = keyword.trim().toLowerCase();
5713
+ const detail = stringifyLogData(entry.data).toLowerCase();
5714
+ return [entry.message, entry.namespace ?? "", detail].join(" ").toLowerCase().includes(normalized);
5715
+ }
5716
+ function LogOverlay({
5717
+ entries,
5718
+ onClear,
5719
+ defaultExpanded = false,
5720
+ exportEnabled = true,
5721
+ onExport
5722
+ }) {
5723
+ const colors = useThemeColors();
5724
+ const [expanded, setExpanded] = (0, import_react50.useState)(defaultExpanded);
5725
+ const [filter, setFilter] = (0, import_react50.useState)("all");
5726
+ const [namespaceFilter, setNamespaceFilter] = (0, import_react50.useState)(ALL_NAMESPACE);
5727
+ const [keyword, setKeyword] = (0, import_react50.useState)("");
5728
+ const namespaces = (0, import_react50.useMemo)(
5729
+ () => [
5730
+ ALL_NAMESPACE,
5731
+ ...Array.from(
5732
+ new Set(
5733
+ entries.map((entry) => entry.namespace).filter((value) => Boolean(value))
5734
+ )
5735
+ )
5736
+ ],
5737
+ [entries]
5738
+ );
5739
+ const filteredEntries = (0, import_react50.useMemo)(() => {
5740
+ return entries.filter((entry) => {
5741
+ const levelMatched = filter === "all" ? true : entry.level === filter;
5742
+ const namespaceMatched = namespaceFilter === ALL_NAMESPACE ? true : entry.namespace === namespaceFilter;
5743
+ const searchMatched = matchesSearch(entry, keyword);
5744
+ return levelMatched && namespaceMatched && searchMatched;
5745
+ });
5746
+ }, [entries, filter, keyword, namespaceFilter]);
5747
+ const levelStyles = {
5748
+ debug: { text: colors.muted, bg: colors.cardElevated },
5749
+ info: { text: colors.info, bg: withAlpha(colors.info) },
5750
+ warn: { text: colors.warning, bg: withAlpha(colors.warning) },
5751
+ error: { text: colors.error, bg: withAlpha(colors.error) }
5752
+ };
5753
+ const handleExport = () => {
5754
+ const payload = {
5755
+ entries: filteredEntries,
5756
+ serialized: serializeLogEntries(filteredEntries)
5757
+ };
5758
+ if (onExport) {
5759
+ onExport(payload);
5760
+ return;
5761
+ }
5762
+ console.info("[LoggerExport]", payload.serialized);
5763
+ };
5764
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(AppView, { pointerEvents: "box-none", style: { position: "absolute", right: 12, bottom: 24, left: 12, zIndex: 9998 }, children: [
5765
+ expanded && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
5766
+ AppView,
5767
+ {
5768
+ testID: "logger-overlay-panel",
5769
+ style: {
5770
+ maxHeight: 360,
5771
+ borderRadius: 16,
5772
+ backgroundColor: colors.card,
5773
+ borderWidth: 0.5,
5774
+ borderColor: colors.border,
5775
+ shadowColor: "#000000",
5776
+ shadowOpacity: 0.15,
5777
+ shadowRadius: 16,
5778
+ shadowOffset: { width: 0, height: 8 },
5779
+ elevation: 12,
5780
+ marginBottom: 12
5781
+ },
5782
+ children: [
5783
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(AppView, { row: true, items: "center", justify: "between", className: "px-4 py-3", style: { borderBottomWidth: 0.5, borderBottomColor: colors.divider }, children: [
5784
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppText, { weight: "semibold", children: "\u5F00\u53D1\u65E5\u5FD7" }),
5785
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(AppView, { row: true, gap: 2, children: [
5786
+ exportEnabled ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppPressable, { testID: "logger-overlay-export", onPress: handleExport, className: "px-3 py-1 rounded-full", style: { backgroundColor: colors.cardElevated }, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppText, { size: "xs", tone: "muted", children: "\u5BFC\u51FA" }) }) : null,
5787
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppPressable, { onPress: onClear, className: "px-3 py-1 rounded-full", style: { backgroundColor: colors.cardElevated }, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppText, { size: "xs", tone: "muted", children: "\u6E05\u7A7A" }) }),
5788
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppPressable, { onPress: () => setExpanded(false), className: "px-3 py-1 rounded-full", style: { backgroundColor: colors.cardElevated }, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppText, { size: "xs", tone: "muted", children: "\u6536\u8D77" }) })
5789
+ ] })
5790
+ ] }),
5791
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, className: "px-3 py-2", contentContainerStyle: { gap: 8 }, children: FILTERS.map((item) => {
5792
+ const active = filter === item;
5793
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
5794
+ AppPressable,
5795
+ {
5796
+ onPress: () => setFilter(item),
5797
+ className: "px-3 py-1 rounded-full",
5798
+ style: { backgroundColor: active ? colors.primary : colors.cardElevated },
5799
+ children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppText, { size: "xs", style: { color: active ? colors.textInverse : colors.textSecondary }, children: item.toUpperCase() })
5800
+ },
5801
+ item
5802
+ );
5803
+ }) }),
5804
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, className: "px-3 pb-2", contentContainerStyle: { gap: 8 }, children: namespaces.map((item) => {
5805
+ const active = namespaceFilter === item;
5806
+ const label = item === ALL_NAMESPACE ? "\u5168\u90E8\u6A21\u5757" : String(item);
5807
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
5808
+ AppPressable,
5809
+ {
5810
+ testID: `logger-namespace-${item}`,
5811
+ onPress: () => setNamespaceFilter(item),
5812
+ className: "px-3 py-1 rounded-full",
5813
+ style: { backgroundColor: active ? colors.info : colors.cardElevated },
5814
+ children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppText, { size: "xs", style: { color: active ? colors.textInverse : colors.textSecondary }, children: label })
5815
+ },
5816
+ item
5817
+ );
5818
+ }) }),
5819
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppView, { className: "px-3 pb-2", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
5820
+ AppInput,
5821
+ {
5822
+ placeholder: DEFAULT_SEARCH_PLACEHOLDER,
5823
+ value: keyword,
5824
+ onChangeText: setKeyword
5825
+ }
5826
+ ) }),
5827
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppScrollView, { className: "px-3 pb-3", showsVerticalScrollIndicator: false, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppView, { gap: 2, children: filteredEntries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppView, { className: "py-8 items-center", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppText, { tone: "muted", children: "\u6682\u65E0\u65E5\u5FD7" }) }) : filteredEntries.map((entry) => {
5828
+ const palette = levelStyles[entry.level];
5829
+ const detail = stringifyLogData(entry.data);
5830
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
5831
+ AppView,
5832
+ {
5833
+ className: "px-3 py-3 rounded-xl",
5834
+ style: { backgroundColor: colors.cardElevated, borderWidth: 0.5, borderColor: colors.divider },
5835
+ children: [
5836
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(AppView, { row: true, items: "center", justify: "between", children: [
5837
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(AppView, { row: true, items: "center", gap: 2, children: [
5838
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppView, { className: "px-2 py-1 rounded-full", style: { backgroundColor: palette.bg }, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppText, { size: "xs", style: { color: palette.text }, children: entry.level.toUpperCase() }) }),
5839
+ entry.namespace ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(AppText, { size: "xs", tone: "muted", children: [
5840
+ "[",
5841
+ entry.namespace,
5842
+ "]"
5843
+ ] }) : null
5844
+ ] }),
5845
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppText, { size: "xs", tone: "muted", children: formatLogTime(entry.timestamp) })
5846
+ ] }),
5847
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppText, { className: "mt-2", size: "sm", children: entry.message }),
5848
+ detail ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppView, { className: "mt-2 px-2 py-2 rounded-lg", style: { backgroundColor: colors.background }, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppText, { size: "xs", tone: "muted", children: detail }) }) : null
5849
+ ]
5850
+ },
5851
+ entry.id
5852
+ );
5853
+ }) }) })
5854
+ ]
5855
+ }
5856
+ ),
5857
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppView, { pointerEvents: "box-none", row: true, justify: "end", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
5858
+ AppPressable,
5859
+ {
5860
+ testID: "logger-overlay-toggle",
5861
+ onPress: () => setExpanded((value) => !value),
5862
+ className: "px-4 py-3 rounded-full",
5863
+ style: { backgroundColor: colors.primary, shadowColor: "#000000", shadowOpacity: 0.18, shadowRadius: 12, shadowOffset: { width: 0, height: 4 }, elevation: 8 },
5864
+ children: /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(AppText, { weight: "semibold", style: { color: colors.textInverse }, children: [
5865
+ "Logs ",
5866
+ entries.length
5867
+ ] })
5868
+ }
5869
+ ) })
5870
+ ] });
5871
+ }
5872
+
5873
+ // src/overlay/logger/provider.tsx
5874
+ var import_jsx_runtime47 = require("nativewind/jsx-runtime");
5875
+ function LoggerProvider({
5876
+ children,
5877
+ enabled = false,
5878
+ level = "debug",
5879
+ maxEntries = 200,
5880
+ overlayEnabled = false,
5881
+ defaultExpanded = false,
5882
+ consoleEnabled = true,
5883
+ transports = [],
5884
+ exportEnabled = true,
5885
+ onExport
5886
+ }) {
5887
+ const [entries, setEntries] = (0, import_react51.useState)([]);
5888
+ const clear = (0, import_react51.useCallback)(() => {
5889
+ setEntries([]);
5890
+ }, []);
5891
+ const resolvedTransports = (0, import_react51.useMemo)(() => {
5892
+ const list = [];
5893
+ if (enabled && consoleEnabled) {
5894
+ list.push(createConsoleTransport());
5895
+ }
5896
+ if (transports.length > 0) {
5897
+ list.push(...transports);
5898
+ }
5899
+ return list;
5900
+ }, [consoleEnabled, enabled, transports]);
5901
+ const write = (0, import_react51.useCallback)(
5902
+ (nextLevel, message, data, namespace) => {
5903
+ if (!enabled || !shouldLog(level, nextLevel)) return;
5904
+ const entry = createLogEntry({ level: nextLevel, message, data, namespace, source: "logger" });
5905
+ setEntries((prev) => [entry, ...prev].slice(0, maxEntries));
5906
+ resolvedTransports.forEach((transport) => transport(entry));
5907
+ },
5908
+ [enabled, level, maxEntries, resolvedTransports]
5909
+ );
5910
+ const contextValue = (0, import_react51.useMemo)(
5911
+ () => ({
5912
+ entries,
5913
+ enabled,
5914
+ level,
5915
+ clear,
5916
+ log: write,
5917
+ debug: (message, data, namespace) => write("debug", message, data, namespace),
5918
+ info: (message, data, namespace) => write("info", message, data, namespace),
5919
+ warn: (message, data, namespace) => write("warn", message, data, namespace),
5920
+ error: (message, data, namespace) => write("error", message, data, namespace)
5921
+ }),
5922
+ [clear, enabled, entries, level, write]
5923
+ );
5924
+ (0, import_react51.useEffect)(() => {
5925
+ if (!enabled) {
5926
+ setGlobalLogger(null);
5927
+ return;
5928
+ }
5929
+ setGlobalLogger(contextValue);
5930
+ return () => {
5931
+ setGlobalLogger(null);
5932
+ };
5933
+ }, [contextValue, enabled]);
5934
+ return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(LoggerContext.Provider, { value: contextValue, children: [
5935
+ children,
5936
+ enabled && overlayEnabled ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
5937
+ LogOverlay,
5938
+ {
5939
+ entries,
5940
+ onClear: clear,
5941
+ defaultExpanded,
5942
+ exportEnabled,
5943
+ onExport
5944
+ }
5945
+ ) : null
5946
+ ] });
5947
+ }
5948
+
5949
+ // src/overlay/error-boundary/component.tsx
5950
+ var import_react52 = __toESM(require("react"));
5951
+ var import_jsx_runtime48 = require("nativewind/jsx-runtime");
5952
+ function areResetKeysChanged(prev = [], next = []) {
5953
+ if (prev.length !== next.length) return true;
5954
+ return prev.some((item, index) => !Object.is(item, next[index]));
5955
+ }
5956
+ function DefaultFallback({
5957
+ error,
5958
+ onReset,
5959
+ title = "\u9875\u9762\u53D1\u751F\u5F02\u5E38",
5960
+ description = "\u5DF2\u81EA\u52A8\u6355\u83B7\u6E32\u67D3\u9519\u8BEF\uFF0C\u4F60\u53EF\u4EE5\u91CD\u8BD5\u5E76\u7ED3\u5408\u5F00\u53D1\u65E5\u5FD7\u7EE7\u7EED\u6392\u67E5\u3002",
5961
+ resetText = "\u91CD\u8BD5",
5962
+ showDetails = isDevelopment()
5963
+ }) {
5964
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(AppView, { testID: "app-error-boundary", flex: true, center: true, className: "px-6 py-8", gap: 4, children: [
5965
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(AppView, { className: "items-center", gap: 2, children: [
5966
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(AppText, { size: "xl", weight: "semibold", children: title }),
5967
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(AppText, { tone: "muted", style: { textAlign: "center" }, children: description })
5968
+ ] }),
5969
+ showDetails ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
5970
+ AppView,
5971
+ {
5972
+ className: "w-full px-4 py-3 rounded-xl",
5973
+ style: { maxWidth: 560, borderWidth: 0.5 },
5974
+ children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(AppText, { testID: "app-error-boundary-detail", size: "sm", children: error.message || String(error) })
5975
+ }
5976
+ ) : null,
5977
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
5978
+ AppPressable,
5979
+ {
5980
+ testID: "app-error-boundary-reset",
5981
+ onPress: onReset,
5982
+ className: "px-4 py-3 rounded-lg",
5983
+ style: { borderWidth: 0.5 },
5984
+ children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(AppText, { weight: "semibold", children: resetText })
5985
+ }
5986
+ )
5987
+ ] });
5988
+ }
5989
+ var AppErrorBoundary = class extends import_react52.default.Component {
5990
+ constructor() {
5991
+ super(...arguments);
5992
+ this.state = {
5993
+ error: null
5994
+ };
5995
+ this.handleReset = () => {
5996
+ this.setState({ error: null });
5997
+ this.props.onReset?.();
5998
+ };
5999
+ }
6000
+ static getDerivedStateFromError(error) {
6001
+ return { error };
6002
+ }
6003
+ componentDidCatch(error, errorInfo) {
6004
+ this.context?.error(
6005
+ "React ErrorBoundary \u6355\u83B7\u6E32\u67D3\u5F02\u5E38",
6006
+ {
6007
+ name: error.name,
6008
+ message: error.message,
6009
+ stack: error.stack,
6010
+ componentStack: errorInfo.componentStack
6011
+ },
6012
+ "react"
6013
+ );
6014
+ this.props.onError?.(error, errorInfo);
6015
+ }
6016
+ componentDidUpdate(prevProps) {
6017
+ if (!this.state.error) return;
6018
+ if (areResetKeysChanged(prevProps.resetKeys, this.props.resetKeys)) {
6019
+ this.handleReset();
6020
+ }
6021
+ }
6022
+ render() {
6023
+ const {
6024
+ children,
6025
+ enabled = false,
6026
+ fallback,
6027
+ title,
6028
+ description,
6029
+ showDetails,
6030
+ resetText
6031
+ } = this.props;
6032
+ if (!enabled) return children;
6033
+ if (!this.state.error) return children;
6034
+ if (typeof fallback === "function") {
6035
+ return fallback({ error: this.state.error, reset: this.handleReset });
6036
+ }
6037
+ if (fallback) return fallback;
6038
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
6039
+ DefaultFallback,
6040
+ {
6041
+ error: this.state.error,
6042
+ onReset: this.handleReset,
6043
+ title,
6044
+ description,
6045
+ showDetails,
6046
+ resetText
6047
+ }
6048
+ );
6049
+ }
6050
+ };
6051
+ AppErrorBoundary.contextType = LoggerContext;
6052
+
4488
6053
  // src/overlay/provider.tsx
4489
- var import_jsx_runtime43 = require("nativewind/jsx-runtime");
4490
- function OverlayProvider({ children }) {
4491
- return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(LoadingProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(ToastProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(AlertProvider, { children }) }) });
6054
+ var import_jsx_runtime49 = require("nativewind/jsx-runtime");
6055
+ function OverlayProvider({ children, loggerProps, errorBoundaryProps }) {
6056
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(LoggerProvider, { ...loggerProps, children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(AppErrorBoundary, { ...errorBoundaryProps, children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(LoadingProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(ToastProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(AlertProvider, { children }) }) }) }) });
4492
6057
  }
4493
6058
 
4494
6059
  // src/overlay/AppProvider.tsx
4495
- var import_jsx_runtime44 = require("nativewind/jsx-runtime");
6060
+ var import_jsx_runtime50 = require("nativewind/jsx-runtime");
4496
6061
  var defaultLightTheme = {
4497
6062
  colors: {
4498
6063
  primary: "#f38b32",
@@ -4518,6 +6083,8 @@ function AppProvider({
4518
6083
  enableNavigation = true,
4519
6084
  enableOverlay = true,
4520
6085
  enableTheme = true,
6086
+ enableLogger = isDevelopment(),
6087
+ enableErrorBoundary = isDevelopment(),
4521
6088
  enableStatusBar = true,
4522
6089
  enableSafeArea = true,
4523
6090
  lightTheme = defaultLightTheme,
@@ -4525,25 +6092,41 @@ function AppProvider({
4525
6092
  defaultDark = false,
4526
6093
  isDark,
4527
6094
  statusBarProps,
6095
+ loggerProps,
6096
+ errorBoundaryProps,
4528
6097
  ...navigationProps
4529
6098
  }) {
4530
6099
  let content = children;
4531
6100
  if (enableOverlay) {
4532
- content = /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(OverlayProvider, { children: content });
6101
+ content = /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
6102
+ OverlayProvider,
6103
+ {
6104
+ loggerProps: enableLogger ? { enabled: true, overlayEnabled: true, ...loggerProps } : { enabled: false, overlayEnabled: false, ...loggerProps },
6105
+ errorBoundaryProps: enableErrorBoundary ? { enabled: true, ...errorBoundaryProps } : { enabled: false, ...errorBoundaryProps },
6106
+ children: content
6107
+ }
6108
+ );
6109
+ } else {
6110
+ if (enableErrorBoundary) {
6111
+ content = /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(AppErrorBoundary, { enabled: true, ...errorBoundaryProps, children: content });
6112
+ }
6113
+ if (enableLogger) {
6114
+ content = /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(LoggerProvider, { enabled: true, overlayEnabled: true, ...loggerProps, children: content });
6115
+ }
4533
6116
  }
4534
6117
  if (enableNavigation) {
4535
- content = /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(NavigationProvider, { ...navigationProps, children: content });
6118
+ content = /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(NavigationProvider, { ...navigationProps, children: content });
4536
6119
  }
4537
6120
  if (enableTheme) {
4538
- content = /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ThemeProvider, { light: lightTheme, dark: darkTheme, defaultDark, isDark, children: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
4539
- enableStatusBar && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(AppStatusBar, { testID: "status-bar", ...statusBarProps }),
6121
+ content = /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(ThemeProvider, { light: lightTheme, dark: darkTheme, defaultDark, isDark, children: /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_jsx_runtime50.Fragment, { children: [
6122
+ enableStatusBar && /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(AppStatusBar, { testID: "status-bar", ...statusBarProps }),
4540
6123
  content
4541
6124
  ] }) });
4542
6125
  }
4543
6126
  if (enableSafeArea) {
4544
- content = /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_react_native_safe_area_context3.SafeAreaProvider, { children: content });
6127
+ content = /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_react_native_safe_area_context3.SafeAreaProvider, { children: content });
4545
6128
  }
4546
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_jsx_runtime44.Fragment, { children: content });
6129
+ return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_jsx_runtime50.Fragment, { children: content });
4547
6130
  }
4548
6131
 
4549
6132
  // src/overlay/loading/hooks.ts
@@ -4561,8 +6144,29 @@ function useAlert() {
4561
6144
  return useAlertContext();
4562
6145
  }
4563
6146
 
6147
+ // src/overlay/logger/hooks.ts
6148
+ var import_react53 = require("react");
6149
+ function useLogger(namespace) {
6150
+ const logger = useLoggerContext();
6151
+ return (0, import_react53.useMemo)(
6152
+ () => ({
6153
+ entries: logger.entries,
6154
+ enabled: logger.enabled,
6155
+ level: logger.level,
6156
+ clear: logger.clear,
6157
+ namespace,
6158
+ log: (level, message, data) => logger.log(level, message, data, namespace),
6159
+ debug: (message, data) => logger.debug(message, data, namespace),
6160
+ info: (message, data) => logger.info(message, data, namespace),
6161
+ warn: (message, data) => logger.warn(message, data, namespace),
6162
+ error: (message, data) => logger.error(message, data, namespace)
6163
+ }),
6164
+ [logger, namespace]
6165
+ );
6166
+ }
6167
+
4564
6168
  // src/navigation/components/AppHeader.tsx
4565
- var import_jsx_runtime45 = require("nativewind/jsx-runtime");
6169
+ var import_jsx_runtime51 = require("nativewind/jsx-runtime");
4566
6170
  function AppHeader({
4567
6171
  title,
4568
6172
  subtitle,
@@ -4576,9 +6180,9 @@ function AppHeader({
4576
6180
  const colors = useThemeColors();
4577
6181
  const insets = (0, import_react_native_safe_area_context4.useSafeAreaInsets)();
4578
6182
  const backgroundColor = transparent ? "transparent" : colors.card;
4579
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(import_jsx_runtime45.Fragment, { children: [
4580
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(AppFocusedStatusBar, { translucent: true, backgroundColor: "transparent" }),
4581
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
6183
+ return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
6184
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(AppFocusedStatusBar, { translucent: true, backgroundColor: "transparent" }),
6185
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
4582
6186
  AppView,
4583
6187
  {
4584
6188
  style: [
@@ -4588,39 +6192,39 @@ function AppHeader({
4588
6192
  },
4589
6193
  style
4590
6194
  ],
4591
- children: /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(AppView, { row: true, items: "center", px: 4, style: styles16.container, children: [
4592
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(AppView, { style: [styles16.sideContainer, styles16.leftContainer], children: leftIcon && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(AppPressable, { onPress: onLeftPress, style: styles16.iconButton, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Icon, { name: leftIcon, size: 24, color: colors.text }) }) }),
4593
- /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(AppView, { style: styles16.centerContainer, children: [
4594
- title && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
6195
+ children: /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(AppView, { row: true, items: "center", px: 4, style: styles17.container, children: [
6196
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(AppView, { style: [styles17.sideContainer, styles17.leftContainer], children: leftIcon && /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(AppPressable, { onPress: onLeftPress, style: styles17.iconButton, children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(Icon, { name: leftIcon, size: 24, color: colors.text }) }) }),
6197
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(AppView, { style: styles17.centerContainer, children: [
6198
+ title && /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
4595
6199
  AppText,
4596
6200
  {
4597
6201
  size: "lg",
4598
6202
  weight: "semibold",
4599
- style: [styles16.title, { color: colors.text }],
6203
+ style: [styles17.title, { color: colors.text }],
4600
6204
  numberOfLines: 1,
4601
6205
  children: title
4602
6206
  }
4603
6207
  ),
4604
- subtitle && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
6208
+ subtitle && /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
4605
6209
  AppText,
4606
6210
  {
4607
6211
  size: "xs",
4608
- style: [styles16.subtitle, { color: colors.textMuted }],
6212
+ style: [styles17.subtitle, { color: colors.textMuted }],
4609
6213
  numberOfLines: 1,
4610
6214
  children: subtitle
4611
6215
  }
4612
6216
  )
4613
6217
  ] }),
4614
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(AppView, { row: true, items: "center", style: [styles16.sideContainer, styles16.rightContainer], children: rightIcons.map((icon, index) => /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(AppPressable, { onPress: icon.onPress, style: styles16.iconButton, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(AppView, { children: [
4615
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Icon, { name: icon.icon, size: 24, color: colors.text }),
4616
- icon.badge ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(AppView, { style: styles16.badge, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(AppText, { size: "xs", color: "white", style: styles16.badgeText, children: icon.badge > 99 ? "99+" : icon.badge }) }) : null
6218
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(AppView, { row: true, items: "center", style: [styles17.sideContainer, styles17.rightContainer], children: rightIcons.map((icon, index) => /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(AppPressable, { onPress: icon.onPress, style: styles17.iconButton, children: /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(AppView, { children: [
6219
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(Icon, { name: icon.icon, size: 24, color: colors.text }),
6220
+ icon.badge ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(AppView, { style: styles17.badge, children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(AppText, { size: "xs", color: "white", style: styles17.badgeText, children: icon.badge > 99 ? "99+" : icon.badge }) }) : null
4617
6221
  ] }) }, index)) })
4618
6222
  ] })
4619
6223
  }
4620
6224
  )
4621
6225
  ] });
4622
6226
  }
4623
- var styles16 = import_react_native30.StyleSheet.create({
6227
+ var styles17 = import_react_native33.StyleSheet.create({
4624
6228
  container: {
4625
6229
  height: 44
4626
6230
  // iOS 标准导航栏高度
@@ -4671,9 +6275,9 @@ var styles16 = import_react_native30.StyleSheet.create({
4671
6275
  });
4672
6276
 
4673
6277
  // src/navigation/components/DrawerContent.tsx
4674
- var import_react_native31 = require("react-native");
6278
+ var import_react_native34 = require("react-native");
4675
6279
  var import_drawer2 = require("@react-navigation/drawer");
4676
- var import_jsx_runtime46 = require("nativewind/jsx-runtime");
6280
+ var import_jsx_runtime52 = require("nativewind/jsx-runtime");
4677
6281
  function DrawerContent({
4678
6282
  state,
4679
6283
  descriptors,
@@ -4702,20 +6306,20 @@ function DrawerContent({
4702
6306
  badge: options.tabBarBadge
4703
6307
  };
4704
6308
  });
4705
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_drawer2.DrawerContentScrollView, { style: [styles17.container, { backgroundColor }], children: [
4706
- header && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_react_native31.View, { style: [styles17.header, { borderBottomColor: dividerColor }], children: header }),
4707
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppView, { className: "py-2", children: drawerItems.map((item) => {
6309
+ return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(import_drawer2.DrawerContentScrollView, { style: [styles18.container, { backgroundColor }], children: [
6310
+ header && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_react_native34.View, { style: [styles18.header, { borderBottomColor: dividerColor }], children: header }),
6311
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(AppView, { className: "py-2", children: drawerItems.map((item) => {
4708
6312
  const isFocused = state.routes[state.index].name === item.name;
4709
6313
  const onPress = () => {
4710
6314
  navigation.navigate(item.name);
4711
6315
  };
4712
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
4713
- import_react_native31.TouchableOpacity,
6316
+ return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
6317
+ import_react_native34.TouchableOpacity,
4714
6318
  {
4715
6319
  onPress,
4716
- style: [styles17.item, isFocused && { backgroundColor: activeBgColor }],
6320
+ style: [styles18.item, isFocused && { backgroundColor: activeBgColor }],
4717
6321
  children: [
4718
- item.icon && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_react_native31.View, { style: styles17.iconContainer, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
6322
+ item.icon && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_react_native34.View, { style: styles18.iconContainer, children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
4719
6323
  Icon,
4720
6324
  {
4721
6325
  name: item.icon,
@@ -4723,28 +6327,28 @@ function DrawerContent({
4723
6327
  color: isFocused ? activeColor : inactiveColor
4724
6328
  }
4725
6329
  ) }),
4726
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
6330
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
4727
6331
  AppText,
4728
6332
  {
4729
6333
  style: [
4730
- styles17.label,
6334
+ styles18.label,
4731
6335
  { color: isFocused ? activeColor : inactiveColor },
4732
- isFocused && styles17.activeLabel
6336
+ isFocused && styles18.activeLabel
4733
6337
  ],
4734
6338
  numberOfLines: 1,
4735
6339
  children: item.label
4736
6340
  }
4737
6341
  ),
4738
- item.badge != null && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_react_native31.View, { style: [styles17.badge, { backgroundColor: activeColor }], children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(AppText, { style: styles17.badgeText, children: typeof item.badge === "number" && item.badge > 99 ? "99+" : item.badge }) })
6342
+ item.badge != null && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_react_native34.View, { style: [styles18.badge, { backgroundColor: activeColor }], children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(AppText, { style: styles18.badgeText, children: typeof item.badge === "number" && item.badge > 99 ? "99+" : item.badge }) })
4739
6343
  ]
4740
6344
  },
4741
6345
  item.name
4742
6346
  );
4743
6347
  }) }),
4744
- footer && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_react_native31.View, { style: [styles17.footer, { borderTopColor: dividerColor }], children: footer })
6348
+ footer && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_react_native34.View, { style: [styles18.footer, { borderTopColor: dividerColor }], children: footer })
4745
6349
  ] });
4746
6350
  }
4747
- var styles17 = import_react_native31.StyleSheet.create({
6351
+ var styles18 = import_react_native34.StyleSheet.create({
4748
6352
  container: {
4749
6353
  flex: 1
4750
6354
  },
@@ -4794,7 +6398,7 @@ var styles17 = import_react_native31.StyleSheet.create({
4794
6398
  });
4795
6399
 
4796
6400
  // src/navigation/hooks/useNavigation.ts
4797
- var import_react43 = require("react");
6401
+ var import_react54 = require("react");
4798
6402
  var import_native3 = require("@react-navigation/native");
4799
6403
  function useNavigation() {
4800
6404
  return (0, import_native3.useNavigation)();
@@ -4810,7 +6414,7 @@ function useDrawerNavigation() {
4810
6414
  }
4811
6415
  function useBackHandler(handler) {
4812
6416
  const navigation = (0, import_native3.useNavigation)();
4813
- (0, import_react43.useEffect)(() => {
6417
+ (0, import_react54.useEffect)(() => {
4814
6418
  const unsubscribe = navigation.addListener("beforeRemove", (e) => {
4815
6419
  if (!handler()) {
4816
6420
  e.preventDefault();
@@ -4842,6 +6446,7 @@ var import_react_native_safe_area_context5 = require("react-native-safe-area-con
4842
6446
  ActionIcons,
4843
6447
  Alert,
4844
6448
  AppButton,
6449
+ AppErrorBoundary,
4845
6450
  AppFocusedStatusBar,
4846
6451
  AppHeader,
4847
6452
  AppImage,
@@ -4869,13 +6474,18 @@ var import_react_native_safe_area_context5 = require("react-native-safe-area-con
4869
6474
  FormItem,
4870
6475
  GradientView,
4871
6476
  Icon,
6477
+ KeyboardDismissView,
6478
+ LOG_LEVEL_WEIGHT,
4872
6479
  Loading,
6480
+ LogOverlay,
6481
+ LoggerProvider,
4873
6482
  MemoryStorage,
4874
6483
  NavigationContainer,
4875
6484
  NavigationIcons,
4876
6485
  NavigationProvider,
4877
6486
  OverlayProvider,
4878
6487
  PageDrawer,
6488
+ Picker,
4879
6489
  Progress,
4880
6490
  Radio,
4881
6491
  RadioGroup,
@@ -4897,7 +6507,10 @@ var import_react_native_safe_area_context5 = require("react-native-safe-area-con
4897
6507
  clsx,
4898
6508
  cn,
4899
6509
  createAPI,
6510
+ createApiLoggerTransport,
6511
+ createConsoleTransport,
4900
6512
  createDrawerScreens,
6513
+ createLogEntry,
4901
6514
  createNavigationTheme,
4902
6515
  createStackScreens,
4903
6516
  createTabScreens,
@@ -4906,10 +6519,12 @@ var import_react_native_safe_area_context5 = require("react-native-safe-area-con
4906
6519
  enhanceError,
4907
6520
  formatCurrency,
4908
6521
  formatDate,
6522
+ formatLogTime,
4909
6523
  formatNumber,
4910
6524
  formatPercent,
4911
6525
  formatRelativeTime,
4912
6526
  generateColorPalette,
6527
+ getGlobalLogger,
4913
6528
  getThemeColors,
4914
6529
  getValidationErrors,
4915
6530
  hexToRgb,
@@ -4917,11 +6532,16 @@ var import_react_native_safe_area_context5 = require("react-native-safe-area-con
4917
6532
  isValidEmail,
4918
6533
  isValidPhone,
4919
6534
  mapHttpStatus,
6535
+ normalizeConsoleArgs,
4920
6536
  omit,
4921
6537
  pick,
4922
6538
  rgbToHex,
6539
+ serializeLogEntries,
6540
+ setGlobalLogger,
6541
+ shouldLog,
4923
6542
  slugify,
4924
6543
  storage,
6544
+ stringifyLogData,
4925
6545
  truncate,
4926
6546
  twMerge,
4927
6547
  useAlert,
@@ -4936,6 +6556,7 @@ var import_react_native_safe_area_context5 = require("react-native-safe-area-con
4936
6556
  useIsFocused,
4937
6557
  useKeyboard,
4938
6558
  useLoading,
6559
+ useLogger,
4939
6560
  useMemoizedFn,
4940
6561
  useMutation,
4941
6562
  useNavigation,