@yorkjs/hive-ui 0.1.4 → 0.1.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yorkjs/hive-ui",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Hive UI - Taro React component library",
5
5
  "main": "src/index.ts",
6
6
  "module": "src/index.ts",
@@ -0,0 +1,105 @@
1
+ @import '../../styles/mixin.styl'
2
+ @import '../../styles/variable.styl'
3
+
4
+ .alert-container
5
+ padding 6PX $WING_BLANK
6
+ position relative
7
+ box-sizing border-box
8
+ width 100%
9
+ display flex
10
+ flex-direction column
11
+ overflow hidden
12
+
13
+ &.is-pressable
14
+ &:active
15
+ opacity 0.7
16
+
17
+ &.show-animation
18
+ animation alertFadeIn 0.3s ease-out forwards
19
+
20
+ &.is-leaving
21
+ animation alertFadeOut 0.25s ease-in forwards
22
+
23
+ .alert-content
24
+ display flex
25
+ flex-direction row
26
+
27
+ .icon-wrapper
28
+ margin-right 8PX
29
+ flex-shrink 0
30
+ display flex
31
+ align-items center
32
+ justify-content center
33
+
34
+ .text-container
35
+ flex 1
36
+ display flex
37
+ flex-direction column
38
+ overflow hidden
39
+
40
+ .message-text
41
+ getPlatformFontSize(13)
42
+ line-height 20PX
43
+ word-break break-word
44
+ white-space pre-line
45
+
46
+ .scroll-text
47
+ getPlatformFontSize(13)
48
+ line-height 20PX
49
+ white-space nowrap
50
+ padding-right 60PX
51
+
52
+ .scroll-wrapper
53
+ overflow hidden
54
+ width 100%
55
+
56
+ .scroll-track
57
+ display flex
58
+ width max-content
59
+ align-items center
60
+ animation alertScroll 18s linear infinite
61
+ will-change transform
62
+
63
+ .description-wrapper
64
+ margin-top 4PX
65
+
66
+ .description-text
67
+ getPlatformFontSize(12)
68
+ line-height 18PX
69
+
70
+ .action-container
71
+ margin-left 8PX
72
+ display flex
73
+ flex-direction row
74
+ align-items center
75
+
76
+ .close-btn-wrapper
77
+ display flex
78
+ align-items center
79
+ justify-content center
80
+
81
+ .close-text
82
+ getPlatformFontSize(12)
83
+
84
+
85
+ @keyframes alertFadeIn
86
+ 0%
87
+ opacity 0
88
+ transform translateY(-20PX)
89
+ 100%
90
+ opacity 1
91
+ transform translateY(0)
92
+
93
+ @keyframes alertFadeOut
94
+ 0%
95
+ opacity 1
96
+ transform translateY(0)
97
+ 100%
98
+ opacity 0
99
+ transform translateY(-20PX)
100
+
101
+ @keyframes alertScroll
102
+ 0%
103
+ transform translate3d(0,0,0)
104
+ 100%
105
+ transform translate3d(-50%,0,0)
@@ -0,0 +1,268 @@
1
+ import React, { useState, useMemo, useEffect, useCallback, memo } from 'react'
2
+ import { View, Text } from '@tarojs/components'
3
+
4
+ import { useTheme } from '../../config'
5
+ import { formatClassNames } from '../../util/function'
6
+
7
+ import Icon from '../Icon'
8
+
9
+ import styles from './index.module.styl'
10
+
11
+ export type AlertType = 'success' | 'error' | 'primary' | 'warning'
12
+
13
+ export interface AlertProps {
14
+ message: React.ReactNode
15
+ description?: React.ReactNode
16
+ type?: AlertType
17
+ showIcon?: boolean
18
+ icon?: React.ReactNode
19
+ iconName?: string
20
+ closable?: boolean
21
+ closeText?: React.ReactNode
22
+ onClose?: () => void
23
+ showAnimation?: boolean
24
+ duration?: number
25
+ onChange?: () => void
26
+ showArrow?: boolean
27
+ borderRadius?: number
28
+ className?: string
29
+ messageClassName?: string
30
+ iconClassName?: string
31
+
32
+ scroll?: boolean
33
+ scrollSpeed?: number
34
+
35
+ clickToClose?: boolean
36
+ }
37
+
38
+ const Alert: React.FC<AlertProps> = ({
39
+ message,
40
+ description,
41
+ type = 'primary',
42
+ showIcon = true,
43
+ icon,
44
+ iconName,
45
+ closable = false,
46
+ closeText,
47
+ onClose,
48
+ showAnimation = false,
49
+ duration = 0,
50
+ onChange,
51
+ showArrow = false,
52
+ borderRadius = 0,
53
+ className,
54
+ messageClassName,
55
+ iconClassName,
56
+ scroll = false,
57
+ scrollSpeed = 17,
58
+ clickToClose = false
59
+ }) => {
60
+
61
+ const { themeSelect } = useTheme()
62
+
63
+ const [visible, setVisible] = useState(true)
64
+ const [isLeaving, setIsLeaving] = useState(false)
65
+
66
+ const config = useMemo(() => {
67
+
68
+ const mapping = {
69
+ warning: {
70
+ icon: 'exclamation-circle-fill',
71
+ color: '#FA8C16',
72
+ bg: themeSelect('#FFF7E6', 'rgba(255,153,0,0.25)')
73
+ },
74
+ success: {
75
+ icon: 'check-circle-fill',
76
+ color: '#03BE02',
77
+ bg: themeSelect('#E9FFE9', 'rgba(3, 190, 2, 0.30)')
78
+ },
79
+ error: {
80
+ icon: 'close-circle-fill',
81
+ color: '#FF3141',
82
+ bg: themeSelect('#FFE5E7', 'rgba(255, 49, 65, 0.3)')
83
+ },
84
+ primary: {
85
+ icon: 'volume-up',
86
+ color: 'var(--primary)',
87
+ bg: themeSelect('#FFF7E6', 'rgba(255, 143, 31, 0.3)')
88
+ },
89
+ }
90
+
91
+ return mapping[type] || mapping.primary
92
+ }, [type])
93
+
94
+ const handleClose = useCallback(() => {
95
+ if (showAnimation) {
96
+ setIsLeaving(true)
97
+ setTimeout(() => {
98
+ setVisible(false)
99
+ onClose?.()
100
+ }, 250)
101
+ }
102
+ else {
103
+ setVisible(false)
104
+ onClose?.()
105
+ }
106
+ }, [showAnimation, onClose])
107
+
108
+ const handleContainerClick = () => {
109
+ if (clickToClose) {
110
+ handleClose()
111
+ }
112
+
113
+ onChange?.()
114
+ }
115
+
116
+ useEffect(() => {
117
+ if (visible && duration > 0) {
118
+ const timer = setTimeout(handleClose, duration)
119
+ return () => clearTimeout(timer)
120
+ }
121
+ }, [visible, duration, handleClose])
122
+
123
+ if (!visible) return null
124
+
125
+ return (
126
+ <View
127
+ className={formatClassNames(styles['alert-container'], className, {
128
+ [styles['is-pressable']]: !!onChange,
129
+ [styles['show-animation']]: showAnimation,
130
+ [styles['is-leaving']]: isLeaving
131
+ })}
132
+ style={{
133
+ backgroundColor: config.bg,
134
+ borderRadius: borderRadius ? `${borderRadius}PX` : '0'
135
+ }}
136
+ onClick={handleContainerClick}
137
+ >
138
+
139
+ <View className={styles['alert-content']}>
140
+
141
+ {
142
+ showIcon
143
+ ? (
144
+ <View className={formatClassNames(styles['icon-wrapper'], iconClassName)}>
145
+ {
146
+ icon
147
+ ? icon
148
+ : (
149
+ <Icon
150
+ name={iconName || config.icon}
151
+ color={config.color}
152
+ size={16}
153
+ />
154
+ )
155
+ }
156
+ </View>
157
+ )
158
+ : undefined
159
+ }
160
+
161
+ <View className={styles['text-container']}>
162
+
163
+ {typeof message === 'string' ? (
164
+
165
+ scroll ? (
166
+
167
+ <View className={styles['scroll-wrapper']}>
168
+
169
+ <View
170
+ className={styles['scroll-track']}
171
+ style={{ animationDuration: `${scrollSpeed}s` }}
172
+ >
173
+
174
+ <Text
175
+ className={formatClassNames(styles['scroll-text'], messageClassName)}
176
+ style={{ color: config.color }}
177
+ >
178
+ {message}
179
+ </Text>
180
+
181
+ <Text
182
+ className={formatClassNames(styles['scroll-text'], messageClassName)}
183
+ style={{ color: config.color }}
184
+ >
185
+ {message}
186
+ </Text>
187
+
188
+ </View>
189
+
190
+ </View>
191
+
192
+ ) : (
193
+
194
+ <Text
195
+ className={formatClassNames(styles['message-text'], messageClassName)}
196
+ style={{ color: config.color }}
197
+ >
198
+ {message}
199
+ </Text>
200
+
201
+ )
202
+
203
+ ) : message}
204
+
205
+ {description && (
206
+ <View className={styles['description-wrapper']}>
207
+ {
208
+ typeof description === 'string'
209
+ ? (
210
+ <Text
211
+ className={styles['description-text']}
212
+ style={{ color: 'var(--content)' }}
213
+ >
214
+ {description}
215
+ </Text>
216
+ )
217
+ : description
218
+ }
219
+ </View>
220
+ )}
221
+
222
+ </View>
223
+
224
+ <View className={styles['action-container']}>
225
+
226
+ {closable && (
227
+ <View
228
+ className={styles['close-btn-wrapper']}
229
+ onClick={(e) => {
230
+ e.stopPropagation()
231
+ handleClose()
232
+ }}
233
+ >
234
+ {
235
+ closeText
236
+ ? (
237
+ <Text className={styles['close-text']} style={{ color: config.color }}>
238
+ {closeText}
239
+ </Text>
240
+ )
241
+ : (
242
+ <Icon
243
+ name="close"
244
+ color={config.color}
245
+ size={14}
246
+ />
247
+ )
248
+ }
249
+ </View>
250
+ )}
251
+
252
+ {(showArrow || (!showArrow && onChange)) && !closable && (
253
+ <Icon
254
+ name="right"
255
+ color={config.color}
256
+ size={12}
257
+ />
258
+ )}
259
+
260
+ </View>
261
+
262
+ </View>
263
+
264
+ </View>
265
+ )
266
+ }
267
+
268
+ export default memo(Alert)
@@ -0,0 +1,15 @@
1
+ @import '../../styles/mixin.styl'
2
+ @import '../../styles/variable.styl'
3
+
4
+ .card
5
+ border-radius 10PX
6
+ overflow hidden
7
+ background-color var(--containerCard)
8
+ margin-left $WING_BLANK
9
+ margin-right $WING_BLANK
10
+ &.show-top-gutter
11
+ margin-top $GUTTER_BLANK
12
+ &.show-bottom-gutter
13
+ margin-bottom $GUTTER_BLANK
14
+ &.show-top-blank
15
+ margin-top $CARD_BLANK
@@ -0,0 +1,44 @@
1
+ import React from 'react'
2
+ import { View } from '@tarojs/components'
3
+ import { formatClassNames } from '../../util/function'
4
+
5
+ import styles from './index.module.styl'
6
+
7
+ export interface CardProps {
8
+ className?: string
9
+ showTopGutter?: boolean
10
+ showBottomGutter?: boolean
11
+ showTopBlank?: boolean
12
+ children?: React.ReactNode
13
+ onClick?: () => void
14
+ }
15
+
16
+ const CellCard: React.FC<CardProps> = ({
17
+ showTopGutter = false,
18
+ showBottomGutter = false,
19
+ showTopBlank = false,
20
+ onClick,
21
+ className,
22
+ children
23
+ }) => {
24
+ const combinedClass = formatClassNames(
25
+ styles['card'],
26
+ {
27
+ [styles['show-top-gutter']]: showTopGutter,
28
+ [styles['show-bottom-gutter']]: showBottomGutter,
29
+ [styles['show-top-blank']]: showTopBlank,
30
+ },
31
+ className
32
+ )
33
+
34
+ return (
35
+ <View
36
+ className={combinedClass}
37
+ onClick={onClick}
38
+ >
39
+ {children}
40
+ </View>
41
+ )
42
+ }
43
+
44
+ export default CellCard
@@ -1,4 +1,5 @@
1
1
  @import '../../styles/mixin.styl'
2
+ @import '../../styles/variable.styl'
2
3
 
3
4
  $CELL_PADDING_HORIZONTAL = 16PX
4
5
  $CELL_PADDING_VERTICAL = 14PX
@@ -1,14 +1,14 @@
1
1
  @import '../../styles/mixin.styl'
2
2
  @import '../../styles/variable.styl'
3
3
 
4
- $CELL_PADDING_HORIZONTAL = 10PX
5
- $CELL_PADDING_VERTICAL = 15PX
4
+ $PADDING = 15PX
6
5
  $LABEL_MARGIN_RIGHT = 20PX
7
6
 
8
7
  .display-card
9
- padding $CELL_PADDING_VERTICAL $CELL_PADDING_HORIZONTAL
8
+ padding $PADDING
10
9
  background-color var(--containerCard)
11
- margin 0 $WING_BLANK
10
+ margin-left $WING_BLANK
11
+ margin-right $WING_BLANK
12
12
  border-radius 10PX
13
13
  &.show-top-gutter
14
14
  margin-top $GUTTER_BLANK
@@ -0,0 +1,60 @@
1
+ import React, { useMemo } from 'react'
2
+ import { Image, ImageProps } from '@tarojs/components'
3
+
4
+ import getResponsiveImage from '../../util/function'
5
+
6
+ export interface IRemoteImageProps extends Omit<ImageProps, 'src'> {
7
+ /** 图片地址 */
8
+ url: string
9
+ /** 期望显示宽度 */
10
+ width?: number
11
+ /** 期望显示高度 */
12
+ height?: number
13
+ /** 是否不进行裁剪(等比缩放) */
14
+ noCrop?: boolean
15
+ /** 图片质量 1-100 */
16
+ quality?: string | number
17
+ /** 自定义样式类 */
18
+ className?: string
19
+ }
20
+
21
+ const RemoteImage: React.FC<IRemoteImageProps> = (props) => {
22
+ const {
23
+ url,
24
+ width = 50,
25
+ height = 50,
26
+ noCrop = false,
27
+ quality,
28
+ mode = 'aspectFill',
29
+ className,
30
+ ...restProps
31
+ } = props
32
+
33
+ const responsiveSrc = useMemo(() => {
34
+ return getResponsiveImage({
35
+ url,
36
+ width,
37
+ height,
38
+ noCrop,
39
+ quality,
40
+ })
41
+ }, [url, width, height, noCrop, quality])
42
+ console.log("🚀 ~ RemoteImage ~ responsiveSrc:", responsiveSrc)
43
+
44
+ const containerStyle: React.CSSProperties = {
45
+ width: width ? `${width}px` : 'auto',
46
+ height: height ? `${height}px` : 'auto',
47
+ }
48
+
49
+ return (
50
+ <Image
51
+ className={className}
52
+ src={responsiveSrc}
53
+ mode={mode}
54
+ style={containerStyle}
55
+ {...restProps}
56
+ />
57
+ )
58
+ }
59
+
60
+ export default React.memo(RemoteImage)
package/src/index.ts CHANGED
@@ -7,6 +7,9 @@ export { default as Checkbox } from './components/Checkbox'
7
7
  export { default as Tag } from './components/Tag'
8
8
  export { default as FieldCard } from './components/FieldCard'
9
9
  export { default as DisplayCard } from './components/DisplayCard'
10
+ export { default as Card } from './components/Card'
11
+ export { default as RemoteImage } from './components/RemoteImage'
12
+ export { default as Alert } from './components/Alert'
10
13
 
11
14
  export { default as FlowItem } from './item/FlowItem'
12
15
  export { default as ProductItem, setProductItemConfig } from './item/ProductItem'
package/src/util/env.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import Taro from "@tarojs/taro"
2
2
 
3
+ export const pixelRatio = Taro.getWindowInfo().pixelRatio
4
+
3
5
  // 支付宝小程序
4
6
  export const isAlipayApp = Taro.getEnv() === Taro.ENV_TYPE.ALIPAY
5
7
  // 微信小程序
@@ -1,3 +1,5 @@
1
+ import { pixelRatio } from "./env"
2
+
1
3
  interface ClassRecord {
2
4
  [key: string]: boolean | string | number
3
5
  }
@@ -53,4 +55,99 @@ export function isEmpty(value: any): value is EmptyValue {
53
55
  }
54
56
 
55
57
  return isEmpty
58
+ }
59
+
60
+ export function formatAssetUrl(url: string, prefix?: string) {
61
+ if (!prefix) {
62
+ prefix = 'https'
63
+ }
64
+
65
+ if (!url) {
66
+ return ''
67
+ }
68
+
69
+ if (url.indexOf('//') === 0) {
70
+ url = prefix + ':' + url
71
+ }
72
+ else if (url.indexOf('http://img.finstao.com') === 0) {
73
+ url = url.replace(/^http/, 'https')
74
+ }
75
+ return url
76
+ }
77
+
78
+ function cleanQuery(url: string) {
79
+ if (url && url.split) {
80
+ let terms = url.split('?')
81
+ return terms.length === 2 ? terms[0] : url
82
+ }
83
+ return url
84
+ }
85
+ function isResponsiveImage(uri: string) {
86
+ return typeof uri === 'string'
87
+ && (uri.indexOf('clouddn') > 0 || uri.indexOf('img.finstao.com') > 0)
88
+ }
89
+
90
+ const imagePixelRatio = Math.min(2, pixelRatio || 2)
91
+
92
+ export interface IWatermarkOptions {
93
+ text?: string
94
+ gravity?: string
95
+ dissolve?: number
96
+ [key: string]: any
97
+ }
98
+
99
+ export interface IGetResponsiveImageOptions {
100
+ url?: string
101
+ width?: number
102
+ height?: number
103
+ noCrop?: boolean
104
+ quality?: string | number
105
+ supportWebp?: boolean
106
+ }
107
+
108
+ /**
109
+ * 获取响应式图片地址
110
+ */
111
+ export default function getResponsiveImage(options: IGetResponsiveImageOptions): string {
112
+ let {
113
+ url,
114
+ width,
115
+ height,
116
+ noCrop,
117
+ quality,
118
+ } = options
119
+
120
+ if (!url) return ''
121
+
122
+ // 如果不是可缩放类型的图片链接(如 svg/base64),原样返回
123
+ if (!isResponsiveImage(url)) {
124
+ return url
125
+ }
126
+
127
+ const suffix: string[] = []
128
+
129
+ if (typeof width === 'number') {
130
+ const w = Math.floor(width * imagePixelRatio)
131
+ suffix.push('w', w.toString())
132
+ }
133
+
134
+ if (typeof height === 'number') {
135
+ const h = Math.floor(height * imagePixelRatio)
136
+ suffix.push('h', h.toString())
137
+ }
138
+
139
+ if (quality) {
140
+ suffix.push('q', quality.toString())
141
+ }
142
+
143
+ const hasImageV2 = suffix.length > 0
144
+ let finalUrl = cleanQuery(url)
145
+
146
+ if (hasImageV2) {
147
+ const mode = noCrop ? 2 : 1
148
+ finalUrl += `?imageView2/${mode}/${suffix.join('/')}`
149
+ }
150
+ console.log("🚀 ~ getResponsiveImage ~ finalUrl:", finalUrl)
151
+
152
+ return formatAssetUrl(finalUrl)
56
153
  }