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:
parent
a4fbfae4ba
commit
542bf0bf66
@ -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>
|
||||
<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' }}>
|
||||
<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'};
|
||||
`
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user