mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: date picker
This commit is contained in:
parent
b9e49f109d
commit
03eaf0b4cb
@ -23,6 +23,7 @@
|
||||
"@slate-yjs/core": "^0.3.1",
|
||||
"@tanstack/react-virtual": "3.0.0-beta.54",
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"events": "^3.3.0",
|
||||
"google-protobuf": "^3.21.2",
|
||||
"i18next": "^22.4.10",
|
||||
@ -33,12 +34,12 @@
|
||||
"protoc-gen-ts": "^0.8.5",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-calendar": "^4.1.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-i18next": "^12.2.0",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-router-dom": "^6.8.0",
|
||||
"react-tailwindcss-datepicker": "^1.5.1",
|
||||
"react18-input-otp": "^1.1.2",
|
||||
"redux": "^4.2.1",
|
||||
"rxjs": "^7.8.0",
|
||||
@ -46,8 +47,8 @@
|
||||
"slate-react": "^0.91.9",
|
||||
"ts-results": "^3.3.0",
|
||||
"utf8": "^3.0.0",
|
||||
"yjs": "^13.5.51",
|
||||
"y-indexeddb": "^9.0.9"
|
||||
"y-indexeddb": "^9.0.9",
|
||||
"yjs": "^13.5.51"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1.2.2",
|
||||
|
@ -39,8 +39,8 @@ export const CellOptionsPopup = ({
|
||||
useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
const { height } = ref.current.getBoundingClientRect();
|
||||
if (top + height > window.innerHeight) {
|
||||
setAdjustedTop(window.innerHeight - height);
|
||||
if (top + height + 40 > window.innerHeight) {
|
||||
setAdjustedTop(window.innerHeight - height - 40);
|
||||
} else {
|
||||
setAdjustedTop(top);
|
||||
}
|
||||
|
@ -0,0 +1,99 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
||||
import { CellCache } from '$app/stores/effects/database/cell/cell_cache';
|
||||
import { FieldController } from '$app/stores/effects/database/field/field_controller';
|
||||
import useOutsideClick from '$app/components/_shared/useOutsideClick';
|
||||
import Calendar from 'react-calendar';
|
||||
import dayjs from 'dayjs';
|
||||
import { ClockSvg } from '$app/components/_shared/svg/ClockSvg';
|
||||
import { MoreSvg } from '$app/components/_shared/svg/MoreSvg';
|
||||
import { CheckboxSvg } from '$app/components/_shared/svg/CheckboxSvg';
|
||||
import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
|
||||
import { useCell } from '$app/components/_shared/database-hooks/useCell';
|
||||
import { DateCellDataPB } from '@/services/backend';
|
||||
|
||||
export const DatePickerPopup = ({
|
||||
left,
|
||||
top,
|
||||
cellIdentifier,
|
||||
cellCache,
|
||||
fieldController,
|
||||
onOutsideClick,
|
||||
}: {
|
||||
left: number;
|
||||
top: number;
|
||||
cellIdentifier: CellIdentifier;
|
||||
cellCache: CellCache;
|
||||
fieldController: FieldController;
|
||||
onOutsideClick: () => void;
|
||||
}) => {
|
||||
const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [adjustedTop, setAdjustedTop] = useState(-100);
|
||||
// const [value, setValue] = useState();
|
||||
const { t } = useTranslation('');
|
||||
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
const { height } = ref.current.getBoundingClientRect();
|
||||
if (top + height + 40 > window.innerHeight) {
|
||||
setAdjustedTop(top - height - 40);
|
||||
} else {
|
||||
setAdjustedTop(top);
|
||||
}
|
||||
}, [ref, window, top, left]);
|
||||
|
||||
useOutsideClick(ref, async () => {
|
||||
onOutsideClick();
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// console.log((data as DateCellDataPB).date);
|
||||
// setSelectedDate(new Date((data as DateCellDataPB).date));
|
||||
}, [data]);
|
||||
|
||||
const onChange = (v: Date | null | (Date | null)[]) => {
|
||||
if (v instanceof Date) {
|
||||
console.log(dayjs(v).format('YYYY-MM-DD'));
|
||||
setSelectedDate(v);
|
||||
// void cellController?.saveCellData(new DateCellDataPB({ date: dayjs(v).format('YYYY-MM-DD') }));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={`fixed z-10 rounded-lg bg-white px-2 py-2 text-xs shadow-md transition-opacity duration-300 ${
|
||||
adjustedTop === -100 ? 'opacity-0' : 'opacity-100'
|
||||
}`}
|
||||
style={{ top: `${adjustedTop + 40}px`, left: `${left}px` }}
|
||||
>
|
||||
<div className={'px-2'}>
|
||||
<Calendar onChange={(d) => onChange(d)} value={selectedDate} />
|
||||
</div>
|
||||
<hr className={'-mx-2 my-4 border-shade-6'} />
|
||||
<div className={'flex items-center justify-between px-4'}>
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<i className={'h-4 w-4'}>
|
||||
<ClockSvg></ClockSvg>
|
||||
</i>
|
||||
<span>{t('grid.field.includeTime')}</span>
|
||||
</div>
|
||||
<i className={'h-5 w-5'}>
|
||||
<EditorUncheckSvg></EditorUncheckSvg>
|
||||
</i>
|
||||
</div>
|
||||
<hr className={'-mx-2 my-4 border-shade-6'} />
|
||||
<div className={'flex items-center justify-between px-4 pb-2'}>
|
||||
<span>
|
||||
{t('grid.field.dateFormat')} & {t('grid.field.timeFormat')}
|
||||
</span>
|
||||
<i className={'h-5 w-5'}>
|
||||
<MoreSvg></MoreSvg>
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,28 +1,29 @@
|
||||
import Picker from 'react-tailwindcss-datepicker';
|
||||
import { DateValueType } from 'react-tailwindcss-datepicker/dist/types';
|
||||
import { useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { DateCellDataPB } from '@/services/backend';
|
||||
import { CellController } from '$app/stores/effects/database/cell/cell_controller';
|
||||
|
||||
export const EditCellDate = ({
|
||||
data,
|
||||
cellController,
|
||||
onEditClick,
|
||||
}: {
|
||||
data?: DateCellDataPB;
|
||||
cellController: CellController<any, any>;
|
||||
onEditClick: (left: number, top: number) => void;
|
||||
}) => {
|
||||
const [value, setValue] = useState<DateValueType>({
|
||||
startDate: new Date(),
|
||||
endDate: new Date(),
|
||||
});
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const onChange = (v: DateValueType) => {
|
||||
console.log(v);
|
||||
const onClick = () => {
|
||||
if (!ref.current) return;
|
||||
const { left, top } = ref.current.getBoundingClientRect();
|
||||
onEditClick(left, top);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={'px-4 py-2'}>
|
||||
<Picker value={value} onChange={onChange} useRange={false} asSingle={true}></Picker>;
|
||||
<div ref={ref} onClick={() => onClick()} className={'px-4 py-2'}>
|
||||
{data?.date || ''}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -19,12 +19,14 @@ export const EditCellWrapper = ({
|
||||
fieldController,
|
||||
onEditFieldClick,
|
||||
onEditOptionsClick,
|
||||
onEditDateClick,
|
||||
}: {
|
||||
cellIdentifier: CellIdentifier;
|
||||
cellCache: CellCache;
|
||||
fieldController: FieldController;
|
||||
onEditFieldClick: (top: number, right: number) => void;
|
||||
onEditOptionsClick: (left: number, top: number) => void;
|
||||
onEditDateClick: (left: number, top: number) => void;
|
||||
}) => {
|
||||
const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
|
||||
const databaseStore = useAppSelector((state) => state.database);
|
||||
@ -66,7 +68,11 @@ export const EditCellWrapper = ({
|
||||
)}
|
||||
|
||||
{cellIdentifier.fieldType === FieldType.DateTime && cellController && (
|
||||
<EditCellDate data={data as DateCellDataPB | undefined} cellController={cellController}></EditCellDate>
|
||||
<EditCellDate
|
||||
data={data as DateCellDataPB | undefined}
|
||||
onEditClick={onEditDateClick}
|
||||
cellController={cellController}
|
||||
></EditCellDate>
|
||||
)}
|
||||
|
||||
{cellIdentifier.fieldType === FieldType.Number && cellController && (
|
||||
|
@ -14,6 +14,7 @@ import { Some } from 'ts-results';
|
||||
import { FieldType } from '@/services/backend';
|
||||
import { CellOptions } from '$app/components/_shared/EditRow/CellOptions';
|
||||
import { CellOptionsPopup } from '$app/components/_shared/EditRow/CellOptionsPopup';
|
||||
import { DatePickerPopup } from '$app/components/_shared/EditRow/DatePickerPopup';
|
||||
|
||||
export const EditRow = ({
|
||||
onClose,
|
||||
@ -43,6 +44,10 @@ export const EditRow = ({
|
||||
const [changeOptionsTop, setChangeOptionsTop] = useState(0);
|
||||
const [changeOptionsLeft, setChangeOptionsLeft] = useState(0);
|
||||
|
||||
const [showDatePicker, setShowDatePicker] = useState(false);
|
||||
const [datePickerTop, setDatePickerTop] = useState(0);
|
||||
const [datePickerLeft, setDatePickerLeft] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
setUnveil(true);
|
||||
}, []);
|
||||
@ -94,6 +99,13 @@ export const EditRow = ({
|
||||
setShowChangeOptionsPopup(true);
|
||||
};
|
||||
|
||||
const onEditDateClick = async (cellIdentifier: CellIdentifier, left: number, top: number) => {
|
||||
setEditingCell(cellIdentifier);
|
||||
setDatePickerLeft(left);
|
||||
setDatePickerTop(top);
|
||||
setShowDatePicker(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`fixed inset-0 z-10 flex items-center justify-center bg-black/30 backdrop-blur-sm transition-opacity duration-300 ${
|
||||
@ -115,6 +127,7 @@ export const EditRow = ({
|
||||
fieldController={controller.fieldController}
|
||||
onEditFieldClick={(top: number, right: number) => onEditFieldClick(cell.cellIdentifier, top, right)}
|
||||
onEditOptionsClick={(left: number, top: number) => onEditOptionsClick(cell.cellIdentifier, left, top)}
|
||||
onEditDateClick={(left: number, top: number) => onEditDateClick(cell.cellIdentifier, left, top)}
|
||||
></EditCellWrapper>
|
||||
))}
|
||||
</div>
|
||||
@ -160,6 +173,16 @@ export const EditRow = ({
|
||||
onOutsideClick={() => setShowChangeOptionsPopup(false)}
|
||||
></CellOptionsPopup>
|
||||
)}
|
||||
{showDatePicker && editingCell && (
|
||||
<DatePickerPopup
|
||||
top={datePickerTop}
|
||||
left={datePickerLeft}
|
||||
cellIdentifier={editingCell}
|
||||
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
|
||||
fieldController={controller.fieldController}
|
||||
onOutsideClick={() => setShowDatePicker(false)}
|
||||
></DatePickerPopup>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -0,0 +1,15 @@
|
||||
export const ClockSvg = () => {
|
||||
return (
|
||||
<svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path
|
||||
d='M8 13C10.7614 13 13 10.7614 13 8C13 5.23858 10.7614 3 8 3C5.23858 3 3 5.23858 3 8C3 10.7614 5.23858 13 8 13Z'
|
||||
stroke='currentColor'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
/>
|
||||
<path d='M8 5V8L10 9' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round' />
|
||||
<path d='M11.5 2.5L13.5 4.5' stroke='currentColor' strokeLinecap='round' />
|
||||
<path d='M4.5 2.5L2.5 4.5' stroke='currentColor' strokeLinecap='round' />
|
||||
</svg>
|
||||
);
|
||||
};
|
@ -4,5 +4,6 @@ import App from './appflowy_app/App';
|
||||
import './styles/tailwind.css';
|
||||
import './styles/font.css';
|
||||
import './styles/template.css';
|
||||
import './styles/Calendar.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />);
|
||||
|
143
frontend/appflowy_tauri/src/styles/Calendar.css
Normal file
143
frontend/appflowy_tauri/src/styles/Calendar.css
Normal file
@ -0,0 +1,143 @@
|
||||
.react-calendar {
|
||||
width: 250px;
|
||||
max-width: 100%;
|
||||
background: white;
|
||||
line-height: 1.125em;
|
||||
}
|
||||
|
||||
.react-calendar--doubleView {
|
||||
width: 700px;
|
||||
}
|
||||
|
||||
.react-calendar--doubleView .react-calendar__viewContainer {
|
||||
display: flex;
|
||||
margin: -0.5em;
|
||||
}
|
||||
|
||||
.react-calendar--doubleView .react-calendar__viewContainer > * {
|
||||
width: 50%;
|
||||
margin: 0.5em;
|
||||
}
|
||||
|
||||
.react-calendar,
|
||||
.react-calendar *,
|
||||
.react-calendar *:before,
|
||||
.react-calendar *:after {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.react-calendar button {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.react-calendar button:enabled:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.react-calendar__navigation {
|
||||
display: flex;
|
||||
height: 44px;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.react-calendar__navigation button {
|
||||
min-width: 44px;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.react-calendar__navigation button:disabled {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.react-calendar__navigation button:enabled:hover,
|
||||
.react-calendar__navigation button:enabled:focus {
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
|
||||
.react-calendar__month-view__weekdays {
|
||||
@apply mb-2 text-center text-xs uppercase text-shade-3;
|
||||
}
|
||||
|
||||
.react-calendar__month-view__weekdays abbr {
|
||||
@apply no-underline;
|
||||
}
|
||||
|
||||
.react-calendar__month-view__weekdays__weekday {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.react-calendar__month-view__weekNumbers .react-calendar__tile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.75em;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.react-calendar__month-view__days__day--weekend {
|
||||
@apply text-main-alert;
|
||||
}
|
||||
|
||||
.react-calendar__month-view__days__day--neighboringMonth {
|
||||
/*color: #757575;*/
|
||||
@apply text-shade-4;
|
||||
}
|
||||
|
||||
.react-calendar__year-view .react-calendar__tile,
|
||||
.react-calendar__decade-view .react-calendar__tile,
|
||||
.react-calendar__century-view .react-calendar__tile {
|
||||
padding: 2em 0.5em;
|
||||
}
|
||||
|
||||
.react-calendar__tile {
|
||||
max-width: 100%;
|
||||
/*padding: 10px 6.6667px;*/
|
||||
background: none;
|
||||
text-align: center;
|
||||
line-height: 16px;
|
||||
@apply p-1;
|
||||
}
|
||||
|
||||
.react-calendar__tile:disabled {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.react-calendar__tile:enabled:hover,
|
||||
.react-calendar__tile:enabled:focus {
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
|
||||
.react-calendar__tile--now {
|
||||
@apply rounded bg-shade-6;
|
||||
}
|
||||
|
||||
.react-calendar__tile--now:enabled:hover,
|
||||
.react-calendar__tile--now:enabled:focus {
|
||||
@apply rounded bg-shade-6;
|
||||
}
|
||||
|
||||
.react-calendar__tile--hasActive {
|
||||
background: #76baff;
|
||||
}
|
||||
|
||||
.react-calendar__tile--hasActive:enabled:hover,
|
||||
.react-calendar__tile--hasActive:enabled:focus {
|
||||
background: #a9d4ff;
|
||||
}
|
||||
|
||||
.react-calendar__tile--active {
|
||||
@apply rounded bg-main-accent text-white;
|
||||
}
|
||||
|
||||
.react-calendar__tile--active:enabled:hover,
|
||||
.react-calendar__tile--active:enabled:focus {
|
||||
background: #1087ff;
|
||||
}
|
||||
|
||||
.react-calendar--selectRange .react-calendar__tile--hover {
|
||||
background-color: #e6e6e6;
|
||||
}
|
@ -1,6 +1,11 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||
content: [
|
||||
'./index.html',
|
||||
'./src/**/*.{js,ts,jsx,tsx}',
|
||||
'./node_modules/react-tailwindcss-datepicker/dist/index.esm.js',
|
||||
],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
|
Loading…
Reference in New Issue
Block a user