|
|
|
|
@ -1,172 +1,211 @@
|
|
|
|
|
# Minimal Calendar Component
|
|
|
|
|
# React Calendar Component
|
|
|
|
|
|
|
|
|
|
A flexible and customizable calendar component built with React, TypeScript, and styled-components.
|
|
|
|
|
A modern, flexible calendar component built with React, TypeScript, and SCSS modules.
|
|
|
|
|
|
|
|
|
|
## Demo
|
|
|
|
|
|
|
|
|
|
Check out the live demo at [calendar.code.meow.ch](https://calendar.code.meow.ch)
|
|
|
|
|
|
|
|
|
|
## Features
|
|
|
|
|
|
|
|
|
|
- 📅 Year, month, and week views
|
|
|
|
|
- 🎨 Multiple header styles (expanded, compacted, tiny, none)
|
|
|
|
|
- 🎯 Customizable month cutoff behavior
|
|
|
|
|
- 🎭 Weekend highlighting
|
|
|
|
|
- 📱 Fully responsive design
|
|
|
|
|
- 🔧 TypeScript support
|
|
|
|
|
- 💅 Styled with styled-components
|
|
|
|
|
- 📅 Multiple calendar views:
|
|
|
|
|
- Full year view with configurable month grid
|
|
|
|
|
- Single month view with customizable layout
|
|
|
|
|
- Week view with interactive options
|
|
|
|
|
- Date range picker with hover preview
|
|
|
|
|
- 🎨 Visual customization:
|
|
|
|
|
- Four header styles: expanded, compacted, tiny, and numeric
|
|
|
|
|
- Configurable day sizes (XS to XL)
|
|
|
|
|
- Adjustable font proportions
|
|
|
|
|
- Optional magnify effect for selected dates
|
|
|
|
|
- Weekend highlighting
|
|
|
|
|
- 🔧 Functional options:
|
|
|
|
|
- Month cutoff handling (dimmed, truncated, or show all)
|
|
|
|
|
- Interactive date range selection
|
|
|
|
|
- Hover states and preview
|
|
|
|
|
- Responsive design for all screen sizes
|
|
|
|
|
- 🛠 Technical features:
|
|
|
|
|
- Built with React + TypeScript
|
|
|
|
|
- SCSS modules for styling
|
|
|
|
|
- date-fns for date manipulation
|
|
|
|
|
- Zero external dependencies beyond core requirements
|
|
|
|
|
|
|
|
|
|
## Installation
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
npm install date-fns styled-components
|
|
|
|
|
npm install
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Development
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
npm run dev
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Basic Usage
|
|
|
|
|
## Usage Examples
|
|
|
|
|
|
|
|
|
|
### Date Range Picker
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
import React, { useState } from 'react';
|
|
|
|
|
import { Year } from './components/calendar/Year';
|
|
|
|
|
import { DateRange } from './types/calendar';
|
|
|
|
|
|
|
|
|
|
function DateRangePicker() {
|
|
|
|
|
const [dateRange, setDateRange] = useState<DateRange>({
|
|
|
|
|
startDate: null,
|
|
|
|
|
endDate: null,
|
|
|
|
|
selecting: false,
|
|
|
|
|
hoverDate: null,
|
|
|
|
|
anchorDate: null
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const handleDateSelect = (date: Date) => {
|
|
|
|
|
setDateRange(prev => {
|
|
|
|
|
if (!prev.selecting) {
|
|
|
|
|
return {
|
|
|
|
|
startDate: date,
|
|
|
|
|
endDate: date,
|
|
|
|
|
selecting: true,
|
|
|
|
|
hoverDate: date,
|
|
|
|
|
anchorDate: date
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
// Complete the selection
|
|
|
|
|
return {
|
|
|
|
|
startDate: prev.anchorDate,
|
|
|
|
|
endDate: date,
|
|
|
|
|
selecting: false,
|
|
|
|
|
hoverDate: null,
|
|
|
|
|
anchorDate: null
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function App() {
|
|
|
|
|
return (
|
|
|
|
|
<Year
|
|
|
|
|
year={2024}
|
|
|
|
|
dayHeaderStyle="tiny"
|
|
|
|
|
weekendDays={[6, 0]} // Saturday and Sunday
|
|
|
|
|
monthCutoff="truncate"
|
|
|
|
|
weekendDays={[6, 0]}
|
|
|
|
|
dateRange={dateRange}
|
|
|
|
|
onDateSelect={handleDateSelect}
|
|
|
|
|
onDateHover={(date) => setDateRange(prev => ({ ...prev, hoverDate: date }))}
|
|
|
|
|
size="l"
|
|
|
|
|
fontProportion={100}
|
|
|
|
|
magnify={true}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Advanced Usage
|
|
|
|
|
|
|
|
|
|
### With Date Selection
|
|
|
|
|
### Single Month View
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
import { useState } from 'react';
|
|
|
|
|
import { Year } from './components/calendar/Year';
|
|
|
|
|
|
|
|
|
|
function App() {
|
|
|
|
|
const [selectedDate, setSelectedDate] = useState<Date | undefined>();
|
|
|
|
|
import React from 'react';
|
|
|
|
|
import { Month } from './components/calendar/Month';
|
|
|
|
|
|
|
|
|
|
function SingleMonth() {
|
|
|
|
|
return (
|
|
|
|
|
<Year
|
|
|
|
|
year={2024}
|
|
|
|
|
<Month
|
|
|
|
|
date={new Date()}
|
|
|
|
|
dayHeaderStyle="expanded"
|
|
|
|
|
monthCutoff="truncate"
|
|
|
|
|
direction="column"
|
|
|
|
|
monthCutoff="dimmed"
|
|
|
|
|
weekendDays={[6, 0]}
|
|
|
|
|
selectedDate={selectedDate}
|
|
|
|
|
onDateSelect={setSelectedDate}
|
|
|
|
|
size="xl"
|
|
|
|
|
fontProportion={100}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### With Controls
|
|
|
|
|
### Week View
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
import { useState } from 'react';
|
|
|
|
|
import { Year } from './components/calendar/Year';
|
|
|
|
|
import { Controls } from './components/calendar/Controls';
|
|
|
|
|
import { HeaderStyle, MonthCutoffType } from './types/calendar';
|
|
|
|
|
|
|
|
|
|
function App() {
|
|
|
|
|
const [headerStyle, setHeaderStyle] = useState<HeaderStyle>('tiny');
|
|
|
|
|
const [monthCutoff, setMonthCutoff] = useState<MonthCutoffType>('truncate');
|
|
|
|
|
import React from 'react';
|
|
|
|
|
import { Week } from './components/calendar/Week';
|
|
|
|
|
|
|
|
|
|
function WeekView() {
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<Controls
|
|
|
|
|
headerStyle={headerStyle}
|
|
|
|
|
monthCutoff={monthCutoff}
|
|
|
|
|
onHeaderStyleChange={setHeaderStyle}
|
|
|
|
|
onMonthCutoffChange={setMonthCutoff}
|
|
|
|
|
/>
|
|
|
|
|
<Year
|
|
|
|
|
year={2024}
|
|
|
|
|
dayHeaderStyle={headerStyle}
|
|
|
|
|
monthCutoff={monthCutoff}
|
|
|
|
|
weekendDays={[6, 0]}
|
|
|
|
|
/>
|
|
|
|
|
</>
|
|
|
|
|
<Week
|
|
|
|
|
startDate={new Date()}
|
|
|
|
|
headerStyle="compacted"
|
|
|
|
|
referenceMonth={new Date().getMonth()}
|
|
|
|
|
weekendDays={[6, 0]}
|
|
|
|
|
size="l"
|
|
|
|
|
fontProportion={100}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Component Props
|
|
|
|
|
|
|
|
|
|
### Year Component
|
|
|
|
|
|
|
|
|
|
| Prop | Type | Description |
|
|
|
|
|
|------|------|-------------|
|
|
|
|
|
| year | number | The year to display |
|
|
|
|
|
| dayHeaderStyle | 'expanded' \| 'compacted' \| 'tiny' \| 'none' | Style of day headers |
|
|
|
|
|
| monthDayOfWeekHeaderStyle | HeaderStyle (optional) | Style of month day headers |
|
|
|
|
|
| monthCutoff | 'dimmed' \| 'truncate' \| undefined | How to handle days from other months |
|
|
|
|
|
| weekendDays | number[] | Array of day indices to mark as weekends (0-6) |
|
|
|
|
|
| selectedDate | Date (optional) | Currently selected date |
|
|
|
|
|
| rangeStart | Date (optional) | Start date for range selection |
|
|
|
|
|
| rangeEnd | Date (optional) | End date for range selection |
|
|
|
|
|
| onDateSelect | (date: Date) => void (optional) | Date selection callback |
|
|
|
|
|
| compact | boolean (optional) | Use compact layout |
|
|
|
|
|
|
|
|
|
|
### Controls Component
|
|
|
|
|
|
|
|
|
|
| Prop | Type | Description |
|
|
|
|
|
|------|------|-------------|
|
|
|
|
|
| headerStyle | HeaderStyle | Current header style |
|
|
|
|
|
| monthCutoff | MonthCutoffType | Current month cutoff type |
|
|
|
|
|
| onHeaderStyleChange | (type: HeaderStyle) => void | Header style change handler |
|
|
|
|
|
| onMonthCutoffChange | (type: MonthCutoffType) => void | Month cutoff change handler |
|
|
|
|
|
|
|
|
|
|
## Styling
|
|
|
|
|
|
|
|
|
|
The calendar uses styled-components for styling. You can customize the appearance by:
|
|
|
|
|
|
|
|
|
|
1. Using the built-in props
|
|
|
|
|
2. Extending the styled components
|
|
|
|
|
3. Wrapping components with custom styled containers
|
|
|
|
|
|
|
|
|
|
Example of custom styling:
|
|
|
|
|
### Compact Year View
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
import styled from 'styled-components';
|
|
|
|
|
import React from 'react';
|
|
|
|
|
import { Year } from './components/calendar/Year';
|
|
|
|
|
|
|
|
|
|
const CustomCalendarContainer = styled.div`
|
|
|
|
|
padding: 2rem;
|
|
|
|
|
background: #fafafa;
|
|
|
|
|
|
|
|
|
|
// Custom styles for the calendar
|
|
|
|
|
.month-title {
|
|
|
|
|
color: #1a73e8;
|
|
|
|
|
}
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
function App() {
|
|
|
|
|
function CompactYear() {
|
|
|
|
|
return (
|
|
|
|
|
<CustomCalendarContainer>
|
|
|
|
|
<Year
|
|
|
|
|
year={2024}
|
|
|
|
|
dayHeaderStyle="tiny"
|
|
|
|
|
/>
|
|
|
|
|
</CustomCalendarContainer>
|
|
|
|
|
<Year
|
|
|
|
|
year={2024}
|
|
|
|
|
dayHeaderStyle="tiny"
|
|
|
|
|
monthCutoff="truncate"
|
|
|
|
|
weekendDays={[6, 0]}
|
|
|
|
|
size="xs"
|
|
|
|
|
fontProportion={80}
|
|
|
|
|
magnify={true}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Header Styles
|
|
|
|
|
## Component Props
|
|
|
|
|
|
|
|
|
|
### Year Component
|
|
|
|
|
```typescript
|
|
|
|
|
interface YearProps {
|
|
|
|
|
year: number;
|
|
|
|
|
dayHeaderStyle: 'expanded' | 'compacted' | 'tiny' | 'none';
|
|
|
|
|
monthDayOfWeekHeaderStyle?: HeaderStyle;
|
|
|
|
|
monthCutoff?: 'dimmed' | 'truncate' | undefined;
|
|
|
|
|
weekendDays?: number[];
|
|
|
|
|
dateRange?: DateRange;
|
|
|
|
|
onDateSelect?: (date: Date) => void;
|
|
|
|
|
onDateHover?: (date: Date) => void;
|
|
|
|
|
size?: 'xl' | 'l' | 'm' | 's' | 'xs';
|
|
|
|
|
fontProportion?: number;
|
|
|
|
|
magnify?: boolean;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- **expanded**: Full day names (e.g., "MONDAY")
|
|
|
|
|
- **compacted**: Three-letter day names (e.g., "MON")
|
|
|
|
|
- **tiny**: Single letter day names (e.g., "M")
|
|
|
|
|
- **none**: No day headers, only numbers
|
|
|
|
|
### Month Component
|
|
|
|
|
```typescript
|
|
|
|
|
interface MonthProps {
|
|
|
|
|
date: Date;
|
|
|
|
|
dayHeaderStyle: HeaderStyle;
|
|
|
|
|
direction: 'row' | 'column';
|
|
|
|
|
monthCutoff?: MonthCutoffType;
|
|
|
|
|
weekendDays?: number[];
|
|
|
|
|
dateRange?: DateRange;
|
|
|
|
|
onDateSelect?: (date: Date) => void;
|
|
|
|
|
onDateHover?: (date: Date) => void;
|
|
|
|
|
size?: DaySize;
|
|
|
|
|
fontProportion?: number;
|
|
|
|
|
magnify?: boolean;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Month Cutoff Options
|
|
|
|
|
## Styling
|
|
|
|
|
|
|
|
|
|
- **dimmed**: Show days from other months with reduced opacity
|
|
|
|
|
- **truncate**: Hide days from other months
|
|
|
|
|
- **undefined**: Show all days normally
|
|
|
|
|
The component uses SCSS modules for styling. Key style files:
|
|
|
|
|
|
|
|
|
|
## Browser Support
|
|
|
|
|
- `src/components/calendar/shared/_colors.scss`: Color variables
|
|
|
|
|
- `src/components/calendar/shared/_variables.scss`: Layout variables
|
|
|
|
|
- Individual component SCSS modules for specific styling
|
|
|
|
|
|
|
|
|
|
The calendar component supports all modern browsers:
|
|
|
|
|
## Browser Support
|
|
|
|
|
|
|
|
|
|
- Chrome (latest)
|
|
|
|
|
- Firefox (latest)
|
|
|
|
|
|