* chore: switch theme to zustand, delete context * chore: switch apps/componenets to consume the hook * chore: update storybook theme usage to use documentElement * chore: dry up theme switcher listener for storybooks * feat: optional theme param to allow toggling * chore: add additional check for matchMedia function * chore: change block explorer test to use light theme as its the default * chore: remove unused headerprops for multisig-signer * chore: remove unused props from theme switcher component * chore: dry up validateTheme func * chore: remove unused props from explorer header test * chore: use new theme switcher in account history container
This commit is contained in:
parent
df609d442c
commit
0b4f918208
@ -1,7 +1,5 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { ThemeContext } from '@vegaprotocol/react-helpers';
|
|
||||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
|
||||||
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
|
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
|
||||||
import {
|
import {
|
||||||
VegaConnectDialog,
|
VegaConnectDialog,
|
||||||
@ -18,8 +16,7 @@ import useLocalValues from './hooks/use-local-values';
|
|||||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [theme, toggleTheme] = useThemeSwitcher();
|
const localValues = useLocalValues();
|
||||||
const localValues = useLocalValues(theme, toggleTheme);
|
|
||||||
const {
|
const {
|
||||||
vegaWalletDialog,
|
vegaWalletDialog,
|
||||||
menu: { setMenuOpen },
|
menu: { setMenuOpen },
|
||||||
@ -56,25 +53,23 @@ function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<EnvironmentProvider>
|
<EnvironmentProvider>
|
||||||
<ThemeContext.Provider value={theme}>
|
<NetworkLoader cache={cacheConfig}>
|
||||||
<NetworkLoader cache={cacheConfig}>
|
<VegaWalletProvider>
|
||||||
<VegaWalletProvider>
|
<LocalContext.Provider value={localValues}>
|
||||||
<LocalContext.Provider value={localValues}>
|
<AppLoader>
|
||||||
<AppLoader>
|
<div className="max-h-full min-h-full dark:bg-lite-black dark:text-neutral-200 bg-white text-neutral-800 grid grid-rows-[min-content,1fr]">
|
||||||
<div className="max-h-full min-h-full dark:bg-lite-black dark:text-neutral-200 bg-white text-neutral-800 grid grid-rows-[min-content,1fr]">
|
<Header />
|
||||||
<Header />
|
<Main />
|
||||||
<Main />
|
<VegaConnectDialog connectors={Connectors} />
|
||||||
<VegaConnectDialog connectors={Connectors} />
|
<VegaManageDialog
|
||||||
<VegaManageDialog
|
dialogOpen={vegaWalletDialog.manage}
|
||||||
dialogOpen={vegaWalletDialog.manage}
|
setDialogOpen={vegaWalletDialog.setManage}
|
||||||
setDialogOpen={vegaWalletDialog.setManage}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</AppLoader>
|
||||||
</AppLoader>
|
</LocalContext.Provider>
|
||||||
</LocalContext.Provider>
|
</VegaWalletProvider>
|
||||||
</VegaWalletProvider>
|
</NetworkLoader>
|
||||||
</NetworkLoader>
|
|
||||||
</ThemeContext.Provider>
|
|
||||||
</EnvironmentProvider>
|
</EnvironmentProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
import React, {
|
import React, { forwardRef, useCallback, useMemo, useRef } from 'react';
|
||||||
forwardRef,
|
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
} from 'react';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
||||||
import { ThemeContext, useScreenDimensions } from '@vegaprotocol/react-helpers';
|
import {
|
||||||
|
useScreenDimensions,
|
||||||
|
useThemeSwitcher,
|
||||||
|
} from '@vegaprotocol/react-helpers';
|
||||||
import type {
|
import type {
|
||||||
GridOptions,
|
GridOptions,
|
||||||
GetRowIdParams,
|
GetRowIdParams,
|
||||||
@ -43,7 +40,7 @@ const ConsoleLiteGrid = <T extends { id?: string }>(
|
|||||||
) => {
|
) => {
|
||||||
const { isMobile, screenSize } = useScreenDimensions();
|
const { isMobile, screenSize } = useScreenDimensions();
|
||||||
const gridRef = useRef<AgGridReact | null>(null);
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
const theme = useContext(ThemeContext);
|
const { theme } = useThemeSwitcher();
|
||||||
const handleOnGridReady = useCallback(() => {
|
const handleOnGridReady = useCallback(() => {
|
||||||
(
|
(
|
||||||
(ref as React.RefObject<AgGridReact>) || gridRef
|
(ref as React.RefObject<AgGridReact>) || gridRef
|
||||||
|
@ -11,8 +11,6 @@ const Header = () => {
|
|||||||
}));
|
}));
|
||||||
const {
|
const {
|
||||||
vegaWalletDialog: { setManage },
|
vegaWalletDialog: { setManage },
|
||||||
theme,
|
|
||||||
toggleTheme,
|
|
||||||
} = useContext(LocalContext);
|
} = useContext(LocalContext);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -25,7 +23,7 @@ const Header = () => {
|
|||||||
setConnectDialog={updateVegaWalletDialog}
|
setConnectDialog={updateVegaWalletDialog}
|
||||||
setManageDialog={setManage}
|
setManageDialog={setManage}
|
||||||
/>
|
/>
|
||||||
<ThemeSwitcher theme={theme} onToggle={toggleTheme} className="-my-4" />
|
<ThemeSwitcher className="-my-4" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -14,8 +14,6 @@ interface MenuState {
|
|||||||
export interface LocalValues {
|
export interface LocalValues {
|
||||||
vegaWalletDialog: VegaWalletDialogState;
|
vegaWalletDialog: VegaWalletDialogState;
|
||||||
menu: MenuState;
|
menu: MenuState;
|
||||||
theme: 'light' | 'dark';
|
|
||||||
toggleTheme: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const LocalContext = createContext<LocalValues>({} as LocalValues);
|
const LocalContext = createContext<LocalValues>({} as LocalValues);
|
||||||
|
@ -3,8 +3,7 @@ import useLocalValues from './use-local-values';
|
|||||||
|
|
||||||
describe('local values hook', () => {
|
describe('local values hook', () => {
|
||||||
it('state of wallet dialog should be properly handled', () => {
|
it('state of wallet dialog should be properly handled', () => {
|
||||||
const setTheme = jest.fn();
|
const { result } = renderHook(() => useLocalValues());
|
||||||
const { result } = renderHook(() => useLocalValues('light', setTheme));
|
|
||||||
expect(result.current.vegaWalletDialog).toBeDefined();
|
expect(result.current.vegaWalletDialog).toBeDefined();
|
||||||
expect(result.current.vegaWalletDialog.manage).toBe(false);
|
expect(result.current.vegaWalletDialog.manage).toBe(false);
|
||||||
act(() => {
|
act(() => {
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import type { LocalValues } from '../context/local-context';
|
import type { LocalValues } from '../context/local-context';
|
||||||
|
|
||||||
const useLocalValues = (theme: 'light' | 'dark', toggleTheme: () => void) => {
|
const useLocalValues = () => {
|
||||||
const [manage, setManage] = useState<boolean>(false);
|
const [manage, setManage] = useState<boolean>(false);
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
return useMemo<LocalValues>(
|
return useMemo<LocalValues>(
|
||||||
() => ({
|
() => ({
|
||||||
vegaWalletDialog: { manage, setManage },
|
vegaWalletDialog: { manage, setManage },
|
||||||
menu: { menuOpen, setMenuOpen, onToggle: () => setMenuOpen(!menuOpen) },
|
menu: { menuOpen, setMenuOpen, onToggle: () => setMenuOpen(!menuOpen) },
|
||||||
theme,
|
|
||||||
toggleTheme,
|
|
||||||
}),
|
}),
|
||||||
[manage, theme, toggleTheme, menuOpen]
|
[manage, menuOpen]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import { useState, useEffect } from 'react';
|
|||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
import { BrowserTracing } from '@sentry/tracing';
|
import { BrowserTracing } from '@sentry/tracing';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { ThemeContext, useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
|
||||||
import {
|
import {
|
||||||
EnvironmentProvider,
|
EnvironmentProvider,
|
||||||
NetworkLoader,
|
NetworkLoader,
|
||||||
@ -19,7 +18,6 @@ import type { InMemoryCacheConfig } from '@apollo/client';
|
|||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { VEGA_ENV } = useEnvironment();
|
const { VEGA_ENV } = useEnvironment();
|
||||||
const [theme, toggleTheme] = useThemeSwitcher();
|
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -57,25 +55,18 @@ function App() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeContext.Provider value={theme}>
|
<TendermintWebsocketProvider>
|
||||||
<TendermintWebsocketProvider>
|
<NetworkLoader cache={cacheConfig}>
|
||||||
<NetworkLoader cache={cacheConfig}>
|
<div className={layoutClasses}>
|
||||||
<div className={layoutClasses}>
|
<Header menuOpen={menuOpen} setMenuOpen={setMenuOpen} />
|
||||||
<Header
|
<Nav menuOpen={menuOpen} />
|
||||||
theme={theme}
|
<Main />
|
||||||
toggleTheme={toggleTheme}
|
<footer className="grid grid-rows-2 grid-cols-[1fr_auto] text-sm md:text-md md:flex md:col-span-2 p-4 gap-4 border-t border-neutral-700 dark:border-neutral-300">
|
||||||
menuOpen={menuOpen}
|
<NetworkInfo />
|
||||||
setMenuOpen={setMenuOpen}
|
</footer>
|
||||||
/>
|
</div>
|
||||||
<Nav menuOpen={menuOpen} />
|
</NetworkLoader>
|
||||||
<Main />
|
</TendermintWebsocketProvider>
|
||||||
<footer className="grid grid-rows-2 grid-cols-[1fr_auto] text-sm md:text-md md:flex md:col-span-2 p-4 gap-4 border-t border-neutral-700 dark:border-neutral-300">
|
|
||||||
<NetworkInfo />
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</NetworkLoader>
|
|
||||||
</TendermintWebsocketProvider>
|
|
||||||
</ThemeContext.Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,12 +14,7 @@ jest.mock('../search', () => ({
|
|||||||
|
|
||||||
const renderComponent = () => (
|
const renderComponent = () => (
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<Header
|
<Header menuOpen={false} setMenuOpen={jest.fn()} />
|
||||||
theme="dark"
|
|
||||||
toggleTheme={jest.fn()}
|
|
||||||
menuOpen={false}
|
|
||||||
setMenuOpen={jest.fn()}
|
|
||||||
/>
|
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -8,18 +8,11 @@ import type { Dispatch, SetStateAction } from 'react';
|
|||||||
import { NetworkSwitcher } from '@vegaprotocol/environment';
|
import { NetworkSwitcher } from '@vegaprotocol/environment';
|
||||||
|
|
||||||
interface ThemeToggleProps {
|
interface ThemeToggleProps {
|
||||||
theme: 'light' | 'dark';
|
|
||||||
toggleTheme: () => void;
|
|
||||||
menuOpen: boolean;
|
menuOpen: boolean;
|
||||||
setMenuOpen: Dispatch<SetStateAction<boolean>>;
|
setMenuOpen: Dispatch<SetStateAction<boolean>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Header = ({
|
export const Header = ({ menuOpen, setMenuOpen }: ThemeToggleProps) => {
|
||||||
theme,
|
|
||||||
toggleTheme,
|
|
||||||
menuOpen,
|
|
||||||
setMenuOpen,
|
|
||||||
}: ThemeToggleProps) => {
|
|
||||||
const headerClasses = classnames(
|
const headerClasses = classnames(
|
||||||
'md:col-span-2',
|
'md:col-span-2',
|
||||||
'grid grid-rows-2 md:grid-rows-1 grid-cols-[1fr_auto] md:grid-cols-[auto_1fr_auto] items-center',
|
'grid grid-rows-2 md:grid-rows-1 grid-cols-[1fr_auto] md:grid-cols-[auto_1fr_auto] items-center',
|
||||||
@ -48,7 +41,7 @@ export const Header = ({
|
|||||||
<Icon name={menuOpen ? 'cross' : 'menu'} />
|
<Icon name={menuOpen ? 'cross' : 'menu'} />
|
||||||
</button>
|
</button>
|
||||||
<Search />
|
<Search />
|
||||||
<ThemeSwitcher theme={theme} onToggle={toggleTheme} className="-my-4" />
|
<ThemeSwitcher className="-my-4" />
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -88,7 +88,7 @@ describe('NestedDataList', () => {
|
|||||||
|
|
||||||
for (let i = 0; i < 8; i++) {
|
for (let i = 0; i < 8; i++) {
|
||||||
const item = getByTestId(`T${i}`);
|
const item = getByTestId(`T${i}`);
|
||||||
const expected = BORDER_COLOURS.dark[i % 5];
|
const expected = BORDER_COLOURS.light[i % 5];
|
||||||
expect(item.style.borderColor.toUpperCase()).toBe(expected);
|
expect(item.style.borderColor.toUpperCase()).toBe(expected);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useCallback, useContext, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import isObject from 'lodash/isObject';
|
import isObject from 'lodash/isObject';
|
||||||
import { ThemeContext } from '@vegaprotocol/react-helpers';
|
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
import { VegaColours } from '@vegaprotocol/tailwindcss-config';
|
import { VegaColours } from '@vegaprotocol/tailwindcss-config';
|
||||||
@ -65,7 +65,7 @@ const NestedDataListItem = ({
|
|||||||
);
|
);
|
||||||
const hasChildren = isObject(value) && !!Object.keys(value).length;
|
const hasChildren = isObject(value) && !!Object.keys(value).length;
|
||||||
const title = useMemo(() => camelToTitle(label), [label]);
|
const title = useMemo(() => camelToTitle(label), [label]);
|
||||||
const theme = useContext(ThemeContext);
|
const { theme } = useThemeSwitcher();
|
||||||
const currentLevelBorder = useMemo(
|
const currentLevelBorder = useMemo(
|
||||||
() => getBorderColour(index, theme),
|
() => getBorderColour(index, theme),
|
||||||
[index, theme]
|
[index, theme]
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import { ThemeContext } from '@vegaprotocol/react-helpers';
|
|
||||||
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
|
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
|
||||||
|
|
||||||
import App from './app/app';
|
import App from './app/app';
|
||||||
@ -36,11 +35,9 @@ root?.render(
|
|||||||
<StrictMode>
|
<StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<EnvironmentProvider>
|
<EnvironmentProvider>
|
||||||
<ThemeContext.Provider value="light">
|
<NetworkLoader cache={cache}>
|
||||||
<NetworkLoader cache={cache}>
|
<App />
|
||||||
<App />
|
</NetworkLoader>
|
||||||
</NetworkLoader>
|
|
||||||
</ThemeContext.Provider>
|
|
||||||
</EnvironmentProvider>
|
</EnvironmentProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
import { AsyncRenderer, Button, Lozenge } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer, Button, Lozenge } from '@vegaprotocol/ui-toolkit';
|
||||||
import type { EthereumConfig } from '@vegaprotocol/web3';
|
import type { EthereumConfig } from '@vegaprotocol/web3';
|
||||||
import { useEthereumConfig, Web3Provider } from '@vegaprotocol/web3';
|
import { useEthereumConfig, Web3Provider } from '@vegaprotocol/web3';
|
||||||
import { ThemeContext, useThemeSwitcher, t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import { ENV } from './config/env';
|
import { ENV } from './config/env';
|
||||||
import { ContractsProvider } from './config/contracts/contracts-provider';
|
import { ContractsProvider } from './config/contracts/contracts-provider';
|
||||||
import {
|
import {
|
||||||
@ -55,7 +55,6 @@ function App() {
|
|||||||
const { VEGA_ENV, ETHEREUM_PROVIDER_URL } = useEnvironment();
|
const { VEGA_ENV, ETHEREUM_PROVIDER_URL } = useEnvironment();
|
||||||
const { config, loading, error } = useEthereumConfig();
|
const { config, loading, error } = useEthereumConfig();
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
const [theme, toggleTheme] = useThemeSwitcher();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
@ -73,25 +72,23 @@ function App() {
|
|||||||
}, [config?.chain_id, ETHEREUM_PROVIDER_URL]);
|
}, [config?.chain_id, ETHEREUM_PROVIDER_URL]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeContext.Provider value={theme}>
|
<AsyncRenderer loading={loading} data={config} error={error}>
|
||||||
<AsyncRenderer loading={loading} data={config} error={error}>
|
<Web3Provider connectors={Connectors}>
|
||||||
<Web3Provider connectors={Connectors}>
|
<Web3Connector dialogOpen={dialogOpen} setDialogOpen={setDialogOpen}>
|
||||||
<Web3Connector dialogOpen={dialogOpen} setDialogOpen={setDialogOpen}>
|
<EthWalletContainer
|
||||||
<EthWalletContainer
|
dialogOpen={dialogOpen}
|
||||||
dialogOpen={dialogOpen}
|
setDialogOpen={setDialogOpen}
|
||||||
setDialogOpen={setDialogOpen}
|
>
|
||||||
>
|
<ContractsProvider>
|
||||||
<ContractsProvider>
|
<div className={pageWrapperClasses}>
|
||||||
<div className={pageWrapperClasses}>
|
<Header />
|
||||||
<Header theme={theme} toggleTheme={toggleTheme} />
|
<ConnectedApp config={config} />
|
||||||
<ConnectedApp config={config} />
|
</div>
|
||||||
</div>
|
</ContractsProvider>
|
||||||
</ContractsProvider>
|
</EthWalletContainer>
|
||||||
</EthWalletContainer>
|
</Web3Connector>
|
||||||
</Web3Connector>
|
</Web3Provider>
|
||||||
</Web3Provider>
|
</AsyncRenderer>
|
||||||
</AsyncRenderer>
|
|
||||||
</ThemeContext.Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,19 +4,14 @@ import {
|
|||||||
VegaLogo,
|
VegaLogo,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
interface HeaderProps {
|
export const Header = () => {
|
||||||
theme: 'light' | 'dark';
|
|
||||||
toggleTheme: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Header = ({ theme, toggleTheme }: HeaderProps) => {
|
|
||||||
return (
|
return (
|
||||||
<header className="relative overflow-hidden py-2 mb-8">
|
<header className="relative overflow-hidden py-2 mb-8">
|
||||||
<BackgroundVideo />
|
<BackgroundVideo />
|
||||||
<div className="relative flex justify-center px-2 dark:bg-black bg-white">
|
<div className="relative flex justify-center px-2 dark:bg-black bg-white">
|
||||||
<div className="w-full max-w-3xl p-5 flex items-center justify-between">
|
<div className="w-full max-w-3xl p-5 flex items-center justify-between">
|
||||||
<VegaLogo />
|
<VegaLogo />
|
||||||
<ThemeSwitcher theme={theme} onToggle={toggleTheme} />
|
<ThemeSwitcher />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a href="./fonts.css">AlphaLyrae font</a></li>
|
<li><a href="./fonts.css">AlphaLyrae font</a></li>
|
||||||
<li><a href="./favicon.ico">Favicon</a></li>
|
<li><a href="./favicon.ico">Favicon</a></li>
|
||||||
<li><a href="./theme.js">Theme</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
(function () {
|
|
||||||
var storedTheme = window.localStorage.getItem('theme');
|
|
||||||
if (
|
|
||||||
storedTheme === 'dark' ||
|
|
||||||
(!storedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
|
||||||
) {
|
|
||||||
document.documentElement.classList.add('dark');
|
|
||||||
}
|
|
||||||
})();
|
|
@ -1,23 +1,17 @@
|
|||||||
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
|
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
|
||||||
import { Header } from './components/header';
|
import { Header } from './components/header';
|
||||||
import { StatsManager } from '@vegaprotocol/network-stats';
|
import { StatsManager } from '@vegaprotocol/network-stats';
|
||||||
import { ThemeContext } from '@vegaprotocol/react-helpers';
|
|
||||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [theme, toggleTheme] = useThemeSwitcher();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeContext.Provider value={theme}>
|
<NetworkLoader>
|
||||||
<NetworkLoader>
|
<div className="w-screen min-h-screen grid pb-6 bg-white text-neutral-900 dark:bg-black dark:text-neutral-100">
|
||||||
<div className="w-screen min-h-screen grid pb-6 bg-white text-neutral-900 dark:bg-black dark:text-neutral-100">
|
<div className="layout-grid w-screen justify-self-center">
|
||||||
<div className="layout-grid w-screen justify-self-center">
|
<Header />
|
||||||
<Header theme={theme} toggleTheme={toggleTheme} />
|
<StatsManager className="max-w-3xl px-6" />
|
||||||
<StatsManager className="max-w-3xl px-6" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</NetworkLoader>
|
</div>
|
||||||
</ThemeContext.Provider>
|
</NetworkLoader>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,12 +4,7 @@ import {
|
|||||||
ThemeSwitcher,
|
ThemeSwitcher,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
interface ThemeToggleProps {
|
export const Header = () => {
|
||||||
theme: 'light' | 'dark';
|
|
||||||
toggleTheme: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Header = ({ theme, toggleTheme }: ThemeToggleProps) => {
|
|
||||||
return (
|
return (
|
||||||
<header className="relative overflow-hidden py-2 mb-10 md:mb-16">
|
<header className="relative overflow-hidden py-2 mb-10 md:mb-16">
|
||||||
<BackgroundVideo />
|
<BackgroundVideo />
|
||||||
@ -17,7 +12,7 @@ export const Header = ({ theme, toggleTheme }: ThemeToggleProps) => {
|
|||||||
<div className="relative flex justify-center px-2 dark:bg-black bg-white">
|
<div className="relative flex justify-center px-2 dark:bg-black bg-white">
|
||||||
<div className="w-full max-w-3xl p-5 flex items-center justify-between">
|
<div className="w-full max-w-3xl p-5 flex items-center justify-between">
|
||||||
<VegaLogo />
|
<VegaLogo />
|
||||||
<ThemeSwitcher theme={theme} onToggle={toggleTheme} />
|
<ThemeSwitcher />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
@ -2,12 +2,11 @@ import {
|
|||||||
addDecimal,
|
addDecimal,
|
||||||
fromNanoSeconds,
|
fromNanoSeconds,
|
||||||
t,
|
t,
|
||||||
ThemeContext,
|
useThemeSwitcher,
|
||||||
} from '@vegaprotocol/react-helpers';
|
} from '@vegaprotocol/react-helpers';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import compact from 'lodash/compact';
|
import compact from 'lodash/compact';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { useContext } from 'react';
|
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import type { AccountHistoryQuery } from './__generated__/AccountHistory';
|
import type { AccountHistoryQuery } from './__generated__/AccountHistory';
|
||||||
import { useAccountsWithBalanceQuery } from './__generated__/AccountHistory';
|
import { useAccountsWithBalanceQuery } from './__generated__/AccountHistory';
|
||||||
@ -209,7 +208,7 @@ export const AccountHistoryChart = ({
|
|||||||
accountType: Schema.AccountType;
|
accountType: Schema.AccountType;
|
||||||
asset: AssetFieldsFragment;
|
asset: AssetFieldsFragment;
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useContext(ThemeContext);
|
const { theme } = useThemeSwitcher();
|
||||||
const values: { cols: string[]; rows: [Date, ...number[]][] } | null =
|
const values: { cols: string[]; rows: [Date, ...number[]][] } | null =
|
||||||
useMemo(() => {
|
useMemo(() => {
|
||||||
if (!data?.balanceChanges.edges.length) {
|
if (!data?.balanceChanges.edges.length) {
|
||||||
|
@ -16,16 +16,10 @@ import {
|
|||||||
|
|
||||||
type NavbarTheme = 'inherit' | 'dark' | 'yellow';
|
type NavbarTheme = 'inherit' | 'dark' | 'yellow';
|
||||||
interface NavbarProps {
|
interface NavbarProps {
|
||||||
theme: 'light' | 'dark';
|
|
||||||
toggleTheme: () => void;
|
|
||||||
navbarTheme?: NavbarTheme;
|
navbarTheme?: NavbarTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Navbar = ({
|
export const Navbar = ({ navbarTheme = 'inherit' }: NavbarProps) => {
|
||||||
theme,
|
|
||||||
toggleTheme,
|
|
||||||
navbarTheme = 'inherit',
|
|
||||||
}: NavbarProps) => {
|
|
||||||
const { VEGA_TOKEN_URL } = useEnvironment();
|
const { VEGA_TOKEN_URL } = useEnvironment();
|
||||||
const { marketId } = useGlobalStore((store) => ({
|
const { marketId } = useGlobalStore((store) => ({
|
||||||
marketId: store.marketId,
|
marketId: store.marketId,
|
||||||
@ -71,7 +65,7 @@ export const Navbar = ({
|
|||||||
</a>
|
</a>
|
||||||
<div className="flex items-center gap-2 ml-auto">
|
<div className="flex items-center gap-2 ml-auto">
|
||||||
<VegaWalletConnectButton />
|
<VegaWalletConnectButton />
|
||||||
<ThemeSwitcher theme={theme} onToggle={toggleTheme} />
|
<ThemeSwitcher />
|
||||||
</div>
|
</div>
|
||||||
</Nav>
|
</Nav>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import type { AppProps } from 'next/app';
|
import type { AppProps } from 'next/app';
|
||||||
import { Navbar } from '../components/navbar';
|
import { Navbar } from '../components/navbar';
|
||||||
import { t, ThemeContext, useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import {
|
import {
|
||||||
useEagerConnect as useVegaEagerConnect,
|
useEagerConnect as useVegaEagerConnect,
|
||||||
VegaWalletProvider,
|
VegaWalletProvider,
|
||||||
@ -48,10 +48,9 @@ const Title = () => {
|
|||||||
function AppBody({ Component }: AppProps) {
|
function AppBody({ Component }: AppProps) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { VEGA_ENV } = useEnvironment();
|
const { VEGA_ENV } = useEnvironment();
|
||||||
const [theme, toggleTheme] = useThemeSwitcher();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeContext.Provider value={theme}>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
{/* Cannot use meta tags in _document.page.tsx see https://nextjs.org/docs/messages/no-document-viewport-meta */}
|
{/* Cannot use meta tags in _document.page.tsx see https://nextjs.org/docs/messages/no-document-viewport-meta */}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
@ -62,8 +61,6 @@ function AppBody({ Component }: AppProps) {
|
|||||||
<Web3Provider>
|
<Web3Provider>
|
||||||
<div className="h-full relative dark:bg-black dark:text-white z-0 grid grid-rows-[min-content,1fr,min-content]">
|
<div className="h-full relative dark:bg-black dark:text-white z-0 grid grid-rows-[min-content,1fr,min-content]">
|
||||||
<Navbar
|
<Navbar
|
||||||
theme={theme}
|
|
||||||
toggleTheme={toggleTheme}
|
|
||||||
navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'}
|
navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'}
|
||||||
/>
|
/>
|
||||||
<main data-testid={location.pathname}>
|
<main data-testid={location.pathname}>
|
||||||
@ -76,7 +73,7 @@ function AppBody({ Component }: AppProps) {
|
|||||||
</Web3Provider>
|
</Web3Provider>
|
||||||
</AppLoader>
|
</AppLoader>
|
||||||
</VegaWalletProvider>
|
</VegaWalletProvider>
|
||||||
</ThemeContext.Provider>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,11 +17,6 @@ export default function Document() {
|
|||||||
type="image/x-icon"
|
type="image/x-icon"
|
||||||
href="https://static.vega.xyz/favicon.ico"
|
href="https://static.vega.xyz/favicon.ico"
|
||||||
/>
|
/>
|
||||||
<script
|
|
||||||
src="https://static.vega.xyz/theme.js"
|
|
||||||
type="text/javascript"
|
|
||||||
async
|
|
||||||
/>
|
|
||||||
{['1', 'true'].includes(process.env['NX_USE_ENV_OVERRIDES'] || '') ? (
|
{['1', 'true'].includes(process.env['NX_USE_ENV_OVERRIDES'] || '') ? (
|
||||||
/* eslint-disable-next-line @next/next/no-sync-scripts */
|
/* eslint-disable-next-line @next/next/no-sync-scripts */
|
||||||
<script src="/assets/env-config.js" type="text/javascript" />
|
<script src="/assets/env-config.js" type="text/javascript" />
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import './styles.scss';
|
import './styles.scss';
|
||||||
import { ThemeContext } from '@vegaprotocol/react-helpers';
|
import { useStorybookThemeObserver } from '@vegaprotocol/react-helpers';
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
export const parameters = {
|
export const parameters = {
|
||||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||||
backgrounds: { disable: true },
|
backgrounds: { disable: true },
|
||||||
@ -14,36 +13,12 @@ export const parameters = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const decorators = [
|
export const decorators = [
|
||||||
(Story, context) => {
|
(Story) => {
|
||||||
// storybook-addon-themes doesn't seem to provide the current selected
|
useStorybookThemeObserver();
|
||||||
// theme in context, we need to provide it in JS as some components
|
|
||||||
// rely on it for rendering
|
|
||||||
const [theme, setTheme] = useState(context.parameters.themes.default);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const observer = new MutationObserver((mutationList) => {
|
|
||||||
if (mutationList.length) {
|
|
||||||
const body = mutationList[0].target;
|
|
||||||
if (body.classList.contains('dark')) {
|
|
||||||
setTheme('dark');
|
|
||||||
} else {
|
|
||||||
setTheme('light');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(document.body, { attributes: true });
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
observer.disconnect();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '100%', height: 500 }}>
|
<div style={{ width: '100%', height: 500 }}>
|
||||||
<ThemeContext.Provider value={theme}>
|
<Story />
|
||||||
<Story />
|
|
||||||
</ThemeContext.Provider>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -13,9 +13,9 @@ import {
|
|||||||
} from 'pennant';
|
} from 'pennant';
|
||||||
import { VegaDataSource } from './data-source';
|
import { VegaDataSource } from './data-source';
|
||||||
import { useApolloClient } from '@apollo/client';
|
import { useApolloClient } from '@apollo/client';
|
||||||
import { useContext, useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import { ThemeContext } from '@vegaprotocol/react-helpers';
|
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuCheckboxItem,
|
DropdownMenuCheckboxItem,
|
||||||
@ -46,7 +46,7 @@ export const CandlesChartContainer = ({
|
|||||||
}: CandlesChartContainerProps) => {
|
}: CandlesChartContainerProps) => {
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
const theme = useContext(ThemeContext);
|
const { theme } = useThemeSwitcher();
|
||||||
|
|
||||||
const [interval, setInterval] = useState<Interval>(Interval.I15M);
|
const [interval, setInterval] = useState<Interval>(Interval.I15M);
|
||||||
const [chartType, setChartType] = useState<ChartType>(ChartType.CANDLE);
|
const [chartType, setChartType] = useState<ChartType>(ChartType.CANDLE);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import './styles.css';
|
import './styles.css';
|
||||||
import { ThemeContext } from '@vegaprotocol/react-helpers';
|
import { useStorybookThemeObserver } from '@vegaprotocol/react-helpers';
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
export const parameters = {
|
export const parameters = {
|
||||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||||
@ -15,36 +14,12 @@ export const parameters = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const decorators = [
|
export const decorators = [
|
||||||
(Story, context) => {
|
(Story) => {
|
||||||
// storybook-addon-themes doesnt seem to provide the current selected
|
useStorybookThemeObserver();
|
||||||
// theme in context, we need to provid it in JS as some components
|
|
||||||
// rely on it for rendering
|
|
||||||
const [theme, setTheme] = useState(context.parameters.themes.default);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const observer = new MutationObserver((mutationList) => {
|
|
||||||
if (mutationList.length) {
|
|
||||||
const body = mutationList[0].target;
|
|
||||||
if (body.classList.contains('dark')) {
|
|
||||||
setTheme('dark');
|
|
||||||
} else {
|
|
||||||
setTheme('light');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(document.body, { attributes: true });
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
observer.disconnect();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '100%', height: 500 }}>
|
<div style={{ width: '100%', height: 500 }}>
|
||||||
<ThemeContext.Provider value={theme}>
|
<Story />
|
||||||
<Story />
|
|
||||||
</ThemeContext.Provider>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -4,18 +4,11 @@ import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
|||||||
import {
|
import {
|
||||||
useDataProvider,
|
useDataProvider,
|
||||||
addDecimal,
|
addDecimal,
|
||||||
ThemeContext,
|
|
||||||
getNumberFormat,
|
getNumberFormat,
|
||||||
|
useThemeSwitcher,
|
||||||
} from '@vegaprotocol/react-helpers';
|
} from '@vegaprotocol/react-helpers';
|
||||||
import { marketDepthProvider } from './market-depth-provider';
|
import { marketDepthProvider } from './market-depth-provider';
|
||||||
import {
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
useContext,
|
|
||||||
} from 'react';
|
|
||||||
import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
|
import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
|
||||||
import type { MarketData } from '@vegaprotocol/market-list';
|
import type { MarketData } from '@vegaprotocol/market-list';
|
||||||
import type {
|
import type {
|
||||||
@ -35,7 +28,7 @@ const formatMidPrice = (midPrice: string, decimalPlaces: number) =>
|
|||||||
type DepthData = Pick<DepthChartProps, 'data' | 'midPrice'>;
|
type DepthData = Pick<DepthChartProps, 'data' | 'midPrice'>;
|
||||||
|
|
||||||
export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
|
export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
|
||||||
const theme = useContext(ThemeContext);
|
const { theme } = useThemeSwitcher();
|
||||||
const variables = useMemo(() => ({ marketId }), [marketId]);
|
const variables = useMemo(() => ({ marketId }), [marketId]);
|
||||||
const [depthData, setDepthData] = useState<DepthData | null>(null);
|
const [depthData, setDepthData] = useState<DepthData | null>(null);
|
||||||
const dataRef = useRef<DepthData | null>(null);
|
const dataRef = useRef<DepthData | null>(null);
|
||||||
|
@ -8,7 +8,6 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
useMemo,
|
useMemo,
|
||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
|
||||||
Fragment,
|
Fragment,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@ -16,9 +15,9 @@ import classNames from 'classnames';
|
|||||||
import {
|
import {
|
||||||
addDecimalsFormatNumber,
|
addDecimalsFormatNumber,
|
||||||
t,
|
t,
|
||||||
ThemeContext,
|
|
||||||
useResizeObserver,
|
useResizeObserver,
|
||||||
formatNumberFixed,
|
formatNumberFixed,
|
||||||
|
useThemeSwitcher,
|
||||||
} from '@vegaprotocol/react-helpers';
|
} from '@vegaprotocol/react-helpers';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import { OrderbookRow } from './orderbook-row';
|
import { OrderbookRow } from './orderbook-row';
|
||||||
@ -281,7 +280,7 @@ export const Orderbook = ({
|
|||||||
fillGaps: initialFillGaps,
|
fillGaps: initialFillGaps,
|
||||||
onResolutionChange,
|
onResolutionChange,
|
||||||
}: OrderbookProps) => {
|
}: OrderbookProps) => {
|
||||||
const theme = useContext(ThemeContext);
|
const { theme } = useThemeSwitcher();
|
||||||
const scrollElement = useRef<HTMLDivElement>(null);
|
const scrollElement = useRef<HTMLDivElement>(null);
|
||||||
const rootElement = useRef<HTMLDivElement>(null);
|
const rootElement = useRef<HTMLDivElement>(null);
|
||||||
const gridElement = useRef<HTMLDivElement>(null);
|
const gridElement = useRef<HTMLDivElement>(null);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import './styles.scss';
|
import './styles.scss';
|
||||||
import { ThemeContext } from '@vegaprotocol/react-helpers';
|
import { useStorybookThemeObserver } from '@vegaprotocol/react-helpers';
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
export const parameters = {
|
export const parameters = {
|
||||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||||
backgrounds: { disable: true },
|
backgrounds: { disable: true },
|
||||||
@ -14,36 +13,12 @@ export const parameters = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const decorators = [
|
export const decorators = [
|
||||||
(Story, context) => {
|
(Story) => {
|
||||||
// storybook-addon-themes doesnt seem to provide the current selected
|
useStorybookThemeObserver();
|
||||||
// theme in context, we need to provid it in JS as some components
|
|
||||||
// rely on it for rendering
|
|
||||||
const [theme, setTheme] = useState(context.parameters.themes.default);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const observer = new MutationObserver((mutationList) => {
|
|
||||||
if (mutationList.length) {
|
|
||||||
const body = mutationList[0].target;
|
|
||||||
if (body.classList.contains('dark')) {
|
|
||||||
setTheme('dark');
|
|
||||||
} else {
|
|
||||||
setTheme('light');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(document.body, { attributes: true });
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
observer.disconnect();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '100%', height: 500 }}>
|
<div style={{ width: '100%', height: 500 }}>
|
||||||
<ThemeContext.Provider value={theme}>
|
<Story />
|
||||||
<Story />
|
|
||||||
</ThemeContext.Provider>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -9,4 +9,5 @@ export * from './use-resize-observer';
|
|||||||
export * from './use-resize';
|
export * from './use-resize';
|
||||||
export * from './use-screen-dimensions';
|
export * from './use-screen-dimensions';
|
||||||
export * from './use-theme-switcher';
|
export * from './use-theme-switcher';
|
||||||
|
export * from './use-storybook-theme-observer';
|
||||||
export * from './use-yesterday';
|
export * from './use-yesterday';
|
||||||
|
25
libs/react-helpers/src/hooks/use-storybook-theme-observer.ts
Normal file
25
libs/react-helpers/src/hooks/use-storybook-theme-observer.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { useMutationObserver } from './use-mutation-observer';
|
||||||
|
import { useThemeSwitcher } from './use-theme-switcher';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for theme classname changes on the body tag and applies the
|
||||||
|
* same theme to the theme store. This is required as some components
|
||||||
|
* (EG AgGrid) rely on the theme as a JS variable rather than purely a className
|
||||||
|
*
|
||||||
|
* Additionally storybook-addon-themes doesn't seem to provide the current selected
|
||||||
|
* theme in context so we listen for changes on the body tag
|
||||||
|
*/
|
||||||
|
export const useStorybookThemeObserver = () => {
|
||||||
|
const { setTheme } = useThemeSwitcher();
|
||||||
|
|
||||||
|
useMutationObserver(document.body, (mutationList) => {
|
||||||
|
if (mutationList.length) {
|
||||||
|
const body = mutationList[0].target as HTMLElement;
|
||||||
|
if (body && body.classList.contains('dark')) {
|
||||||
|
setTheme('dark');
|
||||||
|
} else {
|
||||||
|
setTheme('light');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@ -1,42 +1,77 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import create from 'zustand';
|
||||||
import { LocalStorage } from '../lib/storage';
|
import { LocalStorage } from '../lib/storage';
|
||||||
|
|
||||||
const darkTheme = 'dark';
|
const THEME_STORAGE_KEY = 'theme';
|
||||||
const lightTheme = 'light';
|
const Themes = {
|
||||||
type themeVariant = typeof darkTheme | typeof lightTheme;
|
DARK: 'dark',
|
||||||
|
LIGHT: 'light',
|
||||||
|
} as const;
|
||||||
|
|
||||||
const darkThemeCssClass = darkTheme;
|
type Theme = typeof Themes[keyof typeof Themes];
|
||||||
|
|
||||||
const getCurrentTheme = () => {
|
const validateTheme = (theme: string): theme is Theme => {
|
||||||
const theme = LocalStorage.getItem('theme');
|
if (Object.values(Themes).includes(theme as Theme)) return true;
|
||||||
if (
|
LocalStorage.removeItem(THEME_STORAGE_KEY);
|
||||||
theme === darkTheme ||
|
return false;
|
||||||
(!theme &&
|
|
||||||
typeof window !== 'undefined' &&
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').matches)
|
|
||||||
) {
|
|
||||||
return darkTheme;
|
|
||||||
}
|
|
||||||
return lightTheme;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleTheme = () => {
|
const setThemeClassName = (theme: Theme) => {
|
||||||
const theme = document.documentElement.classList.contains(darkThemeCssClass)
|
if (typeof window !== 'undefined') {
|
||||||
? lightTheme
|
if (theme === Themes.DARK) {
|
||||||
: darkTheme;
|
document.documentElement.classList.add(Themes.DARK);
|
||||||
LocalStorage.setItem('theme', theme);
|
} else {
|
||||||
|
document.documentElement.classList.remove(Themes.DARK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCurrentTheme = () => {
|
||||||
|
const storedTheme = LocalStorage.getItem(THEME_STORAGE_KEY);
|
||||||
|
|
||||||
|
if (storedTheme && validateTheme(storedTheme)) {
|
||||||
|
setThemeClassName(storedTheme);
|
||||||
|
return storedTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
const theme =
|
||||||
|
typeof window !== 'undefined' &&
|
||||||
|
typeof window.matchMedia === 'function' && // jest test environment matchMedia is undefined
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
? Themes.DARK
|
||||||
|
: Themes.LIGHT;
|
||||||
|
|
||||||
|
setThemeClassName(theme);
|
||||||
return theme;
|
return theme;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useThemeSwitcher(): [themeVariant, () => void] {
|
type ThemeStore = {
|
||||||
const [theme, setTheme] = useState<themeVariant>(lightTheme);
|
theme: Theme;
|
||||||
useEffect(() => setTheme(getCurrentTheme()), []);
|
setTheme: (theme?: Theme) => void;
|
||||||
useEffect(() => {
|
};
|
||||||
if (theme === darkTheme) {
|
|
||||||
document.documentElement.classList.add(darkThemeCssClass);
|
const useThemeStore = create<ThemeStore>((set) => ({
|
||||||
} else {
|
theme: getCurrentTheme(),
|
||||||
document.documentElement.classList.remove(darkThemeCssClass);
|
setTheme: (newTheme) => {
|
||||||
}
|
set((state) => {
|
||||||
}, [theme]);
|
let theme: Theme =
|
||||||
return [theme, () => setTheme(toggleTheme)];
|
state.theme === Themes.LIGHT ? Themes.DARK : Themes.LIGHT;
|
||||||
|
|
||||||
|
if (newTheme) {
|
||||||
|
theme = newTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalStorage.setItem(THEME_STORAGE_KEY, theme);
|
||||||
|
|
||||||
|
setThemeClassName(theme);
|
||||||
|
|
||||||
|
return {
|
||||||
|
theme,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export function useThemeSwitcher(): ThemeStore {
|
||||||
|
const { theme, setTheme } = useThemeStore();
|
||||||
|
return { theme, setTheme };
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
export * from './hooks';
|
export * from './hooks';
|
||||||
export * from './lib/context';
|
|
||||||
export * from './lib/format';
|
export * from './lib/format';
|
||||||
export * from './lib/generic-data-provider';
|
export * from './lib/generic-data-provider';
|
||||||
export * from './lib/get-nodes';
|
export * from './lib/get-nodes';
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
export const ThemeContext = React.createContext<'light' | 'dark'>('dark');
|
|
@ -1 +0,0 @@
|
|||||||
export * from './context';
|
|
@ -1,4 +1,3 @@
|
|||||||
export * from './context';
|
|
||||||
export * from './format';
|
export * from './format';
|
||||||
export * from './grid';
|
export * from './grid';
|
||||||
export * from './storage';
|
export * from './storage';
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import '../src/styles.css';
|
import '../src/styles.css';
|
||||||
export const parameters = {
|
export const parameters = {
|
||||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||||
@ -38,47 +41,50 @@ export const globalTypes = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const StoryWrapper = ({ children, className, style }) => (
|
const StoryWrapper = ({ children, fill }) => {
|
||||||
<div style={style} className={className}>
|
const classes = classNames(
|
||||||
<div className="p-4">
|
'p-4',
|
||||||
<div className="dark:bg-black dark:text-neutral-200 bg-white text-neutral-800">
|
'bg-white dark:bg-black',
|
||||||
{children}
|
'text-neutral-800 dark:text-neutral-200',
|
||||||
</div>
|
{
|
||||||
</div>
|
'w-screen h-screen': fill,
|
||||||
</div>
|
}
|
||||||
);
|
);
|
||||||
|
return <div className={classes}>{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
const lightThemeClasses = 'bg-white text-black';
|
const ThemeWrapper = (Story, context) => {
|
||||||
const darkThemeClasses = 'dark bg-black text-white';
|
|
||||||
|
|
||||||
const withTheme = (Story, context) => {
|
|
||||||
const theme = context.parameters.theme || context.globals.theme;
|
const theme = context.parameters.theme || context.globals.theme;
|
||||||
const storyClasses = `h-[100vh] w-[100vw] ${
|
const { setTheme } = useThemeSwitcher();
|
||||||
theme === 'dark' ? darkThemeClasses : lightThemeClasses
|
|
||||||
}`;
|
|
||||||
|
|
||||||
document.body.classList.toggle('dark', theme === 'dark');
|
useEffect(() => {
|
||||||
|
// in side by side mode a 'dark' class on the html tag will interfere
|
||||||
|
// making the light 'side' dark, so remove it in that case
|
||||||
|
if (theme === 'sideBySide') {
|
||||||
|
document.documentElement.classList.remove('dark');
|
||||||
|
} else {
|
||||||
|
setTheme(theme);
|
||||||
|
}
|
||||||
|
}, [setTheme, theme]);
|
||||||
|
|
||||||
return theme === 'sideBySide' ? (
|
return theme === 'sideBySide' ? (
|
||||||
<>
|
<>
|
||||||
<div className={lightThemeClasses}>
|
<div className="bg-white text-black">
|
||||||
<StoryWrapper>
|
<StoryWrapper>
|
||||||
<Story />
|
<Story />
|
||||||
</StoryWrapper>
|
</StoryWrapper>
|
||||||
</div>
|
</div>
|
||||||
<div className={darkThemeClasses}>
|
<div className="dark bg-black text-white">
|
||||||
<StoryWrapper>
|
<StoryWrapper>
|
||||||
<Story />
|
<Story />
|
||||||
</StoryWrapper>
|
</StoryWrapper>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className={storyClasses}>
|
<StoryWrapper fill={true}>
|
||||||
<StoryWrapper>
|
<Story />
|
||||||
<Story />
|
</StoryWrapper>
|
||||||
</StoryWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const decorators = [withTheme];
|
export const decorators = [ThemeWrapper];
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import type { ReactNode, FunctionComponent } from 'react';
|
import type { ReactNode, FunctionComponent } from 'react';
|
||||||
import { useContext } from 'react';
|
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
|
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
|
||||||
import { AgGridReact } from 'ag-grid-react';
|
import { AgGridReact } from 'ag-grid-react';
|
||||||
import { ThemeContext } from '@vegaprotocol/react-helpers';
|
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||||
import 'ag-grid-community/dist/styles/ag-grid.css';
|
import 'ag-grid-community/dist/styles/ag-grid.css';
|
||||||
|
|
||||||
interface GridProps {
|
interface GridProps {
|
||||||
@ -33,7 +32,7 @@ export const AgGridThemed = ({
|
|||||||
gridRef?: React.ForwardedRef<AgGridReact>;
|
gridRef?: React.ForwardedRef<AgGridReact>;
|
||||||
customThemeParams?: string;
|
customThemeParams?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useContext(ThemeContext);
|
const { theme } = useThemeSwitcher();
|
||||||
const defaultProps = { rowHeight: 22, headerHeight: 22 };
|
const defaultProps = { rowHeight: 22, headerHeight: 22 };
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
|
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
|
||||||
import { AgGridReact } from 'ag-grid-react';
|
import { AgGridReact } from 'ag-grid-react';
|
||||||
import { ThemeContext } from '@vegaprotocol/react-helpers';
|
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||||
import 'ag-grid-community/dist/styles/ag-grid.css';
|
import 'ag-grid-community/dist/styles/ag-grid.css';
|
||||||
|
|
||||||
const AgGridLightTheme = React.lazy(() =>
|
const AgGridLightTheme = React.lazy(() =>
|
||||||
@ -23,7 +23,7 @@ export const AgGridThemed = React.forwardRef<
|
|||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
>(({ style, className, ...props }, ref) => {
|
>(({ style, className, ...props }, ref) => {
|
||||||
const theme = React.useContext(ThemeContext);
|
const { theme } = useThemeSwitcher();
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`${className ?? ''} ${
|
className={`${className ?? ''} ${
|
||||||
|
@ -4,14 +4,7 @@ import { ThemeSwitcher } from './theme-switcher';
|
|||||||
|
|
||||||
describe('ThemeSwitcher', () => {
|
describe('ThemeSwitcher', () => {
|
||||||
it('should render successfully', () => {
|
it('should render successfully', () => {
|
||||||
const { baseElement } = render(
|
const { baseElement } = render(<ThemeSwitcher />);
|
||||||
<ThemeSwitcher
|
|
||||||
theme="dark"
|
|
||||||
onToggle={() => {
|
|
||||||
return;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
expect(baseElement).toBeTruthy();
|
expect(baseElement).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import type { Story, Meta } from '@storybook/react';
|
import type { Story, Meta } from '@storybook/react';
|
||||||
import { useState } from 'react';
|
|
||||||
import { ThemeSwitcher } from './theme-switcher';
|
import { ThemeSwitcher } from './theme-switcher';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -8,16 +7,9 @@ export default {
|
|||||||
} as Meta;
|
} as Meta;
|
||||||
|
|
||||||
const Template: Story = () => {
|
const Template: Story = () => {
|
||||||
const [theme, setTheme] = useState<'light' | 'dark'>('light');
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<ThemeSwitcher
|
<ThemeSwitcher />
|
||||||
theme={theme}
|
|
||||||
onToggle={() => {
|
|
||||||
setTheme((curr) => (curr === 'light' ? 'dark' : 'light'));
|
|
||||||
document.body.classList.toggle('dark');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,19 +1,12 @@
|
|||||||
import React from 'react';
|
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||||
import { SunIcon, MoonIcon } from './icons';
|
import { SunIcon, MoonIcon } from './icons';
|
||||||
|
|
||||||
export const ThemeSwitcher = ({
|
export const ThemeSwitcher = ({ className }: { className?: string }) => {
|
||||||
theme,
|
const { theme, setTheme } = useThemeSwitcher();
|
||||||
onToggle,
|
|
||||||
className,
|
|
||||||
}: {
|
|
||||||
theme: 'light' | 'dark';
|
|
||||||
onToggle: () => void;
|
|
||||||
className?: string;
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onToggle}
|
onClick={() => setTheme()}
|
||||||
className={className}
|
className={className}
|
||||||
data-testid="theme-switcher"
|
data-testid="theme-switcher"
|
||||||
>
|
>
|
||||||
|
Loading…
Reference in New Issue
Block a user