feat: user select (#3410)

* feat: make data user-selectable

* fix: consider the whole node for focus

* fix: back out lineheight typing

* fix: straggling occurences

* chore: comment on root user-select
This commit is contained in:
Zach Pomerantz 2022-03-03 11:09:12 -08:00 committed by GitHub
parent a4fbfae4ba
commit 542bf0bf66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 41 additions and 23 deletions

@ -118,7 +118,7 @@ export default function ErrorDialog({ header, error, action, onClick }: ErrorDia
<Rule />
<ErrorColumn>
<Column gap={0.5} ref={setDetails} css={scrollbar}>
<ThemedText.Code>
<ThemedText.Code userSelect>
{error.name}
{error.message ? `: ${error.message}` : ''}
</ThemedText.Code>

@ -22,7 +22,7 @@ import Row from '../Row'
import TokenImg from '../TokenImg'
import TokenInput from './TokenInput'
export const LoadingRow = styled(Row)<{ $loading: boolean }>`
export const USDC = styled(Row)`
${loadingOpacityCss};
`
@ -120,12 +120,12 @@ export default function Input({ disabled, focused }: InputProps) {
onChangeCurrency={updateSwapInputCurrency}
loading={isLoading}
>
<ThemedText.Body2 color="secondary">
<ThemedText.Body2 color="secondary" userSelect>
<Row>
<LoadingRow $loading={isLoading}>{inputUSDC ? `$${inputUSDC.toFixed(2)}` : '-'}</LoadingRow>
<USDC $loading={isLoading}>{inputUSDC ? `$${inputUSDC.toFixed(2)}` : '-'}</USDC>
{balance && (
<Balance color={balanceColor} focused={focused}>
Balance: <span style={{ userSelect: 'text' }}>{formatCurrencyAmount(balance, 4, i18n.locale)}</span>
Balance: <span>{formatCurrencyAmount(balance, 4, i18n.locale)}</span>
</Balance>
)}
</Row>

@ -16,7 +16,7 @@ import { getPriceImpactWarning } from 'utils/prices'
import Column from '../Column'
import Row from '../Row'
import { Balance, InputProps, LoadingRow, useFormattedFieldAmount } from './Input'
import { Balance, InputProps, USDC, useFormattedFieldAmount } from './Input'
import TokenInput from './TokenInput'
export const colorAtom = atom<string | undefined>(undefined)
@ -100,14 +100,14 @@ export default function Output({ disabled, focused, children }: PropsWithChildre
onChangeCurrency={updateSwapOutputCurrency}
loading={isLoading}
>
<ThemedText.Body2 color="secondary">
<ThemedText.Body2 color="secondary" userSelect>
<Row>
<LoadingRow gap={0.5} $loading={isLoading}>
<USDC gap={0.5} $loading={isLoading}>
{outputUSDC ? `$${outputUSDC.toFixed(2)}` : '-'} {priceImpact}
</LoadingRow>
</USDC>
{balance && (
<Balance focused={focused}>
Balance: <span style={{ userSelect: 'text' }}>{formatCurrencyAmount(balance, 4, i18n.locale)}</span>
Balance: <span>{formatCurrencyAmount(balance, 4, i18n.locale)}</span>
</Balance>
)}
</Row>

@ -26,7 +26,7 @@ interface DetailProps {
function Detail({ label, value, color }: DetailProps) {
return (
<ThemedText.Caption>
<ThemedText.Caption userSelect>
<Row gap={2}>
<span>{label}</span>
<Value color={color}>{value}</Value>

@ -38,13 +38,13 @@ function TokenValue({ input, usdc, change }: TokenValueProps) {
<Column justify="flex-start">
<Row gap={0.375} justify="flex-start">
<TokenImg token={input.currency} />
<ThemedText.Body2>
<ThemedText.Body2 userSelect>
{formatCurrencyAmount(input, 6, i18n.locale)} {input.currency.symbol}
</ThemedText.Body2>
</Row>
{usdc && usdcAmount && (
<Row justify="flex-start">
<ThemedText.Caption color="secondary">
<ThemedText.Caption color="secondary" userSelect>
${formatCurrencyAmount(usdcAmount, 2, i18n.locale)}
{change && <Percent gain={change > 0}> {percent}</Percent>}
</ThemedText.Caption>

@ -135,10 +135,12 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
<Body flex align="stretch" gap={0.75} padded open={open}>
<SummaryColumn gap={0.75} flex justify="center">
<Summary input={inputAmount} output={outputAmount} usdc={true} />
<ThemedText.Caption>
{formatLocaleNumber({ number: 1, sigFigs: 1, locale: i18n.locale })} {inputCurrency.symbol} ={' '}
{formatPrice(executionPrice, 6, i18n.locale)} {outputCurrency.symbol}
</ThemedText.Caption>
<Row>
<ThemedText.Caption userSelect>
{formatLocaleNumber({ number: 1, sigFigs: 1, locale: i18n.locale })} {inputCurrency.symbol} ={' '}
{formatPrice(executionPrice, 6, i18n.locale)} {outputCurrency.symbol}
</ThemedText.Caption>
</Row>
</SummaryColumn>
<Rule />
<Row>

@ -81,10 +81,12 @@ export function Trade({ trade }: { trade: InterfaceTrade<Currency, Currency, Tra
: null
return (
<Row gap={0.25} style={{ userSelect: 'text' }}>
{ratio}
{usdc && <ThemedText.Caption color="secondary">{usdc}</ThemedText.Caption>}
</Row>
<ThemedText.Caption userSelect>
<Row gap={0.25}>
{ratio}
{usdc && <ThemedText.Caption color="secondary">{usdc}</ThemedText.Caption>}
</Row>
</ThemedText.Caption>
)
}, [executionPrice, fiatValueInput, fiatValueOutput, flip, inputAmount, outputAmount])

@ -1,6 +1,14 @@
import { useCallback, useEffect, useState } from 'react'
export default function useHasFocus(node: Node | null | undefined): boolean {
useEffect(() => {
if (node instanceof HTMLElement) {
// tabIndex is required to receive blur events from non-button elements.
node.tabIndex = node.tabIndex || -1
// Without explicitly omitting outline, Safari will now outline this node when focused.
node.style.outline = node.style.outline || 'none'
}
}, [node])
const [hasFocus, setHasFocus] = useState(node?.contains(document?.activeElement) ?? false)
const onFocus = useCallback(() => setHasFocus(true), [])
const onBlur = useCallback((e) => setHasFocus(node?.contains(e.relatedTarget) ?? false), [node])

@ -3,12 +3,18 @@ import { Text, TextProps as TextPropsWithCss } from 'rebass'
import styled, { useTheme } from './styled'
import { Color } from './theme'
type TextProps = Omit<TextPropsWithCss, 'css' | 'color'> & { color?: Color }
type TextProps = Omit<TextPropsWithCss, 'css' | 'color' | 'userSelect'> & {
color?: Color
userSelect?: true
}
const TextWrapper = styled(Text)<{ color?: Color; lineHeight: string; noWrap?: true }>`
const TextWrapper = styled(Text)<{ color?: Color; lineHeight: string; noWrap?: true; userSelect?: true }>`
color: ${({ color = 'currentColor', theme }) => theme[color as Color]};
// Avoid the need for placeholders by setting min-height to line-height.
min-height: ${({ lineHeight }) => lineHeight};
// user-select is set to 'none' at the root element (Widget), but is desired for displayed data.
// user-select must be configured through styled-components for cross-browser compat (eg to auto-generate prefixed properties).
user-select: ${({ userSelect }) => userSelect && 'text'};
white-space: ${({ noWrap }) => noWrap && 'nowrap'};
`