|
| 1 | +import React, { useEffect, useRef, useState } from 'react'; |
| 2 | +import ReactDOM from 'react-dom'; |
| 3 | +import { DropdownItem, SearchField, Tab, Text } from '@deriv-com/quill-ui'; |
| 4 | +import { getElementById } from '../../../../_common/common_functions'; |
| 5 | +import Symbols from '../symbols'; |
| 6 | +import { |
| 7 | + marketOrder, |
| 8 | + sortObjectByKeys, |
| 9 | + derived, |
| 10 | +} from '../../../common/active_symbols'; |
| 11 | +import Defaults, { PARAM_NAMES } from '../defaults'; |
| 12 | +import { triggerMarketChange } from '../../../hooks/events'; |
| 13 | + |
| 14 | +const MarketsDropdown = () => { |
| 15 | + const { UNDERLYING } = PARAM_NAMES; |
| 16 | + const [defaultMarkets, setDefaultMarkets] = useState({}); |
| 17 | + const [markets, setMarkets] = useState({}); |
| 18 | + const [isMounted, setIsMounted] = useState(false); |
| 19 | + const [activeMarket, setActiveMarket] = useState('forex'); |
| 20 | + const [selectedMarket, setSelectedMarket] = useState( |
| 21 | + Defaults.get(UNDERLYING) |
| 22 | + ); |
| 23 | + const [searchKey, setSearchKey] = useState(''); |
| 24 | + const itemsContainer = useRef(null); |
| 25 | + const isScrolling = useRef(false); |
| 26 | + |
| 27 | + const filterMarkets = () => { |
| 28 | + const data = JSON.parse(JSON.stringify(defaultMarkets)); |
| 29 | + const searchStr = searchKey?.toLowerCase(); |
| 30 | + let foundMatchingSymbol = false; |
| 31 | + |
| 32 | + Object.keys(data).forEach(marketKey => { |
| 33 | + const market = data[marketKey]; |
| 34 | + |
| 35 | + Object.keys(market.submarkets).forEach(submarketKey => { |
| 36 | + const submarket = market.submarkets[submarketKey]; |
| 37 | + const subMarketName = submarket.name.toLowerCase(); |
| 38 | + |
| 39 | + // If submarket name matches to search key then don't filter children |
| 40 | + if (!subMarketName.includes(searchStr)){ |
| 41 | + Object.keys(submarket.symbols).forEach(symbolKey => { |
| 42 | + const symbol = submarket.symbols[symbolKey]; |
| 43 | + const displayName = symbol.display.toLowerCase(); |
| 44 | + |
| 45 | + if (!displayName.includes(searchStr)) { |
| 46 | + delete submarket.symbols[symbolKey]; |
| 47 | + } else { |
| 48 | + foundMatchingSymbol = true; |
| 49 | + } |
| 50 | + }); |
| 51 | + } else { |
| 52 | + foundMatchingSymbol = true; |
| 53 | + } |
| 54 | + |
| 55 | + if (Object.keys(submarket.symbols).length === 0) { |
| 56 | + delete market.submarkets[submarketKey]; |
| 57 | + } |
| 58 | + }); |
| 59 | + |
| 60 | + if (Object.keys(market.submarkets).length === 0) { |
| 61 | + delete data[marketKey]; |
| 62 | + } |
| 63 | + }); |
| 64 | + |
| 65 | + // If no matching symbols were found, return the original markets object |
| 66 | + if (!foundMatchingSymbol) { |
| 67 | + return defaultMarkets; |
| 68 | + } |
| 69 | + |
| 70 | + return data; |
| 71 | + }; |
| 72 | + |
| 73 | + useEffect(() => { |
| 74 | + setMarkets(filterMarkets()); |
| 75 | + },[searchKey]); |
| 76 | + |
| 77 | + useEffect(() => { |
| 78 | + const marketList = Symbols.markets(); |
| 79 | + const originalMarkets = sortObjectByKeys(marketList, marketOrder); |
| 80 | + setDefaultMarkets(originalMarkets); |
| 81 | + setMarkets(originalMarkets); |
| 82 | + setIsMounted(true); |
| 83 | + }, []); |
| 84 | + |
| 85 | + // Handle selecting of tabs on scroll |
| 86 | + useEffect(() => { |
| 87 | + const container = itemsContainer.current; |
| 88 | + |
| 89 | + const checkActiveMarket = () => { |
| 90 | + const marketDivs = container.querySelectorAll('div'); |
| 91 | + let closestMarket = ''; |
| 92 | + let closestOffset = Infinity; |
| 93 | + |
| 94 | + marketDivs.forEach((div) => { |
| 95 | + const paddingOffset = 110; |
| 96 | + const offsetTop = div.offsetTop - container.scrollTop - paddingOffset; |
| 97 | + |
| 98 | + if (offsetTop <= 0 && Math.abs(offsetTop) < Math.abs(closestOffset)) { |
| 99 | + closestOffset = offsetTop; |
| 100 | + closestMarket = div.getAttribute('data-id'); |
| 101 | + } |
| 102 | + }); |
| 103 | + |
| 104 | + if (closestMarket) { |
| 105 | + setActiveMarket(closestMarket); |
| 106 | + } |
| 107 | + }; |
| 108 | + |
| 109 | + const handleScroll = () => { |
| 110 | + if (!isScrolling.current) { |
| 111 | + checkActiveMarket(); |
| 112 | + } |
| 113 | + }; |
| 114 | + |
| 115 | + container.addEventListener('scroll', handleScroll); |
| 116 | + |
| 117 | + return () => { |
| 118 | + container.removeEventListener('scroll', handleScroll); |
| 119 | + }; |
| 120 | + }, [isMounted]); |
| 121 | + |
| 122 | + const getactiveMarketIndex = () => Object.keys(markets).indexOf(activeMarket); |
| 123 | + |
| 124 | + const scrollToMarketByIndex = (index) => { |
| 125 | + isScrolling.current = true; |
| 126 | + const container = itemsContainer.current; |
| 127 | + |
| 128 | + if (container) { |
| 129 | + const marketDivs = container ? container.children : []; |
| 130 | + if (marketDivs[index]) { |
| 131 | + const offsetTop = marketDivs[index].offsetTop - container.offsetTop; |
| 132 | + container.scrollTo({ |
| 133 | + top : offsetTop, |
| 134 | + behavior: 'smooth', |
| 135 | + }); |
| 136 | + } |
| 137 | + setTimeout(() => { |
| 138 | + isScrolling.current = false; |
| 139 | + }, 1000); |
| 140 | + } |
| 141 | + }; |
| 142 | + |
| 143 | + const handleUnderlyingClick = (underlying) => { |
| 144 | + Defaults.set(UNDERLYING, underlying); |
| 145 | + setSelectedMarket(underlying); |
| 146 | + triggerMarketChange(); |
| 147 | + }; |
| 148 | + |
| 149 | + return ( |
| 150 | + <div className='quill-market-dropdown-container'> |
| 151 | + <div className='quill-market-dropdown-search-container'> |
| 152 | + <SearchField |
| 153 | + inputSize='sm' |
| 154 | + onChange={(e) => setSearchKey(e.target.value)} |
| 155 | + placeholder='Search...' |
| 156 | + value='' |
| 157 | + variant='fill' |
| 158 | + /> |
| 159 | + </div> |
| 160 | + <div className='quill-market-dropdown-tab-container'> |
| 161 | + <Tab.Container |
| 162 | + size='md' |
| 163 | + contentStyle='hug' |
| 164 | + selectedTabIndex={getactiveMarketIndex()} |
| 165 | + onTabClick={(e) => scrollToMarketByIndex(e)} |
| 166 | + > |
| 167 | + <Tab.List> |
| 168 | + {Object.keys(markets).map((ik) => { |
| 169 | + const market = markets[ik]; |
| 170 | + const marketName = derived.includes(ik) |
| 171 | + ? `Derived (${market.name})` |
| 172 | + : market.name; |
| 173 | + return <Tab.Trigger key={ik}>{marketName}</Tab.Trigger>; |
| 174 | + })} |
| 175 | + </Tab.List> |
| 176 | + </Tab.Container> |
| 177 | + <div |
| 178 | + className='quill-market-dropdown-item-container' |
| 179 | + id='quill-market-dropdown-list' |
| 180 | + ref={itemsContainer} |
| 181 | + > |
| 182 | + {Object.keys(markets).map((ik) => { |
| 183 | + const market = markets[ik]; |
| 184 | + const { submarkets } = market; |
| 185 | + |
| 186 | + return ( |
| 187 | + <div id={`${ik}-dropdown-list`} key={ik} data-id={ik}> |
| 188 | + {Object.keys(submarkets).map((sk) => { |
| 189 | + const submarket = submarkets[sk]; |
| 190 | + const { symbols, name } = submarket; |
| 191 | + |
| 192 | + return ( |
| 193 | + <React.Fragment key={sk}> |
| 194 | + { |
| 195 | + <Text size='md' bold className='market-item-heading'> |
| 196 | + {name} |
| 197 | + </Text> |
| 198 | + } |
| 199 | + {Object.keys(symbols).map((yk) => { |
| 200 | + const symbol = symbols[yk]; |
| 201 | + const { display } = symbol; |
| 202 | + |
| 203 | + return ( |
| 204 | + <DropdownItem |
| 205 | + key={yk} |
| 206 | + className='' |
| 207 | + onClick={() => handleUnderlyingClick(yk)} |
| 208 | + label={display} |
| 209 | + selected={yk === selectedMarket} |
| 210 | + size='sm' |
| 211 | + /> |
| 212 | + ); |
| 213 | + })} |
| 214 | + <hr /> |
| 215 | + </React.Fragment> |
| 216 | + ); |
| 217 | + })} |
| 218 | + </div> |
| 219 | + ); |
| 220 | + })} |
| 221 | + </div> |
| 222 | + </div> |
| 223 | + </div> |
| 224 | + ); |
| 225 | +}; |
| 226 | + |
| 227 | +export const init = () => { |
| 228 | + ReactDOM.render( |
| 229 | + <MarketsDropdown />, |
| 230 | + getElementById('markets-dropdown-container') |
| 231 | + ); |
| 232 | +}; |
| 233 | + |
| 234 | +export default init; |
0 commit comments