@nebula-rn/components 0.0.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.
Files changed (56) hide show
  1. package/dist/Camera/index.d.ts +31 -0
  2. package/dist/Camera/index.js +96 -0
  3. package/dist/Map/index.d.ts +86 -0
  4. package/dist/Map/index.js +83 -0
  5. package/dist/Progress/index.d.ts +19 -0
  6. package/dist/Progress/index.js +82 -0
  7. package/dist/RichText/index.d.ts +7 -0
  8. package/dist/RichText/index.js +56 -0
  9. package/dist/Slider/index.d.ts +22 -0
  10. package/dist/Slider/index.js +58 -0
  11. package/dist/Swiper/carousel.d.ts +17 -0
  12. package/dist/Swiper/carousel.js +39 -0
  13. package/dist/Swiper/index.d.ts +19 -0
  14. package/dist/Swiper/index.js +15 -0
  15. package/dist/Swiper/pagination.d.ts +11 -0
  16. package/dist/Swiper/pagination.js +50 -0
  17. package/dist/Video/index.d.ts +42 -0
  18. package/dist/Video/index.js +168 -0
  19. package/dist/Video/utils.d.ts +3 -0
  20. package/dist/Video/utils.js +13 -0
  21. package/dist/WebView/index.d.ts +9 -0
  22. package/dist/WebView/index.js +6 -0
  23. package/dist/assets/loading.png +0 -0
  24. package/dist/assets/video/full.png +0 -0
  25. package/dist/assets/video/mute.png +0 -0
  26. package/dist/assets/video/pause.png +0 -0
  27. package/dist/assets/video/play.png +0 -0
  28. package/dist/assets/video/shrink.png +0 -0
  29. package/dist/assets/video/unmute.png +0 -0
  30. package/dist/assets/video/volume.png +0 -0
  31. package/dist/index.d.ts +8 -0
  32. package/dist/index.js +10 -0
  33. package/dist/utils/index.d.ts +4 -0
  34. package/dist/utils/index.js +8 -0
  35. package/package.json +58 -0
  36. package/src/Camera/index.tsx +179 -0
  37. package/src/Map/index.tsx +275 -0
  38. package/src/Progress/index.tsx +142 -0
  39. package/src/RichText/index.tsx +82 -0
  40. package/src/Slider/index.tsx +118 -0
  41. package/src/Swiper/carousel.tsx +119 -0
  42. package/src/Swiper/index.tsx +64 -0
  43. package/src/Swiper/pagination.tsx +70 -0
  44. package/src/Video/index.tsx +303 -0
  45. package/src/Video/utils.ts +14 -0
  46. package/src/WebView/index.tsx +25 -0
  47. package/src/assets/loading.png +0 -0
  48. package/src/assets/video/full.png +0 -0
  49. package/src/assets/video/mute.png +0 -0
  50. package/src/assets/video/pause.png +0 -0
  51. package/src/assets/video/play.png +0 -0
  52. package/src/assets/video/shrink.png +0 -0
  53. package/src/assets/video/unmute.png +0 -0
  54. package/src/assets/video/volume.png +0 -0
  55. package/src/index.ts +10 -0
  56. package/src/utils/index.ts +12 -0
@@ -0,0 +1,119 @@
1
+ import React, {
2
+ Children,
3
+ FC,
4
+ PropsWithChildren,
5
+ ReactNode,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
10
+ import {
11
+ StyleSheet,
12
+ Text,
13
+ View,
14
+ StyleProp,
15
+ ViewStyle,
16
+ LayoutChangeEvent,
17
+ } from 'react-native';
18
+ import CarouselView, {
19
+ ICarouselInstance,
20
+ } from 'react-native-reanimated-carousel';
21
+ import { DefaultPagination, PaginationProps } from './pagination';
22
+
23
+ export interface CarouselProps {
24
+ infinite?: boolean;
25
+ dots?: boolean;
26
+ autoplay?: boolean;
27
+ autoplayInterval?: number;
28
+ selectedIndex?: number;
29
+ vertical?: boolean;
30
+ pagination?: (props: PaginationProps) => ReactNode;
31
+ dotStyle?: StyleProp<ViewStyle>;
32
+ dotActiveStyle?: StyleProp<ViewStyle>;
33
+ style?: StyleProp<ViewStyle>;
34
+ afterChange?: (index: number) => void;
35
+ }
36
+
37
+ export const Carousel: FC<PropsWithChildren<CarouselProps>> = props => {
38
+ const {
39
+ infinite = false,
40
+ dots = true,
41
+ autoplay = false,
42
+ autoplayInterval = 3000,
43
+ selectedIndex = 0,
44
+ vertical = false,
45
+ pagination: Pagination = DefaultPagination,
46
+ dotStyle = {},
47
+ dotActiveStyle = {},
48
+ children,
49
+ style,
50
+ afterChange,
51
+ } = props;
52
+
53
+ const carouselRef = useRef<ICarouselInstance>(null);
54
+ const [layout, setLayout] = useState({ width: 0, height: 0 });
55
+
56
+ const data = useMemo(() => Children.toArray(children), [children]);
57
+ const count = data.length;
58
+
59
+ const onLayout = (event: LayoutChangeEvent) => {
60
+ const { width: w, height: h } = event.nativeEvent.layout;
61
+ if (w > 0 && h > 0) {
62
+ setLayout({ width: w, height: h });
63
+ }
64
+ };
65
+
66
+ const onSnapToItem = (index: number) => {
67
+ if (afterChange) {
68
+ afterChange(index);
69
+ }
70
+ };
71
+
72
+ const width = layout.width;
73
+ const height = layout.height || 200;
74
+
75
+ if (!children || count === 0) {
76
+ return (
77
+ <Text style={{ backgroundColor: 'white' }}>
78
+ You are supposed to add children inside Carousel
79
+ </Text>
80
+ );
81
+ }
82
+
83
+ return (
84
+ <View onLayout={onLayout} style={[styles.wrapperStyle, style]}>
85
+ {width && (
86
+ <CarouselView
87
+ ref={carouselRef}
88
+ loop={infinite}
89
+ width={width}
90
+ height={height}
91
+ vertical={vertical}
92
+ autoPlay={autoplay}
93
+ autoPlayInterval={autoplayInterval}
94
+ data={data}
95
+ defaultIndex={selectedIndex}
96
+ onSnapToItem={onSnapToItem}
97
+ renderItem={({ item }) => <View style={{ flex: 1 }}>{item}</View>}
98
+ />
99
+ )}
100
+
101
+ {dots && (
102
+ <Pagination
103
+ styles={styles}
104
+ vertical={vertical}
105
+ current={selectedIndex}
106
+ count={count}
107
+ dotStyle={dotStyle}
108
+ dotActiveStyle={dotActiveStyle}
109
+ />
110
+ )}
111
+ </View>
112
+ );
113
+ };
114
+
115
+ const styles = StyleSheet.create({
116
+ wrapperStyle: {
117
+ overflow: 'hidden',
118
+ },
119
+ });
@@ -0,0 +1,64 @@
1
+ import { FC, PropsWithChildren } from 'react';
2
+ import { ViewStyle, StyleProp } from 'react-native';
3
+ import { Carousel } from './carousel';
4
+
5
+ export interface SwiperEvent {
6
+ current: number;
7
+ }
8
+
9
+ export interface SwiperProps {
10
+ style?: StyleProp<ViewStyle>;
11
+ indicatorDots?: boolean;
12
+ indicatorColor?: string;
13
+ indicatorActiveColor?: string;
14
+ autoplay?: boolean;
15
+ current?: number;
16
+ interval?: number;
17
+ circular?: boolean;
18
+ vertical?: boolean;
19
+ onChange?: (event: SwiperEvent) => void;
20
+ onAnimationFinish?: (event: SwiperEvent) => void;
21
+ }
22
+
23
+ export const Swiper: FC<PropsWithChildren<SwiperProps>> = props => {
24
+ const {
25
+ children,
26
+ style,
27
+ indicatorDots,
28
+ indicatorColor = 'rgba(0,0,0,0.3)',
29
+ indicatorActiveColor = '#000',
30
+ autoplay,
31
+ current = 0,
32
+ interval = 5000,
33
+ circular,
34
+ vertical,
35
+ onChange,
36
+ onAnimationFinish,
37
+ } = props;
38
+
39
+ const onAfterChangeHandler = (index: number) => {
40
+ if (onChange) {
41
+ onChange({ current: index });
42
+ }
43
+ if (onAnimationFinish) {
44
+ onAnimationFinish({ current: index });
45
+ }
46
+ };
47
+
48
+ return (
49
+ <Carousel
50
+ style={style}
51
+ dots={indicatorDots}
52
+ dotStyle={{ backgroundColor: indicatorColor }}
53
+ dotActiveStyle={{ backgroundColor: indicatorActiveColor }}
54
+ autoplay={autoplay}
55
+ selectedIndex={current}
56
+ autoplayInterval={interval}
57
+ infinite={circular}
58
+ vertical={vertical}
59
+ afterChange={onAfterChangeHandler}
60
+ >
61
+ {children}
62
+ </Carousel>
63
+ );
64
+ };
@@ -0,0 +1,70 @@
1
+ import React from 'react';
2
+ import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native';
3
+
4
+ export interface PaginationProps {
5
+ vertical?: boolean;
6
+ current: number;
7
+ count: number;
8
+ styles: any;
9
+ dotStyle?: StyleProp<ViewStyle>;
10
+ dotActiveStyle?: StyleProp<ViewStyle>;
11
+ }
12
+
13
+ export const DefaultPagination: React.FC<PaginationProps> = (
14
+ props: PaginationProps,
15
+ ) => {
16
+ const { current, vertical, count, dotStyle, dotActiveStyle } = props;
17
+ const positionStyle = vertical ? 'paginationY' : 'paginationX';
18
+ const flexDirection = vertical ? 'column' : 'row';
19
+ const arr: any = [];
20
+ for (let i = 0; i < count; i++) {
21
+ arr.push(
22
+ <View
23
+ key={`dot-${i}`}
24
+ style={[
25
+ styles.pointStyle,
26
+ styles.spaceStyle,
27
+ dotStyle,
28
+ i === current && styles.pointActiveStyle,
29
+ i === current && dotActiveStyle,
30
+ ]}
31
+ />,
32
+ );
33
+ }
34
+ return (
35
+ <View style={[styles.pagination, styles[positionStyle]]}>
36
+ <View style={{ flexDirection }}>{arr}</View>
37
+ </View>
38
+ );
39
+ };
40
+
41
+ const styles = StyleSheet.create({
42
+ pagination: {
43
+ position: 'absolute',
44
+ alignItems: 'center',
45
+ justifyContent: 'center',
46
+ },
47
+ paginationX: {
48
+ bottom: 10,
49
+ left: 0,
50
+ right: 0,
51
+ },
52
+ paginationY: {
53
+ right: 10,
54
+ top: 0,
55
+ bottom: 0,
56
+ },
57
+ pointStyle: {
58
+ width: 8,
59
+ height: 8,
60
+ borderRadius: 8,
61
+ backgroundColor: '#999',
62
+ },
63
+ pointActiveStyle: {
64
+ backgroundColor: '#333',
65
+ },
66
+ spaceStyle: {
67
+ marginHorizontal: 2.5,
68
+ marginVertical: 3,
69
+ },
70
+ });
@@ -0,0 +1,303 @@
1
+ import { useCallback, useMemo, useRef, useState, FC, ReactNode } from 'react';
2
+ import {
3
+ Image,
4
+ ImageStyle,
5
+ Pressable,
6
+ StyleSheet,
7
+ Text,
8
+ View,
9
+ StyleProp,
10
+ ViewStyle,
11
+ } from 'react-native';
12
+ import RNCamera, {
13
+ OnLoadData,
14
+ OnProgressData,
15
+ ResizeMode,
16
+ VideoRef,
17
+ } from 'react-native-video';
18
+ import { formatTime } from './utils';
19
+
20
+ export interface VideoTimeUpdateEvent {
21
+ currentTime: number;
22
+ duration: number;
23
+ }
24
+
25
+ export interface VideoFullscreenChangeEvent {
26
+ direction: 'vertical' | 'horizontal';
27
+ fullScreen: boolean;
28
+ }
29
+
30
+ export interface VideoMetaDataEvent {
31
+ width: number;
32
+ height: number;
33
+ duration: number;
34
+ durationMillis?: number;
35
+ }
36
+
37
+ export interface VideoErrorEvent {
38
+ errMsg: string;
39
+ }
40
+
41
+ export interface VideoProps {
42
+ src: string;
43
+ duration?: number;
44
+ controls?: boolean;
45
+ autoplay?: boolean;
46
+ loop?: boolean;
47
+ muted?: boolean;
48
+ initialTime?: number;
49
+ objectFit?: 'contain' | 'fill' | 'cover';
50
+ poster?: string;
51
+ showCenterPlayBtn?: boolean;
52
+ style?: StyleProp<ViewStyle>;
53
+ children?: ReactNode;
54
+ onLoad?: () => void;
55
+ onPlay?: () => void;
56
+ onPause?: () => void;
57
+ onEnded?: () => void;
58
+ onError?: (event: VideoErrorEvent) => void;
59
+ onTimeUpdate?: (event: VideoTimeUpdateEvent) => void;
60
+ onFullscreenChange?: (event: VideoFullscreenChangeEvent) => void;
61
+ onLoadedMetaData?: (event: VideoMetaDataEvent) => void;
62
+ }
63
+
64
+ export const Video: FC<VideoProps> = props => {
65
+ const {
66
+ src = '',
67
+ autoplay = false,
68
+ style,
69
+ initialTime = 0,
70
+ loop = false,
71
+ muted = false,
72
+ objectFit = 'contain',
73
+ poster,
74
+ controls = true,
75
+ showCenterPlayBtn = true,
76
+ duration: durationProp,
77
+ onLoad,
78
+ onPlay,
79
+ onPause,
80
+ onEnded,
81
+ onError,
82
+ onLoadedMetaData,
83
+ onFullscreenChange,
84
+ onTimeUpdate,
85
+ children,
86
+ } = props;
87
+
88
+ const videoRef = useRef<VideoRef>(null);
89
+ const [isFullScreen, setIsFullScreen] = useState(false);
90
+ const [isPlaying, setIsPlaying] = useState(autoplay);
91
+ const [isFirst, setIsFirst] = useState(true);
92
+ const [durationMs, setDurationMs] = useState<number | null>(null);
93
+
94
+ const onLoadHandler = useCallback(
95
+ (data: OnLoadData) => {
96
+ const loadedDurationMs = data.duration * 1000;
97
+ setDurationMs(loadedDurationMs);
98
+
99
+ if (initialTime > 0) {
100
+ videoRef.current?.seek(initialTime / 1000);
101
+ }
102
+
103
+ if (onLoad) {
104
+ onLoad();
105
+ }
106
+
107
+ if (onLoadedMetaData) {
108
+ onLoadedMetaData({
109
+ width: data.naturalSize.width,
110
+ height: data.naturalSize.height,
111
+ duration: loadedDurationMs,
112
+ durationMillis: loadedDurationMs,
113
+ });
114
+ }
115
+ },
116
+ [initialTime, onLoad, onLoadedMetaData],
117
+ );
118
+
119
+ const onProgressHandler = useCallback(
120
+ (data: OnProgressData) => {
121
+ if (onTimeUpdate) {
122
+ onTimeUpdate({
123
+ currentTime: data.currentTime * 1000,
124
+ duration: durationMs || data.seekableDuration * 1000,
125
+ });
126
+ }
127
+ },
128
+ [durationMs, onTimeUpdate],
129
+ );
130
+
131
+ const onErrorHandler = useCallback(
132
+ (error: any) => {
133
+ if (onError) {
134
+ onError({
135
+ errMsg: error.error?.localizedDescription || 'Video Error',
136
+ });
137
+ }
138
+ },
139
+ [onError],
140
+ );
141
+
142
+ const onEndedHandler = useCallback(() => {
143
+ if (onEnded) {
144
+ onEnded();
145
+ }
146
+ }, [onEnded]);
147
+
148
+ const onPlaybackStateHandler = useCallback(
149
+ ({ isPlaying: playing }: { isPlaying: boolean }) => {
150
+ setIsPlaying(playing);
151
+ if (playing) {
152
+ setIsFirst(false);
153
+ if (onPlay) onPlay();
154
+ } else if (!isFirst) {
155
+ if (onPause) onPause();
156
+ }
157
+ },
158
+ [isFirst, onPlay, onPause],
159
+ );
160
+
161
+ const onFullscreenWillPresentHandler = useCallback(() => {
162
+ setIsFullScreen(true);
163
+ if (onFullscreenChange) {
164
+ onFullscreenChange({
165
+ fullScreen: true,
166
+ direction: 'vertical',
167
+ });
168
+ }
169
+ }, [onFullscreenChange]);
170
+
171
+ const onFullscreenWillDismissHandler = useCallback(() => {
172
+ setIsFullScreen(false);
173
+ if (onFullscreenChange) {
174
+ onFullscreenChange({
175
+ fullScreen: false,
176
+ direction: 'vertical',
177
+ });
178
+ }
179
+ }, [onFullscreenChange]);
180
+
181
+ const onPlayVideoHandler = useCallback(() => {
182
+ setIsPlaying(true);
183
+ setIsFirst(false);
184
+ }, []);
185
+
186
+ const resizeMode = useMemo(() => {
187
+ const map: Record<string, ResizeMode> = {
188
+ contain: ResizeMode.CONTAIN,
189
+ cover: ResizeMode.COVER,
190
+ fill: ResizeMode.STRETCH,
191
+ };
192
+ return map[objectFit] || ResizeMode.CONTAIN;
193
+ }, [objectFit]);
194
+
195
+ const computedDuration = formatTime(durationProp || durationMs || 0);
196
+ const showPlayBtn = (isFirst || showCenterPlayBtn) && !isPlaying;
197
+
198
+ return (
199
+ <View style={[styles.video, style]}>
200
+ <View
201
+ style={[
202
+ styles.videoContainer,
203
+ isFullScreen && styles.videoTypeFullscreen,
204
+ ]}
205
+ >
206
+ <RNCamera
207
+ ref={videoRef}
208
+ source={{ uri: src }}
209
+ style={styles.fullSize}
210
+ paused={!isPlaying}
211
+ repeat={loop}
212
+ muted={muted}
213
+ controls={controls}
214
+ resizeMode={resizeMode}
215
+ onLoad={onLoadHandler}
216
+ onProgress={onProgressHandler}
217
+ onEnd={onEndedHandler}
218
+ onError={onErrorHandler}
219
+ onPlaybackStateChanged={onPlaybackStateHandler}
220
+ onFullscreenPlayerWillPresent={onFullscreenWillPresentHandler}
221
+ onFullscreenPlayerWillDismiss={onFullscreenWillDismissHandler}
222
+ progressUpdateInterval={250}
223
+ />
224
+
225
+ {showPlayBtn && (
226
+ <View style={styles.videoCover}>
227
+ {poster && isFirst && (
228
+ <Image source={{ uri: poster }} style={styles.videoPoster} />
229
+ )}
230
+ <Pressable onPress={onPlayVideoHandler}>
231
+ <Image
232
+ source={require('../assets/video/play.png')}
233
+ style={styles.videoCoverPlayButton as ImageStyle}
234
+ />
235
+ </Pressable>
236
+ {computedDuration && (
237
+ <Text style={styles.videoCoverDuration}>{computedDuration}</Text>
238
+ )}
239
+ </View>
240
+ )}
241
+ {children}
242
+ </View>
243
+ </View>
244
+ );
245
+ };
246
+
247
+ const styles = StyleSheet.create({
248
+ fullSize: {
249
+ width: '100%',
250
+ height: '100%',
251
+ },
252
+ video: {
253
+ width: '100%',
254
+ height: 225,
255
+ overflow: 'hidden',
256
+ position: 'relative',
257
+ },
258
+ videoContainer: {
259
+ width: '100%',
260
+ height: '100%',
261
+ backgroundColor: '#000',
262
+ position: 'absolute',
263
+ top: 0,
264
+ left: 0,
265
+ right: 0,
266
+ bottom: 0,
267
+ overflow: 'hidden',
268
+ },
269
+ videoTypeFullscreen: {
270
+ position: 'absolute',
271
+ top: '50%',
272
+ left: '50%',
273
+ zIndex: 999,
274
+ },
275
+ videoCover: {
276
+ position: 'absolute',
277
+ top: 0,
278
+ left: 0,
279
+ bottom: 0,
280
+ right: 0,
281
+ width: '100%',
282
+ display: 'flex',
283
+ flexDirection: 'column',
284
+ justifyContent: 'center',
285
+ alignItems: 'center',
286
+ backgroundColor: 'rgba(1, 1, 1, 0.5)',
287
+ zIndex: 1,
288
+ },
289
+ videoPoster: {
290
+ position: 'absolute',
291
+ width: '100%',
292
+ height: '100%',
293
+ },
294
+ videoCoverPlayButton: {
295
+ width: 30,
296
+ height: 30,
297
+ },
298
+ videoCoverDuration: {
299
+ color: '#fff',
300
+ fontSize: 16,
301
+ marginTop: 10,
302
+ },
303
+ });
@@ -0,0 +1,14 @@
1
+ export const formatTime = (time: number): string => {
2
+ if (time === null) return '';
3
+ const sec = Math.round((time / 1000) % 60);
4
+ const min = Math.floor((time - sec) / 1000 / 60);
5
+ return `${min < 10 ? `0${min}` : min}:${sec < 10 ? `0${sec}` : sec}`;
6
+ };
7
+
8
+ export const calcDist = (x: number, y: number): number => {
9
+ return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
10
+ };
11
+
12
+ export const normalizeNumber = (number: number): number => {
13
+ return Math.max(-1, Math.min(number, 1));
14
+ };
@@ -0,0 +1,25 @@
1
+ import React, { FC } from 'react';
2
+ import { StyleProp, ViewStyle } from 'react-native';
3
+ import {
4
+ WebView as RNWebView,
5
+ WebViewProps as RNWebViewProps,
6
+ } from 'react-native-webview';
7
+
8
+ export interface WebViewProps extends Partial<RNWebViewProps> {
9
+ style?: StyleProp<ViewStyle>;
10
+ src?: string;
11
+ html?: string;
12
+ }
13
+
14
+ export const WebView: FC<WebViewProps> = ({ style, src, html, ...rest }) => {
15
+ const source = html ? { html } : { uri: src || '' };
16
+
17
+ return (
18
+ <RNWebView
19
+ {...rest}
20
+ source={source}
21
+ style={style}
22
+ originWhitelist={['*']}
23
+ />
24
+ );
25
+ };
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ // Thanks to the Taro project for the inspiration that helped shape this component library.
2
+ // https://github.com/NervJS/taro.git
3
+ export * from './Camera';
4
+ export * from './Map';
5
+ export * from './Progress';
6
+ export * from './RichText';
7
+ export * from './Slider';
8
+ export * from './Swiper';
9
+ export * from './Video';
10
+ export * from './WebView';
@@ -0,0 +1,12 @@
1
+ export const omit = (
2
+ obj: any = {},
3
+ fields: string[] = [],
4
+ ): { [key: string]: any } => {
5
+ const shallowCopy = { ...obj };
6
+ fields.forEach(key => {
7
+ delete shallowCopy[key];
8
+ });
9
+ return shallowCopy;
10
+ };
11
+
12
+ export const noop = (..._args: any[]): void => {};