react-native-x-components 0.1.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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -0
  3. package/dist/XActionSheet/index.d.ts +47 -0
  4. package/dist/XActionSheet/index.js +100 -0
  5. package/dist/XAnimatedSearchPanel/index.d.ts +33 -0
  6. package/dist/XAnimatedSearchPanel/index.js +295 -0
  7. package/dist/XAnimatedView/index.d.ts +77 -0
  8. package/dist/XAnimatedView/index.js +227 -0
  9. package/dist/XButton/index.d.ts +44 -0
  10. package/dist/XButton/index.js +126 -0
  11. package/dist/XCalendar/XCalendarPopup.d.ts +38 -0
  12. package/dist/XCalendar/XCalendarPopup.js +76 -0
  13. package/dist/XCalendar/index.d.ts +50 -0
  14. package/dist/XCalendar/index.js +241 -0
  15. package/dist/XCarousel/index.d.ts +72 -0
  16. package/dist/XCarousel/index.js +183 -0
  17. package/dist/XCascadeSelect/index.d.ts +39 -0
  18. package/dist/XCascadeSelect/index.js +147 -0
  19. package/dist/XChart/index.d.ts +78 -0
  20. package/dist/XChart/index.js +162 -0
  21. package/dist/XCheckbox/index.d.ts +61 -0
  22. package/dist/XCheckbox/index.js +128 -0
  23. package/dist/XConfirmForm/index.d.ts +69 -0
  24. package/dist/XConfirmForm/index.js +113 -0
  25. package/dist/XDivider/index.d.ts +33 -0
  26. package/dist/XDivider/index.js +61 -0
  27. package/dist/XDropdownMenu/index.d.ts +66 -0
  28. package/dist/XDropdownMenu/index.js +258 -0
  29. package/dist/XElevator/index.d.ts +52 -0
  30. package/dist/XElevator/index.js +138 -0
  31. package/dist/XForm/FormStore.d.ts +82 -0
  32. package/dist/XForm/FormStore.js +279 -0
  33. package/dist/XForm/index.d.ts +116 -0
  34. package/dist/XForm/index.js +212 -0
  35. package/dist/XForm/types.d.ts +82 -0
  36. package/dist/XForm/types.js +1 -0
  37. package/dist/XForm/useForm.d.ts +2 -0
  38. package/dist/XForm/useForm.js +19 -0
  39. package/dist/XForm/utils.d.ts +42 -0
  40. package/dist/XForm/utils.js +131 -0
  41. package/dist/XForm/validate.d.ts +20 -0
  42. package/dist/XForm/validate.js +118 -0
  43. package/dist/XFormPro/components/FieldTrigger.d.ts +14 -0
  44. package/dist/XFormPro/components/FieldTrigger.js +38 -0
  45. package/dist/XFormPro/components/XDateField.d.ts +22 -0
  46. package/dist/XFormPro/components/XDateField.js +22 -0
  47. package/dist/XFormPro/components/XSelectField.d.ts +16 -0
  48. package/dist/XFormPro/components/XSelectField.js +19 -0
  49. package/dist/XFormPro/index.d.ts +44 -0
  50. package/dist/XFormPro/index.js +111 -0
  51. package/dist/XFormPro/renderers.d.ts +24 -0
  52. package/dist/XFormPro/renderers.js +53 -0
  53. package/dist/XFormPro/types.d.ts +142 -0
  54. package/dist/XFormPro/types.js +1 -0
  55. package/dist/XImage/index.d.ts +13 -0
  56. package/dist/XImage/index.js +32 -0
  57. package/dist/XInput/index.d.ts +43 -0
  58. package/dist/XInput/index.js +136 -0
  59. package/dist/XLicensePlate/index.d.ts +40 -0
  60. package/dist/XLicensePlate/index.js +172 -0
  61. package/dist/XLocale/index.d.ts +23 -0
  62. package/dist/XLocale/index.js +174 -0
  63. package/dist/XModalForm/index.d.ts +32 -0
  64. package/dist/XModalForm/index.js +85 -0
  65. package/dist/XMultiSelect/index.d.ts +31 -0
  66. package/dist/XMultiSelect/index.js +131 -0
  67. package/dist/XNumberKeyboard/index.d.ts +57 -0
  68. package/dist/XNumberKeyboard/index.js +142 -0
  69. package/dist/XPicker/index.d.ts +35 -0
  70. package/dist/XPicker/index.js +96 -0
  71. package/dist/XPickerDate/index.d.ts +58 -0
  72. package/dist/XPickerDate/index.js +234 -0
  73. package/dist/XPopupProvider/index.d.ts +23 -0
  74. package/dist/XPopupProvider/index.js +6 -0
  75. package/dist/XProgress/index.d.ts +49 -0
  76. package/dist/XProgress/index.js +196 -0
  77. package/dist/XProviders/XImagePreview/index.d.ts +44 -0
  78. package/dist/XProviders/XImagePreview/index.js +164 -0
  79. package/dist/XProviders/XLoadingModal/index.d.ts +46 -0
  80. package/dist/XProviders/XLoadingModal/index.js +119 -0
  81. package/dist/XProviders/XToast/index.d.ts +50 -0
  82. package/dist/XProviders/XToast/index.js +183 -0
  83. package/dist/XProviders/index.d.ts +31 -0
  84. package/dist/XProviders/index.js +28 -0
  85. package/dist/XProviders/usageExample.d.ts +19 -0
  86. package/dist/XProviders/usageExample.js +83 -0
  87. package/dist/XPullView/index.d.ts +76 -0
  88. package/dist/XPullView/index.js +220 -0
  89. package/dist/XRadio/index.d.ts +83 -0
  90. package/dist/XRadio/index.js +186 -0
  91. package/dist/XRecord/index.d.ts +37 -0
  92. package/dist/XRecord/index.js +160 -0
  93. package/dist/XSignature/XSignatureSkia.d.ts +39 -0
  94. package/dist/XSignature/XSignatureSkia.js +184 -0
  95. package/dist/XSignature/core.d.ts +69 -0
  96. package/dist/XSignature/core.js +108 -0
  97. package/dist/XSignature/index.d.ts +33 -0
  98. package/dist/XSignature/index.js +88 -0
  99. package/dist/XTabs/index.d.ts +65 -0
  100. package/dist/XTabs/index.js +210 -0
  101. package/dist/XTag/index.d.ts +61 -0
  102. package/dist/XTag/index.js +153 -0
  103. package/dist/XTopView/index.d.ts +25 -0
  104. package/dist/XTopView/index.js +8 -0
  105. package/dist/XTopViewStore/index.d.ts +45 -0
  106. package/dist/XTopViewStore/index.js +49 -0
  107. package/dist/XUpload/XUploadImage.d.ts +37 -0
  108. package/dist/XUpload/XUploadImage.js +142 -0
  109. package/dist/XUpload/XUploadVideo.d.ts +24 -0
  110. package/dist/XUpload/XUploadVideo.js +83 -0
  111. package/dist/XUpload/adapters.d.ts +50 -0
  112. package/dist/XUpload/adapters.js +110 -0
  113. package/dist/XUpload/helpers.d.ts +35 -0
  114. package/dist/XUpload/helpers.js +85 -0
  115. package/dist/XUpload/index.d.ts +12 -0
  116. package/dist/XUpload/index.js +5 -0
  117. package/dist/XUpload/provider.d.ts +26 -0
  118. package/dist/XUpload/provider.js +43 -0
  119. package/dist/XUpload/types.d.ts +31 -0
  120. package/dist/XUpload/types.js +4 -0
  121. package/dist/XWheel/index.d.ts +49 -0
  122. package/dist/XWheel/index.js +263 -0
  123. package/dist/index.d.ts +113 -0
  124. package/dist/index.js +127 -0
  125. package/dist/theme.d.ts +164 -0
  126. package/dist/theme.js +161 -0
  127. package/package.json +78 -0
@@ -0,0 +1,31 @@
1
+ /**
2
+ * ============================================================================
3
+ * XMultiSelect —— 多选组件(src/components/MultiSelect 的 X 系列重制版)
4
+ * ============================================================================
5
+ *
6
+ * 逻辑与老版保持一致:
7
+ * - 触发区展示已选标签(tag 横滑)或 placeholder
8
+ * - 打开弹窗时把 value 拷贝成 draft,确定才回传(取消不动原值)
9
+ *
10
+ * 组件替换:
11
+ * - antd Modal -> XPullView(XTopView 宿主,底部弹层)
12
+ * - antd Checkbox -> XCheckbox
13
+ * - antd Button -> XButton
14
+ */
15
+ import React, { type ReactNode } from 'react';
16
+ export interface XMultiSelectProps {
17
+ value?: any[];
18
+ onChange?: (val: any[]) => void;
19
+ options: {
20
+ label: string;
21
+ value: any;
22
+ disabled?: boolean;
23
+ }[];
24
+ placeholder?: string;
25
+ /** 弹层标题,默认取 placeholder */
26
+ title?: string;
27
+ /** 自定义触发器 */
28
+ children?: ReactNode;
29
+ }
30
+ export declare function XMultiSelect({ value, onChange, options, placeholder, title, children }: XMultiSelectProps): React.JSX.Element;
31
+ export default XMultiSelect;
@@ -0,0 +1,131 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * ============================================================================
4
+ * XMultiSelect —— 多选组件(src/components/MultiSelect 的 X 系列重制版)
5
+ * ============================================================================
6
+ *
7
+ * 逻辑与老版保持一致:
8
+ * - 触发区展示已选标签(tag 横滑)或 placeholder
9
+ * - 打开弹窗时把 value 拷贝成 draft,确定才回传(取消不动原值)
10
+ *
11
+ * 组件替换:
12
+ * - antd Modal -> XPullView(XTopView 宿主,底部弹层)
13
+ * - antd Checkbox -> XCheckbox
14
+ * - antd Button -> XButton
15
+ */
16
+ import { useCallback, useState } from 'react';
17
+ import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
18
+ import { XPullView } from '../XPullView';
19
+ import { XCheckbox } from '../XCheckbox';
20
+ import { XButton } from '../XButton';
21
+ import { useXTheme } from '../theme';
22
+ import { useXLocale } from '../XLocale';
23
+ import AntDesign from '@react-native-vector-icons/ant-design';
24
+ export function XMultiSelect({ value = [], onChange, options, placeholder, title, children }) {
25
+ const theme = useXTheme();
26
+ const { t } = useXLocale();
27
+ const resolvedPlaceholder = placeholder ?? t('pleaseSelect');
28
+ const resolvedTitle = title ?? resolvedPlaceholder;
29
+ const [visible, setVisible] = useState(false);
30
+ const [draft, setDraft] = useState([]);
31
+ const openModal = useCallback(() => {
32
+ setDraft([...value]);
33
+ setVisible(true);
34
+ }, [value]);
35
+ const handleCancel = useCallback(() => {
36
+ setVisible(false);
37
+ }, []);
38
+ const handleConfirm = useCallback(() => {
39
+ onChange?.(draft);
40
+ setVisible(false);
41
+ }, [draft, onChange]);
42
+ const handleToggle = useCallback((itemValue) => {
43
+ setDraft(prev => (prev.includes(itemValue) ? prev.filter(v => v !== itemValue) : [...prev, itemValue]));
44
+ }, []);
45
+ const selectedLabels = options.filter(o => value.includes(o.value)).map(o => o.label);
46
+ return (_jsxs(_Fragment, { children: [children ? (_jsx(Pressable, { onPress: openModal, children: children })) : (_jsxs(View, { style: styles.trigger, children: [_jsx(View, { style: styles.triggerLeft, children: selectedLabels.length > 0 ? (_jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, contentContainerStyle: styles.labelScrollContent, children: selectedLabels.map((label, idx) => (_jsx(Pressable, { onPress: openModal, children: _jsx(View, { style: [styles.tag, { backgroundColor: theme.colorPrimaryBg }], children: _jsx(Text, { style: [styles.tagText, { color: theme.colorPrimary }], children: label }) }) }, idx))) })) : (_jsx(Pressable, { style: styles.placeholderWrap, onPress: openModal, children: _jsx(Text, { style: [styles.placeholder, { color: theme.colorTextTertiary }], children: resolvedPlaceholder }) })) }), _jsx(Pressable, { onPress: openModal, hitSlop: 8, children: _jsx(AntDesign, { name: 'right', size: 16, color: theme.colorTextTertiary }) })] })), _jsx(XPullView, { visible: visible, onClose: handleCancel, side: 'bottom', duration: 200, children: _jsxs(View, { style: [styles.panel, { backgroundColor: theme.colorBgContainer }], children: [_jsx(View, { style: [styles.header, { borderBottomColor: theme.colorSplit }], children: _jsx(Text, { style: [styles.headerTitle, { color: theme.colorText }], children: resolvedTitle }) }), _jsx(ScrollView, { style: styles.optionList, contentContainerStyle: styles.optionListContent, children: options.map(o => (_jsxs(Pressable, { style: styles.optionItem, onPress: () => handleToggle(o.value), children: [_jsx(View, { pointerEvents: 'none', children: _jsx(XCheckbox, { checked: draft.includes(o.value), disabled: o.disabled }) }), _jsx(View, { pointerEvents: 'none', style: styles.optionLabelWrap, children: _jsx(Text, { style: [styles.optionLabel, { color: theme.colorText }], children: o.label }) })] }, String(o.value)))) }), _jsxs(View, { style: [styles.footer, { borderTopColor: theme.colorSplit }], children: [_jsx(XButton, { onPress: handleCancel, style: styles.cancelBtn, children: t('cancel') }), _jsxs(XButton, { type: 'primary', onPress: handleConfirm, style: styles.confirmBtn, children: [t('confirm'), draft.length > 0 ? `(${draft.length})` : ''] })] })] }) })] }));
47
+ }
48
+ const styles = StyleSheet.create({
49
+ trigger: {
50
+ flexDirection: 'row',
51
+ alignItems: 'center',
52
+ justifyContent: 'space-between',
53
+ minHeight: 36,
54
+ paddingRight: 4,
55
+ width: '100%',
56
+ },
57
+ triggerLeft: {
58
+ flex: 1,
59
+ marginRight: 8,
60
+ overflow: 'hidden',
61
+ },
62
+ labelScrollContent: {
63
+ flexDirection: 'row',
64
+ alignItems: 'center',
65
+ },
66
+ tag: {
67
+ borderRadius: 4,
68
+ paddingHorizontal: 8,
69
+ paddingVertical: 3,
70
+ marginRight: 6,
71
+ },
72
+ tagText: {
73
+ fontSize: 13,
74
+ },
75
+ placeholderWrap: {
76
+ flex: 1,
77
+ justifyContent: 'center',
78
+ },
79
+ placeholder: {
80
+ fontSize: 15,
81
+ },
82
+ panel: {
83
+ borderTopLeftRadius: 12,
84
+ borderTopRightRadius: 12,
85
+ },
86
+ header: {
87
+ paddingHorizontal: 20,
88
+ paddingTop: 18,
89
+ paddingBottom: 14,
90
+ borderBottomWidth: StyleSheet.hairlineWidth,
91
+ },
92
+ headerTitle: {
93
+ fontSize: 17,
94
+ fontWeight: '600',
95
+ },
96
+ optionList: {
97
+ maxHeight: 380,
98
+ },
99
+ optionListContent: {
100
+ paddingHorizontal: 16,
101
+ paddingVertical: 8,
102
+ },
103
+ optionItem: {
104
+ flexDirection: 'row',
105
+ alignItems: 'center',
106
+ paddingVertical: 12,
107
+ },
108
+ optionLabelWrap: {
109
+ marginLeft: 10,
110
+ flex: 1,
111
+ },
112
+ optionLabel: {
113
+ fontSize: 15,
114
+ },
115
+ footer: {
116
+ flexDirection: 'row',
117
+ paddingHorizontal: 20,
118
+ paddingTop: 14,
119
+ paddingBottom: 24,
120
+ borderTopWidth: StyleSheet.hairlineWidth,
121
+ },
122
+ cancelBtn: {
123
+ flex: 1,
124
+ marginRight: 10,
125
+ },
126
+ confirmBtn: {
127
+ flex: 1,
128
+ marginLeft: 10,
129
+ },
130
+ });
131
+ export default XMultiSelect;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * ============================================================================
3
+ * XNumberKeyboard —— 数字键盘(duxui NumberKeyboard 的 XTheme 重写版)
4
+ * ============================================================================
5
+ *
6
+ * 【与 duxui NumberKeyboard 的关系】
7
+ * duxui 的 NumberKeyboard 是"纯静态键盘"(弹出靠业务配 PullView),
8
+ * 这里完全保留该分层:
9
+ * - XNumberKeyboard:纯键盘 UI(3×4 布局、乱序 random、退格、小数点);
10
+ * - XNumberKeyboardPopup:弹出模式(XPullView 底部拉起 + 取消/确定工具栏
11
+ * + 草稿管理),与 XCalendarPopup/XPickerDate 交互一致,开箱即用。
12
+ *
13
+ * 【键盘布局】
14
+ * 1 2 3 1 2 3
15
+ * 4 5 6 4 5 6
16
+ * 7 8 9 7 8 9
17
+ * . 0 ⌫ 或 0(占两格) ⌫ ← extraKey={null} 收起小数点
18
+ * random 打乱 1~9(duxui 同款防窥)。
19
+ * ============================================================================
20
+ */
21
+ import React from 'react';
22
+ import { StyleProp, ViewStyle } from 'react-native';
23
+ export interface XNumberKeyboardProps {
24
+ /** 按下数字/小数点/自定义键 */
25
+ onKeyPress?: (key: string) => void;
26
+ /** 按下退格 */
27
+ onBackspace?: () => void;
28
+ /** 乱序键盘(防窥),默认 false */
29
+ random?: boolean;
30
+ /**
31
+ * 左下角附加键:默认 '.'(小数点);传 null 收起(0 占两格);
32
+ * 传自定义字符串(如 '-')即渲染自定义键。
33
+ */
34
+ extraKey?: string | null;
35
+ style?: StyleProp<ViewStyle>;
36
+ }
37
+ export declare function XNumberKeyboard({ onKeyPress, onBackspace, random, extraKey, style }: XNumberKeyboardProps): React.JSX.Element;
38
+ export interface XNumberKeyboardPopupProps {
39
+ visible: boolean;
40
+ onClose: () => void;
41
+ /** 点确定:回传草稿值 */
42
+ onConfirm?: (value: string) => void;
43
+ /** 受控值(每次打开同步进草稿) */
44
+ value?: string;
45
+ /** 草稿变化(可选,实时上抛) */
46
+ onChange?: (value: string) => void;
47
+ /** 最大长度,默认不限制 */
48
+ maxLength?: number;
49
+ /** 面板标题 */
50
+ title?: string;
51
+ /** 透传键盘 props */
52
+ random?: boolean;
53
+ extraKey?: string | null;
54
+ duration?: number;
55
+ }
56
+ export declare function XNumberKeyboardPopup({ visible, onClose, onConfirm, value, onChange, maxLength, title, random, extraKey, duration, }: XNumberKeyboardPopupProps): React.JSX.Element;
57
+ export default XNumberKeyboard;
@@ -0,0 +1,142 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * ============================================================================
4
+ * XNumberKeyboard —— 数字键盘(duxui NumberKeyboard 的 XTheme 重写版)
5
+ * ============================================================================
6
+ *
7
+ * 【与 duxui NumberKeyboard 的关系】
8
+ * duxui 的 NumberKeyboard 是"纯静态键盘"(弹出靠业务配 PullView),
9
+ * 这里完全保留该分层:
10
+ * - XNumberKeyboard:纯键盘 UI(3×4 布局、乱序 random、退格、小数点);
11
+ * - XNumberKeyboardPopup:弹出模式(XPullView 底部拉起 + 取消/确定工具栏
12
+ * + 草稿管理),与 XCalendarPopup/XPickerDate 交互一致,开箱即用。
13
+ *
14
+ * 【键盘布局】
15
+ * 1 2 3 1 2 3
16
+ * 4 5 6 4 5 6
17
+ * 7 8 9 7 8 9
18
+ * . 0 ⌫ 或 0(占两格) ⌫ ← extraKey={null} 收起小数点
19
+ * random 打乱 1~9(duxui 同款防窥)。
20
+ * ============================================================================
21
+ */
22
+ import { useCallback, useMemo, useRef, useState } from 'react';
23
+ import { Pressable, StyleSheet, Text, View } from 'react-native';
24
+ import { XPullView } from '../XPullView';
25
+ import { useXTheme } from '../theme';
26
+ import { useXLocale } from '../XLocale';
27
+ /** Fisher–Yates 洗牌 */
28
+ function shuffle(list) {
29
+ const arr = [...list];
30
+ for (let i = arr.length - 1; i > 0; i--) {
31
+ const j = Math.floor(Math.random() * (i + 1));
32
+ [arr[i], arr[j]] = [arr[j], arr[i]];
33
+ }
34
+ return arr;
35
+ }
36
+ export function XNumberKeyboard({ onKeyPress, onBackspace, random = false, extraKey = '.', style }) {
37
+ const t = useXTheme();
38
+ /** 数字键序(random 时打乱一次,组件实例内稳定) */
39
+ const digits = useMemo(() => {
40
+ const base = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
41
+ return random ? shuffle(base) : base;
42
+ }, [random]);
43
+ /** 单个键渲染 */
44
+ const renderKey = (label, onPress, flex = 1) => (_jsx(Pressable, { onPress: onPress, style: ({ pressed }) => [
45
+ styles.key,
46
+ { flex, backgroundColor: pressed ? t.colorBgLayout : t.colorBgContainer, borderRadius: t.borderRadius },
47
+ ], children: _jsx(Text, { style: [styles.keyText, { color: t.colorText }], allowFontScaling: false, children: label }) }, label));
48
+ return (_jsxs(View, { style: [styles.keyboard, { backgroundColor: t.colorBgLayout }, style], children: [[0, 1, 2].map(row => (_jsx(View, { style: styles.row, children: digits.slice(row * 3, row * 3 + 3).map(d => renderKey(d, () => onKeyPress?.(d))) }, row))), _jsxs(View, { style: styles.row, children: [extraKey != null
49
+ ? renderKey(extraKey, () => onKeyPress?.(extraKey))
50
+ : renderKey('0', () => onKeyPress?.('0'), 2.15), extraKey != null && renderKey('0', () => onKeyPress?.('0')), renderKey('⌫', () => onBackspace?.())] })] }));
51
+ }
52
+ export function XNumberKeyboardPopup({ visible, onClose, onConfirm, value, onChange, maxLength, title, random, extraKey = '.', duration = 200, }) {
53
+ const t = useXTheme();
54
+ const { t: i18n } = useXLocale();
55
+ const [draft, setDraft] = useState('');
56
+ const prevVisibleRef = useRef(visible);
57
+ /** 打开瞬间同步草稿(XPickerDate/XCalendarPopup 同套路) */
58
+ if (visible !== prevVisibleRef.current) {
59
+ prevVisibleRef.current = visible;
60
+ if (visible)
61
+ setDraft(value ?? '');
62
+ }
63
+ /** 按键 → 追加(小数点只允许一次;maxLength 截断) */
64
+ const handleKey = useCallback((key) => {
65
+ setDraft(prev => {
66
+ let next = prev;
67
+ if (key === '.') {
68
+ if (prev.includes('.') || prev.length === 0)
69
+ return prev; // 空串不允许以 . 开头
70
+ next = prev + '.';
71
+ }
72
+ else {
73
+ if (maxLength && prev.length >= maxLength)
74
+ return prev;
75
+ next = prev + key;
76
+ }
77
+ onChange?.(next);
78
+ return next;
79
+ });
80
+ }, [maxLength, onChange]);
81
+ /** 退格 */
82
+ const handleBackspace = useCallback(() => {
83
+ setDraft(prev => {
84
+ const next = prev.slice(0, -1);
85
+ onChange?.(next);
86
+ return next;
87
+ });
88
+ }, [onChange]);
89
+ /** 确定 */
90
+ const handleConfirm = useCallback(() => {
91
+ onConfirm?.(draft);
92
+ onClose();
93
+ }, [draft, onConfirm, onClose]);
94
+ return (_jsx(XPullView, { visible: visible, onClose: onClose, side: 'bottom', duration: duration, overlayOpacity: 0.4, children: _jsxs(View, { style: [styles.panel, { backgroundColor: t.colorBgContainer, paddingBottom: 10 }], children: [_jsxs(View, { style: [styles.toolbar, { borderBottomColor: t.colorSplit }], children: [_jsx(Pressable, { onPress: onClose, hitSlop: 8, children: _jsx(Text, { style: [styles.cancelBtn, { color: t.colorTextSecondary }], children: i18n('cancel') }) }), _jsx(Text, { style: [styles.title, { color: t.colorText }], numberOfLines: 1, allowFontScaling: false, children: title ?? draft }), _jsx(Pressable, { onPress: handleConfirm, hitSlop: 8, children: _jsx(Text, { style: [styles.confirmBtn, { color: t.colorPrimary }], children: i18n('confirm') }) })] }), _jsx(XNumberKeyboard, { onKeyPress: handleKey, onBackspace: handleBackspace, random: random, extraKey: extraKey })] }) }));
95
+ }
96
+ const styles = StyleSheet.create({
97
+ keyboard: {
98
+ padding: 8,
99
+ gap: 8,
100
+ },
101
+ row: {
102
+ flexDirection: 'row',
103
+ gap: 8,
104
+ },
105
+ key: {
106
+ height: 46,
107
+ alignItems: 'center',
108
+ justifyContent: 'center',
109
+ },
110
+ keyText: {
111
+ fontSize: 20,
112
+ fontWeight: '500',
113
+ },
114
+ panel: {
115
+ borderTopLeftRadius: 12,
116
+ borderTopRightRadius: 12,
117
+ overflow: 'hidden',
118
+ },
119
+ toolbar: {
120
+ flexDirection: 'row',
121
+ alignItems: 'center',
122
+ justifyContent: 'space-between',
123
+ paddingHorizontal: 16,
124
+ height: 48,
125
+ borderBottomWidth: StyleSheet.hairlineWidth,
126
+ },
127
+ cancelBtn: {
128
+ fontSize: 15,
129
+ },
130
+ title: {
131
+ fontSize: 16,
132
+ fontWeight: '600',
133
+ flex: 1,
134
+ textAlign: 'center',
135
+ marginHorizontal: 8,
136
+ },
137
+ confirmBtn: {
138
+ fontSize: 15,
139
+ fontWeight: '600',
140
+ },
141
+ });
142
+ export default XNumberKeyboard;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * ============================================================================
3
+ * XPicker —— 底部单列滚轮选择器(与 XPickerDate 同一滚轮形式与交互)
4
+ * ============================================================================
5
+ *
6
+ * 【交互逻辑与 XPickerDate 完全一致】
7
+ * 1. 打开时把当前 value 复制到 draft(草稿);
8
+ * 2. 拖拽滚轮/点某一行只改 draft,不立即生效;
9
+ * 3. 点"确定"先 flush 滚轮(按当前视觉位置结算),回传 onChange 后关闭。
10
+ * 取消时不会误改数据。区别仅在数据形态:单列任意选项(label/value),
11
+ * 没有年月日联动,直接使用通用 XWheel 组件。
12
+ *
13
+ * 【结构】
14
+ * XPullView(底部滑入 200ms)
15
+ * └── 面板:工具栏(取消 | 标题 | 确定)+ 单列滚轮(240 高,antd 同款遮罩)
16
+ * ============================================================================
17
+ */
18
+ import React from 'react';
19
+ /** 选项:label 显示文本,value 回传值 */
20
+ export interface XPickerOption<T = any> {
21
+ label: string;
22
+ value: T;
23
+ }
24
+ export interface XPickerProps<T = any> {
25
+ visible: boolean;
26
+ onClose: () => void;
27
+ options: XPickerOption<T>[];
28
+ /** 当前选中值(受控) */
29
+ value?: T;
30
+ /** 点确定回调:value 为选中的值,option 为对应的选项对象 */
31
+ onChange?: (value: T, option?: XPickerOption<T>) => void;
32
+ title?: string;
33
+ duration?: number;
34
+ }
35
+ export declare function XPicker<T = any>({ visible, onClose, options, value, onChange, title, duration }: XPickerProps<T>): React.JSX.Element;
@@ -0,0 +1,96 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * ============================================================================
4
+ * XPicker —— 底部单列滚轮选择器(与 XPickerDate 同一滚轮形式与交互)
5
+ * ============================================================================
6
+ *
7
+ * 【交互逻辑与 XPickerDate 完全一致】
8
+ * 1. 打开时把当前 value 复制到 draft(草稿);
9
+ * 2. 拖拽滚轮/点某一行只改 draft,不立即生效;
10
+ * 3. 点"确定"先 flush 滚轮(按当前视觉位置结算),回传 onChange 后关闭。
11
+ * 取消时不会误改数据。区别仅在数据形态:单列任意选项(label/value),
12
+ * 没有年月日联动,直接使用通用 XWheel 组件。
13
+ *
14
+ * 【结构】
15
+ * XPullView(底部滑入 200ms)
16
+ * └── 面板:工具栏(取消 | 标题 | 确定)+ 单列滚轮(240 高,antd 同款遮罩)
17
+ * ============================================================================
18
+ */
19
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
20
+ import { Pressable, StyleSheet, View, Text } from 'react-native';
21
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
22
+ import { XPullView } from '../XPullView';
23
+ import { XWheel } from '../XWheel';
24
+ import { useXTheme } from '../theme';
25
+ import { useXLocale } from '../XLocale';
26
+ export function XPicker({ visible, onClose, options, value, onChange, title, duration = 200 }) {
27
+ const t = useXTheme();
28
+ const { t: i18n } = useXLocale();
29
+ const resolvedTitle = title ?? i18n('pleaseSelect');
30
+ const insets = useSafeAreaInsets();
31
+ const styles = useMemo(() => StyleSheet.create({
32
+ panel: {
33
+ backgroundColor: t.colorBgContainer,
34
+ borderTopLeftRadius: 12,
35
+ borderTopRightRadius: 12,
36
+ overflow: 'hidden',
37
+ },
38
+ toolbar: {
39
+ flexDirection: 'row',
40
+ alignItems: 'center',
41
+ justifyContent: 'space-between',
42
+ paddingHorizontal: 16,
43
+ height: 48,
44
+ borderBottomWidth: StyleSheet.hairlineWidth,
45
+ borderBottomColor: t.colorSplit,
46
+ },
47
+ cancelBtn: {
48
+ fontSize: 15,
49
+ },
50
+ title: {
51
+ fontSize: 16,
52
+ fontWeight: '600',
53
+ flex: 1,
54
+ textAlign: 'center',
55
+ marginHorizontal: 8,
56
+ },
57
+ confirmBtn: {
58
+ fontSize: 15,
59
+ fontWeight: '600',
60
+ },
61
+ /** 单列占满整行(XWheel 自带 flex:1 与 240 高) */
62
+ columns: {
63
+ flexDirection: 'row',
64
+ },
65
+ }), [t]);
66
+ /** draft:弹窗内的临时选中值,确定时才回传 */
67
+ const [draft, setDraft] = useState(value);
68
+ /** 上一次的 visible,用于识别"本次渲染刚被打开"(与 XPickerDate 同款) */
69
+ const prevVisibleRef = useRef(visible);
70
+ /** 滚轮的 flush 句柄:确定时强制按当前视觉位置结算 */
71
+ const wheelRef = useRef(null);
72
+ /**
73
+ * 打开瞬间同步重置草稿为当前 value(渲染期修正派生状态,避免闪旧值)。
74
+ */
75
+ if (visible !== prevVisibleRef.current) {
76
+ prevVisibleRef.current = visible;
77
+ if (visible) {
78
+ setDraft(value);
79
+ }
80
+ }
81
+ /** 打开期间外部 value 变化(比如上层异步回填)也跟着同步 */
82
+ useEffect(() => {
83
+ if (visible) {
84
+ setDraft(value);
85
+ }
86
+ }, [visible, value]);
87
+ /** 点确定:flush 滚轮拿到"看到的"选项,回传后关闭 */
88
+ const handleConfirm = useCallback(() => {
89
+ const option = wheelRef.current?.flush() ?? options.find(o => o.value === draft);
90
+ if (option) {
91
+ onChange?.(option.value, option);
92
+ }
93
+ onClose();
94
+ }, [options, draft, onChange, onClose]);
95
+ return (_jsx(XPullView, { visible: visible, onClose: onClose, side: 'bottom', duration: duration, overlayOpacity: 0.4, children: _jsxs(View, { style: [styles.panel, { paddingBottom: Math.max(insets.bottom, 10) }], children: [_jsxs(View, { style: styles.toolbar, children: [_jsx(Pressable, { onPress: onClose, hitSlop: 8, children: _jsx(Text, { style: [styles.cancelBtn, { color: t.colorTextSecondary }], children: i18n('cancel') }) }), _jsx(Text, { style: [styles.title, { color: t.colorText }], numberOfLines: 1, children: resolvedTitle }), _jsx(Pressable, { onPress: handleConfirm, hitSlop: 8, children: _jsx(Text, { style: [styles.confirmBtn, { color: t.colorPrimary }], children: i18n('confirm') }) })] }), _jsx(View, { style: styles.columns, children: _jsx(XWheel, { ref: wheelRef, items: options, value: draft, onSelect: v => setDraft(v) }) })] }) }));
96
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * ============================================================================
3
+ * XPickerDate -- 日期时间滚轮选择器(antd-mobile DatePicker 架构移植)
4
+ * ============================================================================
5
+ *
6
+ * 【format 决定列】出现哪些单位 token 就显示哪些列,顺序固定为
7
+ * 年 → 月 → 日 → 时 → 分 → 秒
8
+ * (与 antd-mobile precision 模型一致:antd 用 precision 指定末列,
9
+ * format 是它的超集 -- antd 只能选"到某单位为止的前缀",format 可以
10
+ * 任意组合,如 'YYYY-MM' 或 'HH:mm')。
11
+ *
12
+ * format='YYYY-MM-DD' 年 | 月 | 日(默认)
13
+ * format='YYYY-MM-DD HH:mm:ss' 年 | 月 | 日 | 时 | 分 | 秒
14
+ * format='HH:mm:ss' 时 | 分 | 秒
15
+ * format='YYYY-MM' 年 | 月
16
+ *
17
+ * 输入解析与输出格式化都使用同一个 format:
18
+ * value 传入组件输出过的同格式字符串可精确回读(dayjs customParseFormat),
19
+ * 传 Date 直接取各字段,解析失败取当前时间。
20
+ *
21
+ * 【对标 antd-mobile DatePicker 的逐条核对结论】
22
+ * 1. 列顺序/子集模型(precision) -> format 超集实现 ✅
23
+ * 2. 时/分/秒两位补零,年/月/日不补
24
+ * (antd 默认 renderLabel: ('0'+n).slice(-2)) ✅
25
+ * 3. 日列随年月联动(antd getMonthDays) -> dayjs daysInMonth ✅
26
+ * 4. 滚轮手势/弹簧/橡皮筋/松手立即上报
27
+ * (antd picker-view wheel.tsx) -> 通用 XWheel 组件 ✅
28
+ * 5. min/max 逐列收窄(antd 全量支持) -> 仅 minYear/maxYear ⚠️ 差异,
29
+ * 维持现状:本项目只需要年份范围,无整段 Date 边界需求。
30
+ *
31
+ * 滚轮本体见 ./XWheel.tsx(与 XPicker 单列选择器共用)。
32
+ * ============================================================================
33
+ */
34
+ import React from 'react';
35
+ /** 六个单位(antd DatePrecision 的六个取值),也是列的固定顺序 */
36
+ export type DateUnit = 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second';
37
+ /** 旧版两档模式(向后兼容,format 未传时生效) */
38
+ export type XPickerDateMode = 'date' | 'time';
39
+ export interface XPickerDateProps {
40
+ visible: boolean;
41
+ onClose: () => void;
42
+ /** 当前值:与 format 同格式的字符串(也接受 Date) */
43
+ value?: string | Date;
44
+ /** 确定回调:按 format 格式化后的字符串(如 'YYYY-MM-DD HH:mm:ss') */
45
+ onChange?: (value: string) => void;
46
+ title?: string;
47
+ duration?: number;
48
+ /**
49
+ * 列配置:出现哪些单位 token(YYYY/MM/DD/HH/mm/ss)就显示哪些列,
50
+ * 顺序固定 年→月→日→时→分→秒。输入解析与输出格式化均使用它。
51
+ */
52
+ format?: string;
53
+ /** @deprecated 旧两档模式:date 等价 format='YYYY-MM-DD',time 等价 format='HH:mm:ss'。与 format 同时传时以 format 为准 */
54
+ mode?: XPickerDateMode;
55
+ minYear?: number;
56
+ maxYear?: number;
57
+ }
58
+ export declare function XPickerDate({ visible, onClose, value, onChange, title, duration, format, mode, minYear, maxYear, }: XPickerDateProps): React.JSX.Element;