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',