diff --git a/src/components/Event/components/DateTimeFields/DatePickerField.tsx b/src/components/Event/components/DateTimeFields/DatePickerField.tsx index 97d92aa..187d853 100644 --- a/src/components/Event/components/DateTimeFields/DatePickerField.tsx +++ b/src/components/Event/components/DateTimeFields/DatePickerField.tsx @@ -1,14 +1,8 @@ -import { DatePicker } from '@mui/x-date-pickers/DatePicker' +import { useMediaQuery } from '@linagora/twake-mui' import { PickerValue } from '@mui/x-date-pickers/internals' import { Dayjs } from 'dayjs' -import React from 'react' -import { LONG_DATE_FORMAT } from '../../utils/dateTimeFormatters' -import { ReadOnlyDateField } from '../ReadOnlyPickerField' -import { - dateCalendarLayoutSx, - getDateFieldSlotProps, - getDateSlotProps -} from './dateTimePickerSlotProps' +import { DesktopDatePickerField } from './DesktopDatePickerField' +import { TouchDatePickerField } from './TouchDatePickerField' export interface DatePickerFieldProps { value: Dayjs | null @@ -18,22 +12,11 @@ export interface DatePickerFieldProps { hasError?: boolean } -export const DatePickerField: React.FC = ({ - value, - onChange, - testId, - label, - hasError = false -}) => ( - -) +export const DatePickerField: React.FC = props => { + const isTouch = useMediaQuery('(pointer: coarse)') + return isTouch ? ( + + ) : ( + + ) +} diff --git a/src/components/Event/components/DateTimeFields/DesktopDatePickerField.tsx b/src/components/Event/components/DateTimeFields/DesktopDatePickerField.tsx new file mode 100644 index 0000000..3ec2a50 --- /dev/null +++ b/src/components/Event/components/DateTimeFields/DesktopDatePickerField.tsx @@ -0,0 +1,30 @@ +import { DatePicker } from '@mui/x-date-pickers/DatePicker' +import React from 'react' +import { LONG_DATE_FORMAT } from '../../utils/dateTimeFormatters' +import { ReadOnlyDateField } from '../ReadOnlyPickerField' +import { + dateCalendarLayoutSx, + getDateFieldSlotProps, + getDateSlotProps +} from './dateTimePickerSlotProps' +import { DatePickerFieldProps } from './DatePickerField' + +export const DesktopDatePickerField: React.FC = ({ + value, + onChange, + testId, + label, + hasError = false +}) => ( + +) diff --git a/src/components/Event/components/DateTimeFields/DesktopTimePickerField.tsx b/src/components/Event/components/DateTimeFields/DesktopTimePickerField.tsx new file mode 100644 index 0000000..18531ed --- /dev/null +++ b/src/components/Event/components/DateTimeFields/DesktopTimePickerField.tsx @@ -0,0 +1,35 @@ +import { TimePicker } from '@mui/x-date-pickers/TimePicker' +import React from 'react' +import { EditableTimeField } from '../EditableTimeField' +import { + getTimeFieldSlotProps, + timePickerPopperSx +} from './dateTimePickerSlotProps' +import { TimePickerFieldProps } from './TimePickerField' + +export const DesktopTimePickerField: React.FC = ({ + value, + onChange, + testId, + label, + hasError = false, + disabled = false +}) => ( + null + }} + slotProps={{ + openPickerButton: { sx: { display: 'none' } }, + popper: { sx: timePickerPopperSx }, + field: getTimeFieldSlotProps(testId, hasError, label) + }} + /> +) diff --git a/src/components/Event/components/DateTimeFields/TimePickerField.tsx b/src/components/Event/components/DateTimeFields/TimePickerField.tsx index bccbbb3..babff4b 100644 --- a/src/components/Event/components/DateTimeFields/TimePickerField.tsx +++ b/src/components/Event/components/DateTimeFields/TimePickerField.tsx @@ -1,12 +1,9 @@ +import { useMediaQuery } from '@linagora/twake-mui' import { PickerValue } from '@mui/x-date-pickers/internals' -import { TimePicker } from '@mui/x-date-pickers/TimePicker' import { Dayjs } from 'dayjs' import React from 'react' -import { EditableTimeField } from '../EditableTimeField' -import { - getTimeFieldSlotProps, - timePickerPopperSx -} from './dateTimePickerSlotProps' +import { DesktopTimePickerField } from './DesktopTimePickerField' +import { TouchTimePickerField } from './TouchTimePickerField' export interface TimePickerFieldProps { value: Dayjs | null @@ -17,29 +14,11 @@ export interface TimePickerFieldProps { disabled?: boolean } -export const TimePickerField: React.FC = ({ - value, - onChange, - testId, - label, - hasError = false, - disabled = false -}) => ( - null - }} - slotProps={{ - openPickerButton: { sx: { display: 'none' } }, - popper: { sx: timePickerPopperSx }, - field: getTimeFieldSlotProps(testId, hasError, label) - }} - /> -) +export const TimePickerField: React.FC = props => { + const isTouch = useMediaQuery('(pointer: coarse)') + return isTouch ? ( + + ) : ( + + ) +} diff --git a/src/components/Event/components/DateTimeFields/TouchDatePickerField.tsx b/src/components/Event/components/DateTimeFields/TouchDatePickerField.tsx new file mode 100644 index 0000000..56b73b0 --- /dev/null +++ b/src/components/Event/components/DateTimeFields/TouchDatePickerField.tsx @@ -0,0 +1,193 @@ +import CalendarTodayIcon from '@mui/icons-material/CalendarToday' +import EditIcon from '@mui/icons-material/Edit' +import { + Box, + Button, + Dialog, + IconButton, + Typography +} from '@linagora/twake-mui' +import { DateField, DatePicker } from '@mui/x-date-pickers' +import { DateCalendar } from '@mui/x-date-pickers/DateCalendar' +import { PickerValue } from '@mui/x-date-pickers/internals' +import dayjs from 'dayjs' +import customParseFormat from 'dayjs/plugin/customParseFormat' +import React, { useState } from 'react' +import { useI18n } from 'twake-i18n' +import { ReadOnlyDateField } from '../ReadOnlyPickerField' +import { DatePickerFieldProps } from './DatePickerField' +import { + dateCalendarLayoutSx, + getDateFieldSlotProps, + getDateSlotProps +} from './dateTimePickerSlotProps' + +dayjs.extend(customParseFormat) + +const DISPLAY_FORMAT = 'D MMMM, YYYY' + +interface DatePickerDialogProps { + value: PickerValue + onChange: (value: PickerValue) => void + onAccept: (value: PickerValue) => void + onCancel: () => void +} + +const DatePickerDialogContent: React.FC = ({ + value, + onChange, + onAccept, + onCancel +}) => { + const { t } = useI18n() + + const [viewMode, setViewMode] = useState<'calendar' | 'text'>('calendar') + + const [internalValue, setInternalValue] = useState(value) + + const handleInternalChange = (newValue: PickerValue): void => { + setInternalValue(newValue) + onChange(newValue) + } + + const handleToggleView = (): void => { + setViewMode(viewMode === 'calendar' ? 'text' : 'calendar') + } + + const displayDate = internalValue?.isValid() + ? internalValue.format(DISPLAY_FORMAT) + : t('dateTimeFields.pickADate') + + return ( + + {/* Header */} + + + {displayDate} + + + {viewMode === 'calendar' ? ( + + ) : ( + + )} + + + + {/* Body */} + {viewMode === 'calendar' ? ( + + ) : ( + + + {t('dateTimeFields.date')} + + + + )} + + + + + + + ) +} + +export const TouchDatePickerField: React.FC = ({ + value, + onChange, + testId, + label = 'Date', + hasError = false +}) => { + const [open, setOpen] = useState(false) + const [pendingValue, setPendingValue] = useState(value) + + const handleOpen = (): void => { + setPendingValue(value) + setOpen(true) + } + + const handleAccept = (newValue: PickerValue): void => { + onChange(newValue) + setOpen(false) + } + + const handleCancel = (): void => { + setPendingValue(value) + setOpen(false) + } + + return ( + <> + + + + + + ) +} diff --git a/src/components/Event/components/DateTimeFields/TouchTimePickerField.tsx b/src/components/Event/components/DateTimeFields/TouchTimePickerField.tsx new file mode 100644 index 0000000..5db81e2 --- /dev/null +++ b/src/components/Event/components/DateTimeFields/TouchTimePickerField.tsx @@ -0,0 +1,190 @@ +import { + Box, + Button, + Dialog, + DialogActions, + Divider, + IconButton, + SxProps, + Theme +} from '@linagora/twake-mui' +import AccessTime from '@mui/icons-material/AccessTime' +import KeyboardAltOutlined from '@mui/icons-material/KeyboardAltOutlined' +import { + MobileTimePicker, + renderTimeViewClock, + TimeField +} from '@mui/x-date-pickers' +import { PickerValue } from '@mui/x-date-pickers/internals' +import { StaticTimePicker } from '@mui/x-date-pickers/StaticTimePicker' +import { useState } from 'react' +import { useI18n } from 'twake-i18n' +import { EditableTimeField } from '../EditableTimeField' +import { getTimeFieldSlotProps } from './dateTimePickerSlotProps' +import { TimePickerFieldProps } from './TimePickerField' + +const TIME_DISPLAY_SX = { + fontSize: '48px', + fontWeight: 400 +} as const + +const TIME_INPUT_SX: SxProps = { + width: '100%', + '& .MuiPickersInputBase-sectionAfter': { mx: '16px' }, + '& .MuiPickersSectionList-root': { justifyContent: 'end' }, + '& .MuiPickersInputBase-root': { + ...TIME_DISPLAY_SX, + color: 'text.secondary', + backgroundColor: 'transparent !important', + '&:before': { borderBottomColor: 'transparent !important' }, + '&:after': { borderBottomColor: 'transparent !important' } + }, + '& .MuiPickersInputBase-input': { + textAlign: 'center' + }, + '& .MuiPickersInputBase-root.Mui-focused .MuiPickersInputBase-input': { + backgroundColor: 'transparent !important', + color: 'primary.main' + } +} + +const STATIC_PICKER_SX: SxProps = { + '& .MuiPickersToolbar-content': { justifyContent: 'center' }, + '& .MuiTimePickerToolbar-hourMinuteLabel': { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + gap: '16px' + }, + '& .MuiTimePickerToolbar-hourMinuteLabel .MuiPickersToolbarText-root[data-selected]': + { color: 'primary.main' }, + '& .MuiPickersToolbar-root .MuiTypography-root': TIME_DISPLAY_SX, + '& .MuiPickersToolbar-title': { display: 'none' }, + '& .MuiPickersArrowSwitcher-root': { display: 'none' } +} + +type DialogView = 'clock' | 'keyboard' + +export const TouchTimePickerField: React.FC = ({ + value, + onChange, + testId, + label, + hasError = false, + disabled = false +}) => { + const { t } = useI18n() + + const [open, setOpen] = useState(false) + const [internalValue, setInternalValue] = useState(value) + const [view, setView] = useState('clock') + + const handleOpen = (): void => { + setInternalValue(value) + setOpen(true) + } + + const handleAccept = (): void => { + onChange(internalValue) + setOpen(false) + } + + const handleCancel = (): void => { + setOpen(false) + } + + const toggleView = (): void => + setView(view === 'clock' ? 'keyboard' : 'clock') + + return ( + <> + e.target.blur() + } + }} + /> + + + {view === 'clock' && ( + null }} + sx={STATIC_PICKER_SX} + /> + )} + + {view === 'keyboard' && ( + + + + )} + + + + + + {view === 'clock' ? : } + + + + + + + + + ) +} diff --git a/src/locales/en.json b/src/locales/en.json index a0233f5..e340321 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -347,7 +347,11 @@ "startDate": "Start Date", "startTime": "Start Time", "endDate": "End Date", - "endTime": "End Time" + "endTime": "End Time", + "pickADate": "Pick a date", + "switchToTextInput": "Switch to text input", + "switchToCalendar": "Switch to calendar view", + "switchToClockView": "Switch to clock view" }, "websocket": { "browserOnline": "You’re back online. Reconnecting live updates…", diff --git a/src/locales/fr.json b/src/locales/fr.json index b291e74..7f82933 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -348,7 +348,11 @@ "startDate": "Date du début", "startTime": "Heure du début", "endDate": "Date de la fin", - "endTime": "Heure de la fin" + "endTime": "Heure de la fin", + "pickADate": "Choisissez une date", + "switchToTextInput": "Passer à la saisie de texte", + "switchToCalendar": "Passer à la vue calendrier", + "switchToClockView": "Passer à la vue horloge" }, "websocket": { "browserOnline": "Vous êtes de nouveau en ligne. Reconnexion des mises à jour en temps réel…", diff --git a/src/locales/ru.json b/src/locales/ru.json index de3b561..fa19b12 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -348,7 +348,11 @@ "startDate": "Дата начала", "startTime": "Время начала", "endDate": "Дата окончания", - "endTime": "Время окончания" + "endTime": "Время окончания", + "pickADate": "Выберите дату", + "switchToTextInput": "Переключиться на текстовый ввод", + "switchToCalendar": "Переключиться на календарь", + "switchToClockView": "Переключиться на вид часов" }, "websocket": { "browserOnline": "Вы снова в сети. Подключаем обновления в реальном времени…", diff --git a/src/locales/vi.json b/src/locales/vi.json index 5aa5e1c..9c27aef 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -346,7 +346,11 @@ "startDate": "Ngày bắt đầu", "startTime": "Giờ bắt đầu", "endDate": "Ngày kết thúc", - "endTime": "Giờ kết thúc" + "endTime": "Giờ kết thúc", + "pickADate": "Chọn ngày", + "switchToTextInput": "Chuyển sang nhập văn bản", + "switchToCalendar": "Chuyển sang chế độ lịch", + "switchToClockView": "Chuyển sang chế độ đồng hồ" }, "websocket": { "browserOnline": "Bạn đã trực tuyến trở lại. Đang kết nối lại cập nhật thời gian thực…",