Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions packages/components/src/content/Badge/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { GetProps } from '@onekeyhq/components/src/shared/tamagui';
import platformEnv from '@onekeyhq/shared/src/platformEnv';

import { XStack } from '../../primitives/Stack';
import { TABULAR_NUMS, getFontVariantStyle } from '../../utils/tabularNums';
import { getFontVariantStyle } from '../../utils/tabularNums';

import type { IXStackProps } from '../../primitives';

Expand Down Expand Up @@ -98,19 +98,21 @@ const BadgeTextStyled = styled(SizableText, {
} as const,
});

// Badge.Text is styled from raw tamagui text, so it bypasses the SizableText
// wrapper's app-wide tabular default. Re-apply it here (computed once) so badge
// digits (countdowns, rates, counts) stay equal-width on native. On web the
// web-fonts.css body rule already covers it, so getFontVariantStyle returns
// undefined and this is a passthrough.
const BADGE_TABULAR_STYLE = getFontVariantStyle(TABULAR_NUMS);

function BadgeText({ style, ...props }: GetProps<typeof BadgeTextStyled>) {
// Badge.Text is styled from raw tamagui text, which silently drops the RN
// `fontVariant` prop. Translate it into a real style so a badge holding dense
// numeric data (a ticking countdown, a rate) can opt in with
// `fontVariant={TABULAR_NUMS}`. Badges are proportional by default like all
// other text — most of them hold names or labels.
function BadgeText({
style,
fontVariant,
...props
}: GetProps<typeof BadgeTextStyled>) {
// Merge (not overwrite) so a caller-provided `style` still wins.
const mergedStyle = useMemo(
() => (BADGE_TABULAR_STYLE ? [BADGE_TABULAR_STYLE, style] : style),
[style],
);
const mergedStyle = useMemo(() => {
const variantStyle = getFontVariantStyle(fontVariant);
return variantStyle ? [variantStyle, style] : style;
}, [fontVariant, style]);
return <BadgeTextStyled {...props} style={mergedStyle} />;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,9 @@ export function NumberSizeableText({
{result.map((r, index) =>
typeof r === 'string' ? (
// These slices build fresh SizableTexts (not spreading `props`), so
// they'd otherwise fall back to the app-wide tabular default and
// ignore a caller override (e.g. PROPORTIONAL_NUMS on a hero
// balance) — forward `fontVariant` explicitly to every slice.
// a caller's `fontVariant={TABULAR_NUMS}` would not reach them —
// forward it explicitly, or a split number in a table column would
// have only its decimal part tabular.
<SizableText
key={index}
color={props.color}
Expand Down
6 changes: 0 additions & 6 deletions packages/components/src/hocs/Provider/web-fonts.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,3 @@
font-style: normal;
font-display: swap;
}

/* App-wide tabular (equal-width) figures — see utils/tabularNums.ts. Inherited
by every text node, covering text that bypasses the SizableText wrapper. */
body {
font-variant-numeric: tabular-nums;
}
11 changes: 5 additions & 6 deletions packages/components/src/primitives/SizeableText/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ import { SizableText as TamaguiSizableText } from '@tamagui/text';

import { type SizableTextProps } from '@onekeyhq/components/src/shared/tamagui';

import { TABULAR_NUMS, getFontVariantStyle } from '../../utils/tabularNums';
import { getFontVariantStyle } from '../../utils/tabularNums';

export const StyledSizableText = TamaguiSizableText;

export function SizableText({
size = '$bodyMd',
// App-wide default: tabular (equal-width) figures on ALL text, matching the
// reference exchanges whose brand fonts ship tabular digits by default.
// Only digit glyph widths change — letters are unaffected — so prose is
// safe. Opt out per text with `fontVariant={PROPORTIONAL_NUMS}`.
fontVariant = TABULAR_NUMS,
// No default: text is proportional (the font's natural figures), which is
// right for names, addresses, prose and hero numbers. Dense numeric data
// opts IN with `fontVariant={TABULAR_NUMS}` — see utils/tabularNums.ts.
fontVariant,
style,
...props
}: SizableTextProps) {
Expand Down
48 changes: 19 additions & 29 deletions packages/components/src/utils/tabularNums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,26 @@ import platformEnv from '@onekeyhq/shared/src/platformEnv';
import type { TextStyle } from 'react-native';

/**
* Tabular (equal-width) figures for numeric text.
* Tabular (equal-width) figures — OPT-IN, for dense numeric data only.
*
* `SizableText` applies this by DEFAULT app-wide, and web mirrors it with an
* injected `body { font-variant-numeric: tabular-nums }` rule, so most text
* needs nothing. Reach for the constant directly only for text that bypasses
* the `SizableText` wrapper — raw React Native `<Text>` / `StyleSheet.create`,
* or `Badge.Text` — via `getFontVariantStyle(TABULAR_NUMS)`.
* Apply as `fontVariant={TABULAR_NUMS}` (or a `fontVariant` StyleSheet entry on
* raw React Native `<Text>`) to numbers that are **small, sit in a column, and
* tick**: market/perps list columns, order books, trading tables, ticker strips.
* Every digit then shares one advance width, so columns stay aligned and a value
* doesn't reflow as it updates.
*
* Every digit shares one advance width, so number columns stay aligned and a
* value doesn't reflow as it ticks. Unlike a monospace family (`$monoRegular`),
* only digits are equalized — letters keep Roobert's natural proportional
* widths. Roobert ships the `tnum` OpenType feature, so this works on iOS,
* Android and web.
*
* Monospace is still the right choice for addresses / hashes / mnemonics /
* codes, where character (not just digit) alignment matters — do NOT replace
* those with tabular-nums.
* Do NOT apply it to anything a human reads as words. Text is proportional by
* default and that is almost always right:
* - names & identifiers (account "A1787", wallet "OneKey Pro2-1-6")
* - addresses / hashes (mixed hex — tabular equalizes only the digits, leaving
* the letters proportional, which reads worse than either)
* - prose and marketing copy
* - large hero numbers (a single big number has no column to align with, and
* Roobert's tabular `1` is nearly 2x wider and grows a foot serif)
* - amount input fields
*/
export const TABULAR_NUMS: ['tabular-nums'] = ['tabular-nums'];

/**
* Escape hatch from the app-wide tabular default (`SizableText` defaults
* `fontVariant` to TABULAR_NUMS): pass `fontVariant={PROPORTIONAL_NUMS}` to
* restore the font's natural proportional figures for a specific text.
*/
export const PROPORTIONAL_NUMS: ['proportional-nums'] = ['proportional-nums'];

type IFontVariantStyle = CSSProperties | TextStyle;

function buildFontVariantStyle(
Expand All @@ -51,13 +44,10 @@ function buildFontVariantStyle(
return Object.freeze(style);
}

// Web applies tabular figures globally via the `body` rule in web-fonts.css,
// so the DEFAULT variant needs no inline style — leaving it undefined avoids a
// redundant style attr on every text node and keeps `style` reference-stable.
// Native has no CSS inheritance, so it must carry the variant inline.
const TABULAR_NUMS_STYLE: IFontVariantStyle | undefined = platformEnv.isNative
? buildFontVariantStyle(TABULAR_NUMS)
: undefined;
// Hoisted so the common opt-in path allocates nothing and stays
// reference-stable. Both platforms carry it inline: there is no global rule to
// inherit from any more.
const TABULAR_NUMS_STYLE = buildFontVariantStyle(TABULAR_NUMS);

const fontVariantStyleCache = new WeakMap<
NonNullable<TextStyle['fontVariant']>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { useIntl } from 'react-intl';

import { Popover, SizableText, XStack, YStack } from '@onekeyhq/components';
import {
Popover,
SizableText,
TABULAR_NUMS,
XStack,
YStack,
} from '@onekeyhq/components';
import type { ColorTokens } from '@onekeyhq/components';
import { ETranslations } from '@onekeyhq/shared/src/locale';

const TABULAR_NUMS: ['tabular-nums'] = ['tabular-nums'];

type IHealthFactorRisk = 'critical' | 'warning' | 'success';

// Risk band is a client-side heuristic on the bare health factor. The portfolio
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ScrollView,
SizableText,
Stack,
TABULAR_NUMS,
XStack,
YStack,
useIsKeyboardShown,
Expand Down Expand Up @@ -1302,7 +1303,7 @@ function ProtocolPositionActionPercentHero({
color="$textSubdued"
textAlign="center"
numberOfLines={1}
fontVariant={['tabular-nums']}
fontVariant={TABULAR_NUMS}
/>
</XStack>
</YStack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import { memo } from 'react';

import { useIntl } from 'react-intl';

import { SizableText, XStack, YStack } from '@onekeyhq/components';
import {
SizableText,
TABULAR_NUMS,
XStack,
YStack,
} from '@onekeyhq/components';
import NumberSizeableTextWrapper from '@onekeyhq/kit/src/components/NumberSizeableTextWrapper';
import { Token } from '@onekeyhq/kit/src/components/Token';
import type { ITokenProps } from '@onekeyhq/kit/src/components/Token';
Expand Down Expand Up @@ -73,6 +78,7 @@ const ProtocolPositionSection = memo(
color="$textSubdued"
formatter="balance"
textAlign="right"
fontVariant={TABULAR_NUMS}
>
{asset.amount}
</NumberSizeableTextWrapper>
Expand Down
5 changes: 4 additions & 1 deletion packages/kit/src/components/DeFi/ProtocolValueCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
DashText,
type INumberSizeableTextProps,
SizableText,
TABULAR_NUMS,
Tooltip,
XStack,
} from '@onekeyhq/components';
Expand Down Expand Up @@ -44,7 +45,9 @@ const ProtocolValueCell = memo(
color,
textAlign,
numberOfLines,
fontVariant,
// A value cell is by definition dense column data, so it opts into tabular
// figures by default; callers can still override.
fontVariant = TABULAR_NUMS,
}: IProtocolValueCellProps) => {
const [{ hideValue }] = useSettingsValuePersistAtom();
const valueBN = new BigNumber(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
NumberSizeableText,
SizableText,
Spinner,
TABULAR_NUMS,
XStack,
YStack,
useClipboard,
Expand Down Expand Up @@ -189,6 +190,7 @@ function PerpsSection({
<NumberSizeableText
size="$bodyMdMedium"
color="$text"
fontVariant={TABULAR_NUMS}
formatter="value"
formatterOptions={{ currency: '$' }}
>
Expand Down
3 changes: 3 additions & 0 deletions packages/kit/src/components/TokenListItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
PulseContainer,
SizableText,
Stack,
TABULAR_NUMS,
XStack,
YStack,
} from '@onekeyhq/components';
Expand Down Expand Up @@ -140,6 +141,7 @@ export function TokenListItem({
color="$text"
formatter="balance"
size="$bodyLgMedium"
fontVariant={TABULAR_NUMS}
>
{balance}
</NumberSizeableText>
Expand All @@ -154,6 +156,7 @@ export function TokenListItem({
formatter="value"
color="$textSubdued"
formatterOptions={{ currency: valueProps?.currency ?? '$' }}
fontVariant={TABULAR_NUMS}
>
{valueProps.value}
</NumberSizeableText>
Expand Down
13 changes: 11 additions & 2 deletions packages/kit/src/components/TokenListView/TokenBalanceView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { memo } from 'react';

import { type ISizableTextProps, SizableText } from '@onekeyhq/components';
import {
type ISizableTextProps,
SizableText,
TABULAR_NUMS,
} from '@onekeyhq/components';
import { displayOrUnavailable } from '@onekeyhq/shared/src/utils/tokenValueUtils';

import NumberSizeableTextWrapper from '../NumberSizeableTextWrapper';
Expand All @@ -23,13 +27,18 @@ function TokenBalanceView(props: IProps) {
const balanceParsed = useTokenBalanceParsed($key || '');

if (balanceParsed === undefined) {
return <SizableText {...rest}>-</SizableText>;
return (
<SizableText fontVariant={TABULAR_NUMS} {...rest}>
-
</SizableText>
);
}

return (
<NumberSizeableTextWrapper
formatter="balance"
formatterOptions={{ tokenSymbol: symbol }}
fontVariant={TABULAR_NUMS}
{...rest}
>
{displayOrUnavailable(balanceParsed)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { memo } from 'react';

import type { ISizableTextProps } from '@onekeyhq/components';
import { NumberSizeableText } from '@onekeyhq/components';
import { NumberSizeableText, TABULAR_NUMS } from '@onekeyhq/components';
import { getTokenPriceChangeStyle } from '@onekeyhq/shared/src/utils/tokenUtils';
import {
UNAVAILABLE_DISPLAY,
Expand All @@ -24,6 +24,7 @@ function TokenPriceChangeView(props: IProps) {
<NumberSizeableText
formatter="priceChange"
color="$textSubdued"
fontVariant={TABULAR_NUMS}
{...rest}
>
{UNAVAILABLE_DISPLAY}
Expand All @@ -40,6 +41,7 @@ function TokenPriceChangeView(props: IProps) {
formatter="priceChange"
formatterOptions={{ showPlusMinusSigns }}
color={changeColor}
fontVariant={TABULAR_NUMS}
{...rest}
>
{price24h}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { memo } from 'react';

import type { ISizableTextProps } from '@onekeyhq/components';
import { TABULAR_NUMS } from '@onekeyhq/components';
import { displayOrUnavailable } from '@onekeyhq/shared/src/utils/tokenValueUtils';

import { Currency } from '../Currency';
Expand All @@ -20,6 +21,7 @@ function TokenPriceView(props: IProps) {
<Currency
formatter="price"
sourceCurrency={currency}
fontVariant={TABULAR_NUMS}
{...(rest as React.ComponentProps<typeof Currency>)}
>
{displayOrUnavailable(price)}
Expand Down
13 changes: 11 additions & 2 deletions packages/kit/src/components/TokenListView/TokenValueView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { memo } from 'react';

import { type ISizableTextProps, SizableText } from '@onekeyhq/components';
import {
type ISizableTextProps,
SizableText,
TABULAR_NUMS,
} from '@onekeyhq/components';
import { displayFiatValueOrUnavailable } from '@onekeyhq/shared/src/utils/tokenValueUtils';

import { Currency } from '../Currency';
Expand All @@ -20,13 +24,18 @@ function TokenValueView(props: IProps) {
const { has, fiatValue, balanceParsed, currency } = useTokenValueSlice($key);

if (!has) {
return <SizableText {...rest}>-</SizableText>;
return (
<SizableText fontVariant={TABULAR_NUMS} {...rest}>
-
</SizableText>
);
}

return (
<Currency
formatter="value"
sourceCurrency={currency}
fontVariant={TABULAR_NUMS}
{...(rest as React.ComponentProps<typeof Currency>)}
>
{displayFiatValueOrUnavailable(fiatValue, balanceParsed)}
Expand Down
Loading
Loading