diff --git a/app/SearchBox.tsx b/app/SearchBox.tsx
index 3e3362dde..5ddee2cd5 100644
--- a/app/SearchBox.tsx
+++ b/app/SearchBox.tsx
@@ -195,7 +195,8 @@ export default function SearchBox() {
>
diff --git a/app/components/LiquidityPoolTable.tsx b/app/components/LiquidityPoolTable.tsx
index baa525034..c76483973 100644
--- a/app/components/LiquidityPoolTable.tsx
+++ b/app/components/LiquidityPoolTable.tsx
@@ -111,7 +111,6 @@ export default function LiquidityPoolTable({
|
- |
diff --git a/app/components/layout/Header.tsx b/app/components/layout/Header.tsx
index 1347b1fe2..4e90ea4c6 100644
--- a/app/components/layout/Header.tsx
+++ b/app/components/layout/Header.tsx
@@ -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 {
@@ -24,6 +25,7 @@ export default function Header({
customSorobanRPCAddress,
}: Readonly) {
const { formatMessage } = useIntl()
+
return (
@@ -60,12 +62,12 @@ export default function Header({
-
@@ -99,6 +101,9 @@ export default function Header({
customSorobanRPCAddress={customSorobanRPCAddress}
/>
+
+
+
diff --git a/app/components/layout/LanguageSelector.tsx b/app/components/layout/LanguageSelector.tsx
index 17bd688b4..f05cc4d0f 100644
--- a/app/components/layout/LanguageSelector.tsx
+++ b/app/components/layout/LanguageSelector.tsx
@@ -21,7 +21,7 @@ export default function LanguageSelector({
}) {
const { formatMessage } = useIntl()
return (
-
+
{
+ const [theme, setTheme] = useTheme()
+ const toggleTheme = () => {
+ const newTheme = theme === Theme.Light ? Theme.Dark : Theme.Light
+ localStorage.setItem('theme', newTheme)
+ setTheme(newTheme)
+ }
+
+ return (
+
+ {theme === Theme.Light ? : }
+
+ )
+}
+
+const DarkIcon = () => {
+ return
+}
+
+const LightIcon = () => {
+ return
+}
+
+export default ThemeSwitcher
diff --git a/app/components/shared/ClipboardCopy.tsx b/app/components/shared/ClipboardCopy.tsx
index 77386cd47..a243cfddc 100644
--- a/app/components/shared/ClipboardCopy.tsx
+++ b/app/components/shared/ClipboardCopy.tsx
@@ -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 = Copy to Clipboard
const TooltipCopied = (
@@ -18,14 +17,13 @@ function ClipboardCopy({ text }: { text: string }) {
setCopied(true)
setTimeout(() => setCopied(false), 10000)
}
-
return (
)
diff --git a/app/components/shared/CustomNetworkButton.tsx b/app/components/shared/CustomNetworkButton.tsx
index 21b5a5e5b..c4acaa865 100644
--- a/app/components/shared/CustomNetworkButton.tsx
+++ b/app/components/shared/CustomNetworkButton.tsx
@@ -85,7 +85,7 @@ const ResourceModal = (props: ResourceModalProps) => (
onHide={props.handleCloseFn as () => void}
>
-
+
diff --git a/app/components/shared/NewWindowIcon.tsx b/app/components/shared/NewWindowIcon.tsx
index 48628ec39..f56b8054f 100644
--- a/app/components/shared/NewWindowIcon.tsx
+++ b/app/components/shared/NewWindowIcon.tsx
@@ -3,8 +3,8 @@ import externalLinkSvg from '../../../public/external-link.svg'
const NewWindowIcon = () => (
)
diff --git a/app/components/shared/__tests__/BackendResourceBadgeButton.test.tsx b/app/components/shared/__tests__/BackendResourceBadgeButton.test.tsx
index b3a04ecba..1c8e68c7e 100644
--- a/app/components/shared/__tests__/BackendResourceBadgeButton.test.tsx
+++ b/app/components/shared/__tests__/BackendResourceBadgeButton.test.tsx
@@ -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 () => {
diff --git a/app/context/theme.provider.tsx b/app/context/theme.provider.tsx
new file mode 100644
index 000000000..560400251
--- /dev/null
+++ b/app/context/theme.provider.tsx
@@ -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>,
+]
+
+const ThemeContext = createContext([undefined, () => {}])
+
+function ThemeProvider({ children }: { children: ReactNode }) {
+ const [theme, setTheme] = useState(Theme.Light)
+
+ useEffect(() => {
+ const savedTheme = localStorage.getItem('theme')
+ if (savedTheme) {
+ setTheme(savedTheme as Theme)
+ }
+ }, [])
+
+ return (
+
+ {children}
+
+ )
+}
+
+function useTheme(): ThemeContextType {
+ return useContext(ThemeContext)
+}
+
+export { Theme, ThemeProvider, useTheme }
diff --git a/app/root.tsx b/app/root.tsx
index 4f8dac285..5ad99c8f6 100644
--- a/app/root.tsx
+++ b/app/root.tsx
@@ -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'
@@ -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'
@@ -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 }] : []),
]
@@ -84,8 +87,15 @@ function HtmlDocument({
children,
title,
}: PropsWithChildren<{ title?: string }>) {
+ const [theme] = useTheme()
+ const [isLoading, setIsLoading] = useState(true)
+
+ useEffect(() => {
+ setIsLoading(false)
+ }, [])
+
return (
-
+
- {children}
+ {!isLoading && children}
@@ -136,32 +146,34 @@ function App() {
} = useLoaderData()
return (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
)
}
@@ -184,12 +196,14 @@ export const ErrorBoundary: ErrorBoundaryComponent = () => {
}
return (
-
-
-
Error
-
{errorMessage}
-
-
+
+
+
+
Error
+
{errorMessage}
+
+
+
)
}
diff --git a/app/styles/styles.css b/app/styles/styles.css
index fdf9b9376..abad9dbc6 100644
--- a/app/styles/styles.css
+++ b/app/styles/styles.css
@@ -1,4 +1,7 @@
-body,
+body {
+ background-color: #3c4452;
+}
+
.navbar {
color: #96a2b4;
background-color: #3c4452 !important;
@@ -253,12 +256,52 @@ pre#plain-text {
overflow-wrap: break-word;
}
+.theme-selector {
+ display: flex;
+ width: 38px;
+ height: 38px;
+ border-radius: 100%;
+ transition: background-color 0.3s ease;
+ align-items: center;
+ justify-content: center;
+}
+
+.theme-selector:hover {
+ background-color: lightgray;
+ cursor: pointer;
+}
+
+.clipboard-icon {
+ filter: brightness(0) saturate(100%) hue-rotate(0deg) invert(100%);
+ height: 14px;
+ width: 14px;
+ margin-left: 10px;
+}
+
+.info-icon {
+ filter: brightness(0) saturate(100%) hue-rotate(0deg) invert(100%);
+ height: 24px;
+ width: 24px;
+}
+
+.new-window-icon {
+ filter: brightness(0) saturate(100%) hue-rotate(0deg) invert(100%);
+ height: 14px;
+ width: 14px;
+ margin-left: 10px;
+}
+
+[id="contained-modal-title-lg"] {
+ color: #dce2ec;
+}
+
@media (max-width: 1300px) {
.divider-vertical {
display: none !important;
}
#language-selector,
+ .theme-selector-wrapper,
.network-selector {
margin-top: 10px;
margin-left: 15px !important;
@@ -266,6 +309,10 @@ pre#plain-text {
border-top: hsla(210, 8%, 51%, .28) solid 1px;
}
+ .theme-selector {
+ margin-left: 0px !important;
+ }
+
#language-selector {
margin-bottom: 20px;
}
@@ -293,6 +340,10 @@ pre#plain-text {
/*************************************************************/
#language-selector {
+ margin-left: 10px;
+}
+
+.theme-selector {
margin-left: 30px;
}
@@ -612,4 +663,4 @@ Mobile Rules
font-size: 12px;
padding: 4px 7px;
}
-}
\ No newline at end of file
+}
diff --git a/app/styles/styles.light.css b/app/styles/styles.light.css
new file mode 100644
index 000000000..c87efa43f
--- /dev/null
+++ b/app/styles/styles.light.css
@@ -0,0 +1,303 @@
+[data-bs-theme="light"] {
+ --light-color: #000000;
+ --light-background-color: #ffffff;
+ --light-text-color: #000000;
+ --light-table-bg: #efefef;
+ --light-body-bg: #e7e7e7;
+}
+
+[data-bs-theme="light"] body {
+ background-color: var(--light-body-bg)
+}
+
+[data-bs-theme="light"] .navbar {
+ color: var(--light-color);
+ background-color: var(--light-background-color) !important;
+ font-family: "Noto Sans",
+ "Noto Sans CJK {SC, TC}",
+ sans-serif;
+}
+
+[data-bs-theme="light"] .navbar-toggler {
+ color: transparent;
+}
+
+[data-bs-theme="light"] hr {
+ border-top: 1px solid #4c5667;
+}
+
+
+[data-bs-theme="light"] #footer {
+ border-top: 1px solid #4c5667;
+}
+
+[data-bs-theme="light"] .modal-header,
+.modal-body {
+ background-color: var(--light-table-bg);
+}
+
+[data-bs-theme="light"] .__json-pretty__ {
+ border: 1px black solid;
+ font-size: 13px;
+ background-color: var(--light-body-bg);
+ color: black;
+}
+
+[data-bs-theme="light"] .__json-string__ {
+ color: blue;
+}
+
+/* server.toml: */
+[data-bs-theme="light"] pre#plain-text {
+ background-color: #f5f5f5;
+ color: var(--light-color);
+}
+
+[data-bs-theme="light"] .brand-text {
+ color: var(--light-text-color);
+}
+
+[data-bs-theme="light"] .card {
+ background-color: var(--light-background-color);
+ color: var(--light-text-color);
+}
+
+[data-bs-theme="light"] .card-default>.card-header {
+ background-color: var(--light-background-color);
+ color: var(--light-text-color);
+}
+
+[data-bs-theme="light"] .card-header .pull-right {
+ float: right;
+ color: #fb9678 !important;
+}
+
+[data-bs-theme="light"] .card-header .pull-right:hover {
+ color: #fa6f46 !important;
+}
+
+[data-bs-theme="light"] .secondary-heading {
+ color: var(--light-text-color);
+}
+
+[data-bs-theme="light"] [id*="-table"] thead th {
+ color: #242424 !important;
+}
+
+[data-bs-theme="light"] [id*="-table"] tbody td {
+ color: var(--light-text-color) !important;
+}
+
+[data-bs-theme="light"] [id*="-table"]>tbody>tr>td {
+ border-top: 1px solid #4c5667;
+}
+
+[data-bs-theme="light"] .filter .disclaimer {
+ color: rgb(3, 169, 243);
+}
+
+[data-bs-theme="light"] .card-body td {
+ color: var(--light-text-color) !important;
+}
+
+[data-bs-theme="light"] .table>thead>tr>th,
+.table>tbody>tr:nth-of-type(odd)>td,
+.table>tbody>tr:nth-of-type(even)>td {
+ background-color: var(--light-table-bg);
+}
+
+/* For language dropdown */
+
+[data-bs-theme="light"] .nav-link {
+ color: var(--light-text-color);
+}
+
+[data-bs-theme="light"] .nav-link:hover,
+.nav-link:focus {
+ background-color: var(--light-background-color) !important;
+ color: #08b5e5 !important;
+}
+
+[data-bs-theme="light"] .divider-vertical {
+ border-right: 1px solid #96a2b4;
+}
+
+[data-bs-theme="light"] .clipboard-icon {
+ filter: brightness(0) saturate(100%) hue-rotate(0deg) invert(100%) brightness(0) contrast(100%) grayscale(100%) sepia(0%);
+}
+
+[data-bs-theme="light"] .new-window-icon {
+ filter: brightness(0) saturate(100%) hue-rotate(0deg) invert(100%) brightness(0) contrast(100%) grayscale(100%) sepia(0%);
+}
+
+[data-bs-theme="light"] .info-icon {
+ filter: brightness(0) saturate(100%) hue-rotate(0deg) invert(100%) brightness(0) contrast(100%) grayscale(100%) sepia(0%);
+}
+
+[data-bs-theme="light"] [id="contained-modal-title-lg"] {
+ color: var(--light-text-color);
+}
+
+/* Custom dropdown in lighttheme only */
+[data-bs-theme="light"] .nav-dropdown .dropdown-menu {
+ background-color: var(--light-table-bg);
+ color: var(--light-color);
+}
+
+[data-bs-theme="light"] .nav-dropdown .dropdown-menu a {
+ color: var(--light-text-color);
+}
+
+[data-bs-theme="light"] .nav-dropdown .dropdown-menu a:hover {
+ background-color: var(--light-table-bg);
+ color: #08b5e5;
+}
+
+[data-bs-theme="light"] .nav-dropdown .dropdown-menu .active {
+ background-color: var(--light-table-bg);
+ color: #08b5e5;
+}
+
+@media (max-width: 1300px) {
+ [data-bs-theme="light"] #language-selector,
+ .network-selector {
+ border-top: hsla(210, 8%, 51%, .28) solid 1px;
+ }
+
+ [data-bs-theme="light"] .dropdown-menu {
+ background-color: var(--light-background-color);
+ }
+
+ [data-bs-theme="light"] .dropdown-menu a {
+ color: #00c292 !important;
+ }
+}
+
+/*************************************************************/
+
+[data-bs-theme="light"] #language-selector>span.caret {
+ color: #dce2ec !important;
+}
+
+[data-bs-theme="light"] .network-selector button {
+ background: #08b5e5;
+ border: 1px solid #08b5e5;
+ color: white;
+}
+
+[data-bs-theme="light"] .network-selector .is-inactive {
+ background: transparent;
+ color: #08b5e5;
+}
+
+[data-bs-theme="light"] .network-selector .is-inactive:hover {
+ background: #08b5e5;
+ color: white;
+}
+
+[data-bs-theme="light"] .stellarToml a {
+ background-color: #fec107 !important;
+ color: white !important;
+}
+
+[data-bs-theme="light"] .directoryRow {
+ border-bottom: 1px solid #4c5667;
+}
+
+[data-bs-theme="light"] .directoryRow .anchorLinkCol span a:hover {
+ background-color: #fece3a !important;
+}
+
+[data-bs-theme="light"] #account-id {
+ color: white;
+}
+
+[data-bs-theme="light"] #account-nav,
+#contract-nav {
+ background-color: var(--light-table-bg);
+}
+
+[data-bs-theme="light"] #account-nav>a,
+#contract-nav>a {
+ color: var(--light-text-color);
+}
+
+[data-bs-theme="light"] .break {
+ color: var(--light-text-color) !important;
+}
+
+[data-bs-theme="light"] #account-nav>a:hover,
+#contract-nav>a:hover {
+ background-color: var(--light-background-color) !important;
+ color: #01c0c8;
+}
+
+[data-bs-theme="light"] #account-tab-content,
+#contract-tab-content {
+ background-color: var(--light-table-bg);
+}
+
+[data-bs-theme="light"] .account-tab-active,
+.contract-tab-active {
+ color: #01c0c8 !important;
+ background-color: white;
+}
+
+[data-bs-theme="light"] .account-badge a {
+ background-color: #03a9f3;
+ color: white;
+}
+
+[data-bs-theme="light"] .account-badge span a:hover {
+ color: white;
+}
+
+[data-bs-theme="light"] #paging-controls button {
+ background-color: #08b5e5 !important;
+ color: #ffffff;
+}
+
+[data-bs-theme="light"] #paging-controls button:hover {
+ background-color: #3c4452 !important;
+}
+
+[data-bs-theme="light"] a {
+ color: #00c292;
+}
+
+[data-bs-theme="light"] a:hover {
+ color: #008f6c;
+}
+
+[data-bs-theme="light"] .col-md-1 a span {
+ color: white !important;
+ background-color: #01c0c8 !important;
+}
+
+[data-bs-theme="light"] .col-md-1 a span:hover {
+ background-color: #01a8af !important;
+}
+
+[data-bs-theme="light"] .green-blue-button {
+ color: white !important;
+ background-color: #01c0c8 !important;
+}
+
+[data-bs-theme="light"] .green-blue-button:hover {
+ background-color: #01a8af !important;
+}
+
+[data-bs-theme="light"] .backend-resource-badge-button {
+ color: white;
+ background-color: #01c0c8;
+}
+
+/*
+Mobile Rules
+*/
+
+@media only screen and (max-width: 600px) {
+ [data-bs-theme="light"] #search-container .input-group-text {
+ background-color: #01c0c8;
+ }
+}
diff --git a/public/clipboard.svg b/public/clipboard.svg
index 1785ebd38..dcf784856 100644
--- a/public/clipboard.svg
+++ b/public/clipboard.svg
@@ -1 +1 @@
-
+
diff --git a/public/img/moon.svg b/public/img/moon.svg
new file mode 100644
index 000000000..f1a84ab6f
--- /dev/null
+++ b/public/img/moon.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/img/sun.svg b/public/img/sun.svg
new file mode 100644
index 000000000..e870c1e89
--- /dev/null
+++ b/public/img/sun.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/info-circle.svg b/public/info-circle.svg
index aa455aadc..8aa81232f 100644
--- a/public/info-circle.svg
+++ b/public/info-circle.svg
@@ -1 +1 @@
-
\ No newline at end of file
+