Skip to content

Commit febc78f

Browse files
committed
feat:添加通告栏组件
1 parent 16bc2eb commit febc78f

File tree

4 files changed

+287
-5
lines changed

4 files changed

+287
-5
lines changed

example/examples/src/routes.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,4 +322,12 @@ export const stackPageData: Routes[] = [
322322
// description: 'QuickList 最基础的卡片容器,可承载文字、列表、图片、段落。',
323323
// },
324324
// },
325+
{
326+
name: 'NoticeBar',
327+
component: require('./routes/NoticeBar').default,
328+
params: {
329+
title: 'NoticeBar 通告栏',
330+
description: '通告栏',
331+
},
332+
},
325333
];
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import React from 'react'
2+
import {
3+
Animated,
4+
Easing,
5+
LayoutChangeEvent,
6+
StyleProp,
7+
Text,
8+
TextStyle,
9+
View,
10+
} from 'react-native'
11+
12+
export interface MarqueeProps {
13+
text?: React.ReactNode
14+
loop?: boolean
15+
leading?: number
16+
trailing?: number
17+
className?: string
18+
fps?: number
19+
style?: StyleProp<TextStyle>
20+
maxWidth?: number
21+
}
22+
23+
class Marquee extends React.PureComponent<MarqueeProps, any> {
24+
static defaultProps = {
25+
text: '',
26+
loop: false,
27+
leading: 500,
28+
trailing: 800,
29+
fps: 40,
30+
maxWidth: 1000,
31+
}
32+
33+
texts: any
34+
left: any
35+
36+
constructor(props: MarqueeProps) {
37+
super(props)
38+
39+
this.texts = {}
40+
this.left = new Animated.Value(0)
41+
this.state = {
42+
twidth: 0,
43+
width: 0,
44+
}
45+
}
46+
47+
onLayout = (e: LayoutChangeEvent) => {
48+
if (this.state.twidth) {
49+
return
50+
}
51+
52+
this.setState(
53+
{
54+
twidth: e.nativeEvent.layout.width,
55+
},
56+
() => {
57+
// onLayout may be earlier than onLayoutContainer on android, can not be sure width < twidth at that time.
58+
this.tryStart()
59+
},
60+
)
61+
}
62+
63+
tryStart() {
64+
if (this.state.twidth > this.state.width && this.state.width) {
65+
this.startMove()
66+
}
67+
}
68+
69+
onLayoutContainer = (e: LayoutChangeEvent) => {
70+
if (!this.state.width) {
71+
this.setState(
72+
{
73+
width: e.nativeEvent.layout.width,
74+
},
75+
() => {
76+
this.left.setValue(0)
77+
this.tryStart()
78+
},
79+
)
80+
}
81+
}
82+
83+
startMove = () => {
84+
const { fps = 40, loop } = this.props
85+
const SPPED = (1 / fps) * 1000
86+
// tslint:disable-next-line:no-this-assignment
87+
const { props } = this
88+
Animated.timing(this.left, {
89+
toValue: 1,
90+
duration: this.state.twidth * SPPED,
91+
easing: Easing.linear,
92+
delay: props.leading,
93+
isInteraction: false,
94+
useNativeDriver: true,
95+
}).start(() => {
96+
if (loop) {
97+
this.moveToHeader()
98+
}
99+
})
100+
}
101+
102+
moveToHeader = () => {
103+
Animated.timing(this.left, {
104+
toValue: 0,
105+
duration: 0,
106+
delay: this.props.trailing,
107+
isInteraction: false,
108+
useNativeDriver: true,
109+
}).start(() => {
110+
this.startMove()
111+
})
112+
}
113+
114+
render() {
115+
const { width, twidth } = this.state
116+
const { style, text, maxWidth } = this.props
117+
118+
const textChildren = (
119+
<Text
120+
onLayout={this.onLayout}
121+
numberOfLines={1}
122+
ellipsizeMode="tail"
123+
style={style}>
124+
{text}
125+
</Text>
126+
)
127+
128+
return (
129+
<View
130+
style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}
131+
onLayout={this.onLayoutContainer}>
132+
<Animated.View
133+
// tslint:disable-next-line:jsx-no-multiline-js
134+
style={{
135+
flexDirection: 'row',
136+
transform: [
137+
{
138+
translateX: this.left.interpolate({
139+
inputRange: [0, 1],
140+
outputRange: [0, -twidth + width],
141+
}),
142+
},
143+
],
144+
width: maxWidth,
145+
}}>
146+
{textChildren}
147+
</Animated.View>
148+
</View>
149+
)
150+
}
151+
}
152+
153+
export default Marquee
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import React, {Component} from 'react';
2+
import { StyleProp, Text, TouchableWithoutFeedback, View, ViewStyle, StyleSheet } from 'react-native';
3+
import Icon from '../Icon';
4+
import Marquee, { MarqueeProps } from './Marquee'
5+
6+
7+
export type NoticeBarProps = {
8+
children?: any;
9+
mode?: string;
10+
onPress?: any;
11+
icon?: any;
12+
action?: any;
13+
style?: StyleProp<ViewStyle>
14+
marqueeProps?: MarqueeProps
15+
};
16+
17+
export default class NoticeBar extends Component<NoticeBarProps>{
18+
constructor(props:any) {
19+
super(props)
20+
this.state = {
21+
show: true,
22+
}
23+
}
24+
25+
onPress = () => {
26+
const { mode, onPress } = this.props
27+
if (onPress) {
28+
onPress()
29+
}
30+
if (mode === 'closable') {
31+
this.setState({
32+
show: false,
33+
})
34+
}
35+
}
36+
render() {
37+
let { children, mode, icon, style, action, marqueeProps } = this.props;
38+
let operationDom: any = null;
39+
icon = typeof icon === 'undefined' ? (<Icon fill="notification" color="#f4333c" />) : (icon);
40+
if (mode === 'closable') {
41+
operationDom = (
42+
<TouchableWithoutFeedback onPress={this.onPress}>
43+
<View style={styles.actionWrap}>
44+
{action ? action : <Text style={[styles.close]}>×</Text>}
45+
</View>
46+
</TouchableWithoutFeedback>
47+
)
48+
} else if (mode === 'link') {
49+
operationDom = (
50+
<View style={styles.actionWrap}>
51+
{action ? action : <Text style={[styles.link]}></Text>}
52+
</View>
53+
)
54+
}
55+
const main = (
56+
<View style={[styles.notice, style]}>
57+
{icon && <View style={styles.left15}>{icon}</View>}
58+
<View
59+
style={[styles.container, icon ? styles.left6 : styles.left15]}>
60+
<Marquee
61+
style={styles.content}
62+
text={children}
63+
{...marqueeProps}
64+
/>
65+
</View>
66+
{operationDom}
67+
</View>
68+
)
69+
return (
70+
<View>
71+
{mode === 'closable' ? (main) : (
72+
<TouchableWithoutFeedback onPress={this.onPress}>{ main}</TouchableWithoutFeedback>
73+
)}
74+
</View>
75+
)
76+
}
77+
}
78+
79+
const styles = StyleSheet.create({
80+
notice: {
81+
backgroundColor: '#fffada',
82+
height: 36,
83+
overflow: 'hidden',
84+
flexDirection: 'row',
85+
alignItems: 'center',
86+
},
87+
container: {
88+
flex: 1,
89+
marginRight: 15,
90+
overflow: 'hidden',
91+
width: 0, // ios bug: width size is wrong (usecase: with react-navigation).
92+
},
93+
content: {
94+
fontSize: 15,
95+
color: '#f4333c',
96+
},
97+
left6: {
98+
marginLeft: 5,
99+
},
100+
left15: {
101+
// marginLeft: 15,
102+
},
103+
actionWrap: {
104+
marginRight: 15,
105+
},
106+
close: {
107+
color: '#f4333c',
108+
fontSize: 18,
109+
fontWeight: '200',
110+
textAlign: 'left',
111+
},
112+
link: {
113+
transform: [{ rotate: '225deg' }],
114+
color: '#f4333c',
115+
fontSize: 10,
116+
fontWeight: '500',
117+
textAlign: 'left',
118+
},
119+
});

packages/core/src/index.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,14 @@ export * from './Rating'
6868

6969
export { default as Timeline } from './Timeline';
7070
export * from './Timeline';
71-
export { default as Tabs } from './Tabs'
71+
export { default as Tabs } from './Tabs';
7272
export * from './Tabs'
73-
export { default as QuickList } from './QuickList'
74-
export * from './QuickList'
75-
export { default as Card } from './Card'
76-
export * from './Card'
73+
export { default as QuickList } from './QuickList';
74+
export * from './QuickList';
75+
export { default as Card } from './Card';
76+
export * from './Card';
77+
export { default as NoticeBar } from './NoticeBar';
78+
export * from './NoticeBar';
7779

7880
/**
7981
* Typography

0 commit comments

Comments
 (0)