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,
|
||||
useOrderSubmit,
|
||||
DealTicketAmount,
|
||||
MarketSelector,
|
||||
} from '@vegaprotocol/deal-ticket';
|
||||
import {
|
||||
OrderTimeInForce,
|
||||
@ -20,12 +21,23 @@ import {
|
||||
VegaTxStatus,
|
||||
} from '@vegaprotocol/wallet';
|
||||
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 {
|
||||
market: DealTicketQuery_market;
|
||||
}
|
||||
|
||||
export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||
const navigate = useNavigate();
|
||||
const setMarket = useCallback(
|
||||
(marketId) => {
|
||||
navigate(`/trading/${marketId}`);
|
||||
},
|
||||
[navigate]
|
||||
);
|
||||
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
@ -70,7 +82,13 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||
{
|
||||
label: 'Select Asset',
|
||||
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',
|
||||
|
@ -1,23 +1,23 @@
|
||||
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';
|
||||
|
||||
interface Props {
|
||||
data: SimpleMarkets_markets;
|
||||
market: MarketNames_markets;
|
||||
}
|
||||
|
||||
const MarketNameRenderer = ({ data }: Props) => {
|
||||
const MarketNameRenderer = ({ market }: Props) => {
|
||||
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="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">
|
||||
{data.name}{' '}
|
||||
{market.name}{' '}
|
||||
<SimpleMarketExpires
|
||||
tags={data.tradableInstrument.instrument.metadata.tags}
|
||||
tags={market.tradableInstrument.instrument.metadata.tags}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 ui-small text-deemphasise dark:text-midGrey self-start leading-3">
|
||||
{data.tradableInstrument.instrument.product.quoteName}
|
||||
<div className="col-span-2 text-ui-small text-deemphasise dark:text-midGrey self-start leading-3">
|
||||
{market.tradableInstrument.instrument.product.quoteName}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -21,7 +21,7 @@ const useColumnDefinitions = ({ onClick }: Props) => {
|
||||
minWidth: 300,
|
||||
field: 'name',
|
||||
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 theme = require('../../libs/tailwindcss-config/src/theme-lite');
|
||||
const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes');
|
||||
const vegaCustomClassesLite = require('../../libs/tailwindcss-config/src/vega-custom-classes-lite');
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
@ -11,5 +12,5 @@ module.exports = {
|
||||
],
|
||||
darkMode: 'class',
|
||||
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 './MarketInfoQuery';
|
||||
export * from './MarketNames';
|
||||
|
@ -10,3 +10,4 @@ export * from './info-market';
|
||||
export * from './side-selector';
|
||||
export * from './time-in-force-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-fetch';
|
||||
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,
|
||||
'inset-black': '',
|
||||
'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