Files
2025-12-01 17:21:38 +08:00

105 lines
3.4 KiB
TypeScript

'use client'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { RiClipboardFill, RiClipboardLine } from '@remixicon/react'
import { debounce } from 'lodash-es'
import copy from 'copy-to-clipboard'
import type { InputProps } from '../input'
import Tooltip from '../tooltip'
import ActionButton from '../action-button'
import cn from '@/utils/classnames'
export type InputWithCopyProps = {
showCopyButton?: boolean
copyValue?: string // Value to copy, defaults to input value
onCopy?: (value: string) => void // Callback when copy is triggered
} & Omit<InputProps, 'showClearIcon' | 'onCopy'> // Remove conflicting props
const prefixEmbedded = 'appOverview.overview.appInfo.embedded'
const InputWithCopy = React.forwardRef<HTMLInputElement, InputWithCopyProps>((
{
showCopyButton = true,
copyValue,
onCopy,
value,
wrapperClassName,
...inputProps
},
ref,
) => {
const { t } = useTranslation()
const [isCopied, setIsCopied] = useState<boolean>(false)
// Determine what value to copy
const valueToString = typeof value === 'string' ? value : String(value || '')
const finalCopyValue = copyValue || valueToString
const onClickCopy = debounce(() => {
copy(finalCopyValue)
setIsCopied(true)
onCopy?.(finalCopyValue)
}, 100)
const onMouseLeave = debounce(() => {
setIsCopied(false)
}, 100)
useEffect(() => {
if (isCopied) {
const timeout = setTimeout(() => {
setIsCopied(false)
}, 2000)
return () => {
clearTimeout(timeout)
}
}
}, [isCopied])
return (
<div className={cn('relative w-full', wrapperClassName)}>
<input
ref={ref}
className={cn(
'w-full appearance-none border border-transparent bg-components-input-bg-normal py-[7px] text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs',
'radius-md system-sm-regular px-3',
showCopyButton && 'pr-8',
inputProps.disabled && 'cursor-not-allowed border-transparent bg-components-input-bg-disabled text-components-input-text-filled-disabled hover:border-transparent hover:bg-components-input-bg-disabled',
inputProps.className,
)}
value={value}
{...(({ size: _size, ...rest }) => rest)(inputProps)}
/>
{showCopyButton && (
<div
className="absolute right-2 top-1/2 -translate-y-1/2"
onMouseLeave={onMouseLeave}
>
<Tooltip
popupContent={
(isCopied
? t(`${prefixEmbedded}.copied`)
: t(`${prefixEmbedded}.copy`)) || ''
}
>
<ActionButton
size="xs"
onClick={onClickCopy}
className="hover:bg-components-button-ghost-bg-hover"
>
{isCopied ? (
<RiClipboardFill className='h-3.5 w-3.5 text-text-tertiary' />
) : (
<RiClipboardLine className='h-3.5 w-3.5 text-text-tertiary' />
)}
</ActionButton>
</Tooltip>
</div>
)}
</div>
)
})
InputWithCopy.displayName = 'InputWithCopy'
export default InputWithCopy