mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-23 13:01:59 +03:00
feat: add date picker (#2644)
Co-authored-by: himself65 <himself65@outlook.com>
This commit is contained in:
parent
bedf838fe5
commit
29d8f61c90
@ -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",
|
||||
|
191
packages/component/src/components/date-picker/date-picker.tsx
Normal file
191
packages/component/src/components/date-picker/date-picker.tsx
Normal file
@ -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<Date | null>(
|
||||
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 (
|
||||
<div className={styles.headerStyle}>
|
||||
<div
|
||||
data-testid="date-picker-current-month"
|
||||
className={styles.mouthStyle}
|
||||
>
|
||||
{months[selectedMonth]}
|
||||
</div>
|
||||
<div
|
||||
data-testid="date-picker-current-year"
|
||||
className={styles.yearStyle}
|
||||
>
|
||||
{selectedYear}
|
||||
</div>
|
||||
<div
|
||||
data-testid="month-picker-button"
|
||||
className={styles.arrowDownStyle}
|
||||
onClick={handleOpenMonthPicker}
|
||||
>
|
||||
<ArrowDownSmallIcon />
|
||||
</div>
|
||||
<button
|
||||
data-testid="date-picker-prev-button"
|
||||
className={styles.arrowLeftStyle}
|
||||
onClick={decreaseMonth}
|
||||
disabled={prevMonthButtonDisabled}
|
||||
>
|
||||
<ArrowLeftSmallIcon />
|
||||
</button>
|
||||
<button
|
||||
data-testid="date-picker-next-button"
|
||||
className={styles.arrowRightStyle}
|
||||
onClick={increaseMonth}
|
||||
disabled={nextMonthButtonDisabled}
|
||||
>
|
||||
<ArrowRightSmallIcon />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const renderCustomMonthHeader = ({
|
||||
date,
|
||||
decreaseYear,
|
||||
increaseYear,
|
||||
prevYearButtonDisabled,
|
||||
nextYearButtonDisabled,
|
||||
}: {
|
||||
date: Date;
|
||||
decreaseYear: () => void;
|
||||
increaseYear: () => void;
|
||||
prevYearButtonDisabled: boolean;
|
||||
nextYearButtonDisabled: boolean;
|
||||
}) => {
|
||||
const selectedYear = dayjs(date).year();
|
||||
return (
|
||||
<div className={styles.monthHeaderStyle}>
|
||||
<div
|
||||
data-testid="month-picker-current-year"
|
||||
className={styles.monthTitleStyle}
|
||||
>
|
||||
{selectedYear}
|
||||
</div>
|
||||
<button
|
||||
data-testid="month-picker-prev-button"
|
||||
className={styles.arrowLeftStyle}
|
||||
onClick={decreaseYear}
|
||||
disabled={prevYearButtonDisabled}
|
||||
>
|
||||
<ArrowLeftSmallIcon />
|
||||
</button>
|
||||
<button
|
||||
data-testid="month-picker-next-button"
|
||||
className={styles.arrowRightStyle}
|
||||
onClick={increaseYear}
|
||||
disabled={nextYearButtonDisabled}
|
||||
>
|
||||
<ArrowRightSmallIcon />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<DatePicker
|
||||
onClickOutside={handleCloseMonthPicker}
|
||||
className={styles.inputStyle}
|
||||
calendarClassName={styles.calendarStyle}
|
||||
weekDayClassName={() => 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;
|
200
packages/component/src/components/date-picker/index.css.ts
Normal file
200
packages/component/src/components/date-picker/index.css.ts
Normal file
@ -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',
|
||||
});
|
1
packages/component/src/components/date-picker/index.ts
Normal file
1
packages/component/src/components/date-picker/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './date-picker';
|
@ -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={<VariableSelect selected={[]} onSelect={onChange} />}
|
||||
>
|
||||
<div data-testid="variable-name" className={styles.filterTypeStyle}>
|
||||
{ast.left.name}
|
||||
<div className={styles.filterTypeIconStyle}>
|
||||
{variableDefineMap[ast.left.name].icon}
|
||||
</div>
|
||||
<div>{ast.left.name}</div>
|
||||
</div>
|
||||
</Menu>
|
||||
<Menu
|
||||
@ -101,7 +105,7 @@ export const Arg = ({
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div data-testid="filter-arg" style={{ marginLeft: 4 }}>
|
||||
<div data-testid="filter-arg" style={{ marginLeft: 4, fontWeight: 600 }}>
|
||||
{data.render({ type, value, onChange })}
|
||||
</div>
|
||||
);
|
||||
|
@ -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)',
|
||||
});
|
||||
|
@ -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 }) => (
|
||||
<input
|
||||
className={inputStyle}
|
||||
<AFFiNEDatePicker
|
||||
value={dayjs(value.value as number).format('YYYY-MM-DD')}
|
||||
type="date"
|
||||
onChange={e => {
|
||||
onChange({
|
||||
type: 'literal',
|
||||
value: dayjs(e.target.value, 'YYYY-MM-DD').valueOf(),
|
||||
value: dayjs(e, 'YYYY-MM-DD').valueOf(),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
@ -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 = {
|
||||
|
@ -63,7 +63,7 @@ export const VariableSelect = ({
|
||||
// .filter(v => !selected.find(filter => filter.left.name === v.name))
|
||||
.map(v => (
|
||||
<MenuItem
|
||||
icon={v.icon}
|
||||
icon={variableDefineMap[v.name].icon}
|
||||
key={v.name}
|
||||
onClick={() => {
|
||||
onSelect(createDefaultFilter(v));
|
||||
|
@ -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 (
|
||||
<div>
|
||||
<h1>Save As New View</h1>
|
||||
<div className={styles.saveTitle}>Save As New View</div>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.04)',
|
||||
backgroundColor: 'var(--affine-hover-color)',
|
||||
borderRadius: 8,
|
||||
padding: 20,
|
||||
marginTop: 20,
|
||||
@ -47,11 +47,13 @@ const CreateView = ({
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 20 }}
|
||||
style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 40 }}
|
||||
>
|
||||
<Button onClick={onCancel}>Cancel</Button>
|
||||
<Button className={styles.cancelButton} onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
style={{ marginLeft: 20 }}
|
||||
style={{ marginLeft: 20, borderRadius: '8px' }}
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
if (value.name.trim().length > 0) {
|
||||
@ -82,7 +84,19 @@ export const SaveViewButton = ({ init, onConfirm }: CreateViewProps) => {
|
||||
</div>
|
||||
</Button>
|
||||
<Modal open={show} onClose={() => changeShow(false)}>
|
||||
<ModalWrapper width={560} style={{ padding: '40px' }}>
|
||||
<ModalWrapper
|
||||
width={560}
|
||||
style={{
|
||||
padding: '40px',
|
||||
background: 'var(--affine-background-primary-color)',
|
||||
}}
|
||||
>
|
||||
<ModalCloseButton
|
||||
top={12}
|
||||
right={12}
|
||||
onClick={() => changeShow(false)}
|
||||
hoverColor="var(--affine-icon-color)"
|
||||
/>
|
||||
<CreateView
|
||||
init={init}
|
||||
onCancel={() => changeShow(false)}
|
||||
|
@ -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',
|
||||
});
|
||||
|
@ -1,3 +1,4 @@
|
||||
@import 'react-datepicker/dist/react-datepicker.css';
|
||||
* {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
|
||||
|
13
packages/storybook/src/stories/datepicker.stories.tsx
Normal file
13
packages/storybook/src/stories/datepicker.stories.tsx
Normal file
@ -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<string>(new Date().toString());
|
||||
return <AFFiNEDatePicker value={value} onChange={setValue} />;
|
||||
};
|
@ -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<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}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<void> => {
|
||||
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);
|
||||
});
|
||||
|
82
yarn.lock
82
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"
|
||||
|
Loading…
Reference in New Issue
Block a user