deepsea-components 2.8.3 → 3.0.0

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 (105) hide show
  1. package/dist/cjs/components/AutoFit.d.ts +25 -0
  2. package/dist/cjs/components/AutoFit.js +99 -0
  3. package/dist/cjs/components/AutoFit.js.map +7 -0
  4. package/dist/cjs/components/AutoScroll.d.ts +5 -0
  5. package/dist/cjs/components/AutoScroll.js +8 -6
  6. package/dist/cjs/components/AutoScroll.js.map +2 -2
  7. package/dist/cjs/components/AutoSizeTextarea.d.ts +5 -0
  8. package/dist/cjs/components/AutoSizeTextarea.js +51 -0
  9. package/dist/cjs/components/AutoSizeTextarea.js.map +7 -0
  10. package/dist/cjs/components/CircleText.d.ts +25 -0
  11. package/dist/cjs/components/CircleText.js +48 -0
  12. package/dist/cjs/components/CircleText.js.map +7 -0
  13. package/dist/cjs/components/Flow.d.ts +57 -0
  14. package/dist/cjs/components/Flow.js +119 -0
  15. package/dist/cjs/components/Flow.js.map +7 -0
  16. package/dist/cjs/components/InputFile.d.ts +64 -0
  17. package/dist/cjs/components/InputFile.js +143 -0
  18. package/dist/cjs/components/InputFile.js.map +7 -0
  19. package/dist/cjs/components/LoopSwiper.d.ts +9 -0
  20. package/dist/cjs/components/LoopSwiper.js +116 -0
  21. package/dist/cjs/components/LoopSwiper.js.map +7 -0
  22. package/dist/cjs/components/Ring.d.ts +7 -0
  23. package/dist/cjs/components/Ring.js +37 -0
  24. package/dist/cjs/components/Ring.js.map +7 -0
  25. package/dist/cjs/components/Scroll.js.map +2 -2
  26. package/dist/cjs/components/SectionRing.d.ts +8 -0
  27. package/dist/cjs/components/SectionRing.js +50 -0
  28. package/dist/cjs/components/SectionRing.js.map +7 -0
  29. package/dist/cjs/components/Skeleton.d.ts +3 -0
  30. package/dist/cjs/components/Skeleton.js +60 -0
  31. package/dist/cjs/components/Skeleton.js.map +7 -0
  32. package/dist/cjs/components/TransitionBox.d.ts +10 -0
  33. package/dist/cjs/components/TransitionBox.js +45 -0
  34. package/dist/cjs/components/TransitionBox.js.map +7 -0
  35. package/dist/cjs/components/TransitionNum.d.ts +16 -0
  36. package/dist/cjs/components/TransitionNum.js +62 -0
  37. package/dist/cjs/components/TransitionNum.js.map +7 -0
  38. package/dist/cjs/components/Trapezium.d.ts +9 -0
  39. package/dist/cjs/components/Trapezium.js +44 -0
  40. package/dist/cjs/components/Trapezium.js.map +7 -0
  41. package/dist/cjs/index.d.ts +13 -265
  42. package/dist/cjs/index.js +26 -505
  43. package/dist/cjs/index.js.map +3 -3
  44. package/dist/esm/components/AutoFit.d.ts +25 -0
  45. package/dist/esm/components/AutoFit.js +87 -0
  46. package/dist/esm/components/AutoFit.js.map +1 -0
  47. package/dist/esm/components/AutoScroll.d.ts +5 -0
  48. package/dist/esm/components/AutoScroll.js +8 -5
  49. package/dist/esm/components/AutoScroll.js.map +1 -1
  50. package/dist/esm/components/AutoSizeTextarea.d.ts +5 -0
  51. package/dist/esm/components/AutoSizeTextarea.js +45 -0
  52. package/dist/esm/components/AutoSizeTextarea.js.map +1 -0
  53. package/dist/esm/components/CircleText.d.ts +25 -0
  54. package/dist/esm/components/CircleText.js +49 -0
  55. package/dist/esm/components/CircleText.js.map +1 -0
  56. package/dist/esm/components/Flow.d.ts +57 -0
  57. package/dist/esm/components/Flow.js +130 -0
  58. package/dist/esm/components/Flow.js.map +1 -0
  59. package/dist/esm/components/InputFile.d.ts +64 -0
  60. package/dist/esm/components/InputFile.js +167 -0
  61. package/dist/esm/components/InputFile.js.map +1 -0
  62. package/dist/esm/components/LoopSwiper.d.ts +9 -0
  63. package/dist/esm/components/LoopSwiper.js +122 -0
  64. package/dist/esm/components/LoopSwiper.js.map +1 -0
  65. package/dist/esm/components/Ring.d.ts +7 -0
  66. package/dist/esm/components/Ring.js +26 -0
  67. package/dist/esm/components/Ring.js.map +1 -0
  68. package/dist/esm/components/Scroll.js.map +1 -1
  69. package/dist/esm/components/SectionRing.d.ts +8 -0
  70. package/dist/esm/components/SectionRing.js +31 -0
  71. package/dist/esm/components/SectionRing.js.map +1 -0
  72. package/dist/esm/components/Skeleton.d.ts +3 -0
  73. package/dist/esm/components/Skeleton.js +35 -0
  74. package/dist/esm/components/Skeleton.js.map +1 -0
  75. package/dist/esm/components/TransitionBox.d.ts +10 -0
  76. package/dist/esm/components/TransitionBox.js +50 -0
  77. package/dist/esm/components/TransitionBox.js.map +1 -0
  78. package/dist/esm/components/TransitionNum.d.ts +16 -0
  79. package/dist/esm/components/TransitionNum.js +65 -0
  80. package/dist/esm/components/TransitionNum.js.map +1 -0
  81. package/dist/esm/components/Trapezium.d.ts +9 -0
  82. package/dist/esm/components/Trapezium.js +35 -0
  83. package/dist/esm/components/Trapezium.js.map +1 -0
  84. package/dist/esm/index.d.ts +13 -265
  85. package/dist/esm/index.js +13 -843
  86. package/dist/esm/index.js.map +1 -1
  87. package/package.json +3 -3
  88. package/src/components/AutoFit.tsx +95 -0
  89. package/src/components/AutoScroll.tsx +18 -9
  90. package/src/components/AutoSizeTextarea.tsx +31 -0
  91. package/src/components/CircleText.tsx +56 -0
  92. package/src/components/Flow.tsx +154 -0
  93. package/src/components/InputFile.tsx +193 -0
  94. package/src/components/LoopSwiper.tsx +101 -0
  95. package/src/components/Ring.tsx +19 -0
  96. package/src/components/Scroll.tsx +1 -0
  97. package/src/components/SectionRing.tsx +36 -0
  98. package/src/components/Skeleton.tsx +38 -0
  99. package/src/components/TransitionBox.tsx +36 -0
  100. package/src/components/TransitionNum.tsx +54 -0
  101. package/src/components/Trapezium.tsx +35 -0
  102. package/src/index.tsx +13 -862
  103. package/dist/cjs/skeleton.css +0 -18
  104. package/dist/esm/skeleton.css +0 -18
  105. package/src/skeleton.css +0 -18
package/src/index.tsx CHANGED
@@ -1,863 +1,14 @@
1
- "use client"
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 interface InputFileDataTypes {
11
- base64: string
12
- text: string
13
- arrayBuffer: ArrayBuffer
14
- binary: string
15
- file: File
16
- }
17
-
18
- export type InputFileDataType = keyof InputFileDataTypes
19
-
20
- export interface InputFileData<T> {
21
- result: T
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/InputFile"
7
+ export * from "./components/LoopSwiper"
8
+ export * from "./components/Ring"
9
+ export * from "./components/Scroll"
10
+ export * from "./components/SectionRing"
11
+ export * from "./components/Skeleton"
12
+ export * from "./components/TransitionBox"
13
+ export * from "./components/TransitionNum"
14
+ export * from "./components/Trapezium"