diff --git a/api/events.js b/api/events.js index 892c13cdf..ed4a079e3 100644 --- a/api/events.js +++ b/api/events.js @@ -21,6 +21,32 @@ const isAllDayValue = (value) => typeof value === 'string' && value.toLowerCase().replace(/[\s\-_]/g, '') === 'allday' +const getUTCMonthDay = (month, day) => + dayjs + .utc() + .date(1) + .month(month - 1) + .date(day) + .hour(0) + .minute(0) + .second(0) + .millisecond(0) + +const formatTime = (date, hour, minute) => { + const UTCTime = date.hour(hour).minute(minute) + + if (isNaN(UTCTime)) return null + + const PTTime = UTCTime.tz('America/Los_Angeles') + + return { + utc: UTCTime.format('h:mm a'), + pt: PTTime.format('h:mm a'), + utcDate: UTCTime.format('MMM D'), + ptDate: PTTime.format('MMM D'), + } +} + export const getEvents = (year) => { const events_path = year ? `content/${year}/events` : 'content/events' if (!fs.existsSync(events_path)) return [] @@ -93,14 +119,11 @@ const formatEventDateTime = ( throw new TypeError('date must be in mm/dd format (e.g. 06/12).') } - const UTCDate = dayjs - .utc() - .date(day) - .month(month - 1) - + const UTCDate = getUTCMonthDay(month, day) const formattedDate = UTCDate.format('MMM D') let formattedEndDate = null + let endUTCDate = UTCDate if (endDate && endDate !== startDate) { const [endMonth, endDay] = endDate.split('/') @@ -109,11 +132,7 @@ const formatEventDateTime = ( throw new TypeError('date must be in mm/dd format (e.g. 06/12).') } - const endUTCDate = dayjs - .utc() - .date(endDay) - .month(endMonth - 1) - + endUTCDate = getUTCMonthDay(endMonth, endDay) formattedEndDate = endUTCDate.format('MMM D') } @@ -121,8 +140,18 @@ const formatEventDateTime = ( if (isAllDayValue(startTime) || isAllDayValue(endTime)) { return { date: formattedDate, - startTime: { utc: ALL_DAY, pt: ALL_DAY }, - endTime: { utc: ALL_DAY, pt: ALL_DAY }, + startTime: { + utc: ALL_DAY, + pt: ALL_DAY, + utcDate: formattedDate, + ptDate: formattedDate, + }, + endTime: { + utc: ALL_DAY, + pt: ALL_DAY, + utcDate: formattedEndDate || formattedDate, + ptDate: formattedEndDate || formattedDate, + }, endDate: formattedEndDate, timeDisplay: 'all-day', } @@ -138,16 +167,10 @@ const formatEventDateTime = ( if (!isTBDValue(startTime)) { const [startHour, startMinute] = startTime.split(':') + const parsedStartTime = formatTime(UTCDate, startHour, startMinute) - const UTCStartTime = UTCDate.hour(startHour).minute(startMinute) - - if (!isNaN(UTCStartTime)) { - const PTStartTime = UTCStartTime.tz('America/Los_Angeles') - - formattedStartTime = { - utc: UTCStartTime.format('h:mm a'), - pt: PTStartTime.format('h:mm a'), - } + if (parsedStartTime) { + formattedStartTime = parsedStartTime hasSpecificStartTime = true } } @@ -162,16 +185,10 @@ const formatEventDateTime = ( if (!isTBDValue(endTime)) { const [endHour, endMinute] = endTime.split(':') + const parsedEndTime = formatTime(endUTCDate, endHour, endMinute) - const UTCEndTime = UTCDate.hour(endHour).minute(endMinute) - - if (!isNaN(UTCEndTime)) { - const PTEndTime = UTCEndTime.tz('America/Los_Angeles') - - formattedEndTime = { - utc: UTCEndTime.format('h:mm a'), - pt: PTEndTime.format('h:mm a'), - } + if (parsedEndTime) { + formattedEndTime = parsedEndTime hasSpecificEndTime = true } } diff --git a/components/chip/Chip.jsx b/components/chip/Chip.jsx index ae4ed7203..d3b5dc431 100644 --- a/components/chip/Chip.jsx +++ b/components/chip/Chip.jsx @@ -1,6 +1,6 @@ import clsx from 'clsx' -const Chip = ({ label, icon, customColor }) => { +const Chip = ({ label, icon, customColor, variant }) => { const classes = clsx('chip', { 'custom-color': customColor, }) @@ -13,7 +13,11 @@ const Chip = ({ label, icon, customColor }) => { return (
diff --git a/components/chip/chip.scss b/components/chip/chip.scss index f0560814d..09a9511be 100644 --- a/components/chip/chip.scss +++ b/components/chip/chip.scss @@ -4,6 +4,7 @@ align-items: center; column-gap: 6px; white-space: nowrap; + min-width: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; @@ -47,4 +48,10 @@ fill: $white; } } + + &__label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + } } diff --git a/components/date-time-chip/DateTimeChip.jsx b/components/date-time-chip/DateTimeChip.jsx index dd9a60f15..362885902 100644 --- a/components/date-time-chip/DateTimeChip.jsx +++ b/components/date-time-chip/DateTimeChip.jsx @@ -1,16 +1,46 @@ import { getLiteral } from '../../common/i18n' import IconClock from '../../public/icons/clock' +const TimeRow = ({ children, timezone }) => ( +

+ + + + + {children} + {timezone ? ( + <> + {' '} + {timezone} + + ) : null} + +

+) + +const formatRange = (startTime, endTime, timezone) => { + const start = startTime?.[timezone] + const end = endTime?.[timezone] + + if (!start || !end) return null + + const startDate = startTime?.[`${timezone}Date`] + const endDate = endTime?.[`${timezone}Date`] + + if (startDate && endDate && startDate !== endDate) { + return `${startDate}, ${start} - ${endDate}, ${end}` + } + + return `${start} - ${end}` +} + const DateTimeChip = ({ startTime, endTime, timeDisplay }) => { if (timeDisplay === 'all-day') { return (
-

- - - + {getLiteral('message:all-day')} -

+
) } @@ -18,40 +48,24 @@ const DateTimeChip = ({ startTime, endTime, timeDisplay }) => { if (timeDisplay === 'tbd') { return (
-

- - - + {getLiteral('message:tbd')} -

+
) } + const utcRange = formatRange(startTime, endTime, 'utc') + const ptRange = formatRange(startTime, endTime, 'pt') + return (
- {startTime ? ( -

- - - - {`${startTime.utc} - ${endTime.utc}`} - - {getLiteral('timezone:utc')} - -

+ {utcRange ? ( + {utcRange} ) : null} - {endTime ? ( -

- - - - {`${startTime.pt} - ${endTime.pt}`} - - {getLiteral('timezone:pt')} - -

+ {ptRange ? ( + {ptRange} ) : null}
) diff --git a/components/date-time-chip/date-time-chip.scss b/components/date-time-chip/date-time-chip.scss index d0cfd5c5d..c021a2f36 100644 --- a/components/date-time-chip/date-time-chip.scss +++ b/components/date-time-chip/date-time-chip.scss @@ -6,11 +6,17 @@ &__time { display: inline-flex; - align-items: center; - white-space: nowrap; + align-items: flex-start; + min-width: 0; + margin: 0; color: $white-80; } + &__label { + min-width: 0; + line-height: 1.4; + } + &__timezone { margin-left: spacing(1); font-weight: $medium; diff --git a/components/event-detail/EventDetail.jsx b/components/event-detail/EventDetail.jsx index 3d581718c..7c4bbd898 100644 --- a/components/event-detail/EventDetail.jsx +++ b/components/event-detail/EventDetail.jsx @@ -40,7 +40,7 @@ const EventDetail = ({ event, reverseColumns, isFullPage }) => { /> {event.language && } - {event.location && } + {event.location && }
{
{event.language && } - {event.location && } + {event.location && ( + + )}
-

{ expect(parsed.formattedDate.endDate).toBeTruthy() }) + test('Event with endDate formats end time on the end date', () => { + const parsed = parseEvent({ + ...baseEvent, + date: '05/21', + endDate: '05/22', + UTCStartTime: '09:00', + UTCEndTime: '07:00', + }) + + expect(parsed.formattedDate.startTime.utcDate).toBe('May 21') + expect(parsed.formattedDate.endTime.utcDate).toBe('May 22') + expect(parsed.formattedDate.startTime.ptDate).toBe('May 21') + expect(parsed.formattedDate.endTime.ptDate).toBe('May 22') + }) + test('Event without title throws', () => { expect(() => parseEvent({ ...baseEvent, title: undefined })).toThrow() })