import {Component, createRef} from 'react'; import {findDOMNode} from 'react-dom'; import styled from '@emotion/styled'; import Button from 'sentry/components/button'; import Clipboard from 'sentry/components/clipboard'; import {IconCopy} from 'sentry/icons'; import {inputStyles} from 'sentry/styles/input'; import {selectText} from 'sentry/utils/selectText'; const Wrapper = styled('div')` display: flex; `; export const StyledInput = styled('input')<{rtl?: boolean}>` ${inputStyles}; background-color: ${p => p.theme.backgroundSecondary}; border-right-width: 0; border-top-right-radius: 0; border-bottom-right-radius: 0; direction: ${p => (p.rtl ? 'rtl' : 'ltr')}; &:hover, &:focus { background-color: ${p => p.theme.backgroundSecondary}; border-right-width: 0; } `; const OverflowContainer = styled('div')` flex-grow: 1; border: none; `; export const StyledCopyButton = styled(Button)` flex-shrink: 1; border-radius: 0 0.25em 0.25em 0; box-shadow: none; `; type Props = { /** * Text to copy */ children: string; className?: string; disabled?: boolean; onCopy?: (value: string, event: React.MouseEvent) => void; /** * Always show the ending of a long overflowing text in input */ rtl?: boolean; style?: React.CSSProperties; }; class TextCopyInput extends Component { textRef = createRef(); // Select text when copy button is clicked handleCopyClick = (e: React.MouseEvent) => { if (!this.textRef.current) { return; } const {onCopy, children} = this.props; this.handleSelectText(); onCopy?.(children, e); e.stopPropagation(); }; handleSelectText = () => { const {rtl} = this.props; if (!this.textRef.current) { return; } // We use findDOMNode here because `this.textRef` is not a dom node, // it's a ref to AutoSelectText const node = findDOMNode(this.textRef.current); // eslint-disable-line react/no-find-dom-node if (!node || !(node instanceof HTMLElement)) { return; } if (rtl && node instanceof HTMLInputElement) { // we don't want to select the first character - \u202A, nor the last - \u202C node.setSelectionRange(1, node.value.length - 1); } else { selectText(node); } }; render() { const {className, disabled, style, children, rtl} = this.props; /** * We are using direction: rtl; to always show the ending of a long overflowing text in input. * * This however means that the trailing characters with BiDi class O.N. ('Other Neutrals') goes to the other side. * Hello! becomes !Hello and vice versa. This is a problem for us when we want to show path in this component, because * /user/local/bin becomes user/local/bin/. Wrapping in unicode characters for left-to-righ embedding solves this, * however we need to be aware of them when selecting the text - we are solving that by offsetting the selectionRange. */ const inputValue = rtl ? '\u202A' + children + '\u202C' : children; return ( ); } } export default TextCopyInput;