UI: #334 add config page language setting (#374)

* UI: #334 add config page language setting

* temporary remove logout button

* Unify fullscreen view class for dialog expanded and settings view

- Replace dialog-expanded and settings-view with single fullscreen-view class
- Update ResponsiveDialog to use fullscreen-view class
- Update CalendarLayout to use fullscreen-view class for settings view
- Consolidate CSS rules in Menubar.styl to use fullscreen-view
- Update test cases to use new class name

---------

Co-authored-by: Lê Nhân Phụng <lenhanphung@Phung-Mac-M4.local>
This commit is contained in:
lenhanphung
2025-11-28 20:25:06 +07:00
committed by GitHub
parent 6fcf22a1cf
commit 1a85afc57d
15 changed files with 801 additions and 58 deletions
+15 -1
View File
@@ -1,4 +1,4 @@
import { useRef, useState } from "react";
import { useRef, useState, useEffect } from "react";
import { Menubar, MenubarProps } from "../Menubar/Menubar";
import CalendarApp from "./Calendar";
import { useAppDispatch } from "../../app/hooks";
@@ -51,6 +51,20 @@ export default function CalendarLayout() {
setCurrentView(view);
};
// Hide topbar navigation elements when in settings view (same as fullscreen dialog mode)
useEffect(() => {
if (view === "settings") {
document.body.classList.add("fullscreen-view");
} else {
document.body.classList.remove("fullscreen-view");
}
// Cleanup on unmount
return () => {
document.body.classList.remove("fullscreen-view");
};
}, [view]);
const menubarProps: MenubarProps = {
calendarRef,
onRefresh: handleRefresh,
+2 -2
View File
@@ -149,9 +149,9 @@ function ResponsiveDialog({
React.useEffect(() => {
if (isExpanded) {
document.body.classList.add("dialog-expanded");
document.body.classList.add("fullscreen-view");
} else {
document.body.classList.remove("dialog-expanded");
document.body.classList.remove("fullscreen-view");
}
}, [isExpanded]);
+4 -4
View File
@@ -92,8 +92,8 @@
justify-content flex-end
align-items center
body.dialog-expanded .navigation-controls,
body.dialog-expanded .current-date-time,
body.dialog-expanded .refresh-button,
body.dialog-expanded .select-display
body.fullscreen-view .navigation-controls,
body.fullscreen-view .current-date-time,
body.fullscreen-view .refresh-button,
body.fullscreen-view .select-display
display none
+81 -35
View File
@@ -4,6 +4,8 @@ import AppsIcon from "@mui/icons-material/Apps";
import RefreshIcon from "@mui/icons-material/Refresh";
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import SettingsIcon from "@mui/icons-material/Settings";
import LogoutIcon from "@mui/icons-material/Logout";
import "./Menubar.styl";
import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { stringToColor } from "../Event/utils/eventUtils";
@@ -11,17 +13,21 @@ import {
Avatar,
IconButton,
Popover,
Menu,
MenuItem,
ButtonGroup,
Button,
Select,
MenuItem,
FormControl,
Typography,
Box,
Divider,
} from "@mui/material";
import { push } from "redux-first-history";
import { CalendarApi } from "@fullcalendar/core";
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
import { setLanguage } from "../../features/Settings/SettingsSlice";
import { setView } from "../../features/Settings/SettingsSlice";
import { getUserDisplayName } from "../../utils/userUtils";
import { format } from "date-fns";
import {
enGB,
@@ -58,7 +64,9 @@ export function Menubar({
const user = useAppSelector((state) => state.user.userData);
const applist: AppIconProps[] = (window as any).appList ?? [];
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [langAnchorEl, setLangAnchorEl] = useState<null | HTMLElement>(null);
const [userMenuAnchorEl, setUserMenuAnchorEl] = useState<null | HTMLElement>(
null
);
const dispatch = useAppDispatch();
useEffect(() => {
@@ -117,24 +125,30 @@ export function Menubar({
};
const open = Boolean(anchorEl);
const langOpen = Boolean(langAnchorEl);
const userMenuOpen = Boolean(userMenuAnchorEl);
const handleLangClick = (event: React.MouseEvent<HTMLElement>) => {
setLangAnchorEl(event.currentTarget);
const handleUserMenuClick = (event: React.MouseEvent<HTMLElement>) => {
setUserMenuAnchorEl(event.currentTarget);
};
const handleUserMenuClose = () => {
setUserMenuAnchorEl(null);
};
const handleSettingsClick = () => {
dispatch(setView("settings"));
handleUserMenuClose();
};
const handleLogoutClick = () => {
// TODO: Implement logout functionality
handleUserMenuClose();
};
const handleLangClose = () => setLangAnchorEl(null);
const dateLabel = format(currentDate, "MMMM yyyy", {
locale: dateLocales[lang as keyof typeof dateLocales] || enGB,
});
const availableLangs = [
{ code: "en", label: "English" },
{ code: "fr", label: "Français" },
{ code: "ru", label: "Русский" },
{ code: "vi", label: "Tiếng Việt" },
];
return (
<>
<header className="menubar">
@@ -225,7 +239,7 @@ export function Menubar({
</div>
<div className="menu-items">
<IconButton onClick={handleLangClick}>
<IconButton onClick={handleUserMenuClick}>
<Avatar
style={{
backgroundColor: stringToColor(
@@ -267,10 +281,10 @@ export function Menubar({
</div>
</Popover>
<Popover
open={langOpen}
anchorEl={langAnchorEl}
onClose={handleLangClose}
<Menu
open={userMenuOpen}
anchorEl={userMenuAnchorEl}
onClose={handleUserMenuClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
@@ -279,25 +293,57 @@ export function Menubar({
vertical: "top",
horizontal: "right",
}}
PaperProps={{
sx: {
minWidth: 280,
mt: 1,
padding: "0 !important",
borderRadius: "16px",
},
}}
>
<FormControl size="small" style={{ minWidth: 100, marginRight: 8 }}>
<Select
value={lang}
onChange={(e) => {
dispatch(setLanguage(e.target.value));
handleLangClose();
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
padding: "24px",
}}
>
<Avatar
style={{
backgroundColor: stringToColor(
user && user.family_name
? user.family_name
: user && user.email
? user.email
: ""
),
marginBottom: "8px",
}}
variant="outlined"
aria-label={t("menubar.languageSelector")}
sizes="large"
>
{availableLangs.map(({ code, label }) => (
<MenuItem key={code} value={code}>
{label}
</MenuItem>
))}
</Select>
</FormControl>
</Popover>
{user?.name && user?.family_name
? `${user.name[0]}${user.family_name[0]}`
: (user?.email?.[0] ?? "")}
</Avatar>
<Typography variant="body1" sx={{ fontWeight: 500 }}>
{getUserDisplayName(user)}
</Typography>
<Typography variant="body2" color="text.secondary">
{user?.email}
</Typography>
</Box>
<MenuItem onClick={handleSettingsClick} sx={{ py: 1.5 }}>
<SettingsIcon sx={{ mr: 2 }} />
{t("menubar.settings") || "Settings"}
</MenuItem>
{/* <Divider />
<MenuItem onClick={handleLogoutClick} sx={{ py: 1.5 }}>
<LogoutIcon sx={{ mr: 2 }} />
{t("menubar.logout") || "Logout"}
</MenuItem> */}
</Menu>
</>
);
}