diff --git a/src/App.tsx b/src/App.tsx index 8991663..85c5c43 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,12 +5,14 @@ import { WeekView } from './examples/WeekView'; import { DateRangeView } from './examples/DateRangeView'; import { InteractiveDateRange } from './examples/InteractiveDateRange'; import { CompactYear } from './examples/CompactYear'; +import { ComprehensiveColorScheme } from './examples/ComprehensiveColorScheme'; import styles from './App.module.scss'; const App: React.FC = () => { return (
+ diff --git a/src/components/calendar/DateRange/index.tsx b/src/components/calendar/DateRange/index.tsx index 68d260e..9f4f963 100644 --- a/src/components/calendar/DateRange/index.tsx +++ b/src/components/calendar/DateRange/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { eachDayOfInterval, startOfDay } from 'date-fns'; import { Day } from '../Day'; import { getDateVariations } from '../../../utils/dateUtils'; -import { HeaderStyle, DaySize, DayVariation } from '../../../types/calendar'; +import { HeaderStyle, DaySize, DayVariation, ActiveColors } from '../../../types/calendar'; import styles from './DateRange.module.scss'; import classNames from 'classnames'; @@ -18,6 +18,7 @@ interface DateRangeProps { magnify?: boolean; onDateClick?: (date: Date) => void; activeDates?: Date[]; + activeColors?: ActiveColors; } export const DateRange: React.FC = ({ @@ -31,7 +32,8 @@ export const DateRange: React.FC = ({ fontProportion = 100, magnify = false, onDateClick, - activeDates = [] + activeDates = [], + activeColors }) => { const startDate = startOfDay(from); const endDate = startOfDay(to); @@ -86,6 +88,7 @@ export const DateRange: React.FC = ({ size={size} fontProportion={fontProportion} magnify={magnify} + activeColors={activeColors} />
); diff --git a/src/components/calendar/Day/index.tsx b/src/components/calendar/Day/index.tsx index 97f8621..f5365bc 100644 --- a/src/components/calendar/Day/index.tsx +++ b/src/components/calendar/Day/index.tsx @@ -5,6 +5,7 @@ import styles from './Day.module.scss'; import classNames from 'classnames'; import { DayProps } from '../../../types/calendar'; import { DayNumber } from '../DayNumber'; +import { getCustomColors } from '../../../utils/colorUtils'; export const Day: React.FC = ({ date, @@ -15,7 +16,9 @@ export const Day: React.FC = ({ onHover, size = 'l', fontProportion = 100, - magnify = false + magnify = false, + activeColors, + colorScheme }) => { const isInteractive = Boolean(onSelect || onHover); @@ -63,13 +66,41 @@ export const Day: React.FC = ({ "aria-label": `Select ${format(date, 'PPP')}` } : {}; + // Get custom colors from color scheme if provided + // Note: weekendDays info is already in variations (greyed) so we can infer weekend from that + const schemeColors = getCustomColors(date, variations, colorScheme, [6, 0]); + + // Apply custom colors if the day is active and activeColors is provided (backwards compatibility) + const isActive = variations.includes('active'); + const legacyContentStyles = isActive && activeColors ? { + background: activeColors.contentBg, + color: activeColors.contentColor + } : undefined; + + const legacyHeaderStyles = isActive && activeColors ? { + background: activeColors.headerBg, + color: activeColors.headerColor + } : undefined; + + // Combine scheme colors with legacy active colors (legacy takes precedence for backwards compatibility) + const customStyles = legacyContentStyles || (schemeColors?.content ? { + backgroundColor: schemeColors.content.backgroundColor, + color: schemeColors.content.color + } : undefined); + + const headerCustomStyles = legacyHeaderStyles || (schemeColors?.header ? { + backgroundColor: schemeColors.header.backgroundColor, + color: schemeColors.header.color + } : undefined); + return (
{headerStyle !== 'none' && ( -
+
{getDayLabel(date, headerStyle)}
@@ -80,6 +111,8 @@ export const Day: React.FC = ({ number={parseInt(format(date, 'd'), 10)} proportion={fontProportion} variations={variations} + activeColors={activeColors} + customColor={customStyles?.color} />
diff --git a/src/components/calendar/DayNumber/index.tsx b/src/components/calendar/DayNumber/index.tsx index be6923e..a66c182 100644 --- a/src/components/calendar/DayNumber/index.tsx +++ b/src/components/calendar/DayNumber/index.tsx @@ -1,18 +1,22 @@ import React, { useRef, useEffect, useState } from 'react'; import styles from '../Day/Day.module.scss'; import classNames from 'classnames'; -import { DayVariation } from '../../../types/calendar'; +import { DayVariation, ActiveColors } from '../../../types/calendar'; interface DayNumberProps { number: number; proportion: number; variations?: DayVariation[]; + activeColors?: ActiveColors; + customColor?: string; } export const DayNumber: React.FC = ({ number, proportion, - variations = [] + variations = [], + activeColors, + customColor }) => { const textRef = useRef(null); const [scale, setScale] = useState({ x: 1, y: 1 }); @@ -58,6 +62,14 @@ export const DayNumber: React.FC = ({ } ); + // Apply custom color to text if provided (from color scheme or activeColors) + const isActive = variations.includes('active'); + const textStyle = customColor ? { + fill: customColor + } : (isActive && activeColors ? { + fill: activeColors.contentColor + } : undefined); + return (
= ({ textAnchor="middle" dominantBaseline="central" className={textClasses} + style={textStyle} transform={`matrix(${scale.x}, 0, 0, ${scale.y}, ${50 * (1 - scale.x)}, ${50 * (1 - scale.y)})`} > {number} diff --git a/src/components/calendar/Month/index.tsx b/src/components/calendar/Month/index.tsx index 8fff541..af27c59 100644 --- a/src/components/calendar/Month/index.tsx +++ b/src/components/calendar/Month/index.tsx @@ -17,7 +17,10 @@ export const Month: React.FC = ({ onDateHover, size = 'l', fontProportion = 100, - magnify = false + magnify = false, + activeColors, + activeDates, + colorScheme }) => { const monthStart = startOfMonth(date); const start = startOfWeek(monthStart, { weekStartsOn: 1 }); @@ -45,6 +48,9 @@ export const Month: React.FC = ({ size={size} fontProportion={fontProportion} magnify={magnify} + activeColors={activeColors} + activeDates={activeDates} + colorScheme={colorScheme} /> ); current = addWeeks(current, 1); diff --git a/src/components/calendar/Week/index.tsx b/src/components/calendar/Week/index.tsx index a4b2819..f9f48f7 100644 --- a/src/components/calendar/Week/index.tsx +++ b/src/components/calendar/Week/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { addDays } from 'date-fns'; +import { addDays, isSameDay } from 'date-fns'; import { WeekProps } from '../../../types/calendar'; import { Day } from '../Day'; import { getDateVariations } from '../../../utils/dateUtils'; @@ -17,12 +17,21 @@ export const Week: React.FC = ({ onDateHover, size = 'l', fontProportion = 100, - magnify = false + magnify = false, + activeColors, + activeDates = [], + colorScheme }) => { const allDays = Array.from({ length: 7 }, (_, i) => { const date = addDays(startDate, i); const isOtherMonth = date.getMonth() !== referenceMonth; - const variations = getDateVariations(date, dateRange, weekendDays, i); + let variations = getDateVariations(date, dateRange, weekendDays, i); + + // Check if this date is active + const isActive = activeDates.some(activeDate => isSameDay(activeDate, date)); + if (isActive) { + variations = [...variations, 'active'] as typeof variations; + } const wrapperClasses = classNames( styles.Week__DayWrapper, @@ -52,6 +61,8 @@ export const Week: React.FC = ({ size={size} fontProportion={fontProportion} magnify={magnify} + activeColors={activeColors} + colorScheme={colorScheme} />
); @@ -68,6 +79,8 @@ export const Week: React.FC = ({ size={size} fontProportion={fontProportion} magnify={magnify} + activeColors={activeColors} + colorScheme={colorScheme} />
); diff --git a/src/components/calendar/Year/index.tsx b/src/components/calendar/Year/index.tsx index 5da6248..81f6324 100644 --- a/src/components/calendar/Year/index.tsx +++ b/src/components/calendar/Year/index.tsx @@ -14,7 +14,10 @@ export const Year: React.FC = ({ onDateHover, size = 'l', fontProportion = 100, - magnify = false + magnify = false, + activeColors, + activeDates, + colorScheme }) => { const months = Array.from({ length: 12 }, (_, index) => { const monthDate = new Date(year, index, 1); @@ -33,6 +36,9 @@ export const Year: React.FC = ({ size={size} fontProportion={fontProportion} magnify={magnify} + activeColors={activeColors} + activeDates={activeDates} + colorScheme={colorScheme} /> ); }); diff --git a/src/examples/ComprehensiveColorScheme.tsx b/src/examples/ComprehensiveColorScheme.tsx new file mode 100644 index 0000000..6ff182e --- /dev/null +++ b/src/examples/ComprehensiveColorScheme.tsx @@ -0,0 +1,312 @@ +import React, { useState } from 'react'; +import { Year } from '../components/calendar/Year'; +import { Controls } from '../components/calendar/Controls'; +import { HeaderStyle, MonthCutoffType, DaySize, DateRange, ColorScheme } from '../types/calendar'; +import styles from './Examples.module.scss'; +import { format, isBefore, isEqual } from 'date-fns'; + +export const ComprehensiveColorScheme: React.FC = () => { + const [dateRange, setDateRange] = useState({ + startDate: new Date(2025, 0, 15), + endDate: new Date(2025, 0, 20), + selecting: false, + hoverDate: null, + anchorDate: null + }); + + const [dayHeaderStyle, setDayHeaderStyle] = useState('tiny'); + const [monthCutoff, setMonthCutoff] = useState('truncate'); + const [size, setSize] = useState('l'); + const [fontProportion, setFontProportion] = useState(100); + const [magnify, setMagnify] = useState(false); + const [useCustomScheme, setUseCustomScheme] = useState(true); + const [selectedScheme, setSelectedScheme] = useState<'professional' | 'vibrant' | 'minimal'>('professional'); + + // Define different color schemes + const colorSchemes: Record = { + professional: [ + // Weekday states + { type: 'weekday', state: 'default', colors: { + header: { backgroundColor: '#2c3e50', color: '#ffffff' }, + content: { backgroundColor: '#ffffff', color: '#2c3e50' } + }}, + { type: 'weekday', state: 'selected', colors: { + header: { backgroundColor: '#3498db', color: '#ffffff' }, + content: { backgroundColor: '#e3f2fd', color: '#1976d2' } + }}, + { type: 'weekday', state: 'selecting', colors: { + header: { backgroundColor: '#95a5a6', color: '#ffffff' }, + content: { backgroundColor: '#ecf0f1', color: '#7f8c8d' } + }}, + { type: 'weekday', state: 'rangeStart', colors: { + header: { backgroundColor: '#2980b9', color: '#ffffff' }, + content: { backgroundColor: '#3498db', color: '#ffffff' } + }}, + { type: 'weekday', state: 'rangeEnd', colors: { + header: { backgroundColor: '#2980b9', color: '#ffffff' }, + content: { backgroundColor: '#3498db', color: '#ffffff' } + }}, + { type: 'weekday', state: 'rangeMid', colors: { + header: { backgroundColor: '#5dade2', color: '#ffffff' }, + content: { backgroundColor: '#aed6f1', color: '#1a5490' } + }}, + { type: 'weekday', state: 'active', colors: { + header: { backgroundColor: '#e74c3c', color: '#ffffff' }, + content: { backgroundColor: '#c0392b', color: '#ffffff' } + }}, + + // Weekend states + { type: 'weekend', state: 'default', colors: { + header: { backgroundColor: '#7f8c8d', color: '#ffffff' }, + content: { backgroundColor: '#ecf0f1', color: '#34495e' } + }}, + { type: 'weekend', state: 'selected', colors: { + header: { backgroundColor: '#5dade2', color: '#ffffff' }, + content: { backgroundColor: '#d4e6f1', color: '#2874a6' } + }}, + { type: 'weekend', state: 'rangeStart', colors: { + header: { backgroundColor: '#2980b9', color: '#ffffff' }, + content: { backgroundColor: '#3498db', color: '#ffffff' } + }}, + { type: 'weekend', state: 'rangeEnd', colors: { + header: { backgroundColor: '#2980b9', color: '#ffffff' }, + content: { backgroundColor: '#3498db', color: '#ffffff' } + }}, + { type: 'weekend', state: 'active', colors: { + header: { backgroundColor: '#c0392b', color: '#ffffff' }, + content: { backgroundColor: '#e74c3c', color: '#ffffff' } + }} + ], + + vibrant: [ + // Weekday states + { type: 'weekday', state: 'default', colors: { + header: { backgroundColor: '#9c27b0', color: '#ffffff' }, + content: { backgroundColor: '#ffffff', color: '#4a148c' } + }}, + { type: 'weekday', state: 'selected', colors: { + header: { backgroundColor: '#ff9800', color: '#ffffff' }, + content: { backgroundColor: '#fff3e0', color: '#e65100' } + }}, + { type: 'weekday', state: 'rangeStart', colors: { + header: { backgroundColor: '#f44336', color: '#ffffff' }, + content: { backgroundColor: '#ff5722', color: '#ffffff' } + }}, + { type: 'weekday', state: 'rangeEnd', colors: { + header: { backgroundColor: '#f44336', color: '#ffffff' }, + content: { backgroundColor: '#ff5722', color: '#ffffff' } + }}, + { type: 'weekday', state: 'rangeMid', colors: { + header: { backgroundColor: '#ff7043', color: '#ffffff' }, + content: { backgroundColor: '#ffccbc', color: '#bf360c' } + }}, + { type: 'weekday', state: 'active', colors: { + header: { backgroundColor: '#00bcd4', color: '#ffffff' }, + content: { backgroundColor: '#0097a7', color: '#ffffff' } + }}, + + // Weekend states + { type: 'weekend', state: 'default', colors: { + header: { backgroundColor: '#673ab7', color: '#ffffff' }, + content: { backgroundColor: '#ede7f6', color: '#311b92' } + }}, + { type: 'weekend', state: 'selected', colors: { + header: { backgroundColor: '#ffc107', color: '#000000' }, + content: { backgroundColor: '#fff8e1', color: '#f57f17' } + }}, + { type: 'weekend', state: 'active', colors: { + header: { backgroundColor: '#006064', color: '#ffffff' }, + content: { backgroundColor: '#00acc1', color: '#ffffff' } + }} + ], + + minimal: [ + // Weekday states + { type: 'weekday', state: 'default', colors: { + header: { backgroundColor: '#f5f5f5', color: '#333333' }, + content: { backgroundColor: '#ffffff', color: '#333333' } + }}, + { type: 'weekday', state: 'selected', colors: { + header: { backgroundColor: '#e0e0e0', color: '#000000' }, + content: { backgroundColor: '#f5f5f5', color: '#000000' } + }}, + { type: 'weekday', state: 'rangeStart', colors: { + header: { backgroundColor: '#333333', color: '#ffffff' }, + content: { backgroundColor: '#666666', color: '#ffffff' } + }}, + { type: 'weekday', state: 'rangeEnd', colors: { + header: { backgroundColor: '#333333', color: '#ffffff' }, + content: { backgroundColor: '#666666', color: '#ffffff' } + }}, + { type: 'weekday', state: 'rangeMid', colors: { + header: { backgroundColor: '#999999', color: '#ffffff' }, + content: { backgroundColor: '#cccccc', color: '#333333' } + }}, + { type: 'weekday', state: 'active', colors: { + header: { backgroundColor: '#000000', color: '#ffffff' }, + content: { backgroundColor: '#333333', color: '#ffffff' } + }}, + + // Weekend states + { type: 'weekend', state: 'default', colors: { + header: { backgroundColor: '#fafafa', color: '#666666' }, + content: { backgroundColor: '#f0f0f0', color: '#666666' } + }}, + { type: 'weekend', state: 'active', colors: { + header: { backgroundColor: '#212121', color: '#ffffff' }, + content: { backgroundColor: '#424242', color: '#ffffff' } + }} + ] + }; + + const handleDateSelect = (date: Date) => { + setDateRange(prev => { + if (!prev.selecting || !prev.anchorDate) { + return { + startDate: date, + endDate: date, + selecting: true, + hoverDate: date, + anchorDate: date + }; + } + + if (isEqual(date, prev.anchorDate)) { + return { + startDate: date, + endDate: date, + selecting: false, + hoverDate: null, + anchorDate: null + }; + } + + const [start, end] = isBefore(date, prev.anchorDate) + ? [date, prev.anchorDate] + : [prev.anchorDate, date]; + + return { + startDate: start, + endDate: end, + selecting: false, + hoverDate: null, + anchorDate: null + }; + }); + }; + + const handleDateHover = (date: Date) => { + setDateRange(prev => { + if (prev.selecting && prev.anchorDate) { + if (isEqual(date, prev.anchorDate)) { + return { + ...prev, + startDate: date, + endDate: date, + hoverDate: date + }; + } + + const [start, end] = isBefore(date, prev.anchorDate) + ? [date, prev.anchorDate] + : [prev.anchorDate, date]; + + return { + ...prev, + startDate: start, + endDate: end, + hoverDate: date + }; + } + return prev; + }); + }; + + const activeDates = [ + new Date(2025, 0, 1), // New Year's Day + new Date(2025, 1, 14), // Valentine's Day + new Date(2025, 6, 4), // Independence Day + new Date(2025, 11, 25) // Christmas + ]; + + return ( +
+

Comprehensive Color Scheme Customization

+

+ Customize colors for every state (default, selected, selecting, range start/end/mid, active) + and day type (weekday, weekend). +

+ + + +
+ + {useCustomScheme && ( + + )} +
+ +
+ +
+ +
+
Selected Range: {dateRange.startDate && dateRange.endDate + ? `${format(dateRange.startDate, 'PP')} - ${format(dateRange.endDate, 'PP')}` + : 'None'}
+
+ Active dates (holidays): Jan 1, Feb 14, Jul 4, Dec 25 +
+ {useCustomScheme && ( +
+ Color Scheme Features: +
    +
  • Different colors for weekdays vs weekends
  • +
  • Distinct states: default, selected, selecting, range start/end/mid, active
  • +
  • Customizable header and content colors for each state
  • +
+
+ )} +
+
+ ); +}; \ No newline at end of file diff --git a/src/examples/DateRangePicker.tsx b/src/examples/DateRangePicker.tsx index ef451eb..1d42b5c 100644 --- a/src/examples/DateRangePicker.tsx +++ b/src/examples/DateRangePicker.tsx @@ -2,7 +2,7 @@ import React, { useState, useCallback } from 'react'; import { format, isBefore, isEqual } from 'date-fns'; import { Year } from '../components/calendar/Year'; import { Controls } from '../components/calendar/Controls'; -import { HeaderStyle, MonthCutoffType, DaySize, DateRange } from '../types/calendar'; +import { HeaderStyle, MonthCutoffType, DaySize, DateRange, ActiveColors } from '../types/calendar'; import styles from './Examples.module.scss'; export const DateRangePicker: React.FC = () => { @@ -19,6 +19,43 @@ export const DateRangePicker: React.FC = () => { const [size, setSize] = useState('l'); const [fontProportion, setFontProportion] = useState(100); const [magnify, setMagnify] = useState(false); + const [useCustomColors, setUseCustomColors] = useState(false); + const [colorScheme, setColorScheme] = useState<'blue' | 'green' | 'purple' | 'orange'>('blue'); + const [activeDates, setActiveDates] = useState([ + new Date(2025, 0, 1), // January 1st + new Date(2025, 0, 15), // January 15th + new Date(2025, 1, 14), // February 14th + new Date(2025, 2, 17), // March 17th + new Date(2025, 6, 4), // July 4th + new Date(2025, 11, 25) // December 25th + ]); + + const colorSchemes: Record = { + blue: { + headerBg: '#E3F2FD', + headerColor: '#1976D2', + contentBg: '#2196F3', + contentColor: '#FFFFFF' + }, + green: { + headerBg: '#D4EDDA', + headerColor: '#155724', + contentBg: '#28A745', + contentColor: '#FFFFFF' + }, + purple: { + headerBg: '#E7E5FF', + headerColor: '#4A3F8C', + contentBg: '#6F42C1', + contentColor: '#FFFFFF' + }, + orange: { + headerBg: '#FFF3CD', + headerColor: '#856404', + contentBg: '#FD7E14', + contentColor: '#FFFFFF' + } + }; const handleDateSelect = useCallback((date: Date) => { setDateRange(prev => { @@ -105,6 +142,29 @@ export const DateRangePicker: React.FC = () => { onFontProportionChange={setFontProportion} onMagnifyChange={setMagnify} /> + +
+ + {useCustomColors && ( + + )} +
{ size={size} fontProportion={fontProportion} magnify={magnify} + activeColors={useCustomColors ? colorSchemes[colorScheme] : undefined} + activeDates={activeDates} />
@@ -125,6 +187,10 @@ export const DateRangePicker: React.FC = () => {
Selecting: {dateRange.selecting ? 'Yes' : 'No'}
Hover Date: {formatDateOrNull(dateRange.hoverDate)}
Anchor Date: {formatDateOrNull(dateRange.anchorDate)}
+
+ Active dates (holidays): Jan 1, Jan 15, Feb 14, Mar 17, Jul 4, Dec 25 + {useCustomColors && ' - Custom colors applied'} +
); diff --git a/src/examples/DateRangeWithActiveColors.tsx b/src/examples/DateRangeWithActiveColors.tsx new file mode 100644 index 0000000..96d209b --- /dev/null +++ b/src/examples/DateRangeWithActiveColors.tsx @@ -0,0 +1,108 @@ +import React, { useState } from 'react'; +import { DateRange } from '../components/calendar/DateRange'; +import { ActiveColors } from '../types/calendar'; +import styles from './Examples.module.scss'; + +export const DateRangeWithActiveColors: React.FC = () => { + const [colorScheme, setColorScheme] = useState<'default' | 'green' | 'purple' | 'orange'>('default'); + + const fromDate = new Date(2025, 0, 15); // January 15, 2025 + const toDate = new Date(2025, 0, 20); // January 20, 2025 + + const colorSchemes: Record = { + default: undefined, + green: { + headerBg: '#D4EDDA', + headerColor: '#155724', + contentBg: '#28A745', + contentColor: '#FFFFFF' + }, + purple: { + headerBg: '#E7E5FF', + headerColor: '#4A3F8C', + contentBg: '#6F42C1', + contentColor: '#FFFFFF' + }, + orange: { + headerBg: '#FFF3CD', + headerColor: '#856404', + contentBg: '#FD7E14', + contentColor: '#FFFFFF' + } + }; + + return ( +
+

Date Range with Active Colors

+

+ Customize the colors of selected date ranges using the activeColors prop. +

+ +
+ +
+ +
+

Before (Default Colors)

+ + +

After (Custom Active Colors)

+ +
+ +
+
{``}
+
+ +
+

ActiveColors Properties:

+
    +
  • headerBg: Background color for the day header
  • +
  • headerColor: Text color for the day header
  • +
  • contentBg: Background color for the day content (number)
  • +
  • contentColor: Text color for the day content (number)
  • +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/types/calendar.ts b/src/types/calendar.ts index a5a347b..9c25db9 100644 --- a/src/types/calendar.ts +++ b/src/types/calendar.ts @@ -6,6 +6,42 @@ export type DateRange = { anchorDate: Date | null; }; +export type ActiveColors = { + headerBg: string; + headerColor: string; + contentBg: string; + contentColor: string; +}; + +export type ColorStyle = { + backgroundColor?: string; + color?: string; +}; + +export type StateColors = { + header?: ColorStyle; + content?: ColorStyle; +}; + +export type DayState = + | 'default' + | 'selected' + | 'selecting' + | 'rangeStart' + | 'rangeEnd' + | 'rangeMid' + | 'active'; + +export type DayType = 'weekday' | 'weekend'; + +export type ColorSchemeEntry = { + type: DayType; + state: DayState; + colors: StateColors; +}; + +export type ColorScheme = ColorSchemeEntry[]; + export type HeaderStyle = 'expanded' | 'compacted' | 'tiny' | 'none'; export type MonthCutoffType = 'dimmed' | 'truncate' | undefined; export type DirectionType = 'row' | 'column'; @@ -32,6 +68,9 @@ export interface YearProps { size?: DaySize; fontProportion?: number; magnify?: boolean; + activeColors?: ActiveColors; + activeDates?: Date[]; + colorScheme?: ColorScheme; } export interface MonthProps { @@ -47,6 +86,9 @@ export interface MonthProps { size?: DaySize; fontProportion?: number; magnify?: boolean; + activeColors?: ActiveColors; + activeDates?: Date[]; + colorScheme?: ColorScheme; } export interface WeekProps { @@ -61,6 +103,9 @@ export interface WeekProps { size?: DaySize; fontProportion?: number; magnify?: boolean; + activeColors?: ActiveColors; + activeDates?: Date[]; + colorScheme?: ColorScheme; } export interface DayProps { @@ -73,4 +118,6 @@ export interface DayProps { size?: DaySize; fontProportion?: number; magnify?: boolean; + activeColors?: ActiveColors; + colorScheme?: ColorScheme; } diff --git a/src/utils/colorUtils.ts b/src/utils/colorUtils.ts new file mode 100644 index 0000000..7256c51 --- /dev/null +++ b/src/utils/colorUtils.ts @@ -0,0 +1,89 @@ +import { ColorScheme, DayState, DayType, StateColors, DayVariation } from '../types/calendar'; + +/** + * Determines the day type based on variations (greyed indicates weekend) + * or by checking the day of week + */ +export const getDayType = (date: Date, variations: DayVariation[], weekendDays: number[] = [6, 0]): DayType => { + // If variations include 'greyed', it's a weekend + if (variations.includes('greyed')) { + return 'weekend'; + } + // Fallback to checking day of week + return weekendDays.includes(date.getDay()) ? 'weekend' : 'weekday'; +}; + +/** + * Maps variations to a DayState for color lookup + */ +export const getDayState = (variations: DayVariation[]): DayState => { + // Priority order for states + if (variations.includes('rangeStart') || variations.includes('rangeEnd')) { + if (variations.includes('rangeStart') && !variations.includes('rangeEnd')) { + return 'rangeStart'; + } + if (variations.includes('rangeEnd') && !variations.includes('rangeStart')) { + return 'rangeEnd'; + } + // If both rangeStart and rangeEnd (single day range) + return 'rangeStart'; + } + if (variations.includes('active')) return 'active'; + if (variations.includes('selected')) return 'rangeMid'; + if (variations.includes('selecting')) return 'selecting'; + return 'default'; +}; + +/** + * Finds the appropriate colors from the color scheme + */ +export const findColorInScheme = ( + colorScheme: ColorScheme | undefined, + dayType: DayType, + dayState: DayState +): StateColors | undefined => { + if (!colorScheme) return undefined; + + // First try to find exact match + const exactMatch = colorScheme.find( + entry => entry.type === dayType && entry.state === dayState + ); + if (exactMatch) return exactMatch.colors; + + // Fallback to weekday colors for weekend if not specified + if (dayType === 'weekend') { + const weekdayMatch = colorScheme.find( + entry => entry.type === 'weekday' && entry.state === dayState + ); + if (weekdayMatch) return weekdayMatch.colors; + } + + // Fallback to default state for the day type + const defaultMatch = colorScheme.find( + entry => entry.type === dayType && entry.state === 'default' + ); + if (defaultMatch) return defaultMatch.colors; + + // Final fallback to weekday default + const weekdayDefault = colorScheme.find( + entry => entry.type === 'weekday' && entry.state === 'default' + ); + return weekdayDefault?.colors; +}; + +/** + * Gets the custom colors for a day based on its state and type + */ +export const getCustomColors = ( + date: Date, + variations: DayVariation[], + colorScheme: ColorScheme | undefined, + weekendDays: number[] = [6, 0] +): StateColors | undefined => { + if (!colorScheme) return undefined; + + const dayType = getDayType(date, variations, weekendDays); + const dayState = getDayState(variations); + + return findColorInScheme(colorScheme, dayType, dayState); +}; \ No newline at end of file