feat: [console-lite] - market selector (#726)
* feat: [console-lite] - market selector - first commit * feat: [console-lite] - market selector - add strongly different mobile version * feat: [console-lite] - market selector - fix deal-market lint fail * feat: [console-lite] - market selector - add a bunch of improvements * feat: [console-lite] - market selector - add some int tests * feat: [console-lite] - market selector - fix dialog dimmensions Co-authored-by: maciek <maciek@vegaprotocol.io>
This commit is contained in:
parent
73c059718b
commit
9cfcadd39b
@ -0,0 +1,87 @@
|
|||||||
|
describe('market selector', () => {
|
||||||
|
let markets;
|
||||||
|
before(() => {
|
||||||
|
cy.intercept('POST', '/query', (req) => {
|
||||||
|
const { body } = req;
|
||||||
|
if (body.operationName === 'SimpleMarkets') {
|
||||||
|
req.alias = `gqlSimpleMarketsQuery`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cy.visit('/markets');
|
||||||
|
cy.wait('@gqlSimpleMarketsQuery').then((response) => {
|
||||||
|
if (response.response?.body?.data?.markets?.length) {
|
||||||
|
markets = response.response?.body?.data?.markets;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be properly rendered', () => {
|
||||||
|
if (markets) {
|
||||||
|
cy.visit(`/trading/${markets[0].id}`);
|
||||||
|
cy.get('input[placeholder="Search"]').should(
|
||||||
|
'have.value',
|
||||||
|
markets[0].name
|
||||||
|
);
|
||||||
|
cy.getByTestId('arrow-button').click();
|
||||||
|
cy.getByTestId('market-pane').should('be.visible');
|
||||||
|
cy.getByTestId('market-pane')
|
||||||
|
.children()
|
||||||
|
.find('[role="button"]')
|
||||||
|
.should('contain.text', markets[0].name);
|
||||||
|
cy.getByTestId('market-pane').children().find('[role="button"]').click();
|
||||||
|
cy.getByTestId('market-pane').should('not.be.visible');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('typing should change list', () => {
|
||||||
|
if (markets) {
|
||||||
|
cy.visit(`/trading/${markets[0].id}`);
|
||||||
|
cy.get('input[placeholder="Search"]').type('{backspace}');
|
||||||
|
cy.getByTestId('market-pane')
|
||||||
|
.children()
|
||||||
|
.find('[role="button"]')
|
||||||
|
.should('have.length', 1);
|
||||||
|
cy.get('input[placeholder="Search"]').clear();
|
||||||
|
cy.get('input[placeholder="Search"]').type('a');
|
||||||
|
const filtered = markets.filter((market) => market.name.match(/a/i));
|
||||||
|
cy.getByTestId('market-pane')
|
||||||
|
.children()
|
||||||
|
.find('[role="button"]')
|
||||||
|
.should('have.length', filtered.length);
|
||||||
|
cy.getByTestId('market-pane')
|
||||||
|
.children()
|
||||||
|
.find('[role="button"]')
|
||||||
|
.last()
|
||||||
|
.click();
|
||||||
|
cy.location('pathname').should(
|
||||||
|
'eq',
|
||||||
|
`/trading/${filtered[filtered.length - 1].id}`
|
||||||
|
);
|
||||||
|
cy.get('input[placeholder="Search"]').should(
|
||||||
|
'have.value',
|
||||||
|
filtered[filtered.length - 1].name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('mobile view', () => {
|
||||||
|
if (markets) {
|
||||||
|
cy.viewport('iphone-xr');
|
||||||
|
cy.visit(`/trading/${markets[0].id}`);
|
||||||
|
cy.get('[role="dialog"]').should('not.exist');
|
||||||
|
cy.getByTestId('arrow-button').click();
|
||||||
|
cy.get('[role="dialog"]').should('be.visible');
|
||||||
|
cy.get('input[placeholder="Search"]').clear();
|
||||||
|
cy.getByTestId('market-pane')
|
||||||
|
.children()
|
||||||
|
.find('[role="button"]')
|
||||||
|
.should('have.length', markets.length);
|
||||||
|
cy.pause();
|
||||||
|
cy.getByTestId('dialog-close').click();
|
||||||
|
cy.get('input[placeholder="Search"]').should(
|
||||||
|
'have.value',
|
||||||
|
markets[0].name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -13,6 +13,7 @@ import {
|
|||||||
useOrderValidation,
|
useOrderValidation,
|
||||||
useOrderSubmit,
|
useOrderSubmit,
|
||||||
DealTicketAmount,
|
DealTicketAmount,
|
||||||
|
MarketSelector,
|
||||||
} from '@vegaprotocol/deal-ticket';
|
} from '@vegaprotocol/deal-ticket';
|
||||||
import {
|
import {
|
||||||
OrderTimeInForce,
|
OrderTimeInForce,
|
||||||
@ -20,12 +21,23 @@ import {
|
|||||||
VegaTxStatus,
|
VegaTxStatus,
|
||||||
} from '@vegaprotocol/wallet';
|
} from '@vegaprotocol/wallet';
|
||||||
import { t, addDecimal, toDecimal } from '@vegaprotocol/react-helpers';
|
import { t, addDecimal, toDecimal } from '@vegaprotocol/react-helpers';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import MarketNameRenderer from '../simple-market-list/simple-market-renderer';
|
||||||
|
|
||||||
interface DealTicketMarketProps {
|
interface DealTicketMarketProps {
|
||||||
market: DealTicketQuery_market;
|
market: DealTicketQuery_market;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const setMarket = useCallback(
|
||||||
|
(marketId) => {
|
||||||
|
navigate(`/trading/${marketId}`);
|
||||||
|
},
|
||||||
|
[navigate]
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
control,
|
control,
|
||||||
@ -70,7 +82,13 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
|||||||
{
|
{
|
||||||
label: 'Select Asset',
|
label: 'Select Asset',
|
||||||
description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`,
|
description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`,
|
||||||
component: <h1 className="font-bold mb-16">{market.name}</h1>,
|
component: (
|
||||||
|
<MarketSelector
|
||||||
|
market={market}
|
||||||
|
setMarket={setMarket}
|
||||||
|
ItemRenderer={MarketNameRenderer}
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Select Order Type',
|
label: 'Select Order Type',
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { SimpleMarkets_markets } from './__generated__/SimpleMarkets';
|
import type { MarketNames_markets } from '@vegaprotocol/deal-ticket';
|
||||||
import SimpleMarketExpires from './simple-market-expires';
|
import SimpleMarketExpires from './simple-market-expires';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: SimpleMarkets_markets;
|
market: MarketNames_markets;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MarketNameRenderer = ({ data }: Props) => {
|
const MarketNameRenderer = ({ market }: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full items-center grid grid-rows-2 grid-flow-col gap-x-8 gap-y-0 grid-cols-[min-content,1fr,1fr]">
|
<div className="flex h-full items-center grid grid-rows-2 grid-flow-col gap-x-8 gap-y-0 grid-cols-[min-content,1fr,1fr]">
|
||||||
<div className="w-60 row-span-2 bg-pink rounded-full w-44 h-44 bg-gradient-to-br from-white-60 to--white-80 opacity-30" />
|
<div className="w-60 row-span-2 bg-pink rounded-full w-44 h-44 bg-gradient-to-br from-white-60 to--white-80 opacity-30" />
|
||||||
<div className="col-span-2 uppercase justify-start text-black dark:text-white text-market self-end">
|
<div className="col-span-2 uppercase justify-start text-black dark:text-white text-market self-end">
|
||||||
{data.name}{' '}
|
{market.name}{' '}
|
||||||
<SimpleMarketExpires
|
<SimpleMarketExpires
|
||||||
tags={data.tradableInstrument.instrument.metadata.tags}
|
tags={market.tradableInstrument.instrument.metadata.tags}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-2 ui-small text-deemphasise dark:text-midGrey self-start leading-3">
|
<div className="col-span-2 text-ui-small text-deemphasise dark:text-midGrey self-start leading-3">
|
||||||
{data.tradableInstrument.instrument.product.quoteName}
|
{market.tradableInstrument.instrument.product.quoteName}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -21,7 +21,7 @@ const useColumnDefinitions = ({ onClick }: Props) => {
|
|||||||
minWidth: 300,
|
minWidth: 300,
|
||||||
field: 'name',
|
field: 'name',
|
||||||
cellRenderer: ({ data }: { data: SimpleMarketsType }) => (
|
cellRenderer: ({ data }: { data: SimpleMarketsType }) => (
|
||||||
<MarketNameRenderer data={data} />
|
<MarketNameRenderer market={data} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,7 @@ const { join } = require('path');
|
|||||||
const { createGlobPatternsForDependencies } = require('@nrwl/next/tailwind');
|
const { createGlobPatternsForDependencies } = require('@nrwl/next/tailwind');
|
||||||
const theme = require('../../libs/tailwindcss-config/src/theme-lite');
|
const theme = require('../../libs/tailwindcss-config/src/theme-lite');
|
||||||
const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes');
|
const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes');
|
||||||
|
const vegaCustomClassesLite = require('../../libs/tailwindcss-config/src/vega-custom-classes-lite');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: [
|
content: [
|
||||||
@ -11,5 +12,5 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
darkMode: 'class',
|
darkMode: 'class',
|
||||||
theme,
|
theme,
|
||||||
plugins: [vegaCustomClasses],
|
plugins: [vegaCustomClasses, vegaCustomClassesLite],
|
||||||
};
|
};
|
||||||
|
67
libs/deal-ticket/src/components/__generated__/MarketNames.ts
generated
Normal file
67
libs/deal-ticket/src/components/__generated__/MarketNames.ts
generated
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL query operation: MarketNames
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface MarketNames_markets_tradableInstrument_instrument_metadata {
|
||||||
|
__typename: "InstrumentMetadata";
|
||||||
|
/**
|
||||||
|
* An arbitrary list of tags to associated to associate to the Instrument (string list)
|
||||||
|
*/
|
||||||
|
tags: string[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarketNames_markets_tradableInstrument_instrument_product {
|
||||||
|
__typename: "Future";
|
||||||
|
/**
|
||||||
|
* String representing the quote (e.g. BTCUSD -> USD is quote)
|
||||||
|
*/
|
||||||
|
quoteName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarketNames_markets_tradableInstrument_instrument {
|
||||||
|
__typename: "Instrument";
|
||||||
|
/**
|
||||||
|
* Metadata for this instrument
|
||||||
|
*/
|
||||||
|
metadata: MarketNames_markets_tradableInstrument_instrument_metadata;
|
||||||
|
/**
|
||||||
|
* A reference to or instance of a fully specified product, including all required product parameters for that product (Product union)
|
||||||
|
*/
|
||||||
|
product: MarketNames_markets_tradableInstrument_instrument_product;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarketNames_markets_tradableInstrument {
|
||||||
|
__typename: "TradableInstrument";
|
||||||
|
/**
|
||||||
|
* An instance of or reference to a fully specified instrument.
|
||||||
|
*/
|
||||||
|
instrument: MarketNames_markets_tradableInstrument_instrument;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarketNames_markets {
|
||||||
|
__typename: "Market";
|
||||||
|
/**
|
||||||
|
* Market ID
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* Market full name
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* An instance of or reference to a tradable instrument.
|
||||||
|
*/
|
||||||
|
tradableInstrument: MarketNames_markets_tradableInstrument;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarketNames {
|
||||||
|
/**
|
||||||
|
* One or more instruments that are trading on the VEGA network
|
||||||
|
*/
|
||||||
|
markets: MarketNames_markets[] | null;
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
export * from './DealTicketQuery';
|
export * from './DealTicketQuery';
|
||||||
export * from './MarketInfoQuery';
|
export * from './MarketInfoQuery';
|
||||||
|
export * from './MarketNames';
|
||||||
|
@ -10,3 +10,4 @@ export * from './info-market';
|
|||||||
export * from './side-selector';
|
export * from './side-selector';
|
||||||
export * from './time-in-force-selector';
|
export * from './time-in-force-selector';
|
||||||
export * from './type-selector';
|
export * from './type-selector';
|
||||||
|
export * from './market-selector';
|
||||||
|
292
libs/deal-ticket/src/components/market-selector.tsx
Normal file
292
libs/deal-ticket/src/components/market-selector.tsx
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useMemo,
|
||||||
|
} from 'react';
|
||||||
|
import { gql, useQuery } from '@apollo/client';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import type { DealTicketQuery_market } from './__generated__';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
Icon,
|
||||||
|
Input,
|
||||||
|
Loader,
|
||||||
|
Splash,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import {
|
||||||
|
t,
|
||||||
|
useScreenDimensions,
|
||||||
|
useOutsideClick,
|
||||||
|
} from '@vegaprotocol/react-helpers';
|
||||||
|
import type {
|
||||||
|
MarketNames,
|
||||||
|
MarketNames_markets,
|
||||||
|
} from './__generated__/MarketNames';
|
||||||
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
|
|
||||||
|
export const MARKET_NAMES_QUERY = gql`
|
||||||
|
query MarketNames {
|
||||||
|
markets {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
tradableInstrument {
|
||||||
|
instrument {
|
||||||
|
metadata {
|
||||||
|
tags
|
||||||
|
}
|
||||||
|
product {
|
||||||
|
... on Future {
|
||||||
|
quoteName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
market: DealTicketQuery_market;
|
||||||
|
setMarket: (marketId: string) => void;
|
||||||
|
ItemRenderer?: React.FC<{ market: MarketNames_markets }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeRegExp(str: string) {
|
||||||
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MarketSelector = ({ market, setMarket, ItemRenderer }: Props) => {
|
||||||
|
const { isMobile } = useScreenDimensions();
|
||||||
|
const contRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
const arrowButtonRef = useRef<HTMLButtonElement | null>(null);
|
||||||
|
const [skip, setSkip] = useState(true);
|
||||||
|
const [results, setResults] = useState<MarketNames_markets[]>([]);
|
||||||
|
const [showPane, setShowPane] = useState(false);
|
||||||
|
const [lookup, setLookup] = useState(market.name || '');
|
||||||
|
const [dialogContent, setDialogContent] = useState<React.ReactNode | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data, loading, error } = useQuery<MarketNames>(MARKET_NAMES_QUERY, {
|
||||||
|
skip,
|
||||||
|
});
|
||||||
|
|
||||||
|
const outsideClickCb = useCallback(() => {
|
||||||
|
if (!isMobile) {
|
||||||
|
setShowPane(false);
|
||||||
|
}
|
||||||
|
}, [setShowPane, isMobile]);
|
||||||
|
|
||||||
|
useOutsideClick({ refs: [contRef, arrowButtonRef], func: outsideClickCb });
|
||||||
|
|
||||||
|
const handleOnChange = useCallback(
|
||||||
|
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const {
|
||||||
|
target: { value },
|
||||||
|
} = event;
|
||||||
|
setLookup(value);
|
||||||
|
setShowPane(true);
|
||||||
|
if (value) {
|
||||||
|
setSkip(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setLookup, setShowPane, setSkip]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleMarketSelect = useCallback(
|
||||||
|
({ id, name }) => {
|
||||||
|
setLookup(name);
|
||||||
|
setShowPane(false);
|
||||||
|
setMarket(id);
|
||||||
|
inputRef.current?.focus();
|
||||||
|
},
|
||||||
|
[setLookup, setShowPane, setMarket, inputRef]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleItemKeyDown = useCallback(
|
||||||
|
(
|
||||||
|
event: React.KeyboardEvent,
|
||||||
|
market: MarketNames_markets,
|
||||||
|
index: number
|
||||||
|
) => {
|
||||||
|
switch (event.key) {
|
||||||
|
case 'ArrowDown':
|
||||||
|
if (index < results.length - 1) {
|
||||||
|
(contRef.current?.children[index + 1] as HTMLDivElement).focus();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArrowUp':
|
||||||
|
if (!index) {
|
||||||
|
inputRef.current?.focus();
|
||||||
|
inputRef.current?.setSelectionRange(
|
||||||
|
inputRef.current?.value.length,
|
||||||
|
inputRef.current?.value.length
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(contRef.current?.children[index - 1] as HTMLDivElement).focus();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Enter':
|
||||||
|
handleMarketSelect(market);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[results, handleMarketSelect]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleInputKeyDown = useCallback(
|
||||||
|
(event: React.KeyboardEvent) => {
|
||||||
|
if (event.key === 'ArrowDown') {
|
||||||
|
(contRef.current?.children[0] as HTMLDivElement).focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[contRef]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOnBlur = useCallback(() => {
|
||||||
|
console.log('lookup, showPane', lookup, showPane);
|
||||||
|
if (!lookup && !showPane) {
|
||||||
|
console.log(
|
||||||
|
'2 lookup, showPane, market.name',
|
||||||
|
lookup,
|
||||||
|
showPane,
|
||||||
|
market.name
|
||||||
|
);
|
||||||
|
setLookup(market.name);
|
||||||
|
}
|
||||||
|
}, [market, lookup, showPane, setLookup]);
|
||||||
|
|
||||||
|
const openPane = useCallback(() => {
|
||||||
|
setShowPane(!showPane);
|
||||||
|
setSkip(false);
|
||||||
|
inputRef.current?.focus();
|
||||||
|
}, [showPane, setShowPane, setSkip, inputRef]);
|
||||||
|
|
||||||
|
const handleDialogOnchange = useCallback(
|
||||||
|
(isOpen) => {
|
||||||
|
setShowPane(isOpen);
|
||||||
|
if (!isOpen) {
|
||||||
|
setLookup(lookup || market.name);
|
||||||
|
inputRef.current?.focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setShowPane, lookup, setLookup, market.name, inputRef]
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectorContent = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<div className="relative flex flex-col">
|
||||||
|
<div className="relative w-full min-h-[30px] dark:bg-black">
|
||||||
|
<Input
|
||||||
|
className="h-[30px] w-[calc(100%-20px)] border-none dark:bg-black"
|
||||||
|
ref={inputRef}
|
||||||
|
tabIndex={0}
|
||||||
|
value={lookup}
|
||||||
|
placeholder={t('Search')}
|
||||||
|
onChange={handleOnChange}
|
||||||
|
onKeyDown={handleInputKeyDown}
|
||||||
|
onBlur={handleOnBlur}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className="absolute self-end top-[7px] right-0 z-10"
|
||||||
|
variant="inline-link"
|
||||||
|
onClick={openPane}
|
||||||
|
ref={arrowButtonRef}
|
||||||
|
data-testid="arrow-button"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name={IconNames.ARROW_DOWN}
|
||||||
|
className={classNames('fill-current transition-transform', {
|
||||||
|
'rotate-180': showPane,
|
||||||
|
})}
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<hr className="md:hidden mb-5" />
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'md:absolute flex flex-col top-[30px] z-10 md:drop-shadow-md md:border-1 md:border-black md:dark:border-white bg-white dark:bg-black text-black dark:text-white min-w-full md:max-h-[200px] overflow-y-auto',
|
||||||
|
showPane ? 'block' : 'hidden'
|
||||||
|
)}
|
||||||
|
data-testid="market-pane"
|
||||||
|
>
|
||||||
|
{loading && <Loader />}
|
||||||
|
{error && (
|
||||||
|
<Splash>{t(`Something went wrong: ${error.message}`)}</Splash>
|
||||||
|
)}
|
||||||
|
<div ref={contRef} className="w-full">
|
||||||
|
{results.map((market, i) => (
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
key={market.id}
|
||||||
|
className="cursor-pointer focus:bg-white-95 focus:outline-0 dark:focus:bg-black-80 px-20 py-5"
|
||||||
|
onClick={() => handleMarketSelect(market)}
|
||||||
|
onKeyDown={(e) => handleItemKeyDown(e, market, i)}
|
||||||
|
>
|
||||||
|
{ItemRenderer ? <ItemRenderer market={market} /> : market.name}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [
|
||||||
|
ItemRenderer,
|
||||||
|
error,
|
||||||
|
handleInputKeyDown,
|
||||||
|
handleItemKeyDown,
|
||||||
|
handleMarketSelect,
|
||||||
|
handleOnBlur,
|
||||||
|
handleOnChange,
|
||||||
|
loading,
|
||||||
|
lookup,
|
||||||
|
openPane,
|
||||||
|
results,
|
||||||
|
showPane,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setResults(
|
||||||
|
data?.markets?.filter((item: MarketNames_markets) =>
|
||||||
|
item.name.match(new RegExp(escapeRegExp(lookup), 'i'))
|
||||||
|
) || []
|
||||||
|
);
|
||||||
|
}, [data, lookup]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
inputRef.current?.focus();
|
||||||
|
}, [inputRef]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showPane && isMobile) {
|
||||||
|
setDialogContent(selectorContent);
|
||||||
|
inputRef.current?.focus();
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
} else {
|
||||||
|
setDialogContent(null);
|
||||||
|
}
|
||||||
|
}, [selectorContent, showPane, isMobile, setDialogContent]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!dialogContent && selectorContent}
|
||||||
|
<Dialog
|
||||||
|
titleClassNames="uppercase font-alpha"
|
||||||
|
contentClassNames="left-[0px] top-[99px] h-[calc(100%-99px)] border-0 translate-x-[0] translate-y-[0] border-none overflow-y-auto"
|
||||||
|
title={t('Select Market')}
|
||||||
|
open={Boolean(dialogContent)}
|
||||||
|
onChange={handleDialogOnchange}
|
||||||
|
>
|
||||||
|
{dialogContent}
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -3,3 +3,5 @@ export * from './use-data-provider';
|
|||||||
export * from './use-theme-switcher';
|
export * from './use-theme-switcher';
|
||||||
export * from './use-fetch';
|
export * from './use-fetch';
|
||||||
export * from './use-resize';
|
export * from './use-resize';
|
||||||
|
export * from './use-outside-click';
|
||||||
|
export * from './use-screen-dimensions';
|
||||||
|
27
libs/react-helpers/src/hooks/use-outside-click.ts
Normal file
27
libs/react-helpers/src/hooks/use-outside-click.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import type { RefObject } from 'react';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
refs: RefObject<HTMLElement>[];
|
||||||
|
func: (event: Event) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useOutsideClick = ({ refs, func }: Props) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: Event) => {
|
||||||
|
const found = refs.reduce((agg: boolean, item) => {
|
||||||
|
if (item.current && item.current.contains(event.target as Node)) {
|
||||||
|
agg = true;
|
||||||
|
}
|
||||||
|
return agg;
|
||||||
|
}, false);
|
||||||
|
if (!found) {
|
||||||
|
func(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, [refs, func]);
|
||||||
|
};
|
28
libs/react-helpers/src/hooks/use-screen-dimensions.ts
Normal file
28
libs/react-helpers/src/hooks/use-screen-dimensions.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { theme } from '@vegaprotocol/tailwindcss-config';
|
||||||
|
import { useResize } from './use-resize';
|
||||||
|
|
||||||
|
type Screen = keyof typeof theme.screens;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isMobile: boolean;
|
||||||
|
screen: Screen;
|
||||||
|
width: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useScreenDimensions = (): Props => {
|
||||||
|
const { width } = useResize();
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
width,
|
||||||
|
isMobile: width < parseInt(theme.screens.md),
|
||||||
|
screen: Object.entries(theme.screens).reduce((agg: Screen, entry) => {
|
||||||
|
if (width > parseInt(entry[1])) {
|
||||||
|
agg = entry[0] as Screen;
|
||||||
|
}
|
||||||
|
return agg;
|
||||||
|
}, 'xs'),
|
||||||
|
}),
|
||||||
|
[width]
|
||||||
|
);
|
||||||
|
};
|
@ -33,5 +33,11 @@ module.exports = {
|
|||||||
...theme.boxShadow,
|
...theme.boxShadow,
|
||||||
'inset-black': '',
|
'inset-black': '',
|
||||||
'inset-white': '',
|
'inset-white': '',
|
||||||
|
input: 'none',
|
||||||
|
'input-focus': 'none',
|
||||||
|
'input-dark': 'none',
|
||||||
|
'input-focus-dark': 'none',
|
||||||
|
'input-focus-error': 'none',
|
||||||
|
'input-focus-error-dark': 'none',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
17
libs/tailwindcss-config/src/vega-custom-classes-lite.js
Normal file
17
libs/tailwindcss-config/src/vega-custom-classes-lite.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const plugin = require('tailwindcss/plugin');
|
||||||
|
|
||||||
|
const vegaCustomClassesLite = plugin(function ({ addUtilities }) {
|
||||||
|
addUtilities({
|
||||||
|
'.input-border': {
|
||||||
|
borderWidth: '0',
|
||||||
|
},
|
||||||
|
'.input-border-dark': {
|
||||||
|
borderWidth: '0',
|
||||||
|
},
|
||||||
|
'.shadow-input': {
|
||||||
|
boxShadow: 'none',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = vegaCustomClassesLite;
|
Loading…
Reference in New Issue
Block a user