@tugkanpilka/calendar
v1.0.0
Published
A highly modular, infinite scrolling calendar component built with React and TypeScript
Maintainers
Readme
@docbook/calendar
A highly modular, infinite scrolling calendar component built with React and TypeScript. Follows SOLID principles and is designed to be published as an NPM package.
Features
- Infinite Scrolling: Built-in support for infinite scrolling with IntersectionObserver
- Render Props: Maximum customization through render props pattern
- Theme Support: CSS variables for easy theming
- TypeScript: Fully typed with comprehensive type definitions
- Performance: Optimized with React.memo and efficient rendering
- Modular: Clean architecture with separated concerns
- Zero Dependencies: No internal component dependencies, pure React
Installation
npm install @docbook/calendar
# or
yarn add @docbook/calendarPeer Dependencies
{
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0",
"date-range-utils": "^1.0.16",
"date-fns": "^4.1.0"
}Basic Usage
import { Calendar } from '@docbook/calendar';
import type { DateInfo, MonthInfo } from 'date-range-utils';
import { DateRange, StandardDateGenerationStrategy, MonthGroupingStrategy } from 'date-range-utils';
function MyCalendar() {
const months: MonthInfo<DateInfo>[] = useMemo(() => {
const dateFactory = (date: Date): DateInfo => ({
date,
weekNumber: getWeek(date),
});
return new DateRange<DateInfo, MonthInfo<DateInfo>>(
new Date(2025, 0, 1),
new Date(2025, 11, 31),
)
.create(new StandardDateGenerationStrategy(dateFactory))
.group(new MonthGroupingStrategy());
}, []);
const renderDay = (dateInfo: DateInfo) => {
return (
<div>
{dateInfo.date.getDate()}
</div>
);
};
return (
<Calendar
months={months}
renderDay={renderDay}
/>
);
}Infinite Scrolling
The Calendar component supports infinite scrolling out of the box:
<Calendar
months={months}
renderDay={renderDay}
onLoadMore={async () => {
// Load more months
await loadMoreMonths();
}}
hasMore={hasMoreMonths}
isLoading={isLoading}
/>Custom Rendering
All components support render props for maximum customization:
<Calendar
months={months}
renderDay={(dateInfo) => (
<Day
dateInfo={dateInfo}
onDateClick={handleDateClick}
renderDate={(dayNumber) => (
<span className="custom-day">{dayNumber}</span>
)}
renderIndicator={({ isDotted, dotType }) => (
isDotted ? <CustomIndicator type={dotType} /> : null
)}
/>
)}
/>Components
Calendar
Main orchestrator component.
Props:
months: Array of month information (required)renderDay: Function to render day cells (required)renderWeekNumber: Function to render week numbers (optional)renderMonthHeader: Function to render month headers (optional)showWeekNumbers: Whether to show week numbers (default: true)onLoadMore: Infinite scroll callback (optional)hasMore: Whether there are more items to load (optional)isLoading: Whether currently loading (optional)className: Additional CSS class name (optional)headerClassName: Additional CSS class for header (optional)bodyClassName: Additional CSS class for body (optional)
Day
Day cell component with render props support.
Props:
dateInfo: Date informationonDateClick: Callback when date is clickedonClick: Additional click handlerisSelected: Whether the day is selectedisRounded: Whether the day has rounded cornersisMarked: Whether the day is marked (e.g., today)isDotted: Whether the day has a dot indicatordotType: Type of dot indicator ('primary' | 'secondary')renderDate: Custom render function for date displayrenderIndicator: Custom render function for indicatorclassName: Additional CSS class name
Week
Week number component with render props support.
Props:
text: Week number text (e.g., "W1")isMultiMonth: Whether the week spans multiple monthsonClick: Click handlerisDotted: Whether the week has a dot indicatorisFocused: Whether the week is focuseddotType: Type of dot indicatorrenderText: Custom render function for text displayrenderIndicator: Custom render function for indicatorclassName: Additional CSS class name
MonthHeader
Month header component with render props support.
Props:
text: Month name textonClick: Click handlerisFocused: Whether the month is focusedisDotted: Whether the month has a dot indicatordotType: Type of dot indicatorrenderText: Custom render function for text displayrenderIndicator: Custom render function for indicatorclassName: Additional CSS class name
Utilities
Date Formatting
import { formatCalendarDate } from '@docbook/calendar';
const dayString = formatCalendarDate.day(new Date()); // "2025-01-15"
const weekString = formatCalendarDate.week(new Date(), 3); // "2025-W3"
const monthString = formatCalendarDate.month(2025, "January"); // "2025-M1"Grid Utilities
import { isWeekNumberCell, calculateGridIndex } from '@docbook/calendar';
const isWeekNumber = isWeekNumberCell(7); // true (index 7 is week number column)
const index = calculateGridIndex(2, 3, 8); // 19 (row 2, col 3, 8 columns)Scroll Utilities
import { scrollToDate, findDateElement } from '@docbook/calendar';
await scrollToDate('2025-01-15', containerRef.current);
const element = findDateElement('2025-01-15', containerRef.current);Hooks
useCalendarItems
Generic hook to generate props for calendar components.
import { useCalendarItems } from '@docbook/calendar';
const { getDayProps, getWeekNumberProps, getMonthHeaderProps } = useCalendarItems({
currentDate: '2025-01-15',
setCurrentDate: (date) => console.log(date),
getIndicatorProps: (formattedDate) => ({
isDotted: true,
dotType: 'primary',
}),
});useInfiniteScroll
Hook for infinite scrolling support.
import { useInfiniteScroll } from '@docbook/calendar';
const { sentinelRef } = useInfiniteScroll({
hasMore: true,
isLoading: false,
onLoadMore: async () => {
await loadMore();
},
scrollRef: containerRef,
});useScrollToDate
Hook for scrolling to a specific date.
import { useScrollToDate } from '@docbook/calendar';
const { scrollTo, findElement } = useScrollToDate({
scrollRef: containerRef,
});
await scrollTo('2025-01-15');Theming
The component uses CSS variables for theming. Override them in your CSS:
:root {
--calendar-bg-color: #F5F6F8;
--day-cell-height: 80px;
--day-cell-marked-bg: #ef4444;
--indicator-primary-color: #ef4444;
/* ... more variables */
}TypeScript
All components and utilities are fully typed. Import types as needed:
import type {
CalendarProps,
DayProps,
WeekProps,
MonthHeaderProps,
} from '@docbook/calendar';Styling
The component uses CSS modules. Import the compiled CSS:
import '@docbook/calendar/dist/styles.css';Or use CSS modules in your build configuration.
Migration from Monorepo
If you're migrating from the monorepo version:
- Replace
@shared/ui/organisms/Calendarimports with@docbook/calendar - Remove
@shared/stylesSCSS imports (variables are now included) - Update any internal component dependencies (now using render props)
- Ensure peer dependencies are installed
License
MIT
Contributing
Contributions are welcome! Please read our contributing guidelines first.
