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
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { ButtonHTMLAttributes, ChangeEvent, Fragment, InputHTMLAttributes, MouseEvent as ReactMouseEvent, forwardRef, useRef, useState } from "react"
|
|
4
|
+
import { read, utils, writeFile } from "xlsx"
|
|
5
|
+
|
|
6
|
+
export interface InputFileDataTypes {
|
|
7
|
+
base64: string
|
|
8
|
+
text: string
|
|
9
|
+
arrayBuffer: ArrayBuffer
|
|
10
|
+
binary: string
|
|
11
|
+
file: File
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type InputFileDataType = keyof InputFileDataTypes
|
|
15
|
+
|
|
16
|
+
export interface InputFileData<T> {
|
|
17
|
+
result: T
|
|
18
|
+
file: File
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type InputFileProps = (
|
|
22
|
+
| {
|
|
23
|
+
multiple?: false
|
|
24
|
+
type: "base64" | "text" | "binary"
|
|
25
|
+
onChange?: (data: InputFileData<string>) => void
|
|
26
|
+
}
|
|
27
|
+
| {
|
|
28
|
+
multiple?: false
|
|
29
|
+
type: "arrayBuffer"
|
|
30
|
+
onChange?: (data: InputFileData<ArrayBuffer>) => void
|
|
31
|
+
}
|
|
32
|
+
| {
|
|
33
|
+
multiple?: false
|
|
34
|
+
type?: "file"
|
|
35
|
+
onChange?: (data: InputFileData<File>) => void
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
multiple: true
|
|
39
|
+
type: "base64" | "text" | "binary"
|
|
40
|
+
onChange?: (data: InputFileData<string>[]) => void
|
|
41
|
+
}
|
|
42
|
+
| {
|
|
43
|
+
multiple: true
|
|
44
|
+
type: "arrayBuffer"
|
|
45
|
+
onChange?: (data: InputFileData<ArrayBuffer>[]) => void
|
|
46
|
+
}
|
|
47
|
+
| {
|
|
48
|
+
multiple: true
|
|
49
|
+
type?: "file"
|
|
50
|
+
onChange?: (data: InputFileData<File>[]) => void
|
|
51
|
+
}
|
|
52
|
+
) &
|
|
53
|
+
Omit<InputHTMLAttributes<HTMLInputElement>, "onChange" | "multiple" | "type"> & {
|
|
54
|
+
/** 是否在捕获文件后清除 input 上的文件,默认为 false,主要区别在于如果不清除,连续两次选择同样的文件不会触发 onChange 事件,如果用于 form 表单,请设置为 flase */
|
|
55
|
+
clearAfterChange?: boolean
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function getFileData<T extends InputFileDataType>(file: File, type: T): Promise<InputFileDataTypes[T]> {
|
|
59
|
+
const fileReader = new FileReader()
|
|
60
|
+
switch (type) {
|
|
61
|
+
case "arrayBuffer":
|
|
62
|
+
fileReader.readAsArrayBuffer(file)
|
|
63
|
+
break
|
|
64
|
+
case "binary":
|
|
65
|
+
fileReader.readAsBinaryString(file)
|
|
66
|
+
break
|
|
67
|
+
case "base64":
|
|
68
|
+
fileReader.readAsDataURL(file)
|
|
69
|
+
break
|
|
70
|
+
case "text":
|
|
71
|
+
fileReader.readAsText(file)
|
|
72
|
+
break
|
|
73
|
+
default:
|
|
74
|
+
return file as any
|
|
75
|
+
}
|
|
76
|
+
return new Promise(resolve => {
|
|
77
|
+
fileReader.addEventListener("load", () => {
|
|
78
|
+
resolve(fileReader.result as any)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** 专用于读取文件的组件 */
|
|
84
|
+
export const InputFile = forwardRef<HTMLInputElement, InputFileProps>((props, ref) => {
|
|
85
|
+
const { multiple = false, type = "file", onChange, disabled: inputDisabled, clearAfterChange, ...rest } = props
|
|
86
|
+
const [disabled, setDisabled] = useState(false)
|
|
87
|
+
|
|
88
|
+
async function onInputChange(e: ChangeEvent<HTMLInputElement>) {
|
|
89
|
+
const input = e.target
|
|
90
|
+
const { files } = input
|
|
91
|
+
if (!files || files.length === 0) return
|
|
92
|
+
setDisabled(true)
|
|
93
|
+
try {
|
|
94
|
+
if (multiple) {
|
|
95
|
+
const result: any[] = []
|
|
96
|
+
for (const file of Array.from(files)) {
|
|
97
|
+
result.push({
|
|
98
|
+
result: await getFileData(file, type),
|
|
99
|
+
file
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
onChange?.(result as any)
|
|
103
|
+
} else {
|
|
104
|
+
onChange?.({
|
|
105
|
+
result: await getFileData(files[0], type),
|
|
106
|
+
file: files[0]
|
|
107
|
+
} as any)
|
|
108
|
+
}
|
|
109
|
+
setDisabled(false)
|
|
110
|
+
if (clearAfterChange) input.value = ""
|
|
111
|
+
} catch (error) {
|
|
112
|
+
setDisabled(false)
|
|
113
|
+
if (clearAfterChange) input.value = ""
|
|
114
|
+
throw error
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return <input ref={ref} disabled={disabled || inputDisabled} type="file" multiple={multiple} onChange={onInputChange} {...rest} />
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
export type InputFileButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
122
|
+
input: InputFileProps
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** 专用于读取文件的 button 组件 */
|
|
126
|
+
export const InputFileButton = forwardRef<HTMLButtonElement, InputFileButtonProps>((props, ref) => {
|
|
127
|
+
const { onClick, input: inputProps, ...rest } = props
|
|
128
|
+
const { style, ...restInputProps } = inputProps
|
|
129
|
+
const input = useRef<HTMLInputElement>(null)
|
|
130
|
+
|
|
131
|
+
function onBtnClick(e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) {
|
|
132
|
+
input.current?.click()
|
|
133
|
+
onClick?.(e)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<Fragment>
|
|
138
|
+
<InputFile ref={input} style={{ display: "none", ...style }} {...restInputProps} />
|
|
139
|
+
<button ref={ref} type="button" onClick={onBtnClick} {...rest} />
|
|
140
|
+
</Fragment>
|
|
141
|
+
)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
export interface ImportExcelProps extends Omit<InputFileProps, "multiple" | "onChange" | "accept" | "type"> {
|
|
145
|
+
onChange?: (data: Record<string, string>[]) => void
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** 专门用于读取 excel 的组件 */
|
|
149
|
+
export const ImportExcel = forwardRef<HTMLInputElement, ImportExcelProps>((props, ref) => {
|
|
150
|
+
const { onChange, ...rest } = props
|
|
151
|
+
|
|
152
|
+
function onInputChange(data: InputFileData<ArrayBuffer>) {
|
|
153
|
+
const wb = read(data.result)
|
|
154
|
+
const result = utils.sheet_to_json<any>(wb.Sheets[wb.SheetNames[0]])
|
|
155
|
+
if (typeof result === "object") {
|
|
156
|
+
const $ = result.map(it => {
|
|
157
|
+
const _: Record<string, string> = {}
|
|
158
|
+
Object.keys(it)
|
|
159
|
+
.filter(key => key !== "__rowNum__")
|
|
160
|
+
.forEach(key => (_[key] = String(it[key])))
|
|
161
|
+
return _
|
|
162
|
+
})
|
|
163
|
+
onChange?.($)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return <InputFile ref={ref} accept=".xlsx" type="arrayBuffer" onChange={onInputChange} {...rest} />
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
/** 手动导出 excel */
|
|
171
|
+
export function exportExcel(data: Record<string, string>[], name: string) {
|
|
172
|
+
const workSheet = utils.json_to_sheet(data)
|
|
173
|
+
const workBook = utils.book_new()
|
|
174
|
+
utils.book_append_sheet(workBook, workSheet)
|
|
175
|
+
writeFile(workBook, `${name}${name.endsWith(".xlsx") ? "" : ".xlsx"}`)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface ExportExcelProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
179
|
+
data: Record<string, string>[]
|
|
180
|
+
fileName: string
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** 导出 excel 的 button 组件 */
|
|
184
|
+
export const ExportExcel = forwardRef<HTMLButtonElement, ExportExcelProps>((props, ref) => {
|
|
185
|
+
const { data, fileName, onClick, ...rest } = props
|
|
186
|
+
|
|
187
|
+
function onButtonClick(e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) {
|
|
188
|
+
exportExcel(data, fileName)
|
|
189
|
+
onClick?.(e)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return <button ref={ref} onClick={onButtonClick} {...rest} />
|
|
193
|
+
})
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { css } from "@emotion/css"
|
|
4
|
+
import { clsx } from "deepsea-tools"
|
|
5
|
+
import { CSSProperties, forwardRef, HTMLAttributes, useEffect, useImperativeHandle, useRef, useState } from "react"
|
|
6
|
+
|
|
7
|
+
export interface LoopSwiperProps extends HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
direction?: "horizontal" | "vertical"
|
|
9
|
+
reverse?: boolean
|
|
10
|
+
period: number
|
|
11
|
+
gap?: CSSProperties["gap"]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** 循环播放组件 */
|
|
15
|
+
export const LoopSwiper = forwardRef<HTMLDivElement, LoopSwiperProps>((props, ref) => {
|
|
16
|
+
const { className, style, children, direction, period, reverse, gap, ...rest } = props
|
|
17
|
+
const wrapper = useRef<HTMLDivElement>(null)
|
|
18
|
+
const container = useRef<HTMLDivElement>(null)
|
|
19
|
+
const [swiper, setSwiper] = useState(false)
|
|
20
|
+
const directionRef = useRef(direction)
|
|
21
|
+
directionRef.current = direction
|
|
22
|
+
const flexDirection: CSSProperties["flexDirection"] = direction === "vertical" ? (reverse ? "column-reverse" : "column") : reverse ? "row-reverse" : "row"
|
|
23
|
+
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"
|
|
24
|
+
const animationDuration = `${period}ms`
|
|
25
|
+
const animationTimingFunction = "linear"
|
|
26
|
+
const animationIterationCount = "infinite"
|
|
27
|
+
|
|
28
|
+
useImperativeHandle(ref, () => wrapper.current!, [])
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const wrapperEle = wrapper.current!
|
|
32
|
+
const containerEle = container.current!
|
|
33
|
+
let wrapperWidth = 0
|
|
34
|
+
let wrapperHeight = 0
|
|
35
|
+
let containerWidth = 0
|
|
36
|
+
let containerHeight = 0
|
|
37
|
+
const observer = new ResizeObserver(entries => {
|
|
38
|
+
entries.forEach(entry => {
|
|
39
|
+
if (entry.target === wrapperEle) {
|
|
40
|
+
wrapperWidth = entry.contentRect.width
|
|
41
|
+
wrapperHeight = entry.contentRect.height
|
|
42
|
+
} else if (entry.target === containerEle) {
|
|
43
|
+
containerWidth = entry.contentRect.width
|
|
44
|
+
containerHeight = entry.contentRect.height
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
setSwiper(directionRef.current === "vertical" ? containerHeight > wrapperHeight : containerWidth > wrapperWidth)
|
|
48
|
+
})
|
|
49
|
+
observer.observe(wrapperEle)
|
|
50
|
+
observer.observe(containerEle)
|
|
51
|
+
}, [])
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
ref={wrapper}
|
|
56
|
+
className={clsx(
|
|
57
|
+
css`
|
|
58
|
+
@keyframes deepsea-horizontal-loop-swipe {
|
|
59
|
+
from {
|
|
60
|
+
transform: translateX(0);
|
|
61
|
+
}
|
|
62
|
+
to {
|
|
63
|
+
transform: translateX(-100%);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
@keyframes deepsea-reverse-horizontal-loop-swipe {
|
|
67
|
+
from {
|
|
68
|
+
transform: translateX(0);
|
|
69
|
+
}
|
|
70
|
+
to {
|
|
71
|
+
transform: translateX(100%);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
@keyframes deepsea-vertical-loop-swipe {
|
|
75
|
+
from {
|
|
76
|
+
transform: translateY(0);
|
|
77
|
+
}
|
|
78
|
+
to {
|
|
79
|
+
transform: translateY(-100%);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
@keyframes deepsea-reverse-vertical-loop-swipe {
|
|
83
|
+
from {
|
|
84
|
+
transform: translateY(0);
|
|
85
|
+
}
|
|
86
|
+
to {
|
|
87
|
+
transform: translateY(100%);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
`,
|
|
91
|
+
className
|
|
92
|
+
)}
|
|
93
|
+
style={{ display: "flex", flexDirection, gap, ...style }}
|
|
94
|
+
{...rest}>
|
|
95
|
+
<div ref={container} style={{ display: "flex", flexDirection, gap, animationName, animationTimingFunction, animationDuration, animationIterationCount }}>
|
|
96
|
+
{children}
|
|
97
|
+
</div>
|
|
98
|
+
<div style={{ display: swiper ? "flex" : "none", flexDirection, gap, animationName, animationTimingFunction, animationDuration, animationIterationCount }}>{children}</div>
|
|
99
|
+
</div>
|
|
100
|
+
)
|
|
101
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { forwardRef, HTMLAttributes } from "react"
|
|
4
|
+
|
|
5
|
+
export interface RingProps extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
outerWidth: number
|
|
7
|
+
innerWidth: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** 环形组件 */
|
|
11
|
+
export const Ring = forwardRef<HTMLDivElement, RingProps>((props, ref) => {
|
|
12
|
+
const { outerWidth, innerWidth, style, ...rest } = props
|
|
13
|
+
|
|
14
|
+
const outerRadius = outerWidth / 2
|
|
15
|
+
|
|
16
|
+
const innerRadius = innerWidth / 2
|
|
17
|
+
|
|
18
|
+
return <div ref={ref} 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")` }} {...rest} />
|
|
19
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { DrawArcOptions, drawArc } from "deepsea-tools"
|
|
4
|
+
import { FC, HTMLAttributes } from "react"
|
|
5
|
+
|
|
6
|
+
export interface SectionRingProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
outerRadius: number
|
|
8
|
+
innerRadius: number
|
|
9
|
+
count: number
|
|
10
|
+
angel: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const SectionRing: FC<SectionRingProps> = props => {
|
|
14
|
+
const { outerRadius: o, innerRadius: i, count: c, angel: a, style, ...rest } = props
|
|
15
|
+
|
|
16
|
+
const s = (Math.PI * 2) / c - a
|
|
17
|
+
|
|
18
|
+
function arc(radius: number, startAngle: number, endAngle: number, options: DrawArcOptions = {}) {
|
|
19
|
+
return drawArc(o, o, radius, startAngle, endAngle, options)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
style={{
|
|
25
|
+
...style,
|
|
26
|
+
width: o * 2,
|
|
27
|
+
height: o * 2,
|
|
28
|
+
clipPath: `path("${Array(c)
|
|
29
|
+
.fill(0)
|
|
30
|
+
.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 })}`)
|
|
31
|
+
.join(" ")} Z")`
|
|
32
|
+
}}
|
|
33
|
+
{...rest}
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { css } from "@emotion/css"
|
|
4
|
+
import { clsx } from "deepsea-tools"
|
|
5
|
+
import { forwardRef, HTMLAttributes } from "react"
|
|
6
|
+
|
|
7
|
+
const Skeleton = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>((props, ref) => {
|
|
8
|
+
const { className, ...rest } = props
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
ref={ref}
|
|
13
|
+
className={clsx(
|
|
14
|
+
css`
|
|
15
|
+
@keyframes shimmer {
|
|
16
|
+
0% {
|
|
17
|
+
background-position: -400px 0px;
|
|
18
|
+
}
|
|
19
|
+
100% {
|
|
20
|
+
background-position: 400px 0px;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
background-image: linear-gradient(to right, rgba(0, 0, 0, 0.067) 8%, rgba(0, 0, 0, 0.133) 18%, rgba(0, 0, 0, 0.067) 33%);
|
|
25
|
+
animation-duration: 1s;
|
|
26
|
+
animation-fill-mode: forwards;
|
|
27
|
+
animation-iteration-count: infinite;
|
|
28
|
+
animation-name: shimmer;
|
|
29
|
+
animation-timing-function: linear;
|
|
30
|
+
background-size: 800px 104px;
|
|
31
|
+
`,
|
|
32
|
+
className
|
|
33
|
+
)}
|
|
34
|
+
{...rest}
|
|
35
|
+
/>
|
|
36
|
+
)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export default Skeleton
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useSize } from "ahooks"
|
|
4
|
+
import { CSSProperties, FC, forwardRef, HTMLAttributes, useEffect, useRef, useState } from "react"
|
|
5
|
+
|
|
6
|
+
export interface TransitionBoxProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
containerClassName?: string
|
|
8
|
+
containerStyle?: CSSProperties
|
|
9
|
+
vertical?: boolean
|
|
10
|
+
horizontal?: boolean
|
|
11
|
+
time?: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** 尺寸渐变的组件 */
|
|
15
|
+
export const TransitionBox: FC<TransitionBoxProps> = forwardRef<HTMLDivElement, TransitionBoxProps>((props, ref) => {
|
|
16
|
+
const { style, containerClassName, containerStyle, children, vertical = true, horizontal = true, time = 3000, ...rest } = props
|
|
17
|
+
const container = useRef<HTMLDivElement>(null)
|
|
18
|
+
const size = useSize(container)
|
|
19
|
+
const width = size?.width ?? 0
|
|
20
|
+
const height = size?.height ?? 0
|
|
21
|
+
const [count, setCount] = useState(0)
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
setCount(count => Math.min(count + 1, 3))
|
|
25
|
+
}, [width, height])
|
|
26
|
+
|
|
27
|
+
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 }
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div ref={ref} style={outerStyle} {...rest}>
|
|
31
|
+
<div className={containerClassName} style={{ position: "absolute", ...containerStyle }} ref={container}>
|
|
32
|
+
{children}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
)
|
|
36
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { setFrameInterval } from "deepsea-tools"
|
|
4
|
+
import { ForwardedRef, HTMLAttributes, forwardRef, useEffect, useImperativeHandle, useRef } from "react"
|
|
5
|
+
|
|
6
|
+
export interface TransitionNumProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
7
|
+
/** 当前数字 */
|
|
8
|
+
children: number
|
|
9
|
+
/** 变换周期,单位帧 */
|
|
10
|
+
period: number
|
|
11
|
+
/** 数字转换为字符串的方法 */
|
|
12
|
+
numToStr?: (num: number) => string
|
|
13
|
+
/** 实例 */
|
|
14
|
+
ins?: ForwardedRef<TransitionNumIns>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface TransitionNumIns {
|
|
18
|
+
get(): number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** 渐变数字组件 */
|
|
22
|
+
export const TransitionNum = forwardRef<HTMLDivElement, TransitionNumProps>((props, ref) => {
|
|
23
|
+
const { children: num, period, numToStr, ins, ...rest } = props
|
|
24
|
+
if (!Number.isInteger(num) || !Number.isInteger(period) || period <= 0) {
|
|
25
|
+
throw new RangeError("目标数字必须是整数,周期必须是正整数")
|
|
26
|
+
}
|
|
27
|
+
const ele = useRef<HTMLDivElement>(null)
|
|
28
|
+
const cache = useRef({ num, period, numToStr, show: num })
|
|
29
|
+
cache.current = { ...cache.current, num, period, numToStr }
|
|
30
|
+
|
|
31
|
+
useImperativeHandle(ref, () => ele.current!, [])
|
|
32
|
+
useImperativeHandle(ins, () => ({ get: () => cache.current.show }), [])
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const { num, period, show, numToStr } = cache.current
|
|
36
|
+
ele.current!.innerText = (numToStr || String)(show)
|
|
37
|
+
if (num === show) return
|
|
38
|
+
const div = ele.current!
|
|
39
|
+
const speed = (num - show) / period
|
|
40
|
+
const cancel = setFrameInterval(() => {
|
|
41
|
+
const { num, numToStr } = cache.current
|
|
42
|
+
cache.current.show += speed
|
|
43
|
+
if ((speed > 0 && cache.current.show > num) || (speed < 0 && cache.current.show < num)) {
|
|
44
|
+
cancel()
|
|
45
|
+
cache.current.show = num
|
|
46
|
+
}
|
|
47
|
+
div.innerText = (numToStr || String)(speed > 0 ? Math.floor(cache.current.show) : Math.ceil(cache.current.show))
|
|
48
|
+
}, 1)
|
|
49
|
+
|
|
50
|
+
return cancel
|
|
51
|
+
}, [num])
|
|
52
|
+
|
|
53
|
+
return <div ref={ele} {...rest} />
|
|
54
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { HTMLAttributes, forwardRef } from "react"
|
|
4
|
+
|
|
5
|
+
export interface TrapeziumProps extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
top: number
|
|
7
|
+
bottom: number
|
|
8
|
+
height: number
|
|
9
|
+
borderRadius: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** 梯形组件 */
|
|
13
|
+
export const Trapezium = forwardRef<HTMLDivElement, TrapeziumProps>((props, ref) => {
|
|
14
|
+
const { top, bottom, height, borderRadius, style, ...rest } = props
|
|
15
|
+
|
|
16
|
+
const diff = (bottom - top) / 2
|
|
17
|
+
|
|
18
|
+
const a = Math.atan(height / diff) / 2
|
|
19
|
+
|
|
20
|
+
const b = borderRadius / Math.tan(a)
|
|
21
|
+
|
|
22
|
+
const c = b * Math.cos(a * 2)
|
|
23
|
+
|
|
24
|
+
const d = b * Math.sin(a * 2)
|
|
25
|
+
|
|
26
|
+
const e = Math.PI / 2 - a
|
|
27
|
+
|
|
28
|
+
const f = borderRadius / Math.tan(e)
|
|
29
|
+
|
|
30
|
+
const g = f * Math.cos(a * 2)
|
|
31
|
+
|
|
32
|
+
const h = f * Math.sin(a * 2)
|
|
33
|
+
|
|
34
|
+
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 }} {...rest} />
|
|
35
|
+
})
|