diff --git a/README.md b/README.md index 89428f8..b0decda 100644 --- a/README.md +++ b/README.md @@ -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! diff --git a/USAGE_GUIDE.md b/USAGE_GUIDE.md index 1c151a5..7ad9ce7 100644 --- a/USAGE_GUIDE.md +++ b/USAGE_GUIDE.md @@ -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 + dayName.slice(0, 2)} // Returns "MO", "TU", etc. +/> + +// Example 2: Custom mapping +const customDayNames = (dayName: string) => { + const map: Record = { + 'MON': 'Mo', 'TUE': 'Tu', 'WED': 'We', + 'THU': 'Th', 'FRI': 'Fr', 'SAT': 'Sa', 'SUN': 'Su' + }; + return map[dayName] || dayName; +}; + + + +// Example 3: Lowercase + 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 diff --git a/package.json b/package.json index f25f6c9..e8617cc 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/calendar/DateRange/index.tsx b/src/components/calendar/DateRange/index.tsx index c54e30b..000d84e 100644 --- a/src/components/calendar/DateRange/index.tsx +++ b/src/components/calendar/DateRange/index.tsx @@ -21,6 +21,7 @@ interface DateRangeProps { activeColors?: ActiveColors; colorScheme?: ColorScheme; locale?: Locale; + headerTextTransform?: (dayName: string) => string; } export const DateRange: React.FC = ({ @@ -37,7 +38,8 @@ export const DateRange: React.FC = ({ activeDates = [], activeColors, colorScheme, - locale + locale, + headerTextTransform }) => { const startDate = startOfDay(from); const endDate = startOfDay(to); @@ -95,6 +97,7 @@ export const DateRange: React.FC = ({ activeColors={activeColors} colorScheme={colorScheme} locale={locale} + headerTextTransform={headerTextTransform} /> ); diff --git a/src/components/calendar/Day/Day.module.scss b/src/components/calendar/Day/Day.module.scss index 938ca2f..207d1cf 100644 --- a/src/components/calendar/Day/Day.module.scss +++ b/src/components/calendar/Day/Day.module.scss @@ -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; diff --git a/src/components/calendar/Day/index.tsx b/src/components/calendar/Day/index.tsx index 2396f97..be15ff9 100644 --- a/src/components/calendar/Day/index.tsx +++ b/src/components/calendar/Day/index.tsx @@ -19,7 +19,8 @@ export const Day: React.FC = ({ magnify = false, activeColors, colorScheme, - locale + locale, + headerTextTransform }) => { const isInteractive = Boolean(onSelect || onHover); @@ -45,6 +46,7 @@ export const Day: React.FC = ({ [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 = ({ {headerStyle !== 'none' && (
- {getDayLabel(date, headerStyle, locale)} + {getDayLabel(date, headerStyle, locale, headerTextTransform)}
)} diff --git a/src/components/calendar/Month/index.tsx b/src/components/calendar/Month/index.tsx index c52f7da..9d815bf 100644 --- a/src/components/calendar/Month/index.tsx +++ b/src/components/calendar/Month/index.tsx @@ -22,7 +22,8 @@ export const Month: React.FC = ({ 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 = ({ activeDates={activeDates} colorScheme={colorScheme} locale={locale} + headerTextTransform={headerTextTransform} /> ); current = addWeeks(current, 1); diff --git a/src/components/calendar/Week/index.tsx b/src/components/calendar/Week/index.tsx index 8f5322b..17d0113 100644 --- a/src/components/calendar/Week/index.tsx +++ b/src/components/calendar/Week/index.tsx @@ -21,7 +21,8 @@ export const Week: React.FC = ({ 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 = ({ activeColors={activeColors} colorScheme={colorScheme} locale={locale} + headerTextTransform={headerTextTransform} /> ); @@ -84,6 +86,7 @@ export const Week: React.FC = ({ activeColors={activeColors} colorScheme={colorScheme} locale={locale} + headerTextTransform={headerTextTransform} /> ); diff --git a/src/components/calendar/Year/index.tsx b/src/components/calendar/Year/index.tsx index c62af01..42cfd80 100644 --- a/src/components/calendar/Year/index.tsx +++ b/src/components/calendar/Year/index.tsx @@ -18,7 +18,8 @@ export const Year: React.FC = ({ 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 = ({ activeDates={activeDates} colorScheme={colorScheme} locale={locale} + headerTextTransform={headerTextTransform} /> ); }); diff --git a/src/components/calendar/utils.ts b/src/components/calendar/utils.ts index fc3449c..cb82910 100644 --- a/src/components/calendar/utils.ts +++ b/src/components/calendar/utils.ts @@ -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; }; diff --git a/src/examples/DateRangeWithActiveColors.tsx b/src/examples/DateRangeWithActiveColors.tsx index 96d209b..8739d90 100644 --- a/src/examples/DateRangeWithActiveColors.tsx +++ b/src/examples/DateRangeWithActiveColors.tsx @@ -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([]); + const [lastClicked, setLastClicked] = useState(''); + 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 = { + '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 (
-

Date Range with Active Colors

+

Date Range with Active Colors & Header Transform

- Customize the colors of selected date ranges using the activeColors prop. + Customize colors and header text display using activeColors and headerTextTransform props.

@@ -52,28 +107,42 @@ export const DateRangeWithActiveColors: React.FC = () => { + + +
+ +
+

Last clicked: {lastClicked || 'None'}

+

Active dates: {activeDates.length > 0 + ? activeDates.map(d => format(d, 'MMM d')).join(', ') + : 'None (click dates to toggle)'}

-

Before (Default Colors)

- - -

After (Custom Active Colors)

+

Interactive Date Range with Custom Colors & Headers

@@ -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]'}}` : ''} />`} -
-

ActiveColors Properties:

-
    +
    +

    πŸ“ New Feature: headerTextTransform

    +
      +
    • The headerTextTransform prop accepts a function: (dayName: string) => string
    • +
    • It receives the day name after headerStyle processing (e.g., "MON" for compacted)
    • +
    • You can return any string to display in the header
    • +
    • Great for custom abbreviations or localization needs
    • +
    • Example: dayName => dayName.slice(0, 2) for 2-letter abbreviations
    • +
    + +

    🎨 ActiveColors Properties:

    +
    • headerBg: Background color for the day header
    • headerColor: Text color for the day header
    • contentBg: Background color for the day content (number)
    • diff --git a/src/examples/InteractiveDateRange.tsx b/src/examples/InteractiveDateRange.tsx index 46076ff..85fc3dd 100644 --- a/src/examples/InteractiveDateRange.tsx +++ b/src/examples/InteractiveDateRange.tsx @@ -19,8 +19,10 @@ export const InteractiveDateRange: React.FC = () => { const [useColorScheme, setUseColorScheme] = useState(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 = { @@ -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 = { 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 = () => {
    {` {
             

    🎨 Color Scheme Features:

      +
    • πŸ“… Today's Date: Automatically highlighted with special colors (gold/red/green based on theme)
    • Weekdays vs Weekends: Notice the subtle color differences
    • Selecting State: Hover while range is selected to see the selecting colors
    • Range States: Start/End dates have stronger colors than middle dates
    • Active Dates: Click to toggle - active dates get special highlighting

    - 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!

    )} diff --git a/src/types/calendar.ts b/src/types/calendar.ts index 00bc2b2..45ce20f 100644 --- a/src/types/calendar.ts +++ b/src/types/calendar.ts @@ -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; } diff --git a/src/utils/colorUtils.ts b/src/utils/colorUtils.ts index 7256c51..dd5d8b2 100644 --- a/src/utils/colorUtils.ts +++ b/src/utils/colorUtils.ts @@ -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'; diff --git a/src/utils/dateUtils.ts b/src/utils/dateUtils.ts index f3f4041..07645be 100644 --- a/src/utils/dateUtils.ts +++ b/src/utils/dateUtils.ts @@ -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;