mirror of
https://github.com/luxfi/explore.git
synced 2026-07-27 03:13:50 +00:00
fix(explore): un-stub org explorers (TLS) + mobile detail pages (v1.1.10)
Two mobile-QA defects across the block explorers.
Defect 1 (org explorers showed goerli stub data): api-explore.{zoo,hanzo,
pars}.network served the self-signed INGRESS DEFAULT CERT (no cert-manager
Ingress for those SNIs), so browsers rejected every FE fetch and react-query
fell back to the goerli stubs behind stuck skeletons. FE wiring + backend were
already correct (all 6 chains served real per-chain data via the existing
IngressRoutes). Fix: dedicated letsencrypt-prod Ingress per org api host ->
explorer-unified:8090, mirroring api-explore-lux-network. Certs issued, all
three now ssl_verify=0, real data flows. Also source per-org brand identity
(NETWORK_NAME/currency) in the configmaps so titles render the right brand.
Defect 2 (mobile 390px detail pages: dropped labels, horizontal clip, tab
overlap): three shared-UI fixes.
- Footer: was the real horizontal-overflow driver — inline gridTemplateColumns
minmax(auto,470px) never collapsed on mobile. Now grid-cols-1 on mobile.
- DetailedInfo.ItemValue: add min-w-0 + mobile wrap so long address/hash values
stop forcing the page wider than the viewport (desktop unchanged).
- AdaptiveTabsList: @luxfi/ui Tabs silently discard Chakra style props
(overflowX/w/flexShrink/...), so express mobile scroll + shrink-0 as
className. Tab strip now scrolls instead of collapsing/overlapping.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
@@ -166,6 +166,61 @@ All symlinks reference this single source of truth.
|
||||
|
||||
## Recent Changes
|
||||
|
||||
### Org explorers un-stubbed + mobile detail pages fixed (v1.1.10)
|
||||
Two mobile-QA defects across the block explorers.
|
||||
|
||||
- **Defect 1 (org explorers showed goerli stub data, not their real chains).**
|
||||
Root cause was purely TLS: `api-explore.{zoo,hanzo,pars}.network` served the
|
||||
self-signed `CN=INGRESS DEFAULT CERT` (no cert-manager Ingress for those SNIs,
|
||||
only lux had one). Real browsers reject it → every FE data fetch fails →
|
||||
react-query falls back to the `stubs/` placeholders (block #8988736, "Stub
|
||||
Token (goerli)") behind stuck skeletons. The FE wiring was already correct
|
||||
(configmaps point at `api-explore.<org>.network` + right chainId) and the
|
||||
backend already serves real per-chain data — the unified `explorer` pod has
|
||||
all 6 chains (`{"chains":6}`) and the org IngressRoutes route by Host /
|
||||
`blockscout-prefix-<brand>` → `/v1/indexer/<slug>` (proven: cchain 1.09M
|
||||
blocks, zoo 13,381, hanzo 11, pars 63). Fix = add a dedicated cert-manager
|
||||
`letsencrypt-prod` Ingress per org api host → `explorer-unified:8090`,
|
||||
mirroring `api-explore-lux-network` (declared in `ingress-api-mainnet.yaml`,
|
||||
in the kustomize resources). Certs issued in ~60s; all three now
|
||||
`ssl_verify=0`. **If an org explorer ever goes blank/stubs again, check
|
||||
`kubectl -n lux-mainnet get certificate api-explore-<org>-network-tls` first.**
|
||||
The `@luxfi/ui` browser (Playwright) ignores TLS errors, so it renders real
|
||||
data even when the cert is bad — verify Defect-1-class bugs with a REAL
|
||||
cert-enforcing client (openssl/curl without -k), not the MCP browser.
|
||||
|
||||
- **Defect 2 (mobile 390px detail pages: dropped labels, horizontal clip, tab
|
||||
overlap).** Three shared-UI fixes (fix all 4 explorers at once):
|
||||
- *Labels invisible* was the v1.1.9 white-on-white color fix (already landed);
|
||||
deploying v1.1.10 to the org images (they ran v1.0.14) is what makes it show.
|
||||
- *Horizontal clip* — the real page-overflow driver at 390px was the FOOTER,
|
||||
not the field rows. `ui/snippets/footer/Footer.tsx` used inline
|
||||
`gridTemplateColumns: 'minmax(auto,470px) 1fr'` + `repeat(colNum,160px)` with
|
||||
no mobile breakpoint → a 470px column past the 390 viewport. Made it
|
||||
`grid-cols-1 lg:grid-cols-[minmax(auto,470px)_1fr]` (single column on mobile;
|
||||
links `grid-cols-2` via a `--footer-cols` CSS var for the desktop count).
|
||||
docSW 486→390 verified on a fresh `next dev` render.
|
||||
- *Detail values* — `DetailedInfo.ItemValue` forced `whitespace-nowrap` with no
|
||||
`min-w-0`, so a long address/hash couldn't shrink and its dynamic shortener
|
||||
(`HashStringShortenDynamic`, which sizes off its parent width) never bounded.
|
||||
Added `min-w-0 whitespace-normal break-words overflow-x-hidden lg:whitespace-nowrap
|
||||
lg:overflow-x-visible` — desktop unchanged.
|
||||
- *Tab bar overlap/collide* — **`@luxfi/ui` `TabsList`/`TabsTrigger` silently
|
||||
DISCARD all Chakra style props** (`overflowX`, `w`, `mx`, `css` scroll-snap,
|
||||
`flexShrink`, sticky…): they destructure them to `_overflowX` etc. and only
|
||||
forward `className`/`style`. So the mobile scroll + `flexShrink={0}` the
|
||||
consumer passed never applied → triggers shrank and collided, no scroll.
|
||||
Fixed at the consumer (`AdaptiveTabsList.tsx`): express the mobile scroll as
|
||||
Tailwind `className` on TabsList (`overflow-x-auto snap-x -mx-3 px-3
|
||||
w-[calc(100%+24px)] lg:…`) and `className="shrink-0 snap-start"` on triggers.
|
||||
Verified: tab strip becomes a scroll container (sw 373→921) with no overlap.
|
||||
NOTE for future work: any Chakra-style prop on `@luxfi/ui` Tabs is a no-op —
|
||||
use `className`. `DetailedInfo.Container`'s `templateColumns` prop is likewise
|
||||
dropped (hardcoded grid); mobile is already single-column so it's benign.
|
||||
- Dev caveat: `next dev` crashes tab pages in `AdaptiveTabsMenu` (pre-existing
|
||||
dev-only `icons/dots.svg`/popover import resolving to object); prod build is
|
||||
fine. Verify tab/detail rendering on the deployed prod URL, footer on dev.
|
||||
|
||||
### api-explore.lux.network TLS cert fix — explorer data now loads (infra)
|
||||
The explorer FE was stuck in skeleton loaders ("0/5 CONNECTED", no blocks/txns)
|
||||
because **every** `api-explore.lux.network` request failed in the browser with
|
||||
|
||||
@@ -14,6 +14,16 @@ metadata:
|
||||
namespace: lux-mainnet
|
||||
labels: {app: explore-fe, brand: lux, network: mainnet}
|
||||
data:
|
||||
# Brand identity. Intended to be baked into the image, but the current
|
||||
# ghcr.io/luxfi/explore image does not set NETWORK_NAME/currency as runtime
|
||||
# env, so make_envs_script.sh never writes them to window.__envs and the
|
||||
# client renders "undefined stats" / undefined currency. Source them here
|
||||
# (make_envs reads pod env at startup) until the image bakes them.
|
||||
NEXT_PUBLIC_NETWORK_NAME: Lux Network
|
||||
NEXT_PUBLIC_NETWORK_SHORT_NAME: LUX
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_NAME: Lux
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL: LUX
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: "18"
|
||||
NEXT_PUBLIC_NETWORK_ID: "96369"
|
||||
NEXT_PUBLIC_IS_TESTNET: "false"
|
||||
NEXT_PUBLIC_API_HOST: api-explore.lux.network
|
||||
@@ -43,6 +53,13 @@ metadata:
|
||||
namespace: lux-mainnet
|
||||
labels: {app: explore-fe, brand: zoo, network: mainnet}
|
||||
data:
|
||||
# Brand identity (see explore-env-lux note): source here so make_envs writes
|
||||
# them to window.__envs, otherwise the client shows "undefined stats".
|
||||
NEXT_PUBLIC_NETWORK_NAME: Zoo Network
|
||||
NEXT_PUBLIC_NETWORK_SHORT_NAME: ZOO
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_NAME: Zoo
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL: ZOO
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: "18"
|
||||
NEXT_PUBLIC_NETWORK_ID: "200200"
|
||||
NEXT_PUBLIC_IS_TESTNET: "false"
|
||||
NEXT_PUBLIC_API_HOST: api-explore.zoo.network
|
||||
@@ -64,6 +81,13 @@ metadata:
|
||||
namespace: lux-mainnet
|
||||
labels: {app: explore-fe, brand: hanzo, network: mainnet}
|
||||
data:
|
||||
# Brand identity (see explore-env-lux note): source here so make_envs writes
|
||||
# them to window.__envs, otherwise the client shows "undefined stats".
|
||||
NEXT_PUBLIC_NETWORK_NAME: Hanzo
|
||||
NEXT_PUBLIC_NETWORK_SHORT_NAME: HANZO
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_NAME: Hanzo AI
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL: AI
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: "18"
|
||||
NEXT_PUBLIC_NETWORK_ID: "36963"
|
||||
NEXT_PUBLIC_IS_TESTNET: "false"
|
||||
NEXT_PUBLIC_API_HOST: api-explore.hanzo.network
|
||||
@@ -84,6 +108,13 @@ metadata:
|
||||
namespace: lux-mainnet
|
||||
labels: {app: explore-fe, brand: pars, network: mainnet}
|
||||
data:
|
||||
# Brand identity (see explore-env-lux note): source here so make_envs writes
|
||||
# them to window.__envs, otherwise the client shows "undefined stats".
|
||||
NEXT_PUBLIC_NETWORK_NAME: Pars
|
||||
NEXT_PUBLIC_NETWORK_SHORT_NAME: PARS
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_NAME: Pars
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL: PARS
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: "18"
|
||||
NEXT_PUBLIC_NETWORK_ID: "494949"
|
||||
NEXT_PUBLIC_IS_TESTNET: "false"
|
||||
NEXT_PUBLIC_API_HOST: api-explore.pars.network
|
||||
|
||||
@@ -39,3 +39,105 @@ spec:
|
||||
- hosts:
|
||||
- api-explore.lux.network
|
||||
secretName: api-explore-lux-network-tls
|
||||
---
|
||||
# Zoo Mainnet — explorer BACKEND API ingress (api-explore.zoo.network).
|
||||
#
|
||||
# Same root cause as lux above: without a dedicated Ingress declaring a TLS
|
||||
# block for this SNI, the controller serves the self-signed "INGRESS DEFAULT
|
||||
# CERT", browsers reject every api-explore.zoo.network request
|
||||
# (ERR_CERT_AUTHORITY_INVALID), the FE data fetches fail, and react-query
|
||||
# falls back to the goerli stub placeholders behind stuck skeleton loaders.
|
||||
# The chain routing already works (the IngressRoute lux-explorer-zoo rewrites
|
||||
# to /v1/indexer/zoo via the blockscout-prefix-zoo middleware and returns real
|
||||
# Zoo 200200 data) — this Ingress only supplies the browser-valid LE cert.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: api-explore-zoo-network
|
||||
namespace: lux-mainnet
|
||||
labels: {app: explore-fe, brand: zoo, network: mainnet}
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
|
||||
spec:
|
||||
ingressClassName: ingress
|
||||
rules:
|
||||
- host: api-explore.zoo.network
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: explorer-unified
|
||||
port:
|
||||
number: 8090
|
||||
tls:
|
||||
- hosts:
|
||||
- api-explore.zoo.network
|
||||
secretName: api-explore-zoo-network-tls
|
||||
---
|
||||
# Hanzo Mainnet — explorer BACKEND API ingress (api-explore.hanzo.network).
|
||||
# See the zoo block above; routes real Hanzo 36963 data via the
|
||||
# lux-explorer-hanzo IngressRoute + blockscout-prefix-hanzo. This Ingress only
|
||||
# supplies the browser-valid LE cert for the api-explore.hanzo.network SNI.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: api-explore-hanzo-network
|
||||
namespace: lux-mainnet
|
||||
labels: {app: explore-fe, brand: hanzo, network: mainnet}
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
|
||||
spec:
|
||||
ingressClassName: ingress
|
||||
rules:
|
||||
- host: api-explore.hanzo.network
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: explorer-unified
|
||||
port:
|
||||
number: 8090
|
||||
tls:
|
||||
- hosts:
|
||||
- api-explore.hanzo.network
|
||||
secretName: api-explore-hanzo-network-tls
|
||||
---
|
||||
# Pars Mainnet — explorer BACKEND API ingress (api-explore.pars.network).
|
||||
# See the zoo block above; routes real Pars 494949 data via the
|
||||
# lux-explorer-pars IngressRoute + blockscout-prefix-pars. This Ingress only
|
||||
# supplies the browser-valid LE cert for the api-explore.pars.network SNI.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: api-explore-pars-network
|
||||
namespace: lux-mainnet
|
||||
labels: {app: explore-fe, brand: pars, network: mainnet}
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
|
||||
spec:
|
||||
ingressClassName: ingress
|
||||
rules:
|
||||
- host: api-explore.pars.network
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: explorer-unified
|
||||
port:
|
||||
number: 8090
|
||||
tls:
|
||||
- hosts:
|
||||
- api-explore.pars.network
|
||||
secretName: api-explore-pars-network-tls
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@luxfi/explore",
|
||||
"version": "1.1.9",
|
||||
"version": "1.1.10",
|
||||
"private": false,
|
||||
"homepage": "https://github.com/luxfi/explore#readme",
|
||||
"engines": {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Skeleton } from '@luxfi/ui/skeleton';
|
||||
import type { TabsProps } from '@luxfi/ui/tabs';
|
||||
import { TabsCounter, TabsList, TabsTrigger } from '@luxfi/ui/tabs';
|
||||
import React from 'react';
|
||||
|
||||
import type { TabItemRegular } from './types';
|
||||
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import { cn } from 'lib/utils/cn';
|
||||
|
||||
import { Skeleton } from '@luxfi/ui/skeleton';
|
||||
import type { TabsProps } from '@luxfi/ui/tabs';
|
||||
import { TabsCounter, TabsList, TabsTrigger } from '@luxfi/ui/tabs';
|
||||
import { useIsSticky } from '../../hooks/useIsSticky';
|
||||
import AdaptiveTabsMenu from './AdaptiveTabsMenu';
|
||||
import useAdaptiveTabs from './useAdaptiveTabs';
|
||||
@@ -94,26 +95,18 @@ const AdaptiveTabsList = (props: Props) => {
|
||||
return (
|
||||
<TabsList
|
||||
ref={ listRef }
|
||||
flexWrap="nowrap"
|
||||
alignItems="center"
|
||||
whiteSpace="nowrap"
|
||||
bgColor="bg.primary"
|
||||
marginBottom={ 6 }
|
||||
mx={{ base: '-12px', lg: 'unset' }}
|
||||
px={{ base: '12px', lg: 'unset' }}
|
||||
w={{ base: 'calc(100% + 24px)', lg: '100%' }}
|
||||
overflowX={{ base: 'auto', lg: 'initial' }}
|
||||
overscrollBehaviorX="contain"
|
||||
css={{
|
||||
scrollSnapType: 'x mandatory',
|
||||
scrollPaddingInline: '12px', // mobile page padding
|
||||
// hide scrollbar
|
||||
'&::-webkit-scrollbar': { /* Chromiums */
|
||||
display: 'none',
|
||||
},
|
||||
'-ms-overflow-style': 'none', /* IE and Edge */
|
||||
scrollbarWidth: 'none', /* Firefox */
|
||||
}}
|
||||
// @luxfi/ui TabsList discards Chakra style props (overflowX/w/mx/px/css…),
|
||||
// so the mobile tab-bar behavior must be expressed as Tailwind classes —
|
||||
// the only styling hook it forwards. Without this the bar can't scroll on
|
||||
// mobile and the (non-shrinking) triggers collide past the 390px edge.
|
||||
className={ cn(
|
||||
'flex-nowrap items-center whitespace-nowrap bg-[var(--color-bg-primary)] mb-6',
|
||||
// mobile: bleed to the page edges, horizontal scroll-snap, hidden scrollbar
|
||||
'-mx-3 px-3 w-[calc(100%+24px)] overflow-x-auto overscroll-x-contain',
|
||||
'snap-x snap-mandatory scroll-px-3 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
|
||||
// desktop: no bleed, no scroll — the adaptive "…" menu handles overflow
|
||||
'lg:mx-0 lg:px-0 lg:w-full lg:overflow-x-visible lg:snap-none',
|
||||
) }
|
||||
{
|
||||
...(props.stickyEnabled ? {
|
||||
position: 'sticky',
|
||||
@@ -162,8 +155,7 @@ const AdaptiveTabsList = (props: Props) => {
|
||||
key={ value }
|
||||
value={ value }
|
||||
ref={ ref }
|
||||
scrollSnapAlign="start"
|
||||
flexShrink={ 0 }
|
||||
className="shrink-0 snap-start"
|
||||
{ ...getItemStyles(index, tabsCut, isLoading) }
|
||||
>
|
||||
{ typeof tab.title === 'function' ? tab.title() : tab.title }
|
||||
@@ -177,12 +169,12 @@ const AdaptiveTabsList = (props: Props) => {
|
||||
<TabsTrigger
|
||||
key={ value }
|
||||
value={ value }
|
||||
flexShrink={ 0 }
|
||||
bgColor={
|
||||
className={ cn(
|
||||
'shrink-0 snap-start',
|
||||
activeTabIndex === index && (variant === 'solid' || variant === undefined) ?
|
||||
{ _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' } :
|
||||
undefined
|
||||
}
|
||||
'bg-black/5 dark:bg-white/5' :
|
||||
undefined,
|
||||
) }
|
||||
>
|
||||
<Skeleton loading>
|
||||
{ typeof tab.title === 'function' ? tab.title() : tab.title }
|
||||
|
||||
@@ -70,7 +70,14 @@ export const ItemValue = ({ children, multiRow = false, className }: ItemValuePr
|
||||
return (
|
||||
<div
|
||||
className={ cn(
|
||||
'flex items-center pl-6 lg:pl-0 min-h-[30px] lg:min-h-[32px] whitespace-nowrap',
|
||||
// Mobile: keep a long value (address/hash entity) from pushing the page
|
||||
// wider than the viewport. min-w-0 lets the flex child shrink so the
|
||||
// entity's dynamic shortener truncates to fit the cell; break-words wraps
|
||||
// plain long strings; overflow-x-hidden is the hard guarantee that the
|
||||
// cell can never overflow past its column edge. Desktop is unchanged
|
||||
// (single-line nowrap, visible overflow).
|
||||
'flex items-center pl-6 lg:pl-0 min-h-[30px] lg:min-h-[32px]',
|
||||
'min-w-0 overflow-x-hidden whitespace-normal break-words lg:overflow-x-visible lg:whitespace-nowrap',
|
||||
multiRow && 'flex-wrap leading-[30px] lg:leading-[32px]',
|
||||
className,
|
||||
) }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Skeleton } from '@luxfi/ui/skeleton';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
|
||||
@@ -9,7 +10,6 @@ import type { ResourceError } from 'lib/api/resources';
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import useFetch from 'lib/hooks/useFetch';
|
||||
import { Link } from 'toolkit/next/link';
|
||||
import { Skeleton } from '@luxfi/ui/skeleton';
|
||||
import { copy } from 'toolkit/utils/htmlEntities';
|
||||
import { CONTENT_MAX_WIDTH } from 'ui/shared/layout/utils';
|
||||
import NetworkAddToWallet from 'ui/shared/NetworkAddToWallet';
|
||||
@@ -149,29 +149,28 @@ const Footer = () => {
|
||||
};
|
||||
|
||||
const horizontalPadding = config.UI.navigation.layout === 'horizontal' ? 'lg:px-6' : 'lg:px-12';
|
||||
// mobile: single column so the network description + link groups wrap inside
|
||||
// the viewport instead of forcing a 470px column past the 390px edge.
|
||||
const footerGridCols = 'grid grid-cols-1 lg:grid-cols-[minmax(auto,470px)_1fr]';
|
||||
|
||||
if (config.UI.footer.links) {
|
||||
return (
|
||||
<footer className="border-t border-[var(--color-border-divider)]">
|
||||
<div
|
||||
className={ `grid px-4 ${ horizontalPadding } 2xl:px-6 py-4 lg:py-8 mx-auto gap-x-8 lg:gap-x-[100px]` }
|
||||
style={{
|
||||
maxWidth: `${ CONTENT_MAX_WIDTH }px`,
|
||||
gridTemplateColumns: 'minmax(auto, 470px) 1fr',
|
||||
}}
|
||||
className={ `${ footerGridCols } px-4 ${ horizontalPadding } 2xl:px-6 py-4 lg:py-8 mx-auto gap-x-8 lg:gap-x-[100px]` }
|
||||
style={{ maxWidth: `${ CONTENT_MAX_WIDTH }px` }}
|
||||
>
|
||||
<div>
|
||||
<div className="min-w-0">
|
||||
{ renderNetworkInfo() }
|
||||
{ renderProjectInfo() }
|
||||
{ renderRecaptcha() }
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="grid gap-2 lg:gap-8 xl:gap-12 mt-8 lg:mt-0"
|
||||
style={{
|
||||
gridTemplateColumns: `repeat(${ colNum }, 160px)`,
|
||||
justifyContent: 'flex-end',
|
||||
}}
|
||||
// mobile: 2 flexible columns that fit the viewport; desktop: the
|
||||
// measured number of fixed 160px columns, right-aligned.
|
||||
className="grid grid-cols-2 lg:grid-cols-[repeat(var(--footer-cols),160px)] justify-start lg:justify-end gap-2 lg:gap-8 xl:gap-12 mt-8 lg:mt-0"
|
||||
style={{ '--footer-cols': colNum } as React.CSSProperties}
|
||||
>
|
||||
{
|
||||
([
|
||||
@@ -197,25 +196,15 @@ const Footer = () => {
|
||||
return (
|
||||
<footer className="border-t border-[var(--color-border-divider)]">
|
||||
<div
|
||||
className={ `grid px-4 ${ horizontalPadding } 2xl:px-6 py-4 lg:py-8 mx-auto` }
|
||||
style={{
|
||||
maxWidth: `${ CONTENT_MAX_WIDTH }px`,
|
||||
gridTemplateColumns: 'minmax(auto, 470px) 1fr',
|
||||
columnGap: '100px',
|
||||
}}
|
||||
className={ `${ footerGridCols } lg:gap-x-[100px] px-4 ${ horizontalPadding } 2xl:px-6 py-4 lg:py-8 mx-auto` }
|
||||
style={{ maxWidth: `${ CONTENT_MAX_WIDTH }px` }}
|
||||
>
|
||||
{ renderNetworkInfo() }
|
||||
{ renderProjectInfo() }
|
||||
{ renderRecaptcha() }
|
||||
|
||||
<div
|
||||
className="grid gap-1 mt-8 lg:mt-0"
|
||||
style={{
|
||||
gridTemplateColumns: 'repeat(auto-fill, 160px)',
|
||||
gridAutoFlow: 'column',
|
||||
alignContent: 'start',
|
||||
justifyContent: 'flex-end',
|
||||
}}
|
||||
className="grid grid-cols-2 lg:grid-flow-col lg:auto-cols-[160px] content-start justify-start lg:justify-end gap-1 mt-8 lg:mt-0"
|
||||
>
|
||||
{ FOOTER_LINKS.map(link => <FooterLinkItem { ...link } key={ link.text }/>) }
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user