@lynx-example/scroll-coordinator 0.0.0 → 0.6.13

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/LICENSE CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  Apache License
3
2
  Version 2.0, January 2004
4
3
  http://www.apache.org/licenses/
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,33 @@
1
+ // Copyright 2024 The Lynx Authors. All rights reserved.
2
+ // Licensed under the Apache License Version 2.0 that can be found in the
3
+ // LICENSE file in the root directory of this source tree.
4
+
5
+ import { pluginQRCode } from "@lynx-js/qrcode-rsbuild-plugin";
6
+ import { pluginReactLynx } from "@lynx-js/react-rsbuild-plugin";
7
+ import { defineConfig } from "@lynx-js/rspeedy";
8
+ import { pluginSass } from "@rsbuild/plugin-sass";
9
+ import { pluginTypeCheck } from "@rsbuild/plugin-type-check";
10
+
11
+ export default defineConfig({
12
+ source: {
13
+ entry: {
14
+ base: "./src/base/index.tsx",
15
+ refresh: "./src/refresh/index.tsx",
16
+ "refresh-inner": "./src/refresh-inner/index.tsx",
17
+ viewpager: "./src/viewpager/index.tsx",
18
+ },
19
+ },
20
+ plugins: [
21
+ pluginReactLynx(),
22
+ pluginSass(),
23
+ pluginQRCode(),
24
+ pluginTypeCheck(),
25
+ ],
26
+ output: {
27
+ assetPrefix: "https://lynxjs.org/lynx-examples/scroll-coordinator/dist",
28
+ filename: "[name].[platform].bundle",
29
+ },
30
+ environments: {
31
+ lynx: {},
32
+ },
33
+ });
package/package.json CHANGED
@@ -1,9 +1,35 @@
1
1
  {
2
2
  "name": "@lynx-example/scroll-coordinator",
3
- "version": "0.0.0",
4
- "description": "",
5
- "keywords": [],
3
+ "version": "0.6.13",
4
+ "description": "An example shows how to use `<scroll-coordinator>` in Lynx",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/lynx-family/lynx-examples.git",
8
+ "directory": "examples/scroll-coordinator"
9
+ },
6
10
  "license": "Apache-2.0",
11
+ "author": "Lynx Authors",
7
12
  "type": "module",
8
- "main": "index.js"
9
- }
13
+ "files": [
14
+ "dist/",
15
+ "src/",
16
+ "lynx.config.mjs"
17
+ ],
18
+ "dependencies": {
19
+ "@lynx-js/react": "0.117.0"
20
+ },
21
+ "devDependencies": {
22
+ "@lynx-js/qrcode-rsbuild-plugin": "0.4.6",
23
+ "@lynx-js/react-rsbuild-plugin": "0.13.0",
24
+ "@lynx-js/rspeedy": "0.13.6",
25
+ "@lynx-js/types": "3.7.0",
26
+ "@rsbuild/plugin-sass": "1.5.1",
27
+ "@types/react": "^18.3.28",
28
+ "typescript": "~5.9.3"
29
+ },
30
+ "scripts": {
31
+ "build": "rspeedy build",
32
+ "dev": "rspeedy dev",
33
+ "preview": "rspeedy preview"
34
+ }
35
+ }
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,241 @@
1
+ // Copyright 2024 The Lynx Authors. All rights reserved.
2
+ // Licensed under the Apache License Version 2.0 that can be found in the
3
+ // LICENSE file in the root directory of this source tree.
4
+
5
+ import { root } from "@lynx-js/react";
6
+
7
+ import image0 from "../assets/item_0.jpg";
8
+ import image1 from "../assets/item_1.jpg";
9
+ import image2 from "../assets/item_2.jpg";
10
+
11
+ const App = () => {
12
+ const coverImages = [image0, image1, image2];
13
+ const tabs = ["Featured", "Nearby", "Saved"];
14
+ const listItems = Array.from({ length: 24 }, (_, index) => {
15
+ return {
16
+ image: coverImages[index % coverImages.length],
17
+ title: `Scenic stop ${index + 1}`,
18
+ subtitle: index % 2 === 0 ? "Quiet path with a wide lookout" : "Short trail near the water",
19
+ tag: index % 3 === 0 ? "Fresh" : (index % 3 === 1 ? "Calm" : "Open"),
20
+ };
21
+ });
22
+
23
+ return (
24
+ <view
25
+ style={{
26
+ width: "100%",
27
+ height: "100%",
28
+ flexDirection: "column",
29
+ display: "flex",
30
+ padding: "24px",
31
+ backgroundColor: "#F6F7F9",
32
+ boxSizing: "border-box",
33
+ }}
34
+ >
35
+ <text style={{ fontSize: "32px", fontWeight: "700", color: "#202124" }}>Scroll Coordinator</text>
36
+ <scroll-coordinator
37
+ style={{
38
+ marginTop: "24px",
39
+ width: "100%",
40
+ height: "100%",
41
+ flex: "1",
42
+ display: "flex",
43
+ flexDirection: "column",
44
+ position: "relative",
45
+ overflow: "hidden",
46
+ backgroundColor: "#EDEFF2",
47
+ }}
48
+ >
49
+ {
50
+ /*
51
+ Optional, fixed at the top of the scroll coordinator and won't collapse.
52
+ When it exists, control scroll-coordinator.height == slot.height + toolbar.height through CSS.
53
+ */
54
+ }
55
+ <scroll-coordinator-toolbar
56
+ style={{
57
+ display: "flex",
58
+ width: "100%",
59
+ height: "88rpx",
60
+ alignItems: "center",
61
+ justifyContent: "space-between",
62
+ padding: "0 24rpx",
63
+ backgroundColor: "#FFFFFFAA",
64
+ boxSizing: "border-box",
65
+ }}
66
+ >
67
+ <text style={{ fontSize: "26rpx", fontWeight: "700", color: "#202124" }}>Weekend Routes</text>
68
+ <text style={{ fontSize: "22rpx", color: "#2D7D46" }}>3 picks</text>
69
+ </scroll-coordinator-toolbar>
70
+
71
+ {
72
+ /*
73
+ Required, it can be scrolled in scroll coordinator without affecting the layout,
74
+ so it is recommended to set position: absolute.
75
+ No need to place scroll-view or list inside.
76
+ */
77
+ }
78
+ <scroll-coordinator-header
79
+ style={{
80
+ position: "absolute",
81
+ width: "100%",
82
+ height: "360rpx",
83
+ }}
84
+ >
85
+ <view
86
+ style={{
87
+ width: "100%",
88
+ height: "360rpx",
89
+ overflow: "hidden",
90
+ }}
91
+ >
92
+ <image
93
+ mode="aspectFill"
94
+ src={image0}
95
+ style={{
96
+ width: "100%",
97
+ height: "360rpx",
98
+ }}
99
+ />
100
+ <view
101
+ style={{
102
+ position: "absolute",
103
+ left: "0",
104
+ bottom: "0",
105
+ width: "100%",
106
+ padding: "24rpx",
107
+ backgroundColor: "#00000066",
108
+ boxSizing: "border-box",
109
+ }}
110
+ >
111
+ <text style={{ fontSize: "34rpx", fontWeight: "700", color: "#FFFFFF" }}>Forest Ridge</text>
112
+ <text style={{ marginTop: "8rpx", fontSize: "22rpx", color: "#FFFFFF" }}>
113
+ Sunrise views, shaded paths, and easy waypoints.
114
+ </text>
115
+ </view>
116
+ </view>
117
+ </scroll-coordinator-header>
118
+
119
+ {
120
+ /*
121
+ Required, its size must be the same as scroll coordinator's size.
122
+ If there is a toolbar, subtract its height.
123
+ After the header is completely collapsed, the slot will occupy the entire scroll coordinator viewport.
124
+ In its child nodes, scrolling can be achieved and can continue after the header is collapsed.
125
+ When placing scroll-view, add enable-new-nested on scroll-view to support nested scrolling interactions.
126
+ */
127
+ }
128
+ <scroll-coordinator-slot
129
+ style={{
130
+ width: "100%",
131
+ flexDirection: "column",
132
+ flex: "1",
133
+ display: "flex",
134
+ }}
135
+ >
136
+ {
137
+ /*
138
+ Optional sticky area. When the header is completely collapsed, it will stay at the top of the slot.
139
+ You can place Tabs modules or any other content here.
140
+ By default it accepts up and down dragging gestures. Use enable-drag to control it.
141
+ */
142
+ }
143
+ <scroll-coordinator-slot-drag style={{ width: "100%" }}>
144
+ <view
145
+ style={{
146
+ width: "100%",
147
+ height: "96rpx",
148
+ display: "flex",
149
+ flexDirection: "row",
150
+ alignItems: "center",
151
+ padding: "0 16rpx",
152
+ backgroundColor: "#FFFFFF",
153
+ boxSizing: "border-box",
154
+ }}
155
+ >
156
+ {tabs.map((tab, index) => {
157
+ return (
158
+ <view
159
+ style={{
160
+ height: "56rpx",
161
+ padding: "0 20rpx",
162
+ marginRight: "12rpx",
163
+ justifyContent: "center",
164
+ borderRadius: "8px",
165
+ backgroundColor: index === 0 ? "#DDF3E5" : "#EEF1F4",
166
+ }}
167
+ >
168
+ <text style={{ fontSize: "22rpx", color: index === 0 ? "#1F7A4D" : "#4E5969" }}>{tab}</text>
169
+ </view>
170
+ );
171
+ })}
172
+ </view>
173
+ </scroll-coordinator-slot-drag>
174
+ <list
175
+ scroll-orientation="vertical"
176
+ list-type="single"
177
+ span-count={1}
178
+ style={{
179
+ width: "100%",
180
+ height: "100%",
181
+ flex: "1",
182
+ listMainAxisGap: "12px",
183
+ padding: "12px",
184
+ boxSizing: "border-box",
185
+ }}
186
+ >
187
+ {listItems.map((item) => {
188
+ return (
189
+ <list-item
190
+ item-key={item.title}
191
+ key={item.title}
192
+ >
193
+ <view
194
+ style={{
195
+ width: "100%",
196
+ height: "144rpx",
197
+ display: "flex",
198
+ flexDirection: "row",
199
+ alignItems: "center",
200
+ padding: "12rpx",
201
+ borderRadius: "8px",
202
+ backgroundColor: "#FFFFFF",
203
+ boxSizing: "border-box",
204
+ }}
205
+ >
206
+ <image
207
+ mode="aspectFill"
208
+ src={item.image}
209
+ style={{
210
+ width: "120rpx",
211
+ height: "120rpx",
212
+ borderRadius: "8px",
213
+ }}
214
+ />
215
+ <view
216
+ style={{
217
+ marginLeft: "18rpx",
218
+ flex: 1,
219
+ flexDirection: "column",
220
+ }}
221
+ >
222
+ <text style={{ fontSize: "28rpx", fontWeight: "700", color: "#202124" }}>{item.title}</text>
223
+ <text style={{ marginTop: "8rpx", fontSize: "22rpx", color: "#5F6673" }}>{item.subtitle}</text>
224
+ <text style={{ marginTop: "10rpx", fontSize: "20rpx", color: "#2D7D46" }}>{item.tag}</text>
225
+ </view>
226
+ </view>
227
+ </list-item>
228
+ );
229
+ })}
230
+ </list>
231
+ </scroll-coordinator-slot>
232
+ </scroll-coordinator>
233
+ </view>
234
+ );
235
+ };
236
+
237
+ root.render(<App />);
238
+
239
+ if (import.meta.webpackHot) {
240
+ import.meta.webpackHot.accept();
241
+ }
@@ -0,0 +1,303 @@
1
+ // Copyright 2024 The Lynx Authors. All rights reserved.
2
+ // Licensed under the Apache License Version 2.0 that can be found in the
3
+ // LICENSE file in the root directory of this source tree.
4
+
5
+ import { root, useRef, useState } from "@lynx-js/react";
6
+ import type { NodesRef } from "@lynx-js/types";
7
+
8
+ import loadingGif from "../assets/bg_flower.gif";
9
+ import image0 from "../assets/item_0.jpg";
10
+ import image1 from "../assets/item_1.jpg";
11
+ import image2 from "../assets/item_2.jpg";
12
+
13
+ const App = () => {
14
+ const refreshRef = useRef<NodesRef>(null);
15
+ const [currentPage, setCurrentPage] = useState(0);
16
+ const coverImages = [image0, image1, image2];
17
+ const tabs = ["Featured", "Nearby", "Saved"];
18
+ const listPages = ["A", "B", "C"];
19
+ const listItems = Array.from({ length: 24 }, (_, index) => {
20
+ return {
21
+ image: coverImages[index % coverImages.length],
22
+ title: `Scenic stop ${index + 1}`,
23
+ subtitle: index % 2 === 0 ? "Quiet path with a wide lookout" : "Short trail near the water",
24
+ tag: index % 3 === 0 ? "Fresh" : (index % 3 === 1 ? "Calm" : "Open"),
25
+ };
26
+ });
27
+
28
+ const onStartRefresh = () => {
29
+ setTimeout(() => {
30
+ refreshRef.current?.invoke({
31
+ method: "finishRefresh",
32
+ }).exec();
33
+ }, 2000);
34
+ };
35
+
36
+ return (
37
+ <view
38
+ style={{
39
+ width: "100%",
40
+ height: "100%",
41
+ flexDirection: "column",
42
+ display: "flex",
43
+ padding: "24px",
44
+ backgroundColor: "#F6F7F9",
45
+ boxSizing: "border-box",
46
+ }}
47
+ >
48
+ <text style={{ fontSize: "32px", fontWeight: "700", color: "#202124" }}>Scroll Coordinator</text>
49
+ <refresh
50
+ ref={refreshRef}
51
+ bindstartrefresh={onStartRefresh}
52
+ style={{
53
+ marginTop: "24px",
54
+ width: "100%",
55
+ height: "100%",
56
+ flex: "1",
57
+ }}
58
+ >
59
+ <refresh-header
60
+ style={{
61
+ width: "100%",
62
+ height: "104rpx",
63
+ position: "absolute",
64
+ justifyContent: "center",
65
+ alignItems: "center",
66
+ backgroundColor: "#F6F7F9",
67
+ }}
68
+ >
69
+ <image
70
+ mode="aspectFit"
71
+ src={loadingGif}
72
+ style={{
73
+ width: "48rpx",
74
+ height: "48rpx",
75
+ }}
76
+ />
77
+ <text style={{ marginTop: "8rpx", fontSize: "20rpx", color: "#4E5969" }}>Refreshing routes...</text>
78
+ </refresh-header>
79
+ <scroll-coordinator
80
+ refresh-mode="fold"
81
+ style={{
82
+ width: "100%",
83
+ height: "100%",
84
+ flex: "1",
85
+ display: "flex",
86
+ flexDirection: "column",
87
+ position: "relative",
88
+ overflow: "hidden",
89
+ backgroundColor: "#EDEFF2",
90
+ }}
91
+ >
92
+ {
93
+ /*
94
+ Required, it can be scrolled in scroll coordinator without affecting the layout,
95
+ so it is recommended to set position: absolute.
96
+ No need to place scroll-view or list inside.
97
+ */
98
+ }
99
+ <scroll-coordinator-header
100
+ style={{
101
+ position: "absolute",
102
+ width: "100%",
103
+ height: "360rpx",
104
+ }}
105
+ >
106
+ <view
107
+ style={{
108
+ width: "100%",
109
+ height: "360rpx",
110
+ overflow: "hidden",
111
+ }}
112
+ >
113
+ <image
114
+ mode="aspectFill"
115
+ src={image0}
116
+ style={{
117
+ width: "100%",
118
+ height: "360rpx",
119
+ }}
120
+ />
121
+ <view
122
+ style={{
123
+ position: "absolute",
124
+ left: "0",
125
+ bottom: "0",
126
+ width: "100%",
127
+ padding: "24rpx",
128
+ backgroundColor: "#00000066",
129
+ boxSizing: "border-box",
130
+ }}
131
+ >
132
+ <text style={{ fontSize: "34rpx", fontWeight: "700", color: "#FFFFFF" }}>Forest Ridge</text>
133
+ <text style={{ marginTop: "8rpx", fontSize: "22rpx", color: "#FFFFFF" }}>
134
+ Sunrise views, shaded paths, and easy waypoints.
135
+ </text>
136
+ </view>
137
+ </view>
138
+ </scroll-coordinator-header>
139
+
140
+ {
141
+ /*
142
+ Required, its size must be the same as scroll coordinator's size.
143
+ If there is a toolbar, subtract its height.
144
+ After the header is completely collapsed, the slot will occupy the entire scroll coordinator viewport.
145
+ In its child nodes, scrolling can be achieved and can continue after the header is collapsed.
146
+ When placing scroll-view, add enable-new-nested on scroll-view to support nested scrolling interactions.
147
+ */
148
+ }
149
+ <scroll-coordinator-slot
150
+ style={{
151
+ width: "100%",
152
+ flexDirection: "column",
153
+ flex: "1",
154
+ display: "flex",
155
+ }}
156
+ >
157
+ {
158
+ /*
159
+ Optional sticky area. When the header is completely collapsed, it will stay at the top of the slot.
160
+ You can place Tabs modules or any other content here.
161
+ By default it accepts up and down dragging gestures. Use enable-drag to control it.
162
+ */
163
+ }
164
+ <scroll-coordinator-slot-drag style={{ width: "100%" }}>
165
+ <view
166
+ style={{
167
+ width: "100%",
168
+ height: "96rpx",
169
+ display: "flex",
170
+ flexDirection: "row",
171
+ alignItems: "center",
172
+ padding: "0 16rpx",
173
+ backgroundColor: "#FFFFFF",
174
+ boxSizing: "border-box",
175
+ }}
176
+ >
177
+ {tabs.map((tab, index) => {
178
+ const selected = currentPage === index;
179
+
180
+ return (
181
+ <view
182
+ style={{
183
+ height: "56rpx",
184
+ padding: "0 20rpx",
185
+ marginRight: "12rpx",
186
+ justifyContent: "center",
187
+ borderRadius: "8px",
188
+ backgroundColor: selected ? "#DDF3E5" : "#EEF1F4",
189
+ }}
190
+ >
191
+ <text
192
+ style={{
193
+ fontSize: "22rpx",
194
+ fontWeight: selected ? "700" : "400",
195
+ color: selected ? "#1F7A4D" : "#4E5969",
196
+ }}
197
+ >
198
+ {tab}
199
+ </text>
200
+ </view>
201
+ );
202
+ })}
203
+ </view>
204
+ </scroll-coordinator-slot-drag>
205
+ <viewpager
206
+ style={{
207
+ width: "100%",
208
+ height: "100%",
209
+ flex: "1",
210
+ display: "flex",
211
+ flexDirection: "row",
212
+ }}
213
+ bindwillchange={(e) => {
214
+ setCurrentPage(e.detail.index);
215
+ }}
216
+ >
217
+ {listPages.map((page) => {
218
+ return (
219
+ <viewpager-item
220
+ style={{
221
+ width: "100%",
222
+ height: "100%",
223
+ flexShrink: 0,
224
+ }}
225
+ >
226
+ <list
227
+ scroll-orientation="vertical"
228
+ list-type="single"
229
+ span-count={1}
230
+ style={{
231
+ width: "100%",
232
+ height: "100%",
233
+ listMainAxisGap: "12px",
234
+ padding: "12px",
235
+ boxSizing: "border-box",
236
+ }}
237
+ >
238
+ {listItems.map((item, index) => {
239
+ return (
240
+ <list-item
241
+ item-key={`list-${page}-item-${index}`}
242
+ key={`list-${page}-item-${index}`}
243
+ >
244
+ <view
245
+ style={{
246
+ width: "100%",
247
+ height: "144rpx",
248
+ display: "flex",
249
+ flexDirection: "row",
250
+ alignItems: "center",
251
+ padding: "12rpx",
252
+ borderRadius: "8px",
253
+ backgroundColor: "#FFFFFF",
254
+ boxSizing: "border-box",
255
+ }}
256
+ >
257
+ <image
258
+ mode="aspectFill"
259
+ src={item.image}
260
+ style={{
261
+ width: "120rpx",
262
+ height: "120rpx",
263
+ borderRadius: "8px",
264
+ }}
265
+ />
266
+ <view
267
+ style={{
268
+ marginLeft: "18rpx",
269
+ flex: 1,
270
+ flexDirection: "column",
271
+ }}
272
+ >
273
+ <text style={{ fontSize: "28rpx", fontWeight: "700", color: "#202124" }}>
274
+ {`${item.title} ${page}`}
275
+ </text>
276
+ <text style={{ marginTop: "8rpx", fontSize: "22rpx", color: "#5F6673" }}>
277
+ {item.subtitle}
278
+ </text>
279
+ <text style={{ marginTop: "10rpx", fontSize: "20rpx", color: "#2D7D46" }}>
280
+ {item.tag}
281
+ </text>
282
+ </view>
283
+ </view>
284
+ </list-item>
285
+ );
286
+ })}
287
+ </list>
288
+ </viewpager-item>
289
+ );
290
+ })}
291
+ </viewpager>
292
+ </scroll-coordinator-slot>
293
+ </scroll-coordinator>
294
+ </refresh>
295
+ </view>
296
+ );
297
+ };
298
+
299
+ root.render(<App />);
300
+
301
+ if (import.meta.webpackHot) {
302
+ import.meta.webpackHot.accept();
303
+ }
@@ -0,0 +1,309 @@
1
+ // Copyright 2024 The Lynx Authors. All rights reserved.
2
+ // Licensed under the Apache License Version 2.0 that can be found in the
3
+ // LICENSE file in the root directory of this source tree.
4
+
5
+ import { root, useRef, useState } from "@lynx-js/react";
6
+ import type { NodesRef } from "@lynx-js/types";
7
+
8
+ import loadingGif from "../assets/bg_flower.gif";
9
+ import image0 from "../assets/item_0.jpg";
10
+ import image1 from "../assets/item_1.jpg";
11
+ import image2 from "../assets/item_2.jpg";
12
+
13
+ const App = () => {
14
+ const refreshRefs = useRef<Record<string, NodesRef | null>>({});
15
+ const [currentPage, setCurrentPage] = useState(0);
16
+ const coverImages = [image0, image1, image2];
17
+ const tabs = ["Featured", "Nearby", "Saved"];
18
+ const listPages = ["A", "B", "C"];
19
+ const listItems = Array.from({ length: 24 }, (_, index) => {
20
+ return {
21
+ image: coverImages[index % coverImages.length],
22
+ title: `Scenic stop ${index + 1}`,
23
+ subtitle: index % 2 === 0 ? "Quiet path with a wide lookout" : "Short trail near the water",
24
+ tag: index % 3 === 0 ? "Fresh" : (index % 3 === 1 ? "Calm" : "Open"),
25
+ };
26
+ });
27
+
28
+ const onStartRefresh = (page: string) => {
29
+ setTimeout(() => {
30
+ refreshRefs.current[page]?.invoke({
31
+ method: "finishRefresh",
32
+ }).exec();
33
+ }, 2000);
34
+ };
35
+
36
+ return (
37
+ <view
38
+ style={{
39
+ width: "100%",
40
+ height: "100%",
41
+ flexDirection: "column",
42
+ display: "flex",
43
+ padding: "24px",
44
+ backgroundColor: "#F6F7F9",
45
+ boxSizing: "border-box",
46
+ }}
47
+ >
48
+ <text style={{ fontSize: "32px", fontWeight: "700", color: "#202124" }}>Scroll Coordinator</text>
49
+ <scroll-coordinator
50
+ bounces={false}
51
+ refresh-mode="page"
52
+ style={{
53
+ marginTop: "24px",
54
+ width: "100%",
55
+ height: "100%",
56
+ flex: "1",
57
+ display: "flex",
58
+ flexDirection: "column",
59
+ position: "relative",
60
+ overflow: "hidden",
61
+ backgroundColor: "#EDEFF2",
62
+ }}
63
+ >
64
+ {
65
+ /*
66
+ Required, it can be scrolled in scroll coordinator without affecting the layout,
67
+ so it is recommended to set position: absolute.
68
+ No need to place scroll-view or list inside.
69
+ */
70
+ }
71
+ <scroll-coordinator-header
72
+ style={{
73
+ position: "absolute",
74
+ width: "100%",
75
+ height: "360rpx",
76
+ }}
77
+ >
78
+ <view
79
+ style={{
80
+ width: "100%",
81
+ height: "360rpx",
82
+ overflow: "hidden",
83
+ }}
84
+ >
85
+ <image
86
+ mode="aspectFill"
87
+ src={image0}
88
+ style={{
89
+ width: "100%",
90
+ height: "360rpx",
91
+ }}
92
+ />
93
+ <view
94
+ style={{
95
+ position: "absolute",
96
+ left: "0",
97
+ bottom: "0",
98
+ width: "100%",
99
+ padding: "24rpx",
100
+ backgroundColor: "#00000066",
101
+ boxSizing: "border-box",
102
+ }}
103
+ >
104
+ <text style={{ fontSize: "34rpx", fontWeight: "700", color: "#FFFFFF" }}>Forest Ridge</text>
105
+ <text style={{ marginTop: "8rpx", fontSize: "22rpx", color: "#FFFFFF" }}>
106
+ Sunrise views, shaded paths, and easy waypoints.
107
+ </text>
108
+ </view>
109
+ </view>
110
+ </scroll-coordinator-header>
111
+
112
+ {
113
+ /*
114
+ Required, its size must be the same as scroll coordinator's size.
115
+ If there is a toolbar, subtract its height.
116
+ After the header is completely collapsed, the slot will occupy the entire scroll coordinator viewport.
117
+ In its child nodes, scrolling can be achieved and can continue after the header is collapsed.
118
+ When placing scroll-view, add enable-new-nested on scroll-view to support nested scrolling interactions.
119
+ */
120
+ }
121
+ <scroll-coordinator-slot
122
+ style={{
123
+ width: "100%",
124
+ flexDirection: "column",
125
+ flex: "1",
126
+ display: "flex",
127
+ }}
128
+ >
129
+ {
130
+ /*
131
+ Optional sticky area. When the header is completely collapsed, it will stay at the top of the slot.
132
+ You can place Tabs modules or any other content here.
133
+ By default it accepts up and down dragging gestures. Use enable-drag to control it.
134
+ */
135
+ }
136
+ <scroll-coordinator-slot-drag style={{ width: "100%" }}>
137
+ <view
138
+ style={{
139
+ width: "100%",
140
+ height: "96rpx",
141
+ display: "flex",
142
+ flexDirection: "row",
143
+ alignItems: "center",
144
+ padding: "0 16rpx",
145
+ backgroundColor: "#FFFFFF",
146
+ boxSizing: "border-box",
147
+ }}
148
+ >
149
+ {tabs.map((tab, index) => {
150
+ const selected = currentPage === index;
151
+
152
+ return (
153
+ <view
154
+ style={{
155
+ height: "56rpx",
156
+ padding: "0 20rpx",
157
+ marginRight: "12rpx",
158
+ justifyContent: "center",
159
+ borderRadius: "8px",
160
+ backgroundColor: selected ? "#DDF3E5" : "#EEF1F4",
161
+ }}
162
+ >
163
+ <text
164
+ style={{
165
+ fontSize: "22rpx",
166
+ fontWeight: selected ? "700" : "400",
167
+ color: selected ? "#1F7A4D" : "#4E5969",
168
+ }}
169
+ >
170
+ {tab}
171
+ </text>
172
+ </view>
173
+ );
174
+ })}
175
+ </view>
176
+ </scroll-coordinator-slot-drag>
177
+ <viewpager
178
+ style={{
179
+ width: "100%",
180
+ height: "100%",
181
+ flex: "1",
182
+ display: "flex",
183
+ flexDirection: "row",
184
+ }}
185
+ bindwillchange={(e) => {
186
+ setCurrentPage(e.detail.index);
187
+ }}
188
+ >
189
+ {listPages.map((page) => {
190
+ return (
191
+ <viewpager-item
192
+ style={{
193
+ width: "100%",
194
+ height: "100%",
195
+ flexShrink: 0,
196
+ }}
197
+ >
198
+ <refresh
199
+ ref={(element: NodesRef | null) => {
200
+ refreshRefs.current[page] = element;
201
+ }}
202
+ bindstartrefresh={() => {
203
+ onStartRefresh(page);
204
+ }}
205
+ style={{
206
+ width: "100%",
207
+ height: "100%",
208
+ }}
209
+ >
210
+ <refresh-header
211
+ style={{
212
+ width: "100%",
213
+ height: "104rpx",
214
+ position: "absolute",
215
+ justifyContent: "center",
216
+ alignItems: "center",
217
+ backgroundColor: "#F6F7F9",
218
+ }}
219
+ >
220
+ <image
221
+ mode="aspectFit"
222
+ src={loadingGif}
223
+ style={{
224
+ width: "48rpx",
225
+ height: "48rpx",
226
+ }}
227
+ />
228
+ <text style={{ marginTop: "8rpx", fontSize: "20rpx", color: "#4E5969" }}>
229
+ Refreshing routes...
230
+ </text>
231
+ </refresh-header>
232
+ <list
233
+ scroll-orientation="vertical"
234
+ list-type="single"
235
+ span-count={1}
236
+ style={{
237
+ width: "100%",
238
+ height: "100%",
239
+ listMainAxisGap: "12px",
240
+ padding: "12px",
241
+ boxSizing: "border-box",
242
+ }}
243
+ >
244
+ {listItems.map((item, index) => {
245
+ return (
246
+ <list-item
247
+ item-key={`list-${page}-item-${index}`}
248
+ key={`list-${page}-item-${index}`}
249
+ >
250
+ <view
251
+ style={{
252
+ width: "100%",
253
+ height: "144rpx",
254
+ display: "flex",
255
+ flexDirection: "row",
256
+ alignItems: "center",
257
+ padding: "12rpx",
258
+ borderRadius: "8px",
259
+ backgroundColor: "#FFFFFF",
260
+ boxSizing: "border-box",
261
+ }}
262
+ >
263
+ <image
264
+ mode="aspectFill"
265
+ src={item.image}
266
+ style={{
267
+ width: "120rpx",
268
+ height: "120rpx",
269
+ borderRadius: "8px",
270
+ }}
271
+ />
272
+ <view
273
+ style={{
274
+ marginLeft: "18rpx",
275
+ flex: 1,
276
+ flexDirection: "column",
277
+ }}
278
+ >
279
+ <text style={{ fontSize: "28rpx", fontWeight: "700", color: "#202124" }}>
280
+ {`${item.title} ${page}`}
281
+ </text>
282
+ <text style={{ marginTop: "8rpx", fontSize: "22rpx", color: "#5F6673" }}>
283
+ {item.subtitle}
284
+ </text>
285
+ <text style={{ marginTop: "10rpx", fontSize: "20rpx", color: "#2D7D46" }}>
286
+ {item.tag}
287
+ </text>
288
+ </view>
289
+ </view>
290
+ </list-item>
291
+ );
292
+ })}
293
+ </list>
294
+ </refresh>
295
+ </viewpager-item>
296
+ );
297
+ })}
298
+ </viewpager>
299
+ </scroll-coordinator-slot>
300
+ </scroll-coordinator>
301
+ </view>
302
+ );
303
+ };
304
+
305
+ root.render(<App />);
306
+
307
+ if (import.meta.webpackHot) {
308
+ import.meta.webpackHot.accept();
309
+ }
@@ -0,0 +1,283 @@
1
+ // Copyright 2024 The Lynx Authors. All rights reserved.
2
+ // Licensed under the Apache License Version 2.0 that can be found in the
3
+ // LICENSE file in the root directory of this source tree.
4
+
5
+ import { root, useState } from "@lynx-js/react";
6
+
7
+ import image0 from "../assets/item_0.jpg";
8
+ import image1 from "../assets/item_1.jpg";
9
+ import image2 from "../assets/item_2.jpg";
10
+
11
+ const App = () => {
12
+ const [currentPage, setCurrentPage] = useState(0);
13
+ const coverImages = [image0, image1, image2];
14
+ const tabs = ["Featured", "Nearby", "Saved"];
15
+ const listPages = ["A", "B", "C"];
16
+ const listItems = Array.from({ length: 24 }, (_, index) => {
17
+ return {
18
+ image: coverImages[index % coverImages.length],
19
+ title: `Scenic stop ${index + 1}`,
20
+ subtitle: index % 2 === 0 ? "Quiet path with a wide lookout" : "Short trail near the water",
21
+ tag: index % 3 === 0 ? "Fresh" : (index % 3 === 1 ? "Calm" : "Open"),
22
+ };
23
+ });
24
+
25
+ return (
26
+ <view
27
+ style={{
28
+ width: "100%",
29
+ height: "100%",
30
+ flexDirection: "column",
31
+ display: "flex",
32
+ padding: "24px",
33
+ backgroundColor: "#F6F7F9",
34
+ boxSizing: "border-box",
35
+ }}
36
+ >
37
+ <text style={{ fontSize: "32px", fontWeight: "700", color: "#202124" }}>Scroll Coordinator</text>
38
+ <scroll-coordinator
39
+ style={{
40
+ marginTop: "24px",
41
+ width: "100%",
42
+ height: "100%",
43
+ flex: "1",
44
+ display: "flex",
45
+ flexDirection: "column",
46
+ position: "relative",
47
+ overflow: "hidden",
48
+ backgroundColor: "#EDEFF2",
49
+ }}
50
+ >
51
+ {
52
+ /*
53
+ Optional, fixed at the top of the scroll coordinator and won't collapse.
54
+ When it exists, control scroll-coordinator.height == slot.height + toolbar.height through CSS.
55
+ */
56
+ }
57
+ <scroll-coordinator-toolbar
58
+ style={{
59
+ display: "flex",
60
+ width: "100%",
61
+ height: "88rpx",
62
+ alignItems: "center",
63
+ justifyContent: "space-between",
64
+ padding: "0 24rpx",
65
+ backgroundColor: "#FFFFFFAA",
66
+ boxSizing: "border-box",
67
+ }}
68
+ >
69
+ <text style={{ fontSize: "26rpx", fontWeight: "700", color: "#202124" }}>Weekend Routes</text>
70
+ <text style={{ fontSize: "22rpx", color: "#2D7D46" }}>3 lists</text>
71
+ </scroll-coordinator-toolbar>
72
+
73
+ {
74
+ /*
75
+ Required, it can be scrolled in scroll coordinator without affecting the layout,
76
+ so it is recommended to set position: absolute.
77
+ No need to place scroll-view or list inside.
78
+ */
79
+ }
80
+ <scroll-coordinator-header
81
+ style={{
82
+ position: "absolute",
83
+ width: "100%",
84
+ height: "360rpx",
85
+ }}
86
+ >
87
+ <view
88
+ style={{
89
+ width: "100%",
90
+ height: "360rpx",
91
+ overflow: "hidden",
92
+ }}
93
+ >
94
+ <image
95
+ mode="aspectFill"
96
+ src={image0}
97
+ style={{
98
+ width: "100%",
99
+ height: "360rpx",
100
+ }}
101
+ />
102
+ <view
103
+ style={{
104
+ position: "absolute",
105
+ left: "0",
106
+ bottom: "0",
107
+ width: "100%",
108
+ padding: "24rpx",
109
+ backgroundColor: "#00000066",
110
+ boxSizing: "border-box",
111
+ }}
112
+ >
113
+ <text style={{ fontSize: "34rpx", fontWeight: "700", color: "#FFFFFF" }}>Forest Ridge</text>
114
+ <text style={{ marginTop: "8rpx", fontSize: "22rpx", color: "#FFFFFF" }}>
115
+ Sunrise views, shaded paths, and easy waypoints.
116
+ </text>
117
+ </view>
118
+ </view>
119
+ </scroll-coordinator-header>
120
+
121
+ {
122
+ /*
123
+ Required, its size must be the same as scroll coordinator's size.
124
+ If there is a toolbar, subtract its height.
125
+ After the header is completely collapsed, the slot will occupy the entire scroll coordinator viewport.
126
+ In its child nodes, scrolling can be achieved and can continue after the header is collapsed.
127
+ When placing scroll-view, add enable-new-nested on scroll-view to support nested scrolling interactions.
128
+ */
129
+ }
130
+ <scroll-coordinator-slot
131
+ style={{
132
+ width: "100%",
133
+ flexDirection: "column",
134
+ flex: "1",
135
+ display: "flex",
136
+ }}
137
+ >
138
+ {
139
+ /*
140
+ Optional sticky area. When the header is completely collapsed, it will stay at the top of the slot.
141
+ You can place Tabs modules or any other content here.
142
+ By default it accepts up and down dragging gestures. Use enable-drag to control it.
143
+ */
144
+ }
145
+ <scroll-coordinator-slot-drag style={{ width: "100%" }}>
146
+ <view
147
+ style={{
148
+ width: "100%",
149
+ height: "96rpx",
150
+ display: "flex",
151
+ flexDirection: "row",
152
+ alignItems: "center",
153
+ padding: "0 16rpx",
154
+ backgroundColor: "#FFFFFF",
155
+ boxSizing: "border-box",
156
+ }}
157
+ >
158
+ {tabs.map((tab, index) => {
159
+ const selected = currentPage === index;
160
+
161
+ return (
162
+ <view
163
+ style={{
164
+ height: "56rpx",
165
+ padding: "0 20rpx",
166
+ marginRight: "12rpx",
167
+ justifyContent: "center",
168
+ borderRadius: "8px",
169
+ backgroundColor: selected ? "#DDF3E5" : "#EEF1F4",
170
+ }}
171
+ >
172
+ <text
173
+ style={{
174
+ fontSize: "22rpx",
175
+ fontWeight: selected ? "700" : "400",
176
+ color: selected ? "#1F7A4D" : "#4E5969",
177
+ }}
178
+ >
179
+ {tab}
180
+ </text>
181
+ </view>
182
+ );
183
+ })}
184
+ </view>
185
+ </scroll-coordinator-slot-drag>
186
+ <viewpager
187
+ style={{
188
+ width: "100%",
189
+ height: "100%",
190
+ flex: "1",
191
+ display: "flex",
192
+ flexDirection: "row",
193
+ }}
194
+ bindwillchange={(e) => {
195
+ setCurrentPage(e.detail.index);
196
+ }}
197
+ >
198
+ {listPages.map((page) => {
199
+ return (
200
+ <viewpager-item
201
+ style={{
202
+ width: "100%",
203
+ height: "100%",
204
+ flexShrink: 0,
205
+ }}
206
+ >
207
+ <list
208
+ scroll-orientation="vertical"
209
+ list-type="single"
210
+ span-count={1}
211
+ style={{
212
+ width: "100%",
213
+ height: "100%",
214
+ listMainAxisGap: "12px",
215
+ padding: "12px",
216
+ boxSizing: "border-box",
217
+ }}
218
+ >
219
+ {listItems.map((item, index) => {
220
+ return (
221
+ <list-item
222
+ item-key={`list-${page}-item-${index}`}
223
+ key={`list-${page}-item-${index}`}
224
+ >
225
+ <view
226
+ style={{
227
+ width: "100%",
228
+ height: "144rpx",
229
+ display: "flex",
230
+ flexDirection: "row",
231
+ alignItems: "center",
232
+ padding: "12rpx",
233
+ borderRadius: "8px",
234
+ backgroundColor: "#FFFFFF",
235
+ boxSizing: "border-box",
236
+ }}
237
+ >
238
+ <image
239
+ mode="aspectFill"
240
+ src={item.image}
241
+ style={{
242
+ width: "120rpx",
243
+ height: "120rpx",
244
+ borderRadius: "8px",
245
+ }}
246
+ />
247
+ <view
248
+ style={{
249
+ marginLeft: "18rpx",
250
+ flex: 1,
251
+ flexDirection: "column",
252
+ }}
253
+ >
254
+ <text style={{ fontSize: "28rpx", fontWeight: "700", color: "#202124" }}>
255
+ {`${item.title} ${page}`}
256
+ </text>
257
+ <text style={{ marginTop: "8rpx", fontSize: "22rpx", color: "#5F6673" }}>
258
+ {item.subtitle}
259
+ </text>
260
+ <text style={{ marginTop: "10rpx", fontSize: "20rpx", color: "#2D7D46" }}>
261
+ {item.tag}
262
+ </text>
263
+ </view>
264
+ </view>
265
+ </list-item>
266
+ );
267
+ })}
268
+ </list>
269
+ </viewpager-item>
270
+ );
271
+ })}
272
+ </viewpager>
273
+ </scroll-coordinator-slot>
274
+ </scroll-coordinator>
275
+ </view>
276
+ );
277
+ };
278
+
279
+ root.render(<App />);
280
+
281
+ if (import.meta.webpackHot) {
282
+ import.meta.webpackHot.accept();
283
+ }
package/index.js DELETED
@@ -1 +0,0 @@
1
- export {}