Setup dark and light theme in trading app

This commit is contained in:
Bartłomiej Głownia 2022-03-09 14:05:42 +01:00 committed by Matthew Russell
parent e8a795461d
commit ebbd50edf3
12 changed files with 150 additions and 121 deletions

View File

@ -1,13 +1,14 @@
export function Vega({ className }: { className?: string }) {
return (
<svg className="w-[76px] h-[16px]" viewBox="0 0 283.5 61.3">
<path d="M26.6,53.1L44,0h8.8L31.2,61.3h-9.5L0,0h8.9L26.6,53.1z" />
<path d="M89.6,33.3v21.1h34.3v6.9H81.3V0h41.7V7H89.6v19.3h29.8v6.9H89.6z" />
<path
d="M156.8,7.5h7.4V54h-7.4V7.5z M193.7,0v7.5h-29.5V0H193.7z M164.2,61.3V54h29.5v7.4H164.2z M201.1,54h-7.4V39.2H179v-7.4
h22.1V54z M201.1,7.5v7.4h-7.4V7.5H201.1z"
/>
<path d="M283.5,61.3h-8.6L270.4,49h-30.8L235,61.3h-8.4L250.4,0h9.3L283.5,61.3z M254.8,7.4L242.2,42h25.6L255,7.4H254.8z" />
<svg
width="86"
height="19"
viewBox="0 0 86 19"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path d="M8.05624 16.4619L13.3478 0.0158333H16.0045L9.45138 18.9881H6.58031L0 0.0158333H2.70328L8.05624 16.4619ZM27.1835 10.3067V16.8372H37.5787V18.9873H24.6884V0.0150417H37.3503V2.16442H27.1835V8.16288H36.2395V10.3067H27.1835ZM47.5793 2.31167H49.8165V16.716H47.5793V2.31167V2.31167ZM58.7676 0V2.31167H49.8157V0H58.7683H58.7676ZM49.8157 19V16.716H58.7683V19H49.8157ZM61.0055 16.716H58.7683V12.1481H54.2924V9.86021H61.0055V16.716ZM61.0055 2.31167V4.59483H58.7683V2.31167H61.0055V2.31167ZM86 18.9873H83.3946L82.0251 15.1668H72.6801L71.3106 18.9873H68.7635L75.9769 0.0150417H78.7936L86 18.9873V18.9873ZM77.3138 2.299L73.4694 13.0213H81.239L77.3674 2.29821H77.313L77.3138 2.299Z" />
</svg>
);
}

View File

@ -1,21 +1,18 @@
import { useRouter } from 'next/router';
import classNames from 'classnames';
import { Vega } from '../icons/vega';
import Link from 'next/link';
import { AnchorButton } from '@vegaprotocol/ui-toolkit';
export const Navbar = () => {
const navClasses = classNames(
'flex items-center',
'border-neutral-200 border-b'
);
return (
<nav className={navClasses}>
<nav className="flex items-center">
<Link href="/" passHref={true}>
<a className="px-8">
<Vega />
<a className="px-[26px]">
<Vega className="fill-black dark:fill-white" />
</a>
</Link>
{[
{ name: 'Trading', path: '/', exact: true },
{ name: 'Portfolio', path: '/portfolio' },
{ name: 'Markets', path: '/markets' },
].map((route) => (
@ -28,18 +25,17 @@ export const Navbar = () => {
interface NavLinkProps {
name: string;
path: string;
exact?: boolean;
}
const NavLink = ({ name, path }: NavLinkProps) => {
const NavLink = ({ name, path, exact }: NavLinkProps) => {
const router = useRouter();
const className = classNames('inline-block', 'p-8', {
// Handle direct math and child page matches
'text-vega-pink': router.asPath === path || router.asPath.startsWith(path),
});
const isActive =
router.asPath === path || (!exact && router.asPath.startsWith(path));
return (
<a
className={className}
<AnchorButton
variant={isActive ? 'accent' : 'inline'}
className="px-16 h-[38px] text-h4 uppercase border-0"
href={path}
onClick={(e) => {
e.preventDefault();
@ -47,6 +43,6 @@ const NavLink = ({ name, path }: NavLinkProps) => {
}}
>
{name}
</a>
</AnchorButton>
);
};

View File

@ -1,20 +1,45 @@
import { ApolloProvider } from '@apollo/client';
import { AppProps } from 'next/app';
import Head from 'next/head';
import { useMemo } from 'react';
import { useCallback, useMemo } from 'react';
import { Navbar } from '../components/navbar';
import { createClient } from '../lib/apollo-client';
import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit';
import './styles.css';
function VegaTradingApp({ Component, pageProps }: AppProps) {
const client = useMemo(() => createClient(process.env['NX_VEGA_URL']), []);
useCallback(() => {
if (
localStorage.theme === 'dark' ||
(!('theme' in localStorage) &&
window.matchMedia('(prefers-color-scheme: dark)').matches)
) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}, []);
const setTheme = () => {
localStorage.theme = document.documentElement.classList.toggle('dark')
? 'dark'
: undefined;
};
return (
<ApolloProvider client={client}>
<Head>
<title>Welcome to trading!</title>
<link
rel="icon"
href="https://vega.xyz/favicon-32x32.png"
type="image/png"
/>
</Head>
<div className="h-full grid grid-rows-[min-content,_1fr] dark:bg-black dark:text-white-60 bg-white text-black-60">
<Navbar />
<div className="h-full dark:bg-black dark:text-white-60 bg-white text-black-60">
<div className="flex items-center border-b-[7px] border-vega-yellow">
<Navbar />
<ThemeSwitcher onToggle={setTheme} className="ml-auto mr-8 -my-2" />
</div>
<main>
<Component {...pageProps} />
</main>

View File

@ -1,6 +1,4 @@
import { EtherscanLink } from '@vegaprotocol/ui-toolkit';
import { Callout } from '@vegaprotocol/ui-toolkit';
import { ReactHelpers } from '@vegaprotocol/react-helpers';
import { Callout, Button } from '@vegaprotocol/ui-toolkit';
export function Index() {
/*
@ -9,12 +7,20 @@ export function Index() {
* Note: The corresponding styles are in the ./index.scss file.
*/
return (
<div>
<Callout title="Hello there" headingLevel={1}>
Welcome trading 👋
<div className="m-24 ">
<Callout
intent="help"
title="This is what this thing does"
iconName="endorsed"
headingLevel={1}
>
<div className="flex flex-col">
<div>With a longer explaination</div>
<Button className="block mt-8" variant="secondary">
Action
</Button>
</div>
</Callout>
<EtherscanLink chainId={null} address="address" />
<ReactHelpers />
</div>
);
}

View File

@ -2,8 +2,9 @@ import { AnchorHTMLAttributes, ButtonHTMLAttributes, forwardRef } from 'react';
import classNames from 'classnames';
import { Icon, IconName } from '../icon';
import {
paddingLeftProvided,
paddingRightProvided,
includesLeftPadding,
includesRightPadding,
includesBorderWidth,
} from '../../utils/class-names';
interface CommonProps {
@ -25,6 +26,9 @@ const getClassName = (
className: CommonProps['className'],
variant: CommonProps['variant']
) => {
const paddingLeftProvided = includesLeftPadding(className);
const paddingRightProvided = includesRightPadding(className);
const borderWidthProvided = includesBorderWidth(className);
return classNames(
[
'inline-flex',
@ -32,7 +36,6 @@ const getClassName = (
'justify-center',
'box-border',
'h-28',
'border',
'text-ui',
'no-underline',
'hover:underline',
@ -40,8 +43,10 @@ const getClassName = (
'transition-all',
],
{
'pl-28': !paddingLeftProvided(className) || variant === 'inline',
'pr-28': !paddingRightProvided(className) || variant === 'inline',
'pl-28': !(paddingLeftProvided || variant === 'inline'),
'pr-28': !(paddingRightProvided || variant === 'inline'),
border: !borderWidthProvided,
'hover:border-black dark:hover:border-white': variant !== 'inline',
'active:border-black dark:active:border-white': true,
@ -70,11 +75,12 @@ const getClassName = (
variant === 'accent' || variant === 'inline',
'hover:bg-vega-yellow-dark dark:hover:bg-vega-yellow/30':
variant === 'accent',
'text-black dark:text-black': variant === 'accent',
'hover:text-white dark:hover:text-white': variant === 'accent',
'pl-4': variant === 'inline',
'pr-4': variant === 'inline',
'border-0': variant === 'inline',
'pl-4': variant === 'inline' && !paddingLeftProvided,
'pr-4': variant === 'inline' && !paddingRightProvided,
'border-0': variant === 'inline' && !borderWidthProvided,
underline: variant === 'inline',
'hover:no-underline': variant === 'inline',
'hover:border-transparent dark:hover:border-transparent':

View File

@ -2,8 +2,8 @@ import { InputHTMLAttributes, forwardRef } from 'react';
import classNames from 'classnames';
import { Icon, IconName } from '../icon';
import {
paddingLeftProvided,
paddingRightProvided,
includesLeftPadding,
includesRightPadding,
} from '../../utils/class-names';
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
@ -36,8 +36,8 @@ export const inputClassNames = ({
'disabled:bg-black-10 disabled:dark:bg-white-10',
],
{
'pl-8': !paddingLeftProvided(className),
'pr-8': !paddingRightProvided(className),
'pl-8': !includesLeftPadding(className),
'pr-8': !includesRightPadding(className),
'border-vega-pink dark:border-vega-pink': hasError,
},
className

View File

@ -1,41 +1,32 @@
export const ThemeSwitcher = ({ onToggle }: { onToggle: () => void }) => (
<button type="button" onClick={() => onToggle()}>
<span className="dark:hidden">
<svg
viewBox="0 0 24 24"
fill="none"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="w-24 h-24"
>
<path
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
className="fill-blue/60 stroke-blue-500"
></path>
<path
d="M12 4v1M17.66 6.344l-.828.828M20.005 12.004h-1M17.66 17.664l-.828-.828M12 20.01V19M6.34 17.664l.835-.836M3.995 12.004h1.01M6 6l.835.836"
className="stroke-blue"
></path>
export const ThemeSwitcher = ({
onToggle,
className,
}: {
onToggle: () => void;
className?: string;
}) => (
<button type="button" onClick={() => onToggle()} className={className}>
<span className="dark:hidden text-black">
<svg viewBox="0 0 45 45" className="w-32 h-32">
<g>
<path
d="M22.5 27.79a5.29 5.29 0 1 0 0-10.58 5.29 5.29 0 0 0 0 10.58Z"
fill="currentColor"
></path>
<path
d="M15.01 22.5H10M35 22.5h-5.01M22.5 29.99V35M22.5 10v5.01M17.21 27.79l-3.55 3.55M31.34 13.66l-3.55 3.55M27.79 27.79l3.55 3.55M13.66 13.66l3.55 3.55"
stroke="currentColor"
strokeWidth="1.3"
strokeMiterlimit="10"
></path>
</g>
</svg>
</span>
<span className="hidden dark:inline">
<svg viewBox="0 0 24 24" fill="none" className="w-24 h-24">
<span className="hidden dark:inline text-white">
<svg viewBox="0 0 45 45" className="w-32 h-32">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M17.715 15.15A6.5 6.5 0 0 1 9 6.035C6.106 6.922 4 9.645 4 12.867c0 3.94 3.153 7.136 7.042 7.136 3.101 0 5.734-2.032 6.673-4.853Z"
className="fill-blue/20"
></path>
<path
d="m17.715 15.15.95.316a1 1 0 0 0-1.445-1.185l.495.869ZM9 6.035l.846.534a1 1 0 0 0-1.14-1.49L9 6.035Zm8.221 8.246a5.47 5.47 0 0 1-2.72.718v2a7.47 7.47 0 0 0 3.71-.98l-.99-1.738Zm-2.72.718A5.5 5.5 0 0 1 9 9.5H7a7.5 7.5 0 0 0 7.5 7.5v-2ZM9 9.5c0-1.079.31-2.082.845-2.93L8.153 5.5A7.47 7.47 0 0 0 7 9.5h2Zm-4 3.368C5 10.089 6.815 7.75 9.292 6.99L8.706 5.08C5.397 6.094 3 9.201 3 12.867h2Zm6.042 6.136C7.718 19.003 5 16.268 5 12.867H3c0 4.48 3.588 8.136 8.042 8.136v-2Zm5.725-4.17c-.81 2.433-3.074 4.17-5.725 4.17v2c3.552 0 6.553-2.327 7.622-5.537l-1.897-.632Z"
className="fill-blue"
></path>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M17 3a1 1 0 0 1 1 1 2 2 0 0 0 2 2 1 1 0 1 1 0 2 2 2 0 0 0-2 2 1 1 0 1 1-2 0 2 2 0 0 0-2-2 1 1 0 1 1 0-2 2 2 0 0 0 2-2 1 1 0 0 1 1-1Z"
className="fill-blue"
d="M28.75 11.69A12.39 12.39 0 0 0 22.5 10a12.5 12.5 0 1 0 0 25c2.196 0 4.353-.583 6.25-1.69A12.46 12.46 0 0 0 35 22.5a12.46 12.46 0 0 0-6.25-10.81Zm-6.25 22a11.21 11.21 0 0 1-11.2-11.2 11.21 11.21 0 0 1 11.2-11.2c1.246 0 2.484.209 3.66.62a13.861 13.861 0 0 0-5 10.58 13.861 13.861 0 0 0 5 10.58 11.078 11.078 0 0 1-3.66.63v-.01Z"
fill="currentColor"
></path>
</svg>
</span>

View File

@ -1,6 +1,6 @@
import * as EthereumUtils from './utils/web3';
export { Button } from './components/button';
export { Button, AnchorButton } from './components/button';
export { Callout } from './components/callout';
export { EthereumUtils };
export { EtherscanLink } from './components/etherscan-link';

View File

@ -1,39 +1,39 @@
import { paddingLeftProvided, paddingRightProvided } from './class-names';
import { includesLeftPadding, includesRightPadding } from './class-names';
test('paddingLeftProvided detects class name which affects left padding', () => {
expect(paddingLeftProvided()).toEqual(false);
expect(paddingLeftProvided('')).toEqual(false);
expect(paddingLeftProvided('pl-8')).toEqual(true);
expect(paddingLeftProvided('pl-16')).toEqual(true);
expect(paddingLeftProvided(' pl-16')).toEqual(true);
expect(paddingLeftProvided('prepend pl-8')).toEqual(true);
expect(paddingLeftProvided('pl-16 ')).toEqual(true);
expect(paddingLeftProvided('pl-16 append')).toEqual(true);
expect(paddingLeftProvided('px-8')).toEqual(true);
expect(paddingLeftProvided('px-16')).toEqual(true);
expect(paddingLeftProvided(' px-16')).toEqual(true);
expect(paddingLeftProvided('prepend px-8')).toEqual(true);
expect(paddingLeftProvided('px-16 ')).toEqual(true);
expect(paddingLeftProvided('px-16 append')).toEqual(true);
expect(paddingLeftProvided('px-16a')).toEqual(false);
expect(paddingLeftProvided('apx-16')).toEqual(false);
test('includesLeftPadding detects class name which affects left padding', () => {
expect(includesLeftPadding()).toEqual(false);
expect(includesLeftPadding('')).toEqual(false);
expect(includesLeftPadding('pl-8')).toEqual(true);
expect(includesLeftPadding('pl-16')).toEqual(true);
expect(includesLeftPadding(' pl-16')).toEqual(true);
expect(includesLeftPadding('prepend pl-8')).toEqual(true);
expect(includesLeftPadding('pl-16 ')).toEqual(true);
expect(includesLeftPadding('pl-16 append')).toEqual(true);
expect(includesLeftPadding('px-8')).toEqual(true);
expect(includesLeftPadding('px-16')).toEqual(true);
expect(includesLeftPadding(' px-16')).toEqual(true);
expect(includesLeftPadding('prepend px-8')).toEqual(true);
expect(includesLeftPadding('px-16 ')).toEqual(true);
expect(includesLeftPadding('px-16 append')).toEqual(true);
expect(includesLeftPadding('px-16a')).toEqual(false);
expect(includesLeftPadding('apx-16')).toEqual(false);
});
test('paddingRightProvided detects class name which affects right padding', () => {
expect(paddingRightProvided()).toEqual(false);
expect(paddingRightProvided('')).toEqual(false);
expect(paddingRightProvided('pr-8')).toEqual(true);
expect(paddingRightProvided('pr-16')).toEqual(true);
expect(paddingRightProvided(' pr-16')).toEqual(true);
expect(paddingRightProvided('prepend pr-8')).toEqual(true);
expect(paddingRightProvided('pr-16 ')).toEqual(true);
expect(paddingRightProvided('pr-16 append')).toEqual(true);
expect(paddingRightProvided('px-8')).toEqual(true);
expect(paddingRightProvided('px-16')).toEqual(true);
expect(paddingRightProvided(' px-16')).toEqual(true);
expect(paddingRightProvided('prepend px-8')).toEqual(true);
expect(paddingRightProvided('px-16 ')).toEqual(true);
expect(paddingRightProvided('px-16 append')).toEqual(true);
expect(paddingRightProvided('px-16a')).toEqual(false);
expect(paddingRightProvided('apx-16')).toEqual(false);
test('includesRightPadding detects class name which affects right padding', () => {
expect(includesRightPadding()).toEqual(false);
expect(includesRightPadding('')).toEqual(false);
expect(includesRightPadding('pr-8')).toEqual(true);
expect(includesRightPadding('pr-16')).toEqual(true);
expect(includesRightPadding(' pr-16')).toEqual(true);
expect(includesRightPadding('prepend pr-8')).toEqual(true);
expect(includesRightPadding('pr-16 ')).toEqual(true);
expect(includesRightPadding('pr-16 append')).toEqual(true);
expect(includesRightPadding('px-8')).toEqual(true);
expect(includesRightPadding('px-16')).toEqual(true);
expect(includesRightPadding(' px-16')).toEqual(true);
expect(includesRightPadding('prepend px-8')).toEqual(true);
expect(includesRightPadding('px-16 ')).toEqual(true);
expect(includesRightPadding('px-16 append')).toEqual(true);
expect(includesRightPadding('px-16a')).toEqual(false);
expect(includesRightPadding('apx-16')).toEqual(false);
});

View File

@ -1,4 +1,8 @@
export const paddingLeftProvided = (className?: string) =>
export const includesLeftPadding = (className?: string) =>
!!className?.match(/(^| )p(l|x)-\d+( |$)/);
export const paddingRightProvided = (className?: string) =>
export const includesRightPadding = (className?: string) =>
!!className?.match(/(^| )p(r|x)-\d+( |$)/);
export const includesBorderWidth = (className?: string) =>
!!className?.match(/(^| )border-\d+( |$)/);