feat: ajoute composant CopyButton
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import {useState} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import IconButton from '@mui/material/IconButton'
|
||||
import Tooltip from '@mui/material/Tooltip'
|
||||
import Snackbar from '@mui/material/Snackbar'
|
||||
import Alert from '@mui/material/Alert'
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
||||
import CheckIcon from '@mui/icons-material/Check'
|
||||
|
||||
export default function CopyButton({
|
||||
content,
|
||||
label = 'Copier',
|
||||
size = 'small',
|
||||
hasSnackbarVisible = true,
|
||||
onCopySuccess = null,
|
||||
onCopyError = null
|
||||
}) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
const [snackbar, setSnackbar] = useState({open: false, message: '', severity: 'success'})
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
// Modern clipboard API with fallback
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(content)
|
||||
} else {
|
||||
// Fallback for older browsers or non-HTTPS
|
||||
const textArea = document.createElement('textarea')
|
||||
textArea.value = content
|
||||
textArea.style.position = 'fixed'
|
||||
textArea.style.left = '-999999px'
|
||||
textArea.style.top = '-999999px'
|
||||
document.body.append(textArea)
|
||||
textArea.focus()
|
||||
textArea.select()
|
||||
|
||||
const result = document.execCommand('copy')
|
||||
textArea.remove()
|
||||
|
||||
if (!result) {
|
||||
throw new Error('Copy command failed')
|
||||
}
|
||||
}
|
||||
|
||||
// Success feedback
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
|
||||
if (hasSnackbarVisible) {
|
||||
setSnackbar({
|
||||
open: true,
|
||||
message: 'Contenu copié dans le presse-papier',
|
||||
severity: 'success'
|
||||
})
|
||||
}
|
||||
|
||||
if (onCopySuccess) {
|
||||
onCopySuccess()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to copy to clipboard:', error)
|
||||
|
||||
if (hasSnackbarVisible) {
|
||||
setSnackbar({
|
||||
open: true,
|
||||
message: 'Impossible de copier le contenu',
|
||||
severity: 'error'
|
||||
})
|
||||
}
|
||||
|
||||
if (onCopyError) {
|
||||
onCopyError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleCloseSnackbar = () => {
|
||||
setSnackbar(prev => ({...prev, open: false}))
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title={copied ? 'Copié !' : label}>
|
||||
<IconButton
|
||||
size={size}
|
||||
aria-label={label}
|
||||
sx={{
|
||||
color: copied ? 'success.main' : 'text.secondary',
|
||||
'&:hover': {
|
||||
color: copied ? 'success.dark' : 'primary.main',
|
||||
backgroundColor: copied ? 'success.light' : 'action.hover'
|
||||
},
|
||||
transition: 'all 0.2s ease-in-out'
|
||||
}}
|
||||
onClick={handleCopy}
|
||||
>
|
||||
{copied ? <CheckIcon /> : <ContentCopyIcon />}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
{hasSnackbarVisible && (
|
||||
<Snackbar
|
||||
open={snackbar.open}
|
||||
autoHideDuration={3000}
|
||||
anchorOrigin={{vertical: 'bottom', horizontal: 'center'}}
|
||||
onClose={handleCloseSnackbar}
|
||||
>
|
||||
<Alert
|
||||
variant='filled'
|
||||
severity={snackbar.severity}
|
||||
sx={{width: '100%'}}
|
||||
onClose={handleCloseSnackbar}
|
||||
>
|
||||
{snackbar.message}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
CopyButton.propTypes = {
|
||||
content: PropTypes.string.isRequired,
|
||||
label: PropTypes.string,
|
||||
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
||||
hasSnackbarVisible: PropTypes.bool,
|
||||
onCopySuccess: PropTypes.func,
|
||||
onCopyError: PropTypes.func
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user