1
1
import * as React from 'react'
2
2
3
- type CopyState =
4
- | {
5
- state : 'initial'
3
+ function useCopyLegacy ( content : string ) {
4
+ type CopyState =
5
+ | {
6
+ state : 'initial'
7
+ }
8
+ | {
9
+ state : 'error'
10
+ error : unknown
11
+ }
12
+ | { state : 'success' }
13
+ | { state : 'pending' }
14
+
15
+ // This would be simpler with useActionState but we need to support React 18 here.
16
+ // React 18 also doesn't have async transitions.
17
+ const [ copyState , dispatch ] = React . useReducer (
18
+ (
19
+ state : CopyState ,
20
+ action :
21
+ | { type : 'reset' | 'copied' | 'copying' }
22
+ | { type : 'error' ; error : unknown }
23
+ ) : CopyState => {
24
+ if ( action . type === 'reset' ) {
25
+ return { state : 'initial' }
26
+ }
27
+ if ( action . type === 'copied' ) {
28
+ return { state : 'success' }
29
+ }
30
+ if ( action . type === 'copying' ) {
31
+ return { state : 'pending' }
32
+ }
33
+ if ( action . type === 'error' ) {
34
+ return { state : 'error' , error : action . error }
35
+ }
36
+ return state
37
+ } ,
38
+ {
39
+ state : 'initial' ,
40
+ }
41
+ )
42
+ function copy ( ) {
43
+ if ( isPending ) {
44
+ return
6
45
}
7
- | {
8
- state : 'error'
9
- error : unknown
46
+
47
+ if ( ! navigator . clipboard ) {
48
+ dispatch ( {
49
+ type : 'error' ,
50
+ error : new Error ( 'Copy to clipboard is not supported in this browser' ) ,
51
+ } )
52
+ } else {
53
+ dispatch ( { type : 'copying' } )
54
+ navigator . clipboard . writeText ( content ) . then (
55
+ ( ) => {
56
+ dispatch ( { type : 'copied' } )
57
+ } ,
58
+ ( error ) => {
59
+ dispatch ( { type : 'error' , error } )
60
+ }
61
+ )
10
62
}
11
- | { state : 'success' }
63
+ }
64
+ const reset = React . useCallback ( ( ) => {
65
+ dispatch ( { type : 'reset' } )
66
+ } , [ ] )
67
+
68
+ const isPending = copyState . state === 'pending'
69
+
70
+ return [ copyState , copy , reset , isPending ] as const
71
+ }
72
+
73
+ function useCopyModern ( content : string ) {
74
+ type CopyState =
75
+ | {
76
+ state : 'initial'
77
+ }
78
+ | {
79
+ state : 'error'
80
+ error : unknown
81
+ }
82
+ | { state : 'success' }
12
83
13
- export function CopyButton ( {
14
- actionLabel,
15
- successLabel,
16
- content,
17
- ...props
18
- } : React . HTMLProps < HTMLButtonElement > & {
19
- actionLabel : string
20
- successLabel : string
21
- content : string
22
- } ) {
23
84
const [ copyState , dispatch , isPending ] = React . useActionState (
24
85
(
25
86
state : CopyState ,
@@ -53,6 +114,38 @@ export function CopyButton({
53
114
}
54
115
)
55
116
117
+ function copy ( ) {
118
+ React . startTransition ( ( ) => {
119
+ dispatch ( 'copy' )
120
+ } )
121
+ }
122
+
123
+ const reset = React . useCallback ( ( ) => {
124
+ dispatch ( 'reset' )
125
+ } , [
126
+ // TODO: `dispatch` from `useActionState` is not reactive.
127
+ // Remove from dependencies once https://github.com/facebook/react/pull/29665 is released.
128
+ dispatch ,
129
+ ] )
130
+
131
+ return [ copyState , copy , reset , isPending ] as const
132
+ }
133
+
134
+ const useCopy =
135
+ typeof React . useActionState === 'function' ? useCopyModern : useCopyLegacy
136
+
137
+ export function CopyButton ( {
138
+ actionLabel,
139
+ successLabel,
140
+ content,
141
+ ...props
142
+ } : React . HTMLProps < HTMLButtonElement > & {
143
+ actionLabel : string
144
+ successLabel : string
145
+ content : string
146
+ } ) {
147
+ const [ copyState , copy , reset , isPending ] = useCopy ( content )
148
+
56
149
const error = copyState . state === 'error' ? copyState . error : null
57
150
React . useEffect ( ( ) => {
58
151
if ( error !== null ) {
@@ -63,20 +156,14 @@ export function CopyButton({
63
156
React . useEffect ( ( ) => {
64
157
if ( copyState . state === 'success' ) {
65
158
const timeoutId = setTimeout ( ( ) => {
66
- dispatch ( ' reset' )
159
+ reset ( )
67
160
} , 2000 )
68
161
69
162
return ( ) => {
70
163
clearTimeout ( timeoutId )
71
164
}
72
165
}
73
- } , [
74
- isPending ,
75
- copyState . state ,
76
- // TODO: `dispatch` from `useActionState` is not reactive.
77
- // Remove from dependencies once https://github.com/facebook/react/pull/29665 is released.
78
- dispatch ,
79
- ] )
166
+ } , [ isPending , copyState . state , reset ] )
80
167
const isDisabled = isPending
81
168
const label = copyState . state === 'success' ? successLabel : actionLabel
82
169
const title = label
@@ -94,9 +181,7 @@ export function CopyButton({
94
181
className = { `nextjs-data-runtime-error-copy-stack nextjs-data-runtime-error-copy-stack--${ copyState . state } ` }
95
182
onClick = { ( ) => {
96
183
if ( ! isDisabled ) {
97
- React . startTransition ( ( ) => {
98
- dispatch ( 'copy' )
99
- } )
184
+ copy ( )
100
185
}
101
186
} }
102
187
>
0 commit comments