feat: customizable colors

master
Guillermo Pages 4 months ago
parent 07f9451b39
commit 50ea538717

@ -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 (
<div className={styles.container}>
<DateRangePicker />
<ComprehensiveColorScheme />
<SingleMonth />
<WeekView />
<DateRangeView />

@ -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<DateRangeProps> = ({
@ -31,7 +32,8 @@ export const DateRange: React.FC<DateRangeProps> = ({
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<DateRangeProps> = ({
size={size}
fontProportion={fontProportion}
magnify={magnify}
activeColors={activeColors}
/>
</div>
);

@ -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<DayProps> = ({
date,
@ -15,7 +16,9 @@ export const Day: React.FC<DayProps> = ({
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<DayProps> = ({
"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 (
<div
className={containerClasses}
style={customStyles}
{...interactiveProps}
>
{headerStyle !== 'none' && (
<div className={headerClasses}>
<div className={headerClasses} style={headerCustomStyles}>
<div className={styles.Day__HeaderText}>
{getDayLabel(date, headerStyle)}
</div>
@ -80,6 +111,8 @@ export const Day: React.FC<DayProps> = ({
number={parseInt(format(date, 'd'), 10)}
proportion={fontProportion}
variations={variations}
activeColors={activeColors}
customColor={customStyles?.color}
/>
</div>
</div>

@ -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<DayNumberProps> = ({
number,
proportion,
variations = []
variations = [],
activeColors,
customColor
}) => {
const textRef = useRef<SVGTextElement>(null);
const [scale, setScale] = useState({ x: 1, y: 1 });
@ -58,6 +62,14 @@ export const DayNumber: React.FC<DayNumberProps> = ({
}
);
// 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 (
<div className={containerClasses}>
<svg
@ -74,6 +86,7 @@ export const DayNumber: React.FC<DayNumberProps> = ({
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}

@ -17,7 +17,10 @@ export const Month: React.FC<MonthProps> = ({
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<MonthProps> = ({
size={size}
fontProportion={fontProportion}
magnify={magnify}
activeColors={activeColors}
activeDates={activeDates}
colorScheme={colorScheme}
/>
);
current = addWeeks(current, 1);

@ -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<WeekProps> = ({
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<WeekProps> = ({
size={size}
fontProportion={fontProportion}
magnify={magnify}
activeColors={activeColors}
colorScheme={colorScheme}
/>
</div>
);
@ -68,6 +79,8 @@ export const Week: React.FC<WeekProps> = ({
size={size}
fontProportion={fontProportion}
magnify={magnify}
activeColors={activeColors}
colorScheme={colorScheme}
/>
</div>
);

@ -14,7 +14,10 @@ export const Year: React.FC<YearProps> = ({
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<YearProps> = ({
size={size}
fontProportion={fontProportion}
magnify={magnify}
activeColors={activeColors}
activeDates={activeDates}
colorScheme={colorScheme}
/>
);
});

@ -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<DateRange>({
startDate: new Date(2025, 0, 15),
endDate: new Date(2025, 0, 20),
selecting: false,
hoverDate: null,
anchorDate: null
});
const [dayHeaderStyle, setDayHeaderStyle] = useState<HeaderStyle>('tiny');
const [monthCutoff, setMonthCutoff] = useState<MonthCutoffType>('truncate');
const [size, setSize] = useState<DaySize>('l');
const [fontProportion, setFontProportion] = useState<number>(100);
const [magnify, setMagnify] = useState<boolean>(false);
const [useCustomScheme, setUseCustomScheme] = useState<boolean>(true);
const [selectedScheme, setSelectedScheme] = useState<'professional' | 'vibrant' | 'minimal'>('professional');
// Define different color schemes
const colorSchemes: Record<string, ColorScheme> = {
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 (
<section className={styles.section}>
<h2 className={styles.sectionTitle}>Comprehensive Color Scheme Customization</h2>
<p className={styles.sectionDescription}>
Customize colors for every state (default, selected, selecting, range start/end/mid, active)
and day type (weekday, weekend).
</p>
<Controls
headerStyle={dayHeaderStyle}
monthCutoff={monthCutoff}
size={size}
fontProportion={fontProportion}
magnify={magnify}
onHeaderStyleChange={setDayHeaderStyle}
onMonthCutoffChange={setMonthCutoff}
onSizeChange={setSize}
onFontProportionChange={setFontProportion}
onMagnifyChange={setMagnify}
/>
<div className={styles.controlRow}>
<label className={styles.control}>
<input
type="checkbox"
checked={useCustomScheme}
onChange={(e) => setUseCustomScheme(e.target.checked)}
/>
Use Custom Color Scheme
</label>
{useCustomScheme && (
<select
value={selectedScheme}
onChange={(e) => setSelectedScheme(e.target.value as any)}
style={{ marginLeft: '12px' }}
>
<option value="professional">Professional</option>
<option value="vibrant">Vibrant</option>
<option value="minimal">Minimal</option>
</select>
)}
</div>
<div className={styles.demoContainer}>
<Year
year={2025}
dayHeaderStyle={dayHeaderStyle}
monthCutoff={monthCutoff}
weekendDays={[6, 0]}
dateRange={dateRange}
onDateSelect={handleDateSelect}
onDateHover={handleDateHover}
size={size}
fontProportion={fontProportion}
magnify={magnify}
activeDates={activeDates}
colorScheme={useCustomScheme ? colorSchemes[selectedScheme] : undefined}
/>
</div>
<div className={styles.rangeInfo}>
<div>Selected Range: {dateRange.startDate && dateRange.endDate
? `${format(dateRange.startDate, 'PP')} - ${format(dateRange.endDate, 'PP')}`
: 'None'}</div>
<div style={{ marginTop: '10px', fontSize: '0.9em', color: '#666' }}>
Active dates (holidays): Jan 1, Feb 14, Jul 4, Dec 25
</div>
{useCustomScheme && (
<div style={{ marginTop: '10px', fontSize: '0.9em' }}>
<strong>Color Scheme Features:</strong>
<ul style={{ marginTop: '5px', paddingLeft: '20px' }}>
<li>Different colors for weekdays vs weekends</li>
<li>Distinct states: default, selected, selecting, range start/end/mid, active</li>
<li>Customizable header and content colors for each state</li>
</ul>
</div>
)}
</div>
</section>
);
};

@ -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<DaySize>('l');
const [fontProportion, setFontProportion] = useState<number>(100);
const [magnify, setMagnify] = useState<boolean>(false);
const [useCustomColors, setUseCustomColors] = useState<boolean>(false);
const [colorScheme, setColorScheme] = useState<'blue' | 'green' | 'purple' | 'orange'>('blue');
const [activeDates, setActiveDates] = useState<Date[]>([
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<string, ActiveColors> = {
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}
/>
<div className={styles.controlRow}>
<label className={styles.control}>
<input
type="checkbox"
checked={useCustomColors}
onChange={(e) => setUseCustomColors(e.target.checked)}
/>
Use Custom Active Colors
</label>
{useCustomColors && (
<select
value={colorScheme}
onChange={(e) => setColorScheme(e.target.value as any)}
style={{ marginLeft: '12px' }}
>
<option value="blue">Blue</option>
<option value="green">Green</option>
<option value="purple">Purple</option>
<option value="orange">Orange</option>
</select>
)}
</div>
<div className={styles.demoContainer}>
<Year
year={2025}
@ -117,6 +177,8 @@ export const DateRangePicker: React.FC = () => {
size={size}
fontProportion={fontProportion}
magnify={magnify}
activeColors={useCustomColors ? colorSchemes[colorScheme] : undefined}
activeDates={activeDates}
/>
</div>
<div className={styles.rangeInfo}>
@ -125,6 +187,10 @@ export const DateRangePicker: React.FC = () => {
<div>Selecting: {dateRange.selecting ? 'Yes' : 'No'}</div>
<div>Hover Date: {formatDateOrNull(dateRange.hoverDate)}</div>
<div>Anchor Date: {formatDateOrNull(dateRange.anchorDate)}</div>
<div style={{ marginTop: '10px', fontSize: '0.9em', color: '#666' }}>
Active dates (holidays): Jan 1, Jan 15, Feb 14, Mar 17, Jul 4, Dec 25
{useCustomColors && ' - Custom colors applied'}
</div>
</div>
</section>
);

@ -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<string, ActiveColors | undefined> = {
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 (
<section className={styles.section}>
<h2 className={styles.sectionTitle}>Date Range with Active Colors</h2>
<p className={styles.sectionDescription}>
Customize the colors of selected date ranges using the activeColors prop.
</p>
<div className={styles.controlRow}>
<label className={styles.control}>
Color Scheme:
<select
value={colorScheme}
onChange={(e) => setColorScheme(e.target.value as any)}
style={{ marginLeft: '8px' }}
>
<option value="default">Default (Blue)</option>
<option value="green">Green</option>
<option value="purple">Purple</option>
<option value="orange">Orange</option>
</select>
</label>
</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>
<DateRange
from={fromDate}
to={toDate}
selected={true}
headerStyle="tiny"
size="l"
magnify={true}
activeColors={colorSchemes[colorScheme]}
/>
</div>
<div className={styles.codeExample}>
<pre>{`<DateRange
from={new Date(2025, 0, 15)}
to={new Date(2025, 0, 20)}
selected={true}
headerStyle="tiny"
size="l"
magnify={true}${colorScheme !== 'default' ? `
activeColors={{
headerBg: '${colorSchemes[colorScheme]?.headerBg}',
headerColor: '${colorSchemes[colorScheme]?.headerColor}',
contentBg: '${colorSchemes[colorScheme]?.contentBg}',
contentColor: '${colorSchemes[colorScheme]?.contentColor}'
}}` : ''}
/>`}</pre>
</div>
<div className={styles.info}>
<h4>ActiveColors Properties:</h4>
<ul>
<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>
<li><code>contentColor</code>: Text color for the day content (number)</li>
</ul>
</div>
</section>
);
};

@ -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;
}

@ -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);
};
Loading…
Cancel
Save