Skip to content

Commit

Permalink
Merge pull request #569 from chatch/toggle-color-themes
Browse files Browse the repository at this point in the history
toggle color themes
  • Loading branch information
chatch authored Jan 4, 2024
2 parents 9ba51d3 + ba53e8d commit efcbd1b
Show file tree
Hide file tree
Showing 17 changed files with 494 additions and 50 deletions.
3 changes: 2 additions & 1 deletion app/SearchBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ export default function SearchBox() {
>
<img
src={infoCircleSvg}
style={{ color: 'white', height: 24, width: 24 }}
alt="search info"
className="info-icon"
onClick={handleClickFn}
/>
</InputGroup.Text>
Expand Down
1 change: 0 additions & 1 deletion app/components/LiquidityPoolTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ export default function LiquidityPoolTable({
<th>
<FormattedMessage id="liquidity-pool.trustlines" />
</th>
<th />
</tr>
</thead>
<tbody>
Expand Down
7 changes: 6 additions & 1 deletion app/components/layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { LinkContainer } from 'react-router-bootstrap'
import logoImg from '../../img/logo.png'
import LanguageSelector from './LanguageSelector'
import NetworkSelector from './NetworkSelector'
import ThemeSwitcher from './ThemeSwitcher'
import type { NetworkDetails } from '~/lib/stellar/networks'

interface HeaderProps extends NetworkDetails {
Expand All @@ -24,6 +25,7 @@ export default function Header({
customSorobanRPCAddress,
}: Readonly<HeaderProps>) {
const { formatMessage } = useIntl()

return (
<Navbar collapseOnSelect expand="xxl" fixed="top">
<Navbar.Brand href="/">
Expand Down Expand Up @@ -60,12 +62,12 @@ export default function Header({
<Nav.Link href="/exchanges">
<FormattedMessage id="exchanges" />
</Nav.Link>

<div className="divider-vertical" />

<NavDropdown
title={formatMessage({ id: 'more' })}
id="basic-nav-dropdown"
className="nav-dropdown"
>
<LinkContainer to="/effects">
<NavDropdown.Item>
Expand Down Expand Up @@ -99,6 +101,9 @@ export default function Header({
customSorobanRPCAddress={customSorobanRPCAddress}
/>
</NavItem>
<NavItem className="theme-selector-wrapper">
<ThemeSwitcher />
</NavItem>
<NavItem>
<LanguageSelector switcher={languageSwitcher} />
</NavItem>
Expand Down
2 changes: 1 addition & 1 deletion app/components/layout/LanguageSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default function LanguageSelector({
}) {
const { formatMessage } = useIntl()
return (
<Dropdown id="language-selector">
<Dropdown id="language-selector" className="nav-dropdown">
<Dropdown.Toggle style={styleLanguageSelector}>
<img
src={langSelectImg}
Expand Down
28 changes: 28 additions & 0 deletions app/components/layout/ThemeSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Theme, useTheme } from '~/context/theme.provider'
import lightIcon from 'public/img/sun.svg'
import darkIcon from 'public/img/moon.svg'

const ThemeSwitcher = () => {
const [theme, setTheme] = useTheme()
const toggleTheme = () => {
const newTheme = theme === Theme.Light ? Theme.Dark : Theme.Light
localStorage.setItem('theme', newTheme)
setTheme(newTheme)
}

return (
<div onClick={toggleTheme} className="theme-selector">
{theme === Theme.Light ? <LightIcon /> : <DarkIcon />}
</div>
)
}

const DarkIcon = () => {
return <img src={darkIcon} alt="moon" width={28} height={28} />
}

const LightIcon = () => {
return <img src={lightIcon} alt="sun" width={28} height={28} />
}

export default ThemeSwitcher
4 changes: 1 addition & 3 deletions app/components/shared/ClipboardCopy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useState } from 'react'
import OverlayTrigger from 'react-bootstrap/OverlayTrigger'
import Tooltip from 'react-bootstrap/Tooltip'
import clipSvg from '../../../public/clipboard.svg'

const TooltipCopy = <Tooltip id="tooltip-copy">Copy to Clipboard</Tooltip>
const TooltipCopied = (
<Tooltip id="tooltip-copied" className="in">
Expand All @@ -18,14 +17,13 @@ function ClipboardCopy({ text }: { text: string }) {
setCopied(true)
setTimeout(() => setCopied(false), 10000)
}

return (
<OverlayTrigger delay={300} overlay={copied ? TooltipCopied : TooltipCopy}>
<img
src={clipSvg}
alt="clipboard"
onClick={handleCopyFn}
style={{ color: 'white', height: 14, width: 14, marginLeft: 10 }}
className="clipboard-icon"
/>
</OverlayTrigger>
)
Expand Down
2 changes: 1 addition & 1 deletion app/components/shared/CustomNetworkButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ const ResourceModal = (props: ResourceModalProps) => (
onHide={props.handleCloseFn as () => void}
>
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title-lg" style={{ color: '#dce2ec' }}>
<Modal.Title id="contained-modal-title-lg">
<FormattedMessage id="network.set-custom" />
</Modal.Title>
</Modal.Header>
Expand Down
4 changes: 2 additions & 2 deletions app/components/shared/NewWindowIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import externalLinkSvg from '../../../public/external-link.svg'
const NewWindowIcon = () => (
<img
src={externalLinkSvg}
alt="clipboard"
style={{ color: 'white', height: 14, width: 14, marginLeft: 10 }}
alt="open new window"
className="new-window-icon"
/>
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ describe('BackendResourceBadgeButtonWithResourceModal', () => {
expect(screen.getByRole('button', { name: 'Copy' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument()
expect(
screen.getByRole('link', { name: `${url} clipboard` }),
screen.getByRole('link', { name: `${url} open new window` }),
).toBeInTheDocument()
expect(
screen.getByRole('img', { name: 'open new window' }),
).toBeInTheDocument()
expect(screen.getByRole('img', { name: 'clipboard' })).toBeInTheDocument()
})

it('closes the modal when close button is clicked', async () => {
Expand Down
37 changes: 37 additions & 0 deletions app/context/theme.provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { ReactNode, Dispatch, SetStateAction } from 'react'
import { createContext, useContext, useState, useEffect } from 'react'

enum Theme {
Dark = 'dark',
Light = 'light',
}

type ThemeContextType = [
Theme | undefined,
Dispatch<SetStateAction<Theme | undefined>>,
]

const ThemeContext = createContext<ThemeContextType>([undefined, () => {}])

function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme | undefined>(Theme.Light)

useEffect(() => {
const savedTheme = localStorage.getItem('theme')
if (savedTheme) {
setTheme(savedTheme as Theme)
}
}, [])

return (
<ThemeContext.Provider value={[theme, setTheme]}>
{children}
</ThemeContext.Provider>
)
}

function useTheme(): ThemeContextType {
return useContext(ThemeContext)
}

export { Theme, ThemeProvider, useTheme }
82 changes: 48 additions & 34 deletions app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { PropsWithChildren } from 'react'
import { useState } from 'react'
import { useEffect, useState } from 'react'
import { IntlProvider } from 'react-intl'
import { cssBundleHref } from '@remix-run/css-bundle'
import { json } from '@remix-run/node'
Expand All @@ -20,6 +20,7 @@ import { captureRemixErrorBoundaryError, withSentry } from '@sentry/remix'
import bootstrapStyles from 'bootstrap/dist/css/bootstrap.css'
import jsonPrettyStyles from 'react-json-pretty/themes/1337.css'
import siteStyles from '~/styles/styles.css'
import lightSiteStyles from '~/styles/styles.light.css'

import Footer from './components/layout/Footer'
import Header from './components/layout/Header'
Expand All @@ -38,12 +39,14 @@ import { requestToNetworkDetails } from './lib/stellar/networks'
import { storageInit } from './lib/utils'
import SearchBox from './SearchBox'
import { NotFoundError } from 'stellar-sdk'
import { ThemeProvider, useTheme } from '~/context/theme.provider'
import type { ErrorBoundaryComponent } from '@remix-run/react/dist/routeModules'

export const links: LinksFunction = () => [
{ rel: 'stylesheet', href: bootstrapStyles },
{ rel: 'stylesheet', href: jsonPrettyStyles },
{ rel: 'stylesheet', href: siteStyles },
{ rel: 'stylesheet', href: lightSiteStyles },
...(cssBundleHref ? [{ rel: 'stylesheet', href: cssBundleHref }] : []),
]

Expand Down Expand Up @@ -84,8 +87,15 @@ function HtmlDocument({
children,
title,
}: PropsWithChildren<{ title?: string }>) {
const [theme] = useTheme()
const [isLoading, setIsLoading] = useState(true)

useEffect(() => {
setIsLoading(false)
}, [])

return (
<html lang="en">
<html lang="en" data-bs-theme={theme}>
<head>
<meta charSet="utf-8" />
<meta
Expand All @@ -105,7 +115,7 @@ function HtmlDocument({
<Links />
</head>
<body>
{children}
{!isLoading && children}
<ScrollRestoration />
<Scripts />
<LiveReload />
Expand Down Expand Up @@ -136,32 +146,34 @@ function App() {
} = useLoaderData<typeof loader>()

return (
<HtmlDocument>
<div className="App">
<IntlProvider
key={language}
locale={language}
messages={getMessages(language)}
>
<Header
languageSwitcher={languageSwitcherFn(setLanguage)}
networkType={networkType}
isLocal={isLocal}
isCustom={isCustom}
customHorizonAddress={horizonAddress}
customSorobanRPCAddress={sorobanRPCAddress}
/>
<SearchBox />
<div
id="main-content"
className={navigation.state === 'loading' ? 'loading' : ''}
<ThemeProvider>
<HtmlDocument>
<div className="App">
<IntlProvider
key={language}
locale={language}
messages={getMessages(language)}
>
<Outlet />
</div>
</IntlProvider>
<Footer />
</div>
</HtmlDocument>
<Header
languageSwitcher={languageSwitcherFn(setLanguage)}
networkType={networkType}
isLocal={isLocal}
isCustom={isCustom}
customHorizonAddress={horizonAddress}
customSorobanRPCAddress={sorobanRPCAddress}
/>
<SearchBox />
<div
id="main-content"
className={navigation.state === 'loading' ? 'loading' : ''}
>
<Outlet />
</div>
</IntlProvider>
<Footer />
</div>
</HtmlDocument>
</ThemeProvider>
)
}

Expand All @@ -184,12 +196,14 @@ export const ErrorBoundary: ErrorBoundaryComponent = () => {
}

return (
<HtmlDocument title="Stellar Explorer - Error">
<div className="error-container">
<h1>Error</h1>
<pre>{errorMessage}</pre>
</div>
</HtmlDocument>
<ThemeProvider>
<HtmlDocument title="Stellar Explorer - Error">
<div className="error-container">
<h1>Error</h1>
<pre>{errorMessage}</pre>
</div>
</HtmlDocument>
</ThemeProvider>
)
}

Expand Down
Loading

0 comments on commit efcbd1b

Please sign in to comment.