feat: today special

master
Guillermo Pages 4 months ago
parent 88955f095e
commit 8611d080ae

@ -2,10 +2,12 @@
A modern, flexible, and highly customizable calendar component library for React with TypeScript support.
## 🆕 Version 1.1.0 Features
## 🆕 Version 1.3.0 Features
- 📅 **Today State in Color Schemes**: Define custom colors for today's date through color schemes
- ✏️ **Header Text Transform**: Custom function to transform day name display (e.g., `dayName => dayName.slice(0, 2)`)
- 🌍 **International Locale Support**: Full internationalization with 50+ languages via date-fns
- 🎨 **Comprehensive Color Schemes**: Complete control over colors for every state (default, selecting, selected, range start/end/mid, active) and day type (weekday/weekend)
- 🎨 **Comprehensive Color Schemes**: Complete control over colors for every state (default, today, selecting, selected, range start/end/mid, active) and day type (weekday/weekend)
- 🎯 **Enhanced Weekend Support**: Distinct colors for weekends in all selection states
📖 **[View the complete Usage Guide](./USAGE_GUIDE.md)** for detailed examples of the new features!

@ -1,5 +1,47 @@
# Calendar Component Usage Guide
## ✏️ Header Text Transform (v1.2.0+)
Customize how day names are displayed with a transform function:
```tsx
import { Month } from 'react-calendario';
// Example 1: Two-letter abbreviations
<Month
date={new Date()}
headerStyle="compacted" // Shows "MON", "TUE", etc.
headerTextTransform={(dayName) => dayName.slice(0, 2)} // Returns "MO", "TU", etc.
/>
// Example 2: Custom mapping
const customDayNames = (dayName: string) => {
const map: Record<string, string> = {
'MON': 'Mo', 'TUE': 'Tu', 'WED': 'We',
'THU': 'Th', 'FRI': 'Fr', 'SAT': 'Sa', 'SUN': 'Su'
};
return map[dayName] || dayName;
};
<Month
date={new Date()}
headerStyle="compacted"
headerTextTransform={customDayNames}
/>
// Example 3: Lowercase
<Month
date={new Date()}
headerStyle="tiny"
headerTextTransform={(dayName) => dayName.toLowerCase()}
/>
```
The `headerTextTransform` function:
- Receives the day name after `headerStyle` processing
- Can return any string for display
- Works with all components: Year, Month, Week, DateRange, Day
## 🎨 Custom Color Schemes
### Basic Active Colors (Simple)
@ -29,6 +71,23 @@ Full control over colors for every state and day type:
import { Year, ColorScheme } from 'react-calendario';
const colorScheme: ColorScheme = [
// Today's date colors (highest priority)
{
type: 'weekday',
state: 'today', // Today if it's a weekday
colors: {
header: { backgroundColor: '#ffd700', color: '#000000' },
content: { backgroundColor: '#fff9e6', color: '#000000' }
}
},
{
type: 'weekend',
state: 'today', // Today if it's a weekend
colors: {
header: { backgroundColor: '#ffb347', color: '#000000' },
content: { backgroundColor: '#fff5e6', color: '#000000' }
}
},
// Weekday colors
{
type: 'weekday',
@ -83,6 +142,7 @@ const colorScheme: ColorScheme = [
#### Available States:
- `default` - Normal day appearance
- `today` - Today's date (automatically detected, highest priority)
- `selected` - Day is part of a completed selection (deprecated, use `rangeMid`)
- `selecting` - Day is being hovered during selection (important for weekends!)
- `rangeStart` - First day of a range

@ -1,6 +1,6 @@
{
"name": "react-calendario",
"version": "1.1.1",
"version": "1.3.1",
"description": "A modern, flexible calendar component for React with TypeScript support",
"type": "module",
"main": "dist/index.umd.js",

@ -21,6 +21,7 @@ interface DateRangeProps {
activeColors?: ActiveColors;
colorScheme?: ColorScheme;
locale?: Locale;
headerTextTransform?: (dayName: string) => string;
}
export const DateRange: React.FC<DateRangeProps> = ({
@ -37,7 +38,8 @@ export const DateRange: React.FC<DateRangeProps> = ({
activeDates = [],
activeColors,
colorScheme,
locale
locale,
headerTextTransform
}) => {
const startDate = startOfDay(from);
const endDate = startOfDay(to);
@ -95,6 +97,7 @@ export const DateRange: React.FC<DateRangeProps> = ({
activeColors={activeColors}
colorScheme={colorScheme}
locale={locale}
headerTextTransform={headerTextTransform}
/>
</div>
);

@ -190,6 +190,12 @@
}
// end implicit (midrange) {
// today state - no default styling, handled by color scheme
&.Day__Container--today {
// Today state can combine with other states
// Styling is handled through the color scheme
}
// active state - inverted colors
&.Day__Container--active {
background: colors.$day-header-defaultmode-defaultstate-background-color;

@ -19,7 +19,8 @@ export const Day: React.FC<DayProps> = ({
magnify = false,
activeColors,
colorScheme,
locale
locale,
headerTextTransform
}) => {
const isInteractive = Boolean(onSelect || onHover);
@ -45,6 +46,7 @@ export const Day: React.FC<DayProps> = ({
[styles['Day__Container--rowStart']]: variations.includes('rowStart'),
[styles['Day__Container--rowEnd']]: variations.includes('rowEnd'),
[styles['Day__Container--active']]: variations.includes('active'),
[styles['Day__Container--today']]: variations.includes('today'),
[styles['magnify']]: magnify && (variations.includes('selected') || variations.includes('selecting'))
},
styles[`Day--${headerStyle}`],
@ -103,7 +105,7 @@ export const Day: React.FC<DayProps> = ({
{headerStyle !== 'none' && (
<div className={headerClasses} style={headerCustomStyles}>
<div className={styles.Day__HeaderText}>
{getDayLabel(date, headerStyle, locale)}
{getDayLabel(date, headerStyle, locale, headerTextTransform)}
</div>
</div>
)}

@ -22,7 +22,8 @@ export const Month: React.FC<MonthProps> = ({
activeColors,
activeDates,
colorScheme,
locale = enUS
locale = enUS,
headerTextTransform
}) => {
const monthStart = startOfMonth(date);
const start = startOfWeek(monthStart, { weekStartsOn: 1 });
@ -54,6 +55,7 @@ export const Month: React.FC<MonthProps> = ({
activeDates={activeDates}
colorScheme={colorScheme}
locale={locale}
headerTextTransform={headerTextTransform}
/>
);
current = addWeeks(current, 1);

@ -21,7 +21,8 @@ export const Week: React.FC<WeekProps> = ({
activeColors,
activeDates = [],
colorScheme,
locale
locale,
headerTextTransform
}) => {
const allDays = Array.from({ length: 7 }, (_, i) => {
const date = addDays(startDate, i);
@ -65,6 +66,7 @@ export const Week: React.FC<WeekProps> = ({
activeColors={activeColors}
colorScheme={colorScheme}
locale={locale}
headerTextTransform={headerTextTransform}
/>
</div>
);
@ -84,6 +86,7 @@ export const Week: React.FC<WeekProps> = ({
activeColors={activeColors}
colorScheme={colorScheme}
locale={locale}
headerTextTransform={headerTextTransform}
/>
</div>
);

@ -18,7 +18,8 @@ export const Year: React.FC<YearProps> = ({
activeColors,
activeDates,
colorScheme,
locale
locale,
headerTextTransform
}) => {
const months = Array.from({ length: 12 }, (_, index) => {
const monthDate = new Date(year, index, 1);
@ -41,6 +42,7 @@ export const Year: React.FC<YearProps> = ({
activeDates={activeDates}
colorScheme={colorScheme}
locale={locale}
headerTextTransform={headerTextTransform}
/>
);
});

@ -30,15 +30,18 @@ export const getMinWidthByHeaderStyle = (headerStyle: HeaderStyle) => {
return `min-width: ${baseWidths[headerStyle]};`;
};
export const getDayLabel = (date: Date, headerStyle: HeaderStyle, locale: Locale = enUS): string => {
export const getDayLabel = (date: Date, headerStyle: HeaderStyle, locale: Locale = enUS, headerTextTransform?: (dayName: string) => string): string => {
const day = format(date, 'EEEE', { locale });
let result: string;
switch (headerStyle) {
case 'expanded':
return day.toUpperCase();
result = day.toUpperCase();
break;
case 'compacted':
// Use EEE format for proper 3-letter abbreviation in the locale
return format(date, 'EEE', { locale }).toUpperCase();
result = format(date, 'EEE', { locale }).toUpperCase();
break;
case 'tiny':
// Use EEEEE format for single letter abbreviation when available
// Fallback to first letter for better locale support
@ -47,14 +50,29 @@ export const getDayLabel = (date: Date, headerStyle: HeaderStyle, locale: Locale
// For English, handle special cases (Thursday/Tuesday, Saturday/Sunday)
if (locale.code === 'en-US' || locale.code === 'en') {
const dayName = format(date, 'EEEE', { locale });
if (dayName === 'Thursday') return 'T';
if (dayName === 'Tuesday') return 't';
if (dayName === 'Saturday') return 's';
if (dayName === 'Sunday') return 'S';
if (dayName === 'Thursday') {
result = 'T';
} else if (dayName === 'Tuesday') {
result = 't';
} else if (dayName === 'Saturday') {
result = 's';
} else if (dayName === 'Sunday') {
result = 'S';
} else {
result = singleLetter.toUpperCase();
}
} else {
result = singleLetter.toUpperCase();
}
return singleLetter.toUpperCase();
break;
default:
return day;
result = day;
}
// Apply custom transform if provided
if (headerTextTransform) {
return headerTextTransform(result);
}
return result;
};

@ -1,10 +1,14 @@
import React, { useState } from 'react';
import { DateRange } from '../components/calendar/DateRange';
import { ActiveColors } from '../types/calendar';
import { format, startOfDay } from 'date-fns';
import styles from './Examples.module.scss';
export const DateRangeWithActiveColors: React.FC = () => {
const [colorScheme, setColorScheme] = useState<'default' | 'green' | 'purple' | 'orange'>('default');
const [activeDates, setActiveDates] = useState<Date[]>([]);
const [lastClicked, setLastClicked] = useState<string>('');
const [headerMode, setHeaderMode] = useState<'normal' | 'short' | 'twoLetter'>('normal');
const fromDate = new Date(2025, 0, 15); // January 15, 2025
const toDate = new Date(2025, 0, 20); // January 20, 2025
@ -31,11 +35,62 @@ export const DateRangeWithActiveColors: React.FC = () => {
}
};
const handleDateClick = (date: Date) => {
const dateString = format(date, 'MMMM d, yyyy');
setLastClicked(dateString);
setActiveDates(prev => {
const dateTime = startOfDay(date).getTime();
const existingIndex = prev.findIndex(d =>
startOfDay(d).getTime() === dateTime
);
if (existingIndex >= 0) {
return prev.filter((_, index) => index !== existingIndex);
} else {
return [...prev, date];
}
});
};
// Header text transform functions
const headerTransforms = {
normal: undefined,
short: (dayName: string) => dayName.slice(0, 2),
twoLetter: (dayName: string) => {
// Custom logic: Mo, Tu, We, Th, Fr, Sa, Su
const customMap: Record<string, string> = {
'MONDAY': 'Mo',
'TUESDAY': 'Tu',
'WEDNESDAY': 'We',
'THURSDAY': 'Th',
'FRIDAY': 'Fr',
'SATURDAY': 'Sa',
'SUNDAY': 'Su',
'MON': 'Mo',
'TUE': 'Tu',
'WED': 'We',
'THU': 'Th',
'FRI': 'Fr',
'SAT': 'Sa',
'SUN': 'Su',
'M': 'Mo',
'T': 'Tu',
'W': 'We',
't': 'Th',
'F': 'Fr',
's': 'Sa',
'S': 'Su'
};
return customMap[dayName] || dayName.slice(0, 2);
}
};
return (
<section className={styles.section}>
<h2 className={styles.sectionTitle}>Date Range with Active Colors</h2>
<h2 className={styles.sectionTitle}>Date Range with Active Colors & Header Transform</h2>
<p className={styles.sectionDescription}>
Customize the colors of selected date ranges using the activeColors prop.
Customize colors and header text display using activeColors and headerTextTransform props.
</p>
<div className={styles.controlRow}>
@ -52,28 +107,42 @@ export const DateRangeWithActiveColors: React.FC = () => {
<option value="orange">Orange</option>
</select>
</label>
<label className={styles.control} style={{ marginLeft: '20px' }}>
<strong>Header Transform:</strong>
<select
value={headerMode}
onChange={(e) => setHeaderMode(e.target.value as any)}
style={{ marginLeft: '8px', padding: '4px 8px' }}
>
<option value="normal">Normal (default)</option>
<option value="short">First 2 letters</option>
<option value="twoLetter">Custom 2-letter (Mo, Tu, We...)</option>
</select>
</label>
</div>
<div className={styles.info}>
<p>Last clicked: {lastClicked || 'None'}</p>
<p>Active dates: {activeDates.length > 0
? activeDates.map(d => format(d, 'MMM d')).join(', ')
: 'None (click dates to toggle)'}</p>
</div>
<div className={styles.demoContainer}>
<h3>Before (Default Colors)</h3>
<DateRange
from={fromDate}
to={toDate}
selected={true}
headerStyle="tiny"
size="l"
magnify={true}
/>
<h3 style={{ marginTop: '2rem' }}>After (Custom Active Colors)</h3>
<h3>Interactive Date Range with Custom Colors & Headers</h3>
<DateRange
from={fromDate}
to={toDate}
selected={true}
headerStyle="tiny"
headerStyle="compacted"
size="l"
fontProportion={100}
magnify={true}
onDateClick={handleDateClick}
activeDates={activeDates}
activeColors={colorSchemes[colorScheme]}
headerTextTransform={headerTransforms[headerMode]}
/>
</div>
@ -82,21 +151,35 @@ export const DateRangeWithActiveColors: React.FC = () => {
from={new Date(2025, 0, 15)}
to={new Date(2025, 0, 20)}
selected={true}
headerStyle="tiny"
headerStyle="compacted"
size="l"
magnify={true}${colorScheme !== 'default' ? `
magnify={true}
onDateClick={handleDateClick}
activeDates={activeDates}${colorScheme !== 'default' ? `
activeColors={{
headerBg: '${colorSchemes[colorScheme]?.headerBg}',
headerColor: '${colorSchemes[colorScheme]?.headerColor}',
contentBg: '${colorSchemes[colorScheme]?.contentBg}',
contentColor: '${colorSchemes[colorScheme]?.contentColor}'
}}` : ''}
}}` : ''}${headerMode !== 'normal' ? `
headerTextTransform={${headerMode === 'short'
? '(dayName) => dayName.slice(0, 2)'
: '(dayName) => customTwoLetterMap[dayName]'}}` : ''}
/>`}</pre>
</div>
<div className={styles.info}>
<h4>ActiveColors Properties:</h4>
<ul>
<div className={styles.info} style={{ marginTop: '20px', padding: '15px', background: '#f9f9f9', borderRadius: '8px' }}>
<h4 style={{ margin: '0 0 10px 0' }}>📝 New Feature: headerTextTransform</h4>
<ul style={{ margin: '10px 0', paddingLeft: '20px' }}>
<li>The <code>headerTextTransform</code> prop accepts a function: <code>(dayName: string) => string</code></li>
<li>It receives the day name after headerStyle processing (e.g., "MON" for compacted)</li>
<li>You can return any string to display in the header</li>
<li>Great for custom abbreviations or localization needs</li>
<li>Example: <code>dayName => dayName.slice(0, 2)</code> for 2-letter abbreviations</li>
</ul>
<h4 style={{ margin: '20px 0 10px 0' }}>🎨 ActiveColors Properties:</h4>
<ul style={{ margin: '10px 0', paddingLeft: '20px' }}>
<li><code>headerBg</code>: Background color for the day header</li>
<li><code>headerColor</code>: Text color for the day header</li>
<li><code>contentBg</code>: Background color for the day content (number)</li>

@ -19,8 +19,10 @@ export const InteractiveDateRange: React.FC = () => {
const [useColorScheme, setUseColorScheme] = useState<boolean>(false);
const [colorTheme, setColorTheme] = useState<'ocean' | 'sunset' | 'forest'>('ocean');
const fromDate = new Date(2025, 0, 10); // January 10, 2025
const toDate = new Date(2025, 0, 25); // January 25, 2025
// Use today's date in the range to showcase the 'today' styling
const today = new Date();
const fromDate = new Date(today.getFullYear(), today.getMonth(), Math.max(1, today.getDate() - 5));
const toDate = new Date(today.getFullYear(), today.getMonth(), Math.min(28, today.getDate() + 10));
// Locale options
const locales: Record<string, { locale: Locale; name: string }> = {
@ -34,9 +36,18 @@ export const InteractiveDateRange: React.FC = () => {
zhCN: { locale: zhCN, name: '中文' }
};
// Color schemes with proper weekend support
// Color schemes with proper weekend and today support
const colorSchemes: Record<string, ColorScheme> = {
ocean: [
// Today state (highest priority)
{ type: 'weekday', state: 'today', colors: {
header: { backgroundColor: '#ffd700', color: '#000000' },
content: { backgroundColor: '#fff9e6', color: '#000000' }
}},
{ type: 'weekend', state: 'today', colors: {
header: { backgroundColor: '#ffb347', color: '#000000' },
content: { backgroundColor: '#fff5e6', color: '#000000' }
}},
// Weekday states
{ type: 'weekday', state: 'default', colors: {
header: { backgroundColor: '#006994', color: '#ffffff' },
@ -89,6 +100,15 @@ export const InteractiveDateRange: React.FC = () => {
}}
],
sunset: [
// Today state
{ type: 'weekday', state: 'today', colors: {
header: { backgroundColor: '#ff1744', color: '#ffffff' },
content: { backgroundColor: '#ffebee', color: '#c62828' }
}},
{ type: 'weekend', state: 'today', colors: {
header: { backgroundColor: '#ff6b6b', color: '#ffffff' },
content: { backgroundColor: '#ffe0e0', color: '#d32f2f' }
}},
// Weekday states
{ type: 'weekday', state: 'default', colors: {
header: { backgroundColor: '#ff6b35', color: '#ffffff' },
@ -141,6 +161,15 @@ export const InteractiveDateRange: React.FC = () => {
}}
],
forest: [
// Today state
{ type: 'weekday', state: 'today', colors: {
header: { backgroundColor: '#66bb6a', color: '#ffffff' },
content: { backgroundColor: '#e8f5e9', color: '#2e7d32' }
}},
{ type: 'weekend', state: 'today', colors: {
header: { backgroundColor: '#81c784', color: '#ffffff' },
content: { backgroundColor: '#f1f8e9', color: '#33691e' }
}},
// Weekday states
{ type: 'weekday', state: 'default', colors: {
header: { backgroundColor: '#2d6a4f', color: '#ffffff' },
@ -325,8 +354,8 @@ export const InteractiveDateRange: React.FC = () => {
</div>
<div className={styles.codeExample}>
<pre>{`<DateRange
from={new Date(2025, 0, 10)}
to={new Date(2025, 0, 25)}
from={fromDate} // Includes today's date
to={toDate}
included={${included}}
selected={${selected}}
headerStyle="${dayHeaderStyle}"
@ -352,13 +381,14 @@ export const InteractiveDateRange: React.FC = () => {
<div className={styles.info} style={{ marginTop: '20px', padding: '15px', background: '#f9f9f9', borderRadius: '8px' }}>
<h4 style={{ margin: '0 0 10px 0' }}>🎨 Color Scheme Features:</h4>
<ul style={{ margin: '10px 0', paddingLeft: '20px' }}>
<li><strong>📅 Today's Date:</strong> Automatically highlighted with special colors (gold/red/green based on theme)</li>
<li><strong>Weekdays vs Weekends:</strong> Notice the subtle color differences</li>
<li><strong>Selecting State:</strong> Hover while range is selected to see the selecting colors</li>
<li><strong>Range States:</strong> Start/End dates have stronger colors than middle dates</li>
<li><strong>Active Dates:</strong> Click to toggle - active dates get special highlighting</li>
</ul>
<p style={{ margin: '10px 0 0 0', fontSize: '0.9em', color: '#666' }}>
Try selecting Saturday and Sunday to see weekend-specific colors during selection!
💡 Today's date has its own distinct styling that works with all themes!
</p>
</div>
)}

@ -32,7 +32,8 @@ export type DayState =
| 'rangeStart'
| 'rangeEnd'
| 'rangeMid'
| 'active';
| 'active'
| 'today';
export type DayType = 'weekday' | 'weekend';
@ -55,7 +56,8 @@ export type DayVariation =
| 'selecting'
| 'rowStart'
| 'rowEnd'
| 'active';
| 'active'
| 'today';
export type DaySize = 'xl' | 'l' | 'm' | 's' | 'xs' | 'xxs';
export interface YearProps {
@ -74,6 +76,7 @@ export interface YearProps {
activeDates?: Date[];
colorScheme?: ColorScheme;
locale?: Locale;
headerTextTransform?: (dayName: string) => string;
}
export interface MonthProps {
@ -93,6 +96,7 @@ export interface MonthProps {
activeDates?: Date[];
colorScheme?: ColorScheme;
locale?: Locale;
headerTextTransform?: (dayName: string) => string;
}
export interface WeekProps {
@ -111,6 +115,7 @@ export interface WeekProps {
activeDates?: Date[];
colorScheme?: ColorScheme;
locale?: Locale;
headerTextTransform?: (dayName: string) => string;
}
export interface DayProps {
@ -126,4 +131,5 @@ export interface DayProps {
activeColors?: ActiveColors;
colorScheme?: ColorScheme;
locale?: Locale;
headerTextTransform?: (dayName: string) => string;
}

@ -17,7 +17,8 @@ export const getDayType = (date: Date, variations: DayVariation[], weekendDays:
* Maps variations to a DayState for color lookup
*/
export const getDayState = (variations: DayVariation[]): DayState => {
// Priority order for states
// Priority order for states (today has highest priority for visibility)
if (variations.includes('today')) return 'today';
if (variations.includes('rangeStart') || variations.includes('rangeEnd')) {
if (variations.includes('rangeStart') && !variations.includes('rangeEnd')) {
return 'rangeStart';

@ -1,4 +1,4 @@
import { isSameDay, isWithinInterval, startOfDay } from 'date-fns';
import { isSameDay, isWithinInterval, startOfDay, isToday } from 'date-fns';
import { DateRange, DayVariation } from '../types/calendar';
export const getDateVariations = (
@ -7,9 +7,17 @@ export const getDateVariations = (
weekendDays: number[] = [],
dayIndex: number = 0
): DayVariation[] => {
const greyedClasses: DayVariation[] = weekendDays.includes(date.getDay())
? ['greyed']
: [];
const variations: DayVariation[] = [];
// Check if it's today
if (isToday(date)) {
variations.push('today');
}
// Check if it's a weekend
if (weekendDays.includes(date.getDay())) {
variations.push('greyed');
}
const greyedAndRowRelativeClasses = ['rowStart', 'rowEnd'].reduce((p, c) => {
switch (c) {
@ -20,7 +28,7 @@ export const getDateVariations = (
default:
return p;
}
}, greyedClasses);
}, variations);
if (!dateRange?.startDate || !dateRange?.endDate) {
return greyedAndRowRelativeClasses;

Loading…
Cancel
Save