feat(2093): token sub nav (#2549)
* feat: implement subnav using exisitng DD component * chore: make nav as per design * chore: fix conflicts * style: make css for trigger match designs * chore: add border to subnav card * chore: reogranise files * chore: remove unused imports * fix: minor rendering errors * test: add first tests * chore: fix failing tests from nav update * test: add unit tests for nav * style: lint * style: make navbar theme work properly * style: lint * chore: remove unnecessary click on header * fix: move route config to ts only file * test: tidy up test logic * feat: add nav drop down to ui toolkit * fix: broken routing in places * style: lint Co-authored-by: Joe <joe@vega.xyz>
This commit is contained in:
parent
d474a502a8
commit
b8ff3d3050
@ -33,6 +33,7 @@ context(
|
||||
beforeEach('Navigate to withdrawal page', function () {
|
||||
cy.reload();
|
||||
cy.visit('/');
|
||||
cy.wait_for_spinner();
|
||||
cy.navigate_to('withdraw');
|
||||
cy.connectVegaWallet();
|
||||
cy.ethereum_wallet_connect();
|
||||
@ -52,6 +53,7 @@ context(
|
||||
it('Unable to submit withdrawal with invalid fields', function () {
|
||||
cy.getByTestId(withdraw).should('be.visible').click();
|
||||
cy.getByTestId(selectAsset).select(usdtName);
|
||||
cy.getByTestId(balanceAvailable, txTimeout).should('exist');
|
||||
cy.getByTestId(submitWithdrawalButton).click();
|
||||
cy.getByTestId(formValidationError).should('have.length', 1);
|
||||
cy.getByTestId(amountInput).clear().click().type('0.0000001');
|
||||
@ -74,7 +76,7 @@ context(
|
||||
cy.getByTestId(withdraw).should('be.visible').click();
|
||||
cy.getByTestId(selectAsset).select(usdtName);
|
||||
cy.getByTestId(balanceAvailable, txTimeout).should('exist');
|
||||
cy.getByTestId(withdrawalThreshold).should('have.text', '100,000.00T');
|
||||
cy.getByTestId(withdrawalThreshold).should('have.text', '100,000.00000T');
|
||||
cy.getByTestId(delayTime).should('have.text', 'None');
|
||||
cy.getByTestId(amountInput).click().type('100');
|
||||
cy.getByTestId(submitWithdrawalButton).click();
|
||||
|
@ -1,10 +1,11 @@
|
||||
const navSection = 'nav';
|
||||
const navVesting = '[href="/token/tranches"]';
|
||||
const navSupply = '[href="/token/tranches"]';
|
||||
const navToken = '[href="/token"]';
|
||||
const navStaking = '[href="/validators"]';
|
||||
const navRewards = '[href="/rewards"]';
|
||||
const navWithdraw = '[href="/token/withdraw"]';
|
||||
const navGovernance = '[href="/proposals"]';
|
||||
const navRedeem = '[href="/token/redeem"]';
|
||||
|
||||
const tokenDetailsTable = '.token-details';
|
||||
const address = '[data-testid="token-address"]';
|
||||
@ -34,34 +35,39 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
|
||||
describe('Navigation tabs', function () {
|
||||
it('should have TOKEN tab', function () {
|
||||
it('should have proposals tab', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.get(navToken).should('be.visible');
|
||||
cy.get(navGovernance).should('be.visible');
|
||||
});
|
||||
});
|
||||
it('should have VESTING tab', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.get(navVesting).should('be.visible');
|
||||
});
|
||||
});
|
||||
it('should have STAKING tab', function () {
|
||||
it('should have validators tab', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.get(navStaking).should('be.visible');
|
||||
});
|
||||
});
|
||||
it('should have REWARDS tab', function () {
|
||||
it('should have rewards tab', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.get(navRewards).should('be.visible');
|
||||
});
|
||||
});
|
||||
it('should have WITHDRAW tab', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
|
||||
describe('Token dropdown', function () {
|
||||
before('click on token dropdown', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.getByTestId('state-trigger').click();
|
||||
});
|
||||
});
|
||||
it('should have token dropdown', function () {
|
||||
cy.get(navToken).should('be.visible');
|
||||
});
|
||||
it('should have supply & vesting dropdown', function () {
|
||||
cy.get(navSupply).should('be.visible');
|
||||
});
|
||||
it('should have withdraw dropdown', function () {
|
||||
cy.get(navWithdraw).should('be.visible');
|
||||
});
|
||||
});
|
||||
it('should have GOVERNANCE tab', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.get(navGovernance).should('be.visible');
|
||||
it('should have redeem dropdown', function () {
|
||||
cy.get(navRedeem).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -10,7 +10,7 @@ context(
|
||||
cy.visit('/').navigate_to('vesting');
|
||||
});
|
||||
it('should have vesting tab highlighted', function () {
|
||||
cy.verify_tab_highlighted('vesting');
|
||||
cy.verify_tab_highlighted('token');
|
||||
});
|
||||
|
||||
it('should have VESTING header visible', function () {
|
||||
@ -37,9 +37,7 @@ context(
|
||||
// 1005-VEST-001
|
||||
// 1005-VEST-002
|
||||
it('Able to view tranches', function () {
|
||||
cy.get('[href="/token/tranches"]')
|
||||
.should('have.text', 'Supply & Vesting')
|
||||
.click();
|
||||
cy.navigate_to('supply');
|
||||
cy.url().should('include', '/token/tranches');
|
||||
cy.get('h1').should('contain.text', 'Vesting tranches');
|
||||
});
|
||||
|
@ -10,7 +10,7 @@ context(
|
||||
|
||||
describe('with wallets disconnected', function () {
|
||||
it('should have withdraw tab highlighted', function () {
|
||||
cy.verify_tab_highlighted('withdraw');
|
||||
cy.verify_tab_highlighted('token');
|
||||
});
|
||||
|
||||
it('should have WITHDRAW header visible', function () {
|
||||
|
@ -14,13 +14,25 @@ const navigation = {
|
||||
withdraw: '[href="/token/withdraw"]',
|
||||
proposals: '[href="/proposals"]',
|
||||
pageSpinner: '[data-testid="splash-loader"]',
|
||||
supply: '[href="/token/tranches"]',
|
||||
token: '[href="/token"]',
|
||||
};
|
||||
|
||||
const topLevelRoutes = ['proposals', 'validators', 'rewards'];
|
||||
|
||||
Cypress.Commands.add('navigate_to', (page) => {
|
||||
return cy.get(navigation.section, { timeout: 10000 }).within(() => {
|
||||
cy.get(navigation[page]).click({ force: true });
|
||||
});
|
||||
const tokenDropDown = 'state-trigger';
|
||||
|
||||
if (!topLevelRoutes.includes(page)) {
|
||||
cy.getByTestId(tokenDropDown, { timeout: 10000 }).click();
|
||||
cy.getByTestId('token-dropdown').within(() => {
|
||||
cy.get(navigation[page]).click();
|
||||
});
|
||||
} else {
|
||||
return cy.get(navigation.section, { timeout: 10000 }).within(() => {
|
||||
cy.get(navigation[page]).click();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Cypress.Commands.add('verify_tab_highlighted', (page) => {
|
||||
|
@ -16,6 +16,7 @@ const stakeValidatorListTotalStake = '[col-id="stake"] > div > span';
|
||||
const stakeValidatorListTotalShare = '[col-id="stakeShare"] > div > span';
|
||||
const stakeValidatorListName = '[col-id="validator"]';
|
||||
const vegaKeySelector = '#vega-key-selector';
|
||||
const dialogCloseButton = '[data-testid="dialog-close"]';
|
||||
|
||||
const txTimeout = Cypress.env('txTimeout');
|
||||
const epochTimeout = Cypress.env('epochTimeout');
|
||||
@ -39,6 +40,7 @@ Cypress.Commands.add('staking_validator_page_add_stake', (stake) => {
|
||||
.and('contain', `Add ${stake} $VEGA tokens`)
|
||||
.and('be.visible')
|
||||
.click();
|
||||
cy.get(dialogCloseButton).click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('staking_validator_page_remove_stake', (stake) => {
|
||||
@ -51,6 +53,7 @@ Cypress.Commands.add('staking_validator_page_remove_stake', (stake) => {
|
||||
.and('contain', `Remove ${stake} $VEGA tokens at the end of epoch`)
|
||||
.and('be.visible')
|
||||
.click();
|
||||
cy.get(dialogCloseButton).click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('staking_page_associate_tokens', (amount, options) => {
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { EthWallet } from '../eth-wallet';
|
||||
import { VegaWallet } from '../vega-wallet';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface Route {
|
||||
name: string;
|
||||
@ -29,6 +30,7 @@ const DrawerNavLinks = ({
|
||||
routes: Route[];
|
||||
}) => {
|
||||
const { appDispatch } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
const linkProps = {
|
||||
end: true,
|
||||
onClick: () =>
|
||||
@ -55,7 +57,7 @@ const DrawerNavLinks = ({
|
||||
})
|
||||
}
|
||||
>
|
||||
{name}
|
||||
{t(name)}
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
|
49
apps/token/src/components/nav/nav-dropdown.tsx
Normal file
49
apps/token/src/components/nav/nav-dropdown.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { useState } from 'react';
|
||||
import Routes, { TOKEN_DROPDOWN_ROUTES } from '../../routes/routes';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { NavbarTheme } from './nav-link';
|
||||
import { AppNavLink } from './nav-link';
|
||||
import {
|
||||
NavDropdownMenu,
|
||||
NavDropdownMenuContent,
|
||||
NavDropdownMenuItem,
|
||||
NavDropdownMenuTrigger,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const NavDropDown = ({ navbarTheme }: { navbarTheme: NavbarTheme }) => {
|
||||
const { t } = useTranslation();
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
return (
|
||||
<NavDropdownMenu open={isOpen} onOpenChange={(open) => setOpen(open)}>
|
||||
<AppNavLink
|
||||
name={
|
||||
<NavDropdownMenuTrigger
|
||||
className="w-auto text-capMenu"
|
||||
data-testid="state-trigger"
|
||||
onClick={() => setOpen(!isOpen)}
|
||||
>
|
||||
{t('Token')}
|
||||
</NavDropdownMenuTrigger>
|
||||
}
|
||||
testId="token-dd"
|
||||
path={Routes.TOKEN}
|
||||
navbarTheme={navbarTheme}
|
||||
/>
|
||||
|
||||
<NavDropdownMenuContent data-testid="token-dropdown">
|
||||
{TOKEN_DROPDOWN_ROUTES.map((r) => (
|
||||
<NavDropdownMenuItem key={r.name} onClick={() => setOpen(false)}>
|
||||
<AppNavLink
|
||||
testId={r.name}
|
||||
name={t(r.name)}
|
||||
path={r.path}
|
||||
navbarTheme={'inherit'}
|
||||
end={true}
|
||||
fullWidth={true}
|
||||
/>
|
||||
</NavDropdownMenuItem>
|
||||
))}
|
||||
</NavDropdownMenuContent>
|
||||
</NavDropdownMenu>
|
||||
);
|
||||
};
|
52
apps/token/src/components/nav/nav-link.tsx
Normal file
52
apps/token/src/components/nav/nav-link.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import classNames from 'classnames';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import type { HTMLAttributeAnchorTarget, ReactNode } from 'react';
|
||||
import { getNavLinkClassNames } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export type NavbarTheme = 'inherit' | 'dark' | 'yellow';
|
||||
|
||||
interface AppNavLinkProps {
|
||||
name: ReactNode | string;
|
||||
path: string;
|
||||
navbarTheme: NavbarTheme;
|
||||
testId?: string;
|
||||
target?: HTMLAttributeAnchorTarget;
|
||||
end?: boolean;
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
|
||||
export const AppNavLink = ({
|
||||
name,
|
||||
path,
|
||||
navbarTheme,
|
||||
target,
|
||||
testId,
|
||||
end = false,
|
||||
fullWidth = false,
|
||||
}: AppNavLinkProps) => {
|
||||
const borderClasses = classNames('absolute h-1 w-full bottom-[-1px] left-0', {
|
||||
'bg-black dark:bg-vega-yellow': navbarTheme !== 'yellow',
|
||||
'bg-black': navbarTheme === 'yellow',
|
||||
});
|
||||
return (
|
||||
<NavLink
|
||||
key={path}
|
||||
data-testid={testId}
|
||||
to={{ pathname: path }}
|
||||
className={getNavLinkClassNames(navbarTheme, fullWidth)}
|
||||
target={target}
|
||||
end={end}
|
||||
>
|
||||
{({ isActive }) => {
|
||||
return (
|
||||
<>
|
||||
{name}
|
||||
{isActive && (
|
||||
<span data-testid="link-active" className={borderClasses} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</NavLink>
|
||||
);
|
||||
};
|
62
apps/token/src/components/nav/nav.spec.tsx
Normal file
62
apps/token/src/components/nav/nav.spec.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import { EnvironmentProvider, Networks } from '@vegaprotocol/environment';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { Nav } from './nav';
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
...jest.requireActual('@vegaprotocol/environment'),
|
||||
NetworkSwitcher: () => <div data-testid="network-switcher" />,
|
||||
}));
|
||||
|
||||
const renderComponent = (initialEntries?: string[]) => {
|
||||
return render(
|
||||
<EnvironmentProvider
|
||||
definitions={{ VEGA_ENV: Networks.MAINNET }}
|
||||
config={{ hosts: [] }}
|
||||
>
|
||||
<MemoryRouter initialEntries={initialEntries}>
|
||||
<Nav />
|
||||
</MemoryRouter>
|
||||
</EnvironmentProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('nav', () => {
|
||||
it('Renders title and logo with link to home', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByText('Governance')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('logo-link')).toHaveProperty(
|
||||
'href',
|
||||
'http://localhost/'
|
||||
);
|
||||
});
|
||||
it('Renders network switcher', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByTestId('network-switcher')).toBeInTheDocument();
|
||||
});
|
||||
it('Renders all top level routes', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByTestId('Proposals')).toHaveProperty(
|
||||
'href',
|
||||
'http://localhost/proposals'
|
||||
);
|
||||
expect(screen.getByTestId('Validators')).toHaveProperty(
|
||||
'href',
|
||||
'http://localhost/validators'
|
||||
);
|
||||
expect(screen.getByTestId('Rewards')).toHaveProperty(
|
||||
'href',
|
||||
'http://localhost/rewards'
|
||||
);
|
||||
});
|
||||
it('Shows active state on dropdown trigger when on home route for subroutes', () => {
|
||||
const { getByTestId } = renderComponent(['/token']);
|
||||
const dd = getByTestId('token-dd');
|
||||
expect(within(dd).getByTestId('link-active')).toBeInTheDocument();
|
||||
});
|
||||
it('Shows active state on dropdown trigger when on sub route of dropdown', () => {
|
||||
const { getByTestId } = renderComponent(['/token/withdraw']);
|
||||
const dd = getByTestId('token-dd');
|
||||
expect(within(dd).getByTestId('link-active')).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -1,17 +1,14 @@
|
||||
import classNames from 'classnames';
|
||||
import { NavLink, Link } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { NetworkSwitcher } from '@vegaprotocol/environment';
|
||||
import type { HTMLAttributeAnchorTarget } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Routes from '../../routes/routes';
|
||||
import { TOP_LEVEL_ROUTES } from '../../routes/routes';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import vegaWhite from '../../images/vega_white.png';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { NavDrawer } from './nav-draw';
|
||||
import {
|
||||
getNavLinkClassNames,
|
||||
Nav as ToolkitNav,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Nav as ToolkitNav } from '@vegaprotocol/ui-toolkit';
|
||||
import { AppNavLink } from './nav-link';
|
||||
import { NavDropDown } from './nav-dropdown';
|
||||
|
||||
const useDebouncedResize = () => {
|
||||
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
||||
@ -43,42 +40,12 @@ export const Nav = ({ navbarTheme = 'inherit' }: NavbarProps) => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
const isYellow = navbarTheme === 'yellow';
|
||||
const routes = [
|
||||
{
|
||||
name: t('Proposals'),
|
||||
path: Routes.PROPOSALS,
|
||||
},
|
||||
{
|
||||
name: t('Validators'),
|
||||
path: Routes.VALIDATORS,
|
||||
},
|
||||
{
|
||||
name: t('Rewards'),
|
||||
path: Routes.REWARDS,
|
||||
},
|
||||
{
|
||||
name: t('Token'),
|
||||
path: Routes.TOKEN,
|
||||
},
|
||||
{
|
||||
name: t('Redeem'),
|
||||
path: Routes.REDEEM,
|
||||
},
|
||||
{
|
||||
name: t('Withdraw'),
|
||||
path: Routes.WITHDRAWALS,
|
||||
},
|
||||
{
|
||||
name: t('Supply & Vesting'),
|
||||
path: Routes.TRANCHES,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ToolkitNav
|
||||
navbarTheme={navbarTheme}
|
||||
icon={
|
||||
<Link to="/">
|
||||
<Link to="/" data-testid="logo-link">
|
||||
<img alt="Vega" src={vegaWhite} height={30} width={30} />
|
||||
</Link>
|
||||
}
|
||||
@ -87,54 +54,22 @@ export const Nav = ({ navbarTheme = 'inherit' }: NavbarProps) => {
|
||||
>
|
||||
{isDesktop ? (
|
||||
<nav className="flex items-center flex-1 px-2">
|
||||
{routes.map((r) => (
|
||||
<AppNavLink {...r} navbarTheme={navbarTheme} />
|
||||
{TOP_LEVEL_ROUTES.map((r) => (
|
||||
<AppNavLink
|
||||
key={r.path}
|
||||
testId={r.name}
|
||||
name={t(r.name)}
|
||||
path={r.path}
|
||||
navbarTheme={navbarTheme}
|
||||
/>
|
||||
))}
|
||||
<NavDropDown navbarTheme={navbarTheme} />
|
||||
</nav>
|
||||
) : (
|
||||
<nav className="flex items-center flex-1 px-2 justify-end">
|
||||
<NavDrawer inverted={isYellow} routes={routes} />
|
||||
<NavDrawer inverted={isYellow} routes={TOP_LEVEL_ROUTES} />
|
||||
</nav>
|
||||
)}
|
||||
</ToolkitNav>
|
||||
);
|
||||
};
|
||||
|
||||
interface AppNavLinkProps {
|
||||
name: string;
|
||||
path: string;
|
||||
navbarTheme: NavbarTheme;
|
||||
testId?: string;
|
||||
target?: HTMLAttributeAnchorTarget;
|
||||
}
|
||||
|
||||
const AppNavLink = ({
|
||||
name,
|
||||
path,
|
||||
navbarTheme,
|
||||
target,
|
||||
testId = name,
|
||||
}: AppNavLinkProps) => {
|
||||
const borderClasses = classNames('absolute h-1 w-full bottom-[-1px] left-0', {
|
||||
'bg-black dark:bg-vega-yellow': navbarTheme !== 'yellow',
|
||||
'bg-black': navbarTheme === 'yellow',
|
||||
});
|
||||
return (
|
||||
<NavLink
|
||||
data-testid={testId}
|
||||
to={{ pathname: path }}
|
||||
className={getNavLinkClassNames(navbarTheme)}
|
||||
target={target}
|
||||
end={true}
|
||||
>
|
||||
{({ isActive }) => {
|
||||
return (
|
||||
<>
|
||||
{name}
|
||||
{isActive && <span className={borderClasses} />}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</NavLink>
|
||||
);
|
||||
};
|
||||
|
@ -78,7 +78,7 @@ const HomeNodes = ({
|
||||
<Heading title={t('Validators')} />
|
||||
<h3 className="mb-6">{t('homeValidatorsIntro')}</h3>
|
||||
<div className="flex items-center mb-8 gap-8">
|
||||
<Link to={Routes.STAKING}>
|
||||
<Link to={Routes.VALIDATORS}>
|
||||
<Button size="md">{t('homeValidatorsButtonText')}</Button>
|
||||
</Link>
|
||||
|
||||
@ -123,7 +123,7 @@ const HomeNodes = ({
|
||||
</div>
|
||||
|
||||
{activeNodes.length > nodesToShow && (
|
||||
<Link to={Routes.STAKING}>
|
||||
<Link to={Routes.VALIDATORS}>
|
||||
<span className="underline">
|
||||
And {activeNodes.length - nodesToShow} more...
|
||||
</span>
|
||||
|
@ -59,7 +59,7 @@ export const RedemptionInformation = () => {
|
||||
i18nKey="noVestingTokens"
|
||||
components={{
|
||||
tranchesLink: (
|
||||
<Link className="underline text-white" to={Routes.TRANCHES} />
|
||||
<Link className="underline text-white" to={Routes.SUPPLY} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
@ -81,7 +81,7 @@ const RedemptionRouter = () => {
|
||||
<Callout>
|
||||
<p>{t('You have no VEGA tokens currently vesting.')}</p>
|
||||
</Callout>
|
||||
<Link to={RoutesConfig.TRANCHES}>{t('viewAllTranches')}</Link>
|
||||
<Link to={RoutesConfig.SUPPLY}>{t('viewAllTranches')}</Link>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ export const TrancheTable = ({
|
||||
}
|
||||
return (
|
||||
<TrancheItem
|
||||
link={`${Routes.TRANCHES}/${tranche.tranche_id}`}
|
||||
link={`${Routes.SUPPLY}/${tranche.tranche_id}`}
|
||||
tranche={tranche}
|
||||
locked={locked}
|
||||
unlocked={vested}
|
||||
|
@ -78,7 +78,7 @@ export const RedeemFromTranche = () => {
|
||||
i18nKey="noVestingTokens"
|
||||
components={{
|
||||
tranchesLink: (
|
||||
<Link className="underline text-white" to={Routes.TRANCHES} />
|
||||
<Link className="underline text-white" to={Routes.SUPPLY} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
@ -202,12 +202,12 @@ const LazyWithdrawals = React.lazy(
|
||||
|
||||
const redirects = [
|
||||
{
|
||||
path: Routes.STAKING,
|
||||
path: Routes.VALIDATORS,
|
||||
element: <Navigate to={Routes.VALIDATORS} replace />,
|
||||
},
|
||||
{
|
||||
path: '/tranches',
|
||||
element: <Navigate to={Routes.TRANCHES} replace />,
|
||||
element: <Navigate to={Routes.SUPPLY} replace />,
|
||||
},
|
||||
{
|
||||
path: '/withdrawals',
|
||||
@ -286,7 +286,7 @@ const routerConfig = [
|
||||
index: true,
|
||||
},
|
||||
{
|
||||
path: Routes.TRANCHES,
|
||||
path: Routes.SUPPLY,
|
||||
element: <LazyTranches name="Tranches" />,
|
||||
children: [
|
||||
{ index: true, element: <LazyTranchesTranches /> },
|
||||
|
@ -1,4 +1,4 @@
|
||||
export default {
|
||||
const Routes = {
|
||||
HOME: '/',
|
||||
CLAIM: '/claim',
|
||||
VALIDATORS: '/validators',
|
||||
@ -8,10 +8,54 @@ export default {
|
||||
NOT_PERMITTED: '/not-permitted',
|
||||
NOT_FOUND: '/not-found',
|
||||
CONTRACTS: '/contracts',
|
||||
STAKING: '/staking',
|
||||
TOKEN: '/token',
|
||||
REDEEM: '/token/redeem',
|
||||
WITHDRAWALS: '/token/withdraw',
|
||||
TRANCHES: '/token/tranches',
|
||||
ASSOCIATE: '/token/associate',
|
||||
SUPPLY: '/token/tranches',
|
||||
ASSOCIATE: '/validators/associate',
|
||||
DISASSOCIATE: '/validators/disassociate',
|
||||
};
|
||||
|
||||
export default Routes;
|
||||
|
||||
export const TOP_LEVEL_ROUTES = [
|
||||
{
|
||||
name: 'Proposals',
|
||||
path: Routes.PROPOSALS,
|
||||
},
|
||||
{
|
||||
name: 'Validators',
|
||||
path: Routes.VALIDATORS,
|
||||
},
|
||||
{
|
||||
name: 'Rewards',
|
||||
path: Routes.REWARDS,
|
||||
},
|
||||
];
|
||||
|
||||
export const TOKEN_DROPDOWN_ROUTES = [
|
||||
{
|
||||
name: 'Token',
|
||||
path: Routes.TOKEN,
|
||||
},
|
||||
{
|
||||
name: 'Supply & Vesting',
|
||||
path: Routes.SUPPLY,
|
||||
},
|
||||
{
|
||||
name: 'Withdraw',
|
||||
path: Routes.WITHDRAWALS,
|
||||
},
|
||||
{
|
||||
name: 'Redeem',
|
||||
path: Routes.REDEEM,
|
||||
},
|
||||
{
|
||||
name: 'Associate',
|
||||
path: Routes.ASSOCIATE,
|
||||
},
|
||||
{
|
||||
name: 'Disassociate',
|
||||
path: Routes.DISASSOCIATE,
|
||||
},
|
||||
];
|
||||
|
@ -45,7 +45,7 @@ const Home = ({ name }: RouteChildProps) => {
|
||||
trancheLink: (
|
||||
<Link
|
||||
data-testid="tranches-link"
|
||||
to={Routes.TRANCHES}
|
||||
to={Routes.SUPPLY}
|
||||
className="underline text-white"
|
||||
/>
|
||||
),
|
||||
|
@ -19,6 +19,7 @@ export * from './link';
|
||||
export * from './loader';
|
||||
export * from './lozenge';
|
||||
export * from './nav';
|
||||
export * from './nav-dropdown';
|
||||
export * from './popover';
|
||||
export * from './price-change';
|
||||
export * from './progress-bar';
|
||||
|
@ -0,0 +1,35 @@
|
||||
import type { ComponentMeta } from '@storybook/react';
|
||||
|
||||
import {
|
||||
NavDropdownMenu,
|
||||
NavDropdownMenuContent,
|
||||
NavDropdownMenuItem,
|
||||
NavDropdownMenuTrigger,
|
||||
} from './dropdown-menu';
|
||||
|
||||
export default {
|
||||
title: 'NavDropdownMenu',
|
||||
} as ComponentMeta<typeof NavDropdownMenu>;
|
||||
|
||||
export const RadioItems = () => {
|
||||
return (
|
||||
<div style={{ textAlign: 'center', padding: 50 }}>
|
||||
<NavDropdownMenu>
|
||||
<NavDropdownMenuTrigger>
|
||||
<span>Open</span>
|
||||
</NavDropdownMenuTrigger>
|
||||
<NavDropdownMenuContent>
|
||||
<NavDropdownMenuItem onSelect={() => console.log('minimize')}>
|
||||
Minimize window
|
||||
</NavDropdownMenuItem>
|
||||
<NavDropdownMenuItem onSelect={() => console.log('zoom')}>
|
||||
Zoom
|
||||
</NavDropdownMenuItem>
|
||||
<NavDropdownMenuItem onSelect={() => console.log('smaller')}>
|
||||
Smaller
|
||||
</NavDropdownMenuItem>
|
||||
</NavDropdownMenuContent>
|
||||
</NavDropdownMenu>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,74 @@
|
||||
import classNames from 'classnames';
|
||||
import { Icon } from '../icon';
|
||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
const itemClass = classNames(
|
||||
'relative flex items-center justify-between rounded-sm p-2 text-sm',
|
||||
'cursor-default hover:cursor-pointer',
|
||||
'hover:white dark:hover:white',
|
||||
'focus:white dark:focus:white',
|
||||
'select-none',
|
||||
'whitespace-nowrap'
|
||||
);
|
||||
|
||||
/**
|
||||
* Contains all the parts of a dropdown menu.
|
||||
*/
|
||||
export const NavDropdownMenu = DropdownMenuPrimitive.Root;
|
||||
|
||||
/**
|
||||
* The button that toggles the dropdown menu.
|
||||
* By default, the {@link NavDropdownMenuContent} will position itself against the trigger.
|
||||
*/
|
||||
export const NavDropdownMenuTrigger = forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,
|
||||
React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, forwardedRef) => {
|
||||
const triggerClasses = classNames(
|
||||
className,
|
||||
'bg-transparent whitespace-nowrap'
|
||||
);
|
||||
return (
|
||||
<DropdownMenuPrimitive.Trigger
|
||||
asChild={true}
|
||||
ref={forwardedRef}
|
||||
className={triggerClasses}
|
||||
{...props}
|
||||
>
|
||||
<span className="h-full">
|
||||
{children} <Icon name="arrow-down" className="ml-2" />
|
||||
</span>
|
||||
</DropdownMenuPrimitive.Trigger>
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* The component that pops out when the dropdown menu is open.
|
||||
*/
|
||||
export const NavDropdownMenuContent = forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||
React.ComponentProps<typeof DropdownMenuPrimitive.Content>
|
||||
>(({ className, ...contentProps }, forwardedRef) => (
|
||||
<DropdownMenuPrimitive.Content
|
||||
{...contentProps}
|
||||
ref={forwardedRef}
|
||||
className="min-w-[290px] bg-neutral-200 dark:bg-neutral-900 mt-4 p-2 rounded-xl border border-neutral-700 text-white"
|
||||
align="start"
|
||||
sideOffset={10}
|
||||
/>
|
||||
));
|
||||
|
||||
/**
|
||||
* The component that contains the dropdown menu items.
|
||||
*/
|
||||
export const NavDropdownMenuItem = forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||
React.ComponentProps<typeof DropdownMenuPrimitive.Item>
|
||||
>(({ className, ...itemProps }, forwardedRef) => (
|
||||
<DropdownMenuPrimitive.Item
|
||||
{...itemProps}
|
||||
ref={forwardedRef}
|
||||
className={classNames(itemClass, className)}
|
||||
/>
|
||||
));
|
1
libs/ui-toolkit/src/components/nav-dropdown/index.ts
Normal file
1
libs/ui-toolkit/src/components/nav-dropdown/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './dropdown-menu';
|
@ -2,16 +2,18 @@ import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export function getNavLinkClassNames(
|
||||
navbarTheme: string
|
||||
navbarTheme: string,
|
||||
fullWidth = false
|
||||
): (props: { isActive?: boolean }) => string | undefined {
|
||||
return ({ isActive = false }) => {
|
||||
return getActiveNavLinkClassNames(isActive, navbarTheme);
|
||||
return getActiveNavLinkClassNames(isActive, navbarTheme, fullWidth);
|
||||
};
|
||||
}
|
||||
|
||||
export const getActiveNavLinkClassNames = (
|
||||
isActive: boolean,
|
||||
navbarTheme: string
|
||||
navbarTheme: string,
|
||||
fullWidth = false
|
||||
): string | undefined => {
|
||||
return classNames('mx-2 py-3 self-end relative', {
|
||||
'cursor-default': isActive,
|
||||
@ -20,6 +22,7 @@ export const getActiveNavLinkClassNames = (
|
||||
!isActive && navbarTheme !== 'yellow',
|
||||
'text-black': isActive && navbarTheme === 'yellow',
|
||||
'text-black/60 hover:text-black': !isActive && navbarTheme === 'yellow',
|
||||
'flex-1': fullWidth,
|
||||
});
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user