[#682] responsiveness for event preview (#736)

Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
Camille Moussu
2026-04-09 02:35:38 +02:00
committed by GitHub
parent 509b467024
commit 91f0a5526f
9 changed files with 340 additions and 192 deletions
+41 -31
View File
@@ -10,7 +10,9 @@ import {
IconButton,
Stack,
SxProps,
Theme
Theme,
useMediaQuery,
useTheme
} from '@linagora/twake-mui'
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import CloseIcon from '@mui/icons-material/Close'
@@ -110,35 +112,42 @@ function ResponsiveDialog({
actionsJustifyContent = 'flex-end',
sx,
...otherDialogProps
}: ResponsiveDialogProps) {
}: ResponsiveDialogProps): JSX.Element {
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
const isInIframe = useMemo(() => new CozyBridge().isInIframe(), [])
const baseSx: SxProps<Theme> = {
'& .MuiBackdrop-root': {
backgroundColor: 'rgba(0, 0, 0, 0.1)',
opacity: isExpanded ? '0 !important' : undefined,
transition: isExpanded ? 'none !important' : undefined,
pointerEvents: isExpanded ? 'none' : undefined
},
'& .MuiDialog-paper': {
maxWidth: isExpanded ? '100%' : normalMaxWidth,
width: '100%',
height: isExpanded
? `calc(100vh - ${isInIframe ? '0px' : headerHeight})`
: undefined,
maxHeight: isExpanded && isInIframe ? '100%' : undefined,
margin: isExpanded ? `${isInIframe ? 0 : headerHeight} 0 0 0` : '32px',
boxShadow: isExpanded ? 'none !important' : undefined,
transition: isExpanded ? 'none !important' : undefined,
zIndex: isExpanded ? 1200 : 1300
},
'& .MuiDialogActions-root .MuiBox-root': {
maxWidth: isExpanded ? expandedContentMaxWidth : undefined,
margin: isExpanded ? '0 auto' : undefined,
padding: '0',
width: isExpanded ? '100%' : undefined,
justifyContent: isExpanded ? 'flex-end' : undefined
}
}
const baseSx: SxProps<Theme> = isMobile
? undefined
: {
'& .MuiBackdrop-root': {
opacity: isExpanded ? '0 !important' : undefined,
transition: isExpanded ? 'none !important' : undefined,
pointerEvents: isExpanded ? 'none' : undefined
},
'& .MuiDialog-paper': {
maxWidth: isExpanded ? '100%' : normalMaxWidth,
width: '100%',
height: isExpanded
? `calc(100vh - ${isInIframe ? '0px' : headerHeight})`
: undefined,
maxHeight: isExpanded && isInIframe ? '100%' : undefined,
margin: isExpanded
? `${isInIframe ? 0 : headerHeight} 0 0 0`
: '32px',
boxShadow: isExpanded ? 'none !important' : undefined,
transition: isExpanded ? 'none !important' : undefined,
zIndex: isExpanded ? 1200 : 1300
},
'& .MuiDialogActions-root .MuiBox-root': {
maxWidth: isExpanded ? expandedContentMaxWidth : undefined,
margin: isExpanded ? '0 auto' : undefined,
padding: '0',
width: isExpanded ? '100%' : undefined,
justifyContent: isExpanded ? 'flex-end' : undefined
}
}
const baseContentSx: SxProps<Theme> = {
width: '100%'
@@ -161,7 +170,7 @@ function ResponsiveDialog({
const handleClose = (
event: unknown,
reason: 'backdropClick' | 'escapeKeyDown'
) => {
): void => {
if (isExpanded && reason === 'backdropClick') {
return
}
@@ -175,6 +184,7 @@ function ResponsiveDialog({
open={open}
onClose={handleClose}
maxWidth={false}
fullScreen={isMobile && open}
fullWidth
transitionDuration={isExpanded ? 0 : 300}
sx={[baseSx, ...(Array.isArray(sx) ? sx : [sx])]}
@@ -238,7 +248,7 @@ function ResponsiveDialog({
<DialogActions
sx={{
borderTop: actionsBorderTop
? theme => `1px solid ${theme.palette.divider}`
? (theme: Theme): string => `1px solid ${theme.palette.divider}`
: undefined,
justifyContent: actionsJustifyContent
}}
+15 -7
View File
@@ -1,4 +1,10 @@
import { Box, Link, Typography } from '@linagora/twake-mui'
import {
Box,
Link,
Typography,
useTheme,
useMediaQuery
} from '@linagora/twake-mui'
import React from 'react'
type InfoRowProps = {
@@ -12,14 +18,14 @@ type InfoRowProps = {
flexWrap?: React.CSSProperties['flexWrap']
}
function detectUrls(text: string) {
function detectUrls(text: string): JSX.Element[] {
// Simple regex that captures whole URLs without splitting them apart
const urlRegex = /(https?:\/\/[^\s]+|www\.[^\s]+)/gi
const parts = []
let lastIndex = 0
text.replace(urlRegex, (match, _, offset) => {
text.replace(urlRegex, (match, _, offset: number) => {
// Push the text before the match
if (lastIndex < offset) {
parts.push(
@@ -66,10 +72,12 @@ export function InfoRow({
style,
alignItems = 'center',
flexWrap = 'nowrap'
}: InfoRowProps) {
}: InfoRowProps): JSX.Element {
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
return (
<Box
style={{
sx={{
display: 'flex',
alignItems,
gap: 1,
@@ -87,8 +95,8 @@ export function InfoRow({
sx={{
wordBreak: 'break-word',
whiteSpace: 'pre-line',
maxHeight: '33vh',
overflowY: 'auto',
maxHeight: isMobile ? 'none' : '33vh',
overflowY: isMobile ? undefined : 'auto',
width: '100%',
...style
}}
@@ -1,8 +1,15 @@
import { Box, TextFieldProps, Typography } from '@linagora/twake-mui'
import {
Box,
TextFieldProps,
Typography,
useMediaQuery,
useTheme
} from '@linagora/twake-mui'
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
import {
DatePicker,
DatePickerFieldProps
DatePickerFieldProps,
DatePickerSlotProps
} from '@mui/x-date-pickers/DatePicker'
import { PickerValue } from '@mui/x-date-pickers/internals'
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'
@@ -88,8 +95,10 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
onStartTimeChange,
onEndDateChange,
onEndTimeChange
}) => {
}): JSX.Element => {
const { t, lang } = useI18n()
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
const initialDurationRef = React.useRef<number | null>(null)
const isUserActionRef = React.useRef(false)
@@ -139,7 +148,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
? t('dateTimeFields.date')
: t('dateTimeFields.startDate')
const handleStartDateChange = (value: PickerValue) => {
const handleStartDateChange = (value: PickerValue): void => {
if (!value || !value.isValid()) return
isUserActionRef.current = true
@@ -169,7 +178,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
}
}
const handleStartTimeChange = (value: PickerValue) => {
const handleStartTimeChange = (value: PickerValue): void => {
if (!value || !value.isValid()) return
isUserActionRef.current = true
@@ -196,7 +205,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
}
}
const handleEndDateChange = (value: PickerValue) => {
const handleEndDateChange = (value: PickerValue): void => {
if (!value || !value.isValid()) return
isUserActionRef.current = true
@@ -233,7 +242,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
onEndDateChange(newDateStr)
}
const handleEndTimeChange = (value: PickerValue) => {
const handleEndTimeChange = (value: PickerValue): void => {
if (!value || !value.isValid()) return
isUserActionRef.current = true
@@ -270,7 +279,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
testId: string,
hasError = false,
testLabel?: string
) => ({
): Partial<DatePickerSlotProps<true>> => ({
textField: {
size: 'small' as const,
margin: 'dense' as const,
@@ -360,7 +369,12 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
>
{isExpanded || shouldShowFullFieldsInNormal ? (
<>
<Box display="flex" gap={1} flexDirection="row" alignItems="center">
<Box
display="flex"
gap={1}
flexDirection={isMobile ? 'column' : 'row'}
alignItems="center"
>
<Box sx={{ maxWidth: '300px', width: '48%' }}>
<DatePicker
format={LONG_DATE_FORMAT}
@@ -407,7 +421,12 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
</Box>
)}
</Box>
<Box display="flex" gap={1} flexDirection="row" alignItems="center">
<Box
display="flex"
gap={1}
flexDirection={isMobile ? 'column' : 'row'}
alignItems={isMobile ? 'stretch' : 'center'}
>
<Box sx={{ maxWidth: '300px', width: '48%' }}>
<DatePicker
format={LONG_DATE_FORMAT}
@@ -456,7 +475,12 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
</Box>
</>
) : shouldShowEndDateNormal ? (
<Box display="flex" gap={1} flexDirection="row" alignItems="center">
<Box
display="flex"
gap={1}
flexDirection={isMobile ? 'column' : 'row'}
alignItems={isMobile ? 'stretch' : 'center'}
>
<Box sx={{ maxWidth: '300px', width: '48%' }}>
<DatePicker
format={LONG_DATE_FORMAT}
@@ -501,8 +525,15 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
</Box>
</Box>
) : (
<Box display="flex" gap={1} flexDirection="row" alignItems="center">
<Box sx={{ maxWidth: '300px', width: '48%' }}>
<Box
display="flex"
gap={1}
flexDirection={isMobile ? 'column' : 'row'}
alignItems={isMobile ? 'stretch' : 'center'}
>
<Box
sx={isMobile ? undefined : { maxWidth: '300px', width: '48%' }}
>
<DatePicker
format={LONG_DATE_FORMAT}
value={startDateValue}
@@ -519,62 +550,64 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
}}
/>
</Box>
<Box sx={{ maxWidth: '110px' }}>
<TimePicker
ampm={false}
value={startTimeValue}
onChange={handleStartTimeChange}
disabled={allday}
thresholdToRenderTimeInASingleColumn={48}
timeSteps={{ minutes: 30 }}
slots={{
field: EditableTimeField,
actionBar: () => null
}}
slotProps={{
openPickerButton: { sx: { display: 'none' } },
popper: { sx: timePickerPopperSx },
field: getTimeFieldSlotProps(
'start-time-input',
false,
t('dateTimeFields.startTime')
)
}}
/>
</Box>
{!allday && (
<Typography
sx={{
alignSelf: 'center',
mx: 0.5,
mt: 0.5
}}
>
-
</Typography>
)}
<Box sx={{ maxWidth: '110px' }}>
<TimePicker
ampm={false}
value={endTimeValue}
onChange={handleEndTimeChange}
disabled={allday}
thresholdToRenderTimeInASingleColumn={48}
timeSteps={{ minutes: 30 }}
slots={{
field: EditableTimeField,
actionBar: () => null
}}
slotProps={{
openPickerButton: { sx: { display: 'none' } },
popper: { sx: timePickerPopperSx },
field: getTimeFieldSlotProps(
'end-time-input',
!!validation.errors.dateTime,
t('dateTimeFields.endTime')
)
}}
/>
<Box display="flex" gap={1} flexDirection="row">
<Box sx={{ width: isMobile ? '100%' : '110px' }}>
<TimePicker
ampm={false}
value={startTimeValue}
onChange={handleStartTimeChange}
disabled={allday}
thresholdToRenderTimeInASingleColumn={48}
timeSteps={{ minutes: 30 }}
slots={{
field: EditableTimeField,
actionBar: () => null
}}
slotProps={{
openPickerButton: { sx: { display: 'none' } },
popper: { sx: timePickerPopperSx },
field: getTimeFieldSlotProps(
'start-time-input',
false,
t('dateTimeFields.startTime')
)
}}
/>
</Box>
{!allday && (
<Typography
sx={{
alignSelf: 'center',
mx: 0.5,
mt: 0.5
}}
>
-
</Typography>
)}
<Box sx={{ width: isMobile ? '100%' : '110px' }}>
<TimePicker
ampm={false}
value={endTimeValue}
onChange={handleEndTimeChange}
disabled={allday}
thresholdToRenderTimeInASingleColumn={48}
timeSteps={{ minutes: 30 }}
slots={{
field: EditableTimeField,
actionBar: () => null
}}
slotProps={{
openPickerButton: { sx: { display: 'none' } },
popper: { sx: timePickerPopperSx },
field: getTimeFieldSlotProps(
'end-time-input',
!!validation.errors.dateTime,
t('dateTimeFields.endTime')
)
}}
/>
</Box>
</Box>
</Box>
)}
@@ -1,6 +1,6 @@
import { PartStat } from '@/features/User/models/attendee'
import { userData } from '@/features/User/userDataTypes'
import { Box, Typography } from '@linagora/twake-mui'
import { Box, Typography, useTheme, useMediaQuery } from '@linagora/twake-mui'
import { Dispatch, SetStateAction, useState } from 'react'
import { useI18n } from 'twake-i18n'
import { ContextualizedEvent } from '../EventsTypes'
@@ -21,8 +21,10 @@ export function AttendanceValidation({
user,
setAfterChoiceFunc,
setOpenEditModePopup
}: AttendanceValidationProps) {
}: AttendanceValidationProps): JSX.Element | null {
const { currentUserAttendee, isOwn, calendar } = contextualizedEvent
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
const { t } = useI18n()
const [isLoading, setIsLoading] = useState(false)
const [loadingValue, setLoadingValue] = useState<PartStat | null>(null)
@@ -50,7 +52,7 @@ export function AttendanceValidation({
return null
}
const handleLoadingChange = (loading: boolean, value?: PartStat) => {
const handleLoadingChange = (loading: boolean, value?: PartStat): void => {
setIsLoading(loading)
setLoadingValue(loading && value ? value : null)
}
@@ -66,16 +68,34 @@ export function AttendanceValidation({
}
return (
<>
<Typography variant="body2" sx={{ marginRight: 1 }}>
{calendar.owner?.resource
? t('eventPreview.authorizeQuestion')
: t('eventPreview.attendingQuestion')}
</Typography>
<Box display="flex" gap={1} mx={1} alignItems="center">
<RSVPButton rsvpValue="ACCEPTED" {...commonButtonProps} />
<RSVPButton rsvpValue="DECLINED" {...commonButtonProps} />
<RSVPButton rsvpValue="TENTATIVE" {...commonButtonProps} />
<Box
sx={{
display: 'flex',
flexDirection: isMobile ? 'column' : 'row',
gap: isMobile ? '16px' : undefined,
alignItems: 'center'
}}
>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap'
}}
>
{!isMobile && (
<Typography variant="body2" sx={{ marginRight: 1 }}>
{calendar.owner?.resource
? t('eventPreview.authorizeQuestion')
: t('eventPreview.attendingQuestion')}
</Typography>
)}
<Box display="flex" gap={1} mx={1} alignItems="center">
<RSVPButton rsvpValue="ACCEPTED" {...commonButtonProps} />
<RSVPButton rsvpValue="DECLINED" {...commonButtonProps} />
<RSVPButton rsvpValue="TENTATIVE" {...commonButtonProps} />
</Box>
</Box>
{!contextualizedEvent.isOrganizer && (
<Typography
@@ -91,6 +111,6 @@ export function AttendanceValidation({
setOpen={setOpenCounterModal}
contextualizedEvent={contextualizedEvent}
/>
</>
</Box>
)
}
@@ -20,7 +20,7 @@ export function EventCounterModal({
open: boolean
setOpen: (b: boolean) => void
contextualizedEvent: ContextualizedEvent
}) {
}): JSX.Element {
const { t } = useI18n()
const [isSubmitting, setIsSubmitting] = useState(false)
const [showSuccessToast, setShowSuccessToast] = useState(false)
@@ -56,7 +56,7 @@ export function EventCounterModal({
errors: { dateTime: '' }
})
const handleStartDateChange = (value: string) => {
const handleStartDateChange = (value: string): void => {
setStartDate(value)
if (value > endDate) {
setEndDate(value)
@@ -65,18 +65,18 @@ export function EventCounterModal({
setValidation({ errors: { dateTime: '' } })
}
const handleStartTimeChange = (value: string) => {
const handleStartTimeChange = (value: string): void => {
setStartTime(value)
setValidation({ errors: { dateTime: '' } })
}
const handleEndDateChange = (value: string) => {
const handleEndDateChange = (value: string): void => {
setEndDate(value)
setHasEndDateChanged(true)
setValidation({ errors: { dateTime: '' } })
}
const handleEndTimeChange = (value: string) => {
const handleEndTimeChange = (value: string): void => {
setEndTime(value)
setValidation({ errors: { dateTime: '' } })
}
@@ -107,7 +107,7 @@ export function EventCounterModal({
return true
}
const handleSubmit = async () => {
const handleSubmit = async (): Promise<void> => {
if (!validate()) return
if (
!contextualizedEvent.currentUserAttendee?.cal_address ||
@@ -163,6 +163,21 @@ export function EventCounterModal({
open={open}
onClose={() => setOpen(false)}
title={t('eventPreview.proposeNewTime')}
actions={
<Box display="flex" justifyContent="flex-end">
<Button variant="text" onClick={() => setOpen(false)}>
{t('common.cancel')}
</Button>
<Button
variant="contained"
color="primary"
onClick={() => void handleSubmit()}
disabled={isSubmitting}
>
{t('eventPreview.sendProposal')}
</Button>
</Box>
}
>
{/* Event title */}
<Box display="flex" alignItems="center" gap={1} mb={2}>
@@ -235,21 +250,6 @@ export function EventCounterModal({
}}
/>
</Box>
{/* Actions */}
<Box display="flex" justifyContent="flex-end" gap={2} mt={3}>
<Button variant="text" onClick={() => setOpen(false)}>
{t('common.cancel')}
</Button>
<Button
variant="contained"
color="primary"
onClick={handleSubmit}
disabled={isSubmitting}
>
{t('eventPreview.sendProposal')}
</Button>
</Box>
</ResponsiveDialog>
</>
)
@@ -1,8 +1,9 @@
import { useAppSelector } from '@/app/hooks'
import { useAttendeesFreeBusy } from '@/components/Attendees/useFreeBusy'
import { renderAttendeeBadge } from '@/components/Event/utils/eventUtils'
import { useScreenSizeDetection } from '@/useScreenSizeDetection'
import { extractEventBaseUuid } from '@/utils/extractEventBaseUuid'
import { AvatarGroup, Box, Typography } from '@linagora/twake-mui'
import { AvatarGroup, Box, Button, Typography } from '@linagora/twake-mui'
import PeopleAltOutlinedIcon from '@mui/icons-material/PeopleAltOutlined'
import { alpha, useTheme } from '@mui/material/styles'
import { useState } from 'react'
@@ -30,17 +31,22 @@ export function EventPreviewAttendees({
end,
timezone,
eventUid
}: EventPreviewAttendeesProps) {
}: EventPreviewAttendeesProps): JSX.Element {
const { t } = useI18n()
const theme = useTheme()
const { isTooSmall: isMobile } = useScreenSizeDetection()
const infoIconColor = alpha(theme.palette.grey[900], 0.9)
const infoIconSx = { minWidth: '25px', marginRight: 2, color: infoIconColor }
// Icon takes 25px width + 16px (mr: 2) = 41px, use negative margin to align avatars
const mobileAvatarOffset = '-42px'
const userEmail = useAppSelector(state => state.user.userData.email)
const [showAllAttendees, setShowAllAttendees] = useState(false)
const attendeePreview = makeAttendeePreview(allAttendees, t)
const toFreeBusyAttendee = (a: userAttendee) => ({
const toFreeBusyAttendee = (
a: userAttendee
): { email: string; userId: null } => ({
email: a.cal_address,
userId: null
})
@@ -60,7 +66,7 @@ export function EventPreviewAttendees({
enabled: !!(start && end && showAllAttendees)
})
const busyCaption = (a: userAttendee) =>
const busyCaption = (a: userAttendee): string | undefined =>
freeBusyMap[a.cal_address] === 'busy'
? a.cal_address === userEmail
? t('event.freeBusy.busyCalOwner')
@@ -73,44 +79,100 @@ export function EventPreviewAttendees({
<Box sx={{ ...infoIconSx, mt: 1 }}>
<PeopleAltOutlinedIcon />
</Box>
<Box style={{ marginBottom: 1, display: 'flex', flexDirection: 'row' }}>
<Box sx={{ marginRight: 2 }}>
<Typography>
{t('eventPreview.guests', { count: allAttendees.length })}
</Typography>
<Typography sx={{ fontSize: '13px', color: 'text.secondary' }}>
{attendeePreview}
</Typography>
</Box>
{!showAllAttendees && (
<AvatarGroup max={ATTENDEE_DISPLAY_LIMIT}>
{organizer &&
renderAttendeeBadge(
organizer,
'org',
t,
showAllAttendees,
true
)}
{attendees.map((a, idx) =>
renderAttendeeBadge(a, idx.toString(), t, showAllAttendees)
)}
</AvatarGroup>
)}
<Typography
<Box
sx={{
marginBottom: 1,
display: 'flex',
flexDirection: isMobile ? 'column' : 'row',
gap: isMobile ? 2 : undefined
}}
>
<Box
sx={{
cursor: 'pointer',
marginLeft: 2,
fontSize: '14px',
color: 'text.secondary',
alignSelf: 'center'
marginRight: 2,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}
onClick={() => setShowAllAttendees(prev => !prev)}
>
{showAllAttendees
? t('eventPreview.showLess')
: t('eventPreview.showMore')}
</Typography>
<Box>
<Typography>
{t('eventPreview.guests', { count: allAttendees.length })}
</Typography>
<Typography sx={{ fontSize: '13px', color: 'text.secondary' }}>
{attendeePreview}
</Typography>
</Box>
{isMobile && showAllAttendees && (
<Button
variant="text"
size="small"
sx={{
marginLeft: 2,
fontSize: '14px',
color: 'text.secondary',
alignSelf: 'center'
}}
onClick={() => setShowAllAttendees(false)}
>
{t('eventPreview.showLess')}
</Button>
)}
</Box>
{!showAllAttendees && (
<Box
sx={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
ml: isMobile ? mobileAvatarOffset : undefined
}}
>
<AvatarGroup max={ATTENDEE_DISPLAY_LIMIT}>
{organizer &&
renderAttendeeBadge(
organizer,
'org',
t,
showAllAttendees,
true
)}
{attendees.map((a, idx) =>
renderAttendeeBadge(a, idx.toString(), t, showAllAttendees)
)}
</AvatarGroup>
<Button
variant="text"
size="small"
sx={{
marginLeft: 2,
fontSize: '14px',
color: 'text.secondary',
alignSelf: 'center'
}}
onClick={() => setShowAllAttendees(true)}
>
{t('eventPreview.showMore')}
</Button>
</Box>
)}
{!isMobile && showAllAttendees && (
<Button
variant="text"
size="small"
sx={{
marginLeft: 2,
fontSize: '14px',
color: 'text.secondary',
alignSelf: 'center'
}}
onClick={() => setShowAllAttendees(false)}
>
{t('eventPreview.showLess')}
</Button>
)}
</Box>
</Box>
@@ -33,7 +33,14 @@ export function EventPreviewDetails({
const { t } = useI18n()
const theme = useTheme()
const infoIconColor = alpha(theme.palette.grey[900], 0.9)
const infoIconSx = { minWidth: '25px', marginRight: 2, color: infoIconColor }
const infoIconSx = {
minWidth: '25px',
marginRight: 2,
color: infoIconColor,
display: 'flex',
alignItems: 'center',
alignSelf: 'center'
}
const resources = useMemo(
() =>
@@ -87,13 +94,18 @@ export function EventPreviewDetails({
}
return (
<>
<Box
sx={{
display: 'flex',
gap: '16px',
flexDirection: 'column'
}}
>
{/* Video */}
{event.x_openpass_videoconference && (
<InfoRow
alignItems="flex-start"
icon={
<Box sx={{ ...infoIconSx, mt: 1 }}>
<Box sx={infoIconSx}>
<VideocamOutlinedIcon />
</Box>
}
@@ -136,7 +148,6 @@ export function EventPreviewDetails({
{/* Location */}
{event.location && (
<InfoRow
alignItems="flex-start"
icon={
<Box sx={infoIconSx}>
<LocationOnOutlinedIcon />
@@ -149,7 +160,6 @@ export function EventPreviewDetails({
{/* Resource */}
{resources?.length > 0 && (
<InfoRow
alignItems="flex-start"
flexWrap="wrap"
icon={
<Box sx={infoIconSx}>
@@ -203,7 +213,13 @@ export function EventPreviewDetails({
<InfoRow
alignItems="flex-start"
icon={
<Box sx={infoIconSx}>
<Box
sx={{
...infoIconSx,
alignItems: 'flex-start',
alignSelf: 'flex-start'
}}
>
<SubjectIcon />
</Box>
}
@@ -214,7 +230,6 @@ export function EventPreviewDetails({
{/* Alarm */}
{event.alarm && (
<InfoRow
alignItems="flex-start"
icon={
<Box sx={infoIconSx}>
<NotificationsNoneIcon />
@@ -237,7 +252,6 @@ export function EventPreviewDetails({
{/* Repetition */}
{event.repetition && (
<InfoRow
alignItems="flex-start"
icon={
<Box sx={infoIconSx}>
<RepeatIcon />
@@ -267,6 +281,6 @@ export function EventPreviewDetails({
error
/>
)}
</>
</Box>
)
}
@@ -37,7 +37,7 @@ export function EventPreviewHeader({
display="flex"
justifyContent="flex-end"
alignItems="center"
gap={0.5}
gap={2}
width="100%"
>
{window.DEBUG && (
@@ -92,6 +92,7 @@ export default function EventPreviewModal({
}
actionsJustifyContent="center"
style={{ overflow: 'auto' }}
titleSx={{ backgroundColor: '#FCFCFC' }}
title={
<EventPreviewHeader
event={event}