-
+
+
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
+
+ {/* Tinted overlay that appears when dragging and dropping an item */}
+ { canDrop && isOver &&
}
+
+
+
+
+ { (ipfsReady || url === '/welcome' || url.startsWith('/settings'))
+ ?
+ :
+ }
+
-
-
- { (ipfsReady || url === '/welcome' || url.startsWith('/settings'))
- ?
- :
- }
-
-
-
-
+
+
-
-
-
-
-
+
)
}
diff --git a/src/components/theme-toggle/theme-toggle.css b/src/components/theme-toggle/theme-toggle.css
new file mode 100644
index 000000000..9f723f592
--- /dev/null
+++ b/src/components/theme-toggle/theme-toggle.css
@@ -0,0 +1,48 @@
+.theme-toggle {
+ --size: 2rem;
+ --icon-fill: hsl(210, 22%, 22%);
+ --icon-fill-hover: hsl(210, 22%, 12%);
+
+ background: none;
+ border: none;
+ padding: 0;
+ inline-size: var(--size);
+ block-size: var(--size);
+ aspect-ratio: 1;
+ border-radius: 50%;
+ cursor: pointer;
+ touch-action: manipulation;
+ -webkit-tap-highlight-color: transparent;
+ outline-offset: 5px;
+}
+
+.theme-toggle > svg {
+ inline-size: 100%;
+ block-size: 100%;
+ stroke-linecap: round;
+}
+
+[data-theme='dark'] .theme-toggle {
+ --icon-fill: hsl(25, 100%, 50%);
+ --icon-fill-hover: hsl(25, 100%, 40%);
+}
+
+.theme-toggle:hover,
+.theme-toggle:focus-visible {
+ background: hsl(0 0% 50% / 0.1);
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ .theme-toggle {
+ transition: background-color 0.3s ease;
+ }
+
+ .theme-toggle > svg {
+ transition: transform 0.5s ease;
+ }
+
+ .theme-toggle:hover > svg,
+ .theme-toggle:focus-visible > svg {
+ transform: scale(1.1);
+ }
+}
diff --git a/src/components/theme-toggle/toggle.tsx b/src/components/theme-toggle/toggle.tsx
new file mode 100644
index 000000000..d65bbe52b
--- /dev/null
+++ b/src/components/theme-toggle/toggle.tsx
@@ -0,0 +1,50 @@
+import React from 'react'
+import './theme-toggle.css'
+import { useTheme } from '../../hooks/theme'
+
+export const ThemeToggle = () => {
+ const { currentTheme: isDarkTheme, toggleTheme, toggleThemeWithKey } = useTheme()
+ return (
+
+ )
+}
diff --git a/src/context/theme-provider.tsx b/src/context/theme-provider.tsx
new file mode 100644
index 000000000..2023a5b5d
--- /dev/null
+++ b/src/context/theme-provider.tsx
@@ -0,0 +1,49 @@
+import React from 'react'
+
+export interface ThemeProviderProps {
+ children: React.ReactNode
+}
+
+export type Theme = 'light' | 'dark'
+
+export type ThemeContextValues = {
+ currentTheme: Theme,
+ toggleTheme: () => void;
+ toggleThemeWithKey: (event: React.KeyboardEvent
) => void;
+}
+
+const createThemeContext = () => React.createContext(null)
+export const ThemeContext = createThemeContext()
+
+export const ThemeProvider = ({ children }: ThemeProviderProps) => {
+ const [theme, setTheme] = React.useState(() => {
+ const savedTheme =
+ typeof window !== 'undefined' && localStorage.getItem('theme')
+ if (savedTheme) return savedTheme === 'dark'
+ return window.matchMedia('prefers-color-scheme: dark').matches
+ })
+ React.useEffect(() => {
+ const htmlElem = document.documentElement
+ const currentTheme = theme ? 'dark' : 'light'
+ htmlElem.setAttribute('data-theme', currentTheme)
+ localStorage.setItem('theme', currentTheme)
+ htmlElem.setAttribute('aria-label', `Current theme: ${currentTheme}`)
+ }, [theme])
+ const toggleTheme = () => setTheme((prevTheme) => !prevTheme)
+ const handleKeyDown = (event: React.KeyboardEvent) => {
+ if (event.key === 'Enter' || event.key === ' ') {
+ event.preventDefault()
+ toggleTheme()
+ }
+ }
+ const values: ThemeContextValues = {
+ currentTheme: theme as unknown as Theme,
+ toggleTheme,
+ toggleThemeWithKey: handleKeyDown
+ }
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/hooks/theme.ts b/src/hooks/theme.ts
new file mode 100644
index 000000000..1b1c21bac
--- /dev/null
+++ b/src/hooks/theme.ts
@@ -0,0 +1,10 @@
+import React from 'react'
+import { ThemeContext, ThemeContextValues } from '../context/theme-provider'
+
+export const useTheme = () => {
+ const context = React.useContext(ThemeContext)
+ if(context === null) {
+ throw new Error('Theme context is missing You probably forgot to wrap the component depending on theme in ')
+ }
+ return context as ThemeContextValues
+}
diff --git a/src/index.css b/src/index.css
index 87793d8c3..9d4c94c82 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,4 +1,4 @@
-@import './reset.css';
+@import "./reset.css";
@import "../node_modules/tachyons";
/* They forgot to include word break: https://github.com/tachyons-css/tachyons/issues/563 */
@import "../node_modules/tachyons/src/_word-break.css";
@@ -8,15 +8,59 @@ body {
overflow-y: scroll;
}
-.placeholder-light::placeholder{
- color: #CAD3D8;
+[data-theme="dark"] {
+ --background: #181a1b;
+ --text: #e8e6e3;
+ --element-bg: #1a1c1e;
+ --navy-dark: #0b3a53;
+ --navy-text-color: rgb(202, 198, 191);
+ --snow-muted: rgb(28, 30, 31);
+
+ body {
+ transition: background .3s ease-in;
+ }
+
+ /* from inspecting the DOM, most of the classes here are from tachyon and ipfs-css
+ * so when the data-theme attr is set to dark, we'll have them updated
+ */
+ .bg-snow-muted {
+ background: var(--snow-muted);
+ }
+
+ .charcoal {
+ color: rgb(196, 191, 183);
+ }
+
+ section {
+ background: var(--element-bg) !important;
+ }
+
+ .navy {
+ color: var(--navy-text-color);
+ }
+
+ .joyride-app-explore < div {
+ border: 1px solid red;
+ }
+}
+
+html #root {
+ background-color: var(--background);
+ color: var(--text);
+ transition: background-color 0.3s ease, color 0.3s ease;
+}
+
+.placeholder-light::placeholder {
+ color: #cad3d8;
}
.bg-near-white {
background-color: #fbfbfb;
}
-html, body, #root {
+html,
+body,
+#root {
min-height: 100%;
}
@@ -26,7 +70,7 @@ html, body, #root {
@media only screen and (min-width: 60em) {
.appOverlay {
- width: calc(100% - 148px)
+ width: calc(100% - 148px);
}
}
@@ -36,5 +80,5 @@ html, body, #root {
.react-joyride__tooltip button {
font-size: 14px !important;
- font-family: 'Montserrat';
+ font-family: "Montserrat";
}
diff --git a/src/navigation/NavBar.js b/src/navigation/NavBar.js
index 9925dd224..894537778 100644
--- a/src/navigation/NavBar.js
+++ b/src/navigation/NavBar.js
@@ -12,6 +12,7 @@ import StrokeIpld from '../icons/StrokeIpld.js'
// Styles
import './NavBar.css'
+import { ThemeToggle } from '../components/theme-toggle/toggle'
const NavLink = ({
to,
@@ -73,6 +74,9 @@ export const NavBar = ({ t }) => {