feat(core): replace page filter, journal's date-picker with new one (#5675)

This commit is contained in:
Cats Juice 2024-02-20 13:53:41 +00:00
parent e664494b2f
commit 3fe9d834de
No known key found for this signature in database
GPG Key ID: 1C1E76924FAFDDE4
13 changed files with 185 additions and 94 deletions

View File

@ -89,7 +89,9 @@ export const basicCell = style({
});
// roots
export const calendarRoot = style({});
export const calendarRoot = style({
minWidth: `calc(28px * 7 + ${vars.gapX} * 6)`,
});
export const calendarWrapper = style({
display: 'flex',
flexDirection: 'column',
@ -101,7 +103,7 @@ export const calendarHeader = style({
});
// header
export const headerLayoutCell = style([basicCell]);
export const headerLayoutCell = style([basicCell, { height: 'auto' }]);
export const headerLayoutCellOrigin = style({
width: 0,
height: 'fit-content',

View File

@ -119,12 +119,17 @@ export const DayPicker = memo(function DayPicker(
onClick={openMonthPicker}
ref={headerMonthRef}
className={styles.calendarHeaderTriggerButton}
data-testid="month-picker-button"
data-month={cursor.month()}
data-year={cursor.year()}
>
{monthNames.split(',')[cursor.month()]}
</button>
<button
className={styles.calendarHeaderTriggerButton}
onClick={openYearPicker}
data-testid="year-picker-button"
data-year={cursor.year()}
>
{cursor.year()}
</button>

View File

@ -1 +1,2 @@
export * from './calendar';
export type { DateCell } from './types';

View File

@ -19,6 +19,8 @@ interface HeaderLayoutProps extends HTMLAttributes<HTMLDivElement> {
left: React.ReactNode;
right: React.ReactNode;
}
const autoHeight = { height: 'auto' };
/**
* The `DatePicker` should work with different width
* This is a hack to make header's item align with calendar cell's label, **instead of the cell**
@ -57,6 +59,7 @@ const HeaderLayout = memo(function HeaderLayout({
[styles.yearViewBodyCell]: mode === 'month',
[styles.decadeViewBodyCell]: mode === 'year',
})}
style={autoHeight}
>
<div className={styles.headerLayoutCellOrigin}>
{isLeft ? left : isRight ? right : null}
@ -139,6 +142,7 @@ export const NavButtons = memo(function NavButtons({
size="small"
className={styles.focusInteractive}
disabled={prevDisabled}
data-testid="date-picker-nav-prev"
onClick={onPrev}
>
<ArrowLeftSmallIcon />
@ -151,6 +155,7 @@ export const NavButtons = memo(function NavButtons({
size="small"
className={styles.focusInteractive}
disabled={nextDisabled}
data-testid="date-picker-nav-next"
onClick={onNext}
>
<ArrowRightSmallIcon />

View File

@ -73,6 +73,8 @@ export const MonthPicker = memo(function MonthPicker(
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
closeMonthPicker();
return;
}
@ -101,6 +103,7 @@ export const MonthPicker = memo(function MonthPicker(
const HeaderLeft = useMemo(() => {
return (
<button
data-testid="month-picker-current-year"
onClick={closeMonthPicker}
className={styles.calendarHeaderTriggerButton}
>
@ -137,6 +140,7 @@ export const MonthPicker = memo(function MonthPicker(
data-current-month={month.isSame(dayjs(), 'month')}
onClick={() => onMonthChange(month)}
tabIndex={month.isSame(monthCursor, 'month') ? 0 : -1}
aria-label={month.format('YYYY-MM')}
>
{monthNames.split(',')[month.month()]}
</button>

View File

@ -84,6 +84,8 @@ export const YearPicker = memo(function YearPicker(
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
closeYearPicker();
return;
}

View File

@ -0,0 +1,17 @@
import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css';
export const datePickerTriggerInput = style({
fontSize: cssVar('fontXs'),
width: '50px',
fontWeight: '600',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '22px',
textAlign: 'center',
':hover': {
background: cssVar('hoverColor'),
borderRadius: '4px',
},
});

View File

@ -0,0 +1,51 @@
import { DatePicker, Popover, type PopoverProps } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import dayjs from 'dayjs';
import { useCallback, useState } from 'react';
import { datePickerTriggerInput } from './date-select.css';
const datePickerPopperContentOptions: PopoverProps['contentOptions'] = {
style: { padding: 20, marginTop: 10 },
};
export const DateSelect = ({
value,
onChange,
}: {
value: number;
onChange: (value: number) => void;
}) => {
const t = useAFFiNEI18N();
const [open, setOpen] = useState(false);
const onDateChange = useCallback(
(e: string) => {
setOpen(false);
onChange(dayjs(e, 'YYYY-MM-DD').valueOf());
},
[onChange]
);
return (
<Popover
open={open}
onOpenChange={setOpen}
contentOptions={datePickerPopperContentOptions}
content={
<DatePicker
weekDays={t['com.affine.calendar-date-picker.week-days']()}
monthNames={t['com.affine.calendar-date-picker.month-names']()}
todayLabel={t['com.affine.calendar-date-picker.today']()}
value={dayjs(value as number).format('YYYY-MM-DD')}
onChange={onDateChange}
/>
}
>
<input
value={dayjs(value as number).format('MMM DD')}
className={datePickerTriggerInput}
/>
</Popover>
);
};

View File

@ -1,8 +1,8 @@
import { AFFiNEDatePicker, Input, Menu, MenuItem } from '@affine/component';
import { Input, Menu, MenuItem } from '@affine/component';
import type { LiteralValue, Tag } from '@affine/env/filter';
import dayjs from 'dayjs';
import { type ReactNode } from 'react';
import { DateSelect } from './date-select';
import { FilterTag } from './filter-tag-translation';
import { inputStyle } from './index.css';
import { tBoolean, tDate, tDateRange, tTag } from './logical/custom-type';
@ -67,12 +67,7 @@ literalMatcher.register(tBoolean.create(), {
});
literalMatcher.register(tDate.create(), {
render: ({ value, onChange }) => (
<AFFiNEDatePicker
value={dayjs(value as number).format('YYYY-MM-DD')}
onChange={e => {
onChange(dayjs(e, 'YYYY-MM-DD').valueOf());
}}
/>
<DateSelect value={value as number} onChange={onChange} />
),
});
const getTagsOfArrayTag = (type: TType): Tag[] => {

View File

@ -1,5 +1,6 @@
import { cssVar } from '@toeverything/theme';
import { globalStyle, style } from '@vanilla-extract/css';
import { style } from '@vanilla-extract/css';
const interactive = style({
position: 'relative',
cursor: 'pointer',
@ -41,6 +42,7 @@ export const journalPanel = style({
display: 'flex',
flexDirection: 'column',
alignItems: 'stretch',
overflow: 'hidden',
});
export const dailyCount = style({
height: 0,
@ -179,14 +181,47 @@ export const journalConflictMoreTrigger = style([
},
]);
// TODO: when date-picker's cell is customizable, we should implement by custom cell
// override date-picker's active day when is not journal
globalStyle(
`.${journalPanel}[data-is-journal="false"] .react-datepicker__day[aria-selected="true"]`,
// customize date-picker cell
export const journalDateCell = style([
interactive,
{
backgroundColor: 'transparent',
width: '100%',
height: '100%',
borderRadius: 8,
fontSize: cssVar('fontSm'),
color: cssVar('textPrimaryColor'),
fontWeight: 500,
border: `1px solid ${cssVar('primaryColor')}`,
}
);
fontWeight: 400,
position: 'relative',
selectors: {
'&[data-is-today="true"]': {
fontWeight: 600,
color: cssVar('brandColor'),
},
'&[data-not-current-month="true"]': {
color: cssVar('black10'),
},
'&[data-selected="true"]': {
backgroundColor: cssVar('brandColor'),
fontWeight: 500,
color: cssVar('pureWhite'),
},
'&[data-is-journal="false"][data-selected="true"]': {
backgroundColor: 'transparent',
color: 'var(--affine-text-primary-color)',
fontWeight: 500,
border: `1px solid ${cssVar('primaryColor')}`,
},
},
},
]);
export const journalDateCellDot = style({
width: 4,
height: 4,
borderRadius: '50%',
backgroundColor: cssVar('primaryColor'),
position: 'absolute',
bottom: 0,
left: '50%',
transform: 'translateX(-50%)',
});

View File

@ -1,5 +1,6 @@
import {
AFFiNEDatePicker,
type DateCell,
DatePicker,
IconButton,
Menu,
Scrollable,
@ -80,6 +81,7 @@ interface JournalBlockProps extends EditorExtensionProps {
const EditorJournalPanel = (props: EditorExtensionProps) => {
const { workspace, page } = props;
const t = useAFFiNEI18N();
const { journalDate, isJournal } = useJournalInfoHelper(
page.workspace,
page.id
@ -99,14 +101,45 @@ const EditorJournalPanel = (props: EditorExtensionProps) => {
[journalDate, openJournal]
);
const customDayRenderer = useCallback(
(cell: DateCell) => {
// TODO: add a dot to indicate journal
// has performance issue for now, better to calculate it in advance
// const hasJournal = !!getJournalsByDate(cell.date.format('YYYY-MM-DD'))?.length;
const hasJournal = false;
return (
<button
className={styles.journalDateCell}
data-is-date-cell
tabIndex={cell.focused ? 0 : -1}
data-is-today={cell.isToday}
data-not-current-month={cell.notCurrentMonth}
data-selected={cell.selected}
data-is-journal={isJournal}
data-has-journal={hasJournal}
>
{cell.label}
{hasJournal && !cell.selected ? (
<div className={styles.journalDateCellDot} />
) : null}
</button>
);
},
[isJournal]
);
return (
<div className={styles.journalPanel} data-is-journal={isJournal}>
<AFFiNEDatePicker
inline
value={date}
onSelect={onDateSelect}
calendarClassName={styles.calendar}
/>
<div className={styles.calendar}>
<DatePicker
weekDays={t['com.affine.calendar-date-picker.week-days']()}
monthNames={t['com.affine.calendar-date-picker.month-names']()}
todayLabel={t['com.affine.calendar-date-picker.today']()}
customDayRenderer={customDayRenderer}
value={date}
onChange={onDateSelect}
/>
</div>
<JournalConflictBlock date={dayjs(date)} {...props} />
<JournalDailyCountBlock date={dayjs(date)} {...props} />
</div>

View File

@ -99,7 +99,6 @@ test('use monthpicker to modify the month of datepicker', async ({ page }) => {
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);

View File

@ -78,68 +78,8 @@ export const fillDatePicker = async (page: Page, date: Date) => {
.fill(dateFormat(date));
};
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;
};
export const selectDateFromDatePicker = async (page: Page, date: Date) => {
const datePickerPopup = page.locator('.react-datepicker-popper');
const day = date.getDate();
const month = date.toLocaleString('en-US', { month: 'long' });
const weekday = date.toLocaleString('en-US', { weekday: 'long' });
const year = date.getFullYear().toString();
const nth = function (d: number) {
if (d > 3 && d < 21) return 'th';
switch (d % 10) {
case 1:
return 'st';
case 2:
return 'nd';
case 3:
return 'rd';
default:
return 'th';
}
};
const daySuffix = nth(day);
// Open the date picker popup
await clickDatePicker(page);
const selectDate = async (): Promise<void> => {
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}${daySuffix}, ${year}"]`
);
await dateCell.click();
};
await selectDate();
// Wait for the date picker popup to close
await datePickerPopup.waitFor({ state: 'hidden' });
};
export const selectMonthFromMonthPicker = async (page: Page, date: Date) => {
const month = date.toLocaleString('en-US', { month: 'long' });
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const year = date.getFullYear();
// Open the month picker popup
await clickMonthPicker(page);
@ -148,14 +88,16 @@ export const selectMonthFromMonthPicker = async (page: Page, date: Date) => {
.getByTestId('month-picker-current-year')
.innerText());
if (selectedYear > year) {
await page.locator('[data-testid="month-picker-prev-button"]').click();
await page.locator('[data-testid="date-picker-nav-prev"]').click();
return await selectMonth();
} else if (selectedYear < year) {
await page.locator('[data-testid="month-picker-next-button"]').click();
await page.locator('[data-testid="date-picker-nav-next"]').click();
return await selectMonth();
}
// Click on the day cell
const monthCell = page.locator(`[aria-label="Choose ${month} ${year}"]`);
const monthCell = page.locator(
`[data-is-month-cell][aria-label="${year}-${month}"]`
);
await monthCell.click();
};
await selectMonth();
@ -163,8 +105,8 @@ export const selectMonthFromMonthPicker = async (page: Page, date: Date) => {
export 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' }));
await page.getByTestId('month-picker-button').evaluate(e => e.dataset.month)
).toBe(date.getMonth().toString());
};
const createTag = async (page: Page, name: string) => {