react-native-x-components 0.1.0 → 0.2.1
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/AGENTS.md +128 -0
- package/README.md +54 -0
- package/dist/XActionSheet/global.d.ts +15 -0
- package/dist/XActionSheet/global.js +32 -0
- package/dist/XActionSheet/index.js +3 -2
- package/dist/XCalendar/index.js +10 -6
- package/dist/XDropdownMenu/index.js +37 -22
- package/dist/XInput/index.d.ts +11 -0
- package/dist/XInput/index.js +84 -5
- package/dist/XLicensePlate/index.js +3 -1
- package/dist/XPopupProvider/index.js +2 -1
- package/dist/XSignature/XSignatureSkia.js +13 -5
- package/dist/XSignature/index.js +1 -2
- package/dist/XTheme/BrandColor.d.ts +20 -0
- package/dist/XTheme/BrandColor.js +33 -0
- package/dist/XTheme/ThemeControls.d.ts +22 -0
- package/dist/XTheme/ThemeControls.js +92 -0
- package/dist/XUpload/XUploadImage.js +57 -11
- package/dist/XUpload/XUploadVideo.js +29 -8
- package/dist/XUpload/helpers.d.ts +8 -0
- package/dist/XUpload/helpers.js +23 -0
- package/dist/XUpload/index.d.ts +1 -1
- package/dist/XUpload/index.js +1 -1
- package/dist/XVideoPreview/index.d.ts +17 -0
- package/dist/XVideoPreview/index.js +78 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +10 -1
- package/dist/theme.d.ts +0 -8
- package/dist/theme.js +22 -6
- package/package.json +12 -7
- package/wechat-qrcode.png +0 -0
- package/dist/XElevator/index.d.ts +0 -52
- package/dist/XElevator/index.js +0 -138
package/dist/index.js
CHANGED
|
@@ -90,7 +90,7 @@ export { useXLocale, setXLocale, getXLocale, xT, xMonthTitle, X_WEEKDAYS } from
|
|
|
90
90
|
// ---------------------------------------------------------------------------
|
|
91
91
|
export { XCarousel } from './XCarousel';
|
|
92
92
|
export { XTabs, XTabPane } from './XTabs';
|
|
93
|
-
|
|
93
|
+
// XElevator 已废弃(实现质量不达标,代码保留在 src/_deprecated/),不再导出/发布
|
|
94
94
|
// ---------------------------------------------------------------------------
|
|
95
95
|
// 下拉菜单
|
|
96
96
|
// ---------------------------------------------------------------------------
|
|
@@ -125,3 +125,12 @@ export { XSignatureSkia } from './XSignature/XSignatureSkia';
|
|
|
125
125
|
// 上传(适配器模式:普通 multipart + MinIO 预签名直传)
|
|
126
126
|
// ---------------------------------------------------------------------------
|
|
127
127
|
export * from './XUpload';
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// 主题色 + 全局主题控件
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
export { ThemeControls } from './XTheme/ThemeControls';
|
|
132
|
+
export { useXBrandStore, setXBrand, setXBrandByName, X_BRAND_PRESETS } from './XTheme/BrandColor';
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// 视频预览
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
export { XVideoPreview } from './XVideoPreview';
|
package/dist/theme.d.ts
CHANGED
|
@@ -150,14 +150,6 @@ export declare function setXThemeMode(mode: XThemeMode): void;
|
|
|
150
150
|
export declare function getXThemeMode(): XThemeMode;
|
|
151
151
|
/** 订阅当前模式(React 组件里用) */
|
|
152
152
|
export declare function useXThemeMode(): XThemeMode;
|
|
153
|
-
/**
|
|
154
|
-
* 【核心 Hook】当前模式下的完整 token 集。
|
|
155
|
-
*
|
|
156
|
-
* - 返回值是模块级常量对象(lightTokens / darkTokens),
|
|
157
|
-
* 引用稳定、不会触发无谓重渲染;模式切换时组件自然重渲染取到新对象;
|
|
158
|
-
* - 解析规则:mode==='system' 时跟随 useColorScheme(),
|
|
159
|
-
* 否则用强制值(未挂 Provider 也能工作,状态全局单例)。
|
|
160
|
-
*/
|
|
161
153
|
export declare function useXTheme(): XTheme;
|
|
162
154
|
/** 当前实际生效的明暗方案(isDark 便捷判断用) */
|
|
163
155
|
export declare function useXThemeScheme(): XThemeScheme;
|
package/dist/theme.js
CHANGED
|
@@ -95,7 +95,7 @@ export const darkTokens = {
|
|
|
95
95
|
colorTextQuaternary: 'rgba(255, 255, 255, 0.25)',
|
|
96
96
|
colorTextLightSolid: '#FFFFFF',
|
|
97
97
|
colorBorder: 'rgba(255, 255, 255, 0.22)',
|
|
98
|
-
colorSplit: 'rgba(255, 255, 255, 0.
|
|
98
|
+
colorSplit: 'rgba(255, 255, 255, 0.15)',
|
|
99
99
|
colorProgressTrack: 'rgba(255, 255, 255, 0.12)',
|
|
100
100
|
colorBgContainer: '#212225',
|
|
101
101
|
colorBgLayout: '#000000',
|
|
@@ -142,16 +142,32 @@ export function useXThemeMode() {
|
|
|
142
142
|
/**
|
|
143
143
|
* 【核心 Hook】当前模式下的完整 token 集。
|
|
144
144
|
*
|
|
145
|
-
* -
|
|
146
|
-
*
|
|
147
|
-
* -
|
|
148
|
-
*
|
|
145
|
+
* - 返回值按 (scheme × 品牌色) 缓存,**引用稳定**:同组合永远返回同一对象,
|
|
146
|
+
* 避免每次渲染新对象导致订阅组件级联重渲染(曾引发日历连点卡顿);
|
|
147
|
+
* - mode==='system' 时跟随 useColorScheme(),否则用强制值;
|
|
148
|
+
* - 注入运行时品牌色(zustand XBrand)覆盖 colorPrimary 等键。
|
|
149
149
|
*/
|
|
150
|
+
import { useXBrandStore } from './XTheme/BrandColor';
|
|
151
|
+
const themeCache = new Map();
|
|
150
152
|
export function useXTheme() {
|
|
151
153
|
const mode = useXThemeModeStore(s => s.mode);
|
|
152
154
|
const systemScheme = useColorScheme();
|
|
153
155
|
const scheme = mode === 'system' ? (systemScheme === 'dark' ? 'dark' : 'light') : mode;
|
|
154
|
-
|
|
156
|
+
const brand = useXBrandStore(s => s.brand);
|
|
157
|
+
const key = `${scheme}:${brand.name}`;
|
|
158
|
+
let cached = themeCache.get(key);
|
|
159
|
+
if (!cached) {
|
|
160
|
+
const base = scheme === 'dark' ? darkTokens : lightTokens;
|
|
161
|
+
cached = {
|
|
162
|
+
...base,
|
|
163
|
+
colorPrimary: scheme === 'dark' ? brand.primaryDark : brand.primary,
|
|
164
|
+
colorPrimaryActive: scheme === 'dark' ? brand.primaryDark : brand.primary,
|
|
165
|
+
colorPrimaryBg: brand.primaryBg,
|
|
166
|
+
colorPrimaryDisabled: scheme === 'dark' ? 'rgba(255,255,255,0.18)' : 'rgba(32,128,240,0.28)',
|
|
167
|
+
};
|
|
168
|
+
themeCache.set(key, cached);
|
|
169
|
+
}
|
|
170
|
+
return cached;
|
|
155
171
|
}
|
|
156
172
|
/** 当前实际生效的明暗方案(isDark 便捷判断用) */
|
|
157
173
|
export function useXThemeScheme() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-x-components",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Ant Design 风格的 React Native (Expo) 组件库:表单/弹层(TopView)/日历/Skia逐字签名/上传适配器(MinIO预签名)/轻量图表,内置暗黑模式与中英双语。",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"react-native": "dist/index.js",
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
-
"README.md"
|
|
11
|
+
"README.md",
|
|
12
|
+
"AGENTS.md",
|
|
13
|
+
"wechat-qrcode.png"
|
|
12
14
|
],
|
|
13
15
|
"sideEffects": false,
|
|
14
16
|
"keywords": [
|
|
@@ -31,16 +33,22 @@
|
|
|
31
33
|
"popup"
|
|
32
34
|
],
|
|
33
35
|
"license": "MIT",
|
|
36
|
+
"author": "KevinMao (maoyuxiang)",
|
|
34
37
|
"repository": {
|
|
35
38
|
"type": "git",
|
|
36
|
-
"url": "git+https://github.com/
|
|
39
|
+
"url": "git+https://github.com/TimSpan/rn-x-components.git"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/TimSpan/rn-x-components#readme",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/TimSpan/rn-x-components/issues"
|
|
37
44
|
},
|
|
38
45
|
"peerDependencies": {
|
|
39
46
|
"react": ">=18.0.0",
|
|
40
47
|
"react-native": ">=0.74.0",
|
|
41
48
|
"react-native-reanimated": ">=3.17.0",
|
|
42
49
|
"react-native-safe-area-context": ">=4.0.0",
|
|
43
|
-
"react-native-svg": ">=14.0.0"
|
|
50
|
+
"react-native-svg": ">=14.0.0",
|
|
51
|
+
"react-native-gesture-handler": ">=2.0.0"
|
|
44
52
|
},
|
|
45
53
|
"peerDependenciesMeta": {
|
|
46
54
|
"@shopify/react-native-skia": {
|
|
@@ -61,9 +69,6 @@
|
|
|
61
69
|
"expo-image-picker": {
|
|
62
70
|
"optional": true
|
|
63
71
|
},
|
|
64
|
-
"react-native-gesture-handler": {
|
|
65
|
-
"optional": true
|
|
66
|
-
},
|
|
67
72
|
"react-native-linear-gradient": {
|
|
68
73
|
"optional": true
|
|
69
74
|
},
|
|
Binary file
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ============================================================================
|
|
3
|
-
* XElevator —— 电梯楼层/字母导航列表(duxui Elevator 的 RN 原生重写版)
|
|
4
|
-
* ============================================================================
|
|
5
|
-
*
|
|
6
|
-
* 【与 duxui Elevator 的关系】
|
|
7
|
-
* duxui 用 ScrollView + Layout 手动累加每组高度算 scrollTop;
|
|
8
|
-
* RN 端 SectionList 原生支持分组吸顶 + scrollToLocation,
|
|
9
|
-
* 这里直接用 SectionList 重写:代码量减半、吸顶/定位更稳。
|
|
10
|
-
*
|
|
11
|
-
* 【右侧导航条】
|
|
12
|
-
* - 触摸(按下/滑动)定位:容器拦截 responder 事件,用 locationY 与
|
|
13
|
-
* 各标签 onLayout 记录的 y 区间比对,命中即 scrollToLocation;
|
|
14
|
-
* - 滚动联动高亮:onViewableItemsChanged 取第一个可见项所属 section。
|
|
15
|
-
*
|
|
16
|
-
* 【API 对标 duxui】list -> sections({title, data}[])、onItemClick、
|
|
17
|
-
* showNav、renderTop/Header/Footer/Empty;扩展 navLabel(导航条短标签,
|
|
18
|
-
* 默认取 title 首字符)、keyExtractor。
|
|
19
|
-
* ============================================================================
|
|
20
|
-
*/
|
|
21
|
-
import React from 'react';
|
|
22
|
-
import { StyleProp, ViewStyle } from 'react-native';
|
|
23
|
-
export interface XElevatorSection<T = {
|
|
24
|
-
name: string;
|
|
25
|
-
}> {
|
|
26
|
-
/** 分组标题(吸顶显示) */
|
|
27
|
-
title: string;
|
|
28
|
-
/** 右侧导航条短标签,默认取 title 首字符 */
|
|
29
|
-
navLabel?: string;
|
|
30
|
-
/** 数据 */
|
|
31
|
-
data: T[];
|
|
32
|
-
}
|
|
33
|
-
export interface XElevatorProps<T> {
|
|
34
|
-
sections: XElevatorSection<T>[];
|
|
35
|
-
/** 自定义行渲染,默认显示 item.name */
|
|
36
|
-
renderItem?: (item: T, section: XElevatorSection<T>, index: number) => React.ReactNode;
|
|
37
|
-
keyExtractor?: (item: T, index: number) => string;
|
|
38
|
-
onItemClick?: (item: T, section: XElevatorSection<T>) => void;
|
|
39
|
-
/** 是否显示右侧导航条,默认 true */
|
|
40
|
-
showNav?: boolean;
|
|
41
|
-
/** 列表顶部(搜索框等) */
|
|
42
|
-
renderTop?: () => React.ReactNode;
|
|
43
|
-
/** 列表底部 */
|
|
44
|
-
renderFooter?: () => React.ReactNode;
|
|
45
|
-
/** 空态 */
|
|
46
|
-
renderEmpty?: () => React.ReactNode;
|
|
47
|
-
style?: StyleProp<ViewStyle>;
|
|
48
|
-
}
|
|
49
|
-
export declare function XElevator<T = {
|
|
50
|
-
name: string;
|
|
51
|
-
}>({ sections, renderItem, keyExtractor, onItemClick, showNav, renderTop, renderFooter, renderEmpty, style, }: XElevatorProps<T>): React.JSX.Element;
|
|
52
|
-
export default XElevator;
|
package/dist/XElevator/index.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* ============================================================================
|
|
4
|
-
* XElevator —— 电梯楼层/字母导航列表(duxui Elevator 的 RN 原生重写版)
|
|
5
|
-
* ============================================================================
|
|
6
|
-
*
|
|
7
|
-
* 【与 duxui Elevator 的关系】
|
|
8
|
-
* duxui 用 ScrollView + Layout 手动累加每组高度算 scrollTop;
|
|
9
|
-
* RN 端 SectionList 原生支持分组吸顶 + scrollToLocation,
|
|
10
|
-
* 这里直接用 SectionList 重写:代码量减半、吸顶/定位更稳。
|
|
11
|
-
*
|
|
12
|
-
* 【右侧导航条】
|
|
13
|
-
* - 触摸(按下/滑动)定位:容器拦截 responder 事件,用 locationY 与
|
|
14
|
-
* 各标签 onLayout 记录的 y 区间比对,命中即 scrollToLocation;
|
|
15
|
-
* - 滚动联动高亮:onViewableItemsChanged 取第一个可见项所属 section。
|
|
16
|
-
*
|
|
17
|
-
* 【API 对标 duxui】list -> sections({title, data}[])、onItemClick、
|
|
18
|
-
* showNav、renderTop/Header/Footer/Empty;扩展 navLabel(导航条短标签,
|
|
19
|
-
* 默认取 title 首字符)、keyExtractor。
|
|
20
|
-
* ============================================================================
|
|
21
|
-
*/
|
|
22
|
-
import { useCallback, useRef, useState } from 'react';
|
|
23
|
-
import { SectionList, StyleSheet, Text, View, } from 'react-native';
|
|
24
|
-
import { useXTheme } from '../theme';
|
|
25
|
-
export function XElevator({ sections, renderItem, keyExtractor, onItemClick, showNav = true, renderTop, renderFooter, renderEmpty, style, }) {
|
|
26
|
-
const t = useXTheme();
|
|
27
|
-
const listRef = useRef(null);
|
|
28
|
-
/** 导航条每个标签的 y 中心(onLayout 记录,用于触摸命中) */
|
|
29
|
-
const navYRef = useRef([]);
|
|
30
|
-
/** 当前高亮的分组下标 */
|
|
31
|
-
const [activeIndex, setActiveIndex] = useState(0);
|
|
32
|
-
/** 滚动定位到分组 */
|
|
33
|
-
const scrollToSection = useCallback((index) => {
|
|
34
|
-
if (index < 0 || index >= sections.length)
|
|
35
|
-
return;
|
|
36
|
-
listRef.current?.scrollToLocation({ sectionIndex: index, itemIndex: 0, viewPosition: 0 });
|
|
37
|
-
}, [sections.length]);
|
|
38
|
-
/**
|
|
39
|
-
* 导航条触摸(按下/滑动):locationY 落在哪个标签区间 → 定位。
|
|
40
|
-
* onStartShouldSetResponder 抢占事件,滑动中 onResponderMove 连续命中。
|
|
41
|
-
*/
|
|
42
|
-
const handleNavTouch = useCallback((e) => {
|
|
43
|
-
const y = e.nativeEvent.locationY;
|
|
44
|
-
const ys = navYRef.current;
|
|
45
|
-
for (let i = ys.length - 1; i >= 0; i--) {
|
|
46
|
-
if (ys[i] !== undefined && y >= ys[i]) {
|
|
47
|
-
if (activeIndex !== i)
|
|
48
|
-
setActiveIndex(i);
|
|
49
|
-
scrollToSection(i);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
scrollToSection(0);
|
|
54
|
-
}, [activeIndex, scrollToSection]);
|
|
55
|
-
/** 第一个可见项所属分组 → 导航高亮 */
|
|
56
|
-
const handleViewableItemsChanged = useRef(({ viewableItems }) => {
|
|
57
|
-
const first = viewableItems.find(v => v.section);
|
|
58
|
-
if (!first?.section)
|
|
59
|
-
return;
|
|
60
|
-
const index = sections.findIndex(s => s.title === first.section.title);
|
|
61
|
-
if (index >= 0)
|
|
62
|
-
setActiveIndex(prev => (prev === index ? prev : index));
|
|
63
|
-
}).current;
|
|
64
|
-
/** 默认行渲染 */
|
|
65
|
-
const defaultRenderItem = (item, section, index) => (_jsx(Text, { style: [styles.itemText, { color: t.colorText }], children: item.name }));
|
|
66
|
-
/** SectionList 的行:包一层按压反馈 */
|
|
67
|
-
const renderRow = ({ item, section, index }) => {
|
|
68
|
-
const Row = renderItem ?? defaultRenderItem;
|
|
69
|
-
return (_jsx(View, { style: [styles.item, { borderBottomColor: t.colorSplit }], onTouchEnd: () => onItemClick?.(item, section), children: Row(item, section, index) }));
|
|
70
|
-
};
|
|
71
|
-
const hasData = sections.some(s => s.data.length > 0);
|
|
72
|
-
return (_jsxs(View, { style: [styles.container, style], children: [_jsxs(View, { style: styles.listWrap, children: [renderTop?.(), _jsx(SectionList, { ref: listRef, sections: sections, renderItem: renderRow, renderSectionHeader: ({ section }) => (_jsx(View, { style: [styles.sectionHeader, { backgroundColor: t.colorBgLayout }], children: _jsx(Text, { style: [styles.sectionTitle, { color: t.colorTextSecondary }], children: section.title }) })), keyExtractor: keyExtractor ?? ((item, index) => String(index)), stickySectionHeadersEnabled: true, onViewableItemsChanged: handleViewableItemsChanged, viewabilityConfig: { itemVisiblePercentThreshold: 10 }, ListEmptyComponent: renderEmpty ?? _jsx(Text, { style: [styles.empty, { color: t.colorTextTertiary }], children: "\u6682\u65E0\u6570\u636E" }), ListFooterComponent: renderFooter, getItemLayout: undefined, onScrollToIndexFailed: () => {
|
|
73
|
-
// 分组高度未测量完成时定位可能失败,兜底到顶部分组
|
|
74
|
-
listRef.current?.scrollToLocation({ sectionIndex: 0, itemIndex: 0, viewPosition: 0 });
|
|
75
|
-
} }), renderFooter?.()] }), showNav && hasData && (_jsx(View, { style: [styles.nav, { backgroundColor: t.colorBgContainerDisabled }], onStartShouldSetResponder: () => true, onMoveShouldSetResponder: () => true, onResponderGrant: handleNavTouch, onResponderMove: handleNavTouch, children: sections.map((section, i) => (_jsx(View, { onLayout: e => {
|
|
76
|
-
// 记录每个标签的"区间下界",命中判断用
|
|
77
|
-
navYRef.current[i] = e.nativeEvent.layout.y + e.nativeEvent.layout.height / 2;
|
|
78
|
-
}, style: [styles.navItem, activeIndex === i && styles.navItemActive], children: _jsx(Text, { style: [
|
|
79
|
-
styles.navText,
|
|
80
|
-
{ color: t.colorTextSecondary },
|
|
81
|
-
activeIndex === i && { color: t.colorTextLightSolid },
|
|
82
|
-
], allowFontScaling: false, children: section.navLabel ?? section.title.slice(0, 1) }) }, section.title + i))) }))] }));
|
|
83
|
-
}
|
|
84
|
-
const styles = StyleSheet.create({
|
|
85
|
-
container: {
|
|
86
|
-
flex: 1,
|
|
87
|
-
overflow: 'hidden',
|
|
88
|
-
position: 'relative',
|
|
89
|
-
},
|
|
90
|
-
listWrap: {
|
|
91
|
-
flex: 1,
|
|
92
|
-
},
|
|
93
|
-
sectionHeader: {
|
|
94
|
-
paddingHorizontal: 16,
|
|
95
|
-
paddingVertical: 6,
|
|
96
|
-
},
|
|
97
|
-
sectionTitle: {
|
|
98
|
-
fontSize: 13,
|
|
99
|
-
},
|
|
100
|
-
item: {
|
|
101
|
-
paddingHorizontal: 16,
|
|
102
|
-
paddingVertical: 12,
|
|
103
|
-
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
104
|
-
},
|
|
105
|
-
itemText: {
|
|
106
|
-
fontSize: 15,
|
|
107
|
-
},
|
|
108
|
-
empty: {
|
|
109
|
-
textAlign: 'center',
|
|
110
|
-
paddingVertical: 40,
|
|
111
|
-
fontSize: 14,
|
|
112
|
-
},
|
|
113
|
-
nav: {
|
|
114
|
-
position: 'absolute',
|
|
115
|
-
right: 4,
|
|
116
|
-
top: '20%',
|
|
117
|
-
borderRadius: 12,
|
|
118
|
-
paddingHorizontal: 5,
|
|
119
|
-
paddingVertical: 6,
|
|
120
|
-
alignItems: 'center',
|
|
121
|
-
},
|
|
122
|
-
navItem: {
|
|
123
|
-
paddingVertical: 2,
|
|
124
|
-
paddingHorizontal: 3,
|
|
125
|
-
borderRadius: 9,
|
|
126
|
-
minHeight: 18,
|
|
127
|
-
justifyContent: 'center',
|
|
128
|
-
},
|
|
129
|
-
navItemActive: {
|
|
130
|
-
backgroundColor: '#2080F0',
|
|
131
|
-
},
|
|
132
|
-
navText: {
|
|
133
|
-
fontSize: 11,
|
|
134
|
-
lineHeight: 14,
|
|
135
|
-
textAlign: 'center',
|
|
136
|
-
},
|
|
137
|
-
});
|
|
138
|
-
export default XElevator;
|