deepsea-components 2.8.5 → 3.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.
- package/dist/cjs/components/AutoFit.d.ts +25 -0
- package/dist/cjs/components/AutoFit.js +99 -0
- package/dist/cjs/components/AutoFit.js.map +7 -0
- package/dist/cjs/components/AutoScroll.js.map +2 -2
- package/dist/cjs/components/AutoSizeTextarea.d.ts +5 -0
- package/dist/cjs/components/AutoSizeTextarea.js +51 -0
- package/dist/cjs/components/AutoSizeTextarea.js.map +7 -0
- package/dist/cjs/components/CircleText.d.ts +25 -0
- package/dist/cjs/components/CircleText.js +48 -0
- package/dist/cjs/components/CircleText.js.map +7 -0
- package/dist/cjs/components/Flow.d.ts +57 -0
- package/dist/cjs/components/Flow.js +119 -0
- package/dist/cjs/components/Flow.js.map +7 -0
- package/dist/cjs/components/HlsPlayer.d.ts +6 -0
- package/dist/cjs/components/HlsPlayer.js +64 -0
- package/dist/cjs/components/HlsPlayer.js.map +7 -0
- package/dist/cjs/components/InputFile.d.ts +64 -0
- package/dist/cjs/components/InputFile.js +143 -0
- package/dist/cjs/components/InputFile.js.map +7 -0
- package/dist/cjs/components/LoopSwiper.d.ts +9 -0
- package/dist/cjs/components/LoopSwiper.js +116 -0
- package/dist/cjs/components/LoopSwiper.js.map +7 -0
- package/dist/cjs/components/Ring.d.ts +7 -0
- package/dist/cjs/components/Ring.js +37 -0
- package/dist/cjs/components/Ring.js.map +7 -0
- package/dist/cjs/components/Scroll.js.map +2 -2
- package/dist/cjs/components/SectionRing.d.ts +8 -0
- package/dist/cjs/components/SectionRing.js +50 -0
- package/dist/cjs/components/SectionRing.js.map +7 -0
- package/dist/cjs/components/Skeleton.d.ts +3 -0
- package/dist/cjs/components/Skeleton.js +61 -0
- package/dist/cjs/components/Skeleton.js.map +7 -0
- package/dist/cjs/components/TransitionBox.d.ts +10 -0
- package/dist/cjs/components/TransitionBox.js +45 -0
- package/dist/cjs/components/TransitionBox.js.map +7 -0
- package/dist/cjs/components/TransitionNum.d.ts +16 -0
- package/dist/cjs/components/TransitionNum.js +62 -0
- package/dist/cjs/components/TransitionNum.js.map +7 -0
- package/dist/cjs/components/Trapezium.d.ts +9 -0
- package/dist/cjs/components/Trapezium.js +44 -0
- package/dist/cjs/components/Trapezium.js.map +7 -0
- package/dist/cjs/index.d.ts +14 -265
- package/dist/cjs/index.js +28 -505
- package/dist/cjs/index.js.map +3 -3
- package/dist/esm/components/AutoFit.d.ts +25 -0
- package/dist/esm/components/AutoFit.js +87 -0
- package/dist/esm/components/AutoFit.js.map +1 -0
- package/dist/esm/components/AutoScroll.js.map +1 -1
- package/dist/esm/components/AutoSizeTextarea.d.ts +5 -0
- package/dist/esm/components/AutoSizeTextarea.js +45 -0
- package/dist/esm/components/AutoSizeTextarea.js.map +1 -0
- package/dist/esm/components/CircleText.d.ts +25 -0
- package/dist/esm/components/CircleText.js +49 -0
- package/dist/esm/components/CircleText.js.map +1 -0
- package/dist/esm/components/Flow.d.ts +57 -0
- package/dist/esm/components/Flow.js +130 -0
- package/dist/esm/components/Flow.js.map +1 -0
- package/dist/esm/components/HlsPlayer.d.ts +6 -0
- package/dist/esm/components/HlsPlayer.js +39 -0
- package/dist/esm/components/HlsPlayer.js.map +1 -0
- package/dist/esm/components/InputFile.d.ts +64 -0
- package/dist/esm/components/InputFile.js +167 -0
- package/dist/esm/components/InputFile.js.map +1 -0
- package/dist/esm/components/LoopSwiper.d.ts +9 -0
- package/dist/esm/components/LoopSwiper.js +122 -0
- package/dist/esm/components/LoopSwiper.js.map +1 -0
- package/dist/esm/components/Ring.d.ts +7 -0
- package/dist/esm/components/Ring.js +26 -0
- package/dist/esm/components/Ring.js.map +1 -0
- package/dist/esm/components/Scroll.js.map +1 -1
- package/dist/esm/components/SectionRing.d.ts +8 -0
- package/dist/esm/components/SectionRing.js +31 -0
- package/dist/esm/components/SectionRing.js.map +1 -0
- package/dist/esm/components/Skeleton.d.ts +3 -0
- package/dist/esm/components/Skeleton.js +36 -0
- package/dist/esm/components/Skeleton.js.map +1 -0
- package/dist/esm/components/TransitionBox.d.ts +10 -0
- package/dist/esm/components/TransitionBox.js +50 -0
- package/dist/esm/components/TransitionBox.js.map +1 -0
- package/dist/esm/components/TransitionNum.d.ts +16 -0
- package/dist/esm/components/TransitionNum.js +65 -0
- package/dist/esm/components/TransitionNum.js.map +1 -0
- package/dist/esm/components/Trapezium.d.ts +9 -0
- package/dist/esm/components/Trapezium.js +35 -0
- package/dist/esm/components/Trapezium.js.map +1 -0
- package/dist/esm/index.d.ts +14 -265
- package/dist/esm/index.js +14 -843
- package/dist/esm/index.js.map +1 -1
- package/package.json +4 -3
- package/src/components/AutoFit.tsx +95 -0
- package/src/components/AutoScroll.tsx +1 -0
- package/src/components/AutoSizeTextarea.tsx +31 -0
- package/src/components/CircleText.tsx +56 -0
- package/src/components/Flow.tsx +154 -0
- package/src/components/HlsPlayer.tsx +38 -0
- package/src/components/InputFile.tsx +193 -0
- package/src/components/LoopSwiper.tsx +101 -0
- package/src/components/Ring.tsx +19 -0
- package/src/components/Scroll.tsx +1 -0
- package/src/components/SectionRing.tsx +36 -0
- package/src/components/Skeleton.tsx +39 -0
- package/src/components/TransitionBox.tsx +36 -0
- package/src/components/TransitionNum.tsx +54 -0
- package/src/components/Trapezium.tsx +35 -0
- package/src/index.tsx +14 -862
- package/dist/cjs/skeleton.css +0 -18
- package/dist/esm/skeleton.css +0 -18
- package/src/skeleton.css +0 -18
package/src/index.tsx
CHANGED
|
@@ -1,863 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
import { css } from "@emotion/css"
|
|
3
|
-
import { DrawArcOptions, clsx, drawArc, setFrameInterval } from "deepsea-tools"
|
|
4
|
-
import { ButtonHTMLAttributes, CSSProperties, ChangeEvent, FC, Fragment, HTMLAttributes, InputHTMLAttributes, MouseEvent as ReactMouseEvent, ReactNode, TextareaHTMLAttributes, forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react"
|
|
5
|
-
import { read, utils, writeFile } from "xlsx"
|
|
6
|
-
|
|
7
|
-
export * from "./components/Scroll"
|
|
1
|
+
export * from "./components/AutoFit"
|
|
8
2
|
export * from "./components/AutoScroll"
|
|
9
|
-
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
file: File
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export type InputFileProps = (
|
|
26
|
-
| {
|
|
27
|
-
multiple?: false
|
|
28
|
-
type: "base64" | "text" | "binary"
|
|
29
|
-
onChange?: (data: InputFileData<string>) => void
|
|
30
|
-
}
|
|
31
|
-
| {
|
|
32
|
-
multiple?: false
|
|
33
|
-
type: "arrayBuffer"
|
|
34
|
-
onChange?: (data: InputFileData<ArrayBuffer>) => void
|
|
35
|
-
}
|
|
36
|
-
| {
|
|
37
|
-
multiple?: false
|
|
38
|
-
type?: "file"
|
|
39
|
-
onChange?: (data: InputFileData<File>) => void
|
|
40
|
-
}
|
|
41
|
-
| {
|
|
42
|
-
multiple: true
|
|
43
|
-
type: "base64" | "text" | "binary"
|
|
44
|
-
onChange?: (data: InputFileData<string>[]) => void
|
|
45
|
-
}
|
|
46
|
-
| {
|
|
47
|
-
multiple: true
|
|
48
|
-
type: "arrayBuffer"
|
|
49
|
-
onChange?: (data: InputFileData<ArrayBuffer>[]) => void
|
|
50
|
-
}
|
|
51
|
-
| {
|
|
52
|
-
multiple: true
|
|
53
|
-
type?: "file"
|
|
54
|
-
onChange?: (data: InputFileData<File>[]) => void
|
|
55
|
-
}
|
|
56
|
-
) &
|
|
57
|
-
Omit<InputHTMLAttributes<HTMLInputElement>, "onChange" | "multiple" | "type"> & {
|
|
58
|
-
/** 是否在捕获文件后清除 input 上的文件,默认为 false,主要区别在于如果不清除,连续两次选择同样的文件不会触发 onChange 事件,如果用于 form 表单,请设置为 flase */
|
|
59
|
-
clearAfterChange?: boolean
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export async function getFileData<T extends InputFileDataType>(file: File, type: T): Promise<InputFileDataTypes[T]> {
|
|
63
|
-
const fileReader = new FileReader()
|
|
64
|
-
switch (type) {
|
|
65
|
-
case "arrayBuffer":
|
|
66
|
-
fileReader.readAsArrayBuffer(file)
|
|
67
|
-
break
|
|
68
|
-
case "binary":
|
|
69
|
-
fileReader.readAsBinaryString(file)
|
|
70
|
-
break
|
|
71
|
-
case "base64":
|
|
72
|
-
fileReader.readAsDataURL(file)
|
|
73
|
-
break
|
|
74
|
-
case "text":
|
|
75
|
-
fileReader.readAsText(file)
|
|
76
|
-
break
|
|
77
|
-
default:
|
|
78
|
-
return file as any
|
|
79
|
-
}
|
|
80
|
-
return new Promise(resolve => {
|
|
81
|
-
fileReader.addEventListener("load", () => {
|
|
82
|
-
resolve(fileReader.result as any)
|
|
83
|
-
})
|
|
84
|
-
})
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** 专用于读取文件的组件 */
|
|
88
|
-
export const InputFile = forwardRef<HTMLInputElement, InputFileProps>((props, ref) => {
|
|
89
|
-
const { multiple = false, type = "file", onChange, disabled: inputDisabled, clearAfterChange, ...others } = props
|
|
90
|
-
const [disabled, setDisabled] = useState(false)
|
|
91
|
-
|
|
92
|
-
async function onInputChange(e: ChangeEvent<HTMLInputElement>) {
|
|
93
|
-
const input = e.target
|
|
94
|
-
const { files } = input
|
|
95
|
-
if (!files || files.length === 0) return
|
|
96
|
-
setDisabled(true)
|
|
97
|
-
try {
|
|
98
|
-
if (multiple) {
|
|
99
|
-
const result: any[] = []
|
|
100
|
-
for (const file of Array.from(files)) {
|
|
101
|
-
result.push({
|
|
102
|
-
result: await getFileData(file, type),
|
|
103
|
-
file
|
|
104
|
-
})
|
|
105
|
-
}
|
|
106
|
-
onChange?.(result as any)
|
|
107
|
-
} else {
|
|
108
|
-
onChange?.({
|
|
109
|
-
result: await getFileData(files[0], type),
|
|
110
|
-
file: files[0]
|
|
111
|
-
} as any)
|
|
112
|
-
}
|
|
113
|
-
setDisabled(false)
|
|
114
|
-
if (clearAfterChange) input.value = ""
|
|
115
|
-
} catch (error) {
|
|
116
|
-
setDisabled(false)
|
|
117
|
-
if (clearAfterChange) input.value = ""
|
|
118
|
-
throw error
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return <input disabled={disabled && inputDisabled} ref={ref} type="file" multiple={multiple} onChange={onInputChange} {...others} />
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
export interface ImportExcelProps extends Omit<InputFileProps, "multiple" | "onChange" | "accept" | "type"> {
|
|
126
|
-
onChange?: (data: Record<string, string>[]) => void
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export type InputFileButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
130
|
-
input: InputFileProps
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/** 专用于读取文件的 button 组件 */
|
|
134
|
-
export const InputFileButton = forwardRef<HTMLButtonElement, InputFileButtonProps>((props, ref) => {
|
|
135
|
-
const { onClick, children, input: inputProps, ...others } = props
|
|
136
|
-
const { style, ...otherInputProps } = inputProps
|
|
137
|
-
const input = useRef<HTMLInputElement>(null)
|
|
138
|
-
|
|
139
|
-
function onBtnClick(e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) {
|
|
140
|
-
input.current?.click()
|
|
141
|
-
onClick?.(e)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return (
|
|
145
|
-
<button ref={ref} type="button" onClick={onBtnClick} {...others}>
|
|
146
|
-
<InputFile ref={input} style={{ display: "none", ...style }} {...otherInputProps} />
|
|
147
|
-
{children}
|
|
148
|
-
</button>
|
|
149
|
-
)
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
/** 专门用于读取 excel 的组件 */
|
|
153
|
-
export const ImportExcel = forwardRef<HTMLInputElement, ImportExcelProps>((props, ref) => {
|
|
154
|
-
const { onChange, ...others } = props
|
|
155
|
-
|
|
156
|
-
function onInputChange(data: InputFileData<ArrayBuffer>) {
|
|
157
|
-
const wb = read(data.result)
|
|
158
|
-
const result = utils.sheet_to_json<any>(wb.Sheets[wb.SheetNames[0]])
|
|
159
|
-
if (typeof result === "object") {
|
|
160
|
-
const $ = result.map(it => {
|
|
161
|
-
const _: Record<string, string> = {}
|
|
162
|
-
Object.keys(it)
|
|
163
|
-
.filter(key => key !== "__rowNum__")
|
|
164
|
-
.forEach(key => (_[key] = String(it[key])))
|
|
165
|
-
return _
|
|
166
|
-
})
|
|
167
|
-
onChange?.($)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return <InputFile ref={ref} accept=".xlsx" type="arrayBuffer" onChange={onInputChange} {...others} />
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
/** 手动导出 excel */
|
|
175
|
-
export function exportExcel(data: Record<string, string>[], name: string) {
|
|
176
|
-
const workSheet = utils.json_to_sheet(data)
|
|
177
|
-
const workBook = utils.book_new()
|
|
178
|
-
utils.book_append_sheet(workBook, workSheet)
|
|
179
|
-
writeFile(workBook, `${name}${name.endsWith(".xlsx") ? "" : ".xlsx"}`)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export interface ExportExcelProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
183
|
-
data: Record<string, string>[]
|
|
184
|
-
fileName: string
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/** 导出 excel 的 button 组件 */
|
|
188
|
-
export const ExportExcel = forwardRef<HTMLButtonElement, ExportExcelProps>((props, ref) => {
|
|
189
|
-
const { data, fileName, onClick, ...others } = props
|
|
190
|
-
|
|
191
|
-
function onButtonClick(e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) {
|
|
192
|
-
exportExcel(data, fileName)
|
|
193
|
-
onClick?.(e)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return <button ref={ref} onClick={onButtonClick} {...others} />
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
export interface TransitionBoxProps extends HTMLAttributes<HTMLDivElement> {
|
|
200
|
-
containerClassName?: string
|
|
201
|
-
containerStyle?: CSSProperties
|
|
202
|
-
vertical?: boolean
|
|
203
|
-
horizontal?: boolean
|
|
204
|
-
time?: number
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/** 尺寸渐变的组件 */
|
|
208
|
-
export const TransitionBox: FC<TransitionBoxProps> = props => {
|
|
209
|
-
const { style, containerClassName, containerStyle, children, vertical = true, horizontal = true, time = 3000, ...others } = props
|
|
210
|
-
const box = useRef<HTMLDivElement>(null)
|
|
211
|
-
const [width, setWidth] = useState(0)
|
|
212
|
-
const [height, setHeight] = useState(0)
|
|
213
|
-
const [count, setCount] = useState(0)
|
|
214
|
-
|
|
215
|
-
useEffect(() => {
|
|
216
|
-
const observer = new ResizeObserver(entries => {
|
|
217
|
-
const { width: currentWidth, height: currentHeight } = entries[0].contentRect
|
|
218
|
-
setWidth(currentWidth)
|
|
219
|
-
setHeight(currentHeight)
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
observer.observe(box.current!)
|
|
223
|
-
|
|
224
|
-
return () => {
|
|
225
|
-
observer.disconnect()
|
|
226
|
-
}
|
|
227
|
-
}, [])
|
|
228
|
-
|
|
229
|
-
useEffect(() => {
|
|
230
|
-
setCount(count => Math.min(count + 1, 3))
|
|
231
|
-
}, [width, height])
|
|
232
|
-
|
|
233
|
-
const outerStyle: CSSProperties = { transitionProperty: count === 3 ? [horizontal && "width", vertical && "height"].filter(Boolean).join(", ") : undefined, transitionDuration: count === 3 ? `${time}ms` : undefined, width, height, overflow: "hidden", position: "relative", ...style }
|
|
234
|
-
|
|
235
|
-
return (
|
|
236
|
-
<div style={outerStyle} {...others}>
|
|
237
|
-
<div className={containerClassName} style={{ position: "absolute", ...containerStyle }} ref={box}>
|
|
238
|
-
{children}
|
|
239
|
-
</div>
|
|
240
|
-
</div>
|
|
241
|
-
)
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
export interface SeaRadarTarget {
|
|
245
|
-
/** 半径 */
|
|
246
|
-
radius: number
|
|
247
|
-
/** 弧度 */
|
|
248
|
-
angle: number
|
|
249
|
-
/** 元素 */
|
|
250
|
-
element: ReactNode
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
export interface SeaRadarProps {
|
|
254
|
-
/** 最小圆的半径 */
|
|
255
|
-
unitRadius?: number
|
|
256
|
-
/** 一共的全数 */
|
|
257
|
-
circleCount?: number
|
|
258
|
-
/** 角度分成多少份 */
|
|
259
|
-
directionCount?: number
|
|
260
|
-
/** 圈的宽度 */
|
|
261
|
-
circleWidth?: number
|
|
262
|
-
/** 圈的颜色 */
|
|
263
|
-
circleColor: string
|
|
264
|
-
/** 角度的宽度 */
|
|
265
|
-
directionWidth?: number
|
|
266
|
-
/** 角度颜色 */
|
|
267
|
-
directionColor: string
|
|
268
|
-
/** 背景色 */
|
|
269
|
-
backgroundColor: string
|
|
270
|
-
/** 扫描的颜色 */
|
|
271
|
-
scanColor: string
|
|
272
|
-
/** 扫描的弧度 */
|
|
273
|
-
scanAngle?: number
|
|
274
|
-
/** 扫描一周的时间,单位秒 */
|
|
275
|
-
period?: number
|
|
276
|
-
/** 目标列表 */
|
|
277
|
-
targets?: SeaRadarTarget[]
|
|
278
|
-
/** 展示圈 */
|
|
279
|
-
showCircle?: boolean
|
|
280
|
-
/** 展示方位 */
|
|
281
|
-
showDirection?: boolean
|
|
282
|
-
/** 展示刻度线 */
|
|
283
|
-
showGraduationLine?: boolean
|
|
284
|
-
/** 展示刻度文字 */
|
|
285
|
-
showGraduationText?: boolean
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/** 雷达组件 */
|
|
289
|
-
export const SeaRadar: FC<SeaRadarProps> = props => {
|
|
290
|
-
const { unitRadius = 50, circleCount = 4, circleWidth = 1, circleColor, directionCount = 36, directionWidth = 1, directionColor, backgroundColor, scanColor, scanAngle = Math.PI / 6, period = 8, targets, showGraduationLine, showGraduationText, showCircle, showDirection } = props
|
|
291
|
-
const style = { "--unit-radius": `${unitRadius}px`, "--circle-count": circleCount, "--circle-width": `${circleWidth}px`, "--circle-color": circleColor, "--direction-count": directionCount, "--direction-width": `${directionWidth}px`, "--direction-color": directionColor, "--background-color": backgroundColor, "--scan-color": scanColor, "--period": `${period}s` } as CSSProperties
|
|
292
|
-
return (
|
|
293
|
-
<div className="sea-radar-wrapper" style={style}>
|
|
294
|
-
{Array(180)
|
|
295
|
-
.fill(0)
|
|
296
|
-
.map((_, index) => (
|
|
297
|
-
<div className="sea-radar-little-graduation" style={{ transform: `rotateZ(${2 * index}deg)`, display: showGraduationLine ? "block" : "none" }}></div>
|
|
298
|
-
))}
|
|
299
|
-
{Array(36)
|
|
300
|
-
.fill(0)
|
|
301
|
-
.map((_, index) => (
|
|
302
|
-
<div className="sea-radar-graduation" style={{ transform: `rotateZ(${10 * index}deg)`, display: showGraduationLine ? "block" : "none" }}>
|
|
303
|
-
<span className="sea-radar-graduation-text" style={{ display: showGraduationText ? "block" : "none" }}>
|
|
304
|
-
{String(index * 10).padStart(3, "0")}
|
|
305
|
-
</span>
|
|
306
|
-
</div>
|
|
307
|
-
))}
|
|
308
|
-
<div className="sea-radar">
|
|
309
|
-
<div className="sea-radar-scan" style={{ height: `${unitRadius * circleCount * Math.tan(scanAngle)}px`, clipPath: `polygon(0 0, 0 100%, 100% 100%)` }}></div>
|
|
310
|
-
{showCircle &&
|
|
311
|
-
Array(circleCount)
|
|
312
|
-
.fill(0)
|
|
313
|
-
.map((_, index) => <div className="sea-radar-circle" style={{ width: `${unitRadius * 2 * (index + 1)}px`, height: `${unitRadius * 2 * (index + 1)}px` }}></div>)}
|
|
314
|
-
{showDirection &&
|
|
315
|
-
Array(directionCount)
|
|
316
|
-
.fill(0)
|
|
317
|
-
.map((_, index) => <div className="sea-radar-direction" style={{ transform: `rotateZ(${(360 / directionCount) * index}deg)` }}></div>)}
|
|
318
|
-
{targets?.map(it => (
|
|
319
|
-
<div className="sea-radar-target" style={{ left: `${unitRadius * circleCount + it.radius * Math.cos(it.angle)}px`, bottom: `${unitRadius * circleCount + it.radius * Math.sin(it.angle)}px` }}>
|
|
320
|
-
{it.element}
|
|
321
|
-
</div>
|
|
322
|
-
))}
|
|
323
|
-
</div>
|
|
324
|
-
</div>
|
|
325
|
-
)
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
export interface RingProps extends HTMLAttributes<HTMLDivElement> {
|
|
329
|
-
outerWidth: number
|
|
330
|
-
innerWidth: number
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/** 忘了什么组件 */
|
|
334
|
-
export const Ring: FC<RingProps> = props => {
|
|
335
|
-
const { outerWidth, innerWidth, style, ...leftProps } = props
|
|
336
|
-
|
|
337
|
-
const outerRadius = outerWidth / 2
|
|
338
|
-
|
|
339
|
-
const innerRadius = innerWidth / 2
|
|
340
|
-
|
|
341
|
-
return <div style={{ ...style, width: `${outerWidth}px`, height: `${outerWidth}px`, clipPath: `path("M0,${outerRadius} a${outerRadius},${outerRadius},0,1,0,${outerWidth},0 a${outerRadius},${outerRadius},0,1,0,-${outerWidth},0 l${outerRadius - innerRadius},0 a${innerRadius},${innerRadius},0,0,1,${innerRadius * 2},0 a${innerRadius},${innerRadius},0,0,1,-${innerRadius * 2},0 Z")` }} {...leftProps} />
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
export interface FlowSizeData {
|
|
345
|
-
/** 容器宽度 */
|
|
346
|
-
width: number
|
|
347
|
-
/** 容器高度 */
|
|
348
|
-
height: number
|
|
349
|
-
/** 元素宽度 */
|
|
350
|
-
itemWidth: number
|
|
351
|
-
/** 元素高度 */
|
|
352
|
-
itemHeight: number
|
|
353
|
-
/** 列间距 */
|
|
354
|
-
columnGap: number
|
|
355
|
-
/** 列数 */
|
|
356
|
-
columnCount: number
|
|
357
|
-
/** 行间距 */
|
|
358
|
-
rowGap: number
|
|
359
|
-
/** 行数 */
|
|
360
|
-
rowCount: number
|
|
361
|
-
/** 元素格数 */
|
|
362
|
-
itemCount: number
|
|
363
|
-
/** 最大行数 */
|
|
364
|
-
maxRows: number | null
|
|
365
|
-
/** 是否有元素被隐藏 */
|
|
366
|
-
overflow: boolean
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
export interface FlowProps<T> extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
370
|
-
/** 元素宽度 */
|
|
371
|
-
itemWidth: number
|
|
372
|
-
/** 元素高度 */
|
|
373
|
-
itemHeight: number
|
|
374
|
-
/** 列间距 */
|
|
375
|
-
columnGap?: number | null | (number | null)[]
|
|
376
|
-
/** 行间距 */
|
|
377
|
-
rowGap?: number
|
|
378
|
-
/** 最大行数 */
|
|
379
|
-
maxRows?: number | null
|
|
380
|
-
/** 源数据 */
|
|
381
|
-
data: T[]
|
|
382
|
-
/** 渲染 */
|
|
383
|
-
render: (item: T, index: number, hidden: boolean) => ReactNode
|
|
384
|
-
/** key释放器,默认为 index */
|
|
385
|
-
keyExactor?: (item: T, index: number) => string | number
|
|
386
|
-
/** 容器类名 */
|
|
387
|
-
containerClassName?: string
|
|
388
|
-
/** 容器样式 */
|
|
389
|
-
containerStyle?: CSSProperties
|
|
390
|
-
/** 节流时间,单位毫秒,默认200ms,传入 null 不节流 */
|
|
391
|
-
throttle?: number | null
|
|
392
|
-
/** 动画时间,单位毫秒,默认400ms,传入 null 不展示动画 */
|
|
393
|
-
transitionDuration?: number | null
|
|
394
|
-
/** 变化的回调函数 */
|
|
395
|
-
onSizeChange?: (sizeData: FlowSizeData) => void
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
export function getGapRange(gap?: undefined | number | null | (number | null)[]): [number, number | null] {
|
|
399
|
-
if (typeof gap === "number") return [gap, gap]
|
|
400
|
-
if (Array.isArray(gap)) return [gap[0] || 0, gap[1]]
|
|
401
|
-
return [0, null]
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
export function getGapCountAndSize(width: number, itemWidth: number, minGap: number, maxGap: number | null): [number, number] {
|
|
405
|
-
const count = Math.floor((width + minGap) / (itemWidth + minGap)) || 1
|
|
406
|
-
if (count === 1) return [count, 0]
|
|
407
|
-
const averageGap = (width - itemWidth * count) / (count - 1)
|
|
408
|
-
if (averageGap < minGap) return [count, minGap]
|
|
409
|
-
if (maxGap !== null && averageGap > maxGap) return [count, maxGap]
|
|
410
|
-
return [count, averageGap]
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
/** 自适应浮动组件 */
|
|
414
|
-
export function Flow<T>(props: FlowProps<T>) {
|
|
415
|
-
const { itemWidth, itemHeight, columnGap, rowGap = 0, maxRows, data, render, keyExactor, className, style, containerClassName, containerStyle, throttle, transitionDuration, onSizeChange, ...others } = props
|
|
416
|
-
const [minColumnGap, maxColumnGap] = getGapRange(columnGap)
|
|
417
|
-
const [width, setWidth] = useState(0)
|
|
418
|
-
const [columnCount, setColumnCount] = useState(1)
|
|
419
|
-
const [columnGapSize, setColumnGapSize] = useState(minColumnGap)
|
|
420
|
-
const [showItems, setShowItems] = useState(false)
|
|
421
|
-
const ele = useRef<HTMLDivElement>(null)
|
|
422
|
-
const contentRows = Math.ceil(data.length / columnCount)
|
|
423
|
-
const contentShownRows = typeof maxRows === "number" ? Math.min(contentRows, maxRows) : contentRows
|
|
424
|
-
const height = contentShownRows > 0 ? contentShownRows * (itemHeight + rowGap) - rowGap : 0
|
|
425
|
-
|
|
426
|
-
interface Position {
|
|
427
|
-
left: number
|
|
428
|
-
top: number
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
function getPosition(index: number): Position {
|
|
432
|
-
const y = Math.floor(index / columnCount)
|
|
433
|
-
const x = index - y * columnCount
|
|
434
|
-
return {
|
|
435
|
-
left: x * (itemWidth + columnGapSize),
|
|
436
|
-
top: y * (itemHeight + rowGap)
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function getHidden(index: number) {
|
|
441
|
-
if (typeof maxRows !== "number") return false
|
|
442
|
-
return index >= maxRows * columnCount
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
useEffect(() => {
|
|
446
|
-
let timeout: number
|
|
447
|
-
const observer = new ResizeObserver(entries => {
|
|
448
|
-
clearTimeout(timeout)
|
|
449
|
-
function task() {
|
|
450
|
-
const { inlineSize: width } = entries[0].borderBoxSize[0]
|
|
451
|
-
const [newColumnCount, newColumnGapSize] = getGapCountAndSize(width, itemWidth, minColumnGap, maxColumnGap)
|
|
452
|
-
setShowItems(true)
|
|
453
|
-
setWidth(width)
|
|
454
|
-
setColumnCount(newColumnCount)
|
|
455
|
-
setColumnGapSize(newColumnGapSize)
|
|
456
|
-
}
|
|
457
|
-
if (throttle === null) {
|
|
458
|
-
task()
|
|
459
|
-
} else {
|
|
460
|
-
timeout = window.setTimeout(task, throttle || 200)
|
|
461
|
-
}
|
|
462
|
-
})
|
|
463
|
-
observer.observe(ele.current!)
|
|
464
|
-
|
|
465
|
-
return () => {
|
|
466
|
-
observer.disconnect()
|
|
467
|
-
}
|
|
468
|
-
}, [itemWidth, throttle, columnGap])
|
|
469
|
-
|
|
470
|
-
useEffect(() => {
|
|
471
|
-
onSizeChange?.({ width, height, itemWidth, itemHeight, columnGap: columnGapSize, columnCount, rowGap, rowCount: contentShownRows, overflow: data.length > contentShownRows * columnCount, itemCount: data.length, maxRows: maxRows ?? null })
|
|
472
|
-
}, [width, height, columnGapSize, columnCount, rowGap, contentShownRows, data.length, itemWidth, itemHeight, maxRows])
|
|
473
|
-
|
|
474
|
-
return (
|
|
475
|
-
<div ref={ele} className={className} style={{ position: "relative", boxSizing: "border-box", height, ...style }} {...others}>
|
|
476
|
-
{showItems &&
|
|
477
|
-
data.map((it, idx) => (
|
|
478
|
-
<div
|
|
479
|
-
key={keyExactor?.(it, idx) || idx}
|
|
480
|
-
className={containerClassName}
|
|
481
|
-
style={{
|
|
482
|
-
position: "absolute",
|
|
483
|
-
width: itemWidth,
|
|
484
|
-
height: itemHeight,
|
|
485
|
-
transition: transitionDuration !== null ? `all ${transitionDuration ?? 400}ms` : undefined,
|
|
486
|
-
...getPosition(idx)
|
|
487
|
-
}}>
|
|
488
|
-
<div style={{ width: itemWidth, height: itemHeight, display: maxRows && idx >= maxRows * columnCount ? "none" : "block", ...containerStyle }}>{render(it, idx, getHidden(idx))}</div>
|
|
489
|
-
</div>
|
|
490
|
-
))}
|
|
491
|
-
</div>
|
|
492
|
-
)
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
export interface TrapeziumProps extends HTMLAttributes<HTMLDivElement> {
|
|
496
|
-
top: number
|
|
497
|
-
bottom: number
|
|
498
|
-
height: number
|
|
499
|
-
borderRadius: number
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/** 梯形组件 */
|
|
503
|
-
export const Trapezium = forwardRef<HTMLDivElement, TrapeziumProps>((props, ref) => {
|
|
504
|
-
const { top, bottom, height, borderRadius, style, ...other } = props
|
|
505
|
-
|
|
506
|
-
const diff = (bottom - top) / 2
|
|
507
|
-
|
|
508
|
-
const a = Math.atan(height / diff) / 2
|
|
509
|
-
|
|
510
|
-
const b = borderRadius / Math.tan(a)
|
|
511
|
-
|
|
512
|
-
const c = b * Math.cos(a * 2)
|
|
513
|
-
|
|
514
|
-
const d = b * Math.sin(a * 2)
|
|
515
|
-
|
|
516
|
-
const e = Math.PI / 2 - a
|
|
517
|
-
|
|
518
|
-
const f = borderRadius / Math.tan(e)
|
|
519
|
-
|
|
520
|
-
const g = f * Math.cos(a * 2)
|
|
521
|
-
|
|
522
|
-
const h = f * Math.sin(a * 2)
|
|
523
|
-
|
|
524
|
-
return <div ref={ref} style={{ width: bottom, height, clipPath: `path("M ${diff + f} ${0} A ${borderRadius} ${borderRadius} 0 0 0 ${diff - g} ${h} L ${c} ${height - d} A ${borderRadius} ${borderRadius} 0 0 0 ${b} ${height} L ${bottom - b} ${height} A ${borderRadius} ${borderRadius} 0 0 0 ${bottom - c} ${height - d} L ${top + diff + g} ${h} A ${borderRadius} ${borderRadius} 0 0 0 ${top + diff - f} ${0} Z")`, ...style }} {...other} />
|
|
525
|
-
})
|
|
526
|
-
|
|
527
|
-
export interface LoopSwiperProps extends HTMLAttributes<HTMLDivElement> {
|
|
528
|
-
direction?: "horizontal" | "vertical"
|
|
529
|
-
reverse?: boolean
|
|
530
|
-
period: number
|
|
531
|
-
gap?: CSSProperties["gap"]
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
css`
|
|
535
|
-
@keyframes deepsea-horizontal-loop-swipe {
|
|
536
|
-
from {
|
|
537
|
-
transform: translateX(0);
|
|
538
|
-
}
|
|
539
|
-
to {
|
|
540
|
-
transform: translateX(-100%);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
@keyframes deepsea-reverse-horizontal-loop-swipe {
|
|
544
|
-
from {
|
|
545
|
-
transform: translateX(0);
|
|
546
|
-
}
|
|
547
|
-
to {
|
|
548
|
-
transform: translateX(100%);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
@keyframes deepsea-vertical-loop-swipe {
|
|
552
|
-
from {
|
|
553
|
-
transform: translateY(0);
|
|
554
|
-
}
|
|
555
|
-
to {
|
|
556
|
-
transform: translateY(-100%);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
@keyframes deepsea-reverse-vertical-loop-swipe {
|
|
560
|
-
from {
|
|
561
|
-
transform: translateY(0);
|
|
562
|
-
}
|
|
563
|
-
to {
|
|
564
|
-
transform: translateY(100%);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
`
|
|
568
|
-
|
|
569
|
-
/** 循环播放组件 */
|
|
570
|
-
export const LoopSwiper = forwardRef<HTMLDivElement, LoopSwiperProps>((props, ref) => {
|
|
571
|
-
const { style, children, direction, period, reverse, gap, ...others } = props
|
|
572
|
-
const wrapper = useRef<HTMLDivElement>(null)
|
|
573
|
-
const container = useRef<HTMLDivElement>(null)
|
|
574
|
-
const [swiper, setSwiper] = useState(false)
|
|
575
|
-
const directionRef = useRef(direction)
|
|
576
|
-
directionRef.current = direction
|
|
577
|
-
const flexDirection: CSSProperties["flexDirection"] = direction === "vertical" ? (reverse ? "column-reverse" : "column") : reverse ? "row-reverse" : "row"
|
|
578
|
-
const animationName = swiper ? (direction === "vertical" ? (reverse ? "deepsea-reverse-vertical-loop-swipe" : "deepsea-vertical-loop-swipe") : reverse ? "deepsea-reverse-horizontal-loop-swipe" : "deepsea-horizontal-loop-swipe") : "none"
|
|
579
|
-
const animationDuration = `${period}ms`
|
|
580
|
-
const animationTimingFunction = "linear"
|
|
581
|
-
const animationIterationCount = "infinite"
|
|
582
|
-
|
|
583
|
-
useImperativeHandle(ref, () => wrapper.current!)
|
|
584
|
-
|
|
585
|
-
useEffect(() => {
|
|
586
|
-
const wrapperEle = wrapper.current!
|
|
587
|
-
const containerEle = container.current!
|
|
588
|
-
let wrapperWidth = 0
|
|
589
|
-
let wrapperHeight = 0
|
|
590
|
-
let containerWidth = 0
|
|
591
|
-
let containerHeight = 0
|
|
592
|
-
const observer = new ResizeObserver(entries => {
|
|
593
|
-
entries.forEach(entry => {
|
|
594
|
-
if (entry.target === wrapperEle) {
|
|
595
|
-
wrapperWidth = entry.contentRect.width
|
|
596
|
-
wrapperHeight = entry.contentRect.height
|
|
597
|
-
} else if (entry.target === containerEle) {
|
|
598
|
-
containerWidth = entry.contentRect.width
|
|
599
|
-
containerHeight = entry.contentRect.height
|
|
600
|
-
}
|
|
601
|
-
})
|
|
602
|
-
setSwiper(directionRef.current === "vertical" ? containerHeight > wrapperHeight : containerWidth > wrapperWidth)
|
|
603
|
-
})
|
|
604
|
-
observer.observe(wrapperEle)
|
|
605
|
-
observer.observe(containerEle)
|
|
606
|
-
}, [])
|
|
607
|
-
|
|
608
|
-
return (
|
|
609
|
-
<div ref={wrapper} style={{ display: "flex", flexDirection, gap, ...style }} {...others}>
|
|
610
|
-
<div ref={container} style={{ display: "flex", flexDirection, gap, animationName, animationTimingFunction, animationDuration, animationIterationCount }}>
|
|
611
|
-
{children}
|
|
612
|
-
</div>
|
|
613
|
-
<div style={{ display: swiper ? "flex" : "none", flexDirection, gap, animationName, animationTimingFunction, animationDuration, animationIterationCount }}>{children}</div>
|
|
614
|
-
</div>
|
|
615
|
-
)
|
|
616
|
-
})
|
|
617
|
-
|
|
618
|
-
export interface SectionRingProps extends HTMLAttributes<HTMLDivElement> {
|
|
619
|
-
outerRadius: number
|
|
620
|
-
innerRadius: number
|
|
621
|
-
count: number
|
|
622
|
-
angel: number
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
export const SectionRing: FC<SectionRingProps> = props => {
|
|
626
|
-
const { outerRadius: o, innerRadius: i, count: c, angel: a, style, ...others } = props
|
|
627
|
-
|
|
628
|
-
const s = (Math.PI * 2) / c - a
|
|
629
|
-
|
|
630
|
-
function arc(radius: number, startAngle: number, endAngle: number, options: DrawArcOptions = {}) {
|
|
631
|
-
return drawArc(o, o, radius, startAngle, endAngle, options)
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
return (
|
|
635
|
-
<div
|
|
636
|
-
style={{
|
|
637
|
-
...style,
|
|
638
|
-
width: o * 2,
|
|
639
|
-
height: o * 2,
|
|
640
|
-
clipPath: `path("${Array(c)
|
|
641
|
-
.fill(0)
|
|
642
|
-
.map((it, idx) => `${arc(o, idx * (a + s), idx * (a + s) + a)} ${arc(i, idx * (a + s) + a, idx * (a + s), { line: true, anticlockwise: true })}`)
|
|
643
|
-
.join(" ")} Z")`
|
|
644
|
-
}}
|
|
645
|
-
{...others}
|
|
646
|
-
/>
|
|
647
|
-
)
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
export interface TransitionNumProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
651
|
-
/** 当前数字 */
|
|
652
|
-
children: number
|
|
653
|
-
/** 变换周期,单位帧 */
|
|
654
|
-
period: number
|
|
655
|
-
/** 数字转换为字符串的方法 */
|
|
656
|
-
numToStr?: (num: number) => string
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
export interface TransitionNumIns {
|
|
660
|
-
get(): number
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
/** 渐变数字组件 */
|
|
664
|
-
export const TransitionNum = forwardRef<TransitionNumIns, TransitionNumProps>((props, ref) => {
|
|
665
|
-
const { children: num, period, numToStr, ...others } = props
|
|
666
|
-
if (!Number.isInteger(num) || !Number.isInteger(period) || period <= 0) {
|
|
667
|
-
throw new RangeError("目标数字必须是整数,周期必须是正整数")
|
|
668
|
-
}
|
|
669
|
-
const ele = useRef<HTMLDivElement>(null)
|
|
670
|
-
const cache = useRef({ num, period, numToStr, show: num })
|
|
671
|
-
cache.current = { ...cache.current, num, period, numToStr }
|
|
672
|
-
|
|
673
|
-
useImperativeHandle(ref, () => ({ get: () => cache.current.show }), [])
|
|
674
|
-
|
|
675
|
-
useEffect(() => {
|
|
676
|
-
const { num, period, show, numToStr } = cache.current
|
|
677
|
-
ele.current!.innerText = (numToStr || String)(show)
|
|
678
|
-
if (num === show) return
|
|
679
|
-
const div = ele.current!
|
|
680
|
-
const speed = (num - show) / period
|
|
681
|
-
const cancel = setFrameInterval(() => {
|
|
682
|
-
const { num, numToStr } = cache.current
|
|
683
|
-
cache.current.show += speed
|
|
684
|
-
if ((speed > 0 && cache.current.show > num) || (speed < 0 && cache.current.show < num)) {
|
|
685
|
-
cancel()
|
|
686
|
-
cache.current.show = num
|
|
687
|
-
}
|
|
688
|
-
div.innerText = (numToStr || String)(speed > 0 ? Math.floor(cache.current.show) : Math.ceil(cache.current.show))
|
|
689
|
-
}, 1)
|
|
690
|
-
|
|
691
|
-
return cancel
|
|
692
|
-
}, [num])
|
|
693
|
-
|
|
694
|
-
return <div ref={ele} {...others} />
|
|
695
|
-
})
|
|
696
|
-
|
|
697
|
-
export interface CircleTextProps extends Omit<HTMLAttributes<HTMLSpanElement>, "children"> {
|
|
698
|
-
/** 每一个方块的宽度 */
|
|
699
|
-
width: number
|
|
700
|
-
/** 每一个方块的高度 */
|
|
701
|
-
height: number
|
|
702
|
-
/** 圆弧的半径,不包含方块的高度 */
|
|
703
|
-
radius: number
|
|
704
|
-
/** 开始旋转的弧度,逆时针增加,0 为 x 轴方向 */
|
|
705
|
-
startAngel?: number
|
|
706
|
-
/** 每一个方块之间间隔的弧度 */
|
|
707
|
-
gapAngel?: number
|
|
708
|
-
/** 文字对齐的方式,默认居中 */
|
|
709
|
-
align?: "left" | "center" | "right"
|
|
710
|
-
/** 文字朝向圆心还是外侧,默认外侧 */
|
|
711
|
-
direction?: "inner" | "outer"
|
|
712
|
-
/** 是否反转文字顺序 */
|
|
713
|
-
reverse?: boolean
|
|
714
|
-
/** 分割文字的方法 */
|
|
715
|
-
separator?: string | RegExp | ((text: string) => string[])
|
|
716
|
-
/** 显示的文字 */
|
|
717
|
-
children: string
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
/** 环形文字 */
|
|
721
|
-
export const CircleText: FC<CircleTextProps> = props => {
|
|
722
|
-
const { width, height, radius, startAngel = 0, gapAngel = 0, align = "center", style, direction = "outer", reverse = false, separator, children, ...others } = props
|
|
723
|
-
const unitAngle = Math.atan(width / 2 / radius) * 2
|
|
724
|
-
const totalAngle = (unitAngle + gapAngel) * children.length - gapAngel
|
|
725
|
-
const offsetAngle = align === "left" ? 0 : align === "right" ? totalAngle : totalAngle / 2
|
|
726
|
-
|
|
727
|
-
function getTransform(idx: number) {
|
|
728
|
-
const angle = startAngel - idx * (unitAngle + gapAngel) + offsetAngle - unitAngle / 2
|
|
729
|
-
const x = (radius + height / 2) * Math.cos(angle) - width / 2
|
|
730
|
-
const y = (radius + height / 2) * Math.sin(angle) * -1 - height / 2
|
|
731
|
-
const z = Math.PI / 2 - angle + (direction === "inner" ? Math.PI : 0)
|
|
732
|
-
return `translateX(${x}px) translateY(${y}px) rotateZ(${(z / Math.PI) * 180}deg)`
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
const words = typeof separator === "function" ? separator(children) : children.split(separator ?? "")
|
|
736
|
-
|
|
737
|
-
if (reverse) words.reverse()
|
|
738
|
-
|
|
739
|
-
return (
|
|
740
|
-
<Fragment>
|
|
741
|
-
{words.map((w, idx) => (
|
|
742
|
-
<span key={idx} style={{ position: "absolute", ...style, transform: getTransform(idx), textAlign: "center", width, lineHeight: `${height}px`, height: height }} {...others}>
|
|
743
|
-
{w}
|
|
744
|
-
</span>
|
|
745
|
-
))}
|
|
746
|
-
</Fragment>
|
|
747
|
-
)
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
export const AutoSizeTextArea = forwardRef<HTMLTextAreaElement, TextareaHTMLAttributes<HTMLTextAreaElement>>((props, ref) => {
|
|
751
|
-
const { style = {}, ...others } = props
|
|
752
|
-
const { height, resize, overflowY, ...otherStyle } = style
|
|
753
|
-
const ele = useRef<HTMLTextAreaElement>(null)
|
|
754
|
-
|
|
755
|
-
useImperativeHandle(ref, () => ele.current!)
|
|
756
|
-
|
|
757
|
-
useEffect(() => {
|
|
758
|
-
const textarea = ele.current!
|
|
759
|
-
function resizeTextarea() {
|
|
760
|
-
textarea.style.height = "auto"
|
|
761
|
-
textarea.style.height = `${textarea.scrollHeight + textarea.offsetHeight - textarea.clientHeight}px`
|
|
762
|
-
}
|
|
763
|
-
textarea.addEventListener("input", resizeTextarea)
|
|
764
|
-
textarea.addEventListener("change", resizeTextarea)
|
|
765
|
-
|
|
766
|
-
return () => {
|
|
767
|
-
textarea.removeEventListener("input", resizeTextarea)
|
|
768
|
-
textarea.removeEventListener("change", resizeTextarea)
|
|
769
|
-
}
|
|
770
|
-
}, [])
|
|
771
|
-
|
|
772
|
-
return <textarea ref={ele} style={{ ...otherStyle, resize: "none", overflowY: "hidden" }} {...others} />
|
|
773
|
-
})
|
|
774
|
-
|
|
775
|
-
export interface AutoFitProps extends HTMLAttributes<HTMLDivElement> {
|
|
776
|
-
/** 设计稿宽度,默认 1920 */
|
|
777
|
-
width?: number
|
|
778
|
-
/** 设计稿高度,默认 1080 */
|
|
779
|
-
height?: number
|
|
780
|
-
/**
|
|
781
|
-
* 在哪些方向进行缩放,默认缩放所有方向
|
|
782
|
-
* 1. 水平方向是指,宽度按照设计稿进行占满,高度反向缩放
|
|
783
|
-
* 2. 垂直方向是指,高度按照设计稿进行占满,宽度反向缩放
|
|
784
|
-
* 3. 默认是水平和垂直方向都进行缩放,类似于 background-size: contain 的效果
|
|
785
|
-
*/
|
|
786
|
-
direction?: "horizontal" | "vertical" | "both"
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
/**
|
|
790
|
-
* 自适应缩放组件
|
|
791
|
-
*
|
|
792
|
-
* 注意:
|
|
793
|
-
* 1. 父元素必须设置非 static 定位方式
|
|
794
|
-
* 2. 父元素只能有且仅有一个子元素,那就是 AutoFit
|
|
795
|
-
* 3. 不要设置 AutoFit 的 position、left、top、transform、width、height 属性
|
|
796
|
-
* 4. 在第一次完成缩放前,无论 props 是什么,返回的都是 <div style={{ display: "none" }} />
|
|
797
|
-
* 5. 元素的属性、事件、资源并不是立即加载的,会有一帧的延迟,在第一次完成缩放后才会显示
|
|
798
|
-
*/
|
|
799
|
-
export const AutoFit = forwardRef<HTMLDivElement, AutoFitProps>((props, ref) => {
|
|
800
|
-
const { width = 1920, height = 1080, direction, className, ...rest } = props
|
|
801
|
-
const ele = useRef<HTMLDivElement>(null)
|
|
802
|
-
const [show, setShow] = useState(false)
|
|
803
|
-
useImperativeHandle(ref, () => ele.current!, [ele.current])
|
|
804
|
-
useLayoutEffect(() => {
|
|
805
|
-
const element = ele.current
|
|
806
|
-
const parent = element?.parentElement
|
|
807
|
-
if (!element || !parent) return
|
|
808
|
-
function listener(entries: ResizeObserverEntry[]) {
|
|
809
|
-
const entry = entries[0]
|
|
810
|
-
const { contentRect } = entry
|
|
811
|
-
if (direction === "horizontal") {
|
|
812
|
-
const scale = contentRect.width / width
|
|
813
|
-
element?.style.setProperty("--transform", `scale(${scale})`)
|
|
814
|
-
element?.style.setProperty("--width", `${width}px`)
|
|
815
|
-
element?.style.setProperty("--height", `${contentRect.height / scale}px`)
|
|
816
|
-
} else if (direction === "vertical") {
|
|
817
|
-
const scale = contentRect.height / height
|
|
818
|
-
element?.style.setProperty("--transform", `scale(${scale})`)
|
|
819
|
-
element?.style.setProperty("--width", `${contentRect.width / scale}px`)
|
|
820
|
-
element?.style.setProperty("--height", `${height}px`)
|
|
821
|
-
} else {
|
|
822
|
-
const scale = Math.min(contentRect.width / width, contentRect.height / height)
|
|
823
|
-
element?.style.setProperty("--transform", `translateX(${(contentRect.width - width * scale) / 2}px) translateY(${(contentRect.height - height * scale) / 2}px) scale(${scale})`)
|
|
824
|
-
element?.style.setProperty("--width", `${width}px`)
|
|
825
|
-
element?.style.setProperty("--height", `${height}px`)
|
|
826
|
-
}
|
|
827
|
-
setShow(true)
|
|
828
|
-
}
|
|
829
|
-
const observer = new ResizeObserver(listener)
|
|
830
|
-
observer.observe(parent)
|
|
831
|
-
return () => observer.disconnect()
|
|
832
|
-
}, [ele.current?.parentElement, width, height, direction])
|
|
833
|
-
|
|
834
|
-
if (process.env.NODE_ENV === "development") {
|
|
835
|
-
useEffect(() => {
|
|
836
|
-
const parent = ele.current?.parentElement
|
|
837
|
-
if (!parent) return
|
|
838
|
-
const style = getComputedStyle(parent)
|
|
839
|
-
if (style.position === "static") {
|
|
840
|
-
console.warn("AutoFit 组件的父元素的 position 属性不应该是 static")
|
|
841
|
-
}
|
|
842
|
-
}, [ele.current?.parentElement])
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
if (!show) return <div ref={ele} style={{ display: "none" }} />
|
|
846
|
-
|
|
847
|
-
return (
|
|
848
|
-
<div
|
|
849
|
-
ref={ele}
|
|
850
|
-
className={clsx(
|
|
851
|
-
css`
|
|
852
|
-
position: absolute;
|
|
853
|
-
transform: var(--transform);
|
|
854
|
-
transform-origin: top left;
|
|
855
|
-
width: var(--width);
|
|
856
|
-
height: var(--height);
|
|
857
|
-
`,
|
|
858
|
-
className
|
|
859
|
-
)}
|
|
860
|
-
{...rest}
|
|
861
|
-
/>
|
|
862
|
-
)
|
|
863
|
-
})
|
|
3
|
+
export * from "./components/AutoSizeTextarea"
|
|
4
|
+
export * from "./components/CircleText"
|
|
5
|
+
export * from "./components/Flow"
|
|
6
|
+
export * from "./components/HlsPlayer"
|
|
7
|
+
export * from "./components/InputFile"
|
|
8
|
+
export * from "./components/LoopSwiper"
|
|
9
|
+
export * from "./components/Ring"
|
|
10
|
+
export * from "./components/Scroll"
|
|
11
|
+
export * from "./components/SectionRing"
|
|
12
|
+
export * from "./components/Skeleton"
|
|
13
|
+
export * from "./components/TransitionBox"
|
|
14
|
+
export * from "./components/TransitionNum"
|
|
15
|
+
export * from "./components/Trapezium"
|