diff --git a/ui/src/copyable_text.tsx b/ui/src/copyable_text.tsx index 1d293df180..805deee753 100644 --- a/ui/src/copyable_text.tsx +++ b/ui/src/copyable_text.tsx @@ -6,17 +6,21 @@ import { clas, cssVar, pc } from './theme' const css = stylesheet({ - btnMultiline: { + animate: { opacity: 0, transition: 'opacity .5s' }, + visible: { + opacity: 1, + }, btn: { minWidth: 'initial', - position: 'absolute', + position: 'fixed', width: 24, height: 24, - right: 0, - transform: 'translate(-4px, 4px)', + // TODO: Compute dynamically based on anchor element. + // right: 0, + // transform: 'translate(-4px, 4px)', zIndex: 1, }, copiedBtn: { @@ -54,19 +58,17 @@ export interface CopyableText { height?: S } -export const XCopyableText = ({ model }: { model: CopyableText }) => { +export const ClipboardCopyButton = ({ value, anchorElementRef, showOnHover }: { value: S, anchorElementRef: any, showOnHover: B }) => { const - { name, multiline, label, value, height } = model, - heightStyle = multiline && height === '1' ? fullHeightStyle : undefined, - ref = React.useRef(null), timeoutRef = React.useRef(), [copied, setCopied] = React.useState(false), + [visible, setVisible] = React.useState(!showOnHover), onClick = async () => { - const el = ref.current + const el = anchorElementRef.current if (!el) return try { if (document.queryCommandSupported('copy')) { - el.select() + el.select() // TODO: Test, replace with componentRef document.execCommand('copy') window.getSelection()?.removeAllRanges() } @@ -78,36 +80,59 @@ export const XCopyableText = ({ model }: { model: CopyableText }) => { timeoutRef.current = window.setTimeout(() => setCopied(false), 2000) } + React.useEffect(() => { + const el = anchorElementRef.current + if (el) { + console.log(anchorElementRef.current.getBoundingClientRect()) + // textFieldMultiline: multiline ? { '&:hover button': { opacity: 1 } } : undefined // TODO: Re-implement with pure CSS: '&hover childId' + el.addEventListener('mouseenter', () => { setVisible(true) }) + el.addEventListener('mouseleave', () => { setVisible(false) }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + React.useEffect(() => () => window.clearTimeout(timeoutRef.current), []) + return () +} + +export const XCopyableText = ({ model }: { model: CopyableText }) => { + const + { name, multiline, label, value, height } = model, + heightStyle = multiline && height === '1' ? fullHeightStyle : undefined, + ref = React.useRef(null), + domRef = React.useRef(null) + return ( - -
- {label} - -
- } - styles={{ - root: { - ...heightStyle, - textFieldRoot: { position: 'relative', width: pc(100) }, - textFieldMultiline: multiline ? { '&:hover button': { opacity: 1 } } : undefined - }, - wrapper: heightStyle, - fieldGroup: heightStyle || { minHeight: height }, - field: { ...heightStyle, height, resize: multiline ? 'vertical' : 'none', }, - }} - readOnly - /> + <> + +
+ {label} +
+ } + styles={{ + root: { + ...heightStyle, + textFieldRoot: { position: 'relative', width: pc(100) }, + }, + wrapper: heightStyle, + fieldGroup: heightStyle || { minHeight: height }, + field: { ...heightStyle, height, resize: multiline ? 'vertical' : 'none', }, + }} + readOnly + /> + + ) } \ No newline at end of file diff --git a/ui/src/markdown.tsx b/ui/src/markdown.tsx index e39bae85a0..54b5777205 100644 --- a/ui/src/markdown.tsx +++ b/ui/src/markdown.tsx @@ -20,6 +20,8 @@ import { stylesheet } from 'typestyle' import { cards, grid, substitute } from './layout' import { border, clas, cssVar, padding, pc } from './theme' import { bond } from './ui' +import { ClipboardCopyButton } from './copyable_text' +import ReactDOM from 'react-dom' const css = stylesheet({