diff --git a/packages/bruno-app/src/components/StatusBar/ThemeDropdown/StyledWrapper.js b/packages/bruno-app/src/components/StatusBar/ThemeDropdown/StyledWrapper.js index 8ee95f244..b3366f445 100644 --- a/packages/bruno-app/src/components/StatusBar/ThemeDropdown/StyledWrapper.js +++ b/packages/bruno-app/src/components/StatusBar/ThemeDropdown/StyledWrapper.js @@ -2,79 +2,149 @@ import styled from 'styled-components'; import { rgba } from 'polished'; const StyledWrapper = styled.div` + /* Main container */ .theme-menu { - min-width: 160px; - padding: 4px 0; + min-width: 200px; + height: 325px; + padding: 8px; background: ${(props) => props.theme.dropdown.bg}; - border: 1px solid ${(props) => props.theme.dropdown.separator}; - border-radius: ${(props) => props.theme.border.radius.md}; - box-shadow: ${(props) => props.theme.dropdown.shadow}; + border-radius: 6px; + box-shadow: 0px 1px 4px 0px #0000000D; + outline: none; + + &.two-columns { + min-width: 400px; + } } - .menu-label { - padding: 6px 12px 4px; - font-size: 11px; - font-weight: 500; - text-transform: uppercase; + /* Mode section */ + .mode-section { + padding: 0 8px 12px 8px; + margin: 0 -8px; + border-bottom: 1px solid ${(props) => props.theme.dropdown.separator}; + } + + .mode-label { + font-size: 12px; color: ${(props) => props.theme.dropdown.mutedText}; - letter-spacing: 0.5px; + margin-bottom: 8px; } - .menu-item { + .mode-buttons { + display: flex; + gap: 10px; + } + + .mode-button { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + padding: 8px 4px; + border: 1px solid ${(props) => props.theme.dropdown.separator}; + border-radius: 4px; + background: transparent; + color: ${(props) => props.theme.dropdown.mutedText}; + cursor: pointer; + transition: all 0.15s ease; + + &:hover { + background: ${(props) => props.theme.dropdown.hoverBg}; + color: ${(props) => props.theme.dropdown.color}; + } + + &.focused { + background: ${(props) => props.theme.dropdown.hoverBg}; + color: ${(props) => props.theme.dropdown.color}; + outline: none; + } + + &.active { + background: ${(props) => rgba(props.theme.dropdown.selectedColor, 0.08)}; + border-color: ${(props) => props.theme.dropdown.selectedColor}; + color: ${(props) => props.theme.dropdown.selectedColor}; + + &.focused { + background: ${(props) => rgba(props.theme.dropdown.selectedColor, 0.15)}; + outline: none; + } + } + } + + /* Theme lists container */ + .theme-lists { + display: flex; + gap: 24px; + + &.two-columns { + gap: 0; + + .theme-list { + flex: 1; + padding: 8px 0; + + &:first-child { + padding-right: 12px; + border-right: 1px solid ${(props) => props.theme.dropdown.separator}; + } + + &:last-child { + padding-left: 12px; + } + } + } + } + + /* Individual theme list */ + .theme-list { + min-width: 180px; + padding-top: 8px; + } + + .theme-list-label { + font-size: 12px; + color: ${(props) => props.theme.dropdown.mutedText}; + margin-bottom: 8px; + } + + /* Theme item */ + .theme-item { display: flex; align-items: center; justify-content: space-between; - padding: 6px 12px; + height: 26px; + padding: 4px 8px; + border-radius: 4px; cursor: pointer; + outline: none; color: ${(props) => props.theme.dropdown.color}; font-size: ${(props) => props.theme.font.size.sm}; - &:hover { + &:hover, + &.focused { background: ${(props) => props.theme.dropdown.hoverBg}; } &.active { color: ${(props) => props.theme.dropdown.selectedColor}; - background: ${(props) => rgba(props.theme.dropdown.selectedColor, 0.07)}; - } + background: ${(props) => rgba(props.theme.dropdown.selectedColor, 0.08)}; - &.has-submenu { - padding-right: 8px; + &.focused { + background: ${(props) => rgba(props.theme.dropdown.selectedColor, 0.15)}; + } } } - .menu-item-icon { - margin-right: 8px; - opacity: 0.7; - } - - .menu-item-label { + .theme-item-label { flex: 1; + white-space: nowrap; } .check-icon { + flex-shrink: 0; + margin-left: 12px; color: ${(props) => props.theme.dropdown.selectedColor}; - margin-left: 8px; - } - - .chevron-icon { - opacity: 0.6; - margin-left: 8px; - } - - .menu-divider { - height: 1px; - background: ${(props) => props.theme.dropdown.separator}; - margin: 4px 0; - } - - .submenu { - min-width: 180px; - padding: 4px 0; - background: ${(props) => props.theme.dropdown.bg}; - border: 1px solid ${(props) => props.theme.dropdown.separator}; - border-radius: ${(props) => props.theme.border.radius.md}; - box-shadow: ${(props) => props.theme.dropdown.shadow}; } `; diff --git a/packages/bruno-app/src/components/StatusBar/ThemeDropdown/index.js b/packages/bruno-app/src/components/StatusBar/ThemeDropdown/index.js index a25c1f680..cdd59ffe6 100644 --- a/packages/bruno-app/src/components/StatusBar/ThemeDropdown/index.js +++ b/packages/bruno-app/src/components/StatusBar/ThemeDropdown/index.js @@ -1,17 +1,36 @@ -import React, { useState } from 'react'; +import React, { useState, useRef, useCallback, useEffect } from 'react'; import Tippy from '@tippyjs/react'; -import { IconChevronRight, IconCheck, IconSun, IconMoon, IconDeviceDesktop } from '@tabler/icons'; +import { IconCheck, IconSun, IconMoon, IconDeviceDesktop } from '@tabler/icons'; import ToolHint from 'components/ToolHint'; import { useTheme } from 'providers/Theme'; import { getLightThemes, getDarkThemes } from 'themes/index'; import StyledWrapper from './StyledWrapper'; +// Constants +const MODES = ['light', 'dark', 'system']; +const MODE_BUTTONS = [ + { mode: 'light', icon: IconSun, title: 'Light' }, + { mode: 'dark', icon: IconMoon, title: 'Dark' }, + { mode: 'system', icon: IconDeviceDesktop, title: 'System' } +]; + const ThemeDropdown = ({ children }) => { + // Dropdown state const [isOpen, setIsOpen] = useState(false); - const [lightSubmenuOpen, setLightSubmenuOpen] = useState(false); - const [darkSubmenuOpen, setDarkSubmenuOpen] = useState(false); const [tooltipEnabled, setTooltipEnabled] = useState(true); + // Keyboard navigation state + const [focusedSection, setFocusedSection] = useState('mode'); + const [focusedIndex, setFocusedIndex] = useState(0); + const [isKeyboardNav, setIsKeyboardNav] = useState(false); + + // Refs for focus management + const menuRef = useRef(null); + const modeButtonRefs = useRef([]); + const lightItemRefs = useRef([]); + const darkItemRefs = useRef([]); + + // Theme context const { storedTheme, setStoredTheme, @@ -21,156 +40,263 @@ const ThemeDropdown = ({ children }) => { setThemeVariantDark } = useTheme(); + // Theme data const lightThemes = getLightThemes(); const darkThemes = getDarkThemes(); + const isSystemMode = storedTheme === 'system'; - const handleModeSelect = (mode) => { - setStoredTheme(mode); + // Helper to get class names for focusable items + const getFocusedClass = (section, index) => { + return isKeyboardNav && focusedSection === section && focusedIndex === index ? 'focused' : ''; }; + // Handlers + const handleModeSelect = (mode) => setStoredTheme(mode); + const handleThemeSelect = (themeId, isLight) => { if (isLight) { setThemeVariantLight(themeId); } else { setThemeVariantDark(themeId); } - setIsOpen(false); - setLightSubmenuOpen(false); - setDarkSubmenuOpen(false); }; - const renderSubmenu = (themes, isLight, currentVariant) => ( -