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
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ export function CalendarPanelPopover({
onOpenChange={handleOpenChange}
showHeader={false}
usingSheet={false}
placement="bottom-end"
placement="bottom-start"
floatingPanelProps={{
width: 328,
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function ChartTypeSelect({
onChartTypeChange(chartType);
}
}}
placement="bottom-end"
placement="bottom-start"
floatingPanelProps={{
width: '$56',
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ import {
import { ETranslations } from '@onekeyhq/shared/src/locale';
import platformEnv from '@onekeyhq/shared/src/platformEnv';

import { HEADER_ICON_BUTTON_STYLE_PROPS } from '../utils/NativeChartControlsShared';
import {
HEADER_ICON_BUTTON_STYLE_PROPS,
NATIVE_CHART_OPTION_GRID_GAP,
NATIVE_CHART_OPTION_PILL_LAYOUT_PROPS,
getNativeChartOptionPillColors,
} from '../utils/NativeChartControlsShared';

import {
canToggleTradingViewNativeIndicatorOn,
Expand All @@ -33,14 +38,6 @@ import type {
import type { GestureResponderEvent } from 'react-native';

const INDICATOR_GRID_COLUMN_COUNT = 4;
const INDICATOR_GRID_ITEM_LAYOUT_PROPS = {
flex: 1,
flexBasis: 0,
h: 32,
minWidth: 0,
px: '$2',
borderWidth: 1,
} as const;
export const TRADING_VIEW_NATIVE_INDICATOR_QUICK_BAR_HEIGHT = 31;
const INDICATOR_QUICK_BAR_VERTICAL_PAN_THRESHOLD = 4;

Expand Down Expand Up @@ -87,17 +84,19 @@ function IndicatorPill({
isDisabled: boolean;
onPress?: () => void;
}) {
const { color: textColor, ...pillColors } = getNativeChartOptionPillColors({
isHighlighted: isActive,
isDisabled,
});

return (
<XStack
key={indicator.value}
testID={buildIndicatorItemTestID(indicator.value)}
{...INDICATOR_GRID_ITEM_LAYOUT_PROPS}
borderRadius="$full"
borderCurve="continuous"
borderColor={isActive ? '$bgReverse' : 'transparent'}
{...NATIVE_CHART_OPTION_PILL_LAYOUT_PROPS}
{...pillColors}
alignItems="center"
justifyContent="center"
bg="$bgStrong"
hoverStyle={{
bg: '$bgStrongHover',
}}
Expand All @@ -109,13 +108,7 @@ function IndicatorPill({
userSelect="none"
onPress={isDisabled ? undefined : onPress}
>
<SizableText
size="$bodyMdMedium"
numberOfLines={1}
adjustsFontSizeToFit
minimumFontScale={0.82}
color={getIndicatorTextColor({ isActive, isDisabled })}
>
<SizableText size="$bodyMdMedium" numberOfLines={1} color={textColor}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[🟡 P2] 指标 pill 文字空间被压缩且同时移除了原生自动缩字,StochRSI 会被截断

问题

这一处同时发生了三个方向一致的收缩:共享的 NATIVE_CHART_OPTION_PILL_LAYOUT_PROPS 把指标 pill 的 px 从原来的 $2(8px) 提到 $2.5(10px);IndicatorListPopoverContent 的容器内边距从 p="$3"(12px) 提到 p="$5"(20px);同时这行把 adjustsFontSizeToFit / minimumFontScale={0.82} 删掉了,只留 numberOfLines={1}

以 popover(width: 360,4 列,gap $2) 算:改前每格文字可用宽 (360-2*12)/4 - 2*8 - 2 = 60px,改后是 (360-2*20)/4 - 2*10 - 2 = 52px。而 APP_NATIVE_INDICATOR_OPTIONS 里最长的 StochRSI$bodyMdMedium(14px, 500) 下约需 ~60px。同一个 IndicatorPill 也被移动端 IndicatorsDialogContent 复用,在 375pt 机型上可用宽约 55px

影响

主图/副图指标网格里 StochRSI 这类长标签会被省略成 StochRS…adjustsFontSizeToFit 是 iOS/Android 专有属性,之前正是它在原生指标弹窗里兜住了这个 case(可缩到 14*0.82≈11.5px),删除后原生侧从「能缩字显示完整」直接退化成「截断」,而这个 dialog 并不是本次设计对齐的目标界面。Web 侧原本就贴边(60px vs 60px),本次再少 8px 后必然溢出。

建议

要么给指标 pill 保留原生自动缩字,要么让 pill 的水平内边距按网格来源区分(intervals 从 $3$2.5 是变宽,indicators 从 $2$2.5 是变窄,不适合共用一个常量):

// NativeChartControlsShared.ts
export const NATIVE_CHART_OPTION_PILL_LAYOUT_PROPS = {
  // ...
  px: '$2',
} as const;

或在 IndicatorPill 上单独覆盖 px="$2" 并保留 adjustsFontSizeToFit / minimumFontScale,再用最长标签 StochRSI 在 375pt 机型和 360px popover 上各验一次。

Claude session ↗


Generated by Claude Code

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 保留长指标名称的字体缩放

在桌面端/扩展的 360px 指标弹窗中,p="$5" 和四列布局会让每个 pill 的实际文本宽度只剩约 52px;内置名称 StochRSI 在 14px $bodyMdMedium 下无法完整显示。这里同时移除 adjustsFontSizeToFitminimumFontScale 后,它会被 numberOfLines={1} 截断,复现了本次改动原本要解决的标签省略问题。请为指标 pill 保留缩放能力,或为长指标名提供足够宽度。

Useful? React with 👍 / 👎.

{indicator.label}
</SizableText>
</XStack>
Expand Down Expand Up @@ -146,11 +139,14 @@ function IndicatorGrid({
}, [indicators]);

return (
<YStack gap="$2">
<YStack gap={NATIVE_CHART_OPTION_GRID_GAP}>
{rows.map((row, rowIndex) => {
const placeholderCount = INDICATOR_GRID_COLUMN_COUNT - row.length;
return (
<XStack key={`indicator-row-${rowIndex}`} gap="$2">
<XStack
key={`indicator-row-${rowIndex}`}
gap={NATIVE_CHART_OPTION_GRID_GAP}
>
{row.map((indicator) => {
const isDisabled = !canToggleTradingViewNativeIndicatorOn({
indicatorValue: indicator.value,
Expand All @@ -171,8 +167,8 @@ function IndicatorGrid({
{Array.from({ length: placeholderCount }).map((_, index) => (
<Stack
key={`indicator-placeholder-${rowIndex}-${index}`}
{...INDICATOR_GRID_ITEM_LAYOUT_PROPS}
borderColor="transparent"
{...NATIVE_CHART_OPTION_PILL_LAYOUT_PROPS}
borderColor="$transparent"
opacity={0}
pointerEvents="none"
/>
Expand Down Expand Up @@ -547,7 +543,7 @@ function IndicatorListPopoverContent({
);

return (
<YStack p="$3" gap="$5">
<YStack p="$5" gap="$5">
<IndicatorSection
title={intl.formatMessage({
id: ETranslations.market_main_chart_indicators,
Expand Down Expand Up @@ -595,7 +591,7 @@ export function IndicatorPopover({
}}
showHeader={false}
usingSheet={false}
placement="bottom-end"
placement="bottom-start"
floatingPanelProps={{
width: 360,
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ import { useIntl } from 'react-intl';

import { Icon, SizableText, Stack, XStack, YStack } from '@onekeyhq/components';

import {
NATIVE_CHART_OPTION_GRID_GAP,
NATIVE_CHART_OPTION_PILL_LAYOUT_PROPS,
getNativeChartOptionPillColors,
} from '../utils/NativeChartControlsShared';

import {
INTERVAL_GRID_COLUMN_COUNT,
INTERVAL_GRID_ITEM_LAYOUT_PROPS,
buildIntervalItemTestID,
formatIntervalOptionDisplayLabel,
isIntervalOptionDisabled,
Expand All @@ -34,26 +39,20 @@ function IntervalPill({
disabled?: boolean;
onPress: () => void;
}) {
const isHighlighted = isActive || Boolean(isSelected);
let textColor = '$textSubdued';
if (disabled) {
textColor = '$textDisabled';
} else if (isHighlighted) {
textColor = '$text';
}
const { color: textColor, ...pillColors } = getNativeChartOptionPillColors({
isHighlighted: isActive || Boolean(isSelected),
isDisabled: disabled,
});

return (
<Stack
key={option.value}
testID={buildIntervalItemTestID(section, option.value)}
position="relative"
{...INTERVAL_GRID_ITEM_LAYOUT_PROPS}
borderRadius="$full"
borderCurve="continuous"
borderColor={isHighlighted && !disabled ? '$bgReverse' : 'transparent'}
{...NATIVE_CHART_OPTION_PILL_LAYOUT_PROPS}
{...pillColors}
alignItems="center"
justifyContent="center"
bg="$bgStrong"
overflow="hidden"
hoverStyle={{
bg: '$bgStrongHover',
Expand All @@ -66,29 +65,27 @@ function IntervalPill({
userSelect="none"
onPress={disabled ? undefined : onPress}
>
<SizableText
size="$bodyLgMedium"
color={textColor}
numberOfLines={1}
adjustsFontSizeToFit
minimumFontScale={0.82}
>
<SizableText size="$bodyMdMedium" color={textColor} numberOfLines={1}>
{displayLabel}
</SizableText>
{showCheckMark && !disabled ? (
<Stack
position="absolute"
right={-1}
top={-1}
w={19}
h={19}
borderBottomLeftRadius={12}
right={0}
top={0}
// The design draws a 16px badge offset by -3px behind a clipping
// parent, so only a 13px corner tab is ever visible.
w={13}
h={13}
borderBottomLeftRadius="$2"
borderCurve="continuous"
bg="$bgReverse"
bg="$borderActive"
alignItems="center"
justifyContent="center"
>
<Icon name="Checkmark1SmallOutline" size="$4" color="$iconInverse" />
{/* Checkmark1Small fills ~45% of its 24px viewBox, so $3 draws the
5.7px glyph the design specifies. */}
<Icon name="Checkmark1SmallOutline" size="$3" color="$iconInverse" />
</Stack>
) : null}
</Stack>
Expand Down Expand Up @@ -131,11 +128,14 @@ export function IntervalGrid({
}, [options]);

return (
<YStack gap="$2.5">
<YStack gap={NATIVE_CHART_OPTION_GRID_GAP}>
{rows.map((row, rowIndex) => {
const placeholderCount = INTERVAL_GRID_COLUMN_COUNT - row.length;
return (
<XStack key={`${section}-row-${rowIndex}`} gap="$2.5">
<XStack
key={`${section}-row-${rowIndex}`}
gap={NATIVE_CHART_OPTION_GRID_GAP}
>
{row.map((option) => {
const isSelected = selectedValues?.has(option.value) ?? false;
const isDisabled =
Expand Down Expand Up @@ -167,8 +167,8 @@ export function IntervalGrid({
{Array.from({ length: placeholderCount }).map((_, index) => (
<Stack
key={`${section}-placeholder-${rowIndex}-${index}`}
{...INTERVAL_GRID_ITEM_LAYOUT_PROPS}
borderColor="transparent"
{...NATIVE_CHART_OPTION_PILL_LAYOUT_PROPS}
borderColor="$transparent"
opacity={0}
pointerEvents="none"
/>
Expand All @@ -192,7 +192,7 @@ export function IntervalsDialogSection({
return (
<YStack gap="$3">
<XStack alignItems="center" justifyContent="space-between">
<SizableText size="$bodyLg" color="$textSubdued">
<SizableText size="$bodyMd" color="$textSubdued">
{title}
</SizableText>
{action}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ export const TradingViewNativeIntervalSelector = memo(
title: intl.formatMessage({ id: ETranslations.market_intervals }),
showFooter: false,
testID: 'trading-view-native-intervals-dialog',
// IntervalsDialogContent owns its own padding so the popover and the
// dialog share one layout; drop the default Dialog content padding.
contentContainerProps: { px: '$0', pb: '$0' },
onClose: () => {
handleIntervalsDialogClose(dialogInstance);
},
Expand Down Expand Up @@ -174,6 +177,7 @@ export const TradingViewNativeIntervalSelector = memo(
onIntervalChange={onIntervalChange}
onPreferredValuesChange={handlePreferredValuesChange}
onClose={closeIntervalsPopover}
mode={intervalControlMode}
maxPreferredIntervalCount={
intervalControlMode === 'popover'
? null
Expand Down Expand Up @@ -202,6 +206,9 @@ export const TradingViewNativeIntervalSelector = memo(
onOpenChange={handleIntervalsPopoverOpenChange}
floatingPanelProps={{
width: 360,
// Without this the focus scope moves focus to the Edit button on
// open and it renders with a focus ring the user never asked for.
onOpenAutoFocus: (event) => event.preventDefault(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 不要阻止键盘焦点进入弹窗

在 Web/扩展中用键盘激活 More 时,这里阻止默认的打开聚焦会让焦点留在弹窗外的触发器上;共享 Popover 又明确使用 trapFocus={false},因此后续 Tab 会继续经过工具栏或页面背景,而不是直接进入弹窗中的 Edit 等操作,键盘用户无法正常按弹窗顺序操作。应通过 :focus-visible 等样式区分鼠标与键盘焦点,而不是取消弹窗的自动聚焦。

Useful? React with 👍 / 👎.

}}
renderTrigger={
<IntervalMoreTrigger
Expand Down Expand Up @@ -249,11 +256,14 @@ export const TradingViewNativeIntervalSelector = memo(
activeTextColor="$text"
inactiveTextColor="$textSubdued"
h={30}
p="$0.5"
// No frame padding: the item has to fill the full 30px so its
// active/hover background matches the adjacent More trigger.
p="$0"
segmentControlItemStyleProps={{
minWidth: 42,
px: '$2.5',
py: '$1',
py: '$0',
justifyContent: 'center',
}}
/>
) : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,6 @@ import type { IntlShape } from 'react-intl';
export const MAX_VISIBLE_INTERVAL_COUNT = 4;
export const MAX_PREFERRED_INTERVAL_COUNT = 4;
export const INTERVAL_GRID_COLUMN_COUNT = 4;
export const INTERVAL_GRID_ITEM_LAYOUT_PROPS = {
flex: 1,
flexBasis: 0,
h: 32,
minWidth: 0,
px: '$3',
borderWidth: 1,
} as const;

const PREFERRED_INTERVAL_STORAGE_KEY =
'trading_view_native_preferred_intervals_v1';
Expand Down Expand Up @@ -152,7 +144,14 @@ export function formatIntervalOptionDisplayLabel(
return label;
}

export function getAllIntervalOptions(options: ITradingViewIntervalOption[]) {
/**
* Orders the intervals the chart reported by the canonical template order and
* appends anything it reported outside that list. The chart owns the interval
* list, so a resolution it never reported is never surfaced.
*/
export function getOrderedIntervalOptions(
options: ITradingViewIntervalOption[],
) {
Comment on lines +152 to +154

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed interval-list logic ships without any test coverage

Severity: non-severe

The rewritten interval-ordering logic (getOrderedIntervalOptions at packages/kit/src/components/TradingView/TradingViewV2/components/intervalSelector/NativeIntervalUtils.ts:152) changes which time resolutions the chart offers, yet no test file covers this module, so the new "only offer what the chart reported" behavior is unverified.
Impact: A future regression in which intervals appear (or wrongly disappear) from the chart menus would go unnoticed.

Repository rule: modified logic requires accompanying tests

.cursor/rules/self-testing.mdc Section 6 mandates that new/modified logic has a corresponding .test.ts file covering happy path and edge cases, and that a bug fix includes a regression test. The behavior change here is not pure styling: getAllIntervalOptions previously fabricated disabled: true placeholders for all 14 template resolutions, whereas getOrderedIntervalOptions (packages/kit/src/components/TradingView/TradingViewV2/components/intervalSelector/NativeIntervalUtils.ts:152-201) now drops unmatched templates entirely, which changes dialogOptions in packages/kit/src/components/TradingView/TradingViewV2/components/intervalSelector/hooks/useNativeIntervalSelector.ts:114-117 and therefore the edit grid, sorting order, and preferred-value reconciliation. There is no test file for NativeIntervalUtils.ts in the intervalSelector folder.

const optionsByLabel = new Map<string, ITradingViewIntervalOption>();
const optionsByValue = new Map<string, ITradingViewIntervalOption>();
options.forEach((option) => {
Expand All @@ -170,27 +169,23 @@ export function getAllIntervalOptions(options: ITradingViewIntervalOption[]) {

const seenValues = new Set<string>();
const seenLabels = new Set<string>();
const allOptions: ITradingViewIntervalOption[] =
ALL_INTERVAL_OPTION_TEMPLATES.map((template) => {
const normalizedLabel = normalizeIntervalLabel(template.label);
const matchedOption =
optionsByLabel.get(normalizedLabel) ??
optionsByValue.get(template.fallbackValue);
const option = matchedOption
? {
...matchedOption,
label: template.label,
}
: {
label: template.label,
value: template.fallbackValue,
disabled: true,
};

seenLabels.add(normalizedLabel);
seenValues.add(option.value);
return option;
const allOptions: ITradingViewIntervalOption[] = [];
ALL_INTERVAL_OPTION_TEMPLATES.forEach((template) => {
const normalizedLabel = normalizeIntervalLabel(template.label);
const matchedOption =
optionsByLabel.get(normalizedLabel) ??
optionsByValue.get(template.fallbackValue);
if (!matchedOption) {
return;
}

seenLabels.add(normalizedLabel);
seenValues.add(matchedOption.value);
allOptions.push({
...matchedOption,
label: template.label,
});
});

options.forEach((option) => {
const normalizedLabel = normalizeIntervalLabel(option.label);
Expand Down
Loading