Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
@@ -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
|
||||
}}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user