Browse Source

feat: calendar support weekStartsOn, close #1020 (#1028)

* feat: calendar support weekStartsOn, close #1020

* docs: update calendar api docs

* test: add unit test of calendar weekStartson
pointhalo 3 years ago
parent
commit
28bd50e9a6

+ 32 - 0
content/show/calendar/index-en-US.md

@@ -55,6 +55,37 @@ import { Calendar } from '@douyinfe/semi-ui';
 );
 );
 ```
 ```
 
 
+### Set week start day
+The day of the week can be set as the first day of the week through weekStartsOn, 0 for Sunday, 1 for Monday, and so on. Default is Sunday.  
+`weekStartsOn` is available since v2.18, and takes effect for month view and week view.
+
+```jsx live=true dir="column"
+import React, { useState } from 'react';
+import { RadioGroup, Calendar, Radio } from '@douyinfe/semi-ui';
+
+() => {
+    const [v, setV] = useState(0);
+    return (
+        <div>
+            <RadioGroup defaultValue={v} aria-label="StartOfWeek" name="demo-radio-group-vertical" onChange={e => setV(e.target.value)}>
+                <Radio value={0}>Sunday</Radio>
+                <Radio value={1}>Mon</Radio>
+                <Radio value={2}>Tue</Radio>
+                <Radio value={3}>Wed</Radio>
+                <Radio value={4}>Thu</Radio>
+                <Radio value={5}>Fri</Radio>
+                <Radio value={6}>Sat</Radio>
+            </RadioGroup>
+            <Calendar
+                mode="month"
+                weekStartsOn={v}
+            ></Calendar>
+        </div>
+    );
+};
+```
+
+
 ### Range Mode
 ### Range Mode
 **>=1.5.0**  
 **>=1.5.0**  
 Range Mode. `range` is required which is a left-closed and right-open interval. 
 Range Mode. `range` is required which is a left-closed and right-open interval. 
@@ -302,6 +333,7 @@ import { Calendar } from '@douyinfe/semi-ui';
 | scrollTop    | Scroll height for displayed content in day and week mode                                               | number                | 400          |
 | scrollTop    | Scroll height for displayed content in day and week mode                                               | number                | 400          |
 | showCurrTime | Toggle whether to show red line of current time                                                        | boolean               | true         |
 | showCurrTime | Toggle whether to show red line of current time                                                        | boolean               | true         |
 | width        | Width                                                                                                  | string\|number        | -            |
 | width        | Width                                                                                                  | string\|number        | -            |
+| weekStartsOn | Take the day of the week as the first day of the week, 0 for Sunday, 1 for Monday, and so on. Support after v2.18 | number | 0 |
 
 
 
 
 ### Event Object
 ### Event Object

+ 31 - 0
content/show/calendar/index.md

@@ -49,6 +49,35 @@ import { Calendar } from '@douyinfe/semi-ui';
 () => <Calendar mode="month"></Calendar>;
 () => <Calendar mode="month"></Calendar>;
 ```
 ```
 
 
+### 设置周起始日
+可以通过 weekStartsOn 设置周几作为每周第一天,0 代表周日,1 代表周一,以此类推。默认为周日。weekStartsOn 自 v2.18 起提供,对月视图、周视图生效。
+```jsx live=true dir="column"
+import React, { useState } from 'react';
+import { RadioGroup, Calendar, Radio } from '@douyinfe/semi-ui';
+
+() => {
+    const [v, setV] = useState(0);
+    return (
+        <div>
+            <RadioGroup defaultValue={v} aria-label="周起始日" name="demo-radio-group-vertical" onChange={e => setV(e.target.value)}>
+                <Radio value={0}>周日</Radio>
+                <Radio value={1}>周一</Radio>
+                <Radio value={2}>周二</Radio>
+                <Radio value={3}>周三</Radio>
+                <Radio value={4}>周四</Radio>
+                <Radio value={5}>周五</Radio>
+                <Radio value={6}>周六</Radio>
+            </RadioGroup>
+            <Calendar
+                style={{ marginTop: 20 }}
+                mode="month"
+                weekStartsOn={v}
+            ></Calendar>
+        </div>
+    );
+};
+```
+
 ### 多日视图
 ### 多日视图
 
 
 **>=1.5.0**  
 **>=1.5.0**  
@@ -61,6 +90,7 @@ import { Calendar } from '@douyinfe/semi-ui';
 () => <Calendar mode="range" range={[new Date(2020, 8, 26), new Date(2020, 8, 31)]} />;
 () => <Calendar mode="range" range={[new Date(2020, 8, 26), new Date(2020, 8, 31)]} />;
 ```
 ```
 
 
+
 ### 事件渲染用法
 ### 事件渲染用法
 
 
 通过 `events` 传入需要渲染的事件,`events` 是一个由 event objects 组成的数组,具体形式请参考 events API。
 通过 `events` 传入需要渲染的事件,`events` 是一个由 event objects 组成的数组,具体形式请参考 events API。
@@ -278,6 +308,7 @@ import { Calendar } from '@douyinfe/semi-ui';
 | scrollTop | 日视图和周视图模式下,设置展示内容默认的滚动高度 | number | 400 |
 | scrollTop | 日视图和周视图模式下,设置展示内容默认的滚动高度 | number | 400 |
 | showCurrTime | 显示当前时间 | boolean | true |
 | showCurrTime | 显示当前时间 | boolean | true |
 | width | 日历宽度 | string\|number | - |
 | width | 日历宽度 | string\|number | - |
+| weekStartsOn | 以周几作为每周第一天,0 代表周日,1 代表周一,以此类推。v2.18后支持 | number | 0 |
 
 
 ### Event Object
 ### Event Object
 
 

+ 5 - 3
packages/semi-foundation/calendar/eventUtil.ts

@@ -16,7 +16,7 @@ import {
     endOfDay,
     endOfDay,
     startOfDay,
     startOfDay,
     toDate,
     toDate,
-    Locale
+    Locale,
 } from 'date-fns';
 } from 'date-fns';
 import { EventObject, ParsedRangeEvent } from './foundation';
 import { EventObject, ParsedRangeEvent } from './foundation';
 
 
@@ -96,6 +96,8 @@ export interface DateObj {
     month: string;
     month: string;
 }
 }
 
 
+export type weeekStartsOnEnum = 0 | 1 | 2 | 3 | 4 | 5 | 6;
+
 export const calcRangeData = (value: Date, start: Date, rangeLen: number, mode: string, locale: Locale) => {
 export const calcRangeData = (value: Date, start: Date, rangeLen: number, mode: string, locale: Locale) => {
     const today = getCurrDate();
     const today = getCurrDate();
     const arr: Array<DateObj> = [];
     const arr: Array<DateObj> = [];
@@ -125,8 +127,8 @@ export const calcRangeData = (value: Date, start: Date, rangeLen: number, mode:
  * @returns {object[]} { date: Date, dayString: string, ind: number, isToday: boolean, isWeekend: boolean, weekday: string }
  * @returns {object[]} { date: Date, dayString: string, ind: number, isToday: boolean, isWeekend: boolean, weekday: string }
  * create weekly object array
  * create weekly object array
  */
  */
-export const calcWeekData = (value: Date, mode = 'week', locale: Locale) => {
-    const start = startOfWeek(value);
+export const calcWeekData = (value: Date, mode = 'week', locale: Locale, weekStartsOn: weeekStartsOnEnum) => {
+    const start = startOfWeek(value, { weekStartsOn });
     return calcRangeData(value, start, 7, mode, locale);
     return calcRangeData(value, start, 7, mode, locale);
 };
 };
 
 

+ 7 - 3
packages/semi-foundation/calendar/foundation.ts

@@ -31,10 +31,12 @@ import {
     filterEvents,
     filterEvents,
     parseRangeAllDayEvent,
     parseRangeAllDayEvent,
     DateObj,
     DateObj,
-    checkWeekend
+    checkWeekend,
+    weeekStartsOnEnum
 } from './eventUtil';
 } from './eventUtil';
 
 
 
 
+export { weeekStartsOnEnum };
 export interface EventObject {
 export interface EventObject {
     [x: string]: any;
     [x: string]: any;
     key: string;
     key: string;
@@ -189,7 +191,8 @@ export default class CalendarFoundation<P = Record<string, any>, S = Record<stri
     getWeeklyData(value: Date, dateFnsLocale: Locale) {
     getWeeklyData(value: Date, dateFnsLocale: Locale) {
         const data = {} as WeeklyData;
         const data = {} as WeeklyData;
         data.month = format(value, 'LLL', { locale: dateFnsLocale });
         data.month = format(value, 'LLL', { locale: dateFnsLocale });
-        data.week = calcWeekData(value, 'week', dateFnsLocale);
+        const { weekStartsOn } = this.getProps();
+        data.week = calcWeekData(value, 'week', dateFnsLocale, weekStartsOn);
         this._adapter.setWeeklyData(data);
         this._adapter.setWeeklyData(data);
         return data;
         return data;
     }
     }
@@ -207,9 +210,10 @@ export default class CalendarFoundation<P = Record<string, any>, S = Record<stri
     getMonthlyData(value: Date, dateFnsLocale: Locale) {
     getMonthlyData(value: Date, dateFnsLocale: Locale) {
         const monthStart = startOfMonth(value);
         const monthStart = startOfMonth(value);
         const data = {} as MonthData;
         const data = {} as MonthData;
+        const { weekStartsOn } = this.getProps();
         const numberOfWeek = getWeeksInMonth(value);
         const numberOfWeek = getWeeksInMonth(value);
         [...Array(numberOfWeek).keys()].map(ind => {
         [...Array(numberOfWeek).keys()].map(ind => {
-            data[ind] = calcWeekData(addDays(monthStart, ind * 7), 'month', dateFnsLocale);
+            data[ind] = calcWeekData(addDays(monthStart, ind * 7), 'month', dateFnsLocale, weekStartsOn);
         });
         });
         this._adapter.setMonthlyData(data);
         this._adapter.setMonthlyData(data);
         return data;
         return data;

+ 21 - 2
packages/semi-ui/calendar/__test__/calendar.test.js

@@ -50,8 +50,6 @@ describe('Calendar', () => {
         expect(clickHandler2).toHaveBeenCalledTimes(0)
         expect(clickHandler2).toHaveBeenCalledTimes(0)
         monthWrapper.find('.semi-calendar-month-skeleton li').at(0).simulate('click')
         monthWrapper.find('.semi-calendar-month-skeleton li').at(0).simulate('click')
         expect(clickHandler2).toHaveBeenCalledTimes(1)
         expect(clickHandler2).toHaveBeenCalledTimes(1)
-
-
     })
     })
     
     
     it('test dateGridRender', ()=>{
     it('test dateGridRender', ()=>{
@@ -160,4 +158,25 @@ describe('Calendar', () => {
         expect(dailyCalendar.find(`.eventDay`).length).toBe(eventOnJuly23.length)
         expect(dailyCalendar.find(`.eventDay`).length).toBe(eventOnJuly23.length)
 
 
     })
     })
+
+    it('test weekStartsOn', () => {
+        const displayValue = new Date(2022, 7, 1, 8, 32, 0);
+
+        let calendar = mount(<Calendar
+                    height={400}
+                    mode={'month'}
+                    weekStartsOn={3}
+                    displayValue={displayValue}
+                ></Calendar>);
+        let firstHead = calendar.find('.semi-calendar-month-header li').at(0).text();
+        expect(firstHead).toEqual('周三');
+
+        let defaultCalendar = mount(<Calendar
+                    height={400}
+                    mode={'month'}
+                    displayValue={displayValue}
+                ></Calendar>);
+        let defaultFirstHead = defaultCalendar.find('.semi-calendar-month-header li').at(0).text();
+        expect(defaultFirstHead).toEqual('周日');
+    });
 })
 })

+ 31 - 0
packages/semi-ui/calendar/_story/calendar.stories.js

@@ -527,3 +527,34 @@ class EventRenderDemo extends React.Component {
 }
 }
 
 
 export const EventRender  = () => <EventRenderDemo />;
 export const EventRender  = () => <EventRenderDemo />;
+
+
+export const WeekStartsOnDemo = () => {
+    const [v, setV] = useState(0);
+    return (
+        <div>
+            <RadioGroup defaultValue={v} aria-label="周起始日" name="demo-radio-group-vertical" onChange={e => setV(e.target.value)}>
+                <Radio value={1}>周一</Radio>
+                <Radio value={2}>周二</Radio>
+                <Radio value={3}>周三</Radio>
+                <Radio value={4}>周四</Radio>
+                <Radio value={5}>周五</Radio>
+                <Radio value={6}>周六</Radio>
+                <Radio value={0}>周日</Radio>
+            </RadioGroup>
+            <Calendar
+                mode="month"
+                weekStartsOn={v}
+                dateGridRender={(dateString, date) => {
+                    console.log(dateString);
+                    if (dateString === new Date(2019, 6, 16).toString()) {
+                    return (
+                        <div style={{ backgroundColor: 'red', height: '100%', width: '100%' }}>123test</div>
+                    );
+                    }
+                    return null;
+                }}
+            ></Calendar>
+      </div>
+    )
+}

+ 3 - 1
packages/semi-ui/calendar/index.tsx

@@ -25,6 +25,7 @@ class Calendar extends BaseComponent<CalendarProps, {}> {
         })),
         })),
         mode: PropTypes.string,
         mode: PropTypes.string,
         showCurrTime: PropTypes.bool,
         showCurrTime: PropTypes.bool,
+        weekStartsOn: PropTypes.number,
         scrollTop: PropTypes.number,
         scrollTop: PropTypes.number,
         onClick: PropTypes.func,
         onClick: PropTypes.func,
         renderTimeDisplay: PropTypes.func,
         renderTimeDisplay: PropTypes.func,
@@ -42,7 +43,8 @@ class Calendar extends BaseComponent<CalendarProps, {}> {
         mode: 'week',
         mode: 'week',
         markWeekend: false,
         markWeekend: false,
         height: 600,
         height: 600,
-        scrollTop: 400
+        scrollTop: 400,
+        weekStartsOn: 0,
     };
     };
 
 
     render() {
     render() {

+ 2 - 1
packages/semi-ui/calendar/interface.ts

@@ -1,4 +1,4 @@
-import { EventObject } from '@douyinfe/semi-foundation/calendar/foundation';
+import { EventObject, weeekStartsOnEnum } from '@douyinfe/semi-foundation/calendar/foundation';
 import { strings } from '@douyinfe/semi-foundation/calendar/constants';
 import { strings } from '@douyinfe/semi-foundation/calendar/constants';
 import { ArrayElement } from '../_base/base';
 import { ArrayElement } from '../_base/base';
 import { BaseProps } from '../_base/baseComponent';
 import { BaseProps } from '../_base/baseComponent';
@@ -10,6 +10,7 @@ export interface CalendarProps extends BaseProps {
     events?: EventObject[];
     events?: EventObject[];
     mode?: ArrayElement<typeof strings.MODE>;
     mode?: ArrayElement<typeof strings.MODE>;
     showCurrTime?: boolean;
     showCurrTime?: boolean;
+    weekStartsOn?: weeekStartsOnEnum;
     scrollTop?: number;
     scrollTop?: number;
     onClick?: (e: React.MouseEvent, value: Date) => void;
     onClick?: (e: React.MouseEvent, value: Date) => void;
     onClose?: (e: React.MouseEvent) => void;
     onClose?: (e: React.MouseEvent) => void;