deepsea-components 5.15.21 → 5.15.23

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 (58) hide show
  1. package/README.md +23 -23
  2. package/dist/components/AutoFit.cjs +7 -7
  3. package/dist/components/AutoFit.js +7 -7
  4. package/dist/components/AutoScroll.cjs +7 -7
  5. package/dist/components/AutoScroll.js +7 -7
  6. package/dist/components/AutoSizeTextarea.cjs +4 -4
  7. package/dist/components/AutoSizeTextarea.js +4 -4
  8. package/dist/components/Flow.cjs +16 -16
  9. package/dist/components/Flow.js +16 -16
  10. package/dist/components/FormLabel.cjs +1 -1
  11. package/dist/components/FormLabel.js +1 -1
  12. package/dist/components/InfiniteScroll.cjs +13 -13
  13. package/dist/components/InfiniteScroll.js +13 -13
  14. package/dist/components/LoopSwiper.cjs +33 -33
  15. package/dist/components/LoopSwiper.js +33 -33
  16. package/dist/components/Scroll.cjs +13 -13
  17. package/dist/components/Scroll.js +13 -13
  18. package/dist/components/ScrollMask.module.cjs +2 -2
  19. package/dist/components/ScrollMask.module.js +2 -2
  20. package/dist/components/Skeleton.cjs +17 -17
  21. package/dist/components/Skeleton.js +17 -17
  22. package/dist/components/TransitionNum.cjs +1 -1
  23. package/dist/components/TransitionNum.js +1 -1
  24. package/dist/components/Trapezium.cjs +4 -4
  25. package/dist/components/Trapezium.js +4 -4
  26. package/package.json +4 -3
  27. package/src/components/AutoFit.tsx +104 -104
  28. package/src/components/AutoScroll.tsx +150 -150
  29. package/src/components/AutoSizeTextarea.tsx +50 -50
  30. package/src/components/CircleText.tsx +81 -81
  31. package/src/components/CopyButton.tsx +31 -31
  32. package/src/components/Echart.tsx +69 -69
  33. package/src/components/Flow.tsx +271 -271
  34. package/src/components/FormLabel.tsx +41 -41
  35. package/src/components/HlsPlayer.tsx +34 -34
  36. package/src/components/IconFileType.tsx +162 -162
  37. package/src/components/InfiniteScroll.tsx +163 -163
  38. package/src/components/InputFile.tsx +93 -93
  39. package/src/components/InputFileButton.tsx +122 -122
  40. package/src/components/LoopSwiper.tsx +125 -125
  41. package/src/components/ReadExcel.tsx +13 -13
  42. package/src/components/ReadSheet.tsx +17 -17
  43. package/src/components/Ring.tsx +30 -30
  44. package/src/components/Scroll.tsx +97 -97
  45. package/src/components/ScrollMask.module.css +87 -87
  46. package/src/components/ScrollMask.tsx +68 -68
  47. package/src/components/SectionRing.tsx +40 -40
  48. package/src/components/Skeleton.tsx +45 -45
  49. package/src/components/Title.tsx +27 -27
  50. package/src/components/TransitionBox.tsx +44 -44
  51. package/src/components/TransitionNum.tsx +54 -54
  52. package/src/components/Trapezium.tsx +60 -60
  53. package/src/components/Unify.tsx +38 -38
  54. package/src/components/WriteExcel.tsx +13 -13
  55. package/src/components/WriteSheet.tsx +25 -25
  56. package/src/utils/getReactVersion.ts +7 -7
  57. package/src/utils/index.ts +33 -33
  58. package/tsconfig.json +16 -16
@@ -1,150 +1,150 @@
1
- "use client"
2
-
3
- import { css } from "@emotion/css"
4
- import { clsx, getArray } from "deepsea-tools"
5
- import { CSSProperties, MouseEvent as ReactMouseEvent, forwardRef, useEffect, useImperativeHandle, useRef } from "react"
6
- import Scrollbar from "smooth-scrollbar"
7
- import { ScrollStatus } from "smooth-scrollbar/interfaces/scrollbar"
8
- import { useSize } from "soda-hooks"
9
-
10
- import { px, transformCSSVariable } from "@/utils"
11
-
12
- import { Scroll, ScrollProps } from "./Scroll"
13
-
14
- export interface AutoScrollProps extends ScrollProps {
15
- /** 轮播元素的个数 */
16
- count: number
17
-
18
- /** 轮播元素的高度 */
19
- itemHeight: number
20
-
21
- /**
22
- * 轮播动画的时间,单位毫秒
23
- * @default 1000
24
- */
25
- animation?: number
26
-
27
- /**
28
- * 每个元素的停留时间,单位毫秒
29
- * @default 3000
30
- */
31
- duration?: number
32
-
33
- /**
34
- * 元素之间的间距
35
- * @default 0
36
- */
37
- gap?: number
38
-
39
- /** 容器类名 */
40
- containerClassName?: string
41
-
42
- /** 容器样式 */
43
- containerStyle?: CSSProperties
44
-
45
- /**
46
- * 在鼠标移入时是否继续播放
47
- * @default false
48
- */
49
- playOnMouseEnter?: boolean
50
-
51
- /**
52
- * 是否暂停
53
- * @default false
54
- */
55
- paused?: boolean
56
- }
57
-
58
- export const AutoScroll = forwardRef<HTMLDivElement, AutoScrollProps>((props, ref) => {
59
- const {
60
- count,
61
- itemHeight,
62
- animation = 1000,
63
- duration = 3000,
64
- onMouseEnter,
65
- onMouseLeave,
66
- gap = 0,
67
- containerClassName,
68
- containerStyle,
69
- children,
70
- playOnMouseEnter,
71
- scrollbar,
72
- paused,
73
- ...rest
74
- } = props
75
- const bar = useRef<Scrollbar | null>(null)
76
- const timeout = useRef<NodeJS.Timeout | undefined>(undefined)
77
- const ele = useRef<HTMLDivElement>(null)
78
- const size = useSize(ele)
79
- const pausedRef = useRef(false)
80
- const pausedProps = useRef(paused)
81
- pausedProps.current = paused
82
-
83
- useImperativeHandle(ref, () => ele.current!, [])
84
- useImperativeHandle(scrollbar, () => bar.current!, [])
85
-
86
- useEffect(() => {
87
- if (playOnMouseEnter) pausedRef.current = false
88
- }, [playOnMouseEnter])
89
-
90
- useEffect(() => {
91
- if (!size || count === 0) return
92
- const { height } = size
93
- const range = getArray(count, index => (itemHeight + gap) * (index + 1) - (index === count - 1 ? gap : 0))
94
- const scrollHeight = range[range.length - 1]
95
- if (height >= scrollHeight) return
96
- function scroll(target: number) {
97
- clearTimeout(timeout.current)
98
- timeout.current = setTimeout(() => {
99
- if (pausedRef.current || pausedProps.current) return scroll(target)
100
- bar.current?.scrollTo(0, target, animation)
101
- }, duration)
102
- }
103
- scroll(range[0])
104
- function listener(status: ScrollStatus) {
105
- const { y } = status.offset
106
- const scrollToBottom = Math.abs(y + height - scrollHeight) / itemHeight <= 0.05
107
- const target = scrollToBottom ? 0 : range.find(item => item > y)!
108
- scroll(target)
109
- }
110
- bar.current?.addListener(listener)
111
- return () => {
112
- clearTimeout(timeout.current)
113
- bar.current?.removeListener(listener)
114
- bar.current?.scrollTo(0, 0)
115
- }
116
- }, [size, count, itemHeight, gap, duration, animation])
117
-
118
- function onContainerMouseEnter(e: ReactMouseEvent<HTMLDivElement, MouseEvent>) {
119
- if (playOnMouseEnter) return
120
- pausedRef.current = true
121
- onMouseEnter?.(e)
122
- }
123
-
124
- function onContainerMouseLeave(e: ReactMouseEvent<HTMLDivElement, MouseEvent>) {
125
- if (playOnMouseEnter) return
126
- pausedRef.current = false
127
- onMouseLeave?.(e)
128
- }
129
-
130
- return (
131
- <Scroll ref={ele} scrollbar={bar} onMouseEnter={onContainerMouseEnter} onMouseLeave={onContainerMouseLeave} {...rest}>
132
- <div
133
- className={clsx(
134
- css`
135
- display: flex;
136
- flex-direction: column;
137
- gap: var(--gap, 0);
138
- & > * {
139
- flex: none;
140
- }
141
- `,
142
- containerClassName,
143
- )}
144
- style={transformCSSVariable({ gap: px(gap) }, containerStyle)}
145
- >
146
- {children}
147
- </div>
148
- </Scroll>
149
- )
150
- })
1
+ "use client"
2
+
3
+ import { css } from "@emotion/css"
4
+ import { clsx, getArray } from "deepsea-tools"
5
+ import { CSSProperties, MouseEvent as ReactMouseEvent, forwardRef, useEffect, useImperativeHandle, useRef } from "react"
6
+ import Scrollbar from "smooth-scrollbar"
7
+ import { ScrollStatus } from "smooth-scrollbar/interfaces/scrollbar"
8
+ import { useSize } from "soda-hooks"
9
+
10
+ import { px, transformCSSVariable } from "@/utils"
11
+
12
+ import { Scroll, ScrollProps } from "./Scroll"
13
+
14
+ export interface AutoScrollProps extends ScrollProps {
15
+ /** 轮播元素的个数 */
16
+ count: number
17
+
18
+ /** 轮播元素的高度 */
19
+ itemHeight: number
20
+
21
+ /**
22
+ * 轮播动画的时间,单位毫秒
23
+ * @default 1000
24
+ */
25
+ animation?: number
26
+
27
+ /**
28
+ * 每个元素的停留时间,单位毫秒
29
+ * @default 3000
30
+ */
31
+ duration?: number
32
+
33
+ /**
34
+ * 元素之间的间距
35
+ * @default 0
36
+ */
37
+ gap?: number
38
+
39
+ /** 容器类名 */
40
+ containerClassName?: string
41
+
42
+ /** 容器样式 */
43
+ containerStyle?: CSSProperties
44
+
45
+ /**
46
+ * 在鼠标移入时是否继续播放
47
+ * @default false
48
+ */
49
+ playOnMouseEnter?: boolean
50
+
51
+ /**
52
+ * 是否暂停
53
+ * @default false
54
+ */
55
+ paused?: boolean
56
+ }
57
+
58
+ export const AutoScroll = forwardRef<HTMLDivElement, AutoScrollProps>((props, ref) => {
59
+ const {
60
+ count,
61
+ itemHeight,
62
+ animation = 1000,
63
+ duration = 3000,
64
+ onMouseEnter,
65
+ onMouseLeave,
66
+ gap = 0,
67
+ containerClassName,
68
+ containerStyle,
69
+ children,
70
+ playOnMouseEnter,
71
+ scrollbar,
72
+ paused,
73
+ ...rest
74
+ } = props
75
+ const bar = useRef<Scrollbar | null>(null)
76
+ const timeout = useRef<NodeJS.Timeout | undefined>(undefined)
77
+ const ele = useRef<HTMLDivElement>(null)
78
+ const size = useSize(ele)
79
+ const pausedRef = useRef(false)
80
+ const pausedProps = useRef(paused)
81
+ pausedProps.current = paused
82
+
83
+ useImperativeHandle(ref, () => ele.current!, [])
84
+ useImperativeHandle(scrollbar, () => bar.current!, [])
85
+
86
+ useEffect(() => {
87
+ if (playOnMouseEnter) pausedRef.current = false
88
+ }, [playOnMouseEnter])
89
+
90
+ useEffect(() => {
91
+ if (!size || count === 0) return
92
+ const { height } = size
93
+ const range = getArray(count, index => (itemHeight + gap) * (index + 1) - (index === count - 1 ? gap : 0))
94
+ const scrollHeight = range[range.length - 1]
95
+ if (height >= scrollHeight) return
96
+ function scroll(target: number) {
97
+ clearTimeout(timeout.current)
98
+ timeout.current = setTimeout(() => {
99
+ if (pausedRef.current || pausedProps.current) return scroll(target)
100
+ bar.current?.scrollTo(0, target, animation)
101
+ }, duration)
102
+ }
103
+ scroll(range[0])
104
+ function listener(status: ScrollStatus) {
105
+ const { y } = status.offset
106
+ const scrollToBottom = Math.abs(y + height - scrollHeight) / itemHeight <= 0.05
107
+ const target = scrollToBottom ? 0 : range.find(item => item > y)!
108
+ scroll(target)
109
+ }
110
+ bar.current?.addListener(listener)
111
+ return () => {
112
+ clearTimeout(timeout.current)
113
+ bar.current?.removeListener(listener)
114
+ bar.current?.scrollTo(0, 0)
115
+ }
116
+ }, [size, count, itemHeight, gap, duration, animation])
117
+
118
+ function onContainerMouseEnter(e: ReactMouseEvent<HTMLDivElement, MouseEvent>) {
119
+ if (playOnMouseEnter) return
120
+ pausedRef.current = true
121
+ onMouseEnter?.(e)
122
+ }
123
+
124
+ function onContainerMouseLeave(e: ReactMouseEvent<HTMLDivElement, MouseEvent>) {
125
+ if (playOnMouseEnter) return
126
+ pausedRef.current = false
127
+ onMouseLeave?.(e)
128
+ }
129
+
130
+ return (
131
+ <Scroll ref={ele} scrollbar={bar} onMouseEnter={onContainerMouseEnter} onMouseLeave={onContainerMouseLeave} {...rest}>
132
+ <div
133
+ className={clsx(
134
+ css`
135
+ display: flex;
136
+ flex-direction: column;
137
+ gap: var(--gap, 0);
138
+ & > * {
139
+ flex: none;
140
+ }
141
+ `,
142
+ containerClassName,
143
+ )}
144
+ style={transformCSSVariable({ gap: px(gap) }, containerStyle)}
145
+ >
146
+ {children}
147
+ </div>
148
+ </Scroll>
149
+ )
150
+ })
@@ -1,50 +1,50 @@
1
- "use client"
2
-
3
- import { css } from "@emotion/css"
4
- import { clsx } from "deepsea-tools"
5
- import { TextareaHTMLAttributes, forwardRef, useImperativeHandle, useLayoutEffect, useRef, useState } from "react"
6
-
7
- import { px, transformCSSVariable } from "@/utils"
8
-
9
- /**
10
- * 自适应高度的文本域
11
- */
12
- export const AutoSizeTextArea = forwardRef<HTMLTextAreaElement, TextareaHTMLAttributes<HTMLTextAreaElement>>((props, ref) => {
13
- const { className, style, ...rest } = props
14
- const [height, setHeight] = useState<string | undefined>(undefined)
15
- const ele = useRef<HTMLTextAreaElement>(null)
16
-
17
- useImperativeHandle(ref, () => ele.current!, [])
18
-
19
- useLayoutEffect(() => {
20
- const textarea = ele.current!
21
- function resizeTextarea() {
22
- setHeight("auto")
23
- setHeight(px(textarea.scrollHeight + textarea.offsetHeight - textarea.clientHeight))
24
- }
25
- resizeTextarea()
26
- textarea.addEventListener("input", resizeTextarea)
27
- textarea.addEventListener("change", resizeTextarea)
28
-
29
- return () => {
30
- textarea.removeEventListener("input", resizeTextarea)
31
- textarea.removeEventListener("change", resizeTextarea)
32
- }
33
- }, [])
34
-
35
- return (
36
- <textarea
37
- ref={ele}
38
- className={clsx(
39
- css`
40
- height: var(--height);
41
- resize: none;
42
- overflow-y: hidden;
43
- `,
44
- className,
45
- )}
46
- style={transformCSSVariable({ height }, style)}
47
- {...rest}
48
- />
49
- )
50
- })
1
+ "use client"
2
+
3
+ import { css } from "@emotion/css"
4
+ import { clsx } from "deepsea-tools"
5
+ import { TextareaHTMLAttributes, forwardRef, useImperativeHandle, useLayoutEffect, useRef, useState } from "react"
6
+
7
+ import { px, transformCSSVariable } from "@/utils"
8
+
9
+ /**
10
+ * 自适应高度的文本域
11
+ */
12
+ export const AutoSizeTextArea = forwardRef<HTMLTextAreaElement, TextareaHTMLAttributes<HTMLTextAreaElement>>((props, ref) => {
13
+ const { className, style, ...rest } = props
14
+ const [height, setHeight] = useState<string | undefined>(undefined)
15
+ const ele = useRef<HTMLTextAreaElement>(null)
16
+
17
+ useImperativeHandle(ref, () => ele.current!, [])
18
+
19
+ useLayoutEffect(() => {
20
+ const textarea = ele.current!
21
+ function resizeTextarea() {
22
+ setHeight("auto")
23
+ setHeight(px(textarea.scrollHeight + textarea.offsetHeight - textarea.clientHeight))
24
+ }
25
+ resizeTextarea()
26
+ textarea.addEventListener("input", resizeTextarea)
27
+ textarea.addEventListener("change", resizeTextarea)
28
+
29
+ return () => {
30
+ textarea.removeEventListener("input", resizeTextarea)
31
+ textarea.removeEventListener("change", resizeTextarea)
32
+ }
33
+ }, [])
34
+
35
+ return (
36
+ <textarea
37
+ ref={ele}
38
+ className={clsx(
39
+ css`
40
+ height: var(--height);
41
+ resize: none;
42
+ overflow-y: hidden;
43
+ `,
44
+ className,
45
+ )}
46
+ style={transformCSSVariable({ height }, style)}
47
+ {...rest}
48
+ />
49
+ )
50
+ })
@@ -1,81 +1,81 @@
1
- "use client"
2
-
3
- import { FC, Fragment, HTMLAttributes } from "react"
4
-
5
- export interface CircleTextProps extends Omit<HTMLAttributes<HTMLSpanElement>, "children"> {
6
- /** 每一个方块的宽度 */
7
- width: number
8
- /** 每一个方块的高度 */
9
- height: number
10
- /** 圆弧的半径,不包含方块的高度 */
11
- radius: number
12
- /** 开始旋转的弧度,逆时针增加,0 为 x 轴方向 */
13
- startAngel?: number
14
- /** 每一个方块之间间隔的弧度 */
15
- gapAngel?: number
16
- /** 文字对齐的方式,默认居中 */
17
- align?: "left" | "center" | "right"
18
- /** 文字朝向圆心还是外侧,默认外侧 */
19
- direction?: "inner" | "outer"
20
- /** 是否反转文字顺序 */
21
- reverse?: boolean
22
- /** 分割文字的方法 */
23
- separator?: string | RegExp | ((text: string) => string[])
24
- /** 显示的文字 */
25
- children: string
26
- }
27
-
28
- /** 环形文字 */
29
- export const CircleText: FC<CircleTextProps> = props => {
30
- const {
31
- width,
32
- height,
33
- radius,
34
- startAngel = 0,
35
- gapAngel = 0,
36
- align = "center",
37
- style,
38
- direction = "outer",
39
- reverse = false,
40
- separator,
41
- children,
42
- ...rest
43
- } = props
44
- const unitAngle = Math.atan(width / 2 / radius) * 2
45
- const totalAngle = (unitAngle + gapAngel) * children.length - gapAngel
46
- const offsetAngle = align === "left" ? 0 : align === "right" ? totalAngle : totalAngle / 2
47
-
48
- function getTransform(idx: number) {
49
- const angle = startAngel - idx * (unitAngle + gapAngel) + offsetAngle - unitAngle / 2
50
- const x = (radius + height / 2) * Math.cos(angle) - width / 2
51
- const y = (radius + height / 2) * Math.sin(angle) * -1 - height / 2
52
- const z = Math.PI / 2 - angle + (direction === "inner" ? Math.PI : 0)
53
- return `translateX(${x}px) translateY(${y}px) rotateZ(${(z / Math.PI) * 180}deg)`
54
- }
55
-
56
- const words = typeof separator === "function" ? separator(children) : children.split(separator ?? "")
57
-
58
- if (reverse) words.reverse()
59
-
60
- return (
61
- <Fragment>
62
- {words.map((w, idx) => (
63
- <span
64
- key={idx}
65
- style={{
66
- position: "absolute",
67
- ...style,
68
- transform: getTransform(idx),
69
- textAlign: "center",
70
- width,
71
- lineHeight: `${height}px`,
72
- height: height,
73
- }}
74
- {...rest}
75
- >
76
- {w}
77
- </span>
78
- ))}
79
- </Fragment>
80
- )
81
- }
1
+ "use client"
2
+
3
+ import { FC, Fragment, HTMLAttributes } from "react"
4
+
5
+ export interface CircleTextProps extends Omit<HTMLAttributes<HTMLSpanElement>, "children"> {
6
+ /** 每一个方块的宽度 */
7
+ width: number
8
+ /** 每一个方块的高度 */
9
+ height: number
10
+ /** 圆弧的半径,不包含方块的高度 */
11
+ radius: number
12
+ /** 开始旋转的弧度,逆时针增加,0 为 x 轴方向 */
13
+ startAngel?: number
14
+ /** 每一个方块之间间隔的弧度 */
15
+ gapAngel?: number
16
+ /** 文字对齐的方式,默认居中 */
17
+ align?: "left" | "center" | "right"
18
+ /** 文字朝向圆心还是外侧,默认外侧 */
19
+ direction?: "inner" | "outer"
20
+ /** 是否反转文字顺序 */
21
+ reverse?: boolean
22
+ /** 分割文字的方法 */
23
+ separator?: string | RegExp | ((text: string) => string[])
24
+ /** 显示的文字 */
25
+ children: string
26
+ }
27
+
28
+ /** 环形文字 */
29
+ export const CircleText: FC<CircleTextProps> = props => {
30
+ const {
31
+ width,
32
+ height,
33
+ radius,
34
+ startAngel = 0,
35
+ gapAngel = 0,
36
+ align = "center",
37
+ style,
38
+ direction = "outer",
39
+ reverse = false,
40
+ separator,
41
+ children,
42
+ ...rest
43
+ } = props
44
+ const unitAngle = Math.atan(width / 2 / radius) * 2
45
+ const totalAngle = (unitAngle + gapAngel) * children.length - gapAngel
46
+ const offsetAngle = align === "left" ? 0 : align === "right" ? totalAngle : totalAngle / 2
47
+
48
+ function getTransform(idx: number) {
49
+ const angle = startAngel - idx * (unitAngle + gapAngel) + offsetAngle - unitAngle / 2
50
+ const x = (radius + height / 2) * Math.cos(angle) - width / 2
51
+ const y = (radius + height / 2) * Math.sin(angle) * -1 - height / 2
52
+ const z = Math.PI / 2 - angle + (direction === "inner" ? Math.PI : 0)
53
+ return `translateX(${x}px) translateY(${y}px) rotateZ(${(z / Math.PI) * 180}deg)`
54
+ }
55
+
56
+ const words = typeof separator === "function" ? separator(children) : children.split(separator ?? "")
57
+
58
+ if (reverse) words.reverse()
59
+
60
+ return (
61
+ <Fragment>
62
+ {words.map((w, idx) => (
63
+ <span
64
+ key={idx}
65
+ style={{
66
+ position: "absolute",
67
+ ...style,
68
+ transform: getTransform(idx),
69
+ textAlign: "center",
70
+ width,
71
+ lineHeight: `${height}px`,
72
+ height: height,
73
+ }}
74
+ {...rest}
75
+ >
76
+ {w}
77
+ </span>
78
+ ))}
79
+ </Fragment>
80
+ )
81
+ }
@@ -1,31 +1,31 @@
1
- "use client"
2
-
3
- import ClipboardJS, { Event } from "clipboard"
4
- import { ComponentPropsWithoutRef, forwardRef, useEffect, useImperativeHandle, useRef } from "react"
5
- import { useLatest } from "soda-hooks"
6
-
7
- export { Event } from "clipboard"
8
-
9
- export type CopyButtonProps = ComponentPropsWithoutRef<"button"> & {
10
- text?: string
11
- onCopySuccess?: (e: Event) => void
12
- onCopyError?: (e: Event) => void
13
- }
14
-
15
- export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>((props, ref) => {
16
- const { text, onCopySuccess: _onCoptSuccess, onCopyError: _onCopyError, ...rest } = props
17
- const ele = useRef<HTMLButtonElement>(null)
18
- const onCopySuccess = useLatest(_onCoptSuccess)
19
- const onCopyError = useLatest(_onCopyError)
20
-
21
- useImperativeHandle(ref, () => ele.current!, [ele.current])
22
-
23
- useEffect(() => {
24
- const clipboard = new ClipboardJS(ele.current!)
25
- clipboard.on("success", event => onCopySuccess.current?.(event))
26
- clipboard.on("error", event => onCopyError.current?.(event))
27
- return () => clipboard.destroy()
28
- }, [])
29
-
30
- return <button ref={ele} {...rest} data-clipboard-text={text} />
31
- })
1
+ "use client"
2
+
3
+ import ClipboardJS, { Event } from "clipboard"
4
+ import { ComponentPropsWithoutRef, forwardRef, useEffect, useImperativeHandle, useRef } from "react"
5
+ import { useLatest } from "soda-hooks"
6
+
7
+ export { Event } from "clipboard"
8
+
9
+ export type CopyButtonProps = ComponentPropsWithoutRef<"button"> & {
10
+ text?: string
11
+ onCopySuccess?: (e: Event) => void
12
+ onCopyError?: (e: Event) => void
13
+ }
14
+
15
+ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>((props, ref) => {
16
+ const { text, onCopySuccess: _onCoptSuccess, onCopyError: _onCopyError, ...rest } = props
17
+ const ele = useRef<HTMLButtonElement>(null)
18
+ const onCopySuccess = useLatest(_onCoptSuccess)
19
+ const onCopyError = useLatest(_onCopyError)
20
+
21
+ useImperativeHandle(ref, () => ele.current!, [ele.current])
22
+
23
+ useEffect(() => {
24
+ const clipboard = new ClipboardJS(ele.current!)
25
+ clipboard.on("success", event => onCopySuccess.current?.(event))
26
+ clipboard.on("error", event => onCopyError.current?.(event))
27
+ return () => clipboard.destroy()
28
+ }, [])
29
+
30
+ return <button ref={ele} {...rest} data-clipboard-text={text} />
31
+ })