feat(trading): store current tab selection (#3148)
This commit is contained in:
parent
b2dd053621
commit
1d178b940f
@ -282,7 +282,7 @@ export const LiquidityViewContainer = ({
|
||||
<div className="break-word">{marketId}</div>
|
||||
</HeaderStat>
|
||||
</Header>
|
||||
<Tabs active={getActiveDefaultId()}>
|
||||
<Tabs defaultValue={getActiveDefaultId()}>
|
||||
<Tab
|
||||
id={LiquidityTabs.MyLiquidityProvision}
|
||||
name={t('My liquidity provision')}
|
||||
|
@ -14,7 +14,7 @@ import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
||||
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
|
||||
import {
|
||||
Tab,
|
||||
Tabs,
|
||||
LocalStoragePersistTabs as Tabs,
|
||||
ResizableGrid,
|
||||
ResizableGridPanel,
|
||||
Splash,
|
||||
@ -96,7 +96,7 @@ const MarketBottomPanel = memo(
|
||||
minSize={50}
|
||||
>
|
||||
<TradeGridChild>
|
||||
<Tabs>
|
||||
<Tabs storageKey="console-trade-grid-orders">
|
||||
<Tab id="orders" name={t('Orders')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Orders
|
||||
@ -122,7 +122,7 @@ const MarketBottomPanel = memo(
|
||||
minSize={50}
|
||||
>
|
||||
<TradeGridChild>
|
||||
<Tabs>
|
||||
<Tabs storageKey="console-trade-grid-positions">
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Positions
|
||||
@ -146,7 +146,7 @@ const MarketBottomPanel = memo(
|
||||
</ResizableGrid>
|
||||
) : (
|
||||
<TradeGridChild>
|
||||
<Tabs>
|
||||
<Tabs storageKey="console-trade-grid-positions">
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Positions onMarketClick={onMarketClick} />
|
||||
@ -201,7 +201,7 @@ const MainGrid = memo(
|
||||
preferredSize="50%"
|
||||
>
|
||||
<TradeGridChild>
|
||||
<Tabs>
|
||||
<Tabs storageKey="console-trade-grid-chart">
|
||||
<Tab id="chart" name={t('Chart')}>
|
||||
<TradingViews.Candles marketId={marketId} />
|
||||
</Tab>
|
||||
@ -220,7 +220,7 @@ const MainGrid = memo(
|
||||
minSize={300}
|
||||
>
|
||||
<TradeGridChild>
|
||||
<Tabs>
|
||||
<Tabs storageKey="console-trade-grid-ticket">
|
||||
<Tab id="ticket" name={t('Ticket')}>
|
||||
<TradingViews.Ticket
|
||||
marketId={marketId}
|
||||
@ -244,7 +244,7 @@ const MainGrid = memo(
|
||||
minSize={200}
|
||||
>
|
||||
<TradeGridChild>
|
||||
<Tabs>
|
||||
<Tabs storageKey="console-trade-grid-orderbook">
|
||||
<Tab id="orderbook" name={t('Orderbook')}>
|
||||
<TradingViews.Orderbook marketId={marketId} />
|
||||
</Tab>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { titlefy } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Tabs, Tab } from '@vegaprotocol/ui-toolkit';
|
||||
import { LocalStoragePersistTabs as Tabs, Tab } from '@vegaprotocol/ui-toolkit';
|
||||
import { Markets } from './markets';
|
||||
import { Proposed } from './proposed';
|
||||
import { usePageTitleStore } from '../../stores';
|
||||
@ -14,7 +14,7 @@ export const MarketsPage = () => {
|
||||
updateTitle(titlefy(['Markets']));
|
||||
}, [updateTitle]);
|
||||
return (
|
||||
<Tabs>
|
||||
<Tabs storageKey="console-markets">
|
||||
<Tab id="all-markets" name={t('All markets')}>
|
||||
<Markets />
|
||||
</Tab>
|
||||
|
@ -2,7 +2,11 @@ import { titlefy } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { PositionsContainer } from '@vegaprotocol/positions';
|
||||
import { OrderListContainer } from '@vegaprotocol/orders';
|
||||
import { ResizableGridPanel, Tab, Tabs } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
ResizableGridPanel,
|
||||
Tab,
|
||||
LocalStoragePersistTabs as Tabs,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { WithdrawalsContainer } from './withdrawals-container';
|
||||
import { FillsContainer } from '@vegaprotocol/fills';
|
||||
import type { ReactNode } from 'react';
|
||||
@ -41,7 +45,7 @@ export const Portfolio = () => {
|
||||
<ResizableGrid vertical>
|
||||
<ResizableGridPanel minSize={75}>
|
||||
<PortfolioGridChild>
|
||||
<Tabs>
|
||||
<Tabs storageKey="console-portfolio-account-history">
|
||||
<Tab id="account-history" name={t('Account history')}>
|
||||
<VegaWalletContainer>
|
||||
<AccountHistoryContainer />
|
||||
@ -79,7 +83,7 @@ export const Portfolio = () => {
|
||||
minSize={50}
|
||||
>
|
||||
<PortfolioGridChild>
|
||||
<Tabs>
|
||||
<Tabs storageKey="console-portfolio-collateral">
|
||||
<Tab id="collateral" name={t('Collateral')}>
|
||||
<VegaWalletContainer>
|
||||
<AccountsContainer />
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { useLocalStorage } from './use-local-storage';
|
||||
import { useLocalStorage, useLocalStorageSnapshot } from './use-local-storage';
|
||||
|
||||
describe('useLocalStorage', () => {
|
||||
afterEach(() => window.localStorage.clear());
|
||||
@ -50,3 +50,51 @@ describe('useLocalStorage', () => {
|
||||
expect(B.current[0]).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('useLocalStorageSnapshot', () => {
|
||||
afterEach(() => window.localStorage.clear());
|
||||
it("should return null if there's no value set", () => {
|
||||
const { result } = renderHook(() => useLocalStorageSnapshot('test'));
|
||||
const [value] = result.current;
|
||||
expect(value).toBeNull();
|
||||
});
|
||||
it('should return already saved value', () => {
|
||||
window.localStorage.setItem('test', '123');
|
||||
const { result } = renderHook(() => useLocalStorageSnapshot('test'));
|
||||
const [value] = result.current;
|
||||
expect(value).toEqual('123');
|
||||
});
|
||||
it('should save given value', () => {
|
||||
const { result } = renderHook(() => useLocalStorageSnapshot('test'));
|
||||
const setValue = result.current[1];
|
||||
expect(result.current[0]).toBeNull();
|
||||
act(() => setValue('123'));
|
||||
expect(result.current[0]).toEqual('123');
|
||||
act(() => setValue('456'));
|
||||
expect(result.current[0]).toEqual('456');
|
||||
});
|
||||
it('should remove given value', () => {
|
||||
const { result } = renderHook(() => useLocalStorageSnapshot('test'));
|
||||
const setValue = result.current[1];
|
||||
const removeValue = result.current[2];
|
||||
act(() => setValue('123'));
|
||||
expect(result.current[0]).toEqual('123');
|
||||
act(() => removeValue());
|
||||
expect(result.current[0]).toBeNull();
|
||||
});
|
||||
it('should not return value set by storage event (by another tab)', () => {
|
||||
const { result: A } = renderHook(() => useLocalStorageSnapshot('test-a'));
|
||||
act(() => {
|
||||
window.localStorage.setItem('test-a', '123');
|
||||
window.dispatchEvent(
|
||||
new StorageEvent('storage', {
|
||||
key: 'test-a',
|
||||
oldValue: window.localStorage.getItem('test-a'),
|
||||
storageArea: window.localStorage,
|
||||
newValue: '123',
|
||||
})
|
||||
);
|
||||
});
|
||||
expect(A.current[0]).toBeNull();
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,10 @@
|
||||
import { useCallback, useEffect, useMemo, useSyncExternalStore } from 'react';
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useSyncExternalStore,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { LocalStorage } from '@vegaprotocol/utils';
|
||||
|
||||
type LocalStorageCallback = (key: string) => void;
|
||||
@ -10,6 +16,12 @@ const unregisterCallback = (callback: LocalStorageCallback) =>
|
||||
const triggerCallbacks = (key: string) =>
|
||||
LOCAL_STORAGE_CALLBACKS.forEach((cb) => cb(key));
|
||||
|
||||
type UseLocalStorageHook = [
|
||||
string | null | undefined,
|
||||
(value: string) => void,
|
||||
() => void
|
||||
];
|
||||
|
||||
export const useLocalStorage = (key: string) => {
|
||||
const subscribe = useCallback(
|
||||
(onStoreChange: () => void) => {
|
||||
@ -52,7 +64,27 @@ export const useLocalStorage = (key: string) => {
|
||||
return () => window.removeEventListener('storage', onStorage);
|
||||
}, [key, onStorage]);
|
||||
|
||||
return useMemo<
|
||||
[string | null | undefined, (value: string) => void, () => void]
|
||||
>(() => [value, setValue, removeValue], [removeValue, setValue, value]);
|
||||
return useMemo<UseLocalStorageHook>(
|
||||
() => [value, setValue, removeValue],
|
||||
[removeValue, setValue, value]
|
||||
);
|
||||
};
|
||||
|
||||
export const useLocalStorageSnapshot = (key: string) => {
|
||||
const [value, setStoredValue] = useState(LocalStorage.getItem(key));
|
||||
const setValue = useCallback(
|
||||
(value: string) => {
|
||||
LocalStorage.setItem(key, value);
|
||||
setStoredValue(value);
|
||||
},
|
||||
[key]
|
||||
);
|
||||
const removeValue = useCallback(() => {
|
||||
LocalStorage.removeItem(key);
|
||||
setStoredValue(null);
|
||||
}, [key]);
|
||||
return useMemo<UseLocalStorageHook>(
|
||||
() => [value, setValue, removeValue],
|
||||
[removeValue, setValue, value]
|
||||
);
|
||||
};
|
||||
|
@ -1,23 +1,31 @@
|
||||
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
||||
import { useLocalStorageSnapshot } from '@vegaprotocol/react-helpers';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactElement, ReactNode } from 'react';
|
||||
import { Children, isValidElement, useState } from 'react';
|
||||
|
||||
interface TabsProps {
|
||||
export interface TabsProps extends TabsPrimitive.TabsProps {
|
||||
children: ReactElement<TabProps>[];
|
||||
active?: string;
|
||||
}
|
||||
|
||||
export const Tabs = ({ children, active: activeDefaultId }: TabsProps) => {
|
||||
export const Tabs = ({
|
||||
children,
|
||||
defaultValue,
|
||||
value,
|
||||
onValueChange,
|
||||
...props
|
||||
}: TabsProps) => {
|
||||
const [activeTab, setActiveTab] = useState<string>(() => {
|
||||
return activeDefaultId ?? children[0].props.id;
|
||||
if (defaultValue) {
|
||||
return defaultValue;
|
||||
}
|
||||
return children[0].props.id;
|
||||
});
|
||||
|
||||
return (
|
||||
<TabsPrimitive.Root
|
||||
value={activeTab}
|
||||
{...props}
|
||||
value={value || activeTab}
|
||||
onValueChange={onValueChange || setActiveTab}
|
||||
className="h-full grid grid-rows-[min-content_1fr]"
|
||||
onValueChange={(value) => setActiveTab(value)}
|
||||
>
|
||||
<div className="border-b border-default">
|
||||
<TabsPrimitive.List
|
||||
@ -26,7 +34,7 @@ export const Tabs = ({ children, active: activeDefaultId }: TabsProps) => {
|
||||
>
|
||||
{Children.map(children, (child) => {
|
||||
if (!isValidElement(child) || child.props.hidden) return null;
|
||||
const isActive = child.props.id === activeTab;
|
||||
const isActive = child.props.id === (value || activeTab);
|
||||
const triggerClass = classNames(
|
||||
'relative px-4 py-1 border-r border-default',
|
||||
'uppercase',
|
||||
@ -82,3 +90,15 @@ interface TabProps {
|
||||
export const Tab = ({ children, ...props }: TabProps) => {
|
||||
return <div {...props}>{children}</div>;
|
||||
};
|
||||
|
||||
export const LocalStoragePersistTabs = ({
|
||||
storageKey,
|
||||
...props
|
||||
}: Omit<TabsProps, 'value' | 'onValueChange'> & { storageKey: string }) => {
|
||||
const [value, onValueChange] = useLocalStorageSnapshot(
|
||||
`active-tab-${storageKey}`
|
||||
);
|
||||
return (
|
||||
<Tabs {...props} value={value || undefined} onValueChange={onValueChange} />
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user