{"version":3,"file":"Calendar.cjs","sources":["../../../src/components/Calendar/Calendar.tsx"],"sourcesContent":["'use client'\n\nimport dayjs from 'dayjs'\nimport {\n  type ComponentProps,\n  type MouseEvent,\n  type PropsWithChildren,\n  forwardRef,\n  memo,\n  useCallback,\n  useEffect,\n  useId,\n  useMemo,\n  useState,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useIntl } from '../../intl'\nimport { Button } from '../Button'\nimport { FaCaretDownIcon, FaChevronLeftIcon, FaChevronRightIcon } from '../Icon'\nimport { Cluster } from '../Layout'\n\nimport { CalendarTable } from './CalendarTable'\nimport { YearPicker } from './YearPicker'\nimport { getFromDate, getMonthArray, getToDate, isBetween, minDate } from './calendarHelper'\n\ntype AbstractProps = {\n  /** 選択可能な開始日 */\n  from?: Date\n  /** 選択可能な終了日 */\n  to?: Date\n  /** トリガのセレクトイベントを処理するハンドラ */\n  onSelectDate: (e: MouseEvent, date: Date) => void\n  /** 選択された日付 */\n  value?: Date\n}\ntype Props = AbstractProps & Omit<ComponentProps<'div'>, keyof AbstractProps>\n\ntype DayJsType = ReturnType<typeof dayjs>\n\nconst classNameGenerator = tv({\n  slots: {\n    container:\n      'smarthr-ui-Calendar shr-inline-block shr-overflow-hidden shr-rounded-m shr-bg-white shr-text-black shr-shadow-layer-3 forced-colors:shr-border-shorthand forced-colors:shr-shadow-none',\n    header: 'smarthr-ui-Calendar-header shr-border-b-shorthand shr-flex shr-items-center shr-p-1',\n    yearMonth: 'smarthr-ui-Calendar-yearMonth shr-me-0.5 shr-text-base shr-font-bold',\n    monthButtons: 'smarthr-ui-Calendar-monthButtons shr-ms-auto shr-flex',\n    tableLayout: 'shr-relative',\n    yearSelectButton:\n      'smarthr-ui-Calendar-selectingYear [&[aria-expanded=\"true\"]_.smarthr-ui-Icon]:shr-rotate-180',\n  },\n})\n\nexport const Calendar = forwardRef<HTMLDivElement, Props>(\n  ({ from = minDate, to, onSelectDate, value, className, ...rest }, ref) => {\n    const { formatDate, getWeekStartDay } = useIntl()\n\n    const classNames = useMemo(() => {\n      const { container, yearMonth, header, monthButtons, tableLayout, yearSelectButton } =\n        classNameGenerator()\n\n      return {\n        container: container({ className }),\n        header: header(),\n        yearMonth: yearMonth(),\n        monthButtons: monthButtons(),\n        tableLayout: tableLayout(),\n        yearSelectButton: yearSelectButton(),\n      }\n    }, [className])\n\n    const formattedFrom = useMemo(() => {\n      const date = getFromDate(from)\n      const day = dayjs(date)\n\n      return {\n        day,\n        date,\n        year: day.year(),\n      }\n    }, [from])\n    const formattedTo = useMemo(() => {\n      const date = getToDate(to)\n      const day = dayjs(date)\n\n      return {\n        day,\n        date,\n        year: day.year(),\n      }\n    }, [to])\n\n    const isValidValue = useMemo(\n      () => value && isBetween(value, formattedFrom.date, formattedTo.date),\n      [value, formattedFrom.date, formattedTo.date],\n    )\n\n    const [currentMonth, setCurrentMonth] = useState(\n      (() => {\n        if (isValidValue) {\n          return dayjs(value)\n        }\n\n        const today = dayjs()\n\n        return formattedTo.day.isBefore(today)\n          ? formattedTo.day\n          : formattedFrom.day.isAfter(today)\n            ? formattedFrom.day\n            : today\n      })(),\n    )\n    const [isSelectingYear, setIsSelectingYear] = useState(false)\n\n    const yearPickerId = useId()\n\n    useEffect(() => {\n      if (isValidValue) {\n        setCurrentMonth(dayjs(value))\n      }\n    }, [value, isValidValue])\n\n    const calculatedCurrentMonth = useMemo(() => {\n      const d = currentMonth.toDate()\n\n      return {\n        prev: currentMonth.subtract(1, 'month'),\n        next: currentMonth.add(1, 'month'),\n        day: currentMonth,\n        months: getMonthArray(d, getWeekStartDay()),\n        yearMonthText: formatDate({\n          date: d,\n          parts: ['year', 'month'],\n          options: {\n            disableSlashInJa: true,\n            capitalizeFirstLetter: true,\n          },\n        }),\n        selectedText: currentMonth.toString(),\n      }\n    }, [currentMonth, formatDate, getWeekStartDay])\n\n    const onSelectYear = useCallback(\n      (e: MouseEvent<HTMLButtonElement>) => {\n        e.stopPropagation()\n        setCurrentMonth(currentMonth.year(parseInt(e.currentTarget.value, 10)))\n        setIsSelectingYear(false)\n      },\n      [currentMonth],\n    )\n\n    const onClickSelectYear = useCallback((e: MouseEvent<HTMLButtonElement>) => {\n      e.stopPropagation()\n      setIsSelectingYear((current) => !current)\n    }, [])\n\n    return (\n      <div {...rest} ref={ref} className={classNames.container}>\n        <header className={classNames.header}>\n          <YearMonthRender className={classNames.yearMonth}>\n            {calculatedCurrentMonth.yearMonthText}\n          </YearMonthRender>\n          <YearSelectButton\n            aria-expanded={isSelectingYear}\n            aria-controls={yearPickerId}\n            onClick={onClickSelectYear}\n            className={classNames.yearSelectButton}\n          />\n          <MonthDirectionCluster\n            isSelectingYear={isSelectingYear}\n            directionMonth={calculatedCurrentMonth}\n            from={formattedFrom.day}\n            to={formattedTo.day}\n            setCurrentMonth={setCurrentMonth}\n            className={classNames.monthButtons}\n          />\n        </header>\n        <div className={classNames.tableLayout}>\n          <YearPicker\n            fromYear={formattedFrom.year}\n            toYear={formattedTo.year}\n            selectedYear={value?.getFullYear()}\n            onSelectYear={onSelectYear}\n            isDisplayed={isSelectingYear}\n            id={yearPickerId}\n          />\n          <CalendarTable\n            current={calculatedCurrentMonth}\n            from={formattedFrom.date}\n            to={formattedTo.date}\n            onSelectDate={onSelectDate}\n            selectedDayText={isValidValue ? calculatedCurrentMonth.selectedText : ''}\n          />\n        </div>\n      </div>\n    )\n  },\n)\n\nconst YearMonthRender = memo<PropsWithChildren<{ className: string }>>(\n  ({ children, className }) => <div className={className}>{children}</div>,\n)\n\nconst YearSelectButton = memo<{\n  'aria-expanded': boolean\n  'aria-controls': string\n  onClick: (e: MouseEvent<HTMLButtonElement>) => void\n  className: string\n}>((props) => {\n  const { localize } = useIntl()\n  const selectYearAltText = useMemo(\n    () =>\n      localize({\n        id: 'smarthr-ui/Calendar/selectYear',\n        defaultText: '年を選択する',\n      }),\n    [localize],\n  )\n\n  return (\n    <Button {...props} size=\"S\">\n      <FaCaretDownIcon alt={selectYearAltText} />\n    </Button>\n  )\n})\n\nconst MonthDirectionCluster = memo<{\n  isSelectingYear: boolean\n  directionMonth: {\n    prev: DayJsType\n    next: DayJsType\n  }\n  from: DayJsType\n  to: DayJsType\n  setCurrentMonth: (day: DayJsType) => void\n  className: string\n}>(({ isSelectingYear, directionMonth: { prev, next }, from, to, setCurrentMonth, className }) => {\n  const { localize } = useIntl()\n  const onClickMonthPrev = useCallback(() => setCurrentMonth(prev), [prev, setCurrentMonth])\n  const onClickMonthNext = useCallback(() => setCurrentMonth(next), [next, setCurrentMonth])\n\n  const previousMonthAltText = useMemo(\n    () =>\n      localize({\n        id: 'smarthr-ui/Calendar/previousMonth',\n        defaultText: '前の月へ',\n      }),\n    [localize],\n  )\n\n  const nextMonthAltText = useMemo(\n    () =>\n      localize({\n        id: 'smarthr-ui/Calendar/nextMonth',\n        defaultText: '次の月へ',\n      }),\n    [localize],\n  )\n\n  return (\n    <Cluster gap={0.5} className={className}>\n      <Button\n        disabled={isSelectingYear || prev.isBefore(from, 'month')}\n        onClick={onClickMonthPrev}\n        size=\"S\"\n        className=\"smarthr-ui-Calendar-monthButtonPrev\"\n      >\n        <FaChevronLeftIcon alt={previousMonthAltText} />\n      </Button>\n      <Button\n        disabled={isSelectingYear || next.isAfter(to, 'month')}\n        onClick={onClickMonthNext}\n        size=\"S\"\n        className=\"smarthr-ui-Calendar-monthButtonNext\"\n      >\n        <FaChevronRightIcon alt={nextMonthAltText} />\n      </Button>\n    </Cluster>\n  )\n})\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA;AACE;AACE;AAEA;AACA;AACA;AACA;AACA;AAED;AACF;AAEM;;AAIH;AACE;;AAIE;;;;;;;AAOJ;AAEA;AACE;AACA;;;;AAKE;;AAEJ;AACA;AACE;AACA;;;;AAKE;;AAEJ;AAEA;;;AAQM;;AAGF;AAEA;;;;;;;AASJ;;;AAII;;AAEJ;AAEA;AACE;;;;AAKE;AACA;;AAEE;AACA;AACA;AACE;AACA;AACD;;AAEH;;;AAIJ;;AAGI;;AAEF;AAIF;;;;AAKA;AAwCF;AAGF;AAIA;AAME;;AAIM;AACA;AACD;AAIL;AAKF;AAEA;AAWE;AACA;AACA;;AAKM;AACA;AACD;;AAOC;AACA;AACD;AAIL;AAoBF;;"}