aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/atoms/layout/time/time.tsx
blob: 886fee0ab889cc8d9ae84a65ff514ae6d88a75c4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import {
  type ForwardRefRenderFunction,
  type TimeHTMLAttributes,
  forwardRef,
} from 'react';
import { useIntl } from 'react-intl';
import { CONFIG } from '../../../../utils/config';

type GetDateOptionsConfig = {
  hasDay: boolean;
  hasMonth: boolean;
  hasWeekDay: boolean;
  hasYear: boolean;
};

const getDateOptions = ({
  hasDay,
  hasMonth,
  hasWeekDay,
  hasYear,
}: GetDateOptionsConfig): Intl.DateTimeFormatOptions => {
  const day: Intl.DateTimeFormatOptions['day'] = 'numeric';
  const month: Intl.DateTimeFormatOptions['month'] = 'long';
  const weekDay: Intl.DateTimeFormatOptions['weekday'] = 'long';
  const year: Intl.DateTimeFormatOptions['year'] = 'numeric';
  const options: [
    keyof Intl.DateTimeFormatOptions,
    Intl.DateTimeFormatOptions[keyof Intl.DateTimeFormatOptions],
  ][] = [];

  if (hasDay) options.push(['day', day]);
  if (hasMonth) options.push(['month', month]);
  if (hasWeekDay) options.push(['weekday', weekDay]);
  if (hasYear) options.push(['year', year]);

  return Object.fromEntries(options);
};

export type TimeProps = Omit<
  TimeHTMLAttributes<HTMLTimeElement>,
  'children' | 'dateTime'
> & {
  /**
   * A valid date string.
   */
  date: string;
  /**
   * Should we hide the day number?
   *
   * @default false
   */
  hideDay?: boolean;
  /**
   * Should we hide the month?
   *
   * @default false
   */
  hideMonth?: boolean;
  /**
   * Should we hide the year?
   *
   * @default false
   */
  hideYear?: boolean;
  /**
   * The current locale.
   *
   * @default CONFIG.locales.defaultLocale
   */
  locale?: string;
  /**
   * Should we display the time in addition to the date?
   *
   * @default false
   */
  showTime?: boolean;
  /**
   * Should we display the week day?
   *
   * @default false
   */
  showWeekDay?: boolean;
};

const TimeWithRef: ForwardRefRenderFunction<HTMLTimeElement, TimeProps> = (
  {
    date,
    hideDay = false,
    hideMonth = false,
    hideYear = false,
    locale = CONFIG.locales.defaultLocale,
    showTime = false,
    showWeekDay = false,
    ...props
  },
  ref
) => {
  const intl = useIntl();
  const dateOptions = getDateOptions({
    hasDay: !hideDay,
    hasMonth: !hideMonth,
    hasWeekDay: showWeekDay,
    hasYear: !hideYear,
  });
  const fullDate = new Date(date);
  const dateTime = fullDate.toISOString();
  const readableDate = fullDate.toLocaleDateString(locale, dateOptions);
  const formattedTime = fullDate.toLocaleTimeString(locale, {
    hour: 'numeric',
    minute: 'numeric',
  });
  const readableTime =
    locale === 'fr' ? formattedTime.replace(':', 'h') : formattedTime;

  return (
    <time {...props} dateTime={dateTime} ref={ref}>
      {showTime
        ? intl.formatMessage(
            {
              defaultMessage: '{date} at {time}',
              description: 'Time: readable date and time',
              id: '8q5PXx',
            },
            { date: readableDate, time: readableTime }
          )
        : readableDate}
    </time>
  );
};

/**
 * Time component.
 *
 * Render a date with an optional time in a `<time>` element.
 */
export const Time = forwardRef(TimeWithRef);