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.
Files changed (108) 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.js.map +2 -2
  5. package/dist/cjs/components/AutoSizeTextarea.d.ts +5 -0
  6. package/dist/cjs/components/AutoSizeTextarea.js +51 -0
  7. package/dist/cjs/components/AutoSizeTextarea.js.map +7 -0
  8. package/dist/cjs/components/CircleText.d.ts +25 -0
  9. package/dist/cjs/components/CircleText.js +48 -0
  10. package/dist/cjs/components/CircleText.js.map +7 -0
  11. package/dist/cjs/components/Flow.d.ts +57 -0
  12. package/dist/cjs/components/Flow.js +119 -0
  13. package/dist/cjs/components/Flow.js.map +7 -0
  14. package/dist/cjs/components/HlsPlayer.d.ts +6 -0
  15. package/dist/cjs/components/HlsPlayer.js +64 -0
  16. package/dist/cjs/components/HlsPlayer.js.map +7 -0
  17. package/dist/cjs/components/InputFile.d.ts +64 -0
  18. package/dist/cjs/components/InputFile.js +143 -0
  19. package/dist/cjs/components/InputFile.js.map +7 -0
  20. package/dist/cjs/components/LoopSwiper.d.ts +9 -0
  21. package/dist/cjs/components/LoopSwiper.js +116 -0
  22. package/dist/cjs/components/LoopSwiper.js.map +7 -0
  23. package/dist/cjs/components/Ring.d.ts +7 -0
  24. package/dist/cjs/components/Ring.js +37 -0
  25. package/dist/cjs/components/Ring.js.map +7 -0
  26. package/dist/cjs/components/Scroll.js.map +2 -2
  27. package/dist/cjs/components/SectionRing.d.ts +8 -0
  28. package/dist/cjs/components/SectionRing.js +50 -0
  29. package/dist/cjs/components/SectionRing.js.map +7 -0
  30. package/dist/cjs/components/Skeleton.d.ts +3 -0
  31. package/dist/cjs/components/Skeleton.js +61 -0
  32. package/dist/cjs/components/Skeleton.js.map +7 -0
  33. package/dist/cjs/components/TransitionBox.d.ts +10 -0
  34. package/dist/cjs/components/TransitionBox.js +45 -0
  35. package/dist/cjs/components/TransitionBox.js.map +7 -0
  36. package/dist/cjs/components/TransitionNum.d.ts +16 -0
  37. package/dist/cjs/components/TransitionNum.js +62 -0
  38. package/dist/cjs/components/TransitionNum.js.map +7 -0
  39. package/dist/cjs/components/Trapezium.d.ts +9 -0
  40. package/dist/cjs/components/Trapezium.js +44 -0
  41. package/dist/cjs/components/Trapezium.js.map +7 -0
  42. package/dist/cjs/index.d.ts +14 -265
  43. package/dist/cjs/index.js +28 -505
  44. package/dist/cjs/index.js.map +3 -3
  45. package/dist/esm/components/AutoFit.d.ts +25 -0
  46. package/dist/esm/components/AutoFit.js +87 -0
  47. package/dist/esm/components/AutoFit.js.map +1 -0
  48. package/dist/esm/components/AutoScroll.js.map +1 -1
  49. package/dist/esm/components/AutoSizeTextarea.d.ts +5 -0
  50. package/dist/esm/components/AutoSizeTextarea.js +45 -0
  51. package/dist/esm/components/AutoSizeTextarea.js.map +1 -0
  52. package/dist/esm/components/CircleText.d.ts +25 -0
  53. package/dist/esm/components/CircleText.js +49 -0
  54. package/dist/esm/components/CircleText.js.map +1 -0
  55. package/dist/esm/components/Flow.d.ts +57 -0
  56. package/dist/esm/components/Flow.js +130 -0
  57. package/dist/esm/components/Flow.js.map +1 -0
  58. package/dist/esm/components/HlsPlayer.d.ts +6 -0
  59. package/dist/esm/components/HlsPlayer.js +39 -0
  60. package/dist/esm/components/HlsPlayer.js.map +1 -0
  61. package/dist/esm/components/InputFile.d.ts +64 -0
  62. package/dist/esm/components/InputFile.js +167 -0
  63. package/dist/esm/components/InputFile.js.map +1 -0
  64. package/dist/esm/components/LoopSwiper.d.ts +9 -0
  65. package/dist/esm/components/LoopSwiper.js +122 -0
  66. package/dist/esm/components/LoopSwiper.js.map +1 -0
  67. package/dist/esm/components/Ring.d.ts +7 -0
  68. package/dist/esm/components/Ring.js +26 -0
  69. package/dist/esm/components/Ring.js.map +1 -0
  70. package/dist/esm/components/Scroll.js.map +1 -1
  71. package/dist/esm/components/SectionRing.d.ts +8 -0
  72. package/dist/esm/components/SectionRing.js +31 -0
  73. package/dist/esm/components/SectionRing.js.map +1 -0
  74. package/dist/esm/components/Skeleton.d.ts +3 -0
  75. package/dist/esm/components/Skeleton.js +36 -0
  76. package/dist/esm/components/Skeleton.js.map +1 -0
  77. package/dist/esm/components/TransitionBox.d.ts +10 -0
  78. package/dist/esm/components/TransitionBox.js +50 -0
  79. package/dist/esm/components/TransitionBox.js.map +1 -0
  80. package/dist/esm/components/TransitionNum.d.ts +16 -0
  81. package/dist/esm/components/TransitionNum.js +65 -0
  82. package/dist/esm/components/TransitionNum.js.map +1 -0
  83. package/dist/esm/components/Trapezium.d.ts +9 -0
  84. package/dist/esm/components/Trapezium.js +35 -0
  85. package/dist/esm/components/Trapezium.js.map +1 -0
  86. package/dist/esm/index.d.ts +14 -265
  87. package/dist/esm/index.js +14 -843
  88. package/dist/esm/index.js.map +1 -1
  89. package/package.json +4 -3
  90. package/src/components/AutoFit.tsx +95 -0
  91. package/src/components/AutoScroll.tsx +1 -0
  92. package/src/components/AutoSizeTextarea.tsx +31 -0
  93. package/src/components/CircleText.tsx +56 -0
  94. package/src/components/Flow.tsx +154 -0
  95. package/src/components/HlsPlayer.tsx +38 -0
  96. package/src/components/InputFile.tsx +193 -0
  97. package/src/components/LoopSwiper.tsx +101 -0
  98. package/src/components/Ring.tsx +19 -0
  99. package/src/components/Scroll.tsx +1 -0
  100. package/src/components/SectionRing.tsx +36 -0
  101. package/src/components/Skeleton.tsx +39 -0
  102. package/src/components/TransitionBox.tsx +36 -0
  103. package/src/components/TransitionNum.tsx +54 -0
  104. package/src/components/Trapezium.tsx +35 -0
  105. package/src/index.tsx +14 -862
  106. package/dist/cjs/skeleton.css +0 -18
  107. package/dist/esm/skeleton.css +0 -18
  108. 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
+ })
@@ -1,4 +1,5 @@
1
1
  "use client"
2
+
2
3
  import { css } from "@emotion/css"
3
4
  import { clsx } from "deepsea-tools"
4
5
  import { CSSProperties, ForwardedRef, forwardRef, HTMLAttributes, useEffect, useImperativeHandle, useLayoutEffect, useRef } from "react"
@@ -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
+ })