diff --git a/src-web/App.tsx b/src-web/App.tsx index d3f18c35..d6817500 100644 --- a/src-web/App.tsx +++ b/src-web/App.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { invoke } from '@tauri-apps/api/tauri'; import Editor from './components/Editor/Editor'; import { HStack, VStack } from './components/Stacks'; -import { DropdownMenuRadio } from './components/Dropdown'; +import { Dropdown, DropdownMenuRadio } from './components/Dropdown'; import { WindowDragRegion } from './components/WindowDragRegion'; import { IconButton } from './components/IconButton'; import { Sidebar } from './components/Sidebar'; @@ -50,25 +50,10 @@ function App() { return ( <>
- - - - - - - - + - + + + + - -
- {response?.method.toUpperCase()} -  •  - {response?.status} -  •  - {response?.elapsed}ms  •  - {response?.elapsed2}ms -
- + + {response && ( +
+ {response.method.toUpperCase()} +  •  + {response.status} +  •  + {response.elapsed}ms  •  + {response.elapsed2}ms +
+ )} + setResponse(null), + disabled: !response, + }, + { + label: 'Other Thing', + }, + ]} + > + +
- + {error &&
{error}
} {response !== null && ( <> diff --git a/src-web/components/Dropdown.tsx b/src-web/components/Dropdown.tsx index a510c4dc..75cd8059 100644 --- a/src-web/components/Dropdown.tsx +++ b/src-web/components/Dropdown.tsx @@ -56,76 +56,26 @@ export function DropdownMenuRadio({ ); } -export function Dropdown() { - const [bookmarksChecked, setBookmarksChecked] = useState(true); - const [urlsChecked, setUrlsChecked] = useState(false); - const [person, setPerson] = useState('pedro'); +export interface DropdownProps { + children: ReactNode; + items: { + label: string; + onSelect?: () => void; + disabled?: boolean; + }[]; +} +export function Dropdown({ children, items }: DropdownProps) { return ( - - - - + {children} - ⌘T}>New Tab - ⌘N}>New Window - ⇧⌘N}> - New Private Window - - - }> - More Tools - - - - ⌘S}>Save Page As… - Create Shortcut… - Name Window… - - Developer Tools - - - - - - - setBookmarksChecked(!!v)} - rightSlot={⌘B} - leftSlot={ - - - - } - > - Show Bookmarks - - setUrlsChecked(!!v)} - leftSlot={ - - - - } - > - Show Full URLs - - - - - People - - Pedro Duarte - - Colm Tuite - - + {items.map((item, i) => ( + item.onSelect?.()} disabled={item.disabled}> + {item.label} + + ))} @@ -157,7 +107,7 @@ const DropdownMenuContent = forwardRef {children} @@ -173,12 +123,14 @@ function DropdownMenuItem({ rightSlot, className, children, + disabled, ...props }: DropdownMenuItemProps) { return ( @@ -311,7 +263,7 @@ const ItemInner = forwardRef(function ItemInner( )} {...props} > -
{leftSlot}
+ {leftSlot &&
{leftSlot}
}
{children}
{rightSlot &&
{rightSlot}
}
diff --git a/src-web/components/Icon.tsx b/src-web/components/Icon.tsx index d1b1c7a4..ce2cbc1a 100644 --- a/src-web/components/Icon.tsx +++ b/src-web/components/Icon.tsx @@ -3,14 +3,25 @@ import { CameraIcon, GearIcon, HomeIcon, + MoonIcon, PaperPlaneIcon, + SunIcon, TriangleDownIcon, UpdateIcon, } from '@radix-ui/react-icons'; import classnames from 'classnames'; import { NamedExoticComponent } from 'react'; -type IconName = 'archive' | 'home' | 'camera' | 'gear' | 'triangle-down' | 'paper-plane' | 'update'; +type IconName = + | 'archive' + | 'home' + | 'camera' + | 'gear' + | 'triangle-down' + | 'paper-plane' + | 'update' + | 'sun' + | 'moon'; const icons: Record> = { 'paper-plane': PaperPlaneIcon, @@ -20,6 +31,8 @@ const icons: Record> = { gear: GearIcon, home: HomeIcon, update: UpdateIcon, + sun: SunIcon, + moon: MoonIcon, }; export interface IconProps { diff --git a/src-web/components/Input.tsx b/src-web/components/Input.tsx index 7627a39c..90b24797 100644 --- a/src-web/components/Input.tsx +++ b/src-web/components/Input.tsx @@ -47,7 +47,7 @@ export function Input({ id={id} className={classnames( className, - 'bg-transparent min-w-0 pl-3 pr-2 w-full focus:outline-none', + 'bg-transparent min-w-0 pl-3 pr-2 w-full focus:outline-none text-gray-900', leftSlot && 'pl-1', rightSlot && 'pr-1', size === 'md' && 'h-10', diff --git a/src-web/components/Sidebar.tsx b/src-web/components/Sidebar.tsx index dadb15c1..fd2a2131 100644 --- a/src-web/components/Sidebar.tsx +++ b/src-web/components/Sidebar.tsx @@ -1,32 +1,22 @@ -import { HTMLAttributes } from 'react'; +import React, { HTMLAttributes } from 'react'; import classnames from 'classnames'; +import { IconButton } from './IconButton'; +import { Button } from './Button'; +import useTheme from '../hooks/useTheme'; import { HStack } from './Stacks'; import { WindowDragRegion } from './WindowDragRegion'; -import { IconButton } from './IconButton'; -import { DropdownMenuRadio } from './Dropdown'; -import { Button } from './Button'; -type Props = HTMLAttributes; +type Props = Omit, 'children'>; export function Sidebar({ className, ...props }: Props) { + const { toggleTheme } = useTheme(); return (
- - - - - + +
    {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((i) => ( diff --git a/src-web/components/UrlBar.tsx b/src-web/components/UrlBar.tsx index 6d0cf62a..8f61d404 100644 --- a/src-web/components/UrlBar.tsx +++ b/src-web/components/UrlBar.tsx @@ -28,7 +28,7 @@ export function UrlBar({ sendRequest, onMethodChange, method, onUrlChange, url } }; return ( -
    + void } { + useEffect(() => { + if (!subscribeToChanges) return; + const unsub = subscribeToPreferredThemeChange(setTheme); + return unsub; + }, [subscribeToChanges]); + + return { + toggleTheme: toggleTheme, + }; +} diff --git a/src-web/lib/theme.ts b/src-web/lib/theme.ts new file mode 100644 index 00000000..1ae132a1 --- /dev/null +++ b/src-web/lib/theme.ts @@ -0,0 +1,22 @@ +export type Theme = 'dark' | 'light'; + +export function toggleTheme() { + const currentTheme = document.documentElement.getAttribute('data-theme') ?? getPreferredTheme(); + const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; + setTheme(newTheme); +} + +export function setTheme(theme?: Theme) { + document.documentElement.setAttribute('data-theme', theme ?? getPreferredTheme()); +} + +export function getPreferredTheme(): Theme { + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; +} + +export function subscribeToPreferredThemeChange(cb: (theme: Theme) => void): () => void { + const listener = (e: MediaQueryListEvent) => cb(e.matches ? 'dark' : 'light'); + const m = window.matchMedia('(prefers-color-scheme: dark)'); + m.addEventListener('change', listener); + return () => m.removeEventListener('change', listener); +} diff --git a/src-web/main.css b/src-web/main.css index 72446582..9f12c038 100644 --- a/src-web/main.css +++ b/src-web/main.css @@ -21,8 +21,12 @@ html, body, #root { overflow: hidden; } +* { + transition: background-color 150ms ease, border-color 150ms ease; +} + @layer base { - :root { + :root, [data-theme="light"] { /* Colors */ --color-white: 255 100% 100%; --color-background: var(--color-white); @@ -79,43 +83,41 @@ html, body, #root { --border-radius-lg: 0.5rem; } - @media (prefers-color-scheme: dark) { - :root { - --color-white: 255 100% 100%; - --color-background: 217 21% 7%; + [data-theme="dark"] { + --color-white: 255 100% 100%; + --color-background: 217 21% 7%; - --color-blue-900: 217 91% 95%; - --color-blue-800: 217 91% 88%; - --color-blue-700: 217 91% 76%; - --color-blue-600: 217 91% 70%; - --color-blue-500: 217 91% 65%; - --color-blue-400: 217 91% 58%; - --color-blue-300: 217 91% 43%; - --color-blue-200: 217 91% 30%; - --color-blue-100: 217 91% 20%; - --color-blue-50: 217 91% 10%; + --color-blue-900: 217 91% 95%; + --color-blue-800: 217 91% 88%; + --color-blue-700: 217 91% 76%; + --color-blue-600: 217 91% 70%; + --color-blue-500: 217 91% 65%; + --color-blue-400: 217 91% 58%; + --color-blue-300: 217 91% 43%; + --color-blue-200: 217 91% 30%; + --color-blue-100: 217 91% 20%; + --color-blue-50: 217 91% 10%; - --color-violet-900: 258 90% 95%; - --color-violet-800: 258 90% 88%; - --color-violet-700: 258 90% 76%; - --color-violet-600: 258 90% 70%; - --color-violet-500: 258 90% 65%; - --color-violet-400: 258 90% 58%; - --color-violet-300: 258 90% 43%; - --color-violet-200: 258 90% 30%; - --color-violet-100: 258 90% 20%; - --color-violet-50: 258 90% 10%; + --color-violet-900: 258 90% 95%; + --color-violet-800: 258 90% 88%; + --color-violet-700: 258 90% 76%; + --color-violet-600: 258 90% 70%; + --color-violet-500: 258 90% 65%; + --color-violet-400: 258 90% 58%; + --color-violet-300: 258 90% 43%; + --color-violet-200: 258 90% 30%; + --color-violet-100: 258 90% 20%; + --color-violet-50: 258 90% 10%; - --color-gray-900: 217 21% 95%; - --color-gray-800: 217 21% 88%; - --color-gray-700: 217 21% 76%; - --color-gray-600: 217 21% 70%; - --color-gray-500: 217 21% 65%; - --color-gray-400: 217 21% 58%; - --color-gray-300: 217 21% 43%; - --color-gray-200: 217 21% 30%; - --color-gray-100: 217 21% 25%; - --color-gray-50: 217 21% 15%; - } + --color-gray-900: 217 21% 95%; + --color-gray-800: 217 21% 88%; + --color-gray-700: 217 21% 76%; + --color-gray-600: 217 21% 70%; + --color-gray-500: 217 21% 65%; + --color-gray-400: 217 21% 58%; + --color-gray-300: 217 21% 43%; + --color-gray-200: 217 21% 30%; + --color-gray-100: 217 21% 25%; + --color-gray-50: 217 21% 15%; } } diff --git a/src-web/main.tsx b/src-web/main.tsx index b689b960..ed45a463 100644 --- a/src-web/main.tsx +++ b/src-web/main.tsx @@ -4,10 +4,13 @@ import App from './App'; import { HelmetProvider } from 'react-helmet-async'; import { MotionConfig } from 'framer-motion'; import init, { greet } from 'hello'; -import { invoke } from '@tauri-apps/api' +import { invoke } from '@tauri-apps/api'; +import { setTheme } from './lib/theme'; import './main.css'; +setTheme(); + await init(); greet(); await invoke('load_db');