Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add classNames #926

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 54 additions & 7 deletions packages/react-calendar/src/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
isView,
rangeOf,
} from './shared/propTypes.js';
import { between } from './shared/utils.js';
import { between, mergeTileClassNames, type TileClassNames } from './shared/utils.js';

import type {
Action,
Expand Down Expand Up @@ -60,6 +60,15 @@ defaultMinDate.setFullYear(1, 0, 1);
defaultMinDate.setHours(0, 0, 0, 0);
const defaultMaxDate = new Date(8.64e15);

type CenturyClassNames = React.ComponentProps<typeof CenturyView>['classNames'];
type DecadeClassNames = React.ComponentProps<typeof DecadeView>['classNames'];
type MonthClassNames = React.ComponentProps<typeof MonthView>['classNames'];
type YearClassNames = React.ComponentProps<typeof YearView>['classNames'];
type NavigationClassNames = React.ComponentProps<typeof Navigation>['classNames'];

const localSlotNames = ['base', 'selectRange', 'doubleView', 'viewContainer'] as const;
type LocalSlotName = (typeof localSlotNames)[number];

export type CalendarProps = {
/**
* The beginning of a period that shall be displayed. If you wish to use react-calendar in an uncontrolled way, use `defaultActiveStartDate` instead.
Expand Down Expand Up @@ -87,6 +96,22 @@ export type CalendarProps = {
* @example ['class1', 'class2 class3']
*/
className?: ClassName;
/**
* Class names that will be added to appropriate slots.
*
* @example {}
*/
classNames?: Partial<
Record<LocalSlotName, ClassName> & {
decade: DecadeClassNames;
century: CenturyClassNames;
month: MonthClassNames;
year: YearClassNames;
navigation: NavigationClassNames;
tileGroup: ClassName;
tile: TileClassNames;
}
>;
/**
* The beginning of a period that shall be displayed by default. If you wish to use react-calendar in a controlled way, use `activeStartDate` instead.
*
Expand Down Expand Up @@ -614,6 +639,7 @@ const Calendar = forwardRef(function Calendar(props: CalendarProps, ref) {
allowPartialRange,
calendarType,
className,
classNames = {},
defaultActiveStartDate,
defaultValue,
defaultView,
Expand Down Expand Up @@ -1042,6 +1068,10 @@ const Calendar = forwardRef(function Calendar(props: CalendarProps, ref) {
<CenturyView
formatYear={formatYear}
showNeighboringCentury={showNeighboringCentury}
classNames={{
...classNames.century,
tile: mergeTileClassNames(classNames.tile, classNames.century?.tile),
}}
{...commonProps}
/>
);
Expand All @@ -1051,13 +1081,25 @@ const Calendar = forwardRef(function Calendar(props: CalendarProps, ref) {
<DecadeView
formatYear={formatYear}
showNeighboringDecade={showNeighboringDecade}
classNames={{
...classNames.decade,
tile: mergeTileClassNames(classNames.tile, classNames.decade?.tile),
}}
{...commonProps}
/>
);
}
case 'year': {
return (
<YearView formatMonth={formatMonth} formatMonthYear={formatMonthYear} {...commonProps} />
<YearView
formatMonth={formatMonth}
formatMonthYear={formatMonthYear}
classNames={{
...classNames.year,
tile: mergeTileClassNames(classNames.tile, classNames.year?.tile),
}}
{...commonProps}
/>
);
}
case 'month': {
Expand All @@ -1077,6 +1119,10 @@ const Calendar = forwardRef(function Calendar(props: CalendarProps, ref) {
}
showNeighboringMonth={showNeighboringMonth}
showWeekNumbers={showWeekNumbers}
classNames={{
...classNames.month,
tile: mergeTileClassNames(classNames.tile, classNames.month?.tile),
}}
{...commonProps}
/>
);
Expand Down Expand Up @@ -1115,6 +1161,7 @@ const Calendar = forwardRef(function Calendar(props: CalendarProps, ref) {
showDoubleView={showDoubleView}
view={view}
views={views}
classNames={classNames.navigation}
/>
);
}
Expand All @@ -1124,16 +1171,16 @@ const Calendar = forwardRef(function Calendar(props: CalendarProps, ref) {
return (
<div
className={clsx(
baseClassName,
selectRange && valueArray.length === 1 && `${baseClassName}--selectRange`,
showDoubleView && `${baseClassName}--doubleView`,
className,
[baseClassName, className, classNames.base],
selectRange &&
valueArray.length === 1 && [`${baseClassName}--selectRange`, classNames.selectRange],
showDoubleView && [`${baseClassName}--doubleView`, classNames.doubleView],
)}
ref={inputRef}
>
{renderNavigation()}
<div
className={`${baseClassName}__viewContainer`}
className={clsx(`${baseClassName}__viewContainer`, classNames.viewContainer)}
onBlur={selectRange ? onMouseLeave : undefined}
onMouseLeave={selectRange ? onMouseLeave : undefined}
>
Expand Down
72 changes: 62 additions & 10 deletions packages/react-calendar/src/Calendar/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import React from 'react';
import { getUserLocale } from 'get-user-locale';
import clsx from 'clsx';

import {
getCenturyLabel,
Expand All @@ -18,11 +19,27 @@ import {
formatYear as defaultFormatYear,
} from '../shared/dateFormatter.js';

import type { Action, NavigationLabelFunc, RangeType } from '../shared/types.js';
import type { Action, ClassName, NavigationLabelFunc, RangeType } from '../shared/types.js';

const className = 'react-calendar__navigation';

const slotNames = [
'base',
'label',
'divider',
'labelText',
'labelTextFrom',
'labelTextTo',
'arrow',
'prev2Button',
'prevButton',
'nextButton',
'next2Button',
] as const;
type SlotName = (typeof slotNames)[number];

type NavigationProps = {
classNames?: Partial<Record<SlotName, ClassName>>;
/**
* The beginning of a period that shall be displayed. If you wish to use react-calendar in an uncontrolled way, use `defaultActiveStartDate` instead.
*
Expand Down Expand Up @@ -173,6 +190,7 @@ export default function Navigation({
showDoubleView,
view,
views,
classNames = {},
}: NavigationProps) {
const drillUpAvailable = views.indexOf(view) > 0;
const shouldShowPrevNext2Buttons = view !== 'century';
Expand Down Expand Up @@ -257,19 +275,33 @@ export default function Navigation({
<button
aria-label={navigationAriaLabel}
aria-live={navigationAriaLive}
className={labelClassName}
className={clsx(labelClassName, classNames.label)}
disabled={!drillUpAvailable}
onClick={drillUp}
style={{ flexGrow: 1 }}
type="button"
>
<span className={`${labelClassName}__labelText ${labelClassName}__labelText--from`}>
<span
className={clsx(
`${labelClassName}__labelText`,
`${labelClassName}__labelText--from`,
classNames.labelText,
classNames.labelTextFrom,
)}
>
{renderLabel(activeStartDate)}
</span>
{showDoubleView ? (
<>
<span className={`${labelClassName}__divider`}> – </span>
<span className={`${labelClassName}__labelText ${labelClassName}__labelText--to`}>
<span className={clsx(`${labelClassName}__divider`, classNames.divider)}> – </span>
<span
className={clsx(
`${labelClassName}__labelText`,
`${labelClassName}__labelText--to`,
classNames.labelText,
classNames.labelTextTo,
)}
>
{renderLabel(nextActiveStartDate)}
</span>
</>
Expand All @@ -279,11 +311,16 @@ export default function Navigation({
}

return (
<div className={className}>
<div className={clsx(className, classNames.base)}>
{prev2Label !== null && shouldShowPrevNext2Buttons ? (
<button
aria-label={prev2AriaLabel}
className={`${className}__arrow ${className}__prev2-button`}
className={clsx(
`${className}__arrow`,
`${className}__prev2-button`,
classNames.arrow,
classNames.prev2Button,
)}
disabled={prev2ButtonDisabled}
onClick={onClickPrevious2}
type="button"
Expand All @@ -294,7 +331,12 @@ export default function Navigation({
{prevLabel !== null && (
<button
aria-label={prevAriaLabel}
className={`${className}__arrow ${className}__prev-button`}
className={clsx(
`${className}__arrow`,
`${className}__prev-button`,
classNames.arrow,
classNames.prevButton,
)}
disabled={prevButtonDisabled}
onClick={onClickPrevious}
type="button"
Expand All @@ -306,7 +348,12 @@ export default function Navigation({
{nextLabel !== null && (
<button
aria-label={nextAriaLabel}
className={`${className}__arrow ${className}__next-button`}
className={clsx(
`${className}__arrow`,
`${className}__next-button`,
classNames.arrow,
classNames.nextButton,
)}
disabled={nextButtonDisabled}
onClick={onClickNext}
type="button"
Expand All @@ -317,7 +364,12 @@ export default function Navigation({
{next2Label !== null && shouldShowPrevNext2Buttons ? (
<button
aria-label={next2AriaLabel}
className={`${className}__arrow ${className}__next2-button`}
className={clsx(
`${className}__arrow`,
`${className}__next2-button`,
classNames.arrow,
classNames.next2Button,
)}
disabled={next2ButtonDisabled}
onClick={onClickNext2}
type="button"
Expand Down
2 changes: 1 addition & 1 deletion packages/react-calendar/src/CenturyView.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe('CenturyView', () => {
return 'firstDayOfTheMonth';
}

return null;
return;
};

const { container } = render(
Expand Down
25 changes: 20 additions & 5 deletions packages/react-calendar/src/CenturyView.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
import React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';

import Decades from './CenturyView/Decades.js';
import Decades, { slotNames as decadesSlotNames } from './CenturyView/Decades.js';

import { tileGroupProps } from './shared/propTypes.js';
import type { ClassName } from './shared/types.js';
import { pickClassNames } from './shared/utils.js';

type CenturyViewProps = React.ComponentProps<typeof Decades>;
const localSlotNames = ['base'] as const;
type LocalSlotName = (typeof localSlotNames)[number];

type DecadesProps = React.ComponentProps<typeof Decades>;

type CenturyViewProps = Omit<DecadesProps, 'classNames'> & {
classNames?: DecadesProps['classNames'] & Partial<Record<LocalSlotName, ClassName>>;
};

/**
* Displays a given century.
*/
const CenturyView: React.FC<CenturyViewProps> = function CenturyView(props) {
const CenturyView: React.FC<CenturyViewProps> = function CenturyView({
classNames = {},
...props
}) {
function renderDecades() {
return <Decades {...props} />;
return <Decades {...props} classNames={pickClassNames(classNames, decadesSlotNames)} />;
}

return <div className="react-calendar__century-view">{renderDecades()}</div>;
return (
<div className={clsx('react-calendar__century-view', classNames.base)}>{renderDecades()}</div>
);
};

CenturyView.propTypes = {
Expand Down
10 changes: 7 additions & 3 deletions packages/react-calendar/src/CenturyView/Decade.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@ import Decade from './Decade.js';

const tileProps = {
activeStartDate: new Date(2018, 0, 1),
classes: ['react-calendar__tile'],
classNames: {
decadeTile: 'react-calendar__tile',
},
currentCentury: 2001,
date: new Date(2011, 0, 1),
};
} satisfies React.ComponentProps<typeof Decade>;

describe('Decade', () => {
it('applies given classNames properly', () => {
const { container } = render(
<Decade
{...tileProps}
classes={['react-calendar__tile', 'react-calendar__tile--flag']}
classNames={{
decadeTile: ['react-calendar__tile', 'react-calendar__tile--flag'],
}}
tileClassName={() => 'testFunctionClassName'}
/>,
);
Expand Down
Loading