diff --git a/package-lock.json b/package-lock.json index ebd90a372e2..c4f33ea4ed5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@binary-com/binary-document-uploader": "^2.4.4", "@binary-com/binary-style": "^0.2.26", "@binary-com/webtrader-charts": "^0.6.1", - "@deriv-com/quill-ui": "^1.11.7", + "@deriv-com/quill-ui": "^1.12.2", "@livechat/customer-sdk": "4.0.2", "canvas-toBlob": "1.0.0", "classnames": "2.2.5", @@ -2503,9 +2503,9 @@ } }, "node_modules/@deriv-com/quill-ui": { - "version": "1.11.7", - "resolved": "https://registry.npmjs.org/@deriv-com/quill-ui/-/quill-ui-1.11.7.tgz", - "integrity": "sha512-7ilcZSNUwiQnyFGNb2Kf4CLNegM7jO1rb1BhS2/Dz/2gYHzELZVcOSSde6/fKuQ3gqgGOwuXiJ3G/xrN2jF7gQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@deriv-com/quill-ui/-/quill-ui-1.12.2.tgz", + "integrity": "sha512-BJndKaXMdhLoYMuUxoDqOxHeeBq3vxSILosYM+7qA3ijr3GV4dtJSLAix+HpTJWG2bonJGdjebnyuk+GQA58Bw==", "dependencies": { "@deriv-com/quill-tokens": "^2.0.7", "@deriv/quill-icons": "^1.22.10", diff --git a/package.json b/package.json index f9db4b67717..15ba9b4b5fa 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "@binary-com/binary-document-uploader": "^2.4.4", "@binary-com/binary-style": "^0.2.26", "@binary-com/webtrader-charts": "^0.6.1", - "@deriv-com/quill-ui": "^1.11.7", + "@deriv-com/quill-ui": "^1.12.2", "@livechat/customer-sdk": "4.0.2", "canvas-toBlob": "1.0.0", "classnames": "2.2.5", diff --git a/src/javascript/app/pages/trade/common.js b/src/javascript/app/pages/trade/common.js index f94c4691b02..eb34f389a74 100644 --- a/src/javascript/app/pages/trade/common.js +++ b/src/javascript/app/pages/trade/common.js @@ -3,7 +3,7 @@ const Symbols = require('./symbols'); const Tick = require('./tick'); const contractsElement = require('./contracts.jsx'); const marketsElement = require('./markets.jsx'); -const MarketDropdownElement = require('./markets/markets-dropdown.jsx'); +const MarketSelectorElement = require('./markets/market-selector.jsx'); const TabsElement = require('../bottom/tabs.jsx'); const formatMoney = require('../../common/currency').formatMoney; const ActiveSymbols = require('../../common/active_symbols'); @@ -63,7 +63,7 @@ const commonTrading = (() => { // All other Quill refactored components TabsElement.init(); - MarketDropdownElement.init(); + MarketSelectorElement.init(); }; /* diff --git a/src/javascript/app/pages/trade/contract.js b/src/javascript/app/pages/trade/contract.js index 008f380afc2..09f66cca820 100644 --- a/src/javascript/app/pages/trade/contract.js +++ b/src/javascript/app/pages/trade/contract.js @@ -126,7 +126,7 @@ const Contract = (() => { } barrier = form_barrier.barrier_category; - contracts.available.forEach((current_obj) => { + contracts?.available.forEach((current_obj) => { const contract_category = current_obj.contract_category; // for callput and callputequals, populate duration for both if (form === contract_category || (/callput/.test(form) && /callput/.test(contract_category))) { @@ -202,7 +202,7 @@ const Contract = (() => { const getContractCategories = () => { const contracts = Contract.contracts().contracts_for; const contract_categories = {}; - contracts.available.forEach((current_obj) => { + contracts?.available?.forEach((current_obj) => { const contract_category = current_obj.contract_category; const contract_barrier = current_obj.barrier_category; const contract_display = current_obj.contract_category_display; diff --git a/src/javascript/app/pages/trade/contracts.jsx b/src/javascript/app/pages/trade/contracts.jsx index 701eb49a5c7..cc0e4e251f8 100644 --- a/src/javascript/app/pages/trade/contracts.jsx +++ b/src/javascript/app/pages/trade/contracts.jsx @@ -1,12 +1,12 @@ import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; -import Defaults from './defaults'; +import { Divider, DropdownItem, DropdownTitle } from '@deriv-com/quill-ui'; +import Defaults, { PARAM_NAMES } from './defaults'; import { getElementById } from '../../../_common/common_functions'; -import { localize } from '../../../_common/localize'; import { triggerContractChange } from '../../hooks/events'; -class Contracts extends React.Component { +export class Contracts extends React.Component { constructor (props) { super(props); const { contracts, contracts_tree, selected } = props; @@ -38,31 +38,6 @@ class Contracts extends React.Component { } }; - openDropDown = () => { - if (this.state.contracts_tree.length < 1) return; - this.positionDropDown(); - this.setState({ open: true }); - }; - - closeDropDown = () => { - this.setState({ open: false }); - const el_dropdown = this.references.wrapper; - // reposition dropdown after the animation is finished. - setTimeout(() => el_dropdown.removeAttribute('style'), 500); - }; - - positionDropDown = () => { - const el_dropdown = this.references.wrapper; - const pos = el_dropdown.getBoundingClientRect(); - - if ((pos.x + pos.width + 10) > window.innerWidth) { - // 10 is padding right for the element - el_dropdown.style.left = `${window.innerWidth - (pos.x + pos.width + 10)}px`; - } else if ((pos.x + pos.width + 10) !== window.innerWidth) { - el_dropdown.removeAttribute('style'); - } - }; - saveRef = (name, node) => { this.references[name] = node; }; getCurrentType = () => { @@ -93,7 +68,6 @@ class Contracts extends React.Component { }; onContractClick = (formname) => { - this.closeDropDown(); if (formname === this.state.formname) { return; } // Notify for changes on contract. this.el_contract.value = formname; @@ -101,6 +75,10 @@ class Contracts extends React.Component { this.el_contract.dispatchEvent(event); this.setState({ formname }); + }; + + saveSelectedContract = (formname) => { + Defaults.set(PARAM_NAMES.CONTRACT_NAME,this.state.contracts[formname]); triggerContractChange(); }; @@ -110,65 +88,53 @@ class Contracts extends React.Component { const { contracts, contracts_tree, - open, formname, } = this.state; - const is_mobile = window.innerWidth <= 767; + return ( -
-
- - {this.getCurrentType()} - - - {this.getCurrentContract()} -
-
-
- {localize('Select Trade Type')} - -
-
- { contracts_tree.map((contract, idx) => { - if (typeof contract === 'object') { - return ( -
-
{contracts[contract[0]]}
-
- {contract[1].map((subtype, i) => -
- {contracts[subtype]} -
- )} -
-
- ); - } +
+
+ { contracts_tree.map((contract, idx) => { + if (typeof contract === 'object') { return ( -
-
{contracts[contract]}
-
-
- {contracts[contract]} -
-
-
+ + + {contract[1].map((subtype, i) => { + if (subtype === formname){ + this.saveSelectedContract(formname); + } + + return ( + ); + })} + + ); - })} -
+ } + + if (contract === formname){ + this.saveSelectedContract(formname); + } + + return ( + + + + + + ); + })}
); diff --git a/src/javascript/app/pages/trade/defaults.js b/src/javascript/app/pages/trade/defaults.js index 9e426969e07..fb8c0565593 100644 --- a/src/javascript/app/pages/trade/defaults.js +++ b/src/javascript/app/pages/trade/defaults.js @@ -40,6 +40,8 @@ const Defaults = (() => { SELECTED_TICK : 'selected_tick', TIME_START : 'time_start', UNDERLYING : 'underlying', + MARKET_NAME : 'market_name', + CONTRACT_NAME : 'contract_name', }; const getDefault = (key) => { const p_value = params[key] || Url.param(key); diff --git a/src/javascript/app/pages/trade/markets/market-selector.jsx b/src/javascript/app/pages/trade/markets/market-selector.jsx new file mode 100644 index 00000000000..73faa28d599 --- /dev/null +++ b/src/javascript/app/pages/trade/markets/market-selector.jsx @@ -0,0 +1,85 @@ +import React, { useEffect, useRef, useState } from 'react'; +import ReactDOM from 'react-dom'; +import { CustomDropdown } from '@deriv-com/quill-ui'; +import { MarketsDropdown, getMarketName } from './markets-dropdown.jsx'; +import { getElementById } from '../../../../_common/common_functions'; +import { localize } from '../../../../_common/localize'; +import { useContractChange, useMarketChange } from '../../../hooks/events.js'; +import Defaults, { PARAM_NAMES } from '../defaults.js'; + +const MarketSelector = () => { + const [marketLabel, setMarketLabel] = useState(getMarketName()); + const [isMarketDropdownOpen, setMarketDropdownOpen] = useState(false); + const [isContractDropdownOpen, setIsContractDropdownOpen] = useState(false); + const [tradeTypeLabel, setTradeTypeLabel] = useState(''); + const hasContractChange = useContractChange(); + const hasMarketChange = useMarketChange(); + + const contractDropdownRef = useRef(null); + + const handleOutsideClick = (event) => { + if ( + contractDropdownRef.current && + !contractDropdownRef.current.contains(event.target) + ) { + setIsContractDropdownOpen(false); + } + }; + + useEffect(() => { + document.addEventListener('mousedown', handleOutsideClick); + document.addEventListener('touchstart', handleOutsideClick); + + return () => { + document.removeEventListener('mousedown', handleOutsideClick); + document.removeEventListener('touchstart', handleOutsideClick); + }; + }, []); + + useEffect(() => { + const contractName = decodeURIComponent( + Defaults.get(PARAM_NAMES.CONTRACT_NAME) + ); + setTradeTypeLabel(contractName); + setIsContractDropdownOpen(false); + }, [hasContractChange]); + + useEffect(() => { + setMarketLabel(getMarketName()); + setMarketDropdownOpen(false); + }, [hasMarketChange]); + + return ( +
+ + + +
+ setIsContractDropdownOpen((e) => !e)} + /> +
+
+
+ ); +}; + +export const init = () => { + ReactDOM.render( + , + getElementById('markets-dropdown-container') + ); +}; + +export default init; diff --git a/src/javascript/app/pages/trade/markets/markets-dropdown.jsx b/src/javascript/app/pages/trade/markets/markets-dropdown.jsx index 00bf350801d..05f2e8c5ce1 100644 --- a/src/javascript/app/pages/trade/markets/markets-dropdown.jsx +++ b/src/javascript/app/pages/trade/markets/markets-dropdown.jsx @@ -1,17 +1,44 @@ import React, { useEffect, useRef, useState } from 'react'; -import ReactDOM from 'react-dom'; -import { DropdownItem, SearchField, Tab, Text } from '@deriv-com/quill-ui'; +import { + DropdownItem, + SearchField, + Tab, + DropdownTitle, + Divider, + useDropdown, +} from '@deriv-com/quill-ui'; import { getElementById } from '../../../../_common/common_functions'; import Symbols from '../symbols'; -import { +import ActiveSymbols, { marketOrder, sortObjectByKeys, derived, } from '../../../common/active_symbols'; import Defaults, { PARAM_NAMES } from '../defaults'; import { triggerMarketChange } from '../../../hooks/events'; +import { localize } from '../../../../_common/localize'; + +export const getMarketName = () => { + const obj = ActiveSymbols.getMarkets(); + const symbolKey = Defaults.get(PARAM_NAMES.UNDERLYING); + + // Find the market and submarket where the symbolKey exists + const marketKey = Object.keys(obj).find(market => + Object.keys(obj[market].submarkets).some(submarket => + obj[market].submarkets[submarket].symbols?.[symbolKey] + ) + ); + + // Return the display value if found + const displayValue = obj[marketKey]?.submarkets?.[Object.keys(obj[marketKey].submarkets).find(submarket => + obj[marketKey].submarkets[submarket].symbols?.[symbolKey] + )]?.symbols[symbolKey]?.display; -const MarketsDropdown = () => { + return displayValue || null; + +}; + +export const MarketsDropdown = () => { const { UNDERLYING } = PARAM_NAMES; const [defaultMarkets, setDefaultMarkets] = useState({}); const [markets, setMarkets] = useState({}); @@ -23,25 +50,28 @@ const MarketsDropdown = () => { const [searchKey, setSearchKey] = useState(''); const itemsContainer = useRef(null); const isScrolling = useRef(false); + const underlyings = Symbols.getAllSymbols() || {}; + + const { close: closeMarketDropdown } = useDropdown(); const filterMarkets = () => { const data = JSON.parse(JSON.stringify(defaultMarkets)); const searchStr = searchKey?.toLowerCase(); let foundMatchingSymbol = false; - Object.keys(data).forEach(marketKey => { + Object.keys(data).forEach((marketKey) => { const market = data[marketKey]; - Object.keys(market.submarkets).forEach(submarketKey => { + Object.keys(market.submarkets).forEach((submarketKey) => { const submarket = market.submarkets[submarketKey]; const subMarketName = submarket.name.toLowerCase(); // If submarket name matches to search key then don't filter children - if (!subMarketName.includes(searchStr)){ - Object.keys(submarket.symbols).forEach(symbolKey => { + if (!subMarketName.includes(searchStr)) { + Object.keys(submarket.symbols).forEach((symbolKey) => { const symbol = submarket.symbols[symbolKey]; const displayName = symbol.display.toLowerCase(); - + if (!displayName.includes(searchStr)) { delete submarket.symbols[symbolKey]; } else { @@ -56,23 +86,23 @@ const MarketsDropdown = () => { delete market.submarkets[submarketKey]; } }); - + if (Object.keys(market.submarkets).length === 0) { delete data[marketKey]; } }); - + // If no matching symbols were found, return the original markets object if (!foundMatchingSymbol) { return defaultMarkets; } - + return data; }; useEffect(() => { setMarkets(filterMarkets()); - },[searchKey]); + }, [searchKey]); useEffect(() => { const marketList = Symbols.markets(); @@ -92,7 +122,7 @@ const MarketsDropdown = () => { let closestOffset = Infinity; marketDivs.forEach((div) => { - const paddingOffset = 110; + const paddingOffset = 120; const offsetTop = div.offsetTop - container.scrollTop - paddingOffset; if (offsetTop <= 0 && Math.abs(offsetTop) < Math.abs(closestOffset)) { @@ -144,6 +174,15 @@ const MarketsDropdown = () => { Defaults.set(UNDERLYING, underlying); setSelectedMarket(underlying); triggerMarketChange(); + + // Old Trigger + const underlyingElement = getElementById('underlying'); + const event = new Event('change'); + underlyingElement.value = underlying; + underlyingElement.setAttribute('data-text', underlyings[underlying]); + underlyingElement.dispatchEvent(event); + + closeMarketDropdown(); }; return ( @@ -152,7 +191,7 @@ const MarketsDropdown = () => { setSearchKey(e.target.value)} - placeholder='Search...' + placeholder={localize('Search...')} value='' variant='fill' /> @@ -191,27 +230,23 @@ const MarketsDropdown = () => { return ( - { - - {name} - - } + {Object.keys(symbols).map((yk) => { const symbol = symbols[yk]; const { display } = symbol; - + const isSelected = yk === selectedMarket; + return ( handleUnderlyingClick(yk)} label={display} - selected={yk === selectedMarket} + selected={isSelected} size='sm' /> ); })} -
+
); })} @@ -224,11 +259,3 @@ const MarketsDropdown = () => { ); }; -export const init = () => { - ReactDOM.render( - , - getElementById('markets-dropdown-container') - ); -}; - -export default init; diff --git a/src/sass/_common/reskin.scss b/src/sass/_common/reskin.scss index a643f96a1c5..084ee6a4960 100644 --- a/src/sass/_common/reskin.scss +++ b/src/sass/_common/reskin.scss @@ -33,14 +33,42 @@ } +.market-navigation-container { + display: flex; + align-items: center; +} + +.quill-market-selector { + &-container { + display: flex; + flex-wrap: nowrap; + width: 100%; + position: relative; + z-index: 100; + + .contract-dropdown { + top: -999px; + + &-open { + top: unset; + } + } + } + &-dropdown { + width: 100%; + max-width: 360px; + margin: 0 10px 0 0; + } +} + .quill-market-dropdown { &-container { width: 360px; - padding-top: 8px; + padding: 8px 0 0; background: var(--core-color-solid-slate-50); margin: 10px 0px; border-radius: 8px; - border: 1px solid rgba(0, 0, 0, 0.04) + border: 1px solid rgba(0, 0, 0, 0.04); } &-search-container { padding: var(--core-spacing-400) var(--core-spacing-800); @@ -51,15 +79,19 @@ } } &-item-container { - height: 500px; + height: fit-content; + max-height: 300px; overflow-y: auto; padding: 0 var(--core-spacing-400); } - - } .explanation-container { margin-block-end: 50px; padding-block: 20px; +} + +div#contract_component { + position: absolute; + z-index: 100; } \ No newline at end of file diff --git a/src/sass/reset.scss b/src/sass/reset.scss index 7d351eccea9..b607ba16fb8 100644 --- a/src/sass/reset.scss +++ b/src/sass/reset.scss @@ -20,4 +20,8 @@ button { &:hover { background: initial; } +} + +input { + padding: initial; } \ No newline at end of file diff --git a/src/templates/app/trade/last_digit.jsx b/src/templates/app/trade/last_digit.jsx index 3694f817987..2b2195c43c0 100644 --- a/src/templates/app/trade/last_digit.jsx +++ b/src/templates/app/trade/last_digit.jsx @@ -1,15 +1,8 @@ import React from 'react'; import { Select } from '../../_common/components/elements.jsx'; -const { Button, Heading } = require('@deriv-com/quill-ui'); - const LastDigit = () => (
-