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:
Dexter Edwards 2023-01-12 11:45:22 +00:00 committed by GitHub
parent d474a502a8
commit b8ff3d3050
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 403 additions and 124 deletions

View File

@ -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();

View File

@ -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');
});
});
});

View File

@ -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');
});

View File

@ -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 () {

View File

@ -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) => {

View File

@ -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) => {

View File

@ -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>
);
})}

View 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>
);
};

View 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>
);
};

View 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();
});
});

View File

@ -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>
);
};

View File

@ -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>

View File

@ -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} />
),
}}
/>

View File

@ -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>
</>
);
}

View File

@ -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}

View File

@ -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} />
),
}}
/>

View File

@ -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 /> },

View File

@ -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,
},
];

View File

@ -45,7 +45,7 @@ const Home = ({ name }: RouteChildProps) => {
trancheLink: (
<Link
data-testid="tranches-link"
to={Routes.TRANCHES}
to={Routes.SUPPLY}
className="underline text-white"
/>
),

View File

@ -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';

View File

@ -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>
);
};

View File

@ -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)}
/>
));

View File

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

View File

@ -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,
});
};