Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(explorer): add darkmode theme to SyntaxHighlighter #4127

8 changes: 8 additions & 0 deletions apps/explorer/src/components/ThemedIotaLogo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { IotaLogoWeb } from '@iota/ui-icons';

export function ThemedIotaLogo(): React.JSX.Element {
return <IotaLogoWeb className="text-neutral-10 dark:text-neutral-92" width={137} height={36} />;
}
12 changes: 6 additions & 6 deletions apps/explorer/src/components/footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

import { Divider } from '@iota/apps-ui-kit';
import { LegalLinks, LegalText } from './Legal';
import { IotaLogoWeb } from '@iota/ui-icons';
import { Link } from '~/components/ui';
import { FOOTER_LINKS } from '~/lib/constants';
import { ThemedIotaLogo } from '../ThemedIotaLogo';

function FooterLinks(): JSX.Element {
return (
Expand All @@ -26,11 +26,11 @@ function FooterLinks(): JSX.Element {

function Footer(): JSX.Element {
return (
<footer className="sticky top-[100%] bg-neutral-96 px-5 py-10 md:px-10 md:py-14">
<footer className="sticky top-[100%] px-5 py-10 md:px-10 md:py-14">
<nav className="container flex flex-col justify-center gap-md md:gap-lg">
<div className="flex flex-col-reverse items-center gap-7.5 md:flex-row md:justify-between ">
<div className="hidden self-center text-neutral-10 md:flex md:self-start">
<IotaLogoWeb width={137} height={36} />
<div className="hidden self-center md:flex md:self-start">
<ThemedIotaLogo />
</div>
<div>
<FooterLinks />
Expand All @@ -42,8 +42,8 @@ function Footer(): JSX.Element {
<LegalLinks />
</div>
</nav>
<div className="mt-4 flex justify-center pt-5 text-neutral-10 md:hidden md:self-start">
<IotaLogoWeb width={137} height={36} />
<div className="mt-4 flex justify-center pt-5 md:hidden md:self-start">
<ThemedIotaLogo />
</div>
<p className="mt-8 w-full text-center text-body-sm text-neutral-40">{EXPLORER_REV}</p>
</footer>
Expand Down
4 changes: 2 additions & 2 deletions apps/explorer/src/components/gas-breakdown/GasBreakdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ function GasPaymentLinks({ objectIds }: { objectIds: string[] }): JSX.Element {
function GasInfo({ label, info }: { label: string; info?: React.ReactNode }) {
return (
<div className="flex flex-col gap-2 md:flex-row md:gap-10">
<span className="w-full flex-shrink-0 text-label-lg text-neutral-40 dark:text-neutral-60 md:w-40">
<span className="w-full flex-shrink-0 text-label-lg text-neutral-40 md:w-40 dark:text-neutral-60">
{label}
</span>
{info ? (
info
) : (
<span className="text-label-lg text-neutral-40 dark:text-neutral-60 md:w-40">
<span className="text-label-lg text-neutral-40 md:w-40 dark:text-neutral-60">
--
</span>
)}
Expand Down
12 changes: 7 additions & 5 deletions apps/explorer/src/components/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,29 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { IotaLogoWeb } from '@iota/ui-icons';

import { NetworkSelector } from '../network';
import Search from '../search/Search';
import { LinkWithQuery } from '~/components/ui';
import { ThemeSwitcher, ThemedIotaLogo } from '~/components';

function Header(): JSX.Element {
return (
<header className="flex h-header justify-center overflow-visible bg-neutral-98">
<header className="flex h-header justify-center overflow-visible backdrop-blur-lg">
<div className="container flex h-full flex-1 items-center justify-between gap-5">
<LinkWithQuery
data-testid="nav-logo-button"
to="/"
className="flex flex-nowrap items-center gap-1 text-neutral-10"
>
<IotaLogoWeb width={137} height={36} />
<ThemedIotaLogo />
</LinkWithQuery>
<div className="flex w-[360px] justify-center">
<Search />
</div>
<NetworkSelector />
<div className="flex flex-row gap-xs">
<ThemeSwitcher />
<NetworkSelector />
</div>
</div>
</header>
);
Expand Down
58 changes: 58 additions & 0 deletions apps/explorer/src/components/header/ThemeSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { Button, ButtonType } from '@iota/apps-ui-kit';
import { DarkMode, LightMode } from '@iota/ui-icons';
import { useEffect, useLayoutEffect } from 'react';
import { useTheme, Theme } from '@iota/core';

const ICON_MAP: Record<Theme, (props: React.SVGProps<SVGSVGElement>) => JSX.Element> = {
[Theme.Light]: LightMode,
[Theme.Dark]: DarkMode,
};

export function ThemeSwitcher(): React.JSX.Element {
const { theme, setTheme } = useTheme();

const ThemeIcon = ICON_MAP[theme];

function handleOnClick(): void {
const newTheme = theme === Theme.Light ? Theme.Dark : Theme.Light;
setTheme(newTheme);
saveThemeToLocalStorage(newTheme);
}

function saveThemeToLocalStorage(newTheme: Theme): void {
localStorage.setItem('theme', newTheme);
}

function updateDocumentClass(theme: Theme): void {
document.documentElement.classList.toggle('dark', theme === Theme.Dark);
}

useLayoutEffect(() => {
const storedTheme = localStorage.getItem('theme') as Theme | null;
if (storedTheme) {
setTheme(storedTheme);
updateDocumentClass(storedTheme);
} else {
const prefersDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
const preferredTheme = prefersDarkTheme ? Theme.Dark : Theme.Light;

setTheme(preferredTheme);
updateDocumentClass(preferredTheme);
}
}, []);

useEffect(() => {
updateDocumentClass(theme);
}, [theme]);

return (
<Button
type={ButtonType.Ghost}
onClick={handleOnClick}
icon={<ThemeIcon className="h-5 w-5" />}
/>
);
}
1 change: 1 addition & 0 deletions apps/explorer/src/components/header/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
// SPDX-License-Identifier: Apache-2.0

export * from './Header';
export * from './ThemeSwitcher';
1 change: 1 addition & 0 deletions apps/explorer/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ export * from './AreaGraph';
export * from './GraphTooltipContent';
export * from './IotaTokenCard';
export * from './TransactionsCardGraph';
export * from './ThemedIotaLogo';
10 changes: 6 additions & 4 deletions apps/explorer/src/components/layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { KioskClientProvider, useCookieConsentBanner } from '@iota/core';
import { KioskClientProvider, useCookieConsentBanner, ThemeProvider } from '@iota/core';
import { IotaClientProvider, WalletProvider } from '@iota/dapp-kit';
import type { Network } from '@iota/iota-sdk/client';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
Expand Down Expand Up @@ -39,9 +39,11 @@ export function Layout(): JSX.Element {
<WalletProvider autoConnect enableUnsafeBurner={import.meta.env.DEV}>
<KioskClientProvider>
<NetworkContext.Provider value={[network, setNetwork]}>
<Outlet />
<Toaster />
<ReactQueryDevtools />
<ThemeProvider appId="iota-explorer">
<Outlet />
<Toaster />
<ReactQueryDevtools />
</ThemeProvider>
</NetworkContext.Provider>
</KioskClientProvider>
</WalletProvider>
Expand Down
2 changes: 1 addition & 1 deletion apps/explorer/src/components/layout/PageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function PageLayout({ content, loading }: PageLayoutProps): JSX.Element {
: "The explorer is running slower than usual. We're working to fix the issue and appreciate your patience.";

return (
<div className="relative min-h-screen w-full bg-neutral-98">
<div className="relative min-h-screen w-full">
<section ref={headerRef} className="fixed top-0 z-20 flex w-full flex-col">
{renderNetworkDegradeBanner && (
<InfoBox
Expand Down
20 changes: 11 additions & 9 deletions apps/explorer/src/components/module/PkgModulesWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export function PkgModulesWrapper({
isLoading={false}
suggestions={searchSuggestions}
renderSuggestion={(suggestion) => (
<div className="z-10 flex cursor-pointer justify-between bg-neutral-98">
<div className="z-10 flex cursor-pointer justify-between">
<ListItem
hideBottomBorder
onClick={() => onChangeModule(suggestion.label)}
Expand All @@ -131,7 +131,7 @@ export function PkgModulesWrapper({
)}
/>
</div>
<div className="flex-1 overflow-auto pt-sm">
<div className="max-h-[560px] flex-1 overflow-auto pt-sm">
<VerticalList>
<div className="flex flex-col gap-sm">
{moduleNames.map((name) => (
Expand Down Expand Up @@ -189,13 +189,15 @@ function ExecutePanelContent({
))}
</SegmentedButton>

<ListTabContent id={EXECUTE_TAB.id}>
<ModuleFunctionsInteraction
key={`${packageId}-${moduleName}`}
packageId={packageId}
moduleName={moduleName}
/>
</ListTabContent>
<div className="pr-md--rs">
<ListTabContent id={EXECUTE_TAB.id}>
<ModuleFunctionsInteraction
key={`${packageId}-${moduleName}`}
packageId={packageId}
moduleName={moduleName}
/>
</ListTabContent>
</div>
</TabsProvider>
</TabbedContentWrapper>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/explorer/src/components/object/ObjectFieldsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export function ObjectFieldsCard({
}}
isLoading={false}
renderSuggestion={(suggestion) => (
<div className="flex cursor-pointer justify-between bg-neutral-98">
<div className="flex cursor-pointer justify-between">
<ListItem hideBottomBorder>
<div className="overflow-hidden text-ellipsis">
{suggestion.label}
Expand Down
2 changes: 1 addition & 1 deletion apps/explorer/src/components/search/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function Search(): JSX.Element {
isLoading={isPending || debouncedQuery !== query}
suggestions={results}
renderSuggestion={(suggestion) => (
<div className="flex cursor-pointer justify-between bg-neutral-98">
<div className="flex cursor-pointer justify-between">
<ListItem hideBottomBorder>
<div className="overflow-hidden text-ellipsis">{suggestion.label}</div>
<div className="break-words pl-xs text-caption font-medium uppercase text-steel">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import type { Language } from 'prism-react-renderer';
import type { IotaMoveNormalizedType } from '@iota/iota-sdk/client';
import { LinkWithQuery } from '../ui';
import { normalizeIotaAddress } from '@iota/iota-sdk/utils';
import { useTheme } from '~/hooks';
import { Theme } from '~/lib/ui';

interface TypeReference {
address: string;
Expand All @@ -35,19 +37,25 @@ export function SyntaxHighlighter({
const observerElem = useRef<HTMLDivElement | null>(null);
const { isIntersecting } = useOnScreen(observerElem);
const [loadedLines, setLoadedLines] = useState(MAX_LINES);
const { theme } = useTheme();

useEffect(() => {
if (isIntersecting) {
setLoadedLines((prev) => prev + MAX_LINES);
}
}, [isIntersecting]);

const isDark = theme === Theme.Dark;
const codeTheme = isDark ? themes.oneDark : themes.github;

return (
<div className="overflow-auto whitespace-pre font-mono text-sm">
<Highlight code={code} language={language} theme={themes.github}>
<Highlight code={code} language={language} theme={codeTheme}>
{({ style, tokens, getLineProps, getTokenProps }) => (
<pre className="overflow-auto bg-transparent p-xs font-medium" style={style}>
{tokens.slice(0, loadedLines).map((line, i) => (
<div {...getLineProps({ line, key: i })} key={i} className="table-row">
<div className="table-cell select-none pr-4 text-left text-primary-30 opacity-50">
<div className="table-cell select-none pr-4 text-left text-primary-30 opacity-50 dark:text-primary-80">
{i + 1}
</div>

Expand Down
8 changes: 8 additions & 0 deletions apps/explorer/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ body,
--steel-dark: theme('colors.steel.dark');
}

:root {
@apply bg-neutral-98;
}

:root.dark {
@apply bg-neutral-4;
}

@layer base {
body {
@apply antialiased;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function BalanceChangeEntry({ change }: { change: BalanceChange }): JSX.Element
</Card>
{recipient && (
<div className="flex flex-wrap items-center justify-between px-sm py-xs">
<span className="w-full flex-shrink-0 text-label-lg text-neutral-40 dark:text-neutral-60 md:w-40">
<span className="w-full flex-shrink-0 text-label-lg text-neutral-40 md:w-40 dark:text-neutral-60">
Recipient
</span>
<AddressLink address={recipient} />
Expand Down
1 change: 1 addition & 0 deletions apps/explorer/tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import uiKitResponsivePreset from '../../apps/ui-kit/src/lib/tailwind/responsive
export default {
presets: [preset, uiKitResponsivePreset],
content: ['./src/**/*.{js,jsx,ts,tsx}', './../ui-kit/src/lib/**/*.{js,jsx,ts,tsx}'],
darkMode: 'selector',
theme: {
// This COLOR are duplicated from @iota/core tailwind.config.ts!!!
// They are repeated here cause uiKitResponsivePreset overwrites the colors, and they are still used throughout Explorer
Expand Down
Loading