Skip to content

Commit

Permalink
henry/dtra-1563/fix: asset/symbol selection bug fix (deriv-com#16333)
Browse files Browse the repository at this point in the history
* fix: make market-category-item clickable area bigger

* fix: correctly sort market categories

* fix: automatic scroll to center on all tab

* fix: update quill-ui packages

* fix: remove searchbar hiding when scrolling down

* fix: add role to get rid of sonarcloud error

* fix: remove snackbar on scroll demo

* fix: remove dismiss snackbar on scroll

* fix: code smell

* fix: convert span to button

* fix: handle accessibility

* fix: remove display name

* fix: remove unused imports

* fix: turn element into div

* fix: remove unused imports

* fix: add keyboard event handler

* fix: build fail and refactor clickkeyboardeventhandler

* fix: build error

* fix: scroll to center

* fix: remove extra active_symbol calls for v2

* fix: failing tests
  • Loading branch information
henry-deriv authored Aug 15, 2024
1 parent 7569ae1 commit df7f2a8
Show file tree
Hide file tree
Showing 13 changed files with 81 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ const ActiveSymbolsList = observer(({ isOpen, setIsOpen }: TActiveSymbolsList) =
<ActionSheet.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>
<ActionSheet.Portal shouldCloseOnDrag fullHeightOnOpen>
<SymbolsSearchField
marketCategoriesRef={marketCategoriesRef}
searchValue={searchValue}
setSearchValue={setSearchValue}
isSearching={isSearching}
Expand Down Expand Up @@ -62,8 +61,8 @@ const ActiveSymbolsList = observer(({ isOpen, setIsOpen }: TActiveSymbolsList) =
selectedSymbol={selectedSymbol}
setSelectedSymbol={setSelectedSymbol}
setIsOpen={setIsOpen}
ref={marketCategoriesRef}
isOpen={isOpen}
marketCategoriesRef={marketCategoriesRef}
/>
)}
</Tab.Container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ describe('<MarketCategories />', () => {
setSelectedSymbol: jest.fn(),
setIsOpen: jest.fn(),
isOpen: false,
marketCategoriesRef: {
current: document.createElement('div'),
},
};
beforeEach(() => {
jest.clearAllMocks();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
import React, { forwardRef, Ref } from 'react';
import React from 'react';
import { Tab } from '@deriv-com/quill-ui';
import useActiveSymbols from 'AppV2/Hooks/useActiveSymbols';
import { categorizeSymbols } from 'AppV2/Utils/symbol-categories-utils';
import MarketCategory from '../MarketCategory';
import MarketCategoryTab from '../MarketCategoryTab/market-category-tab';
import { observer } from '@deriv/stores';

type TMarketCategories = {
selectedSymbol: string;
setSelectedSymbol: (input: string) => void;
setIsOpen: (input: boolean) => void;
isOpen: boolean;
marketCategoriesRef: React.RefObject<HTMLDivElement>;
};

const MarketCategories = forwardRef(
({ selectedSymbol, setSelectedSymbol, setIsOpen, isOpen }: TMarketCategories, ref: Ref<HTMLDivElement>) => {
const MarketCategories = observer(
({ selectedSymbol, setSelectedSymbol, setIsOpen, isOpen, marketCategoriesRef }: TMarketCategories) => {
const { activeSymbols } = useActiveSymbols();
const categorizedSymbols = categorizeSymbols(activeSymbols);

return (
<React.Fragment>
<Tab.List>
{Object.values(categorizedSymbols).map(category => (
<MarketCategoryTab key={category.market} category={category} />
))}
</Tab.List>
<Tab.Content className='market-categories__list' ref={ref}>
<Tab.Content className='market-categories__list' ref={marketCategoriesRef}>
{Object.values(categorizedSymbols).map(category => (
<MarketCategory
key={category.market}
Expand All @@ -40,6 +43,4 @@ const MarketCategories = forwardRef(
}
);

MarketCategories.displayName = 'MarketCategories';

export default MarketCategories;
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,7 @@ const MarketCategory = ({ category, selectedSymbol, setSelectedSymbol, setIsOpen
const prevSymbol = usePrevious(selectedSymbol);

useEffect(() => {
if (
isOpen &&
category.market === 'all' &&
selectedSymbol &&
itemRefs.current[selectedSymbol] &&
prevSymbol === selectedSymbol
) {
if (isOpen && category.market === 'all' && selectedSymbol && itemRefs.current[selectedSymbol] && !prevSymbol) {
timerRef.current = setTimeout(() => {
itemRefs.current[selectedSymbol]?.scrollIntoView({ block: 'center' });
}, 50);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,10 @@
&--selected {
background-color: var(--semantic-color-slate-solid-surface-inverse-lowest);
}
&-left {
display: flex;
flex: 1 0 0;
align-items: center;
gap: var(--semantic-spacing-gap-lg);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import clsx from 'clsx';
import { observer, useStore } from '@deriv/stores';
import { useTraderStore } from 'Stores/useTraderStores';
import { useModulesStore } from 'Stores/useModulesStores';
import { clickAndKeyEventHandler } from '@deriv/shared';

type TMarketCategoryItem = {
item: ActiveSymbols[0];
Expand All @@ -30,15 +31,13 @@ const MarketCategoryItem = forwardRef(
setIsFavorite(favoriteSymbols.includes(item.symbol));
}, [favoriteSymbols, item.symbol]);

const handleSelect = async (e: React.MouseEvent<HTMLSpanElement>) => {
const symbol = (e.target as HTMLSpanElement).getAttribute('data-symbol');
setSelectedSymbol(symbol ?? '');
const handleSelect = async (symbol: string) => {
setSelectedSymbol(symbol);
await onSymbolChange({ target: { name: 'symbol', value: symbol } });
setIsOpen(false);
};

const toggleFavorites = (e: React.MouseEvent<HTMLSpanElement>) => {
const symbol = (e.currentTarget as HTMLSpanElement).getAttribute('data-symbol');
const toggleFavorites = (symbol: string) => {
if (!symbol) return;
const symbolIndex = favoriteSymbols.indexOf(symbol);

Expand Down Expand Up @@ -69,33 +68,48 @@ const MarketCategoryItem = forwardRef(
setIsFavorite(favoriteSymbols.includes(symbol));
};

const handleSelectDecorator = (e?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => {
const symbol = (e?.currentTarget as HTMLElement).getAttribute('data-symbol') || '';
clickAndKeyEventHandler(() => handleSelect(symbol), e);
};

const toggleFavoritesDecorator = (e?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => {
const symbol = (e?.currentTarget as HTMLElement).getAttribute('data-symbol') || '';
clickAndKeyEventHandler(() => toggleFavorites(symbol), e);
};

return (
<div
className={clsx('market-category-item', {
'market-category-item--selected': selectedSymbol === item.symbol,
})}
ref={ref}
>
<SymbolIconsMapper symbol={item.symbol} />
<Text
size='sm'
className={clsx('market-category-item-symbol', {
'market-category-item-symbol--selected': selectedSymbol === item.symbol,
})}
<span
className='market-category-item-left'
data-symbol={item.symbol}
onClick={handleSelectDecorator}
onKeyDown={handleSelectDecorator}
>
<span onClick={handleSelect} data-symbol={item.symbol}>
{item.display_name}
</span>
</Text>
{!item.exchange_is_open && (
<Tag
label={<Localize key='exchange-closed' i18n_default_text='CLOSED' />}
color='error'
variant={selectedSymbol === item.symbol ? 'outline' : 'fill'}
showIcon={false}
/>
)}
<span onClick={toggleFavorites} data-symbol={item.symbol}>
<SymbolIconsMapper symbol={item.symbol} />
<Text
size='sm'
className={clsx('market-category-item-symbol', {
'market-category-item-symbol--selected': selectedSymbol === item.symbol,
})}
>
<span>{item.display_name}</span>
</Text>
{!item.exchange_is_open && (
<Tag
label={<Localize key='exchange-closed' i18n_default_text='CLOSED' />}
color='error'
variant={selectedSymbol === item.symbol ? 'outline' : 'fill'}
showIcon={false}
/>
)}
</span>
<span onClick={toggleFavoritesDecorator} onKeyDown={toggleFavoritesDecorator} data-symbol={item.symbol}>
{isFavorite ? (
<StandaloneStarFillIcon fill='var(--core-color-solid-mustard-700)' iconSize='sm' />
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@ describe('<SymbolsSearchField />', () => {
setIsSearching: jest.fn(),
searchValue: '',
setSearchValue: jest.fn(),
marketCategoriesRef: {
current: document.createElement('div'),
},
};
jest.clearAllMocks();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,4 @@
&--is-focused {
padding: var(--core-spacing-400) var(--core-spacing-400) var(--core-spacing-400) var(--core-spacing-800);
}
&--is-hidden {
display: none;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useRef, useEffect, useState, forwardRef, Ref } from 'react';
import React, { useRef, useEffect } from 'react';
import { Button, SearchField } from '@deriv-com/quill-ui';
import clsx from 'clsx';
import { observer, useStore } from '@deriv/stores';
Expand All @@ -11,32 +11,15 @@ export type TSymbolsSearchField = {
setIsSearching: (input: boolean) => void;
searchValue: string;
setSearchValue: (input: string) => void;
marketCategoriesRef: React.MutableRefObject<HTMLDivElement | null>;
};

const SymbolsSearchField = observer(
({ isSearching, setIsSearching, searchValue, setSearchValue, marketCategoriesRef }: TSymbolsSearchField) => {
({ isSearching, setIsSearching, searchValue, setSearchValue }: TSymbolsSearchField) => {
const { ui } = useStore();
const { is_dark_mode_on } = ui;
const { contract_type } = useTraderStore();
const contract_name = getContractTypesConfig()[contract_type]?.title;
const inputRef = useRef<HTMLInputElement | null>(null);
const [prevScrollY, setPrevScrollY] = useState(marketCategoriesRef.current?.scrollTop);
const [isSearchFieldVisible, setIsSearchFieldVisible] = useState(true);

useEffect(() => {
const handleScroll = () => {
const currentScrollY = marketCategoriesRef.current?.scrollTop;

if (prevScrollY && currentScrollY !== null && currentScrollY !== undefined)
setIsSearchFieldVisible(prevScrollY > currentScrollY);
setPrevScrollY(currentScrollY);
};
marketCategoriesRef.current?.addEventListener('scroll', handleScroll);
return () => {
marketCategoriesRef.current?.removeEventListener('scroll', handleScroll);
};
}, [prevScrollY, marketCategoriesRef]);

useEffect(() => {
const inputElement = inputRef.current;
Expand All @@ -61,7 +44,6 @@ const SymbolsSearchField = observer(
<div
className={clsx('symbols-search-field__container', {
'symbols-search-field--is-focused': isSearching,
'symbols-search-field--is-hidden': !isSearchFieldVisible,
})}
>
<SearchField
Expand Down
3 changes: 2 additions & 1 deletion packages/trader/src/AppV2/Hooks/useActiveSymbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ const useActiveSymbols = () => {

if (
(isVanillaContract(previous_contract_type) && is_vanilla) ||
(isTurbosContract(previous_contract_type) && is_turbos)
(isTurbosContract(previous_contract_type) && is_turbos) ||
getContractTypesList().length === 0
) {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/trader/src/AppV2/Utils/sort-symbols-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const sortSymbols = (symbolsList: ActiveSymbols) => {
{}
);

return symbolsList.sort((a, b) => {
return symbolsList.slice().sort((a, b) => {
const marketOrderA = marketOrderMap[a.market] !== undefined ? marketOrderMap[a.market] : symbolsList.length;
const marketOrderB = marketOrderMap[b.market] !== undefined ? marketOrderMap[b.market] : symbolsList.length;
if (marketOrderA !== marketOrderB) {
Expand Down
5 changes: 3 additions & 2 deletions packages/trader/src/AppV2/Utils/symbol-categories-utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ActiveSymbols } from '@deriv/api-types';
import { localize } from '@deriv/translations';
import sortSymbols from './sort-symbols-utils';

type SubmarketGroup = {
submarket_display_name: string;
Expand All @@ -22,7 +23,8 @@ export const categorizeSymbols = (symbols: ActiveSymbols): Record<string, Market
return {};
}
// Categorize ActiveSymbols array into object categorized by markets
let categorizedSymbols = symbols.reduce((acc: Record<string, MarketGroup>, symbol: ActiveSymbols[0]) => {
const sortedSymbols = sortSymbols(symbols);
let categorizedSymbols = sortedSymbols.reduce((acc: Record<string, MarketGroup>, symbol: ActiveSymbols[0]) => {
const { market, market_display_name, subgroup, subgroup_display_name, submarket, submarket_display_name } =
symbol;

Expand All @@ -42,7 +44,6 @@ export const categorizeSymbols = (symbols: ActiveSymbols): Record<string, Market

return acc;
}, {});

// Sort categorizedSymbols by submarket_display_name
Object.keys(categorizedSymbols).forEach(market => {
Object.keys(categorizedSymbols[market].subgroups).forEach(subgroup => {
Expand Down
19 changes: 17 additions & 2 deletions packages/trader/src/Stores/Modules/Trading/trade-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ export default class TradeStore extends BaseStore {
is_accumulator: computed,
is_chart_loading: observable,
is_digits_widget_active: observable,
is_dtrader_v2_enabled: computed,
is_equal: observable,
is_market_closed: observable,
is_mobile_digit_view_selected: observable,
Expand Down Expand Up @@ -664,8 +665,11 @@ export default class TradeStore extends BaseStore {
async loadActiveSymbols(should_set_default_symbol = true, should_show_loading = true) {
this.should_show_active_symbols_loading = should_show_loading;

await this.setActiveSymbols();
await this.root_store.active_symbols.setActiveSymbols();
if (!this.is_dtrader_v2_enabled) {
await this.setActiveSymbols();
await this.root_store.active_symbols.setActiveSymbols();
}

const { symbol, showModal } = getTradeURLParams({ active_symbols: this.active_symbols });
if (showModal && should_show_loading && !this.root_store.client.is_logging_in) {
this.root_store.ui.toggleUrlUnavailableModal(true);
Expand Down Expand Up @@ -1282,6 +1286,16 @@ export default class TradeStore extends BaseStore {
}
}

get is_dtrader_v2_enabled() {
const is_dtrader_v2 = JSON.parse(localStorage.getItem('FeatureFlagsStore') ?? '{}')?.data?.dtrader_v2;

return (
is_dtrader_v2 &&
this.root_store.ui.is_mobile &&
(window.location.pathname.startsWith(routes.trade) || window.location.pathname.startsWith('/contract/'))
);
}

get is_synthetics_available() {
return !!this.active_symbols?.find(item => item.market === 'synthetic_index');
}
Expand Down Expand Up @@ -1805,6 +1819,7 @@ export default class TradeStore extends BaseStore {
});
}
if ('active_symbols' in req) {
if (this.is_dtrader_v2_enabled) return;
if (this.root_store.client.is_logged_in) {
return WS.authorized.activeSymbols('brief');
}
Expand Down

0 comments on commit df7f2a8

Please sign in to comment.