-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #112 from valcosmos/dev
feat: add calendar
- Loading branch information
Showing
13 changed files
with
469 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import dayjs, { Dayjs } from 'dayjs' | ||
import MonthCalendar from './MonthCalendar' | ||
import './index.scss' | ||
import Header from './Header' | ||
import { CSSProperties, ReactNode, useState } from 'react' | ||
import cs from 'classnames' | ||
import LocaleContext from './LocaleContext' | ||
|
||
export interface CalendarProps { | ||
value: Dayjs | ||
style?: CSSProperties | ||
className?: string | string[] | ||
// 定制日期显示,会完全覆盖日期单元格 | ||
dateRender?: (currentDate: Dayjs) => ReactNode | ||
// 定制日期单元格,内容会被添加到单元格内,只在全屏日历模式下生效。 | ||
dateInnerContent?: (currentDate: Dayjs) => ReactNode | ||
// 国际化相关 | ||
locale?: string | ||
onChange?: (date: Dayjs) => void | ||
} | ||
|
||
export function Calendar(props: CalendarProps) { | ||
const { value, style, className, locale, onChange } = props | ||
|
||
const [curValue, setCurValue] = useState<Dayjs>(value) | ||
|
||
const [curMonth, setCurMonth] = useState<Dayjs>(value) | ||
|
||
const classNames = cs('calendar', className) | ||
|
||
function selectHandler(date: Dayjs) { | ||
setCurValue(date) | ||
setCurMonth(date) | ||
onChange?.(date) | ||
} | ||
|
||
function prevMonthHandler() { | ||
setCurMonth(curMonth.subtract(1, 'month')) | ||
} | ||
|
||
function nextMonthHandler() { | ||
setCurMonth(curMonth.add(1, 'month')) | ||
} | ||
|
||
function todayHandler() { | ||
const date = dayjs(Date.now()) | ||
|
||
setCurValue(date) | ||
setCurMonth(date) | ||
onChange?.(date) | ||
} | ||
|
||
return ( | ||
<LocaleContext.Provider | ||
value={{ | ||
locale: locale || navigator.language, | ||
}} | ||
> | ||
<div className={classNames} style={style}> | ||
<Header | ||
curMonth={curMonth} | ||
prevMonthHandler={prevMonthHandler} | ||
nextMonthHandler={nextMonthHandler} | ||
todayHandler={todayHandler} | ||
/> | ||
<MonthCalendar | ||
{...props} | ||
value={curValue} | ||
curMonth={curMonth} | ||
selectHandler={selectHandler} | ||
/> | ||
</div> | ||
</LocaleContext.Provider> | ||
) | ||
} | ||
|
||
export default Calendar |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { Dayjs } from 'dayjs' | ||
import { useContext } from 'react' | ||
import LocaleContext from './LocaleContext' | ||
import allLocales from './locale' | ||
interface HeaderProps { | ||
curMonth: Dayjs | ||
prevMonthHandler: () => void | ||
nextMonthHandler: () => void | ||
todayHandler: () => void | ||
} | ||
function Header(props: HeaderProps) { | ||
const { curMonth, prevMonthHandler, nextMonthHandler, todayHandler } = props | ||
|
||
const localeContext = useContext(LocaleContext) | ||
const CalendarContext = allLocales[localeContext.locale] | ||
|
||
return ( | ||
<div className="calendar-header"> | ||
<div className="calendar-header-left"> | ||
<div className="calendar-header-icon" onClick={prevMonthHandler}> | ||
< | ||
</div> | ||
<div className="calendar-header-value">{curMonth.format(CalendarContext.formatMonth)}</div> | ||
<div className="calendar-header-icon" onClick={nextMonthHandler}> | ||
> | ||
</div> | ||
<button className="calendar-header-btn" onClick={todayHandler}> | ||
{CalendarContext.today} | ||
</button> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
export default Header |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { createContext } from 'react' | ||
|
||
export interface LocaleContextType { | ||
locale: string | ||
} | ||
|
||
const LocaleContext = createContext<LocaleContextType>({ | ||
locale: 'zh-CN', | ||
}) | ||
|
||
export default LocaleContext |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import { Dayjs } from 'dayjs' | ||
import { CalendarProps } from '.' | ||
import LocaleContext from './LocaleContext' | ||
import { useContext } from 'react' | ||
import allLocales from './locale' | ||
import cs from 'classnames' | ||
|
||
interface MonthCalendarProps extends CalendarProps { | ||
selectHandler?: (date: Dayjs) => void | ||
curMonth: Dayjs | ||
} | ||
|
||
function getAllDays(date: Dayjs) { | ||
const startDate = date.startOf('month') | ||
const day = startDate.day() | ||
|
||
const daysInfo: Array<{ date: Dayjs; currentMonth: boolean }> = new Array(6 * 7) | ||
|
||
for (let i = 0; i < day; i++) { | ||
daysInfo[i] = { | ||
date: startDate.subtract(day - i, 'day'), | ||
currentMonth: false, | ||
} | ||
} | ||
|
||
for (let i = day; i < daysInfo.length; i++) { | ||
const calcDate = startDate.add(i - day, 'day') | ||
|
||
daysInfo[i] = { | ||
date: calcDate, | ||
currentMonth: calcDate.month() === date.month(), | ||
} | ||
} | ||
|
||
return daysInfo | ||
} | ||
|
||
function MonthCalendar(props: MonthCalendarProps) { | ||
const localeContext = useContext(LocaleContext) | ||
|
||
const { value, curMonth, dateRender, dateInnerContent, selectHandler } = props | ||
|
||
const CalendarLocale = allLocales[localeContext.locale] | ||
|
||
const weekList = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'] | ||
|
||
const allDays = getAllDays(curMonth) | ||
|
||
function renderDays(days: Array<{ date: Dayjs; currentMonth: boolean }>) { | ||
const rows = [] | ||
for (let i = 0; i < 6; i++) { | ||
const row = [] | ||
for (let j = 0; j < 7; j++) { | ||
const item = days[i * 7 + j] | ||
row[j] = ( | ||
<div | ||
className={ | ||
'calendar-month-body-cell ' + | ||
(item.currentMonth ? 'calendar-month-body-cell-current' : '') | ||
} | ||
onClick={() => selectHandler?.(item.date)} | ||
> | ||
{dateRender ? ( | ||
dateRender(item.date) | ||
) : ( | ||
<div className="calendar-month-body-cell-date"> | ||
<div | ||
className={cs( | ||
'calendar-month-body-cell-date-value', | ||
value.format('YYYY-MM-DD') === item.date.format('YYYY-MM-DD') | ||
? 'calendar-month-body-cell-date-selected' | ||
: '', | ||
)} | ||
> | ||
{item.date.date()} | ||
</div> | ||
<div className="calendar-month-cell-body-date-content"> | ||
{dateInnerContent?.(item.date)} | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
) | ||
} | ||
rows.push(row) | ||
} | ||
return rows.map((row, index) => ( | ||
<div className="calendar-month-body-row" key={index}> | ||
{row} | ||
</div> | ||
)) | ||
} | ||
|
||
return ( | ||
<div className="calendar-month"> | ||
<div className="calendar-month-week-list"> | ||
{weekList.map(week => ( | ||
<div className="calendar-month-week-list-item" key={week}> | ||
{CalendarLocale.week[week]} | ||
</div> | ||
))} | ||
</div> | ||
<div className="calendar-month-body">{renderDays(allDays)}</div> | ||
</div> | ||
) | ||
} | ||
|
||
export default MonthCalendar |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import React from 'react' | ||
import { Meta, StoryFn } from '@storybook/react' | ||
|
||
import Calendar from '.' | ||
import dayjs from 'dayjs' | ||
|
||
const meta = { | ||
title: 'Data Display/Calendar 组件', | ||
id: 'calendar', | ||
component: Calendar, | ||
decorators: [ | ||
Story => ( | ||
<div style={{ padding: '20px' }}> | ||
<Story /> | ||
</div> | ||
), | ||
], | ||
} satisfies Meta<typeof Calendar> | ||
|
||
export default meta | ||
|
||
const Template: StoryFn<typeof Calendar> = args => <Calendar {...args} /> | ||
|
||
export const BasicCalendar = Template.bind({}) | ||
BasicCalendar.args = { | ||
value: dayjs('2024-02-11'), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
.calendar { | ||
width: 100%; | ||
} | ||
|
||
.calendar-header { | ||
&-left { | ||
display: flex; | ||
align-items: center; | ||
|
||
height: 28px; | ||
line-height: 28px; | ||
} | ||
|
||
&-value { | ||
font-size: 20px; | ||
} | ||
|
||
&-btn { | ||
background: #eee; | ||
cursor: pointer; | ||
border: 0; | ||
padding: 0 15px; | ||
line-height: 28px; | ||
|
||
&:hover { | ||
background: #ccc; | ||
} | ||
} | ||
|
||
&-icon { | ||
width: 28px; | ||
height: 28px; | ||
|
||
line-height: 28px; | ||
|
||
border-radius: 50%; | ||
text-align: center; | ||
font-size: 12px; | ||
|
||
user-select: none; | ||
cursor: pointer; | ||
|
||
margin-right: 12px; | ||
|
||
&:not(:first-child) { | ||
margin: 0 12px; | ||
} | ||
|
||
&:hover { | ||
background: #ccc; | ||
} | ||
} | ||
|
||
} | ||
|
||
.calendar-month { | ||
&-week-list { | ||
display: flex; | ||
padding: 0; | ||
width: 100%; | ||
box-sizing: border-box; | ||
border-bottom: 1px solid #eee; | ||
|
||
&-item { | ||
padding: 20px 16px; | ||
text-align: left; | ||
color: #7d7d7f; | ||
flex: 1; | ||
} | ||
} | ||
|
||
&-body { | ||
&-row { | ||
height: 100px; | ||
display: flex; | ||
} | ||
|
||
&-cell { | ||
flex: 1; | ||
border: 1px solid #eee; | ||
color: #ccc; | ||
overflow: hidden; | ||
|
||
&-current { | ||
color: #000; | ||
} | ||
|
||
&-date { | ||
padding: 10px; | ||
|
||
&-selected { | ||
background: blue; | ||
width: 28px; | ||
height: 28px; | ||
line-height: 28px; | ||
text-align: center; | ||
color: #fff; | ||
border-radius: 50%; | ||
cursor: pointer; | ||
} | ||
} | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import Calendar from './Calendar' | ||
|
||
export default Calendar |
Oops, something went wrong.