From f825743cc7142a8b521a7d3e7be02fc49d882dc3 Mon Sep 17 00:00:00 2001
From: MariaBanaszkiewicz
<80327686+MariaBanaszkiewicz@users.noreply.github.com>
Date: Thu, 28 Nov 2024 11:55:31 +0100
Subject: [PATCH] [@mantine/dates] Add `withWeekNumbers` prop support to all
components based on Calendar (#7179)
* add week numbers to month component
* add date picker demo and test
* fix prettier and types issues
* changes after review
---
.../DatePicker.demo.withWeekNumbers.tsx | 21 ++++++++++
.../DatePicker/DatePicker.demos.story.tsx | 5 +++
.../demos/src/demos/dates/DatePicker/index.ts | 1 +
.../styles-api/src/data/Dates.styles-api.ts | 1 +
.../src/components/Calendar/Calendar.tsx | 2 +
.../CalendarHeader/CalendarHeader.module.css | 2 +-
.../src/components/Month/Month.module.css | 13 ++++++
.../src/components/Month/Month.story.tsx | 4 ++
.../dates/src/components/Month/Month.tsx | 19 +++++++++
.../get-week-number/get-week-number.test.ts | 42 +++++++++++++++++++
.../Month/get-week-number/get-week-number.ts | 9 ++++
.../src/components/MonthLevel/MonthLevel.tsx | 2 +
.../MonthLevelGroup/MonthLevelGroup.tsx | 2 +
.../components/WeekdaysRow/WeekdaysRow.tsx | 5 +++
scripts/build/rollup/rollup-externals.ts | 1 +
15 files changed, 128 insertions(+), 1 deletion(-)
create mode 100644 packages/@docs/demos/src/demos/dates/DatePicker/DatePicker.demo.withWeekNumbers.tsx
create mode 100644 packages/@mantine/dates/src/components/Month/get-week-number/get-week-number.test.ts
create mode 100644 packages/@mantine/dates/src/components/Month/get-week-number/get-week-number.ts
diff --git a/packages/@docs/demos/src/demos/dates/DatePicker/DatePicker.demo.withWeekNumbers.tsx b/packages/@docs/demos/src/demos/dates/DatePicker/DatePicker.demo.withWeekNumbers.tsx
new file mode 100644
index 00000000000..2f856b4c9d6
--- /dev/null
+++ b/packages/@docs/demos/src/demos/dates/DatePicker/DatePicker.demo.withWeekNumbers.tsx
@@ -0,0 +1,21 @@
+import { DatePicker } from '@mantine/dates';
+import { MantineDemo } from '@mantinex/demo';
+
+const code = `
+import { DatePicker } from '@mantine/dates';
+
+function Demo() {
+ return ;
+}
+`;
+
+function Demo() {
+ return ;
+}
+
+export const withWeekNumbers: MantineDemo = {
+ type: 'code',
+ centered: true,
+ component: Demo,
+ code,
+};
diff --git a/packages/@docs/demos/src/demos/dates/DatePicker/DatePicker.demos.story.tsx b/packages/@docs/demos/src/demos/dates/DatePicker/DatePicker.demos.story.tsx
index 4a2fd58db67..f1a879231c3 100644
--- a/packages/@docs/demos/src/demos/dates/DatePicker/DatePicker.demos.story.tsx
+++ b/packages/@docs/demos/src/demos/dates/DatePicker/DatePicker.demos.story.tsx
@@ -112,3 +112,8 @@ export const Demo_excludeDate = {
name: '⭐ Demo: excludeDate',
render: renderDemo(demos.excludeDate),
};
+
+export const Demo_withWeekNumbers = {
+ name: '⭐ Demo: withWeekNumbers',
+ render: renderDemo(demos.withWeekNumbers),
+};
diff --git a/packages/@docs/demos/src/demos/dates/DatePicker/index.ts b/packages/@docs/demos/src/demos/dates/DatePicker/index.ts
index 09d04b3abe3..a9edbe4e366 100644
--- a/packages/@docs/demos/src/demos/dates/DatePicker/index.ts
+++ b/packages/@docs/demos/src/demos/dates/DatePicker/index.ts
@@ -19,3 +19,4 @@ export { renderDay } from './DatePicker.demo.renderDay';
export { hideWeekdays } from './DatePicker.demo.hideWeekdays';
export { hideOutsideDates } from './DatePicker.demo.hideOutsideDates';
export { excludeDate } from './DatePicker.demo.excludeDate';
+export { withWeekNumbers } from './DatePicker.demo.withWeekNumbers';
diff --git a/packages/@docs/styles-api/src/data/Dates.styles-api.ts b/packages/@docs/styles-api/src/data/Dates.styles-api.ts
index 1db27743fe4..10877acc789 100644
--- a/packages/@docs/styles-api/src/data/Dates.styles-api.ts
+++ b/packages/@docs/styles-api/src/data/Dates.styles-api.ts
@@ -28,6 +28,7 @@ export const MonthStylesApi: StylesApiData = {
weekdaysRow: 'Weekdays tr element',
weekday: 'Weekday th element',
day: 'Month day control',
+ weekNumber: 'Week number td element',
},
vars: {},
diff --git a/packages/@mantine/dates/src/components/Calendar/Calendar.tsx b/packages/@mantine/dates/src/components/Calendar/Calendar.tsx
index 484253112fa..08cda126f88 100644
--- a/packages/@mantine/dates/src/components/Calendar/Calendar.tsx
+++ b/packages/@mantine/dates/src/components/Calendar/Calendar.tsx
@@ -204,6 +204,7 @@ export const Calendar = factory((_props, ref) => {
__onDayMouseEnter,
withCellSpacing,
highlightToday,
+ withWeekNumbers,
// YearLevelGroup props
monthsListFormat,
@@ -349,6 +350,7 @@ export const Calendar = factory((_props, ref) => {
static={isStatic}
withCellSpacing={withCellSpacing}
highlightToday={highlightToday}
+ withWeekNumbers={withWeekNumbers}
{...stylesApiProps}
/>
)}
diff --git a/packages/@mantine/dates/src/components/CalendarHeader/CalendarHeader.module.css b/packages/@mantine/dates/src/components/CalendarHeader/CalendarHeader.module.css
index 553ad308646..b00dd8abc14 100644
--- a/packages/@mantine/dates/src/components/CalendarHeader/CalendarHeader.module.css
+++ b/packages/@mantine/dates/src/components/CalendarHeader/CalendarHeader.module.css
@@ -7,7 +7,7 @@
--dch-control-size: var(--dch-control-size-sm);
display: flex;
- max-width: calc(var(--dch-control-size) * 7 + rem(7px));
+ max-width: calc(var(--dch-control-size) * 8 + rem(7px));
margin-bottom: var(--mantine-spacing-xs);
}
diff --git a/packages/@mantine/dates/src/components/Month/Month.module.css b/packages/@mantine/dates/src/components/Month/Month.module.css
index ac4073754fd..ccc3f258410 100644
--- a/packages/@mantine/dates/src/components/Month/Month.module.css
+++ b/packages/@mantine/dates/src/components/Month/Month.module.css
@@ -10,3 +10,16 @@
padding: 0.5px;
}
}
+
+.weekNumber {
+ --wn-size-xs: 30px;
+ --wn-size-sm: 36px;
+ --wn-size-md: 42px;
+ --wn-size-lg: 48px;
+ --wn-size-xl: 54px;
+ color: var(--mantine-color-dimmed);
+ font-weight: normal;
+ font-size: var(--wn-fz, var(--mantine-font-size-sm));
+ text-align: center;
+ width: var(--wn-size, var(--wn-size-sm));
+}
diff --git a/packages/@mantine/dates/src/components/Month/Month.story.tsx b/packages/@mantine/dates/src/components/Month/Month.story.tsx
index 34c5f020588..91b6c621ad5 100644
--- a/packages/@mantine/dates/src/components/Month/Month.story.tsx
+++ b/packages/@mantine/dates/src/components/Month/Month.story.tsx
@@ -100,3 +100,7 @@ export function Sizes() {
));
return sizes;
}
+
+export function withWeekNumbers() {
+ return ;
+}
diff --git a/packages/@mantine/dates/src/components/Month/Month.tsx b/packages/@mantine/dates/src/components/Month/Month.tsx
index 7f189154e90..af55c4e3eb2 100644
--- a/packages/@mantine/dates/src/components/Month/Month.tsx
+++ b/packages/@mantine/dates/src/components/Month/Month.tsx
@@ -2,9 +2,12 @@ import dayjs from 'dayjs';
import {
Box,
BoxProps,
+ createVarsResolver,
ElementProps,
factory,
Factory,
+ getFontSize,
+ getSize,
MantineSize,
StylesApiProps,
useProps,
@@ -17,6 +20,7 @@ import { Day, DayProps, DayStylesNames } from '../Day';
import { WeekdaysRow } from '../WeekdaysRow';
import { getDateInTabOrder } from './get-date-in-tab-order/get-date-in-tab-order';
import { getMonthDays } from './get-month-days/get-month-days';
+import { getWeekNumber } from './get-week-number/get-week-number';
import { isAfterMinDate } from './is-after-min-date/is-after-min-date';
import { isBeforeMaxDate } from './is-before-max-date/is-before-max-date';
import { isSameMonth } from './is-same-month/is-same-month';
@@ -31,6 +35,7 @@ export type MonthStylesNames =
| 'monthThead'
| 'monthTbody'
| 'monthCell'
+ | 'weekNumber'
| DayStylesNames;
export interface MonthSettings {
@@ -99,6 +104,9 @@ export interface MonthSettings {
/** Determines whether today should be highlighted with a border, `false` by default */
highlightToday?: boolean;
+
+ /** Determines whether week numbers should be displayed */
+ withWeekNumbers?: boolean;
}
export interface MonthProps
@@ -125,6 +133,13 @@ const defaultProps: Partial = {
withCellSpacing: true,
};
+const varsResolver = createVarsResolver((_, { size }) => ({
+ weekNumber: {
+ '--wn-fz': getFontSize(size),
+ '--wn-size': getSize(size, 'wn-size'),
+ },
+}));
+
export const Month = factory((_props, ref) => {
const props = useProps('Month', defaultProps, _props);
const {
@@ -158,6 +173,7 @@ export const Month = factory((_props, ref) => {
withCellSpacing,
size,
highlightToday,
+ withWeekNumbers,
...others
} = props;
@@ -171,6 +187,7 @@ export const Month = factory((_props, ref) => {
styles,
unstyled,
vars,
+ varsResolver,
rootSelector: 'month',
});
@@ -261,6 +278,7 @@ export const Month = factory((_props, ref) => {
return (
+ {withWeekNumbers && {getWeekNumber(row)} | }
{cells}
);
@@ -279,6 +297,7 @@ export const Month = factory((_props, ref) => {
classNames={resolvedClassNames}
styles={resolvedStyles}
unstyled={unstyled}
+ withWeekNumbers={withWeekNumbers}
/>
)}
diff --git a/packages/@mantine/dates/src/components/Month/get-week-number/get-week-number.test.ts b/packages/@mantine/dates/src/components/Month/get-week-number/get-week-number.test.ts
new file mode 100644
index 00000000000..61c2a32fa11
--- /dev/null
+++ b/packages/@mantine/dates/src/components/Month/get-week-number/get-week-number.test.ts
@@ -0,0 +1,42 @@
+import { getWeekNumber } from './get-week-number';
+
+describe('@mantine/dates/get-week-number', () => {
+ it('should return the correct ISO week number for a given week', () => {
+ const week = [
+ new Date('2023-01-02'), // Monday
+ new Date('2023-01-03'), // Tuesday
+ new Date('2023-01-04'), // Wednesday
+ new Date('2023-01-05'), // Thursday
+ new Date('2023-01-06'), // Friday
+ new Date('2023-01-07'), // Saturday
+ new Date('2023-01-08'), // Sunday
+ ];
+ expect(getWeekNumber(week)).toBe(1);
+ });
+
+ it('should return the correct ISO week number when the week spans two years', () => {
+ const week = [
+ new Date('2022-12-26'), // Monday
+ new Date('2022-12-27'), // Tuesday
+ new Date('2022-12-28'), // Wednesday
+ new Date('2022-12-29'), // Thursday
+ new Date('2022-12-30'), // Friday
+ new Date('2022-12-31'), // Saturday
+ new Date('2023-01-01'), // Sunday
+ ];
+ expect(getWeekNumber(week)).toBe(52);
+ });
+
+ it('should return the correct ISO week number for a week in the middle of the year', () => {
+ const week = [
+ new Date('2023-06-12'), // Monday
+ new Date('2023-06-13'), // Tuesday
+ new Date('2023-06-14'), // Wednesday
+ new Date('2023-06-15'), // Thursday
+ new Date('2023-06-16'), // Friday
+ new Date('2023-06-17'), // Saturday
+ new Date('2023-06-18'), // Sunday
+ ];
+ expect(getWeekNumber(week)).toBe(24);
+ });
+});
diff --git a/packages/@mantine/dates/src/components/Month/get-week-number/get-week-number.ts b/packages/@mantine/dates/src/components/Month/get-week-number/get-week-number.ts
new file mode 100644
index 00000000000..4d414198590
--- /dev/null
+++ b/packages/@mantine/dates/src/components/Month/get-week-number/get-week-number.ts
@@ -0,0 +1,9 @@
+import dayjs from 'dayjs';
+import isoWeek from 'dayjs/plugin/isoWeek.js';
+
+dayjs.extend(isoWeek);
+
+export function getWeekNumber(week: Date[]): number {
+ const monday = week.find((date) => dayjs(date).day() === 1);
+ return dayjs(monday).isoWeek();
+}
diff --git a/packages/@mantine/dates/src/components/MonthLevel/MonthLevel.tsx b/packages/@mantine/dates/src/components/MonthLevel/MonthLevel.tsx
index eec35872f99..a1ed55a3f18 100644
--- a/packages/@mantine/dates/src/components/MonthLevel/MonthLevel.tsx
+++ b/packages/@mantine/dates/src/components/MonthLevel/MonthLevel.tsx
@@ -77,6 +77,7 @@ export const MonthLevel = factory((_props, ref) => {
__onDayMouseEnter,
withCellSpacing,
highlightToday,
+ withWeekNumbers,
// CalendarHeader settings
__preventFocus,
@@ -181,6 +182,7 @@ export const MonthLevel = factory((_props, ref) => {
static={isStatic}
withCellSpacing={withCellSpacing}
highlightToday={highlightToday}
+ withWeekNumbers={withWeekNumbers}
{...stylesApiProps}
/>
diff --git a/packages/@mantine/dates/src/components/MonthLevelGroup/MonthLevelGroup.tsx b/packages/@mantine/dates/src/components/MonthLevelGroup/MonthLevelGroup.tsx
index 8c94864c5bc..fd39d2372b6 100644
--- a/packages/@mantine/dates/src/components/MonthLevelGroup/MonthLevelGroup.tsx
+++ b/packages/@mantine/dates/src/components/MonthLevelGroup/MonthLevelGroup.tsx
@@ -60,6 +60,7 @@ export const MonthLevelGroup = factory((_props, ref) =>
__onDayMouseEnter,
withCellSpacing,
highlightToday,
+ withWeekNumbers,
// CalendarHeader settings
__preventFocus,
@@ -162,6 +163,7 @@ export const MonthLevelGroup = factory((_props, ref) =>
static={isStatic}
withCellSpacing={withCellSpacing}
highlightToday={highlightToday}
+ withWeekNumbers={withWeekNumbers}
/>
);
});
diff --git a/packages/@mantine/dates/src/components/WeekdaysRow/WeekdaysRow.tsx b/packages/@mantine/dates/src/components/WeekdaysRow/WeekdaysRow.tsx
index d47897940a2..0d89471f25a 100644
--- a/packages/@mantine/dates/src/components/WeekdaysRow/WeekdaysRow.tsx
+++ b/packages/@mantine/dates/src/components/WeekdaysRow/WeekdaysRow.tsx
@@ -42,6 +42,9 @@ export interface WeekdaysRowProps
/** Choose cell type that will be used to render weekdays, defaults to th */
cellComponent?: 'td' | 'th';
+
+ /** Determines whether week numbers should be displayed */
+ withWeekNumbers?: boolean;
}
export type WeekdaysRowFactory = Factory<{
@@ -74,6 +77,7 @@ export const WeekdaysRow = factory((_props, ref) => {
weekdayFormat,
cellComponent: CellComponent = 'th',
__staticSelector,
+ withWeekNumbers,
...others
} = props;
@@ -105,6 +109,7 @@ export const WeekdaysRow = factory((_props, ref) => {
return (
+ {withWeekNumbers && #}
{weekdays}
);
diff --git a/scripts/build/rollup/rollup-externals.ts b/scripts/build/rollup/rollup-externals.ts
index dbc5717a905..626a8e4572b 100644
--- a/scripts/build/rollup/rollup-externals.ts
+++ b/scripts/build/rollup/rollup-externals.ts
@@ -7,6 +7,7 @@ export const ROLLUP_EXTERNALS = [
'dayjs/plugin/customParseFormat',
'dayjs/plugin/utc.js',
'dayjs/plugin/timezone.js',
+ 'dayjs/plugin/isoWeek.js',
'klona/full',
'highlight.js/lib/languages/typescript',
'react-is',