🔥

【React/Bootstrap】シンプルなオリジナルカレンダーを作る!

2024/07/24に公開
1

reactでカレンダー機能が必要になり色々と探していたんですが、いまいちしっくりくるカレンダーが見つからなかったので、シンプルなオリジナルのカレンダーを自作しました。
なおスタイルはbootstrapで実装。

作成

まずはメインから。

app/calendar_main/page.tsx
    "use client";
    import React,{useState } from 'react';
    import  Calendar  from '../calendar/page';
    import 'bootstrap/dist/css/bootstrap.min.css';
    
    
    export default function Main() {
    const getTodayDate = () => {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, '0'); // Months are zero-indexed
    const day = String(today.getDate()).padStart(2, '0');
    return `${year}/${month}/${day}`;
	};
	
	const [day, setDay] = useState(getTodayDate());
	const [calendar, setCalendar] = useState(false);
	
	const handleDateChange = (newDate:any) => {
		setDay(newDate);
		setCalendar(false); // カレンダーを閉じる
	};
	
  return (
		<>
		<div className="mt-5 p-3 mb-5">
		<form>
		<h6 className="mb-4 mt-1 text-center fw-bold">カレンダーアイコンをクリックしてね</h6>
		<div className="input-group">
		<input type="text" className="form-control" id="autoSizingInputGroup" value={day} onChange={(e) => setDay(e.target.value)} readOnly onFocus={(e) => e.target.blur()}/>
		<div className="input-group-text" onClick={() => setCalendar(!calendar)} >
		<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-calendar-week" viewBox="0 0 16 16"><path d="M11 6.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1zm-3 0a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1zm-5 3a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1zm3 0a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1z"/><path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/></svg>
		</div>
		</div>
		
		{calendar == true && (<div className="p-3 mt-3 mb-3" style={{background:"#F4F4F4",border:"1px solid #e1e1e1",borderRadius:"5px"}}>
		<Calendar onDateSelect={handleDateChange} />
		</div>) }
		
		</form>
		</div>
		</>
  );
}

次にCalendarコンポーネントはこちら

app/calendar/page.tsx
    "use client";
    import React, { useState } from 'react';
    
    interface CalendarProps {
      onDateSelect: (date: string) => void;
    }
    
    const Calendar: React.FC<CalendarProps> = ({ onDateSelect }) => {
  const [currentDate, setCurrentDate] = useState(new Date());

  const renderHeader = () => {
    const today = new Date();
    const canNavigatePast = currentDate.getMonth() !== today.getMonth() || currentDate.getFullYear() !== today.getFullYear();

    return (
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
        <div
          onClick={prevMonth}
          style={{ cursor: canNavigatePast ? 'pointer' : 'not-allowed', opacity: canNavigatePast ? 1 : 0.5 }}
        >
          &lt;
        </div>
        <div>
          <span>{currentDate.toLocaleDateString('ja-JP', { month: 'long', year: 'numeric' })}</span>
        </div>
        <div onClick={nextMonth} style={{cursor:"pointer"}}>
          &gt;
        </div>
      </div>
    );
  };

  const renderDays = () => {
    const days = ['日', '月', '火', '水', '木', '金', '土'];
    return (
      <thead>
        <tr>
          {days.map((day, index) => (
            <th key={index} style={{ padding: '0.5rem', backgroundColor: '#f4f4f4', textAlign: 'center' }}>
              {day}
            </th>
          ))}
        </tr>
      </thead>
    );
  };

  const renderCells = () => {
    const today = new Date();
    const monthStart = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
    const monthEnd = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0);
    const startDate = new Date(monthStart);
    startDate.setDate(startDate.getDate() - startDate.getDay());

    const rows = [];
    let days = [];
    let day = new Date(startDate);

    while (day <= monthEnd) {
      for (let i = 0; i < 7; i++) {
        const formattedDate = day.getDate();
        const cloneDay = new Date(day);

        let cellStyle: React.CSSProperties = {
          textAlign: 'center',
          cursor: 'pointer',
          padding: '0.5rem',
        };

        if (day.getMonth() !== currentDate.getMonth()) {
          cellStyle.color = '#ccc'; // Different month dates color
          cellStyle.visibility = 'hidden'; // Hide dates from other months
        } else if (day.toDateString() === today.toDateString()) {
          cellStyle.color = 'red'; // Today's date color
          cellStyle.fontWeight = 'bold'; // Today's date bold
        } else if (day < today || (currentDate.getMonth() === today.getMonth() && day.getDate() < today.getDate())) {
          cellStyle.color = '#ccc'; // Past dates color
          cellStyle.pointerEvents = 'none'; // Disable clicking past dates
        }

        days.push(
          <td
            key={day.toISOString()}
            style={cellStyle}
            onClick={() => onDateClick(cloneDay)}
          >
            <span>{formattedDate}</span>
          </td>
        );

        day.setDate(day.getDate() + 1);
      }

      rows.push(
        <tr key={day.toISOString()}>
          {days}
        </tr>
      );
      days = [];
    }

    return <tbody>{rows}</tbody>;
  };

  const nextMonth = () => {
    setCurrentDate(new Date(currentDate.setMonth(currentDate.getMonth() + 1)));
  };

  const prevMonth = () => {
    if (!canNavigatePast()) return;
    setCurrentDate(new Date(currentDate.setMonth(currentDate.getMonth() - 1)));
  };

  const onDateClick = (day: Date) => {
    const formattedDate = day.toLocaleDateString('ja-JP', {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
    });

    onDateSelect(formattedDate); // Pass date to parent component
  };

  const canNavigatePast = () => {
    const today = new Date();
    return currentDate.getMonth() !== today.getMonth() || currentDate.getFullYear() !== today.getFullYear();
  };

  return (
    <div style={{ width: '100%' }}>
      {renderHeader()}
      <table style={{ width: '100%', borderCollapse: 'collapse' }}>
        {renderDays()}
        {renderCells()}
      </table>
    </div>
  );
};

    export default Calendar;

これで下記のように、本日の日付とカレンダーアイコンが表示されアイコンクリックで下にカレンダーが表示されるはず。

以上ー

Discussion

nap5nap5

ぼくもカレンダー作成にチャレンジしてみました