-
Notifications
You must be signed in to change notification settings - Fork 3.7k
feat(web): add theme system with light/dark/night modes and compact u… #432
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| # Changelog | ||
|
|
||
| ## [Unreleased] | ||
|
|
||
| ### Added | ||
| - **Theme selector**: Added theme toggle button in sidebar with three theme options: | ||
| - **Light**: Light mode | ||
| - **Dark**: Balanced dark mode with brighter backgrounds and more visible UI elements | ||
| - **Night**: Darkest mode (previously the only dark mode) | ||
| - **System**: Follow system preference | ||
|
|
||
| ### Changed | ||
| - **apps/web/src/hooks/useTheme.ts**: Extended theme system to support `light`, `dark`, `night`, and `system` themes. Added `THEME_OPTIONS` export and `Theme` type export. | ||
| - **apps/web/src/index.css**: Added CSS variables for the new "dark" middle-ground theme and renamed existing dark theme to "night". The dark theme now uses brighter backgrounds (neutral-800/900 vs neutral-950) and more visible borders/accents. | ||
| - **apps/web/src/components/Sidebar.tsx**: Added theme selector menu with icon (Sun/Moon/MoonStar) in the sidebar header, available in both desktop and web modes. | ||
| - **apps/web/src/components/ChatView.tsx**: Made user messages more compact: | ||
| - Reduced padding (px-3 py-2 instead of px-4 py-3) | ||
| - Smaller text (13px instead of 14px) | ||
| - Action buttons (copy, rollback) and timestamp moved below the message card | ||
| - Actions only appear on hover for cleaner UI |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,8 +2,11 @@ import { | |
| ChevronRightIcon, | ||
| FolderIcon, | ||
| GitPullRequestIcon, | ||
| MoonIcon, | ||
| MoonStarIcon, | ||
| RocketIcon, | ||
| SquarePenIcon, | ||
| SunIcon, | ||
| TerminalIcon, | ||
| } from "lucide-react"; | ||
| import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | ||
|
|
@@ -60,6 +63,14 @@ import { | |
| } from "./ui/sidebar"; | ||
| import { formatWorktreePathForDisplay, getOrphanedWorktreePathForThread } from "../worktreeCleanup"; | ||
| import { isNonEmpty as isNonEmptyString } from "effect/String"; | ||
| import { useTheme, THEME_OPTIONS, type Theme } from "../hooks/useTheme"; | ||
| import { | ||
| Menu, | ||
| MenuPopup, | ||
| MenuRadioGroup, | ||
| MenuRadioItem, | ||
| MenuTrigger, | ||
| } from "./ui/menu"; | ||
|
|
||
| const EMPTY_KEYBINDINGS: ResolvedKeybindingsConfig = []; | ||
| const THREAD_PREVIEW_LIMIT = 6; | ||
|
|
@@ -257,7 +268,14 @@ function ProjectFavicon({ cwd }: { cwd: string }) { | |
| ); | ||
| } | ||
|
|
||
| function ThemeIcon({ theme }: { theme: Theme }) { | ||
| if (theme === "light") return <SunIcon className="size-3.5" />; | ||
| if (theme === "night") return <MoonStarIcon className="size-3.5" />; | ||
| return <MoonIcon className="size-3.5" />; | ||
| } | ||
|
|
||
| export default function Sidebar() { | ||
| const { theme, setTheme } = useTheme(); | ||
| const projects = useStore((store) => store.projects); | ||
| const threads = useStore((store) => store.threads); | ||
| const markThreadUnread = useStore((store) => store.markThreadUnread); | ||
|
|
@@ -990,36 +1008,79 @@ export default function Sidebar() { | |
| </div> | ||
| ); | ||
|
|
||
| const themeSelector = ( | ||
| <Menu> | ||
| <Tooltip> | ||
| <TooltipTrigger | ||
| render={ | ||
| <MenuTrigger | ||
| render={ | ||
| <button | ||
| type="button" | ||
| aria-label="Change theme" | ||
| className="inline-flex size-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground" | ||
| > | ||
| <ThemeIcon theme={theme} /> | ||
| </button> | ||
| } | ||
| /> | ||
| } | ||
| /> | ||
| <TooltipPopup side="bottom">Theme</TooltipPopup> | ||
| </Tooltip> | ||
| <MenuPopup side="bottom" align="end" className="min-w-[140px]"> | ||
| <MenuRadioGroup | ||
| value={theme} | ||
| onValueChange={(value) => setTheme(value as Theme)} | ||
| > | ||
| {THEME_OPTIONS.map((option) => ( | ||
| <MenuRadioItem key={option.value} value={option.value}> | ||
| {option.label} | ||
|
Comment on lines
+1036
to
+1038
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Adding Useful? React with 👍 / 👎. |
||
| </MenuRadioItem> | ||
| ))} | ||
| </MenuRadioGroup> | ||
| </MenuPopup> | ||
| </Menu> | ||
| ); | ||
|
|
||
| return ( | ||
| <> | ||
| {isElectron ? ( | ||
| <> | ||
| <SidebarHeader className="drag-region h-[52px] flex-row items-center gap-2 px-4 py-0 pl-[82px]"> | ||
| <SidebarHeader className="drag-region h-[52px] flex-row items-center gap-1 px-4 py-0 pl-[82px]"> | ||
| {wordmark} | ||
| {showDesktopUpdateButton && ( | ||
| <Tooltip> | ||
| <TooltipTrigger | ||
| render={ | ||
| <button | ||
| type="button" | ||
| aria-label={desktopUpdateTooltip} | ||
| aria-disabled={desktopUpdateButtonDisabled || undefined} | ||
| disabled={desktopUpdateButtonDisabled} | ||
| className={`inline-flex size-7 ml-auto mt-2 items-center justify-center rounded-md text-muted-foreground transition-colors ${desktopUpdateButtonInteractivityClasses} ${desktopUpdateButtonClasses}`} | ||
| onClick={handleDesktopUpdateButtonClick} | ||
| > | ||
| <RocketIcon className="size-3.5" /> | ||
| </button> | ||
| } | ||
| /> | ||
| <TooltipPopup side="bottom">{desktopUpdateTooltip}</TooltipPopup> | ||
| </Tooltip> | ||
| )} | ||
| <div className="ml-auto mt-2 flex items-center gap-0.5"> | ||
| {themeSelector} | ||
| {showDesktopUpdateButton && ( | ||
| <Tooltip> | ||
| <TooltipTrigger | ||
| render={ | ||
| <button | ||
| type="button" | ||
| aria-label={desktopUpdateTooltip} | ||
| aria-disabled={desktopUpdateButtonDisabled || undefined} | ||
| disabled={desktopUpdateButtonDisabled} | ||
| className={`inline-flex size-7 items-center justify-center rounded-md text-muted-foreground transition-colors ${desktopUpdateButtonInteractivityClasses} ${desktopUpdateButtonClasses}`} | ||
| onClick={handleDesktopUpdateButtonClick} | ||
| > | ||
| <RocketIcon className="size-3.5" /> | ||
| </button> | ||
| } | ||
| /> | ||
| <TooltipPopup side="bottom">{desktopUpdateTooltip}</TooltipPopup> | ||
| </Tooltip> | ||
| )} | ||
| </div> | ||
| </SidebarHeader> | ||
| </> | ||
| ) : ( | ||
| <SidebarHeader className="gap-3 px-3 py-2 sm:gap-2.5 sm:px-4 sm:py-3"> | ||
| {wordmark} | ||
| <div className="flex items-center gap-2"> | ||
| {wordmark} | ||
| <div className="ml-auto"> | ||
| {themeSelector} | ||
| </div> | ||
| </div> | ||
| </SidebarHeader> | ||
| )} | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The user-message action row is now hidden with
opacity-0and only revealed viagroup-hover/focus-within, which breaks discoverability and access on touch/non-hover environments; in particular, rows without focusable actions (for example image-only messages without revert) have no way to triggerfocus-within, so their timestamp remains permanently hidden unless hover exists. This regresses core message controls and metadata visibility on mobile.Useful? React with 👍 / 👎.