From 29d8f61c90cc91db61cde5cae627def97ad8b1c3 Mon Sep 17 00:00:00 2001 From: JimmFly Date: Thu, 8 Jun 2023 17:55:16 +0800 Subject: [PATCH] feat: add date picker (#2644) Co-authored-by: himself65 --- packages/component/package.json | 2 + .../components/date-picker/date-picker.tsx | 191 +++++++++++++++++ .../src/components/date-picker/index.css.ts | 200 ++++++++++++++++++ .../src/components/date-picker/index.ts | 1 + .../components/page-list/filter/condition.tsx | 8 +- .../components/page-list/filter/index.css.ts | 11 +- .../page-list/filter/literal-matcher.tsx | 7 +- .../page-list/filter/shared-types.tsx | 2 - .../src/components/page-list/filter/vars.tsx | 2 +- .../components/page-list/view/create-view.tsx | 28 ++- .../page-list/view/view-list.css.ts | 14 ++ packages/component/src/theme/global.css | 1 + .../src/stories/datepicker.stories.tsx | 13 ++ tests/parallels/all-page.spec.ts | 166 ++++++++++++++- yarn.lock | 82 ++++++- 15 files changed, 706 insertions(+), 22 deletions(-) create mode 100644 packages/component/src/components/date-picker/date-picker.tsx create mode 100644 packages/component/src/components/date-picker/index.css.ts create mode 100644 packages/component/src/components/date-picker/index.ts create mode 100644 packages/storybook/src/stories/datepicker.stories.tsx diff --git a/packages/component/package.json b/packages/component/package.json index b64dacb463..a5df609d12 100644 --- a/packages/component/package.json +++ b/packages/component/package.json @@ -43,6 +43,7 @@ "lit": "^2.7.5", "lottie-web": "^5.12.0", "react": "18.3.0-canary-16d053d59-20230506", + "react-datepicker": "^4.12.0", "react-dom": "18.3.0-canary-16d053d59-20230506", "react-error-boundary": "^4.0.9", "react-is": "^18.2.0", @@ -56,6 +57,7 @@ "@blocksuite/lit": "0.0.0-20230606130340-805f430b-nightly", "@blocksuite/store": "0.0.0-20230606130340-805f430b-nightly", "@types/react": "^18.2.6", + "@types/react-datepicker": "^4.11.2", "@types/react-dnd": "^3.0.2", "@types/react-dom": "18.2.4", "@vanilla-extract/css": "^1.11.0", diff --git a/packages/component/src/components/date-picker/date-picker.tsx b/packages/component/src/components/date-picker/date-picker.tsx new file mode 100644 index 0000000000..e154676a79 --- /dev/null +++ b/packages/component/src/components/date-picker/date-picker.tsx @@ -0,0 +1,191 @@ +import { + ArrowDownSmallIcon, + ArrowLeftSmallIcon, + ArrowRightSmallIcon, +} from '@blocksuite/icons'; +import dayjs from 'dayjs'; +import { useCallback, useState } from 'react'; +import DatePicker from 'react-datepicker'; + +import * as styles from './index.css'; +const months = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', +]; +type DatePickerProps = { + value?: string; + onChange: (value: string) => void; +}; + +export const AFFiNEDatePicker = (props: DatePickerProps) => { + const { value, onChange } = props; + const [openMonthPicker, setOpenMonthPicker] = useState(false); + const [selectedDate, setSelectedDate] = useState( + value ? dayjs(value).toDate() : null + ); + const handleOpenMonthPicker = useCallback(() => { + setOpenMonthPicker(true); + }, []); + const handleCloseMonthPicker = useCallback(() => { + setOpenMonthPicker(false); + }, []); + const handleSelectDate = (date: Date | null) => { + if (date) { + setSelectedDate(date); + onChange(dayjs(date).format('YYYY-MM-DD')); + setOpenMonthPicker(false); + } + }; + const renderCustomHeader = ({ + date, + decreaseMonth, + increaseMonth, + prevMonthButtonDisabled, + nextMonthButtonDisabled, + }: { + date: Date; + decreaseMonth: () => void; + increaseMonth: () => void; + prevMonthButtonDisabled: boolean; + nextMonthButtonDisabled: boolean; + }) => { + const selectedYear = dayjs(date).year(); + const selectedMonth = dayjs(date).month(); + return ( +
+
+ {months[selectedMonth]} +
+
+ {selectedYear} +
+
+ +
+ + +
+ ); + }; + const renderCustomMonthHeader = ({ + date, + decreaseYear, + increaseYear, + prevYearButtonDisabled, + nextYearButtonDisabled, + }: { + date: Date; + decreaseYear: () => void; + increaseYear: () => void; + prevYearButtonDisabled: boolean; + nextYearButtonDisabled: boolean; + }) => { + const selectedYear = dayjs(date).year(); + return ( +
+
+ {selectedYear} +
+ + +
+ ); + }; + return ( + styles.weekStyle} + dayClassName={() => styles.dayStyle} + popperClassName={styles.popperStyle} + monthClassName={() => styles.mouthsStyle} + selected={selectedDate} + onChange={handleSelectDate} + showPopperArrow={false} + dateFormat="MMM dd" + showMonthYearPicker={openMonthPicker} + shouldCloseOnSelect={!openMonthPicker} + renderCustomHeader={({ + date, + decreaseYear, + increaseYear, + decreaseMonth, + increaseMonth, + prevYearButtonDisabled, + nextYearButtonDisabled, + prevMonthButtonDisabled, + nextMonthButtonDisabled, + }) => + openMonthPicker + ? renderCustomMonthHeader({ + date, + decreaseYear, + increaseYear, + prevYearButtonDisabled, + nextYearButtonDisabled, + }) + : renderCustomHeader({ + date, + decreaseMonth, + increaseMonth, + prevMonthButtonDisabled, + nextMonthButtonDisabled, + }) + } + /> + ); +}; + +export default AFFiNEDatePicker; diff --git a/packages/component/src/components/date-picker/index.css.ts b/packages/component/src/components/date-picker/index.css.ts new file mode 100644 index 0000000000..9f808d3292 --- /dev/null +++ b/packages/component/src/components/date-picker/index.css.ts @@ -0,0 +1,200 @@ +import { globalStyle, style } from '@vanilla-extract/css'; + +export const inputStyle = style({ + fontSize: 'var(--affine-font-xs)', + width: '50px', + fontWeight: '600', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: '22px', + marginLeft: '10px', + marginRight: '10px', +}); +export const popperStyle = style({ + boxShadow: 'var(--affine-shadow-2)', + padding: '0 10px', + marginTop: '16px', + background: 'var(--affine-background-overlay-panel-color)', + borderRadius: '12px', + width: '300px', +}); + +globalStyle('.react-datepicker__header', { + background: 'var(--affine-background-overlay-panel-color)', + border: 'none', + marginBottom: '6px', +}); +export const headerStyle = style({ + background: 'var(--affine-background-overlay-panel-color)', + border: 'none', + display: 'flex', + width: '100%', + alignItems: 'center', + marginBottom: '12px', + padding: '0 14px', + position: 'relative', +}); +export const monthHeaderStyle = style({ + background: 'var(--affine-background-overlay-panel-color)', + border: 'none', + display: 'flex', + width: '100%', + alignItems: 'center', + marginBottom: '18px', + padding: '0 14px', + position: 'relative', + '::after': { + content: '""', + position: 'absolute', + width: 'calc(100% - 24px)', + height: '1px', + background: 'var(--affine-border-color)', + bottom: '-18px', + left: '12px', + }, +}); +export const monthTitleStyle = style({ + color: 'var(--affine-text-primary-color)', + fontWeight: '600', + fontSize: 'var(--affine-font-sm)', + marginLeft: '12px', +}); +export const yearStyle = style({ + marginLeft: '8px', + color: 'var(--affine-text-primary-color)', + fontWeight: '600', + fontSize: 'var(--affine-font-sm)', +}); +export const mouthStyle = style({ + color: 'var(--affine-text-primary-color)', + fontWeight: '600', + fontSize: 'var(--affine-font-sm)', +}); +export const arrowLeftStyle = style({ + width: '16px', + height: '16px', + textAlign: 'right', + position: 'absolute', + right: '50px', +}); +export const arrowRightStyle = style({ + width: '16px', + height: '16px', + right: '14px', + position: 'absolute', +}); +export const weekStyle = style({ + fontSize: 'var(--affine-font-xs)', + color: 'var(--affine-text-secondary-color)', + display: 'inline-block', + width: '28px', + height: '28px', + lineHeight: '28px', + padding: '0 4px', + margin: '0px 6px', + verticalAlign: 'middle', +}); +export const calendarStyle = style({ + background: 'var(--affine-background-overlay-panel-color)', + border: 'none', + width: '100%', +}); +export const dayStyle = style({ + fontSize: 'var(--affine-font-xs)', + color: 'var(--affine-text-primary-color)', + display: 'inline-block', + width: '28px', + height: '28px', + lineHeight: '28px', + padding: '0 4px', + margin: '6px 12px 6px 0px', + verticalAlign: 'middle', + fontWeight: '400', + borderRadius: '8px', + selectors: { + '&:hover': { + background: 'var(--affine-hover-color)', + borderRadius: '8px', + transition: 'background-color 0.3s ease-in-out', + }, + '&[aria-selected="true"]': { + color: 'var(--affine-black)', + background: 'var(--affine-hover-color)', + }, + '&[aria-selected="true"]:hover': { + background: 'var(--affine-hover-color)', + }, + '&[tabindex="0"][aria-selected="false"]': { + background: 'var(--affine-background-overlay-panel-color)', + }, + '&.react-datepicker__day--today[aria-selected="false"]': { + background: 'var(--affine-primary-color)', + color: 'var(--affine-palette-line-white)', + }, + '&.react-datepicker__day--today[aria-selected="false"]:hover': { + color: 'var(--affine-black)', + background: 'var(--affine-hover-color)', + }, + '&.react-datepicker__day--outside-month[aria-selected="false"]': { + color: 'var(--affine-text-disable-color)', + }, + }, +}); +export const arrowDownStyle = style({ + width: '16px', + height: '16px', + marginLeft: '4px', + color: 'var(--affine-icon-color)', + fontSize: 'var(--affine-font-sm)', + cursor: 'pointer', +}); +export const mouthsStyle = style({ + fontSize: 'var(--affine-font-base)', + color: 'var(--affine-text-primary-color)', + display: 'inline-block', + lineHeight: '22px', + padding: '6px 16px', + fontWeight: '400', + borderRadius: '8px', + selectors: { + '&:hover': { + background: 'var(--affine-hover-color)', + transition: 'background-color 0.3s ease-in-out', + borderRadius: '8px', + }, + '&[aria-selected="true"]': { + color: 'var(--affine-black)', + background: 'var(--affine-hover-color)', + fontWeight: '400', + }, + '&[aria-selected="true"]:hover': { + background: 'var(--affine-hover-color)', + }, + '&[tabindex="0"][aria-selected="false"]': { + background: 'var(--affine-background-overlay-panel-color)', + }, + '&.react-datepicker__month-text--today[aria-selected="false"]': { + background: 'var(--affine-primary-color)', + color: 'var(--affine-palette-line-white)', + }, + '&.react-datepicker__month-text--today[aria-selected="false"]:hover': { + background: 'var(--affine-hover-color)', + color: 'var(--affine-black)', + }, + }, +}); + +globalStyle(`${calendarStyle} .react-datepicker__month-container`, { + float: 'none', + width: '100%', +}); +globalStyle(`${calendarStyle} .react-datepicker__month-wrapper`, { + display: 'flex', + justifyContent: 'space-between', + marginBottom: '18px', +}); +globalStyle(`${calendarStyle} .react-datepicker__month-text`, { + margin: '0', + width: '64px', +}); diff --git a/packages/component/src/components/date-picker/index.ts b/packages/component/src/components/date-picker/index.ts new file mode 100644 index 0000000000..e5ce37c674 --- /dev/null +++ b/packages/component/src/components/date-picker/index.ts @@ -0,0 +1 @@ +export * from './date-picker'; diff --git a/packages/component/src/components/page-list/filter/condition.tsx b/packages/component/src/components/page-list/filter/condition.tsx index baf6e03ee4..4deedf0074 100644 --- a/packages/component/src/components/page-list/filter/condition.tsx +++ b/packages/component/src/components/page-list/filter/condition.tsx @@ -6,6 +6,7 @@ import { Menu, MenuItem } from '../../../ui/menu'; import * as styles from './index.css'; import { literalMatcher } from './literal-matcher'; import type { TFunction, TType } from './logical/typesystem'; +import { variableDefineMap } from './shared-types'; import { filterMatcher, VariableSelect, vars } from './vars'; export const Condition = ({ @@ -35,7 +36,10 @@ export const Condition = ({ content={} >
- {ast.left.name} +
+ {variableDefineMap[ast.left.name].icon} +
+
{ast.left.name}
+
{data.render({ type, value, onChange })}
); diff --git a/packages/component/src/components/page-list/filter/index.css.ts b/packages/component/src/components/page-list/filter/index.css.ts index 8593ea77fd..c6ffb31708 100644 --- a/packages/component/src/components/page-list/filter/index.css.ts +++ b/packages/component/src/components/page-list/filter/index.css.ts @@ -41,12 +41,21 @@ export const filterItemCloseStyle = style({ }); export const inputStyle = style({ fontSize: 'var(--affine-font-xs)', + margin: '0 10px', }); export const switchStyle = style({ fontSize: 'var(--affine-font-xs)', color: 'var(--affine-text-secondary-color)', - marginLeft: '4px', + margin: '0 10px', }); export const filterTypeStyle = style({ fontSize: 'var(--affine-font-sm)', + display: 'flex', + marginRight: '10px', +}); +export const filterTypeIconStyle = style({ + fontSize: 'var(--affine-font-base)', + marginRight: '6px', + display: 'flex', + color: 'var(--affine-icon-color)', }); diff --git a/packages/component/src/components/page-list/filter/literal-matcher.tsx b/packages/component/src/components/page-list/filter/literal-matcher.tsx index e2bdf52228..f785343fa5 100644 --- a/packages/component/src/components/page-list/filter/literal-matcher.tsx +++ b/packages/component/src/components/page-list/filter/literal-matcher.tsx @@ -2,6 +2,7 @@ import type { Literal } from '@affine/env/filter'; import dayjs from 'dayjs'; import type { ReactNode } from 'react'; +import { AFFiNEDatePicker } from '../../date-picker'; import { inputStyle } from './index.css'; import { tBoolean, tDate } from './logical/custom-type'; import { Matcher } from './logical/matcher'; @@ -33,14 +34,12 @@ literalMatcher.register(tBoolean.create(), { }); literalMatcher.register(tDate.create(), { render: ({ value, onChange }) => ( - { onChange({ type: 'literal', - value: dayjs(e.target.value, 'YYYY-MM-DD').valueOf(), + value: dayjs(e, 'YYYY-MM-DD').valueOf(), }); }} /> diff --git a/packages/component/src/components/page-list/filter/shared-types.tsx b/packages/component/src/components/page-list/filter/shared-types.tsx index 5a3527ce4c..541d45aef1 100644 --- a/packages/component/src/components/page-list/filter/shared-types.tsx +++ b/packages/component/src/components/page-list/filter/shared-types.tsx @@ -1,6 +1,5 @@ import type { Literal, LiteralValue, VariableMap } from '@affine/env/filter'; import { DateTimeIcon, FavoritedIcon } from '@blocksuite/icons'; -import type { ReactElement } from 'react'; import { tBoolean, tDate } from './logical/custom-type'; import type { TType } from './logical/typesystem'; @@ -13,7 +12,6 @@ export const toLiteral = (value: LiteralValue): Literal => ({ export type FilterVariable = { name: keyof VariableMap; type: TType; - icon: ReactElement; }; export const variableDefineMap = { diff --git a/packages/component/src/components/page-list/filter/vars.tsx b/packages/component/src/components/page-list/filter/vars.tsx index a1f2b114d9..7471648b18 100644 --- a/packages/component/src/components/page-list/filter/vars.tsx +++ b/packages/component/src/components/page-list/filter/vars.tsx @@ -63,7 +63,7 @@ export const VariableSelect = ({ // .filter(v => !selected.find(filter => filter.left.name === v.name)) .map(v => ( { onSelect(createDefaultFilter(v)); diff --git a/packages/component/src/components/page-list/view/create-view.tsx b/packages/component/src/components/page-list/view/create-view.tsx index ad2c1f3c76..db5f7448e9 100644 --- a/packages/component/src/components/page-list/view/create-view.tsx +++ b/packages/component/src/components/page-list/view/create-view.tsx @@ -3,7 +3,7 @@ import { SaveIcon } from '@blocksuite/icons'; import { uuidv4 } from '@blocksuite/store'; import { useState } from 'react'; -import { Button, Input, Modal, ModalWrapper } from '../../..'; +import { Button, Input, Modal, ModalCloseButton, ModalWrapper } from '../../..'; import { FilterList } from '../filter'; import * as styles from './view-list.css'; @@ -25,10 +25,10 @@ const CreateView = ({ return (
-

Save As New View

+
Save As New View
- +
changeShow(false)}> - + + changeShow(false)} + hoverColor="var(--affine-icon-color)" + /> changeShow(false)} diff --git a/packages/component/src/components/page-list/view/view-list.css.ts b/packages/component/src/components/page-list/view/view-list.css.ts index a1f2c4d6bb..bf24422003 100644 --- a/packages/component/src/components/page-list/view/view-list.css.ts +++ b/packages/component/src/components/page-list/view/view-list.css.ts @@ -60,3 +60,17 @@ export const saveText = style({ justifyContent: 'center', fontSize: 'var(--affine-font-sm)', }); +export const cancelButton = style({ + background: 'var(--affine-hover-color)', + borderRadius: '8px', + ':hover': { + background: 'var(--affine-hover-color)', + color: 'var(--affine-text-primary-color)', + border: '1px solid var(--affine-border-color)', + }, +}); +export const saveTitle = style({ + fontSize: 'var(--affine-font-h-6)', + fontWeight: '600', + lineHeight: '24px', +}); diff --git a/packages/component/src/theme/global.css b/packages/component/src/theme/global.css index a0f19b2c98..9bdca2e1b1 100644 --- a/packages/component/src/theme/global.css +++ b/packages/component/src/theme/global.css @@ -1,3 +1,4 @@ +@import 'react-datepicker/dist/react-datepicker.css'; * { -webkit-overflow-scrolling: touch; -webkit-tap-highlight-color: rgba(255, 255, 255, 0); diff --git a/packages/storybook/src/stories/datepicker.stories.tsx b/packages/storybook/src/stories/datepicker.stories.tsx new file mode 100644 index 0000000000..52490f2ff4 --- /dev/null +++ b/packages/storybook/src/stories/datepicker.stories.tsx @@ -0,0 +1,13 @@ +import { AFFiNEDatePicker } from '@affine/component/date-picker'; +import type { StoryFn } from '@storybook/react'; +import { useState } from 'react'; + +export default { + title: 'AFFiNE/AFFiNEDatePicker', + component: AFFiNEDatePicker, +}; + +export const Default: StoryFn = () => { + const [value, setValue] = useState(new Date().toString()); + return ; +}; diff --git a/tests/parallels/all-page.spec.ts b/tests/parallels/all-page.spec.ts index 0243a67c6e..51311dffae 100644 --- a/tests/parallels/all-page.spec.ts +++ b/tests/parallels/all-page.spec.ts @@ -89,10 +89,25 @@ test('allow creation of filters by favorite', async ({ page }) => { ).toBe('false'); }); +const monthNames = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', +]; + const dateFormat = (date: Date) => { - return `${date.getFullYear()}-${(date.getMonth() + 1) - .toString() - .padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`; + const month = monthNames[date.getMonth()]; + const day = date.getDate().toString().padStart(2, '0'); + return `${month} ${day}`; }; const checkPagesCount = async (page: Page, count: number) => { await expect((await page.locator('[data-testid="title"]').all()).length).toBe( @@ -107,6 +122,12 @@ const checkDatePicker = async (page: Page, date: Date) => { .inputValue() ).toBe(dateFormat(date)); }; +const clickDatePicker = async (page: Page) => { + await page.locator('[data-testid="filter-arg"]').locator('input').click(); +}; +const clickMonthPicker = async (page: Page) => { + await page.locator('[data-testid="month-picker-button"]').click(); +}; const fillDatePicker = async (page: Page, date: Date) => { await page .locator('[data-testid="filter-arg"]') @@ -141,3 +162,142 @@ test('allow creation of filters by created time', async ({ page }) => { await checkDatePicker(page, tomorrow); await checkPagesCount(page, 1); }); + +const checkIsLastMonth = (date: Date): boolean => { + const targetMonth = date.getMonth(); + const currentMonth = new Date().getMonth(); + const lastMonth = currentMonth === 0 ? 11 : currentMonth - 1; + return targetMonth === lastMonth; +}; +const checkIsNextMonth = (date: Date): boolean => { + const targetMonth = date.getMonth(); + const currentMonth = new Date().getMonth(); + const nextMonth = currentMonth === 11 ? 0 : currentMonth + 1; + return targetMonth === nextMonth; +}; +const selectDateFromDatePicker = async (page: Page, date: Date) => { + const datePickerPopup = page.locator('.react-datepicker-popper'); + const day = date.getDate().toString(); + const month = date.toLocaleString('en-US', { month: 'long' }); + const weekday = date.toLocaleString('en-US', { weekday: 'long' }); + const year = date.getFullYear().toString(); + // Open the date picker popup + await clickDatePicker(page); + const selectDate = async (): Promise => { + if (checkIsLastMonth(date)) { + const lastMonthButton = page.locator( + '[data-testid="date-picker-prev-button"]' + ); + await lastMonthButton.click(); + } else if (checkIsNextMonth(date)) { + const nextMonthButton = page.locator( + '[data-testid="date-picker-next-button"]' + ); + await nextMonthButton.click(); + } + // Click on the day cell + const dateCell = page.locator( + `[aria-disabled="false"][aria-label="Choose ${weekday}, ${month} ${day}th, ${year}"]` + ); + await dateCell.click(); + }; + await selectDate(); + + // Wait for the date picker popup to close + await datePickerPopup.waitFor({ state: 'hidden' }); +}; + +test('creation of filters by created time, then click date picker to modify the date', async ({ + page, +}) => { + await openHomePage(page); + await waitEditorLoad(page); + await clickSideBarAllPageButton(page); + await closeDownloadTip(page); + await createFirstFilter(page, 'Created'); + await checkFilterName(page, 'after'); + // init date + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + await checkDatePicker(page, yesterday); + await checkPagesCount(page, 1); + // change date + const today = new Date(); + await selectDateFromDatePicker(page, today); + await checkPagesCount(page, 0); + // change filter + await page.locator('[data-testid="filter-name"]').click(); + await page + .locator('[data-testid="filter-name-select"]') + .locator('button', { hasText: 'before' }) + .click(); + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + await selectDateFromDatePicker(page, tomorrow); + await checkDatePicker(page, tomorrow); + await checkPagesCount(page, 1); +}); +const checkIsLastYear = (date: Date): boolean => { + const targetYear = date.getFullYear(); + const currentYear = new Date().getFullYear(); + const lastYear = currentYear - 1; + return targetYear === lastYear; +}; +const checkIsNextYear = (date: Date): boolean => { + const targetYear = date.getFullYear(); + const currentYear = new Date().getFullYear(); + const nextYear = currentYear + 1; + return targetYear === nextYear; +}; +const selectMonthFromMonthPicker = async (page: Page, date: Date) => { + const month = date.toLocaleString('en-US', { month: 'long' }); + const year = date.getFullYear().toString(); + // Open the month picker popup + await clickMonthPicker(page); + const selectMonth = async (): Promise => { + if (checkIsLastYear(date)) { + const lastYearButton = page.locator( + '[data-testid="month-picker-prev-button"]' + ); + await lastYearButton.click(); + } else if (checkIsNextYear(date)) { + const nextYearButton = page.locator( + '[data-testid="month-picker-next-button"]' + ); + await nextYearButton.click(); + } + // Click on the day cell + const monthCell = page.locator(`[aria-label="Choose ${month} ${year}"]`); + await monthCell.click(); + }; + await selectMonth(); +}; +const checkDatePickerMonth = async (page: Page, date: Date) => { + expect( + await page.locator('[data-testid="date-picker-current-month"]').innerText() + ).toBe(date.toLocaleString('en-US', { month: 'long' })); +}; +test('use monthpicker to modify the month of datepicker', async ({ page }) => { + await openHomePage(page); + await waitEditorLoad(page); + await clickSideBarAllPageButton(page); + await closeDownloadTip(page); + await createFirstFilter(page, 'Created'); + await checkFilterName(page, 'after'); + // init date + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + await checkDatePicker(page, yesterday); + // change month + await clickDatePicker(page); + const lastMonth = new Date(); + lastMonth.setMonth(lastMonth.getMonth() - 1); + await selectMonthFromMonthPicker(page, lastMonth); + await checkDatePickerMonth(page, lastMonth); + // change month + await clickDatePicker(page); + const nextMonth = new Date(); + nextMonth.setMonth(nextMonth.getMonth() + 1); + await selectMonthFromMonthPicker(page, nextMonth); + await checkDatePickerMonth(page, nextMonth); +}); diff --git a/yarn.lock b/yarn.lock index 444ac8eb3e..caaf355c21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -87,6 +87,7 @@ __metadata: "@toeverything/hooks": "workspace:*" "@toeverything/theme": ^0.6.1 "@types/react": ^18.2.6 + "@types/react-datepicker": ^4.11.2 "@types/react-dnd": ^3.0.2 "@types/react-dom": 18.2.4 "@vanilla-extract/css": ^1.11.0 @@ -97,6 +98,7 @@ __metadata: lit: ^2.7.5 lottie-web: ^5.12.0 react: 18.3.0-canary-16d053d59-20230506 + react-datepicker: ^4.12.0 react-dom: 18.3.0-canary-16d053d59-20230506 react-error-boundary: ^4.0.9 react-is: ^18.2.0 @@ -6753,7 +6755,7 @@ __metadata: languageName: node linkType: hard -"@popperjs/core@npm:^2.11.6, @popperjs/core@npm:^2.11.7, @popperjs/core@npm:^2.11.8": +"@popperjs/core@npm:^2.11.6, @popperjs/core@npm:^2.11.7, @popperjs/core@npm:^2.11.8, @popperjs/core@npm:^2.9.2": version: 2.11.8 resolution: "@popperjs/core@npm:2.11.8" checksum: e5c69fdebf52a4012f6a1f14817ca8e9599cb1be73dd1387e1785e2ed5e5f0862ff817f420a87c7fc532add1f88a12e25aeb010ffcbdc98eace3d55ce2139cf0 @@ -9874,6 +9876,18 @@ __metadata: languageName: node linkType: hard +"@types/react-datepicker@npm:^4.11.2": + version: 4.11.2 + resolution: "@types/react-datepicker@npm:4.11.2" + dependencies: + "@popperjs/core": ^2.9.2 + "@types/react": "*" + date-fns: ^2.0.1 + react-popper: ^2.2.5 + checksum: 592cff1604c74a336dcf5bb72db3f9f47de50bcab4e3d95684b21b4cf6b22e7e7e6351758e866e734c209aa41d21601f04322e1b03920a9473152e4db08bc170 + languageName: node + linkType: hard + "@types/react-dnd@npm:^3.0.2": version: 3.0.2 resolution: "@types/react-dnd@npm:3.0.2" @@ -12593,6 +12607,13 @@ __metadata: languageName: node linkType: hard +"classnames@npm:^2.2.6": + version: 2.3.2 + resolution: "classnames@npm:2.3.2" + checksum: 2c62199789618d95545c872787137262e741f9db13328e216b093eea91c85ef2bfb152c1f9e63027204e2559a006a92eb74147d46c800a9f96297ae1d9f96f4e + languageName: node + linkType: hard + "clean-regexp@npm:^1.0.0": version: 1.0.0 resolution: "clean-regexp@npm:1.0.0" @@ -13497,7 +13518,7 @@ __metadata: languageName: node linkType: hard -"date-fns@npm:^2.29.3": +"date-fns@npm:^2.0.1, date-fns@npm:^2.24.0, date-fns@npm:^2.29.3": version: 2.30.0 resolution: "date-fns@npm:2.30.0" dependencies: @@ -23422,6 +23443,23 @@ __metadata: languageName: node linkType: hard +"react-datepicker@npm:^4.12.0": + version: 4.12.0 + resolution: "react-datepicker@npm:4.12.0" + dependencies: + "@popperjs/core": ^2.9.2 + classnames: ^2.2.6 + date-fns: ^2.24.0 + prop-types: ^15.7.2 + react-onclickoutside: ^6.12.2 + react-popper: ^2.3.0 + peerDependencies: + react: ^16.9.0 || ^17 || ^18 + react-dom: ^16.9.0 || ^17 || ^18 + checksum: 6d637b1132a9607846defb08a9b736a1b003fd40ae33f58d643168adb5de10b37dc9e9414c586abe9981768e0f56486b849fdb3890a5417277ad142b5525a5c2 + languageName: node + linkType: hard + "react-dnd@npm:*": version: 16.0.1 resolution: "react-dnd@npm:16.0.1" @@ -23524,6 +23562,13 @@ __metadata: languageName: node linkType: hard +"react-fast-compare@npm:^3.0.1": + version: 3.2.2 + resolution: "react-fast-compare@npm:3.2.2" + checksum: 2071415b4f76a3e6b55c84611c4d24dcb12ffc85811a2840b5a3f1ff2d1a99be1020d9437ee7c6e024c9f4cbb84ceb35e48cf84f28fcb00265ad2dfdd3947704 + languageName: node + linkType: hard + "react-i18next@npm:^12.3.1": version: 12.3.1 resolution: "react-i18next@npm:12.3.1" @@ -23593,6 +23638,30 @@ __metadata: languageName: node linkType: hard +"react-onclickoutside@npm:^6.12.2": + version: 6.13.0 + resolution: "react-onclickoutside@npm:6.13.0" + peerDependencies: + react: ^15.5.x || ^16.x || ^17.x || ^18.x + react-dom: ^15.5.x || ^16.x || ^17.x || ^18.x + checksum: a7cfe62e91f7f92891cda7b885d6eec7b2854850f8703ccafcea5c04bb93a210a566a70b51b9fae0cc30c3485e04eb6a3f3ae58f495cac3ec2747b4fc44d1ad2 + languageName: node + linkType: hard + +"react-popper@npm:^2.2.5, react-popper@npm:^2.3.0": + version: 2.3.0 + resolution: "react-popper@npm:2.3.0" + dependencies: + react-fast-compare: ^3.0.1 + warning: ^4.0.2 + peerDependencies: + "@popperjs/core": ^2.0.0 + react: ^16.8.0 || ^17 || ^18 + react-dom: ^16.8.0 || ^17 || ^18 + checksum: 837111c98738011c69b3069a464ea5bdcbf487105b6148e8faf90cb7337e134edb1b98b8824322941c378756cca30a15c18c25f558e53b85ed5762fa0dc8e6b2 + languageName: node + linkType: hard + "react-refresh@npm:^0.14.0": version: 0.14.0 resolution: "react-refresh@npm:0.14.0" @@ -27370,6 +27439,15 @@ __metadata: languageName: node linkType: hard +"warning@npm:^4.0.2": + version: 4.0.3 + resolution: "warning@npm:4.0.3" + dependencies: + loose-envify: ^1.0.0 + checksum: 4f2cb6a9575e4faf71ddad9ad1ae7a00d0a75d24521c193fa464f30e6b04027bd97aa5d9546b0e13d3a150ab402eda216d59c1d0f2d6ca60124d96cd40dfa35c + languageName: node + linkType: hard + "watchpack@npm:^2.2.0, watchpack@npm:^2.4.0": version: 2.4.0 resolution: "watchpack@npm:2.4.0"