Commit bc291d93 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Navigation: horizontal layout (#1991)

* add ENV for navigation layout type

* add network menu to top bar

* renaming and main component placeholder

* add logo, testnet badge, login and connect wallet buttons to the nav bar

* refactor once more

* styles for nav link group

* style nav link

* style groupped nav links

* update values for L2 review

* update demo values

* fix deployment values for L2 review

* tests

* fix prop collision

* shorten filter input on marketplace page and update MainArea height

* change margin between text and lightning label

* add active state to "Other" nav menu

* popover fixes

* fix link width

* [skip ci] update demo values
parent 4ca72bd9
import type { ContractCodeIde } from 'types/client/contract'; import type { ContractCodeIde } from 'types/client/contract';
import { NAVIGATION_LINK_IDS, type NavItemExternal, type NavigationLinkId } from 'types/client/navigation-items'; import { NAVIGATION_LINK_IDS, type NavItemExternal, type NavigationLinkId, type NavigationLayout } from 'types/client/navigation';
import type { ChainIndicatorId } from 'types/homepage'; import type { ChainIndicatorId } from 'types/homepage';
import type { NetworkExplorer } from 'types/networks'; import type { NetworkExplorer } from 'types/networks';
import type { ColorThemeId } from 'types/settings'; import type { ColorThemeId } from 'types/settings';
...@@ -38,7 +38,7 @@ const defaultColorTheme = (() => { ...@@ -38,7 +38,7 @@ const defaultColorTheme = (() => {
const HOMEPAGE_PLATE_BACKGROUND_DEFAULT = 'radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)'; const HOMEPAGE_PLATE_BACKGROUND_DEFAULT = 'radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)';
const UI = Object.freeze({ const UI = Object.freeze({
sidebar: { navigation: {
logo: { logo: {
'default': getExternalAssetFilePath('NEXT_PUBLIC_NETWORK_LOGO'), 'default': getExternalAssetFilePath('NEXT_PUBLIC_NETWORK_LOGO'),
dark: getExternalAssetFilePath('NEXT_PUBLIC_NETWORK_LOGO_DARK'), dark: getExternalAssetFilePath('NEXT_PUBLIC_NETWORK_LOGO_DARK'),
...@@ -51,6 +51,7 @@ const UI = Object.freeze({ ...@@ -51,6 +51,7 @@ const UI = Object.freeze({
highlightedRoutes, highlightedRoutes,
otherLinks: parseEnvJson<Array<NavItemExternal>>(getEnvValue('NEXT_PUBLIC_OTHER_LINKS')) || [], otherLinks: parseEnvJson<Array<NavItemExternal>>(getEnvValue('NEXT_PUBLIC_OTHER_LINKS')) || [],
featuredNetworks: getExternalAssetFilePath('NEXT_PUBLIC_FEATURED_NETWORKS'), featuredNetworks: getExternalAssetFilePath('NEXT_PUBLIC_FEATURED_NETWORKS'),
layout: (getEnvValue('NEXT_PUBLIC_NAVIGATION_LAYOUT') || 'vertical') as NavigationLayout,
}, },
footer: { footer: {
links: getExternalAssetFilePath('NEXT_PUBLIC_FOOTER_LINKS'), links: getExternalAssetFilePath('NEXT_PUBLIC_FOOTER_LINKS'),
......
...@@ -17,8 +17,8 @@ import { GAS_UNITS } from '../../../types/client/gasTracker'; ...@@ -17,8 +17,8 @@ import { GAS_UNITS } from '../../../types/client/gasTracker';
import type { GasUnit } from '../../../types/client/gasTracker'; import type { GasUnit } from '../../../types/client/gasTracker';
import type { MarketplaceAppOverview, MarketplaceAppSecurityReportRaw, MarketplaceAppSecurityReport } from '../../../types/client/marketplace'; import type { MarketplaceAppOverview, MarketplaceAppSecurityReportRaw, MarketplaceAppSecurityReport } from '../../../types/client/marketplace';
import type { MultichainProviderConfig } from '../../../types/client/multichainProviderConfig'; import type { MultichainProviderConfig } from '../../../types/client/multichainProviderConfig';
import { NAVIGATION_LINK_IDS } from '../../../types/client/navigation-items'; import { NAVIGATION_LINK_IDS } from '../../../types/client/navigation';
import type { NavItemExternal, NavigationLinkId } from '../../../types/client/navigation-items'; import type { NavItemExternal, NavigationLinkId, NavigationLayout } from '../../../types/client/navigation';
import { ROLLUP_TYPES } from '../../../types/client/rollup'; import { ROLLUP_TYPES } from '../../../types/client/rollup';
import type { BridgedTokenChain, TokenBridge } from '../../../types/client/token'; import type { BridgedTokenChain, TokenBridge } from '../../../types/client/token';
import { PROVIDERS as TX_INTERPRETATION_PROVIDERS } from '../../../types/client/txInterpretation'; import { PROVIDERS as TX_INTERPRETATION_PROVIDERS } from '../../../types/client/txInterpretation';
...@@ -533,6 +533,7 @@ const schema = yup ...@@ -533,6 +533,7 @@ const schema = yup
.transform(replaceQuotes) .transform(replaceQuotes)
.json() .json()
.of(yup.string()), .of(yup.string()),
NEXT_PUBLIC_NAVIGATION_LAYOUT: yup.string<NavigationLayout>().oneOf([ 'horizontal', 'vertical' ]),
NEXT_PUBLIC_NETWORK_LOGO: yup.string().test(urlTest), NEXT_PUBLIC_NETWORK_LOGO: yup.string().test(urlTest),
NEXT_PUBLIC_NETWORK_LOGO_DARK: yup.string().test(urlTest), NEXT_PUBLIC_NETWORK_LOGO_DARK: yup.string().test(urlTest),
NEXT_PUBLIC_NETWORK_ICON: yup.string().test(urlTest), NEXT_PUBLIC_NETWORK_ICON: yup.string().test(urlTest),
......
...@@ -27,6 +27,7 @@ NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://example.com ...@@ -27,6 +27,7 @@ NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://example.com
NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED=true NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED=true
NEXT_PUBLIC_FEATURED_NETWORKS=https://example.com NEXT_PUBLIC_FEATURED_NETWORKS=https://example.com
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/accounts','/apps'] NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/accounts','/apps']
NEXT_PUBLIC_NAVIGATION_LAYOUT=horizontal
NEXT_PUBLIC_FOOTER_LINKS=https://example.com NEXT_PUBLIC_FOOTER_LINKS=https://example.com
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS=false NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS=false
......
...@@ -5,7 +5,7 @@ imagePullSecrets: ...@@ -5,7 +5,7 @@ imagePullSecrets:
config: config:
network: network:
id: 420 id: 420
name: "Base Göerli" name: "Base"
shortname: Base shortname: Base
currency: currency:
name: Ether name: Ether
...@@ -52,28 +52,31 @@ frontend: ...@@ -52,28 +52,31 @@ frontend:
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: validation NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: validation
NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/base.svg NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/base.svg
NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/base.svg NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/base.svg
NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/base-goerli.json NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/base-mainnet.json
NEXT_PUBLIC_API_HOST: blockscout-optimism-goerli.k8s-dev.blockscout.com NEXT_PUBLIC_API_HOST: base.blockscout.com
NEXT_PUBLIC_MARKETPLACE_ENABLED: true NEXT_PUBLIC_MARKETPLACE_ENABLED: true
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM: https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM: https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json
NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_STATS_API_HOST: https://stats-optimism-goerli.k8s-dev.blockscout.com NEXT_PUBLIC_STATS_API_HOST: https://stats-l2-base-mainnet.k8s-prod-1.blockscout.com
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/base-goerli.json
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND: "linear-gradient(136.9deg,rgb(107 94 236) 1.5%,rgb(0 82 255) 56.84%,rgb(82 62 231) 98.54%)" NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND: "linear-gradient(136.9deg,rgb(107 94 236) 1.5%,rgb(0 82 255) 56.84%,rgb(82 62 231) 98.54%)"
NEXT_PUBLIC_NETWORK_RPC_URL: https://goerli.optimism.io NEXT_PUBLIC_NETWORK_RPC_URL: https://mainnet.base.org
NEXT_PUBLIC_WEB3_WALLETS: "['coinbase']" NEXT_PUBLIC_WEB3_WALLETS: "['coinbase']"
NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET: true NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET: true
NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs']" NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs']"
NEXT_PUBLIC_VISUALIZE_API_HOST: https://visualizer-optimism-goerli.k8s-dev.blockscout.com NEXT_PUBLIC_VISUALIZE_API_HOST: https://visualizer.services.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info-test.k8s-dev.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info.services.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://admin-rs-test.k8s-dev.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://admin-rs.services.blockscout.com
NEXT_PUBLIC_ROLLUP_TYPE=optimistic NEXT_PUBLIC_NAME_SERVICE_API_HOST: https://bens.services.blockscout.com
NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://blockscout-main.k8s-dev.blockscout.com NEXT_PUBLIC_METADATA_SERVICE_API_HOST: https://metadata.services.blockscout.com
NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw NEXT_PUBLIC_ROLLUP_TYPE: optimistic
NEXT_PUBLIC_ROLLUP_L1_BASE_URL: https://eth.blockscout.com
NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL: https://bridge.base.org/withdraw
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62 NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62
NEXT_PUBLIC_USE_NEXT_JS_PROXY: true NEXT_PUBLIC_USE_NEXT_JS_PROXY: true
NEXT_PUBLIC_NAVIGATION_LAYOUT: horizontal
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/blocks','/name-domains']"
envFromSecret: envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI
...@@ -82,4 +85,4 @@ frontend: ...@@ -82,4 +85,4 @@ frontend:
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY
NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID
FAVICON_GENERATOR_API_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_FAVICON_GENERATOR_API_KEY FAVICON_GENERATOR_API_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_FAVICON_GENERATOR_API_KEY
NEXT_PUBLIC_OG_IMAGE_URL: https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/base-goerli.png?raw=true NEXT_PUBLIC_OG_IMAGE_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/base-mainnet.png
...@@ -93,6 +93,7 @@ frontend: ...@@ -93,6 +93,7 @@ frontend:
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE: "{ \"id\": \"632018\", \"width\": \"320\", \"height\": \"100\" }" NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE: "{ \"id\": \"632018\", \"width\": \"320\", \"height\": \"100\" }"
NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: true NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: true
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED: true NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED: true
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/apps']"
envFromSecret: envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI
......
...@@ -16,7 +16,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will ...@@ -16,7 +16,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
- [API configuration](ENVS.md#api-configuration) - [API configuration](ENVS.md#api-configuration)
- [UI configuration](ENVS.md#ui-configuration) - [UI configuration](ENVS.md#ui-configuration)
- [Homepage](ENVS.md#homepage) - [Homepage](ENVS.md#homepage)
- [Sidebar](ENVS.md#sidebar) - [Navigation](ENVS.md#navigation)
- [Footer](ENVS.md#footer) - [Footer](ENVS.md#footer)
- [Favicon](ENVS.md#favicon) - [Favicon](ENVS.md#favicon)
- [Meta](ENVS.md#meta) - [Meta](ENVS.md#meta)
...@@ -122,7 +122,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will ...@@ -122,7 +122,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
&nbsp; &nbsp;
### Sidebar ### Navigation
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | | Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- |
...@@ -134,6 +134,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will ...@@ -134,6 +134,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
| NEXT_PUBLIC_OTHER_LINKS | `Array<{url: string; text: string}>` | List of links for the "Other" navigation menu | - | - | `[{'url':'https://blockscout.com','text':'Blockscout'}]` | v1.0.x+ | | NEXT_PUBLIC_OTHER_LINKS | `Array<{url: string; text: string}>` | List of links for the "Other" navigation menu | - | - | `[{'url':'https://blockscout.com','text':'Blockscout'}]` | v1.0.x+ |
| NEXT_PUBLIC_NAVIGATION_HIDDEN_LINKS | `Array<LinkId>` | List of external links hidden in the navigation. Supported ids are `eth_rpc_api`, `rpc_api` | - | - | `['eth_rpc_api']` | v1.16.0+ | | NEXT_PUBLIC_NAVIGATION_HIDDEN_LINKS | `Array<LinkId>` | List of external links hidden in the navigation. Supported ids are `eth_rpc_api`, `rpc_api` | - | - | `['eth_rpc_api']` | v1.16.0+ |
| NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES | `Array<string>` | List of menu item routes that should have a lightning label | - | - | `['/accounts']` | v1.31.0+ | | NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES | `Array<string>` | List of menu item routes that should have a lightning label | - | - | `['/accounts']` | v1.31.0+ |
| NEXT_PUBLIC_NAVIGATION_LAYOUT | `vertical \| horizontal` | Navigation menu layout type | - | `vertical` | `horizontal` | v1.32.0+ |
#### Featured network configuration properties #### Featured network configuration properties
......
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { NavItemInternal, NavItem, NavGroupItem } from 'types/client/navigation-items'; import type { NavItemInternal, NavItem, NavGroupItem } from 'types/client/navigation';
import config from 'configs/app'; import config from 'configs/app';
import { rightLineArrow } from 'lib/html-entities'; import { rightLineArrow } from 'lib/html-entities';
...@@ -187,18 +187,37 @@ export default function useNavItems(): ReturnType { ...@@ -187,18 +187,37 @@ export default function useNavItems(): ReturnType {
icon: 'graphQL', icon: 'graphQL',
isActive: pathname === '/graphiql', isActive: pathname === '/graphiql',
} : null, } : null,
!config.UI.sidebar.hiddenLinks?.rpc_api && { !config.UI.navigation.hiddenLinks?.rpc_api && {
text: 'RPC API', text: 'RPC API',
icon: 'RPC', icon: 'RPC',
url: 'https://docs.blockscout.com/for-users/api/rpc-endpoints', url: 'https://docs.blockscout.com/for-users/api/rpc-endpoints',
}, },
!config.UI.sidebar.hiddenLinks?.eth_rpc_api && { !config.UI.navigation.hiddenLinks?.eth_rpc_api && {
text: 'Eth RPC API', text: 'Eth RPC API',
icon: 'RPC', icon: 'RPC',
url: ' https://docs.blockscout.com/for-users/api/eth-rpc', url: ' https://docs.blockscout.com/for-users/api/eth-rpc',
}, },
].filter(Boolean); ].filter(Boolean);
const otherNavItems: Array<NavItem> | Array<Array<NavItem>> = [
{
text: 'Verify contract',
nextRoute: { pathname: '/contract-verification' as const },
isActive: pathname.startsWith('/contract-verification'),
},
config.features.gasTracker.isEnabled && {
text: 'Gas tracker',
nextRoute: { pathname: '/gas-tracker' as const },
isActive: pathname.startsWith('/gas-tracker'),
},
config.features.publicTagsSubmission.isEnabled && {
text: 'Submit public tag',
nextRoute: { pathname: '/public-tags/submit' as const },
isActive: pathname.startsWith('/public-tags/submit'),
},
...config.UI.navigation.otherLinks,
].filter(Boolean);
const mainNavItems: ReturnType['mainNavItems'] = [ const mainNavItems: ReturnType['mainNavItems'] = [
{ {
text: 'Blockchain', text: 'Blockchain',
...@@ -233,24 +252,8 @@ export default function useNavItems(): ReturnType { ...@@ -233,24 +252,8 @@ export default function useNavItems(): ReturnType {
{ {
text: 'Other', text: 'Other',
icon: 'gear', icon: 'gear',
subItems: [ isActive: otherNavItems.flat().some(item => isInternalItem(item) && item.isActive),
{ subItems: otherNavItems,
text: 'Verify contract',
nextRoute: { pathname: '/contract-verification' as const },
isActive: pathname.startsWith('/contract-verification'),
},
config.features.gasTracker.isEnabled && {
text: 'Gas tracker',
nextRoute: { pathname: '/gas-tracker' as const },
isActive: pathname.startsWith('/gas-tracker'),
},
config.features.publicTagsSubmission.isEnabled && {
text: 'Submit public tag',
nextRoute: { pathname: '/public-tags/submit' as const },
isActive: pathname.startsWith('/public-tags/submit'),
},
...config.UI.sidebar.otherLinks,
].filter(Boolean),
}, },
].filter(Boolean); ].filter(Boolean);
......
...@@ -25,7 +25,7 @@ const wagmiConfig = (() => { ...@@ -25,7 +25,7 @@ const wagmiConfig = (() => {
name: `${ config.chain.name } explorer`, name: `${ config.chain.name } explorer`,
description: `${ config.chain.name } explorer`, description: `${ config.chain.name } explorer`,
url: config.app.baseUrl, url: config.app.baseUrl,
icons: [ config.UI.sidebar.icon.default ].filter(Boolean), icons: [ config.UI.navigation.icon.default ].filter(Boolean),
}, },
enableEmail: true, enableEmail: true,
ssr: true, ssr: true,
......
...@@ -3,7 +3,7 @@ import type { TestFixture, Page } from '@playwright/test'; ...@@ -3,7 +3,7 @@ import type { TestFixture, Page } from '@playwright/test';
import config from 'configs/app'; import config from 'configs/app';
import { buildExternalAssetFilePath } from 'configs/app/utils'; import { buildExternalAssetFilePath } from 'configs/app/utils';
export type MockConfigResponseFixture = (envName: string, envValue: string, content: string, isImage?: boolean) => Promise<void>; export type MockConfigResponseFixture = (envName: string, envValue: string, content: string, isImage?: boolean) => Promise<string>;
const fixture: TestFixture<MockConfigResponseFixture, { page: Page }> = async({ page }, use) => { const fixture: TestFixture<MockConfigResponseFixture, { page: Page }> = async({ page }, use) => {
await use(async(envName, envValue, content, isImage) => { await use(async(envName, envValue, content, isImage) => {
...@@ -21,6 +21,7 @@ const fixture: TestFixture<MockConfigResponseFixture, { page: Page }> = async({ ...@@ -21,6 +21,7 @@ const fixture: TestFixture<MockConfigResponseFixture, { page: Page }> = async({
})); }));
} }
return url;
}); });
}; };
......
...@@ -61,6 +61,9 @@ export const ENVS_MAP: Record<string, Array<[string, string]>> = { ...@@ -61,6 +61,9 @@ export const ENVS_MAP: Record<string, Array<[string, string]>> = {
noWalletClient: [ noWalletClient: [
[ 'NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID', '' ], [ 'NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID', '' ],
], ],
noAccount: [
[ 'NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED', 'false' ],
],
noNftMarketplaces: [ noNftMarketplaces: [
[ 'NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES', '' ], [ 'NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES', '' ],
], ],
......
...@@ -7,7 +7,7 @@ import type { IconName } from 'ui/shared/IconSvg'; ...@@ -7,7 +7,7 @@ import type { IconName } from 'ui/shared/IconSvg';
type NavIconOrComponent = { type NavIconOrComponent = {
icon?: IconName; icon?: IconName;
} | { } | {
iconComponent?: React.FC<{size?: number}>; iconComponent?: React.FC<{ size?: number; className?: string }>;
}; };
type NavItemCommon = { type NavItemCommon = {
...@@ -35,3 +35,5 @@ import type { ArrayElement } from '../utils'; ...@@ -35,3 +35,5 @@ import type { ArrayElement } from '../utils';
export const NAVIGATION_LINK_IDS = [ 'rpc_api', 'eth_rpc_api' ] as const; export const NAVIGATION_LINK_IDS = [ 'rpc_api', 'eth_rpc_api' ] as const;
export type NavigationLinkId = ArrayElement<typeof NAVIGATION_LINK_IDS>; export type NavigationLinkId = ArrayElement<typeof NAVIGATION_LINK_IDS>;
export type NavigationLayout = 'vertical' | 'horizontal';
...@@ -40,10 +40,12 @@ const Home = () => { ...@@ -40,10 +40,12 @@ const Home = () => {
`${ config.chain.name } explorer` `${ config.chain.name } explorer`
} }
</Heading> </Heading>
{ config.UI.navigation.layout === 'vertical' && (
<Box display={{ base: 'none', lg: 'flex' }}> <Box display={{ base: 'none', lg: 'flex' }}>
{ config.features.account.isEnabled && <ProfileMenuDesktop isHomePage/> } { config.features.account.isEnabled && <ProfileMenuDesktop isHomePage/> }
{ config.features.blockchainInteraction.isEnabled && <WalletMenuDesktop isHomePage/> } { config.features.blockchainInteraction.isEnabled && <WalletMenuDesktop isHomePage/> }
</Box> </Box>
) }
</Flex> </Flex>
<SearchBar isHomepage/> <SearchBar isHomepage/>
</Box> </Box>
......
...@@ -225,7 +225,7 @@ const Marketplace = () => { ...@@ -225,7 +225,7 @@ const Marketplace = () => {
placeholder="Find app by name or keyword..." placeholder="Find app by name or keyword..."
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
size={ feature.securityReportsUrl ? 'xs' : 'sm' } size={ feature.securityReportsUrl ? 'xs' : 'sm' }
flex="1" w={{ base: '100%', lg: '350px' }}
/> />
</Flex> </Flex>
......
...@@ -13,6 +13,7 @@ const LayoutDefault = ({ children }: Props) => { ...@@ -13,6 +13,7 @@ const LayoutDefault = ({ children }: Props) => {
return ( return (
<Layout.Container> <Layout.Container>
<Layout.TopRow/> <Layout.TopRow/>
<Layout.NavBar/>
<HeaderMobile/> <HeaderMobile/>
<Layout.MainArea> <Layout.MainArea>
<Layout.SideBar/> <Layout.SideBar/>
......
...@@ -13,6 +13,7 @@ const LayoutError = ({ children }: Props) => { ...@@ -13,6 +13,7 @@ const LayoutError = ({ children }: Props) => {
return ( return (
<Layout.Container> <Layout.Container>
<Layout.TopRow/> <Layout.TopRow/>
<Layout.NavBar/>
<HeaderMobile/> <HeaderMobile/>
<Layout.MainArea> <Layout.MainArea>
<Layout.SideBar/> <Layout.SideBar/>
......
...@@ -12,6 +12,7 @@ const LayoutHome = ({ children }: Props) => { ...@@ -12,6 +12,7 @@ const LayoutHome = ({ children }: Props) => {
return ( return (
<Layout.Container> <Layout.Container>
<Layout.TopRow/> <Layout.TopRow/>
<Layout.NavBar/>
<HeaderMobile hideSearchBar/> <HeaderMobile hideSearchBar/>
<Layout.MainArea> <Layout.MainArea>
<Layout.SideBar/> <Layout.SideBar/>
......
...@@ -9,6 +9,7 @@ const LayoutSearchResults = ({ children }: Props) => { ...@@ -9,6 +9,7 @@ const LayoutSearchResults = ({ children }: Props) => {
return ( return (
<Layout.Container> <Layout.Container>
<Layout.TopRow/> <Layout.TopRow/>
<Layout.NavBar/>
{ children } { children }
</Layout.Container> </Layout.Container>
); );
......
import { Flex, chakra } from '@chakra-ui/react'; import { Flex, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import config from 'configs/app';
interface Props { interface Props {
children: React.ReactNode; children: React.ReactNode;
className?: string; className?: string;
} }
const TOP_BAR_HEIGHT = 36;
const HORIZONTAL_NAV_BAR_HEIGHT = config.UI.navigation.layout === 'horizontal' ? 49 : 0;
const MainArea = ({ children, className }: Props) => { const MainArea = ({ children, className }: Props) => {
return ( return (
<Flex className={ className } w="100%" minH="calc(100vh - 36px)" alignItems="stretch"> <Flex
className={ className }
w="100%"
minH={{
base: `calc(100vh - ${ TOP_BAR_HEIGHT }px)`,
lg: `calc(100vh - ${ TOP_BAR_HEIGHT + HORIZONTAL_NAV_BAR_HEIGHT }px)`,
}}
alignItems="stretch"
>
{ children } { children }
</Flex> </Flex>
); );
......
import { Flex, chakra } from '@chakra-ui/react'; import { Flex, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import config from 'configs/app';
interface Props { interface Props {
className?: string; className?: string;
children: React.ReactNode; children: React.ReactNode;
...@@ -13,7 +15,7 @@ const MainColumn = ({ children, className }: Props) => { ...@@ -13,7 +15,7 @@ const MainColumn = ({ children, className }: Props) => {
flexDir="column" flexDir="column"
flexGrow={ 1 } flexGrow={ 1 }
w={{ base: '100%', lg: 'auto' }} w={{ base: '100%', lg: 'auto' }}
paddingX={{ base: 3, lg: 12 }} paddingX={{ base: 3, lg: config.UI.navigation.layout === 'horizontal' ? 6 : 12 }}
paddingTop={{ base: `${ 12 + 52 }px`, lg: 6 }} // 12px is top padding of content area, 52px is search bar height paddingTop={{ base: `${ 12 + 52 }px`, lg: 6 }} // 12px is top padding of content area, 52px is search bar height
paddingBottom={ 8 } paddingBottom={ 8 }
> >
......
import config from 'configs/app';
import NavigationDesktop from 'ui/snippets/navigation/horizontal/NavigationDesktop';
const EmptyComponent = () => null;
export default config.UI.navigation.layout === 'horizontal' ? NavigationDesktop : EmptyComponent;
import NavigationDesktop from 'ui/snippets/navigation/NavigationDesktop'; import config from 'configs/app';
import NavigationDesktop from 'ui/snippets/navigation/vertical/NavigationDesktop';
export default NavigationDesktop; const EmptyComponent = () => null;
export default config.UI.navigation.layout === 'horizontal' ? EmptyComponent : NavigationDesktop;
...@@ -5,6 +5,7 @@ import Container from './Container'; ...@@ -5,6 +5,7 @@ import Container from './Container';
import Content from './Content'; import Content from './Content';
import MainArea from './MainArea'; import MainArea from './MainArea';
import MainColumn from './MainColumn'; import MainColumn from './MainColumn';
import NavBar from './NavBar';
import SideBar from './SideBar'; import SideBar from './SideBar';
export { export {
...@@ -12,6 +13,7 @@ export { ...@@ -12,6 +13,7 @@ export {
Content, Content,
MainArea, MainArea,
SideBar, SideBar,
NavBar,
MainColumn, MainColumn,
Footer, Footer,
TopRow, TopRow,
......
...@@ -140,7 +140,7 @@ const Footer = () => { ...@@ -140,7 +140,7 @@ const Footer = () => {
const containerProps: GridProps = { const containerProps: GridProps = {
as: 'footer', as: 'footer',
px: { base: 4, lg: 12 }, px: { base: 4, lg: config.UI.navigation.layout === 'horizontal' ? 6 : 12 },
py: { base: 4, lg: 8 }, py: { base: 4, lg: 8 },
borderTop: '1px solid', borderTop: '1px solid',
borderColor: 'divider', borderColor: 'divider',
......
...@@ -3,7 +3,8 @@ import React from 'react'; ...@@ -3,7 +3,8 @@ import React from 'react';
import config from 'configs/app'; import config from 'configs/app';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import NavigationMobile from 'ui/snippets/navigation/NavigationMobile'; import NavigationMobile from 'ui/snippets/navigation/mobile/NavigationMobile';
import TestnetBadge from 'ui/snippets/navigation/TestnetBadge';
import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo';
import NetworkMenuButton from 'ui/snippets/networkMenu/NetworkMenuButton'; import NetworkMenuButton from 'ui/snippets/networkMenu/NetworkMenuButton';
import NetworkMenuContentMobile from 'ui/snippets/networkMenu/NetworkMenuContentMobile'; import NetworkMenuContentMobile from 'ui/snippets/networkMenu/NetworkMenuContentMobile';
...@@ -47,10 +48,10 @@ const Burger = ({ isMarketplaceAppPage }: Props) => { ...@@ -47,10 +48,10 @@ const Burger = ({ isMarketplaceAppPage }: Props) => {
<DrawerOverlay/> <DrawerOverlay/>
<DrawerContent maxWidth="330px"> <DrawerContent maxWidth="330px">
<DrawerBody p={ 6 } display="flex" flexDirection="column"> <DrawerBody p={ 6 } display="flex" flexDirection="column">
{ config.chain.isTestnet && <IconSvg name="testnet" h="14px" w="37px" color="red.400" alignSelf="flex-start"/> } <TestnetBadge alignSelf="flex-start"/>
<Flex alignItems="center" justifyContent="space-between"> <Flex alignItems="center" justifyContent="space-between">
<NetworkLogo onClick={ handleNetworkLogoClick }/> <NetworkLogo onClick={ handleNetworkLogoClick }/>
{ config.UI.sidebar.featuredNetworks ? ( { config.UI.navigation.featuredNetworks ? (
<NetworkMenuButton <NetworkMenuButton
isMobile isMobile
isActive={ networkMenu.isOpen } isActive={ networkMenu.isOpen }
......
...@@ -36,10 +36,12 @@ const HeaderDesktop = ({ renderSearchBar, isMarketplaceAppPage }: Props) => { ...@@ -36,10 +36,12 @@ const HeaderDesktop = ({ renderSearchBar, isMarketplaceAppPage }: Props) => {
<Box width="100%"> <Box width="100%">
{ searchBar } { searchBar }
</Box> </Box>
{ config.UI.navigation.layout === 'vertical' && (
<Box display="flex"> <Box display="flex">
{ config.features.account.isEnabled && <ProfileMenuDesktop/> } { config.features.account.isEnabled && <ProfileMenuDesktop/> }
{ config.features.blockchainInteraction.isEnabled && <WalletMenuDesktop/> } { config.features.blockchainInteraction.isEnabled && <WalletMenuDesktop/> }
</Box> </Box>
) }
</HStack> </HStack>
); );
}; };
......
import { useColorModeValue, useBreakpointValue } from '@chakra-ui/react'; import { useColorModeValue, useBreakpointValue, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps'; import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
...@@ -6,7 +6,13 @@ import IconSvg from 'ui/shared/IconSvg'; ...@@ -6,7 +6,13 @@ import IconSvg from 'ui/shared/IconSvg';
export const LIGHTNING_LABEL_CLASS_NAME = 'lightning-label'; export const LIGHTNING_LABEL_CLASS_NAME = 'lightning-label';
const LightningLabel = ({ bgColor, isCollapsed }: { bgColor?: string; isCollapsed?: boolean }) => { interface Props {
className?: string;
iconColor?: string;
isCollapsed?: boolean;
}
const LightningLabel = ({ className, iconColor, isCollapsed }: Props) => {
const isLgScreen = useBreakpointValue({ base: false, lg: true, xl: false }); const isLgScreen = useBreakpointValue({ base: false, lg: true, xl: false });
const themeBgColor = useColorModeValue('white', 'black'); const themeBgColor = useColorModeValue('white', 'black');
const defaultTransitionProps = getDefaultTransitionProps({ transitionProperty: 'color' }); const defaultTransitionProps = getDefaultTransitionProps({ transitionProperty: 'color' });
...@@ -15,14 +21,14 @@ const LightningLabel = ({ bgColor, isCollapsed }: { bgColor?: string; isCollapse ...@@ -15,14 +21,14 @@ const LightningLabel = ({ bgColor, isCollapsed }: { bgColor?: string; isCollapse
const color = React.useMemo(() => { const color = React.useMemo(() => {
if (isCollapsed || (!isExpanded && isLgScreen)) { if (isCollapsed || (!isExpanded && isLgScreen)) {
return (bgColor && bgColor !== 'transparent') ? bgColor : themeBgColor; return (iconColor && iconColor !== 'transparent') ? iconColor : themeBgColor;
} }
return 'transparent'; return 'transparent';
}, [ bgColor, themeBgColor, isCollapsed, isExpanded, isLgScreen ]); }, [ iconColor, themeBgColor, isCollapsed, isExpanded, isLgScreen ]);
return ( return (
<IconSvg <IconSvg
className={ LIGHTNING_LABEL_CLASS_NAME } className={ LIGHTNING_LABEL_CLASS_NAME + (className ? ` ${ className }` : '') }
name="lightning_navbar" name="lightning_navbar"
boxSize={ 4 } boxSize={ 4 }
ml={{ base: 1, lg: isExpanded ? 1 : 0, xl: isCollapsed ? 0 : 1 }} ml={{ base: 1, lg: isExpanded ? 1 : 0, xl: isCollapsed ? 0 : 1 }}
...@@ -35,4 +41,4 @@ const LightningLabel = ({ bgColor, isCollapsed }: { bgColor?: string; isCollapse ...@@ -35,4 +41,4 @@ const LightningLabel = ({ bgColor, isCollapsed }: { bgColor?: string; isCollapse
); );
}; };
export default LightningLabel; export default chakra(LightningLabel);
import { chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { NavItem, NavGroupItem } from 'types/client/navigation-items'; import type { NavItem, NavGroupItem } from 'types/client/navigation';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
const NavLinkIcon = ({ item }: { item: NavItem | NavGroupItem}) => { interface Props {
className?: string;
item: NavItem | NavGroupItem;
}
const NavLinkIcon = ({ item, className }: Props) => {
if ('icon' in item && item.icon) { if ('icon' in item && item.icon) {
return <IconSvg name={ item.icon } boxSize="30px" flexShrink={ 0 }/>; return <IconSvg className={ className } name={ item.icon } boxSize="30px" flexShrink={ 0 }/>;
} }
if ('iconComponent' in item && item.iconComponent) { if ('iconComponent' in item && item.iconComponent) {
const IconComponent = item.iconComponent; const IconComponent = item.iconComponent;
return <IconComponent size={ 30 }/>; return <IconComponent className={ className } size={ 30 }/>;
} }
return null; return null;
}; };
export default NavLinkIcon; export default chakra(NavLinkIcon);
import { chakra } from '@chakra-ui/react';
import React from 'react';
import config from 'configs/app';
import IconSvg from 'ui/shared/IconSvg';
interface Props {
className?: string;
}
const TestnetBadge = ({ className }: Props) => {
if (!config.chain.isTestnet) {
return null;
}
return <IconSvg className={ className } name="testnet" h="14px" w="37px" color="red.400"/>;
};
export default React.memo(chakra(TestnetBadge));
import { chakra } from '@chakra-ui/react';
import React from 'react';
import type { NavItem } from 'types/client/navigation';
import { route } from 'nextjs-routes';
import { isInternalItem } from 'lib/hooks/useNavItems';
import LinkExternal from 'ui/shared/links/LinkExternal';
import LinkInternal from 'ui/shared/links/LinkInternal';
import LightningLabel from '../LightningLabel';
import NavLinkIcon from '../NavLinkIcon';
import useColors from '../useColors';
import { checkRouteHighlight } from '../utils';
interface Props {
className?: string;
item: NavItem;
noIcon?: boolean;
}
const NavLink = ({ className, item, noIcon }: Props) => {
const isInternalLink = isInternalItem(item);
const colors = useColors();
const color = 'isActive' in item && item.isActive ? colors.text.active : colors.text.default;
const bgColor = 'isActive' in item && item.isActive ? colors.bg.active : colors.bg.default;
const Link = isInternalLink ? LinkInternal : LinkExternal;
const href = isInternalLink ? route(item.nextRoute) : item.url;
const isHighlighted = checkRouteHighlight(item);
return (
<chakra.li
listStyleType="none"
>
<Link
className={ className }
href={ href }
display="flex"
alignItems="center"
color={ color }
bgColor={ bgColor }
_hover={{ textDecoration: 'none', color: colors.text.hover }}
w="224px"
px={ 2 }
py="9px"
fontSize="sm"
lineHeight={ 5 }
fontWeight={ 500 }
borderRadius="base"
>
{ !noIcon && <NavLinkIcon item={ item } mr={ 3 }/> }
<chakra.span>{ item.text }</chakra.span>
{ isHighlighted && <LightningLabel iconColor={ bgColor } position={{ lg: 'static' }} ml={{ lg: '2px' }} isCollapsed={ false }/> }
</Link>
</chakra.li>
);
};
export default React.memo(chakra(NavLink));
import { HStack, Popover, PopoverBody, PopoverContent, PopoverTrigger, chakra, StackDivider } from '@chakra-ui/react';
import React from 'react';
import type { NavGroupItem } from 'types/client/navigation';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
import IconSvg from 'ui/shared/IconSvg';
import LightningLabel from '../LightningLabel';
import useColors from '../useColors';
import { checkRouteHighlight } from '../utils';
import NavLink from './NavLink';
interface Props {
item: NavGroupItem;
}
const NavLinkGroup = ({ item }: Props) => {
const colors = useColors();
const bgColor = item.isActive ? colors.bg.active : colors.bg.default;
const color = item.isActive ? colors.text.active : colors.text.default;
const isHighlighted = checkRouteHighlight(item.subItems);
const hasGroups = item.subItems.some((subItem) => Array.isArray(subItem));
return (
<Popover
trigger="hover"
placement="bottom-start"
isLazy
>
{ ({ isOpen }) => (
<>
<PopoverTrigger>
<chakra.li
listStyleType="none"
display="flex"
alignItems="center"
px={ 2 }
py={ 1.5 }
fontSize="sm"
lineHeight={ 5 }
fontWeight={ 500 }
cursor="pointer"
color={ isOpen ? colors.text.hover : color }
_hover={{ color: colors.text.hover }}
bgColor={ bgColor }
borderRadius="base"
{ ...getDefaultTransitionProps() }
>
{ item.text }
{ isHighlighted && <LightningLabel iconColor={ bgColor } position={{ lg: 'static' }} ml={{ lg: '2px' }}/> }
<IconSvg name="arrows/east-mini" boxSize={ 5 } transform="rotate(-90deg)" ml={ 1 }/>
</chakra.li>
</PopoverTrigger>
<PopoverContent w="fit-content">
<PopoverBody p={ 4 }>
{ hasGroups ? (
<HStack divider={ <StackDivider borderColor="divider"/> } alignItems="flex-start">
{ item.subItems.map((subItem, index) => {
if (!Array.isArray(subItem)) {
return <NavLink key={ subItem.text } item={ subItem }/>;
}
return (
<chakra.ul key={ index } display="flex" flexDir="column" rowGap={ 1 }>
{ subItem.map((navItem) => <NavLink key={ navItem.text } item={ navItem }/>) }
</chakra.ul>
);
}) }
</HStack>
) : (
<chakra.ul display="flex" flexDir="column" rowGap={ 1 }>
{ item.subItems.map((subItem) => {
if (Array.isArray(subItem)) {
return null;
}
return <NavLink key={ subItem.text } item={ subItem }/>;
}) }
</chakra.ul>
) }
</PopoverBody>
</PopoverContent>
</>
) }
</Popover>
);
};
export default React.memo(NavLinkGroup);
import type { BrowserContext } from '@playwright/test';
import React from 'react';
import * as profileMock from 'mocks/user/profile';
import { contextWithAuth } from 'playwright/fixtures/auth';
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
import { test, expect } from 'playwright/lib';
import NavigationDesktop from './NavigationDesktop';
const testWithAuth = test.extend<{ context: BrowserContext }>({
context: contextWithAuth,
});
testWithAuth('base view +@dark-mode', async({ render, mockApiResponse, mockAssetResponse, mockEnvs, page }) => {
const hooksConfig = {
router: {
route: '/blocks',
pathname: '/blocks',
},
};
await mockApiResponse('user_info', profileMock.base);
await mockAssetResponse(profileMock.base.avatar, './playwright/mocks/image_s.jpg');
await mockEnvs([
...ENVS_MAP.userOps,
...ENVS_MAP.nameService,
[ 'NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES', '["/blocks","/apps"]' ],
]);
const component = await render(<NavigationDesktop/>, { hooksConfig });
await component.getByText('Blockchain').hover();
await expect(page.getByText('Blocks')).toBeVisible();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1500, height: 450 } });
});
test('with groped items', async({ render, mockEnvs, page }) => {
const hooksConfig = {
router: {
route: '/apps',
pathname: '/apps',
},
};
await mockEnvs([
...ENVS_MAP.optimisticRollup,
...ENVS_MAP.userOps,
...ENVS_MAP.nameService,
...ENVS_MAP.navigationHighlightedRoutes,
...ENVS_MAP.noWalletClient,
...ENVS_MAP.noAccount,
]);
const component = await render(<NavigationDesktop/>, { hooksConfig });
await component.getByText('Blockchain').hover();
await expect(page.getByText('Blocks')).toBeVisible();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1500, height: 450 } });
});
import { chakra, Flex } from '@chakra-ui/react';
import React from 'react';
import config from 'configs/app';
import useNavItems, { isGroupItem } from 'lib/hooks/useNavItems';
import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo';
import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop';
import WalletMenuDesktop from 'ui/snippets/walletMenu/WalletMenuDesktop';
import TestnetBadge from '../TestnetBadge';
import NavLink from './NavLink';
import NavLinkGroup from './NavLinkGroup';
const NavigationDesktop = () => {
const { mainNavItems } = useNavItems();
return (
<Flex
display={{ base: 'none', lg: 'flex' }}
alignItems="center"
px={ 6 }
py={ 2 }
borderBottomWidth="1px"
borderColor="divider"
>
<NetworkLogo isCollapsed={ false } w={{ lg: 'auto' }} maxW="120px"/>
<TestnetBadge ml={ 3 }/>
<chakra.nav ml="auto" mr={ config.features.account.isEnabled || config.features.blockchainInteraction.isEnabled ? 8 : 0 }>
<Flex as="ul" columnGap={ 3 }>
{ mainNavItems.map((item) => {
if (isGroupItem(item)) {
return <NavLinkGroup key={ item.text } item={ item }/>;
} else {
return <NavLink key={ item.text } item={ item } noIcon py={ 1.5 } w="fit-content"/>;
}
}) }
</Flex>
</chakra.nav>
{ config.features.account.isEnabled && <ProfileMenuDesktop buttonBoxSize="32px"/> }
{ config.features.blockchainInteraction.isEnabled && <WalletMenuDesktop size="sm"/> }
</Flex>
);
};
export default React.memo(NavigationDesktop);
...@@ -6,14 +6,14 @@ import { ...@@ -6,14 +6,14 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { NavGroupItem } from 'types/client/navigation-items'; import type { NavGroupItem } from 'types/client/navigation';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LightningLabel from './LightningLabel'; import LightningLabel from '../LightningLabel';
import NavLinkIcon from './NavLinkIcon'; import NavLinkIcon from '../NavLinkIcon';
import useNavLinkStyleProps from './useNavLinkStyleProps'; import useNavLinkStyleProps from '../useNavLinkStyleProps';
import { checkRouteHighlight } from './utils'; import { checkRouteHighlight } from '../utils';
type Props = { type Props = {
item: NavGroupItem; item: NavGroupItem;
...@@ -43,7 +43,7 @@ const NavLinkGroup = ({ item, onClick, isExpanded }: Props) => { ...@@ -43,7 +43,7 @@ const NavLinkGroup = ({ item, onClick, isExpanded }: Props) => {
> >
{ item.text } { item.text }
</Text> </Text>
{ isHighlighted && (<LightningLabel bgColor={ styleProps.itemProps.bgColor }/>) } { isHighlighted && (<LightningLabel iconColor={ styleProps.itemProps.bgColor }/>) }
</HStack> </HStack>
<IconSvg name="arrows/east-mini" transform="rotate(180deg)" boxSize={ 6 }/> <IconSvg name="arrows/east-mini" transform="rotate(180deg)" boxSize={ 6 }/>
</Flex> </Flex>
......
...@@ -5,9 +5,9 @@ import React, { useCallback } from 'react'; ...@@ -5,9 +5,9 @@ import React, { useCallback } from 'react';
import useHasAccount from 'lib/hooks/useHasAccount'; import useHasAccount from 'lib/hooks/useHasAccount';
import useNavItems, { isGroupItem } from 'lib/hooks/useNavItems'; import useNavItems, { isGroupItem } from 'lib/hooks/useNavItems';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import NavLink from 'ui/snippets/navigation/NavLink';
import NavLinkGroupMobile from './NavLinkGroupMobile'; import NavLink from '../vertical/NavLink';
import NavLinkGroup from './NavLinkGroup';
const DRAWER_WIDTH = 330; const DRAWER_WIDTH = 330;
...@@ -66,7 +66,7 @@ const NavigationMobile = ({ onNavLinkClick, isMarketplaceAppPage }: Props) => { ...@@ -66,7 +66,7 @@ const NavigationMobile = ({ onNavLinkClick, isMarketplaceAppPage }: Props) => {
> >
{ mainNavItems.map((item, index) => { { mainNavItems.map((item, index) => {
if (isGroupItem(item)) { if (isGroupItem(item)) {
return <NavLinkGroupMobile key={ item.text } item={ item } onClick={ onGroupItemOpen(index) } isExpanded={ isMarketplaceAppPage }/>; return <NavLinkGroup key={ item.text } item={ item } onClick={ onGroupItemOpen(index) } isExpanded={ isMarketplaceAppPage }/>;
} else { } else {
return <NavLink key={ item.text } item={ item } onClick={ onNavLinkClick } isCollapsed={ isCollapsed }/>; return <NavLink key={ item.text } item={ item } onClick={ onNavLinkClick } isCollapsed={ isCollapsed }/>;
} }
......
...@@ -6,7 +6,6 @@ type Props = { ...@@ -6,7 +6,6 @@ type Props = {
isExpanded?: boolean; isExpanded?: boolean;
isCollapsed?: boolean; isCollapsed?: boolean;
isActive?: boolean; isActive?: boolean;
px?: string | number;
} }
export default function useNavLinkProps({ isExpanded, isCollapsed, isActive }: Props) { export default function useNavLinkProps({ isExpanded, isCollapsed, isActive }: Props) {
......
import type { NavItem } from 'types/client/navigation-items'; import type { NavItem } from 'types/client/navigation';
import config from 'configs/app'; import config from 'configs/app';
import { isInternalItem } from 'lib/hooks/useNavItems'; import { isInternalItem } from 'lib/hooks/useNavItems';
...@@ -7,5 +7,5 @@ export function checkRouteHighlight(item: NavItem | Array<NavItem> | Array<Array ...@@ -7,5 +7,5 @@ export function checkRouteHighlight(item: NavItem | Array<NavItem> | Array<Array
if (Array.isArray(item)) { if (Array.isArray(item)) {
return item.some((subItem) => checkRouteHighlight(subItem)); return item.some((subItem) => checkRouteHighlight(subItem));
} }
return isInternalItem(item) && (config.UI.sidebar.highlightedRoutes.includes(item.nextRoute.pathname)); return isInternalItem(item) && (config.UI.navigation.highlightedRoutes.includes(item.nextRoute.pathname));
} }
...@@ -2,7 +2,7 @@ import { Link, Text, HStack, Tooltip, Box, useBreakpointValue, chakra, shouldFor ...@@ -2,7 +2,7 @@ import { Link, Text, HStack, Tooltip, Box, useBreakpointValue, chakra, shouldFor
import NextLink from 'next/link'; import NextLink from 'next/link';
import React from 'react'; import React from 'react';
import type { NavItem } from 'types/client/navigation-items'; import type { NavItem } from 'types/client/navigation';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
...@@ -10,11 +10,11 @@ import useIsMobile from 'lib/hooks/useIsMobile'; ...@@ -10,11 +10,11 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import { isInternalItem } from 'lib/hooks/useNavItems'; import { isInternalItem } from 'lib/hooks/useNavItems';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LightningLabel, { LIGHTNING_LABEL_CLASS_NAME } from './LightningLabel'; import LightningLabel, { LIGHTNING_LABEL_CLASS_NAME } from '../LightningLabel';
import NavLinkIcon from './NavLinkIcon'; import NavLinkIcon from '../NavLinkIcon';
import useColors from './useColors'; import useColors from '../useColors';
import useNavLinkStyleProps from './useNavLinkStyleProps'; import useNavLinkStyleProps from '../useNavLinkStyleProps';
import { checkRouteHighlight } from './utils'; import { checkRouteHighlight } from '../utils';
type Props = { type Props = {
item: NavItem; item: NavItem;
...@@ -72,7 +72,7 @@ const NavLink = ({ item, isCollapsed, px, className, onClick, disableActiveState ...@@ -72,7 +72,7 @@ const NavLink = ({ item, isCollapsed, px, className, onClick, disableActiveState
{ !isInternalLink && <IconSvg name="arrows/north-east" boxSize={ 4 } color="text_secondary" verticalAlign="middle"/> } { !isInternalLink && <IconSvg name="arrows/north-east" boxSize={ 4 } color="text_secondary" verticalAlign="middle"/> }
</Text> </Text>
{ isHighlighted && ( { isHighlighted && (
<LightningLabel bgColor={ styleProps.itemProps.bgColor } isCollapsed={ isCollapsed }/> <LightningLabel iconColor={ styleProps.itemProps.bgColor } isCollapsed={ isCollapsed }/>
) } ) }
</HStack> </HStack>
</Tooltip> </Tooltip>
......
...@@ -11,22 +11,22 @@ import { ...@@ -11,22 +11,22 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { NavGroupItem } from 'types/client/navigation-items'; import type { NavGroupItem } from 'types/client/navigation';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LightningLabel from './LightningLabel'; import LightningLabel from '../LightningLabel';
import NavLinkIcon from '../NavLinkIcon';
import useNavLinkStyleProps from '../useNavLinkStyleProps';
import { checkRouteHighlight } from '../utils';
import NavLink from './NavLink'; import NavLink from './NavLink';
import NavLinkIcon from './NavLinkIcon';
import useNavLinkStyleProps from './useNavLinkStyleProps';
import { checkRouteHighlight } from './utils';
type Props = { type Props = {
item: NavGroupItem; item: NavGroupItem;
isCollapsed?: boolean; isCollapsed?: boolean;
} }
const NavLinkGroupDesktop = ({ item, isCollapsed }: Props) => { const NavLinkGroup = ({ item, isCollapsed }: Props) => {
const isExpanded = isCollapsed === false; const isExpanded = isCollapsed === false;
const styleProps = useNavLinkStyleProps({ isCollapsed, isExpanded, isActive: item.isActive }); const styleProps = useNavLinkStyleProps({ isCollapsed, isExpanded, isActive: item.isActive });
...@@ -58,7 +58,7 @@ const NavLinkGroupDesktop = ({ item, isCollapsed }: Props) => { ...@@ -58,7 +58,7 @@ const NavLinkGroupDesktop = ({ item, isCollapsed }: Props) => {
{ item.text } { item.text }
</Text> </Text>
{ isHighlighted && ( { isHighlighted && (
<LightningLabel bgColor={ styleProps.itemProps.bgColor } isCollapsed={ isCollapsed }/> <LightningLabel iconColor={ styleProps.itemProps.bgColor } isCollapsed={ isCollapsed }/>
) } ) }
<IconSvg <IconSvg
name="arrows/east-mini" name="arrows/east-mini"
...@@ -105,4 +105,4 @@ const NavLinkGroupDesktop = ({ item, isCollapsed }: Props) => { ...@@ -105,4 +105,4 @@ const NavLinkGroupDesktop = ({ item, isCollapsed }: Props) => {
); );
}; };
export default NavLinkGroupDesktop; export default NavLinkGroup;
...@@ -11,8 +11,9 @@ import IconSvg from 'ui/shared/IconSvg'; ...@@ -11,8 +11,9 @@ import IconSvg from 'ui/shared/IconSvg';
import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo';
import NetworkMenu from 'ui/snippets/networkMenu/NetworkMenu'; import NetworkMenu from 'ui/snippets/networkMenu/NetworkMenu';
import TestnetBadge from '../TestnetBadge';
import NavLink from './NavLink'; import NavLink from './NavLink';
import NavLinkGroupDesktop from './NavLinkGroupDesktop'; import NavLinkGroup from './NavLinkGroup';
const NavigationDesktop = () => { const NavigationDesktop = () => {
const appProps = useAppContext(); const appProps = useAppContext();
...@@ -71,7 +72,7 @@ const NavigationDesktop = () => { ...@@ -71,7 +72,7 @@ const NavigationDesktop = () => {
}} }}
onClick={ handleContainerClick } onClick={ handleContainerClick }
> >
{ config.chain.isTestnet && <IconSvg name="testnet" h="14px" w="49px" color="red.400" position="absolute" pl={ 3 } top="34px"/> } <TestnetBadge position="absolute" pl={ 3 } w="49px" top="34px"/>
<Box <Box
as="header" as="header"
display="flex" display="flex"
...@@ -87,13 +88,13 @@ const NavigationDesktop = () => { ...@@ -87,13 +88,13 @@ const NavigationDesktop = () => {
transitionTimingFunction="ease" transitionTimingFunction="ease"
> >
<NetworkLogo isCollapsed={ isCollapsed }/> <NetworkLogo isCollapsed={ isCollapsed }/>
{ Boolean(config.UI.sidebar.featuredNetworks) && <NetworkMenu isCollapsed={ isCollapsed }/> } { Boolean(config.UI.navigation.featuredNetworks) && <NetworkMenu isCollapsed={ isCollapsed }/> }
</Box> </Box>
<Box as="nav" mt={ 6 } w="100%"> <Box as="nav" mt={ 6 } w="100%">
<VStack as="ul" spacing="1" alignItems="flex-start"> <VStack as="ul" spacing="1" alignItems="flex-start">
{ mainNavItems.map((item) => { { mainNavItems.map((item) => {
if (isGroupItem(item)) { if (isGroupItem(item)) {
return <NavLinkGroupDesktop key={ item.text } item={ item } isCollapsed={ isCollapsed }/>; return <NavLinkGroup key={ item.text } item={ item } isCollapsed={ isCollapsed }/>;
} else { } else {
return <NavLink key={ item.text } item={ item } isCollapsed={ isCollapsed }/>; return <NavLink key={ item.text } item={ item } isCollapsed={ isCollapsed }/>;
} }
......
...@@ -26,7 +26,7 @@ const LogoFallback = ({ isCollapsed, isSmall }: { isCollapsed?: boolean; isSmall ...@@ -26,7 +26,7 @@ const LogoFallback = ({ isCollapsed, isSmall }: { isCollapsed?: boolean; isSmall
xl: isCollapsed ? 'none' : 'block', xl: isCollapsed ? 'none' : 'block',
}; };
if (config.UI.sidebar[field].default) { if (config.UI.navigation[field].default) {
return <Skeleton w="100%" borderRadius="sm" display={ display }/>; return <Skeleton w="100%" borderRadius="sm" display={ display }/>;
} }
...@@ -43,11 +43,11 @@ const LogoFallback = ({ isCollapsed, isSmall }: { isCollapsed?: boolean; isSmall ...@@ -43,11 +43,11 @@ const LogoFallback = ({ isCollapsed, isSmall }: { isCollapsed?: boolean; isSmall
const NetworkLogo = ({ isCollapsed, onClick, className }: Props) => { const NetworkLogo = ({ isCollapsed, onClick, className }: Props) => {
const logoSrc = useColorModeValue(config.UI.sidebar.logo.default, config.UI.sidebar.logo.dark || config.UI.sidebar.logo.default); const logoSrc = useColorModeValue(config.UI.navigation.logo.default, config.UI.navigation.logo.dark || config.UI.navigation.logo.default);
const iconSrc = useColorModeValue(config.UI.sidebar.icon.default, config.UI.sidebar.icon.dark || config.UI.sidebar.icon.default); const iconSrc = useColorModeValue(config.UI.navigation.icon.default, config.UI.navigation.icon.dark || config.UI.navigation.icon.default);
const darkModeFilter = { filter: 'brightness(0) invert(1)' }; const darkModeFilter = { filter: 'brightness(0) invert(1)' };
const logoStyle = useColorModeValue({}, !config.UI.sidebar.logo.dark ? darkModeFilter : {}); const logoStyle = useColorModeValue({}, !config.UI.navigation.logo.dark ? darkModeFilter : {});
const iconStyle = useColorModeValue({}, !config.UI.sidebar.icon.dark ? darkModeFilter : {}); const iconStyle = useColorModeValue({}, !config.UI.navigation.icon.dark ? darkModeFilter : {});
return ( return (
<Box <Box
......
...@@ -15,8 +15,8 @@ export default function useNetworkMenu() { ...@@ -15,8 +15,8 @@ export default function useNetworkMenu() {
const fetch = useFetch(); const fetch = useFetch();
const { isPending, data } = useQuery<unknown, ResourceError<unknown>, Array<FeaturedNetwork>>({ const { isPending, data } = useQuery<unknown, ResourceError<unknown>, Array<FeaturedNetwork>>({
queryKey: [ 'featured-network' ], queryKey: [ 'featured-network' ],
queryFn: async() => fetch(config.UI.sidebar.featuredNetworks || '', undefined, { resource: 'featured-network' }), queryFn: async() => fetch(config.UI.navigation.featuredNetworks || '', undefined, { resource: 'featured-network' }),
enabled: Boolean(config.UI.sidebar.featuredNetworks) && isOpen, enabled: Boolean(config.UI.navigation.featuredNetworks) && isOpen,
staleTime: Infinity, staleTime: Infinity,
}); });
......
...@@ -7,7 +7,7 @@ import config from 'configs/app'; ...@@ -7,7 +7,7 @@ import config from 'configs/app';
import useNavItems from 'lib/hooks/useNavItems'; import useNavItems from 'lib/hooks/useNavItems';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps'; import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
import NavLink from 'ui/snippets/navigation/NavLink'; import NavLink from 'ui/snippets/navigation/vertical/NavLink';
const feature = config.features.account; const feature = config.features.account;
......
...@@ -14,9 +14,10 @@ type Props = { ...@@ -14,9 +14,10 @@ type Props = {
isHomePage?: boolean; isHomePage?: boolean;
className?: string; className?: string;
fallbackIconSize?: number; fallbackIconSize?: number;
buttonBoxSize?: string;
}; };
const ProfileMenuDesktop = ({ isHomePage, className, fallbackIconSize }: Props) => { const ProfileMenuDesktop = ({ isHomePage, className, fallbackIconSize, buttonBoxSize }: Props) => {
const { data, error, isPending } = useFetchProfileInfo(); const { data, error, isPending } = useFetchProfileInfo();
const loginUrl = useLoginUrl(); const loginUrl = useLoginUrl();
const { themedBackground, themedBorderColor, themedColor } = useMenuButtonColors(); const { themedBackground, themedBorderColor, themedColor } = useMenuButtonColors();
...@@ -88,7 +89,7 @@ const ProfileMenuDesktop = ({ isHomePage, className, fallbackIconSize }: Props) ...@@ -88,7 +89,7 @@ const ProfileMenuDesktop = ({ isHomePage, className, fallbackIconSize }: Props)
icon={ <UserAvatar size={ 20 } fallbackIconSize={ fallbackIconSize }/> } icon={ <UserAvatar size={ 20 } fallbackIconSize={ fallbackIconSize }/> }
variant={ variant } variant={ variant }
colorScheme="blue" colorScheme="blue"
boxSize="40px" boxSize={ buttonBoxSize ?? '40px' }
flexShrink={ 0 } flexShrink={ 0 }
{ ...iconButtonProps } { ...iconButtonProps }
{ ...iconButtonStyles } { ...iconButtonStyles }
......
import { IconButton, Popover, PopoverTrigger } from '@chakra-ui/react';
import React from 'react';
import IconSvg from 'ui/shared/IconSvg';
import NetworkMenuContentDesktop from 'ui/snippets/networkMenu/NetworkMenuContentDesktop';
import useNetworkMenu from 'ui/snippets/networkMenu/useNetworkMenu';
const NetworkMenu = () => {
const menu = useNetworkMenu();
return (
<Popover placement="bottom-start" trigger="click" isLazy isOpen={ menu.isOpen } onClose={ menu.onClose }>
<PopoverTrigger>
<IconButton
variant="simple"
colorScheme="blue"
aria-label="Network menu"
icon={ <IconSvg name="networks" boxSize={ 4 }/> }
p="1px"
boxSize={ 4 }
borderRadius="none"
onClick={ menu.onToggle }
/>
</PopoverTrigger>
<NetworkMenuContentDesktop items={ menu.data } tabs={ menu.availableTabs }/>
</Popover>
);
};
export default React.memo(NetworkMenu);
import React from 'react'; import React from 'react';
import { FEATURED_NETWORKS_MOCK } from 'mocks/config/network';
import * as statsMock from 'mocks/stats/index'; import * as statsMock from 'mocks/stats/index';
import { test, expect } from 'playwright/lib'; import { test, expect } from 'playwright/lib';
...@@ -30,6 +31,22 @@ test('with secondary coin price +@mobile', async({ render, mockApiResponse }) => ...@@ -30,6 +31,22 @@ test('with secondary coin price +@mobile', async({ render, mockApiResponse }) =>
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test('with horizontal nav bar layout', async({ render, mockApiResponse, mockEnvs, mockConfigResponse, mockAssetResponse, page }) => {
const FEATURED_NETWORKS_URL = 'https://localhost:3000/featured-networks.json';
await mockApiResponse('stats', statsMock.base);
await mockEnvs([
[ 'NEXT_PUBLIC_NAVIGATION_LAYOUT', 'horizontal' ],
[ 'NEXT_PUBLIC_FEATURED_NETWORKS', FEATURED_NETWORKS_URL ],
]);
await mockConfigResponse('NEXT_PUBLIC_FEATURED_NETWORKS', FEATURED_NETWORKS_URL, FEATURED_NETWORKS_MOCK);
await mockAssetResponse('https://localhost:3000/my-logo.png', './playwright/mocks/image_s.jpg');
const component = await render(<TopBar/>);
await component.getByLabel('Network menu').click();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1500, height: 500 } });
});
test('with DeFi dropdown +@dark-mode +@mobile', async({ render, page, mockApiResponse, mockEnvs }) => { test('with DeFi dropdown +@dark-mode +@mobile', async({ render, page, mockApiResponse, mockEnvs }) => {
await mockEnvs([ await mockEnvs([
[ [
......
import { Flex, Divider, useColorModeValue } from '@chakra-ui/react'; import { Flex, Divider, useColorModeValue, Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import config from 'configs/app'; import config from 'configs/app';
import DeFiDropdown from './DeFiDropdown'; import DeFiDropdown from './DeFiDropdown';
import NetworkMenu from './NetworkMenu';
import Settings from './settings/Settings'; import Settings from './settings/Settings';
import TopBarStats from './TopBarStats'; import TopBarStats from './TopBarStats';
const feature = config.features.deFiDropdown;
const TopBar = () => { const TopBar = () => {
const bgColor = useColorModeValue('gray.50', 'whiteAlpha.100'); const bgColor = useColorModeValue('gray.50', 'whiteAlpha.100');
...@@ -22,13 +21,19 @@ const TopBar = () => { ...@@ -22,13 +21,19 @@ const TopBar = () => {
> >
<TopBarStats/> <TopBarStats/>
<Flex alignItems="center"> <Flex alignItems="center">
{ feature.isEnabled && ( { config.features.deFiDropdown.isEnabled && (
<> <>
<DeFiDropdown/> <DeFiDropdown/>
<Divider mr={ 3 } ml={{ base: 2, sm: 3 }} height={ 4 } orientation="vertical"/> <Divider mr={ 3 } ml={{ base: 2, sm: 3 }} height={ 4 } orientation="vertical"/>
</> </>
) } ) }
<Settings/> <Settings/>
{ config.UI.navigation.layout === 'horizontal' && Boolean(config.UI.navigation.featuredNetworks) && (
<Box display={{ base: 'none', lg: 'flex' }}>
<Divider mx={ 3 } height={ 4 } orientation="vertical"/>
<NetworkMenu/>
</Box>
) }
</Flex> </Flex>
</Flex> </Flex>
); );
......
...@@ -24,7 +24,7 @@ const Settings = () => { ...@@ -24,7 +24,7 @@ const Settings = () => {
</PopoverTrigger> </PopoverTrigger>
<PopoverContent overflowY="hidden" w="auto" fontSize="sm"> <PopoverContent overflowY="hidden" w="auto" fontSize="sm">
<PopoverBody boxShadow="2xl" p={ 4 }> <PopoverBody boxShadow="2xl" p={ 4 }>
<SettingsColorTheme/> <SettingsColorTheme onSelect={ onClose }/>
<Box borderColor="divider" borderWidth="1px" my={ 3 }/> <Box borderColor="divider" borderWidth="1px" my={ 3 }/>
<SettingsIdentIcon/> <SettingsIdentIcon/>
</PopoverBody> </PopoverBody>
......
...@@ -6,7 +6,11 @@ import { COLOR_THEMES } from 'lib/settings/colorTheme'; ...@@ -6,7 +6,11 @@ import { COLOR_THEMES } from 'lib/settings/colorTheme';
import SettingsSample from './SettingsSample'; import SettingsSample from './SettingsSample';
const SettingsColorTheme = () => { interface Props {
onSelect?: () => void;
}
const SettingsColorTheme = ({ onSelect }: Props) => {
const { setColorMode } = useColorMode(); const { setColorMode } = useColorMode();
const [ activeHex, setActiveHex ] = React.useState<string>(); const [ activeHex, setActiveHex ] = React.useState<string>();
...@@ -58,7 +62,8 @@ const SettingsColorTheme = () => { ...@@ -58,7 +62,8 @@ const SettingsColorTheme = () => {
setTheme(hex); setTheme(hex);
setActiveHex(hex); setActiveHex(hex);
}, [ setTheme ]); onSelect?.();
}, [ setTheme, onSelect ]);
const activeTheme = COLOR_THEMES.find((theme) => theme.hex === activeHex); const activeTheme = COLOR_THEMES.find((theme) => theme.hex === activeHex);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment