🔥
【React/Bootstrap】シンプルなオリジナルカレンダーを作る!
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 }}
>
<
</div>
<div>
<span>{currentDate.toLocaleDateString('ja-JP', { month: 'long', year: 'numeric' })}</span>
</div>
<div onClick={nextMonth} style={{cursor:"pointer"}}>
>
</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
ぼくもカレンダー作成にチャレンジしてみました