#698 personal settings
This commit is contained in:
@@ -1,15 +1,21 @@
|
||||
import { Drawer, IconButton, Box } from '@linagora/twake-mui'
|
||||
import {
|
||||
Drawer,
|
||||
Box,
|
||||
useTheme,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
ListItemButton
|
||||
} from '@linagora/twake-mui'
|
||||
import { CalendarSidebarProps } from './SideBar'
|
||||
import { SidebarCommonContent } from './SidebarCommonContent'
|
||||
import { ViewSwitcher } from './ViewSwitcher'
|
||||
import { MainTitle } from '@/components/Menubar/MainTitle'
|
||||
import HelpOutlineIcon from '@mui/icons-material/HelpOutline'
|
||||
import { useI18n } from 'twake-i18n'
|
||||
import { AppListMenu } from '@/components/Menubar/AppListMenu'
|
||||
import { UserMenu } from '@/components/Menubar/UserMenu'
|
||||
import { useAppDispatch, useAppSelector } from '@/app/hooks'
|
||||
import { useAppDispatch } from '@/app/hooks'
|
||||
import { useUtilMenus } from '../hooks/useUtilMenus'
|
||||
import { setIsMobileSearchOpen } from '@/features/Calendars/CalendarSlice'
|
||||
import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined'
|
||||
import { UtilButtons } from './UtilButtons'
|
||||
|
||||
export const MobileSidebar: React.FC<CalendarSidebarProps> = ({
|
||||
open,
|
||||
@@ -26,25 +32,20 @@ export const MobileSidebar: React.FC<CalendarSidebarProps> = ({
|
||||
onDateChange
|
||||
}) => {
|
||||
const { t } = useI18n()
|
||||
const user = useAppSelector(state => state.user.userData)
|
||||
const theme = useTheme()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const {
|
||||
anchorEl,
|
||||
supportLink,
|
||||
userMenuAnchorEl,
|
||||
handleAppMenuOpen,
|
||||
handleAppMenuClose,
|
||||
handleUserMenuOpen,
|
||||
handleUserMenuClose,
|
||||
handleSettingsClick,
|
||||
handleLogoutClick
|
||||
} = useUtilMenus()
|
||||
const { handleSettingsClick } = useUtilMenus()
|
||||
|
||||
const openSearch = (): void => {
|
||||
dispatch(setIsMobileSearchOpen(true))
|
||||
}
|
||||
|
||||
const openSettings = (): void => {
|
||||
handleSettingsClick()
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
variant="temporary"
|
||||
@@ -77,39 +78,7 @@ export const MobileSidebar: React.FC<CalendarSidebarProps> = ({
|
||||
onDateChange={onDateChange}
|
||||
/>
|
||||
|
||||
<Box display="flex" alignItems="center">
|
||||
{supportLink && (
|
||||
<IconButton
|
||||
component="a"
|
||||
href={supportLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ marginRight: 4 }}
|
||||
aria-label={t('menubar.help')}
|
||||
title={t('menubar.help')}
|
||||
>
|
||||
<HelpOutlineIcon fontSize="small" />
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
<AppListMenu
|
||||
anchorEl={anchorEl}
|
||||
onAppMenuOpen={handleAppMenuOpen}
|
||||
onAppMenuClose={handleAppMenuClose}
|
||||
iconSize="small"
|
||||
/>
|
||||
|
||||
<UserMenu
|
||||
anchorEl={userMenuAnchorEl}
|
||||
onClose={handleUserMenuClose}
|
||||
onSettingsClick={handleSettingsClick}
|
||||
onLogoutClick={() => void handleLogoutClick()}
|
||||
onUserMenuOpen={handleUserMenuOpen}
|
||||
isIframe={isIframe}
|
||||
user={user}
|
||||
size="s"
|
||||
/>
|
||||
</Box>
|
||||
<UtilButtons isIframe={isIframe} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -127,6 +96,32 @@ export const MobileSidebar: React.FC<CalendarSidebarProps> = ({
|
||||
setSelectedCalendars={setSelectedCalendars}
|
||||
openSearchOnMobile={openSearch}
|
||||
/>
|
||||
|
||||
<Box sx={{ marginTop: 1 }}>
|
||||
<ListItemButton sx={{ padding: 0 }} onClick={openSettings}>
|
||||
<ListItemIcon sx={{ minWidth: 'unset' }}>
|
||||
<SettingsOutlinedIcon
|
||||
sx={{
|
||||
marginRight: 1,
|
||||
color: theme.palette.grey.A700,
|
||||
fontSize: 20
|
||||
}}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t('menubar.settings')}
|
||||
slotProps={{
|
||||
primary: {
|
||||
sx: {
|
||||
fontSize: '14px',
|
||||
fontWeight: 500,
|
||||
lineHeight: '20px'
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</Box>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { IconButton, Box } from '@linagora/twake-mui'
|
||||
import HelpOutlineIcon from '@mui/icons-material/HelpOutline'
|
||||
import { useI18n } from 'twake-i18n'
|
||||
import { AppListMenu } from '@/components/Menubar/AppListMenu'
|
||||
import { UserMenu } from '@/components/Menubar/UserMenu'
|
||||
import { useAppSelector } from '@/app/hooks'
|
||||
import { useUtilMenus } from '../hooks/useUtilMenus'
|
||||
|
||||
export const UtilButtons: React.FC<{ isIframe?: boolean }> = ({ isIframe }) => {
|
||||
const { t } = useI18n()
|
||||
const user = useAppSelector(state => state.user.userData)
|
||||
|
||||
const {
|
||||
anchorEl,
|
||||
supportLink,
|
||||
userMenuAnchorEl,
|
||||
handleAppMenuOpen,
|
||||
handleAppMenuClose,
|
||||
handleUserMenuOpen,
|
||||
handleUserMenuClose,
|
||||
handleSettingsClick,
|
||||
handleLogoutClick
|
||||
} = useUtilMenus()
|
||||
|
||||
return (
|
||||
<Box display="flex" alignItems="center">
|
||||
{supportLink && (
|
||||
<IconButton
|
||||
component="a"
|
||||
href={supportLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ marginRight: 4 }}
|
||||
aria-label={t('menubar.help')}
|
||||
title={t('menubar.help')}
|
||||
>
|
||||
<HelpOutlineIcon fontSize="small" />
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
<AppListMenu
|
||||
anchorEl={anchorEl}
|
||||
onAppMenuOpen={handleAppMenuOpen}
|
||||
onAppMenuClose={handleAppMenuClose}
|
||||
iconSize="small"
|
||||
/>
|
||||
|
||||
<UserMenu
|
||||
anchorEl={userMenuAnchorEl}
|
||||
onClose={handleUserMenuClose}
|
||||
onSettingsClick={handleSettingsClick}
|
||||
onLogoutClick={() => void handleLogoutClick()}
|
||||
onUserMenuOpen={handleUserMenuOpen}
|
||||
isIframe={isIframe}
|
||||
user={user}
|
||||
size="s"
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Box } from '@linagora/twake-mui'
|
||||
import { useScreenSizeDetection } from '@/useScreenSizeDetection'
|
||||
import { Box, SxProps, Theme } from '@linagora/twake-mui'
|
||||
import { useI18n } from 'twake-i18n'
|
||||
|
||||
interface WeekDaySelectorProps {
|
||||
@@ -17,27 +18,72 @@ export const FC_DAYS = [
|
||||
{ fc: 0, ics: 'SU' }
|
||||
]
|
||||
|
||||
export function WeekDaySelector({
|
||||
const WeekDayStyle = (
|
||||
isSelected: boolean,
|
||||
isMobile: boolean,
|
||||
disabled?: boolean
|
||||
): SxProps<Theme> => {
|
||||
const desktopStyle = {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: '4px',
|
||||
border: '1px solid',
|
||||
borderColor: isSelected ? 'primary.main' : '#AEAEC0',
|
||||
color: isSelected ? '#fff' : '#8C9CAF',
|
||||
fontSize: 16,
|
||||
fontWeight: 400,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
bgcolor: isSelected ? 'primary.main' : 'transparent',
|
||||
cursor: disabled ? 'default' : 'pointer',
|
||||
'@media (hover: hover)': {
|
||||
'&:hover': !disabled
|
||||
? {
|
||||
borderColor: 'primary.main',
|
||||
bgcolor: 'primary.main',
|
||||
color: '#fff'
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
} as SxProps<Theme>
|
||||
|
||||
if (isMobile) {
|
||||
return {
|
||||
...desktopStyle,
|
||||
width: 200,
|
||||
justifyContent: 'start',
|
||||
padding: 1
|
||||
}
|
||||
}
|
||||
|
||||
return desktopStyle
|
||||
}
|
||||
|
||||
export const WeekDaySelector: React.FC<WeekDaySelectorProps> = ({
|
||||
selectedDays,
|
||||
onChange,
|
||||
disabled
|
||||
}: WeekDaySelectorProps) {
|
||||
}) => {
|
||||
const { t } = useI18n()
|
||||
|
||||
const getDayLabel = (ics: string) => {
|
||||
const { isTooSmall: isMobile } = useScreenSizeDetection()
|
||||
|
||||
const getDayLabel = (ics: string, fullLabel: boolean): string => {
|
||||
const weekdayKey = fullLabel ? 'fullDays' : 'days'
|
||||
const dayMap: Record<string, string> = {
|
||||
MO: t('event.repeat.days.monday'),
|
||||
TU: t('event.repeat.days.tuesday'),
|
||||
WE: t('event.repeat.days.wednesday'),
|
||||
TH: t('event.repeat.days.thursday'),
|
||||
FR: t('event.repeat.days.friday'),
|
||||
SA: t('event.repeat.days.saturday'),
|
||||
SU: t('event.repeat.days.sunday')
|
||||
MO: t(`event.repeat.${weekdayKey}.monday`),
|
||||
TU: t(`event.repeat.${weekdayKey}.tuesday`),
|
||||
WE: t(`event.repeat.${weekdayKey}.wednesday`),
|
||||
TH: t(`event.repeat.${weekdayKey}.thursday`),
|
||||
FR: t(`event.repeat.${weekdayKey}.friday`),
|
||||
SA: t(`event.repeat.${weekdayKey}.saturday`),
|
||||
SU: t(`event.repeat.${weekdayKey}.sunday`)
|
||||
}
|
||||
return dayMap[ics] || ics
|
||||
}
|
||||
|
||||
const handleToggle = (fcDay: number) => {
|
||||
const handleToggle = (fcDay: number): void => {
|
||||
if (disabled) return
|
||||
const updated = selectedDays.includes(fcDay)
|
||||
? selectedDays.filter(d => d !== fcDay)
|
||||
@@ -46,10 +92,10 @@ export function WeekDaySelector({
|
||||
}
|
||||
|
||||
return (
|
||||
<Box display="flex" gap={1}>
|
||||
<Box display="flex" flexDirection={isMobile ? 'column' : 'row'} gap={1}>
|
||||
{FC_DAYS.map(({ fc, ics }) => {
|
||||
const isSelected = selectedDays.includes(fc)
|
||||
const fullLabel = getDayLabel(ics)
|
||||
const fullLabel = getDayLabel(ics, isMobile)
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -60,30 +106,9 @@ export function WeekDaySelector({
|
||||
aria-pressed={isSelected}
|
||||
onClick={() => handleToggle(fc)}
|
||||
disabled={disabled}
|
||||
sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: '4px',
|
||||
border: '1px solid',
|
||||
borderColor: isSelected ? 'primary.main' : '#AEAEC0',
|
||||
color: isSelected ? '#fff' : '#8C9CAF',
|
||||
fontSize: 16,
|
||||
fontWeight: 400,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
bgcolor: isSelected ? 'primary.main' : 'transparent',
|
||||
cursor: disabled ? 'default' : 'pointer',
|
||||
'&:hover': !disabled
|
||||
? {
|
||||
borderColor: 'primary.main',
|
||||
bgcolor: 'primary.main',
|
||||
color: '#fff'
|
||||
}
|
||||
: undefined
|
||||
}}
|
||||
sx={WeekDayStyle(isSelected, isMobile, disabled)}
|
||||
>
|
||||
{fullLabel.charAt(0)}
|
||||
{isMobile ? fullLabel : fullLabel.charAt(0)}
|
||||
</Box>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -10,6 +10,9 @@ import './Menubar.styl'
|
||||
import { DatePickerMobile } from './components/DatePickerMobile'
|
||||
import { Typography } from '@mui/material'
|
||||
import { SmallNavigationControls } from './components/SmallNavigationControls'
|
||||
import { useAppDispatch, useAppSelector } from '@/app/hooks'
|
||||
import { setView } from '@/features/Settings/SettingsSlice'
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
|
||||
|
||||
export type MobileMenubarProps = {
|
||||
calendarRef: React.RefObject<CalendarApi | null>
|
||||
@@ -27,6 +30,9 @@ export const MobileMenubar: React.FC<MobileMenubarProps> = ({
|
||||
onOpenSidebar
|
||||
}) => {
|
||||
const { t } = useI18n()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const view = useAppSelector(state => state.settings.view)
|
||||
|
||||
const [openDatePicker, setOpenDatePicker] = useState(false)
|
||||
|
||||
@@ -46,53 +52,81 @@ export const MobileMenubar: React.FC<MobileMenubarProps> = ({
|
||||
})
|
||||
}
|
||||
|
||||
const handleBackClick = (event: React.MouseEvent): void => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
dispatch(setView('calendar'))
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="menubar menubar--mobile">
|
||||
<div className="left-menu">
|
||||
<IconButton
|
||||
onClick={onOpenSidebar}
|
||||
aria-label={t('menubar.toggleSidebar')}
|
||||
sx={{ mr: 2 }}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<div className="menu-items">
|
||||
<SmallNavigationControls onNavigate={handleNavigation} />
|
||||
</div>
|
||||
<div className="menu-items">
|
||||
<Stack direction="row" className="current-date-time">
|
||||
<Typography
|
||||
sx={{ lineHeight: 'unset' }}
|
||||
onClick={onToggleDatePicker}
|
||||
>
|
||||
{dateLabel}
|
||||
</Typography>
|
||||
{view === 'settings' ? (
|
||||
<>
|
||||
<IconButton
|
||||
onClick={handleBackClick}
|
||||
aria-label={t('settings.back')}
|
||||
className="back-button"
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
|
||||
<Typography variant="h3" sx={{ width: '100%' }}>
|
||||
{t('menubar.settings')}
|
||||
</Typography>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="left-menu">
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={onToggleDatePicker}
|
||||
aria-label={
|
||||
openDatePicker
|
||||
? t('menubar.hideDatePicker')
|
||||
: t('menubar.showDatePicker')
|
||||
}
|
||||
title={
|
||||
openDatePicker
|
||||
? t('menubar.hideDatePicker')
|
||||
: t('menubar.showDatePicker')
|
||||
}
|
||||
aria-expanded={openDatePicker}
|
||||
onClick={onOpenSidebar}
|
||||
aria-label={t('menubar.toggleSidebar')}
|
||||
sx={{ mr: 2 }}
|
||||
>
|
||||
{openDatePicker ? <ArrowDropUpIcon /> : <ArrowDropDownIcon />}
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</div>
|
||||
</div>
|
||||
<div className="right-menu">
|
||||
<div className="search-container">
|
||||
<SearchBar />
|
||||
</div>
|
||||
</div>
|
||||
<div className="menu-items">
|
||||
<SmallNavigationControls onNavigate={handleNavigation} />
|
||||
</div>
|
||||
<div className="menu-items">
|
||||
<Stack direction="row" className="current-date-time">
|
||||
<Typography
|
||||
sx={{ lineHeight: 'unset' }}
|
||||
onClick={onToggleDatePicker}
|
||||
>
|
||||
{dateLabel}
|
||||
</Typography>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={onToggleDatePicker}
|
||||
aria-label={
|
||||
openDatePicker
|
||||
? t('menubar.hideDatePicker')
|
||||
: t('menubar.showDatePicker')
|
||||
}
|
||||
title={
|
||||
openDatePicker
|
||||
? t('menubar.hideDatePicker')
|
||||
: t('menubar.showDatePicker')
|
||||
}
|
||||
aria-expanded={openDatePicker}
|
||||
>
|
||||
{openDatePicker ? (
|
||||
<ArrowDropUpIcon />
|
||||
) : (
|
||||
<ArrowDropDownIcon />
|
||||
)}
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</div>
|
||||
</div>
|
||||
<div className="right-menu">
|
||||
<div className="search-container">
|
||||
<SearchBar />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{openDatePicker ? (
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
styled,
|
||||
ButtonBase,
|
||||
SwipeableDrawer
|
||||
} from '@linagora/twake-mui'
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'
|
||||
import React, { forwardRef, useImperativeHandle, useState } from 'react'
|
||||
|
||||
export interface MobileSelectorHandle {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const StyledSwipeableDrawer = styled(SwipeableDrawer)(({ theme }) => ({
|
||||
zIndex: theme.zIndex.modal + 100
|
||||
}))
|
||||
|
||||
const SelectorButton = styled(ButtonBase)(({ theme }) => ({
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: theme.spacing(1.5, 2),
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
color: theme.palette.text.secondary,
|
||||
textAlign: 'left',
|
||||
cursor: 'pointer',
|
||||
transition: 'border-color 0.2s',
|
||||
'&:hover': {
|
||||
borderColor: theme.palette.text.primary
|
||||
}
|
||||
}))
|
||||
|
||||
interface MobileSelectorProps {
|
||||
displayText: string
|
||||
children?: React.ReactNode
|
||||
bottomSheetRef?: React.RefObject<HTMLDivElement>
|
||||
paperRef?: React.RefObject<HTMLDivElement>
|
||||
bottomSheetChildren?: (props: {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
}) => React.ReactNode
|
||||
}
|
||||
|
||||
export const MobileSelector = forwardRef<
|
||||
MobileSelectorHandle,
|
||||
MobileSelectorProps
|
||||
>(
|
||||
(
|
||||
{ displayText, children, bottomSheetRef, paperRef, bottomSheetChildren },
|
||||
ref
|
||||
) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const onClose = (): void => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
open,
|
||||
onClose
|
||||
}))
|
||||
|
||||
return (
|
||||
<>
|
||||
<SelectorButton onClick={() => setOpen(true)}>
|
||||
<Typography variant="body1">{displayText}</Typography>
|
||||
<Box
|
||||
component={ArrowDropDownIcon}
|
||||
sx={{
|
||||
fontSize: 20,
|
||||
transition: 'transform 0.2s',
|
||||
transform: open ? 'rotate(180deg)' : 'rotate(0deg)'
|
||||
}}
|
||||
/>
|
||||
</SelectorButton>
|
||||
|
||||
{children ? (
|
||||
<StyledSwipeableDrawer
|
||||
ref={bottomSheetRef}
|
||||
anchor="bottom"
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onOpen={(): void => {}}
|
||||
disableAutoFocus
|
||||
slotProps={{
|
||||
paper: {
|
||||
ref: paperRef,
|
||||
sx: { maxHeight: '90dvh' }
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</StyledSwipeableDrawer>
|
||||
) : (
|
||||
bottomSheetChildren?.({ open, onClose })
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
MobileSelector.displayName = 'MobileSelector'
|
||||
Reference in New Issue
Block a user