feat(#447): 447 - UI toolkit and theme updates

* feat: 447 Refactored 'progress' intent to be 'prompt' as now white. Added yellow 'selected' intent

* feat: 447 Colour consolidation

* feat: 447 Colour consolidation extra renaming

* feat: 447 Fixing specified red colours

* feat: 447 Removed unused darker red

* feat: 447 Documenting additional colours in storybook

* feat: 447 Buttons updated (except 'accent', which will probably get removed when navs built)

* feat: 447 Text inputs updated

* feat:frontend-monorepo-447: Trading nav

* feat:frontend-monorepo-447: Updated toggle button colours

* feat:frontend-monorepo-447: Custom checkboxes

* feat:frontend-monorepo-447: Tweaks to radio buttons

* feat:frontend-monorepo-447: Input dates get dark color scheme in dark mode

* feat:frontend-monorepo-447: Dropdown updates

* feat:frontend-monorepo-447: Icon menu

* feat:frontend-monorepo-447: Focus visual styles moved to focus-visible for radios and toggle

* feat:frontend-monorepo-447: Tweak to focus styles for text input and textarea

* feat:frontend-monorepo-447: Labeled input

* feat:frontend-monorepo-447: Labeled input description red when in error

* feat:frontend-monorepo-447: Tooltip visual update

* feat:frontend-monorepo-447: Added disabled state to checkbox

* feat:frontend-monorepo-447: Custom select with radix

* feat:frontend-monorepo-447: Reverted back to native Select for a11y concerns

* feat:frontend-monorepo-447: Added visual cue for dropdown items when multiple can be selected

* feat:frontend-monorepo-447: Removed shadow from buttons in Explorer where it looked wrong

* feat:frontend-monorepo-447: Added box shadow classes into tailwind theme

* feat:frontend-monorepo-447: Colour primitives documentation updated

* feat:frontend-monorepo-447: Cleaning up box shadow use further

* feat:frontend-monorepo-447: Intents util updated

* feat:frontend-monorepo-447: Dialog component updated

* feat:frontend-monorepo-447: Callout component updated

* feat:frontend-monorepo-447: Adjusted apps to handle toolkit changes

* feat:frontend-monorepo-447: Moved tabs to ui-toolkit and styled

* feat:frontend-monorepo-447: Fixed ui-toolkit tests

* feat:frontend-monorepo-447: Token eth wallet made dark to support new buttons

* feat:frontend-monorepo-447: Ran prettier

* frontend-monorepo-447: Simplified button class functions and exported for use on other elements

* frontend-monorepo-447: Used newly exported button classes on Link elements in eth-wallet

* frontend-monorepo-447: Moved trading nav from ui-toolkit to trading app

* frontend-monorepo-447: Simplified intents and updated stories

* frontend-monorepo-447: Using classnames in requested spot

* frontend-monorepo-447: Removed unnecessary 'asChild' prop on dropdown triggers

* frontend-monorepo-447: Made use of the XPrimitive Radix naming convention

* frontend-monorepo-447: Simplified types in 'getButtonClasses'

* frontend-monorepo-447: Added 'asChild' to dropdown trigger to avoid nested buttons

* frontend-monorepo-447: Moved input label and description into Formgroup component. Refactored based on tweaked structure

* frontend-monorepo-447: Externally linked input label

* frontend-monorepo-447: Adding correct text colours to Intent.None backgrounds

* frontend-monorepo-447: Improved intent function name

* frontend-monorepo-447: Removed new navbar until implementation ticket is picked up

* frontend-monorepo-447: using testing-library/user-event for tab click unit tests

* frontend-monorepo-447: Removed unused button import

* frontend-monorepo-447: Little extra use of classnames in form-group.tsx

* feat: make navbar pink for light mode

* fix: problem with theme not switching when dependent in js on theme value

* fix: bg of row hover

* fix: dont use vega pink for sell red

* fix: type error in generate orders func

* fix: lint

Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
Sam Keen 2022-06-24 04:16:01 +01:00 committed by GitHub
parent 4c5a026a03
commit 2302ef4378
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 1047 additions and 526 deletions

View File

@ -36,7 +36,12 @@ export const JumpTo = ({
placeholder={placeholder}
className="max-w-[200px]"
/>
<Button data-testid="go-submit" variant="secondary" type="submit">
<Button
data-testid="go-submit"
variant="secondary"
boxShadow={false}
type="submit"
>
{t('Go')}
</Button>
</div>

View File

@ -75,7 +75,12 @@ export const Search = () => {
</InputError>
)}
</FormGroup>
<Button type="submit" variant="secondary" data-testid="search-button">
<Button
type="submit"
boxShadow={false}
variant="secondary"
data-testid="search-button"
>
{t('Search')}
</Button>
</form>

View File

@ -35,7 +35,7 @@ export const DrawerToggle = ({
}, [variant]);
return (
<Button variant="inline" className={classes} onClick={onToggle}>
<Button variant="inline-link" className={classes} onClick={onToggle}>
<Icon name={iconName as IconName} />
</Button>
);

View File

@ -101,7 +101,7 @@ const SimpleMarketList = () => {
<div className="absolute right-16 top-1/2 -translate-y-1/2">
<Button
onClick={() => onClick(market.id)}
variant="inline"
variant="inline-link"
prependIconName="chevron-right"
/>
</div>

View File

@ -70,7 +70,7 @@ export default ({ steps }: StepperProps) => {
{index === steps.length - 1 ? 'Finish' : 'Continue'}
</Button>
<Button
variant="inline"
variant="inline-link"
disabled={index === 0}
onClick={handleBack}
>

View File

@ -2,6 +2,7 @@ import { useWeb3React } from '@web3-react/core';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { getButtonClasses, Button } from '@vegaprotocol/ui-toolkit';
import {
AppStateActionType,
@ -70,7 +71,7 @@ const AssociatedAmounts = ({
rightLabel={t('notAssociated')}
leftColor={Colors.white.DEFAULT}
rightColor={Colors.black.DEFAULT}
light={true}
light={false}
/>
{vestingAssociationByVegaKey.length ? (
<>
@ -129,6 +130,7 @@ const ConnectedKey = () => {
name="VEGA"
symbol="In vesting contract"
balance={totalInVestingContract}
dark={true}
/>
<LockedProgress
locked={totalLockedBalance}
@ -136,7 +138,7 @@ const ConnectedKey = () => {
total={totalVestedBalance.plus(totalLockedBalance)}
leftLabel={t('Locked')}
rightLabel={t('Unlocked')}
light={true}
light={false}
/>
</>
)}
@ -153,6 +155,7 @@ const ConnectedKey = () => {
name="VEGA"
symbol="In Wallet"
balance={walletWithAssociations}
dark={true}
/>
{!Object.keys(
appState.associationBreakdown.stakingAssociations
@ -163,15 +166,17 @@ const ConnectedKey = () => {
/>
)}
<WalletCardActions>
<Link className="flex-1" to={`${Routes.STAKING}/associate`}>
<span className="flex items-center justify-center w-full text-center px-28 border h-28">
{t('associate')}
</span>
<Link
className={getButtonClasses('flex-1 mr-4', 'secondary')}
to={`${Routes.STAKING}/associate`}
>
{t('associate')}
</Link>
<Link className="flex-1" to={`${Routes.STAKING}/disassociate`}>
<span className="flex items-center justify-center w-full px-28 border h-28">
{t('disassociate')}
</span>
<Link
className={getButtonClasses('flex-1 ml-4', 'secondary')}
to={`${Routes.STAKING}/disassociate`}
>
{t('disassociate')}
</Link>
</WalletCardActions>
</>
@ -185,7 +190,7 @@ export const EthWallet = () => {
const pendingTxs = usePendingTransactions();
return (
<WalletCard>
<WalletCard dark={true}>
<WalletCardHeader>
<h1 className="text-h3 uppercase">{t('ethereumKey')}</h1>
{account && (
@ -203,7 +208,7 @@ export const EthWallet = () => {
})
}
>
<Loader size="small" forceTheme="light" />
<Loader size="small" forceTheme="dark" />
{t('pendingTransactions')}
</button>
</div>
@ -215,7 +220,8 @@ export const EthWallet = () => {
{account ? (
<ConnectedKey />
) : (
<button
<Button
variant={'secondary'}
className="w-full px-28 border h-28"
onClick={() =>
appDispatch({
@ -226,7 +232,7 @@ export const EthWallet = () => {
data-test-id="connect-to-eth-wallet-button"
>
{t('connectEthWalletToAssociate')}
</button>
</Button>
)}
{account && (
<WalletCardActions>

View File

@ -1,8 +1,8 @@
import React from 'react';
import { formatNumber } from '../../lib/format-number';
import type { BigNumber } from '../../lib/bignumber';
import { theme } from '@vegaprotocol/tailwindcss-config';
import classnames from 'classnames';
const Colors = theme.colors;
@ -14,9 +14,10 @@ const ProgressContents = ({
children: React.ReactNode;
}) => (
<div
className={`flex justify-between py-2 font-mono ${
light ? 'gap-0 px-0 text-black' : 'gap-y-0 gap-x-4 px-4'
}`}
className={classnames('flex justify-between py-2 font-mono', {
'gap-0 px-0 text-black': light,
'gap-y-0 gap-x-4': !light,
})}
>
{children}
</div>
@ -25,17 +26,22 @@ const ProgressContents = ({
const ProgressIndicator = ({
bgColor,
side,
light,
}: {
bgColor: string;
side: 'left' | 'right';
light: boolean;
}) => (
<span
style={{
backgroundColor: bgColor,
}}
className={`inline-block w-12 h-12 border border-black ${
side === 'left' ? 'mr-8' : 'ml-8'
}`}
className={classnames('inline-block w-12 h-12 border', {
'mr-8': side === 'left',
'ml-8': side === 'right',
'border-black': light,
'border-white': !light,
})}
/>
);
@ -73,7 +79,7 @@ export const LockedProgress = ({
leftLabel,
rightLabel,
leftColor = Colors.vega.pink,
rightColor = Colors.green.DEFAULT,
rightColor = Colors.vega.green,
light = false,
}: LockedProgressProps) => {
const lockedPercentage = React.useMemo(() => {
@ -85,19 +91,28 @@ export const LockedProgress = ({
}, [total, unlocked]);
return (
<div className="border-x border-x-white">
<div className={`flex ${light && 'border border-black'}`}>
<>
<div
className={classnames('flex border', {
'border-black': light,
'border-white': !light,
})}
>
<ProgressBar percentage={lockedPercentage} bgColor={leftColor} />
<ProgressBar percentage={unlockedPercentage} bgColor={rightColor} />
</div>
<ProgressContents light={light}>
<span>
<ProgressIndicator bgColor={leftColor} side={'left'} />
<ProgressIndicator bgColor={leftColor} side={'left'} light={false} />
{leftLabel}
</span>
<span>
{rightLabel}
<ProgressIndicator bgColor={rightColor} side={'right'} />
<ProgressIndicator
bgColor={rightColor}
side={'right'}
light={false}
/>
</span>
</ProgressContents>
@ -105,6 +120,6 @@ export const LockedProgress = ({
<span>{formatNumber(locked, 2)}</span>
<span>{formatNumber(unlocked, 2)}</span>
</ProgressContents>
</div>
</>
);
};

View File

@ -180,15 +180,15 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => {
</div>
))}
<WalletCardActions>
<Link style={{ flex: 1 }} to={Routes.GOVERNANCE}>
<span className="flex items-center justify-center w-full px-28 border h-28 bg-white text-black">
<Link className="flex-1 pr-8" to={Routes.GOVERNANCE}>
<Button variant={'secondary'} className="w-full">
{t('governance')}
</span>
</Button>
</Link>
<Link style={{ flex: 1 }} to={Routes.STAKING}>
<span className="flex items-center justify-center w-full px-28 border h-28 bg-white text-black">
<Link className="flex-1 pl-8" to={Routes.STAKING}>
<Button variant={'secondary'} className="w-full">
{t('staking')}
</span>
</Button>
</Link>
</WalletCardActions>
<VegaWalletAssetList accounts={accounts} />

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en" class="bg-black w-full h-full">
<html lang="en" class="dark bg-black w-full h-full">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="//static.vega.xyz/fonts.css" />

View File

@ -25,7 +25,7 @@ export const TargetAddressMismatch = ({
}}
components={{
bold: <strong />,
red: <span className={'text-red'} />,
red: <span className={'text-vega-red'} />,
}}
/>
</p>

View File

@ -88,7 +88,9 @@ export const StakingNode = ({ vegaKey, data }: StakingNodeProps) => {
if (!nodeInfo) {
return (
<span className={'text-red'}>{t('stakingNodeNotFound', { node })}</span>
<span className={'text-vega-red'}>
{t('stakingNodeNotFound', { node })}
</span>
);
}

View File

@ -40,7 +40,7 @@ export const TrancheProgress = ({
<span className="tranches__progress-title">{t('Redeemed')}</span>
<ProgressBar
width={220}
color={Colors.green.DEFAULT}
color={Colors.vega.green}
percentage={removedPercentage}
/>
<span className="tranches__progress-numbers">

View File

@ -36,7 +36,7 @@ export const VestingChart = () => {
['pink', Colors.vega.pink],
['green', Colors.vega.green],
['orange', Colors.orange],
['yellow', Colors.yellow.DEFAULT],
['yellow', Colors.vega.yellow],
].map(([key, color]) => (
<linearGradient key={key} id={key} x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor={color} stopOpacity={0.85} />
@ -119,7 +119,7 @@ export const VestingChart = () => {
dot={false}
type="monotone"
dataKey="publicSale"
stroke={Colors.yellow.DEFAULT}
stroke={Colors.vega.yellow}
fill="url(#yellow)"
yAxisId={0}
strokeWidth={2}

View File

@ -76,6 +76,7 @@ export const generateOrders = (override?: PartialDeep<Orders>): Orders => {
id: 'c6f4337b31ed57a961969c3ba10297b369d01b9e75a4cbb96db4fc62886444e6',
name: 'BTCUSD Monthly (30 Jun 2022)',
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {

View File

@ -1,8 +1,8 @@
import { useRouter } from 'next/router';
import { Vega } from '../icons/vega';
import Link from 'next/link';
import { AnchorButton } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers';
import classNames from 'classnames';
export const Navbar = () => {
return (
@ -33,14 +33,17 @@ const NavLink = ({ name, path, exact, testId = name }: NavLinkProps) => {
const router = useRouter();
const isActive =
router.asPath === path || (!exact && router.asPath.startsWith(path));
const linkClasses = classNames(
'px-16 py-6 border-0 self-end',
'uppercase xs:text-ui sm:text-body-large md:text-h5 lg:text-h4',
{
'bg-vega-pink dark:bg-vega-yellow text-white dark:text-black': isActive,
'text-black dark:text-white': !isActive,
}
);
return (
<AnchorButton
variant={isActive ? 'accent' : 'inline'}
className="px-16 py-6 h-[38px] uppercase border-0 self-end xs:text-ui sm:text-body-large md:text-h5 lg:text-h4"
data-testid={testId}
href={path}
>
{name}
</AnchorButton>
<Link data-testid={testId} href={path} passHref={true}>
<a className={linkClasses}>{name}</a>
</Link>
);
};

View File

@ -24,84 +24,80 @@ function AppBody({ Component, pageProps }: AppProps) {
const { push } = useRouter();
const store = useGlobalStore();
const { VEGA_NETWORKS } = useEnvironment();
const [, toggleTheme] = useThemeSwitcher();
const [theme, toggleTheme] = useThemeSwitcher();
return (
<div className="h-full dark:bg-black dark:text-white-60 bg-white relative z-0 text-black-60 grid grid-rows-[min-content,1fr]">
<AppLoader>
<div className="flex items-stretch border-b-[7px] border-vega-yellow">
<Navbar />
<div className="flex items-center gap-4 ml-auto mr-8">
<VegaWalletConnectButton
setConnectDialog={(open) => {
store.setVegaWalletConnectDialog(open);
}}
setManageDialog={(open) => {
store.setVegaWalletManageDialog(open);
}}
/>
<ThemeSwitcher onToggle={toggleTheme} className="-my-4" />
<ThemeContext.Provider value={theme}>
<div className="h-full dark:bg-black dark:text-white-60 bg-white relative z-0 text-black-60 grid grid-rows-[min-content,1fr]">
<AppLoader>
<div className="flex items-stretch border-b-[7px] border-vega-pink dark:border-vega-yellow">
<Navbar />
<div className="flex items-center gap-4 ml-auto mr-8">
<VegaWalletConnectButton
setConnectDialog={(open) => {
store.setVegaWalletConnectDialog(open);
}}
setManageDialog={(open) => {
store.setVegaWalletManageDialog(open);
}}
/>
<ThemeSwitcher onToggle={toggleTheme} className="-my-4" />
</div>
</div>
</div>
<main data-testid={pageProps.page}>
{/* @ts-ignore conflict between @types/react and nextjs internal types */}
<Component {...pageProps} />
</main>
<VegaConnectDialog
connectors={Connectors}
dialogOpen={store.vegaWalletConnectDialog}
setDialogOpen={(open) => store.setVegaWalletConnectDialog(open)}
/>
<VegaManageDialog
dialogOpen={store.vegaWalletManageDialog}
setDialogOpen={(open) => store.setVegaWalletManageDialog(open)}
/>
<NetworkSwitcherDialog
dialogOpen={store.vegaNetworkSwitcherDialog}
setDialogOpen={(open) => store.setVegaNetworkSwitcherDialog(open)}
onConnect={({ network }) => {
if (VEGA_NETWORKS[network]) {
push(VEGA_NETWORKS[network] ?? '');
}
}}
/>
</AppLoader>
</div>
<main data-testid={pageProps.page}>
{/* @ts-ignore conflict between @types/react and nextjs internal types */}
<Component {...pageProps} />
</main>
<VegaConnectDialog
connectors={Connectors}
dialogOpen={store.vegaWalletConnectDialog}
setDialogOpen={(open) => store.setVegaWalletConnectDialog(open)}
/>
<VegaManageDialog
dialogOpen={store.vegaWalletManageDialog}
setDialogOpen={(open) => store.setVegaWalletManageDialog(open)}
/>
<NetworkSwitcherDialog
dialogOpen={store.vegaNetworkSwitcherDialog}
setDialogOpen={(open) => store.setVegaNetworkSwitcherDialog(open)}
onConnect={({ network }) => {
if (VEGA_NETWORKS[network]) {
push(VEGA_NETWORKS[network] ?? '');
}
}}
/>
</AppLoader>
</div>
</ThemeContext.Provider>
);
}
function VegaTradingApp(props: AppProps) {
const [theme] = useThemeSwitcher();
return (
<EnvironmentProvider>
<ThemeContext.Provider value={theme}>
<VegaWalletProvider>
<Head>
<link
rel="preload"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
<title>{t('Welcome to Vega trading!')}</title>
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
{['1', 'true'].includes(
process.env['NX_USE_ENV_OVERRIDES'] || ''
) ? (
/* eslint-disable-next-line @next/next/no-sync-scripts */
<script src="./env-config.js" type="text/javascript" />
) : null}
</Head>
<AppBody {...props} />
</VegaWalletProvider>
</ThemeContext.Provider>
<VegaWalletProvider>
<Head>
<link
rel="preload"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
<title>{t('Welcome to Vega trading!')}</title>
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
{['1', 'true'].includes(process.env['NX_USE_ENV_OVERRIDES'] || '') ? (
/* eslint-disable-next-line @next/next/no-sync-scripts */
<script src="./env-config.js" type="text/javascript" />
) : null}
</Head>
<AppBody {...props} />
</VegaWalletProvider>
</EnvironmentProvider>
);
}

View File

@ -18,8 +18,8 @@ import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
import { SelectMarketDialog } from '@vegaprotocol/market-list';
import {
ArrowDown,
GridTab,
GridTabs,
Tab,
Tabs,
PriceCellChange,
} from '@vegaprotocol/ui-toolkit';
import type { CandleClose } from '@vegaprotocol/types';
@ -66,7 +66,7 @@ export const TradeMarketHeader = ({
<div className="flex flex-col md:flex-row gap-20 md:gap-64 ml-auto mr-8">
<button
onClick={() => setOpen(!open)}
className="shrink-0 dark:text-vega-yellow text-black text-h5 flex items-center gap-8 px-4 py-0 h-37 hover:bg-vega-yellow dark:hover:bg-white/20"
className="shrink-0 dark:text-vega-yellow text-black text-h5 flex items-center gap-8 px-4 py-0 h-37 hover:bg-black/20 dark:hover:bg-white/20"
>
<span className="break-words text-left">{market.name}</span>
<ArrowDown color="yellow" borderX={8} borderTop={12} />
@ -122,47 +122,47 @@ export const TradeGrid = ({ market }: TradeGridProps) => {
className="row-start-1 row-end-2 col-start-1 col-end-4"
/>
<TradeGridChild className="row-start-2 row-end-3 col-start-1 col-end-2">
<GridTabs>
<GridTab id="candles" name={t('Candles')}>
<Tabs>
<Tab id="candles" name={t('Candles')}>
<TradingViews.Candles marketId={market.id} />
</GridTab>
<GridTab id="depth" name={t('Depth')}>
</Tab>
<Tab id="depth" name={t('Depth')}>
<TradingViews.Depth marketId={market.id} />
</GridTab>
</GridTabs>
</Tab>
</Tabs>
</TradeGridChild>
<TradeGridChild className="row-start-2 row-end-3 col-start-2 col-end-3">
<GridTabs>
<GridTab id="ticket" name={t('Ticket')}>
<Tabs>
<Tab id="ticket" name={t('Ticket')}>
<TradingViews.Ticket marketId={market.id} />
</GridTab>
<GridTab id="info" name={t('Info')}>
</Tab>
<Tab id="info" name={t('Info')}>
<TradingViews.Info marketId={market.id} />
</GridTab>
</GridTabs>
</Tab>
</Tabs>
</TradeGridChild>
<TradeGridChild className="row-start-2 row-end-3 col-start-3 col-end-4">
<GridTabs>
<GridTab id="trades" name={t('Trades')}>
<Tabs>
<Tab id="trades" name={t('Trades')}>
<TradingViews.Trades marketId={market.id} />
</GridTab>
<GridTab id="orderbook" name={t('Orderbook')}>
</Tab>
<Tab id="orderbook" name={t('Orderbook')}>
<TradingViews.Orderbook marketId={market.id} />
</GridTab>
</GridTabs>
</Tab>
</Tabs>
</TradeGridChild>
<TradeGridChild className="col-span-3">
<GridTabs>
<GridTab id="orders" name={t('Orders')}>
<Tabs>
<Tab id="orders" name={t('Orders')}>
<TradingViews.Orders />
</GridTab>
<GridTab id="positions" name={t('Positions')}>
</Tab>
<Tab id="positions" name={t('Positions')}>
<TradingViews.Positions />
</GridTab>
<GridTab id="accounts" name={t('Accounts')}>
</Tab>
<Tab id="accounts" name={t('Accounts')}>
<TradingViews.Accounts />
</GridTab>
</GridTabs>
</Tab>
</Tabs>
</TradeGridChild>
</div>
</>

View File

@ -3,8 +3,7 @@ import { t } from '@vegaprotocol/react-helpers';
import { PositionsContainer } from '@vegaprotocol/positions';
import { OrderListContainer } from '@vegaprotocol/order-list';
import { AccountsContainer } from '@vegaprotocol/accounts';
import { AnchorButton, GridTab, GridTabs } from '@vegaprotocol/ui-toolkit';
import { AnchorButton, Tab, Tabs } from '@vegaprotocol/ui-toolkit';
import { WithdrawalsContainer } from './withdrawals/withdrawals-container';
const Portfolio = () => {
@ -20,54 +19,54 @@ const Portfolio = () => {
</h2>
</aside>
<section data-testid="portfolio-grid">
<GridTabs>
<GridTab id="positions" name={t('Positions')}>
<Tabs>
<Tab id="positions" name={t('Positions')}>
<div className={tabClassName}>
<h4 className="text-h4 text-black dark:text-white">
{t('Positions')}
</h4>
<PositionsContainer />
</div>
</GridTab>
<GridTab id="orders" name={t('Orders')}>
</Tab>
<Tab id="orders" name={t('Orders')}>
<div className={tabClassName}>
<h4 className="text-h4 text-black dark:text-white">
{t('Orders')}
</h4>
<OrderListContainer />
</div>
</GridTab>
<GridTab id="fills" name={t('Fills')}>
</Tab>
<Tab id="fills" name={t('Fills')}>
<div className={tabClassName}>
<h4 className="text-h4 text-black dark:text-white">
{t('Fills')}
</h4>
</div>
</GridTab>
<GridTab id="history" name={t('History')}>
</Tab>
<Tab id="history" name={t('History')}>
<div className={tabClassName}>
<h4 className="text-h4 text-black dark:text-white">
{t('History')}
</h4>
</div>
</GridTab>
</GridTabs>
</Tab>
</Tabs>
</section>
</main>
<section className="fixed bottom-0 left-0 w-full h-[200px]">
<GridTabs>
<GridTab id="collateral" name={t('Collateral')}>
<Tabs>
<Tab id="collateral" name={t('Collateral')}>
<AccountsContainer />
</GridTab>
<GridTab id="deposits" name={t('Deposits')}>
</Tab>
<Tab id="deposits" name={t('Deposits')}>
<AnchorButton data-testid="deposit" href="/portfolio/deposit">
{t('Deposit')}
</AnchorButton>
</GridTab>
<GridTab id="withdrawals" name={t('Withdrawals')}>
</Tab>
<Tab id="withdrawals" name={t('Withdrawals')}>
<WithdrawalsContainer />
</GridTab>
</GridTabs>
</Tab>
</Tabs>
</section>
</div>
</Web3Container>

View File

@ -17,7 +17,6 @@ import { useContext, useMemo, useState } from 'react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { ThemeContext } from '@vegaprotocol/react-helpers';
import {
Button,
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
@ -58,14 +57,14 @@ export const CandlesChartContainer = ({
return new VegaDataSource(client, marketId, keypair?.pub);
}, [client, marketId, keypair]);
const dropdownTriggerStyles = 'border-black-60 dark:border-white-60 px-20';
return (
<div className="h-full flex flex-col">
<div className="p-8 flex flex-row flex-wrap gap-8">
<DropdownMenu>
<DropdownMenuTrigger asChild={true}>
<Button appendIconName="caret-down" variant="secondary">
{t('Interval')}
</Button>
<DropdownMenuTrigger className={dropdownTriggerStyles}>
{t('Interval')}
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuRadioGroup
@ -90,10 +89,8 @@ export const CandlesChartContainer = ({
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild={true}>
<Button appendIconName="caret-down" variant="secondary">
<Icon name={chartTypeIcon.get(chartType) as IconName} />
</Button>
<DropdownMenuTrigger className={dropdownTriggerStyles}>
<Icon name={chartTypeIcon.get(chartType) as IconName} />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuRadioGroup
@ -114,10 +111,8 @@ export const CandlesChartContainer = ({
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild={true}>
<Button appendIconName="caret-down" variant="secondary">
{t('Overlays')}
</Button>
<DropdownMenuTrigger className={dropdownTriggerStyles}>
{t('Overlays')}
</DropdownMenuTrigger>
<DropdownMenuContent>
{Object.values(Overlay).map((overlay) => (
@ -145,10 +140,8 @@ export const CandlesChartContainer = ({
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild={true}>
<Button appendIconName="caret-down" variant="secondary">
{t('Studies')}
</Button>
<DropdownMenuTrigger className={dropdownTriggerStyles}>
{t('Studies')}
</DropdownMenuTrigger>
<DropdownMenuContent>
{Object.values(Study).map((study) => (

View File

@ -109,7 +109,7 @@ export const DealTicket = ({
)}
<Button
className="w-full mb-8"
variant="primary"
variant="trade"
type="submit"
disabled={isDisabled}
data-testid="place-order"

View File

@ -6,6 +6,73 @@ const shadeOfGray = (shade) => {
return `#${hexValue}${hexValue}${hexValue}`;
};
const colours = {
transparent: 'transparent',
current: 'currentColor',
text: '#C7C7C7',
deemphasise: '#8A9BA8',
white: {
DEFAULT: '#FFF',
strong: '#FFF',
normal: '#F5F8FA',
muted: '#676767',
'02': shadeOfGray(2),
'05': shadeOfGray(5),
10: shadeOfGray(10),
25: shadeOfGray(25),
40: shadeOfGray(40),
60: shadeOfGray(60),
70: shadeOfGray(70),
80: shadeOfGray(80),
90: shadeOfGray(90),
95: shadeOfGray(95),
100: shadeOfGray(100),
},
black: {
DEFAULT: '#000',
strong: '#000',
normal: '#000',
muted: '#BFCCD6',
'02': shadeOfGray(100 - 2),
'05': shadeOfGray(100 - 5),
10: shadeOfGray(100 - 10),
25: shadeOfGray(100 - 25),
40: shadeOfGray(100 - 40),
50: shadeOfGray(100 - 50),
60: shadeOfGray(100 - 60),
70: shadeOfGray(100 - 70),
80: shadeOfGray(100 - 80),
90: shadeOfGray(100 - 90),
95: shadeOfGray(100 - 95),
100: shadeOfGray(100 - 100),
},
vega: {
yellow: '#DFFF0B',
'yellow-dark': '#474B0A',
pink: '#FF077F',
green: '#00F780',
'green-medium': '#00DE73',
'green-dark': '#008545',
red: '#FF261A',
'red-dark': '#EB001B',
},
blue: '#1DA2FB',
coral: '#FF6057',
pink: '#FF2D5E',
orange: '#D9822B',
danger: '#FF261A',
warning: '#FF7A1A',
selected: '#DFFF0B',
success: '#00F780',
'danger-bg': '#9E0025', // for white text
};
const boxShadowPosition = {
outer: '2px 2px 0 0',
insetUnderline: 'inset 0 -2px 0 0',
insetShading: 'inset 2px 2px 6px',
};
module.exports = {
screens: {
xs: '500px',
@ -16,67 +83,7 @@ module.exports = {
xxl: '1536px',
},
colors: {
transparent: 'transparent',
current: 'currentColor',
vega: {
yellow: '#EDFF22',
pink: '#FF2D5E',
green: '#00F780',
red: '#FF261A',
},
red: {
DEFAULT: '#ED1515',
dark: '#EB001B',
bar: 'rgba(47, 246, 139, 0.45)', // #2FF68B 45%
},
green: {
DEFAULT: '#26FF8A',
dark: '#008545',
bar: 'rgba(47, 246, 139, 0.45)', // #2FF68B 45%
},
text: '#C7C7C7',
deemphasise: '#8A9BA8',
white: {
DEFAULT: '#FFF',
strong: '#FFF',
normal: '#F5F8FA',
muted: '#676767',
'02': shadeOfGray(2),
'05': shadeOfGray(5),
10: shadeOfGray(10),
25: shadeOfGray(25),
40: shadeOfGray(40),
60: shadeOfGray(60),
80: shadeOfGray(80),
95: shadeOfGray(95),
100: shadeOfGray(100),
},
black: {
DEFAULT: '#000',
strong: '#000',
normal: '#000',
muted: '#BFCCD6',
'02': shadeOfGray(100 - 2),
'05': shadeOfGray(100 - 5),
10: shadeOfGray(100 - 10),
25: shadeOfGray(100 - 25),
40: shadeOfGray(100 - 40),
60: shadeOfGray(100 - 60),
80: shadeOfGray(100 - 80),
95: shadeOfGray(100 - 95),
100: shadeOfGray(100 - 100),
},
blue: '#1DA2FB',
coral: '#FF6057',
orange: '#D9822B',
yellow: {
DEFAULT: '#EDFF22',
dark: '#474B0A', // yellow 0.3 opacity on black
},
danger: '#FF261A',
warning: '#FF7A1A',
success: '#26FF8A',
'danger-bg': '#9E0025', // for white text
...colours,
},
spacing: {
0: '0px',
@ -121,7 +128,9 @@ module.exports = {
borderWidth: {
DEFAULT: '1px',
1: '1px',
2: '2px',
4: '4px',
7: '7px',
},
borderRadius: {
none: '0',
@ -178,11 +187,27 @@ module.exports = {
ui: ['14px', '20px'],
'ui-small': ['12px', '16px'],
},
boxShadow: {
callout: '5px 5px 0 1px rgba(255, 255, 255, 0.05)',
focus: '0px 0px 0px 1px #FFFFFF, 0px 0px 3px 2px #FFE600',
'focus-dark': '0px 0px 0px 1px #000000, 0px 0px 3px 2px #FFE600',
radio: '1px 1px 0 0',
intent: `3px 3px 0 0`,
'vega-yellow': `${boxShadowPosition.outer} ${colours.vega.yellow}`,
'vega-pink': `${boxShadowPosition.outer} ${colours.vega.pink}`,
'inset-black': `${boxShadowPosition.insetUnderline} ${colours.black.DEFAULT}`,
'inset-white': `${boxShadowPosition.insetUnderline} ${colours.white.DEFAULT}`,
'inset-vega-yellow': `${boxShadowPosition.insetUnderline} ${colours.vega.yellow}`,
'inset-vega-pink': `${boxShadowPosition.insetUnderline} ${colours.vega.pink}`,
'inset-danger': `${boxShadowPosition.insetUnderline} ${colours.danger}`,
input: `${boxShadowPosition.insetShading} ${colours.white['80']}`,
'input-dark': `${boxShadowPosition.insetShading} ${colours.black['80']}`,
'input-focus': `${boxShadowPosition.insetShading} ${colours.white['80']}, ${boxShadowPosition.insetUnderline} ${colours.vega.pink}`,
'input-focus-dark': `${boxShadowPosition.insetShading} ${colours.black['80']}, ${boxShadowPosition.insetUnderline} ${colours.vega.yellow}`,
'input-focus-error': `${boxShadowPosition.insetShading} ${colours.white['80']}, ${boxShadowPosition.insetUnderline} ${colours.danger}`,
'input-focus-error-dark': `${boxShadowPosition.insetShading} ${colours.black['80']}, ${boxShadowPosition.insetUnderline} ${colours.danger}`,
'checkbox-focus': `${boxShadowPosition.insetShading} ${colours.white['80']}, ${boxShadowPosition.outer} ${colours.vega.pink}`,
'checkbox-focus-dark': `${boxShadowPosition.insetShading} ${colours.black['80']}, ${boxShadowPosition.outer} ${colours.vega.yellow}`,
},
backgroundImage: {
'fairground-nav': "url('https://static.vega.xyz/fairground-nav-bg.jpg')",
},
};

View File

@ -29,6 +29,25 @@ const vegaCustomClasses = plugin(function ({ addUtilities }) {
'.syntax-highlighter-wrapper .hljs-string': {
color: theme.colors.blue,
},
'.input-border': {
borderWidth: '1px',
borderStyle: 'solid',
borderTopColor: theme.colors.black['60'],
borderLeftColor: theme.colors.black['60'],
borderRightColor: theme.colors.black['40'],
borderBottomColor: theme.colors.black['40'],
},
'.input-border-dark': {
borderWidth: '1px',
borderStyle: 'solid',
borderTopColor: theme.colors.white['40'],
borderLeftColor: theme.colors.white['40'],
borderRightColor: theme.colors.white['60'],
borderBottomColor: theme.colors.white['60'],
},
'.color-scheme-dark': {
colorScheme: 'dark',
},
});
});

View File

@ -14,7 +14,7 @@ import BigNumber from 'bignumber.js';
import { sortTrades } from './trades-data-provider';
export const UP_CLASS = 'text-vega-green';
export const DOWN_CLASS = 'text-vega-pink';
export const DOWN_CLASS = 'text-vega-red';
const changeCellClass =
(dataKey: string) =>

View File

@ -9,7 +9,7 @@ const agGridLightVariables = `
--ag-header-background-color: ${theme.colors.white[100]};
--ag-odd-row-background-color: ${theme.colors.white[100]};
--ag-row-border-color: ${theme.colors.white[100]};
--ag-row-hover-color: ${theme.colors.yellow.DEFAULT};
--ag-row-hover-color: ${theme.colors.black[10]};
--ag-font-size: 12px;
}

View File

@ -11,7 +11,7 @@ const Template: Story = (args) => (
<div className="mb-8">
<Button {...args} />
</div>
{args['variant'] !== 'inline' && <Button {...args} disabled />}
{args['variant'] !== 'inline-link' && <Button {...args} disabled />}
</>
);
@ -26,18 +26,18 @@ Secondary.args = {
variant: 'secondary',
};
export const Trade = Template.bind({});
Trade.args = {
children: 'Trade',
variant: 'trade',
};
export const Accent = Template.bind({});
Accent.args = {
children: 'Accent',
variant: 'accent',
};
export const Inline = Template.bind({});
Inline.args = {
children: 'Inline',
variant: 'inline',
};
export const InlineLink = Template.bind({});
InlineLink.args = {
children: 'Inline link',
@ -67,13 +67,13 @@ export const NavAccent: Story = () => (
export const NavInline: Story = () => (
<>
<div className="mb-8">
<Button variant="inline" className="uppercase">
<Button variant="inline-link" className="uppercase">
Background
</Button>
</div>
<div className="mb-8">
<Button
variant="inline"
variant="inline-link"
className="uppercase"
prependIconName="menu-open"
>
@ -82,7 +82,7 @@ export const NavInline: Story = () => (
</div>
<div className="mb-8">
<Button
variant="inline"
variant="inline-link"
className="uppercase"
appendIconName="menu-closed"
>
@ -96,12 +96,33 @@ export const IconPrepend = Template.bind({});
IconPrepend.args = {
children: 'Icon prepend',
prependIconName: 'search',
variant: 'accent',
variant: 'trade',
};
export const IconAppend = Template.bind({});
IconAppend.args = {
children: 'Icon append',
appendIconName: 'search',
variant: 'accent',
variant: 'trade',
};
export const InlineIconPrepend = Template.bind({});
InlineIconPrepend.args = {
children: 'Icon prepend',
prependIconName: 'search',
variant: 'inline-link',
};
export const InlineIconAppend = Template.bind({});
InlineIconAppend.args = {
children: 'Icon append',
appendIconName: 'search',
variant: 'inline-link',
};
export const SpanWithButtonStyleAndContent = Template.bind({});
SpanWithButtonStyleAndContent.args = {
children: 'Apply button styles to other elements (i.e. span, <Link>)',
appendIconName: 'search',
variant: 'trade',
};

View File

@ -1,4 +1,8 @@
import type { AnchorHTMLAttributes, ButtonHTMLAttributes } from 'react';
import type {
AnchorHTMLAttributes,
ButtonHTMLAttributes,
ReactNode,
} from 'react';
import { forwardRef } from 'react';
import classNames from 'classnames';
import type { IconName } from '../icon';
@ -9,13 +13,15 @@ import {
includesBorderWidth,
includesHeight,
} from '../../utils/class-names';
import classnames from 'classnames';
interface CommonProps {
children?: React.ReactNode;
variant?: 'primary' | 'secondary' | 'accent' | 'inline' | 'inline-link';
children?: ReactNode;
variant?: 'primary' | 'secondary' | 'trade' | 'accent' | 'inline-link';
className?: string;
prependIconName?: IconName;
appendIconName?: IconName;
boxShadow?: boolean;
}
export interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>,
@ -25,21 +31,30 @@ export interface AnchorButtonProps
extends AnchorHTMLAttributes<HTMLAnchorElement>,
CommonProps {}
const getClasses = (
variant: CommonProps['variant'],
paddingLeftProvided: boolean,
paddingRightProvided: boolean,
borderWidthProvided: boolean,
heightProvided: boolean
export const getButtonClasses = (
className?: string,
variant?: 'primary' | 'secondary' | 'trade' | 'accent' | 'inline-link',
boxShadow?: boolean
) => {
const paddingLeftProvided = includesLeftPadding(className);
const paddingRightProvided = includesRightPadding(className);
const borderWidthProvided = includesBorderWidth(className);
const heightProvided = includesHeight(className);
// Add classes into variables if there are multiple classes shared in multiple button styles
const sharedClasses =
'inline-flex items-center justify-center box-border transition-all disabled:no-underline';
const underlineOnHover = 'no-underline hover:underline';
const commonHoverAndActiveBorder =
'hover:border-black dark:hover:border-white active:border-black dark:active:border-white';
'inline-flex items-center justify-center box-border transition-[background-color] ease-linear duration-50 disabled:no-underline';
const commonButtonClasses = classnames(
'relative disabled:static',
'text-ui font-semibold focus-visible:outline-none border no-underline hover:no-underline',
{
'shadow-none': !boxShadow,
'shadow-[3px_3px_0_0] focus-visible:shadow-vega-pink dark:focus-visible:shadow-vega-yellow active:top-[1px] active:left-[1px] active:shadow-[2px_2px_0_0]':
boxShadow === undefined || boxShadow,
}
);
const commonDisabled =
'disabled:bg-black-10 dark:disabled:bg-white-10 disabled:text-black-60 dark:disabled:text-white-60 disabled:border-black-25 dark:disabled:border-white-25';
'disabled:bg-black-10 dark:disabled:bg-white-10 disabled:text-black-60 dark:disabled:text-white-60 disabled:border-black-25 dark:disabled:border-white-25 disabled:shadow-none dark:disabled:shadow-none';
const inlineTextColour =
'text-black-95 dark:text-white-95 hover:text-black hover:dark:text-white active:text-black dark:active:text-vega-yellow';
@ -62,104 +77,92 @@ const getClasses = (
const primaryClasses = [
sharedClasses,
commonHoverAndActiveBorder,
underlineOnHover,
commonButtonClasses,
commonDisabled,
standardButtonPaddingLeft,
standardButtonPaddingRight,
standardButtonBorderWidth,
buttonHeight,
'bg-black dark:bg-white hover:bg-black-80 dark:hover:bg-white-80 active:bg-white dark:active:bg-black',
'text-ui text-white dark:text-black active:text-black dark:active:text-white',
'bg-black dark:bg-white hover:bg-black-80 dark:hover:bg-white-90 active:bg-black-80 dark:active:bg-white-90',
'border-white dark:border-black shadow-black active:shadow-black dark:shadow-white-80 dark:active:shadow-white',
'text-white dark:text-black',
];
const secondaryClasses = [
sharedClasses,
commonHoverAndActiveBorder,
underlineOnHover,
commonButtonClasses,
commonDisabled,
standardButtonPaddingLeft,
standardButtonPaddingRight,
standardButtonBorderWidth,
buttonHeight,
'bg-white dark:bg-black hover:bg-black-25 dark:hover:bg-white-25 active:bg-black dark:active:bg-white',
'text-ui text-black dark:text-white active:text-white dark:active:text-black',
'border-black-60 dark:border-white-60 hover:border-black',
'bg-white dark:bg-black hover:bg-black-25 dark:hover:bg-white-25',
'border-black dark:border-white shadow-black dark:shadow-white',
'text-black dark:text-white',
];
const tradeClasses = [
sharedClasses,
commonButtonClasses,
commonDisabled,
standardButtonPaddingLeft,
standardButtonPaddingRight,
standardButtonBorderWidth,
buttonHeight,
'bg-vega-green hover:bg-vega-green-medium',
'border-black disabled:shadow-none dark:disabled:shadow-none shadow-black dark:shadow-white',
'text-black',
];
const accentClasses = [
sharedClasses,
commonHoverAndActiveBorder,
underlineOnHover,
commonDisabled,
standardButtonPaddingLeft,
standardButtonPaddingRight,
standardButtonBorderWidth,
buttonHeight,
'bg-vega-yellow dark:bg-vega-yellow hover:bg-yellow/dark dark:hover:bg-vega-yellow/30 active:bg-white dark:active:bg-black',
'text-ui uppercase text-black dark:text-black hover:text-white dark:hover:text-white active:text-black dark:active:text-white',
'bg-vega-yellow dark:bg-vega-yellow hover:bg-vega-yellow-dark dark:hover:bg-vega-yellow-dark active:bg-white dark:active:bg-black',
'uppercase text-black dark:text-black hover:text-white dark:hover:text-white active:text-black dark:active:text-white',
'border-transparent dark:border-transparent',
];
const inlineClasses = [
sharedClasses,
inlineButtonPaddingLeft,
inlineButtonPaddingRight,
buttonHeight,
inlineTextColour,
'border-none',
'text-ui',
];
const inlineLinkClasses = [
sharedClasses,
inlineButtonPaddingLeft,
inlineButtonPaddingRight,
buttonHeight,
inlineTextColour,
'underline hover:underline',
'underline hover:underline hover:text-black-60 dark:hover:text-white-80',
'border-none',
];
let variantClasses: string[];
switch (variant) {
case 'primary':
return primaryClasses;
variantClasses = primaryClasses;
break;
case 'secondary':
return secondaryClasses;
variantClasses = secondaryClasses;
break;
case 'trade':
variantClasses = tradeClasses;
break;
case 'accent':
return accentClasses;
case 'inline':
return inlineClasses;
variantClasses = accentClasses;
break;
case 'inline-link':
return inlineLinkClasses;
variantClasses = inlineLinkClasses;
break;
default:
return '';
variantClasses = [''];
}
return classNames(...variantClasses, className);
};
const classes = (
className: CommonProps['className'],
variant: CommonProps['variant']
) => {
const paddingLeftProvided = includesLeftPadding(className);
const paddingRightProvided = includesRightPadding(className);
const borderWidthProvided = includesBorderWidth(className);
const heightProvided = includesHeight(className);
return classNames(
getClasses(
variant,
paddingLeftProvided,
paddingRightProvided,
borderWidthProvided,
heightProvided
),
className
);
};
const getContent = (
children: React.ReactNode,
export const getButtonContent = (
children: ReactNode,
prependIconName?: IconName,
appendIconName?: IconName
) => {
@ -190,6 +193,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
className,
prependIconName,
appendIconName,
boxShadow,
...props
},
ref
@ -197,11 +201,11 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
return (
<button
ref={ref}
className={classes(className, variant)}
className={getButtonClasses(className, variant, boxShadow)}
type={type}
{...props}
>
{getContent(children, prependIconName, appendIconName)}
{getButtonContent(children, prependIconName, appendIconName)}
</button>
);
}
@ -215,13 +219,18 @@ export const AnchorButton = forwardRef<HTMLAnchorElement, AnchorButtonProps>(
className,
prependIconName,
appendIconName,
boxShadow,
...props
},
ref
) => {
return (
<a ref={ref} className={classes(className, variant)} {...props}>
{getContent(children, prependIconName, appendIconName)}
<a
ref={ref}
className={getButtonClasses(className, variant, boxShadow)}
{...props}
>
{getButtonContent(children, prependIconName, appendIconName)}
</a>
);
}

View File

@ -17,31 +17,29 @@ it('renders title and icon', () => {
it(`Applies class for success intent`, () => {
render(<Callout intent={Intent.Danger} />);
expect(screen.getByTestId('callout')).toHaveClass('shadow-danger');
expect(screen.getByTestId('callout')).toHaveClass('border-danger');
});
it(`Applies class for warning intent`, () => {
render(<Callout intent={Intent.Warning} />);
expect(screen.getByTestId('callout')).toHaveClass('shadow-warning');
expect(screen.getByTestId('callout')).toHaveClass('border-warning');
});
it(`Applies class for danger intent`, () => {
render(<Callout intent={Intent.Danger} />);
expect(screen.getByTestId('callout')).toHaveClass('shadow-danger');
expect(screen.getByTestId('callout')).toHaveClass('border-danger');
});
it(`Applies class for primary intent`, () => {
render(<Callout intent={Intent.Primary} />);
expect(screen.getByTestId('callout')).toHaveClass(
'shadow-vega-pink',
'dark:shadow-vega-yellow'
'border-black dark:border-white'
);
});
it(`Applies class for none intent`, () => {
render(<Callout />);
expect(screen.getByTestId('callout')).toHaveClass(
'shadow-black',
'dark:shadow-white'
'border-black dark:border-white'
);
});

View File

@ -21,31 +21,31 @@ const Template: ComponentStory<typeof Callout> = (args) => (
export const Default = Template.bind({});
Default.args = {
children: 'Content',
children: 'No intent supplied',
};
export const Primary = Template.bind({});
Primary.args = {
intent: Intent.Primary,
children: 'Content',
children: 'Intent: Primary',
};
export const Danger = Template.bind({});
Danger.args = {
intent: Intent.Danger,
children: 'Content',
children: 'Intent: Danger',
};
export const Warning = Template.bind({});
Warning.args = {
intent: Intent.Warning,
children: 'Content',
children: 'Intent: Warning',
};
export const Success = Template.bind({});
Success.args = {
intent: Intent.Success,
children: 'Content',
children: 'Intent: Success',
};
export const IconAndContent = Template.bind({});
@ -55,7 +55,7 @@ IconAndContent.args = {
iconName: 'endorsed',
children: (
<div className="flex flex-col">
<div>With a longer explaination</div>
<div>With a longer explanation</div>
<Button className="block mt-8" variant="secondary">
Action
</Button>
@ -74,7 +74,7 @@ CustomIconAndContent.args = {
),
children: (
<div className="flex flex-col">
<div>With a longer explaination</div>
<div>With a longer explanation</div>
<Button className="block mt-8" variant="secondary">
Action
</Button>
@ -89,7 +89,7 @@ Loading.args = {
isLoading: true,
children: (
<div className="flex flex-col">
<div>With a longer explaination</div>
<div>With a longer explanation</div>
<Button className="block mt-8" variant="secondary">
Action
</Button>

View File

@ -1,13 +1,13 @@
import type { ReactNode } from 'react';
import type { ReactNode, ReactElement } from 'react';
import classNames from 'classnames';
import { getIntentShadow, Intent } from '../../utils/intent';
import { getIntentBorder, Intent } from '../../utils/intent';
import { Loader } from '../loader';
import type { IconName } from '../icon';
import { Icon } from '../icon';
interface CalloutRootProps {
children?: React.ReactNode;
title?: React.ReactElement | string;
children?: ReactNode;
title?: ReactElement | string;
intent?: Intent;
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
isLoading?: boolean;
@ -89,13 +89,10 @@ export function Callout({
const className = classNames(
'flex gap-20',
'border',
'border-black',
'dark:border-white',
'text-body-large',
'dark:text-white',
'p-16',
getIntentShadow(intent)
getIntentBorder(intent)
);
const TitleTag: keyof JSX.IntrinsicElements = headingLevel
? `h${headingLevel}`

View File

@ -0,0 +1,64 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { Checkbox } from './checkbox';
describe('Checkbox', () => {
it('should render checkbox with label successfully', () => {
render(<Checkbox label="test" />);
expect(screen.getByText('test')).toBeInTheDocument();
});
it('should render a checked checkbox if specified in state', () => {
render(<Checkbox label="checked" state={'checked'} />);
expect(screen.getByTestId('checkbox-checked')).toBeInTheDocument();
});
it('should render an unchecked checkbox if specified in state', () => {
render(<Checkbox label="unchecked" state={'unchecked'} />);
expect(screen.getByTestId('checkbox-unchecked')).toBeInTheDocument();
});
it('should render an indeterminate checkbox if specified in state', () => {
render(<Checkbox label="indeterminate" state={'indeterminate'} />);
expect(screen.getByTestId('checkbox-indeterminate')).toBeInTheDocument();
});
it('should render a checkbox in error if specified', () => {
render(<Checkbox label="error" error={true} />);
expect(screen.getByTestId('checkbox-error')).toBeInTheDocument();
});
it('should render a checked checkbox in error if specified', () => {
render(<Checkbox label="error-checked" state={'checked'} error={true} />);
expect(screen.getByTestId('checkbox-error-checked')).toBeInTheDocument();
});
it('should render an unchecked checkbox in error if specified', () => {
render(
<Checkbox label="error-unchecked" state={'unchecked'} error={true} />
);
expect(screen.getByTestId('checkbox-error-unchecked')).toBeInTheDocument();
});
it('should render an indeterminate checkbox in error if specified', () => {
render(
<Checkbox
label="error-indeterminate"
state={'indeterminate'}
error={true}
/>
);
expect(
screen.getByTestId('checkbox-error-indeterminate')
).toBeInTheDocument();
});
it('fires callback on change if provided', () => {
const callback = jest.fn();
render(<Checkbox label="onchange" onChange={callback} />);
const checkbox = screen.getByText('onchange');
fireEvent.click(checkbox);
expect(callback.mock.calls.length).toEqual(1);
});
});

View File

@ -0,0 +1,45 @@
import type { ComponentStory, ComponentMeta } from '@storybook/react';
import { Checkbox } from './checkbox';
import { useState } from 'react';
export default {
component: Checkbox,
title: 'Checkbox',
} as ComponentMeta<typeof Checkbox>;
export const Controlled: ComponentStory<typeof Checkbox> = () => {
const [checkboxState, setCheckboxState] = useState<
'checked' | 'unchecked' | 'indeterminate'
>('indeterminate');
return (
<Checkbox
label={'controlled - initially indeterminate'}
state={checkboxState}
onChange={() => {
if (
checkboxState === 'indeterminate' ||
checkboxState === 'unchecked'
) {
setCheckboxState('checked');
}
if (checkboxState === 'checked') {
setCheckboxState('unchecked');
}
}}
/>
);
};
export const Default: ComponentStory<typeof Checkbox> = () => (
<Checkbox label={'uncontrolled - default checkbox behaviour'} />
);
export const Error: ComponentStory<typeof Checkbox> = () => (
<Checkbox label={'error'} error={true} />
);
export const Disabled: ComponentStory<typeof Checkbox> = () => (
<Checkbox label={'disabled'} disabled />
);

View File

@ -0,0 +1,81 @@
import classnames from 'classnames';
import { Icon } from '../icon';
import type { ChangeEvent, InputHTMLAttributes } from 'react';
export interface CheckboxProps extends InputHTMLAttributes<HTMLInputElement> {
label: string;
className?: string;
state?: 'checked' | 'unchecked' | 'indeterminate';
error?: boolean;
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
}
export const Checkbox = ({
label,
className,
state,
error,
onChange,
...props
}: CheckboxProps) => {
const containerClasses = classnames(
className,
'grid grid-cols-[auto_1fr] select-none'
);
const inputClasses = 'sr-only peer';
const vegaCheckboxClasses = classnames(
'col-start-1 row-start-1',
'inline-block w-20 h-20 relative z-0',
'shadow-input dark:shadow-input-dark bg-white dark:bg-white-25',
'focus-visible:outline-none focus-visible:shadow-checkbox-focus dark:focus-visible:shadow-checkbox-focus-dark',
'cursor-pointer peer-disabled:cursor-default',
{
'input-border dark:input-border-dark': !error,
'border border-vega-red': error,
}
);
// In uncontrolled elements without state, we apply the hidden class and 'peer-checked' can
// override it as necessary. At other times, we control display properties via state conditions.
const iconClasses = classnames(
'col-start-1 row-start-1 place-self-center relative z-50 pointer-events-none',
{
hidden: !state || state === 'unchecked',
}
);
const tickClasses = classnames(iconClasses, {
'peer-checked:block': !state,
block: state === 'checked',
hidden: state === 'indeterminate',
});
const minusClasses = classnames(iconClasses, {
block: state === 'indeterminate',
hidden: state === 'checked',
});
const labelClasses = classnames(
'col-start-2 row-start-1 pl-8 cursor-pointer peer-disabled:cursor-default'
);
return (
<label
className={containerClasses}
data-testid={`checkbox${error ? '-error' : ''}${
state ? `-${state}` : ''
}`}
>
<input
type="checkbox"
className={inputClasses}
onChange={onChange}
{...props}
/>
<span className={vegaCheckboxClasses} />
<span className={tickClasses}>
<Icon name={'tick'} className="fill-vega-pink dark:fill-vega-yellow" />
</span>
<span className={minusClasses}>
<Icon name={'minus'} className="fill-vega-pink dark:fill-vega-yellow" />
</span>
<span className={labelClasses}>{label}</span>
</label>
);
};

View File

@ -0,0 +1 @@
export * from './checkbox';

View File

@ -15,7 +15,9 @@ const Template: ComponentStory<typeof Dialog> = (args) => {
return (
<div>
<Button onClick={() => setOpen(true)}>Open dialog</Button>
<Dialog {...args} open={open} setOpen={setOpen} />
<Dialog {...args} open={open} onChange={setOpen}>
{args.children}
</Dialog>
</div>
);
};
@ -23,38 +25,38 @@ const Template: ComponentStory<typeof Dialog> = (args) => {
export const Default = Template.bind({});
Default.args = {
open: false,
title: 'Title',
title: 'No intent supplied',
children: <p>Some content</p>,
};
export const Primary = Template.bind({});
Primary.args = {
open: false,
title: 'Intent: Primary',
children: <p>Some content</p>,
intent: Intent.Primary,
};
export const Danger = Template.bind({});
Danger.args = {
open: false,
title: 'Danger',
title: 'Intent: Danger',
children: <p>Some content</p>,
intent: Intent.Danger,
};
export const Success = Template.bind({});
Success.args = {
open: false,
title: 'Success',
children: <p>Some content</p>,
intent: Intent.Success,
};
export const Warning = Template.bind({});
Warning.args = {
open: false,
title: 'Warning',
title: 'Intent: Warning',
children: <p>Some content</p>,
intent: Intent.Warning,
};
export const Modal = Template.bind({});
Modal.args = {
export const Success = Template.bind({});
Success.args = {
open: false,
title: 'Modal (Prompt)',
title: 'Intent: Success',
children: <p>Some content</p>,
intent: Intent.Warning,
intent: Intent.Success,
};

View File

@ -2,7 +2,7 @@ import * as DialogPrimitives from '@radix-ui/react-dialog';
import classNames from 'classnames';
import type { ReactNode } from 'react';
import type { Intent } from '../../utils/intent';
import { getIntentShadow } from '../../utils/intent';
import { getIntentShadow, getIntentBorder } from '../../utils/intent';
import { Icon } from '../icon';
interface DialogProps {
@ -30,6 +30,7 @@ export function Dialog({
// Need to apply background and text colors again as content is rendered in a portal
'dark:bg-black dark:text-white-95 bg-white text-black-95',
getIntentShadow(intent),
getIntentBorder(intent),
contentClassNames
);
return (
@ -41,10 +42,13 @@ export function Dialog({
/>
<DialogPrimitives.Content className={contentClasses}>
<DialogPrimitives.Close
className="p-12 absolute top-0 right-0"
className="p-2 absolute top-8 right-8 leading-[0] focus:outline-none focus-visible:outline-none focus-visible:border focus-visible:border-vega-yellow"
data-testid="dialog-close"
>
<Icon name="cross" />
<Icon
name="cross"
className="focus:outline-none focus-visible:outline-none"
/>
</DialogPrimitives.Close>
{title && (
<h1

View File

@ -6,13 +6,11 @@ import {
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuItemIndicator,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from './dropdown-menu';
import { Button } from '../button';
import { Icon } from '../icon';
export default {
@ -31,20 +29,16 @@ export const CheckboxItems = () => {
return (
<div style={{ textAlign: 'center', padding: 50 }}>
<DropdownMenu>
<DropdownMenuTrigger>
<Button appendIconName="chevron-down">Options</Button>
<DropdownMenuTrigger className="w-[300px]">
<span>Select many things</span>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuContent className="w-[300px]">
{checkboxItems.map(({ label, state: [checked, setChecked] }) => (
<DropdownMenuCheckboxItem
key={label}
inset
checked={checked}
onCheckedChange={setChecked}
>
<DropdownMenuItemIndicator>
<Icon name="tick" />
</DropdownMenuItemIndicator>
{label}
</DropdownMenuCheckboxItem>
))}
@ -56,13 +50,13 @@ export const CheckboxItems = () => {
export const RadioItems = () => {
const files = ['README.md', 'index.js', 'page.css'];
const [file, setFile] = useState(files[1]);
const [selected, setSelected] = useState(files[1]);
return (
<div style={{ textAlign: 'center', padding: 50 }}>
<DropdownMenu>
<DropdownMenuTrigger>
<Button appendIconName="chevron-down">Open</Button>
<span>Open</span>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem inset onSelect={() => console.log('minimize')}>
@ -75,19 +69,38 @@ export const RadioItems = () => {
Smaller
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuRadioGroup value={file} onValueChange={setFile}>
<DropdownMenuRadioGroup value={selected} onValueChange={setSelected}>
{files.map((file) => (
<DropdownMenuRadioItem key={file} inset value={file}>
{file}
<DropdownMenuItemIndicator>
<Icon name="tick" />
</DropdownMenuItemIndicator>
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<p>Selected file: {file}</p>
<p>Selected file: {selected}</p>
</div>
);
};
export const IconMenu = () => {
const iconMenuItems = [
{ label: 'IconMenu Item 1' },
{ label: 'IconMenu Item 2' },
];
return (
<div style={{ textAlign: 'center', padding: 50 }}>
<DropdownMenu>
<DropdownMenuTrigger>
<Icon name="cog" />
</DropdownMenuTrigger>
<DropdownMenuContent>
{iconMenuItems.map(({ label }) => (
<DropdownMenuItem key={label}>{label}</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
);
};

View File

@ -1,34 +1,31 @@
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import classNames from 'classnames';
import { forwardRef } from 'react';
import { Button } from '../button';
const itemStyles = classNames([
'text-ui',
'text-black',
'dark:text-white',
'flex',
'items-center',
'justify-between',
'leading-1',
const itemClass = classNames(
'relative',
'flex items-center justify-between',
'text-ui leading-1',
'h-[25px]',
'py-0 pr-8',
'cursor-default',
'hover:cursor-pointer',
'select-none',
'whitespace-nowrap',
'h-[25px]',
'py-0',
'pr-8',
'color-black',
]);
'focus:bg-vega-pink dark:focus:bg-vega-yellow',
'focus:text-white dark:focus:text-black',
'focus:outline-none'
);
const itemClass = classNames(itemStyles, [
'focus:bg-vega-yellow',
'dark:focus:bg-vega-yellow',
'focus:text-black',
'dark:focus:text-black',
'focus:outline-none',
]);
function getItemClasses(inset: boolean) {
return classNames(itemClass, inset ? 'pl-28' : 'pl-4', 'relative');
function getItemClasses(inset: boolean, checked?: boolean) {
return classNames(
itemClass,
inset ? 'pl-28' : 'pl-8',
checked
? 'bg-vega-pink dark:bg-vega-yellow text-white dark:text-black'
: 'text-black dark:text-white'
);
}
/**
@ -40,7 +37,25 @@ export const DropdownMenu = DropdownMenuPrimitive.Root;
* The button that toggles the dropdown menu.
* By default, the {@link DropdownMenuContent} will position itself against the trigger.
*/
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
export const DropdownMenuTrigger = forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,
React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>
>(({ children, className }, forwardedRef) => (
<DropdownMenuPrimitive.Trigger
asChild={true}
ref={forwardedRef}
className="focus-visible:outline-none focus-visible:shadow-inset-vega-pink dark:focus-visible:shadow-inset-vega-yellow"
>
<Button
variant="secondary"
appendIconName="chevron-down"
boxShadow={false}
className={classNames(className, 'justify-between px-8 font-normal')}
>
{children}
</Button>
</DropdownMenuPrimitive.Trigger>
));
/**
* Used to group multiple {@link DropdownMenuRadioItem}s.
@ -58,7 +73,7 @@ export const DropdownMenuContent = forwardRef<
{...contentProps}
ref={forwardedRef}
className={classNames(
'inline-block box-border border-1 border-black bg-white dark:bg-black p-4',
'inline-block box-border border-1 border-black bg-white dark:bg-black-60',
className
)}
/>
@ -92,7 +107,12 @@ export const DropdownMenuCheckboxItem = forwardRef<
<DropdownMenuPrimitive.CheckboxItem
{...checkboxItemProps}
ref={forwardedRef}
className={classNames(getItemClasses(inset), className)}
className={classNames(
getItemClasses(inset, checkboxItemProps.checked),
className,
'hover:shadow-inset-black dark:hover:shadow-inset-white',
'focus:shadow-inset-black dark:focus:shadow-inset-white'
)}
/>
));

View File

@ -1,32 +1,49 @@
import classNames from 'classnames';
import type { ReactNode } from 'react';
import classnames from 'classnames';
interface FormGroupProps {
children: ReactNode;
label?: string;
labelFor?: string;
labelAlign?: 'left' | 'right';
labelDescription?: string;
className?: string;
hasError?: boolean;
}
export const FormGroup = ({
children,
label,
labelFor,
labelDescription,
labelAlign = 'left',
className,
hasError,
}: FormGroupProps) => {
const labelClasses = classNames('block text-ui mb-4', {
'text-right': labelAlign === 'right',
});
return (
<div
data-testid="form-group"
className={className?.includes('mb') ? className : `${className} mb-20`}
className={classnames(className, { 'mb-20': !className?.includes('mb') })}
>
{label && (
<label className={labelClasses} htmlFor={labelFor}>
{label}
<label htmlFor={labelFor}>
<div
className={classNames(
'mb-4 text-body-large text-black dark:text-white',
{
'border-l-4 border-danger pl-8': hasError,
'text-right': labelAlign === 'right',
}
)}
>
<div className="font-bold mb-2">{label}</div>
{labelDescription && (
<div className={classNames({ 'text-danger': hasError })}>
{labelDescription}
</div>
)}
</div>
</label>
)}
{children}

View File

@ -11,20 +11,48 @@ export default {
labelFor: {
type: 'string',
},
labelDescription: {
type: 'string',
},
className: {
type: 'string',
},
hasError: {
type: 'boolean',
},
},
} as Meta;
const Template: Story = (args) => (
<FormGroup {...args} label="label" labelFor="test">
<FormGroup {...args}>
<Input id="labelFor" />
</FormGroup>
);
export const Default = Template.bind({});
Default.args = {
label: 'label',
label: 'Label',
labelFor: 'labelFor',
};
export const Error = Template.bind({});
Error.args = {
label: 'Label',
labelFor: 'labelFor',
hasError: true,
};
export const WithDescription = Template.bind({});
WithDescription.args = {
label: 'Label',
labelFor: 'labelFor',
labelDescription: 'with description text',
};
export const WithDescriptionAndError = Template.bind({});
WithDescriptionAndError.args = {
hasError: true,
label: 'Label',
labelFor: 'labelFor',
labelDescription: 'with description text',
};

View File

@ -1 +0,0 @@
export * from './grid-tabs';

View File

@ -6,10 +6,10 @@ export * from './button';
export * from './callout';
export * from './card';
export * from './copy-with-tooltip';
export * from './checkbox';
export * from './dialog';
export * from './dropdown-menu';
export * from './form-group';
export * from './grid-tabs';
export * from './icon';
export * from './indicator';
export * from './input';
@ -24,6 +24,7 @@ export * from './select';
export * from './sparkline';
export * from './splash';
export * from './syntax-highlighter';
export * from './tabs';
export * from './text-area';
export * from './theme-switcher';
export * from './toggle';

View File

@ -1,6 +1,6 @@
import classNames from 'classnames';
import { Intent } from '../../utils/intent';
import { getVariantBackground } from '../../utils/intent';
import { getIntentTextAndBackground } from '../../utils/intent';
interface IndicatorProps {
variant?: Intent;
@ -9,7 +9,7 @@ interface IndicatorProps {
export const Indicator = ({ variant = Intent.None }: IndicatorProps) => {
const names = classNames(
'inline-block w-8 h-8 mb-2 mr-8 rounded',
getVariantBackground(variant)
getIntentTextAndBackground(variant)
);
return <div className={names} />;
};

View File

@ -131,17 +131,22 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
const hasPrepended = !!(prependIconName || prependElement);
const hasAppended = !!(appendIconName || appendElement);
const inputClassName = classNames('appearance-none', 'h-28', className, {
'pl-28': hasPrepended,
'pr-28': hasAppended,
'border-vega-pink dark:border-vega-pink': hasError,
});
const inputClassName = classNames(
'appearance-none',
'h-28',
'dark:color-scheme-dark',
className,
{
'pl-28': hasPrepended,
'pr-28': hasAppended,
}
);
const input = (
<input
{...props}
ref={ref}
className={classNames(defaultFormElement, inputClassName)}
className={classNames(defaultFormElement(hasError), inputClassName)}
/>
);

View File

@ -1,6 +1,6 @@
import type { ReactNode } from 'react';
import classNames from 'classnames';
import { getVariantBackground } from '../../utils/intent';
import { getIntentTextAndBackground } from '../../utils/intent';
import { Intent } from '../../utils/intent';
interface LozengeProps {
@ -15,7 +15,7 @@ const getLozengeClasses = (
) => {
return classNames(
['rounded-md', 'font-mono', 'leading-none', 'p-4'],
getVariantBackground(variant),
getIntentTextAndBackground(variant),
className
);
};

View File

@ -40,8 +40,8 @@ const priceChangeClassNames = (value: number | bigint) =>
value === 0
? 'text-black dark:text-white'
: value > 0
? `text-green-dark dark:text-green-vega `
: `text-red-dark dark:text-red-vega`;
? `text-vega-green-dark dark:text-vega-green `
: `text-vega-red-dark dark:text-vega-red`;
export const PriceCellChange = React.memo(
({ candles, decimalPlaces }: PriceChangeCellProps) => {

View File

@ -35,9 +35,8 @@ export const Radio = ({ id, value, label, disabled, hasError }: RadioProps) => {
const itemClasses = classNames(
'flex justify-center items-center',
'w-[17px] h-[17px] rounded-full border',
'focus:outline-0 focus-visible:outline-0',
'focus-visible:shadow-radio focus-visible:shadow-vega-pink dark:focus-visible:shadow-vega-yellow',
'border-black-60 dark:border-white-60',
'focus:outline-none focus-visible:outline-none',
'focus-visible:shadow-vega-pink dark:focus-visible:shadow-vega-yellow',
'dark:bg-white-25',
{
'border-black-60 dark:border-white-60': !hasError,

View File

@ -8,7 +8,9 @@ export default {
const Template: Story = (args) => (
<Select {...args}>
<option value="Only option">Only option</option>
<option value="Option 1">Option 1</option>
<option value="Option 2">Option 2</option>
<option value="Option 3">Option 3</option>
</Select>
);

View File

@ -15,9 +15,7 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
<select
ref={ref}
{...props}
className={classNames(defaultFormElement, className, 'h-28', {
'border-vega-pink dark:border-vega-pink': hasError,
})}
className={classNames(defaultFormElement(hasError), className, 'h-28')}
/>
)
);

View File

@ -36,7 +36,7 @@ it('Renders a red line if the last value is less than the first', () => {
const paths = screen.getAllByTestId('sparkline-path');
const path = paths[0];
expect(path).toHaveClass(
'[vector-effect:non-scaling-stroke] stroke-red-dark dark:stroke-red'
'[vector-effect:non-scaling-stroke] stroke-vega-red-dark dark:stroke-vega-red'
);
});
@ -48,7 +48,7 @@ it('Renders a green line if the last value is greater than the first', () => {
const paths = screen.getAllByTestId('sparkline-path');
const path = paths[0];
expect(path).toHaveClass(
'[vector-effect:non-scaling-stroke] stroke-green-dark dark:stroke-green'
'[vector-effect:non-scaling-stroke] stroke-vega-green-dark dark:stroke-vega-green'
);
});

View File

@ -8,8 +8,8 @@ function colorByChange(a: number, b: number) {
return a === b
? 'stroke-black/40 dark:stroke-white/40'
: a < b
? 'stroke-green-dark dark:stroke-green'
: 'stroke-red-dark dark:stroke-red';
? 'stroke-vega-green-dark dark:stroke-vega-green'
: 'stroke-vega-red-dark dark:stroke-vega-red';
}
export interface SparklineProps {

View File

@ -0,0 +1 @@
export * from './tabs';

View File

@ -0,0 +1,33 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Tabs, Tab } from './tabs';
const renderComponent = (
<Tabs>
<Tab id="one" name="Tab one">
<p>Tab one content</p>
</Tab>
<Tab id="two" name="Tab two">
<p>Tab two content</p>
</Tab>
<Tab id="three" name="Tab three">
<p>Tab three content</p>
</Tab>
</Tabs>
);
describe('Tabs', () => {
it('should render tabs successfully', () => {
render(renderComponent);
expect(screen.getByTestId('Tab one')).toBeInTheDocument();
expect(screen.getByTestId('Tab two')).toBeInTheDocument();
expect(screen.getByTestId('Tab three')).toBeInTheDocument();
});
it('shows tabs display the correct content when clicked', async () => {
render(renderComponent);
expect(screen.getByText('Tab one content')).toBeInTheDocument();
await userEvent.click(screen.getByText('Tab two'));
expect(await screen.getByText('Tab two content')).toBeInTheDocument();
});
});

View File

@ -0,0 +1,21 @@
import type { Story, Meta } from '@storybook/react';
import { Tabs, Tab } from './tabs';
export default {
component: Tabs,
title: 'Tabs',
} as Meta;
export const Default: Story = () => (
<Tabs>
<Tab id="one" name="Tab one">
<p>Tab one content</p>
</Tab>
<Tab id="two" name="Tab two">
<p>Tab two content</p>
</Tab>
<Tab id="three" name="Tab three">
<p>Tab three content</p>
</Tab>
</Tabs>
);

View File

@ -1,73 +1,79 @@
import * as Tabs from '@radix-ui/react-tabs';
import * as TabsPrimitive from '@radix-ui/react-tabs';
import classNames from 'classnames';
import type { ReactElement, ReactNode } from 'react';
import { Children, isValidElement, useState } from 'react';
interface GridTabsProps {
children: ReactElement<GridTabProps>[];
interface TabsProps {
children: ReactElement<TabProps>[];
}
export const GridTabs = ({ children }: GridTabsProps) => {
export const Tabs = ({ children }: TabsProps) => {
const [activeTab, setActiveTab] = useState<string>(() => {
return children[0].props.id;
});
return (
<Tabs.Root
<TabsPrimitive.Root
value={activeTab}
className="h-full grid grid-rows-[min-content_1fr]"
onValueChange={(value) => setActiveTab(value)}
>
<div className="bg-black-10 dark:bg-white-10">
<Tabs.List
<TabsPrimitive.List
className="flex flex-nowrap gap-4 overflow-x-auto"
role="tablist"
>
{Children.map(children, (child) => {
if (!isValidElement(child)) return null;
const isActive = child.props.id === activeTab;
const triggerClass = classNames('py-4', 'px-12', 'capitalize', {
'text-black dark:text-vega-yellow': isActive,
'bg-white dark:bg-black': isActive,
'text-black-60 dark:text-white-60': !isActive,
'bg-black-10 dark:bg-white-10': !isActive,
});
const triggerClass = classNames(
'py-4 px-20',
'capitalize',
'focus-visible:outline-none focus-visible:shadow-inset-vega-pink dark:focus-visible:shadow-inset-vega-yellow',
{
'font-semibold text-vega-pink dark:text-vega-yellow': isActive,
'bg-white dark:bg-black': isActive,
'text-black dark:text-white': !isActive,
'bg-white-90 dark:bg-black-70 hover:bg-white-95 dark:hover:bg-black-80':
!isActive,
}
);
return (
<Tabs.Trigger
<TabsPrimitive.Trigger
data-testid={child.props.name}
value={child.props.id}
className={triggerClass}
>
{child.props.name}
</Tabs.Trigger>
</TabsPrimitive.Trigger>
);
})}
</Tabs.List>
</TabsPrimitive.List>
</div>
<div className="h-full overflow-auto">
{Children.map(children, (child) => {
if (!isValidElement(child)) return null;
return (
<Tabs.Content
<TabsPrimitive.Content
value={child.props.id}
className="h-full"
data-testid={`tab-${child.props.id}`}
>
{child.props.children}
</Tabs.Content>
</TabsPrimitive.Content>
);
})}
</div>
</Tabs.Root>
</TabsPrimitive.Root>
);
};
interface GridTabProps {
interface TabProps {
children: ReactNode;
id: string;
name: string;
}
export const GridTab = ({ id, children }: GridTabProps) => {
return <div>{children}</div>;
export const Tab = ({ children, ...props }: TabProps) => {
return <div {...props}>{children}</div>;
};

View File

@ -11,9 +11,7 @@ export interface TextAreaProps
export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
({ className, hasError, ...props }, ref) => {
const classes = classNames(defaultFormElement, className, {
'border-vega-pink dark:border-vega-pink': hasError,
});
const classes = classNames(defaultFormElement(hasError), className);
return <textarea {...props} ref={ref} className={classes} />;
}
);

View File

@ -34,9 +34,10 @@ export const Toggle = ({
'border border-black-60 active:border-black dark:border-white-60 dark:active:border-white peer-checked:border-black dark:peer-checked:border-vega-yellow',
'group-first-of-type:rounded-tl group-first-of-type:rounded-bl group-last-of-type:rounded-tr group-last-of-type:rounded-br',
'px-28 py-4',
'peer-checked:bg-vega-yellow hover:bg-black-25 dark:hover:bg-white-25 hover:peer-checked:bg-vega-yellow',
'text-ui text-black-60 dark:text-white-60 peer-checked:text-black active:text-black dark:active:text-white peer-checked:font-bold text-center',
'cursor-pointer peer-checked:cursor-auto select-none transition-all'
'peer-checked:bg-vega-pink dark:peer-checked:bg-vega-yellow hover:bg-black-10 dark:hover:bg-white-25 hover:peer-checked:bg-vega-pink dark:hover:peer-checked:bg-vega-yellow focus-visible:bg-black-10 dark:focus-visible:bg-white-25',
'text-ui text-center peer-checked:font-bold peer-checked:text-white dark:peer-checked:text-black text-black-60 dark:text-white-60 active:text-black dark:active:text-white focus-visible:text-black dark:focus-visible:text-white',
'focus-within:shadow-inset-black',
'cursor-pointer peer-checked:cursor-auto select-none transition-all duration-75'
);
return (

View File

@ -11,7 +11,7 @@ const Template: Story<TooltipProps> = (args) => <Tooltip {...args} />;
export const Uncontrolled = Template.bind({});
Uncontrolled.args = {
children: <button>Click me!</button>,
children: <button>Hover on me!</button>,
description: 'Tooltip content!',
};

View File

@ -21,15 +21,19 @@ export const Tooltip = ({ children, description, open, align }: TooltipProps) =>
<Root open={open}>
<Trigger asChild>{children}</Trigger>
<Content align={align} alignOffset={5}>
<div className="relative z-0 p-8 bg-black-50 border border-black-60 text-white rounded-sm max-w-sm">
{description}
</div>
<Arrow
width={10}
height={5}
offset={10}
className="fill-vega-green"
className="z-[1] fill-black-60 dark:fill-white-60 z-0 translate-x-[1px] translate-y-[-1px]"
/>
<Arrow
width={8}
height={4}
className="z-[1] translate-y-[-1px] fill-black-50"
/>
<div className="px-12 py-8 bg-vega-green text-black rounded-sm max-w-sm">
{description}
</div>
</Content>
</Root>
</Provider>

View File

@ -30,11 +30,28 @@ full colour palette [here](https://tailwindcss.com/docs/customizing-colors/#defa
subtitle="Black"
colors={theme.colors.black}
/>
<ColorItem
title="theme.color.blue"
subtitle="Blue"
colors={[theme.colors.blue]}
/>
<ColorItem
title="theme.color.coral"
subtitle="Coral"
colors={[theme.colors.coral]}
/>
<ColorItem
title="theme.color.orange"
subtitle="Orange"
colors={[theme.colors.orange]}
/>
<ColorPalette>
<ColorItem
title="theme.color.selected"
subtitle="Selected"
colors={[theme.colors.selected]}
/>
</ColorPalette>
</ColorPalette>
### Vega
@ -51,13 +68,23 @@ full colour palette [here](https://tailwindcss.com/docs/customizing-colors/#defa
<ColorPalette>
<ColorItem
title="theme.color.intent"
subtitle="Intent"
colors={{
success: theme.colors.success,
warning: theme.colors.warning,
danger: theme.colors.danger,
}}
title="theme.color.danger"
subtitle="danger"
colors={[theme.colors.danger]}
/>
</ColorPalette>
<ColorPalette>
<ColorItem
title="theme.color.warning"
subtitle="warning"
colors={[theme.colors.warning]}
/>
</ColorPalette>
<ColorPalette>
<ColorItem
title="theme.color.success"
subtitle="success"
colors={[theme.colors.success]}
/>
</ColorPalette>

View File

@ -1,30 +1,40 @@
export enum Intent {
None,
Primary,
Success,
Warning,
Danger,
Warning,
Success,
}
export const getIntentShadow = (intent?: Intent) => {
export const getIntentShadow = (intent = Intent.None) => {
return {
'shadow-callout': true,
'shadow-intent': true,
'shadow-danger': intent === Intent.Danger,
'shadow-warning': intent === Intent.Warning,
'shadow-black dark:shadow-white':
intent === Intent.None || intent === Intent.Primary,
'shadow-success': intent === Intent.Success,
'shadow-black dark:shadow-white': intent === Intent.None,
'shadow-vega-pink dark:shadow-vega-yellow': intent === Intent.Primary,
};
};
export const getVariantBackground = (variant?: Intent) => {
export const getIntentBorder = (intent = Intent.None) => {
return {
'bg-black text-white dark:bg-white dark:text-black':
variant === Intent.None,
'bg-vega-pink text-black dark:bg-vega-yellow dark:text-black-normal':
variant === Intent.Primary,
'bg-danger text-white': variant === Intent.Danger,
'bg-warning text-black': variant === Intent.Warning,
'bg-success text-black': variant === Intent.Success,
border: true,
'border-danger': intent === Intent.Danger,
'border-warning': intent === Intent.Warning,
'border-black dark:border-white':
intent === Intent.None || intent === Intent.Primary,
'border-success': intent === Intent.Success,
};
};
export const getIntentTextAndBackground = (intent = Intent.None) => {
return {
'bg-black text-white dark:bg-white text-black': intent === Intent.None,
'bg-vega-pink text-black dark:bg-vega-yellow dark:text-black-normal':
intent === Intent.Primary,
'bg-danger text-white': intent === Intent.Danger,
'bg-warning text-black': intent === Intent.Warning,
'bg-success text-black': intent === Intent.Success,
};
};

View File

@ -1,14 +1,22 @@
export const defaultFormElement = [
'flex items-center w-full',
'box-border',
'border rounded-none',
'bg-clip-padding',
'border-black-60 dark:border-white-60',
'bg-black-25 dark:bg-white-25',
'text-black placeholder:text-black-60 dark:text-white dark:placeholder:text-white-60',
'text-ui',
'px-8',
'focus-visible:shadow-focus dark:focus-visible:shadow-focus-dark',
'focus-visible:outline-0',
'disabled:bg-black-10 disabled:dark:bg-white-10',
];
import classnames from 'classnames';
export const defaultFormElement = (hasError?: boolean) =>
classnames(
'flex items-center w-full',
'box-border',
'border rounded-none',
'bg-clip-padding',
'shadow-input dark:shadow-input-dark',
'bg-white dark:bg-white-25',
'text-black placeholder:text-black-60 dark:text-white dark:placeholder:text-white-60',
'text-ui',
'px-8',
'focus-visible:outline-none',
'disabled:bg-black-10 disabled:dark:bg-white-10',
{
'input-border dark:input-border-dark focus-visible:shadow-input-focus dark:focus-visible:shadow-input-focus-dark':
!hasError,
'border-vega-red focus:shadow-input-focus-error dark:focus:shadow-input-focus-error-dark':
hasError,
}
);

View File

@ -23,7 +23,7 @@ export const VegaManageDialog = ({
title={t('SELECT A VEGA KEY')}
open={dialogOpen}
onChange={setDialogOpen}
intent={Intent.Warning}
intent={Intent.Primary}
>
<div className="text-ui">
{keypairs ? (

View File

@ -22,7 +22,7 @@ export const Web3ConnectDialog = ({
<Dialog
open={dialogOpen}
onChange={setDialogOpen}
intent={Intent.Warning}
intent={Intent.Primary}
title={t('Connect to your Ethereum wallet')}
>
<ul data-testid="web3-connector-list">

View File

@ -24,12 +24,14 @@
"@radix-ui/react-dropdown-menu": "^0.1.6",
"@radix-ui/react-icons": "^1.1.1",
"@radix-ui/react-radio-group": "^0.1.5",
"@radix-ui/react-select": "^0.1.1",
"@radix-ui/react-tabs": "^0.1.5",
"@radix-ui/react-tooltip": "^0.1.7",
"@sentry/nextjs": "^6.19.3",
"@sentry/react": "^6.19.2",
"@sentry/tracing": "^6.19.2",
"@vegaprotocol/vegawallet-service-api-client": "^0.4.13",
"@testing-library/user-event": "^14.2.1",
"@walletconnect/ethereum-provider": "^1.7.5",
"@web3-react/core": "8.0.20-beta.0",
"@web3-react/metamask": "8.0.16-beta.0",

View File

@ -3349,6 +3349,13 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.5.tgz#db5a11bf66bdab39569719555b0f76e138d7bd64"
integrity sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==
"@radix-ui/number@0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-0.1.0.tgz#73ad13d5cc5f75fa5e147d72e5d5d5e50d688256"
integrity sha512-rpf6QiOWLHAkM4FEMYu9i+5Jr8cKT893+R4mPpcdsy4LD7omr9JfdOqj/h/xPA5+EcVrpMMlU6rrRYpUB5UI8g==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/popper@0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/popper/-/popper-0.1.0.tgz#c387a38f31b7799e1ea0d2bb1ca0c91c2931b063"
@ -3613,6 +3620,31 @@
"@radix-ui/react-use-callback-ref" "0.1.0"
"@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-select@^0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-0.1.1.tgz#ceedea6856a37e4079492e1c69601797cedd2c85"
integrity sha512-xY3jLz2c6ZBHflldGsA79bE/swUfRMpiRPQf+JDihOufsd3z5uW22PFe0MS5bGZiIpK6aAZAvwqEd2Bu7hqp8w==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/number" "0.1.0"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-collection" "0.1.4"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-dismissable-layer" "0.1.5"
"@radix-ui/react-focus-scope" "0.1.4"
"@radix-ui/react-id" "0.1.5"
"@radix-ui/react-label" "0.1.5"
"@radix-ui/react-portal" "0.1.4"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-callback-ref" "0.1.0"
"@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-use-layout-effect" "0.1.0"
"@radix-ui/react-use-previous" "0.1.1"
"@radix-ui/react-visually-hidden" "0.1.4"
aria-hidden "^1.1.1"
react-remove-scroll "^2.4.0"
"@radix-ui/react-slot@0.1.2":
version "0.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-0.1.2.tgz#e6f7ad9caa8ce81cc8d532c854c56f9b8b6307c8"
@ -5816,6 +5848,11 @@
"@babel/runtime" "^7.12.5"
"@testing-library/dom" "^8.0.0"
"@testing-library/user-event@^14.2.1":
version "14.2.1"
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.2.1.tgz#8c5ff2d004544bb2220e1d864f7267fe7eb6c556"
integrity sha512-HOr1QiODrq+0j9lKU5i10y9TbhxMBMRMGimNx10asdmau9cb8Xb1Vyg0GvTwyIL2ziQyh2kAloOtAQFBQVuecA==
"@tootallnate/once@1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"