diff --git a/web/src/components/ChatboxHeader/index.jsx b/web/src/components/ChatboxHeader/index.jsx index 7947b64c..f70ba88c 100644 --- a/web/src/components/ChatboxHeader/index.jsx +++ b/web/src/components/ChatboxHeader/index.jsx @@ -1,112 +1,15 @@ import styles from "./index.module.css"; -import { useContext } from "react"; -import PropTypes from "prop-types"; - -import Avatar from "@mui/material/Avatar"; - -import BrightnessMediumIcon from "@mui/icons-material/BrightnessMedium"; -import LightModeIcon from "@mui/icons-material/LightMode"; -import DarkModeIcon from "@mui/icons-material/DarkMode"; - -import { Dropdown, DropdownButton, DropdownMenu } from "@/components/DropdownMenu"; -import { ThemeContext } from "@/contexts/theme"; -import { UserContext } from "@/contexts/user"; - - -const ThemeIcon = ({ theme }) => { - switch (theme) { - case "light": - return ; - case "dark": - return ; - default: - return ; - } -} -ThemeIcon.propTypes = { - theme: PropTypes.string, -}; +import ThemeSelector from "@/components/ThemeSelector"; +import UserMenu from "@/components/UserMenu"; const ChatboxHeader = () => { - const { theme, setTheme } = useContext(ThemeContext); - const { username, avatar } = useContext(UserContext); - - const onThemeClick = (theme) => { - setTheme(theme); - } - - const handleLogout = async (e) => { - e.preventDefault(); - // See - // This is a bit hard-coded? - window.location.href = "/oauth2/sign_out"; - }; - return (
- - - - Theme - - -
  • - -
  • -
  • - -
  • -
  • - -
  • -
    -
    - - - - - -
  • {username}
  • -
    -
  • - -
  • -
    -
    + +
    ); diff --git a/web/src/components/ChatboxHeader/index.module.css b/web/src/components/ChatboxHeader/index.module.css index da337cd5..aa76c8d7 100644 --- a/web/src/components/ChatboxHeader/index.module.css +++ b/web/src/components/ChatboxHeader/index.module.css @@ -17,70 +17,3 @@ margin-left: auto; margin-right: 2vw; } - -.themeMenuTitle { - background-color: transparent; - color: var(--text-primary); - display: flex; - align-items: center; - justify-content: center; - border: none; -} - -.themeMenuTitle:hover { - background-color: var(--bg-secondary); - border-radius: 5px; -} - -.themeMenuText { - margin: 0.3em; - text-align: start; - font-size: 0.8rem; -} - -.themeMenuList { - background-color: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: 3px; - padding: 0.5rem; -} - -.themeMenuItem { - width: 100%; - margin: 2px; - background-color: transparent; - /* must add transparent border or the button will shake on hover */ - border: 1px solid transparent; - color: var(--text-primary); - display: flex; - align-items: center; -} - -.themeMenuItem:hover { - border: 1px solid var(--border-color); - border-radius: 3px; -} - -.themeMenuItem.selected { - border: 1px solid var(--border-color); - border-radius: 3px; -} - -.userInfoMenu { - background-color: transparent; - border: none; -} - -.userInfoMenuList { - background-color: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: 3px; - position: absolute; - right: 2vw; - z-index: 1; - padding: 0.5rem; -} - -.userInfoMenuUsernameHr { - border: 1px solid var(--border-color); -} diff --git a/web/src/components/ThemeSelector/index.jsx b/web/src/components/ThemeSelector/index.jsx new file mode 100644 index 00000000..85a05d12 --- /dev/null +++ b/web/src/components/ThemeSelector/index.jsx @@ -0,0 +1,74 @@ +import styles from "./index.module.css"; + +import { useContext } from "react"; +import PropTypes from "prop-types"; + +import BrightnessMediumIcon from "@mui/icons-material/BrightnessMedium"; +import LightModeIcon from "@mui/icons-material/LightMode"; +import DarkModeIcon from "@mui/icons-material/DarkMode"; + +import { Dropdown, DropdownButton, DropdownMenu } from "@/components/DropdownMenu"; +import { ThemeContext } from "@/contexts/theme"; + + +const ThemeIcon = ({ theme }) => { + switch (theme) { + case "light": + return ; + case "dark": + return ; + default: + return ; + } +}; +ThemeIcon.propTypes = { + theme: PropTypes.string, +}; + + +const ThemeSelector = () => { + const { theme, setTheme } = useContext(ThemeContext); + + return ( + + + + Theme + + +
  • + +
  • +
  • + +
  • +
  • + +
  • +
    +
    + ); +}; + +export default ThemeSelector; diff --git a/web/src/components/ThemeSelector/index.module.css b/web/src/components/ThemeSelector/index.module.css new file mode 100644 index 00000000..3440cad7 --- /dev/null +++ b/web/src/components/ThemeSelector/index.module.css @@ -0,0 +1,47 @@ +.themeMenuTitle { + background-color: transparent; + color: var(--text-primary); + display: flex; + align-items: center; + justify-content: center; + border: none; +} + +.themeMenuTitle:hover { + background-color: var(--bg-secondary); + border-radius: 5px; +} + +.themeMenuText { + margin: 0.3em; + text-align: start; + font-size: 0.8rem; +} + +.themeMenuList { + background-color: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 3px; + padding: 0.5rem; +} + +.themeMenuItem { + width: 100%; + margin: 2px; + background-color: transparent; + /* must add transparent border or the button will shake on hover */ + border: 1px solid transparent; + color: var(--text-primary); + display: flex; + align-items: center; +} + +.themeMenuItem:hover { + border: 1px solid var(--border-color); + border-radius: 3px; +} + +.themeMenuItem.selected { + border: 1px solid var(--border-color); + border-radius: 3px; +} diff --git a/web/src/components/ChatboxHeader/index.test.jsx b/web/src/components/ThemeSelector/index.test.jsx similarity index 51% rename from web/src/components/ChatboxHeader/index.test.jsx rename to web/src/components/ThemeSelector/index.test.jsx index 6e8d0403..5b035f26 100644 --- a/web/src/components/ChatboxHeader/index.test.jsx +++ b/web/src/components/ThemeSelector/index.test.jsx @@ -2,15 +2,12 @@ import { render, screen, fireEvent } from "@testing-library/react"; import { describe, it, expect, vi } from "vitest"; import { ThemeContext } from "@/contexts/theme"; -import { UserContext } from "@/contexts/user"; -import ChatboxHeader from "./index"; +import ThemeSelector from "./index"; const setup = () => render( - - - + ); @@ -20,18 +17,8 @@ const mockThemeContextValue = { setTheme: mockSetTheme, }; -const mockUserContextValue = { - username: "testuser", - avatar: "testavatar.png", -}; describe("ChatboxHeader", () => { - it("renders the username and avatar", () => { - setup(); - expect(screen.getByAltText("testuser's avatar")).toBeDefined(); - expect(screen.getByText("testuser")).toBeDefined(); - }); - it("renders the theme menu and allows theme change", () => { setup(); fireEvent.click(screen.getByText("Theme")); @@ -39,12 +26,4 @@ describe("ChatboxHeader", () => { fireEvent.click(screen.getByLabelText("Set theme to dark mode")); expect(mockSetTheme).toHaveBeenCalledWith("dark"); }); - - it("handles logout", () => { - setup(); - delete window.location; - window.location = { href: "" }; - fireEvent.click(screen.getByText("Logout")); - expect(window.location.href).toBe("/oauth2/sign_out"); - }); }); diff --git a/web/src/components/UserMenu/index.jsx b/web/src/components/UserMenu/index.jsx new file mode 100644 index 00000000..bc0e0d7f --- /dev/null +++ b/web/src/components/UserMenu/index.jsx @@ -0,0 +1,51 @@ +import styles from "./index.module.css"; + +import { useContext } from "react"; + +import Avatar from "@mui/material/Avatar"; + +import { Dropdown, DropdownButton, DropdownMenu } from "@/components/DropdownMenu"; +import { UserContext } from "@/contexts/user"; + + +const UserMenu = () => { + const { username, avatar } = useContext(UserContext); + + const handleLogout = async (e) => { + e.preventDefault(); + // See + // This is a bit hard-coded? + window.location.href = "/oauth2/sign_out"; + }; + + return ( + + + + + +
  • {username}
  • +
    +
  • + +
  • +
    +
    + ); +}; + +export default UserMenu; diff --git a/web/src/components/UserMenu/index.module.css b/web/src/components/UserMenu/index.module.css new file mode 100644 index 00000000..0fd8a49f --- /dev/null +++ b/web/src/components/UserMenu/index.module.css @@ -0,0 +1,19 @@ + +.userInfoMenu { + background-color: transparent; + border: none; +} + +.userInfoMenuList { + background-color: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 3px; + position: absolute; + right: 2vw; + z-index: 1; + padding: 0.5rem; +} + +.userInfoMenuUsernameHr { + border: 1px solid var(--border-color); +} diff --git a/web/src/components/UserMenu/index.test.jsx b/web/src/components/UserMenu/index.test.jsx new file mode 100644 index 00000000..0f3344e0 --- /dev/null +++ b/web/src/components/UserMenu/index.test.jsx @@ -0,0 +1,33 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect } from "vitest"; + +import { UserContext } from "@/contexts/user"; + +import UserMenu from "./index"; + +const setup = () => render( + + + +); + +const mockUserContextValue = { + username: "testuser", + avatar: "testavatar.png", +}; + +describe("ChatboxHeader", () => { + it("renders the username and avatar", () => { + setup(); + expect(screen.getByAltText("testuser's avatar")).toBeDefined(); + expect(screen.getByText("testuser")).toBeDefined(); + }); + + it("handles logout", () => { + setup(); + delete window.location; + window.location = { href: "" }; + fireEvent.click(screen.getByText("Logout")); + expect(window.location.href).toBe("/oauth2/sign_out"); + }); +});