react-native-x-components 0.1.0 → 0.2.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.
@@ -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;