Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DTRA-2055 / Kate / [DTrader-V2] Trade page scrolling issue #17176

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Carousel from 'AppV2/Components/Carousel';
import BarrierDescription from './barrier-description';
import BarrierInput from './barrier-input';
import CarouselHeader from 'AppV2/Components/Carousel/carousel-header';
import { removeFocus } from 'AppV2/Utils/layout-utils';

type TDurationProps = {
is_minimized?: boolean;
Expand Down Expand Up @@ -49,7 +50,11 @@ const Barrier = observer(({ is_minimized }: TDurationProps) => {
readOnly
label={<Localize i18n_default_text='Barrier' key={`barrier${is_minimized ? '-minimized' : ''}`} />}
value={v2_params_initial_values.barrier_1 || barrier_1}
onClick={() => setIsOpen(true)}
onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
removeFocus(e);
setIsOpen(true);
}}
onMouseDown={removeFocus}
kate-deriv marked this conversation as resolved.
Show resolved Hide resolved
className={clsx('trade-params__option', is_minimized && 'trade-params__option--minimized')}
/>
<ActionSheet.Root isOpen={is_open} onClose={() => onClose(false)} position='left' expandable={false}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from 'AppV2/Utils/trade-params-utils';
import { useDtraderQuery } from 'AppV2/Hooks/useDtraderQuery';
import { ProposalResponse } from 'Stores/Modules/Trading/trade-store';
import { removeFocus } from 'AppV2/Utils/layout-utils';

const timeToMinutes = (time: string) => {
const [hours, minutes] = time.split(':').map(Number);
Expand Down Expand Up @@ -151,9 +152,11 @@ const DayInput = ({
textAlignment='center'
value={formatted_date}
disabled={duration_units_list.filter(item => item.value === 'd').length === 0}
onClick={() => {
onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
removeFocus(e);
Copy link
Contributor

@wojciech-deriv wojciech-deriv Oct 16, 2024

Choose a reason for hiding this comment

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

is there any chance to find solution which does not require to remember to remove focus from every single element? it feels like a recipe for sneaky bugs, I'm sure ppl will forget and it will sneak through the PRs.

The same way missing dependencies in useHook sneaks through all the time and causes sneaky bugs all the time. Everyone knows about useEffect dependencies, but ppl still forget to add them and miss them in reviews, so basically its a source of bugs and unnecessary overhead in reviews.

cc @kate-deriv @nijil-deriv

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will try to rework, @nijil-deriv gave me a tip to think about wrapper. The idea is to wrap all trade params with wrapper and inside that wrapper to track focus. If it'll be inside input, then remove it

Copy link
Contributor Author

@kate-deriv kate-deriv Oct 16, 2024

Choose a reason for hiding this comment

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

Based on comments I did another solution, could you please check?
Link
What was done: TradeParameters (in packages/trader/src/AppV2/Components/TradeParameters/trade-parameters.tsx) is a container for all trade parameters, so inside of useEffect I added an event listener for focusin. If event.target contains specific custom attribute (data-focus), then I'll remove focus.
Custom attributes (data-focus) were added for all trade params inputs. That was the only way that works for me to indicate that it's correct input. We can't just remove focus from all inputs inside trade params components as user won't be able to even type something for stake, barrier and etc
@wojciech-deriv @nijil-deriv

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If alternative solution will melt your hearts, I'll merge that branch into a current one

setOpen(true);
}}
onMouseDown={removeFocus}
leftIcon={<LabelPairedCalendarSmRegularIcon width={24} height={24} />}
/>

Expand All @@ -164,9 +167,11 @@ const DayInput = ({
name='time'
value={`${(formatted_date === formatted_current_date ? end_time : temp_expiry_time) || '23:59:59'} GMT`}
disabled={formatted_date !== formatted_current_date || !is_24_hours_contract}
onClick={() => {
onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
removeFocus(e);
setOpenTimePicker(true);
}}
onMouseDown={removeFocus}
leftIcon={<LabelPairedClockThreeSmRegularIcon width={24} height={24} />}
/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getDisplayedContractTypes } from 'AppV2/Utils/trade-types-utils';
import useActiveSymbols from 'AppV2/Hooks/useActiveSymbols';
import { getDatePickerStartDate, getSmallestDuration } from 'AppV2/Utils/trade-params-utils';
import { useStore } from '@deriv/stores';
import { removeFocus } from 'AppV2/Utils/layout-utils';

type TDurationProps = {
is_minimized?: boolean;
Expand Down Expand Up @@ -170,7 +171,11 @@ const Duration = observer(({ is_minimized }: TDurationProps) => {
noStatusIcon
disabled={isMarketClosed(activeSymbols, symbol)}
className={clsx('trade-params__option', is_minimized && 'trade-params__option--minimized')}
onClick={() => setOpen(true)}
onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
removeFocus(e);
setOpen(true);
}}
onMouseDown={removeFocus}
status={has_error ? 'error' : 'neutral'}
/>
<ActionSheet.Root
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import CarouselHeader from 'AppV2/Components/Carousel/carousel-header';
import TradeParamDefinition from 'AppV2/Components/TradeParamDefinition';
import { isSmallScreen } from 'AppV2/Utils/trade-params-utils';
import GrowthRatePicker from './growth-rate-picker';
import { removeFocus } from 'AppV2/Utils/layout-utils';

type TGrowthRateProps = {
is_minimized?: boolean;
Expand Down Expand Up @@ -98,7 +99,11 @@ const GrowthRate = observer(({ is_minimized }: TGrowthRateProps) => {
label={
<Localize i18n_default_text='Growth rate' key={`growth-rate${is_minimized ? '-minimized' : ''}`} />
}
onClick={() => setIsOpen(true)}
onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
removeFocus(e);
setIsOpen(true);
}}
onMouseDown={removeFocus}
readOnly
value={`${getGrowthRatePercentage(growth_rate)}%`}
variant='fill'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Skeleton } from '@deriv/components';
import { Localize } from '@deriv/translations';
import { useTraderStore } from 'Stores/useTraderStores';
import LastDigitSelector from './last-digit-selector';
import { removeFocus } from 'AppV2/Utils/layout-utils';

type TLastDigitSelectorProps = {
is_minimized?: boolean;
Expand Down Expand Up @@ -47,7 +48,11 @@ const LastDigitPrediction = observer(({ is_minimized }: TLastDigitSelectorProps)
}
value={last_digit}
className={clsx('trade-params__option', 'trade-params__option--minimized')}
onClick={() => setIsOpen(true)}
onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
removeFocus(e);
setIsOpen(true);
}}
onMouseDown={removeFocus}
/>
<ActionSheet.Root isOpen={is_open} onClose={onActionSheetClose} position='left' expandable={false}>
<ActionSheet.Portal shouldCloseOnDrag>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import CarouselHeader from 'AppV2/Components/Carousel/carousel-header';
import TradeParamDefinition from 'AppV2/Components/TradeParamDefinition';
import { isSmallScreen } from 'AppV2/Utils/trade-params-utils';
import MultiplierWheelPicker from './multiplier-wheel-picker';
import { removeFocus } from 'AppV2/Utils/layout-utils';

type TMultiplierProps = {
is_minimized?: boolean;
Expand Down Expand Up @@ -67,7 +68,11 @@ const Multiplier = observer(({ is_minimized }: TMultiplierProps) => {
}
value={`x${multiplier}`}
className={classname}
onClick={() => setIsOpen(true)}
onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
removeFocus(e);
setIsOpen(true);
}}
onMouseDown={removeFocus}
/>
<ActionSheet.Root
expandable={false}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Carousel from 'AppV2/Components/Carousel';
import CarouselHeader from 'AppV2/Components/Carousel/carousel-header';
import TradeParamDefinition from 'AppV2/Components/TradeParamDefinition';
import PayoutPerPointWheel from './payout-per-point-wheel';
import { removeFocus } from 'AppV2/Utils/layout-utils';

type TPayoutPerPointProps = {
is_minimized?: boolean;
Expand Down Expand Up @@ -90,7 +91,11 @@ const PayoutPerPoint = observer(({ is_minimized }: TPayoutPerPointProps) => {
key={`payout-per-point${is_minimized ? '-minimized' : ''}`}
/>
}
onClick={() => setIsOpen(true)}
onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
removeFocus(e);
setIsOpen(true);
}}
onMouseDown={removeFocus}
readOnly
variant='fill'
value={`${v2_params_initial_values?.payout_per_point ?? payout_per_point} ${currency_display_code}`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import TradeParamDefinition from 'AppV2/Components/TradeParamDefinition';
import { addUnit, isSmallScreen } from 'AppV2/Utils/trade-params-utils';
import RiskManagementPicker from './risk-management-picker';
import RiskManagementContent from './risk-management-content';
import { removeFocus } from 'AppV2/Utils/layout-utils';

type TRiskManagementProps = {
is_minimized?: boolean;
Expand Down Expand Up @@ -79,7 +80,11 @@ const RiskManagement = observer(({ is_minimized }: TRiskManagementProps) => {
key={`risk-management${is_minimized ? '-minimized' : ''}`}
/>
}
onClick={() => setIsOpen(true)}
onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
removeFocus(e);
setIsOpen(true);
}}
onMouseDown={removeFocus}
readOnly
value={getRiskManagementText()}
variant='fill'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useTraderStore } from 'Stores/useTraderStores';
import { getDisplayedContractTypes } from 'AppV2/Utils/trade-types-utils';
import StakeDetails from './stake-details';
import useContractsForCompany from 'AppV2/Hooks/useContractsForCompany';
import { removeFocus } from 'AppV2/Utils/layout-utils';

type TStakeProps = {
is_minimized?: boolean;
Expand Down Expand Up @@ -203,7 +204,11 @@ const Stake = observer(({ is_minimized }: TStakeProps) => {
readOnly
label={<Localize i18n_default_text='Stake' key={`stake${is_minimized ? '-minimized' : ''}`} />}
noStatusIcon
onClick={() => setIsOpen(true)}
onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
removeFocus(e);
setIsOpen(true);
}}
onMouseDown={removeFocus}
value={`${v2_params_initial_values?.stake ?? amount} ${getCurrencyDisplayCode(currency)}`}
className={clsx('trade-params__option', is_minimized && 'trade-params__option--minimized')}
status={stake_error && !is_open ? 'error' : undefined}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Carousel from 'AppV2/Components/Carousel';
import CarouselHeader from 'AppV2/Components/Carousel/carousel-header';
import { isSmallScreen } from 'AppV2/Utils/trade-params-utils';
import StrikeWheel from './strike-wheel';
import { removeFocus } from 'AppV2/Utils/layout-utils';

type TStrikeProps = {
is_minimized?: boolean;
Expand Down Expand Up @@ -97,7 +98,11 @@ const Strike = observer(({ is_minimized }: TStrikeProps) => {
<TextField
className={classname}
label={<Localize i18n_default_text='Strike price' key={`strike${is_minimized ? '-minimized' : ''}`} />}
onClick={() => setIsOpen(true)}
onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
removeFocus(e);
setIsOpen(true);
}}
onMouseDown={removeFocus}
readOnly
variant='fill'
value={barrier_1}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Carousel from 'AppV2/Components/Carousel';
import CarouselHeader from 'AppV2/Components/Carousel/carousel-header';
import TakeProfitAndStopLossInput from '../RiskManagement/take-profit-and-stop-loss-input';
import TradeParamDefinition from 'AppV2/Components/TradeParamDefinition';
import { removeFocus } from 'AppV2/Utils/layout-utils';

type TTakeProfitProps = {
is_minimized?: boolean;
Expand Down Expand Up @@ -46,7 +47,11 @@ const TakeProfit = observer(({ is_minimized }: TTakeProfitProps) => {
label={
<Localize i18n_default_text='Take profit' key={`take-profit${is_minimized ? '-minimized' : ''}`} />
}
onClick={() => setIsOpen(true)}
onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
removeFocus(e);
setIsOpen(true);
}}
onMouseDown={removeFocus}
readOnly
variant='fill'
value={has_take_profit && take_profit ? `${take_profit} ${getCurrencyDisplayCode(currency)}` : '-'}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TRADE_TYPES } from '@deriv/shared';
import { isTradeParamVisible, getChartHeight } from '../layout-utils';
import { isTradeParamVisible, getChartHeight, removeFocus } from '../layout-utils';

describe('isTradeParamVisible', () => {
it('should return correct value for expiration component key', () => {
Expand Down Expand Up @@ -132,3 +135,17 @@ describe('getChartHeight', () => {
).toEqual(chart_height_with_additional_info);
});
});

describe('removeFocus', () => {
it('removes focus from the element', () => {
const MockComponent = () => (
<input type='text' onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => removeFocus(e)} />
);
render(<MockComponent />);

const input = screen.getByRole('textbox');
userEvent.click(input);

expect(input).not.toHaveFocus();
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ReactElement, ReactNode } from 'react';
import React, { ReactElement } from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { CONTRACT_TYPES, TRADE_TYPES } from '@deriv/shared';
Expand Down
2 changes: 2 additions & 0 deletions packages/trader/src/AppV2/Utils/layout-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,5 @@ export const getChartHeight = ({
return height - HEIGHT.ADDITIONAL_INFO;
return height;
};

export const removeFocus = (e: React.MouseEvent<HTMLInputElement, MouseEvent>) => (e.target as HTMLElement)?.blur();
Loading