瀏覽代碼

chore: date picker

ascarbek 2 年之前
父節點
當前提交
03eaf0b4cb

+ 4 - 3
frontend/appflowy_tauri/package.json

@@ -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",

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CellOptionsPopup.tsx

@@ -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);
     }

+ 99 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/DatePickerPopup.tsx

@@ -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>
+  );
+};

+ 10 - 9
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellDate.tsx

@@ -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>
   );
 };

+ 7 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellWrapper.tsx

@@ -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 && (

+ 23 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditRow.tsx

@@ -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>
   );

+ 15 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/ClockSvg.tsx

@@ -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>
+  );
+};

+ 1 - 0
frontend/appflowy_tauri/src/main.tsx

@@ -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 - 0
frontend/appflowy_tauri/src/styles/Calendar.css

@@ -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;
+}

+ 6 - 1
frontend/appflowy_tauri/tailwind.config.cjs

@@ -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: {