[#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>
)}