mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
chore: simplify date editor
This commit is contained in:
@@ -32,7 +32,6 @@
|
|||||||
"mobx": "^6.13.7",
|
"mobx": "^6.13.7",
|
||||||
"mobx-react-lite": "^4.1.0",
|
"mobx-react-lite": "^4.1.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-datepicker": "^8.4.0",
|
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-force-graph-2d": "^1.27.1",
|
"react-force-graph-2d": "^1.27.1",
|
||||||
"react-hot-toast": "^2.5.2",
|
"react-hot-toast": "^2.5.2",
|
||||||
|
41
web/pnpm-lock.yaml
generated
41
web/pnpm-lock.yaml
generated
@@ -80,9 +80,6 @@ importers:
|
|||||||
react:
|
react:
|
||||||
specifier: ^18.3.1
|
specifier: ^18.3.1
|
||||||
version: 18.3.1
|
version: 18.3.1
|
||||||
react-datepicker:
|
|
||||||
specifier: ^8.4.0
|
|
||||||
version: 8.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
|
||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^18.3.1
|
specifier: ^18.3.1
|
||||||
version: 18.3.1(react@18.3.1)
|
version: 18.3.1(react@18.3.1)
|
||||||
@@ -1021,12 +1018,6 @@ packages:
|
|||||||
react: '>=16.8.0'
|
react: '>=16.8.0'
|
||||||
react-dom: '>=16.8.0'
|
react-dom: '>=16.8.0'
|
||||||
|
|
||||||
'@floating-ui/react@0.27.9':
|
|
||||||
resolution: {integrity: sha512-Y0aCJBNtfVF6ikI1kVzA0WzSAhVBz79vFWOhvb5MLCRNODZ1ylGSLTuncchR7JsLyn9QzV6JD44DyZhhOtvpRw==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>=17.0.0'
|
|
||||||
react-dom: '>=17.0.0'
|
|
||||||
|
|
||||||
'@floating-ui/utils@0.2.9':
|
'@floating-ui/utils@0.2.9':
|
||||||
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
|
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
|
||||||
|
|
||||||
@@ -2267,9 +2258,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
|
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
date-fns@4.1.0:
|
|
||||||
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
|
|
||||||
|
|
||||||
dayjs@1.11.13:
|
dayjs@1.11.13:
|
||||||
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
||||||
|
|
||||||
@@ -3306,12 +3294,6 @@ packages:
|
|||||||
queue-microtask@1.2.3:
|
queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
|
|
||||||
react-datepicker@8.4.0:
|
|
||||||
resolution: {integrity: sha512-6nPDnj8vektWCIOy9ArS3avus9Ndsyz5XgFCJ7nBxXASSpBdSL6lG9jzNNmViPOAOPh6T5oJyGaXuMirBLECag==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc
|
|
||||||
react-dom: ^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc
|
|
||||||
|
|
||||||
react-dom@18.3.1:
|
react-dom@18.3.1:
|
||||||
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
|
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3695,9 +3677,6 @@ packages:
|
|||||||
systemjs@6.15.1:
|
systemjs@6.15.1:
|
||||||
resolution: {integrity: sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==}
|
resolution: {integrity: sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==}
|
||||||
|
|
||||||
tabbable@6.2.0:
|
|
||||||
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
|
|
||||||
|
|
||||||
tailwind-merge@2.6.0:
|
tailwind-merge@2.6.0:
|
||||||
resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
|
resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
|
||||||
|
|
||||||
@@ -4925,14 +4904,6 @@ snapshots:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
|
||||||
'@floating-ui/react@0.27.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
|
||||||
dependencies:
|
|
||||||
'@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
|
||||||
'@floating-ui/utils': 0.2.9
|
|
||||||
react: 18.3.1
|
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
|
||||||
tabbable: 6.2.0
|
|
||||||
|
|
||||||
'@floating-ui/utils@0.2.9': {}
|
'@floating-ui/utils@0.2.9': {}
|
||||||
|
|
||||||
'@github/relative-time-element@4.4.8': {}
|
'@github/relative-time-element@4.4.8': {}
|
||||||
@@ -6248,8 +6219,6 @@ snapshots:
|
|||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
is-data-view: 1.0.2
|
is-data-view: 1.0.2
|
||||||
|
|
||||||
date-fns@4.1.0: {}
|
|
||||||
|
|
||||||
dayjs@1.11.13: {}
|
dayjs@1.11.13: {}
|
||||||
|
|
||||||
debug@4.4.1:
|
debug@4.4.1:
|
||||||
@@ -7407,14 +7376,6 @@ snapshots:
|
|||||||
|
|
||||||
queue-microtask@1.2.3: {}
|
queue-microtask@1.2.3: {}
|
||||||
|
|
||||||
react-datepicker@8.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
|
||||||
dependencies:
|
|
||||||
'@floating-ui/react': 0.27.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
|
||||||
clsx: 2.1.1
|
|
||||||
date-fns: 4.1.0
|
|
||||||
react: 18.3.1
|
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
|
||||||
|
|
||||||
react-dom@18.3.1(react@18.3.1):
|
react-dom@18.3.1(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
@@ -7872,8 +7833,6 @@ snapshots:
|
|||||||
|
|
||||||
systemjs@6.15.1: {}
|
systemjs@6.15.1: {}
|
||||||
|
|
||||||
tabbable@6.2.0: {}
|
|
||||||
|
|
||||||
tailwind-merge@2.6.0: {}
|
tailwind-merge@2.6.0: {}
|
||||||
|
|
||||||
tailwindcss-animate@1.0.7(tailwindcss@3.4.17):
|
tailwindcss-animate@1.0.7(tailwindcss@3.4.17):
|
||||||
|
49
web/src/components/DateTimeInput.tsx
Normal file
49
web/src/components/DateTimeInput.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { isEqual } from "lodash-es";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
import { cn } from "@/utils";
|
||||||
|
|
||||||
|
// Helper function to convert Date to local datetime string.
|
||||||
|
const toLocalDateTimeString = (date: Date | undefined): string => {
|
||||||
|
if (!date) return "";
|
||||||
|
return new Date(date.getTime() - date.getTimezoneOffset() * 60000).toISOString().slice(0, -1);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
value: Date | undefined;
|
||||||
|
originalValue: Date | undefined;
|
||||||
|
onChange: (date: Date) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DateTimeInput: React.FC<Props> = ({ label, value, originalValue, onChange }) => {
|
||||||
|
return (
|
||||||
|
<div className="w-full flex items-center gap-2">
|
||||||
|
<span>{label}</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className={cn(
|
||||||
|
"flex-1 px-1 bg-transparent rounded text-xs transition-all",
|
||||||
|
"border-transparent focus:border-gray-300 dark:focus:border-zinc-700",
|
||||||
|
!isEqual(value, originalValue) && "border-gray-300 dark:border-zinc-700",
|
||||||
|
"border",
|
||||||
|
)}
|
||||||
|
defaultValue={toLocalDateTimeString(value)}
|
||||||
|
onBlur={(e) => {
|
||||||
|
const inputValue = e.target.value;
|
||||||
|
if (inputValue) {
|
||||||
|
const date = new Date(inputValue);
|
||||||
|
if (!isNaN(date.getTime())) {
|
||||||
|
onChange(date);
|
||||||
|
} else {
|
||||||
|
toast.error("Invalid datetime format. Use format: 2023-12-31T23:59:59");
|
||||||
|
e.target.value = toLocalDateTimeString(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder="YYYY-MM-DDTHH:mm:ss"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DateTimeInput;
|
@@ -3,7 +3,6 @@ import { isEqual } from "lodash-es";
|
|||||||
import { LoaderIcon, SendIcon } from "lucide-react";
|
import { LoaderIcon, SendIcon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import DatePicker from "react-datepicker";
|
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import useLocalStorage from "react-use/lib/useLocalStorage";
|
import useLocalStorage from "react-use/lib/useLocalStorage";
|
||||||
@@ -19,6 +18,7 @@ import { UserSetting } from "@/types/proto/api/v1/user_service";
|
|||||||
import { cn } from "@/utils";
|
import { cn } from "@/utils";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import { convertVisibilityFromString } from "@/utils/memo";
|
import { convertVisibilityFromString } from "@/utils/memo";
|
||||||
|
import DateTimeInput from "../DateTimeInput";
|
||||||
import AddMemoRelationPopover from "./ActionButton/AddMemoRelationPopover";
|
import AddMemoRelationPopover from "./ActionButton/AddMemoRelationPopover";
|
||||||
import LocationSelector from "./ActionButton/LocationSelector";
|
import LocationSelector from "./ActionButton/LocationSelector";
|
||||||
import MarkdownMenu from "./ActionButton/MarkdownMenu";
|
import MarkdownMenu from "./ActionButton/MarkdownMenu";
|
||||||
@@ -30,7 +30,6 @@ import RelationListView from "./RelationListView";
|
|||||||
import ResourceListView from "./ResourceListView";
|
import ResourceListView from "./ResourceListView";
|
||||||
import { handleEditorKeydownWithMarkdownShortcuts, hyperlinkHighlightedText } from "./handlers";
|
import { handleEditorKeydownWithMarkdownShortcuts, hyperlinkHighlightedText } from "./handlers";
|
||||||
import { MemoEditorContext } from "./types";
|
import { MemoEditorContext } from "./types";
|
||||||
import "react-datepicker/dist/react-datepicker.css";
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -71,7 +70,10 @@ const MemoEditor = observer((props: Props) => {
|
|||||||
isComposing: false,
|
isComposing: false,
|
||||||
isDraggingFile: false,
|
isDraggingFile: false,
|
||||||
});
|
});
|
||||||
const [displayTime, setDisplayTime] = useState<Date | undefined>();
|
const [createTime, setCreateTime] = useState<Date | undefined>();
|
||||||
|
const [updateTime, setUpdateTime] = useState<Date | undefined>();
|
||||||
|
const [originalCreateTime, setOriginalCreateTime] = useState<Date | undefined>();
|
||||||
|
const [originalUpdateTime, setOriginalUpdateTime] = useState<Date | undefined>();
|
||||||
const [hasContent, setHasContent] = useState<boolean>(false);
|
const [hasContent, setHasContent] = useState<boolean>(false);
|
||||||
const [isVisibilitySelectorOpen, setIsVisibilitySelectorOpen] = useState(false);
|
const [isVisibilitySelectorOpen, setIsVisibilitySelectorOpen] = useState(false);
|
||||||
const editorRef = useRef<EditorRefActions>(null);
|
const editorRef = useRef<EditorRefActions>(null);
|
||||||
@@ -119,7 +121,10 @@ const MemoEditor = observer((props: Props) => {
|
|||||||
const memo = await memoStore.getOrFetchMemoByName(memoName);
|
const memo = await memoStore.getOrFetchMemoByName(memoName);
|
||||||
if (memo) {
|
if (memo) {
|
||||||
handleEditorFocus();
|
handleEditorFocus();
|
||||||
setDisplayTime(memo.displayTime);
|
setCreateTime(memo.createTime);
|
||||||
|
setUpdateTime(memo.updateTime);
|
||||||
|
setOriginalCreateTime(memo.createTime);
|
||||||
|
setOriginalUpdateTime(memo.updateTime);
|
||||||
setState((prevState) => ({
|
setState((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
memoVisibility: memo.visibility,
|
memoVisibility: memo.visibility,
|
||||||
@@ -361,9 +366,13 @@ const MemoEditor = observer((props: Props) => {
|
|||||||
if (["content", "resources", "relations", "location"].some((key) => updateMask.has(key))) {
|
if (["content", "resources", "relations", "location"].some((key) => updateMask.has(key))) {
|
||||||
updateMask.add("update_time");
|
updateMask.add("update_time");
|
||||||
}
|
}
|
||||||
if (!isEqual(displayTime, prevMemo.displayTime)) {
|
if (createTime && !isEqual(createTime, prevMemo.createTime)) {
|
||||||
updateMask.add("display_time");
|
updateMask.add("create_time");
|
||||||
memoPatch.displayTime = displayTime;
|
memoPatch.createTime = createTime;
|
||||||
|
}
|
||||||
|
if (updateTime && !isEqual(updateTime, prevMemo.updateTime)) {
|
||||||
|
updateMask.add("update_time");
|
||||||
|
memoPatch.updateTime = updateTime;
|
||||||
}
|
}
|
||||||
if (updateMask.size === 0) {
|
if (updateMask.size === 0) {
|
||||||
toast.error("No changes detected");
|
toast.error("No changes detected");
|
||||||
@@ -487,19 +496,6 @@ const MemoEditor = observer((props: Props) => {
|
|||||||
onCompositionStart={handleCompositionStart}
|
onCompositionStart={handleCompositionStart}
|
||||||
onCompositionEnd={handleCompositionEnd}
|
onCompositionEnd={handleCompositionEnd}
|
||||||
>
|
>
|
||||||
{memoName && displayTime && (
|
|
||||||
<DatePicker
|
|
||||||
selected={displayTime}
|
|
||||||
onChange={(date) => date && setDisplayTime(date)}
|
|
||||||
showTimeSelect
|
|
||||||
showMonthDropdown
|
|
||||||
showYearDropdown
|
|
||||||
yearDropdownItemNumber={5}
|
|
||||||
dateFormatCalendar=" "
|
|
||||||
customInput={<span className="cursor-pointer text-sm text-gray-400 dark:text-gray-500">{displayTime.toLocaleString()}</span>}
|
|
||||||
calendarClassName="ml-24 sm:ml-44"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Editor ref={editorRef} {...editorConfig} />
|
<Editor ref={editorRef} {...editorConfig} />
|
||||||
<ResourceListView resourceList={state.resourceList} setResourceList={handleSetResourceList} />
|
<ResourceListView resourceList={state.resourceList} setResourceList={handleSetResourceList} />
|
||||||
<RelationListView relationList={referenceRelations} setRelationList={handleSetRelationList} />
|
<RelationListView relationList={referenceRelations} setRelationList={handleSetRelationList} />
|
||||||
@@ -547,6 +543,20 @@ const MemoEditor = observer((props: Props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Show memo metadata if memoName is provided */}
|
||||||
|
{memoName && (
|
||||||
|
<div className="w-full mb-4 text-xs leading-5 px-4 opacity-60 font-mono text-gray-500 dark:text-zinc-500">
|
||||||
|
{!isEqual(createTime, updateTime) && (
|
||||||
|
<DateTimeInput label="Updated" value={updateTime} originalValue={originalUpdateTime} onChange={setUpdateTime} />
|
||||||
|
)}
|
||||||
|
<DateTimeInput label="Created" value={createTime} originalValue={originalCreateTime} onChange={setCreateTime} />
|
||||||
|
<div className="w-full flex items-center gap-2">
|
||||||
|
<span>ID:</span>
|
||||||
|
<span>{memoName}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</MemoEditorContext.Provider>
|
</MemoEditorContext.Provider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
import { CalendarIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
||||||
import DatePicker from "react-datepicker";
|
|
||||||
import i18n from "@/i18n";
|
import i18n from "@/i18n";
|
||||||
import type { MonthNavigatorProps } from "@/types/statistics";
|
import type { MonthNavigatorProps } from "@/types/statistics";
|
||||||
import "react-datepicker/dist/react-datepicker.css";
|
|
||||||
|
|
||||||
export const MonthNavigator = ({ visibleMonth, onMonthChange }: MonthNavigatorProps) => {
|
export const MonthNavigator = ({ visibleMonth, onMonthChange }: MonthNavigatorProps) => {
|
||||||
const currentMonth = dayjs(visibleMonth).toDate();
|
const currentMonth = dayjs(visibleMonth).toDate();
|
||||||
@@ -18,25 +16,9 @@ export const MonthNavigator = ({ visibleMonth, onMonthChange }: MonthNavigatorPr
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full mb-1 flex flex-row justify-between items-center gap-1">
|
<div className="w-full mb-1 flex flex-row justify-between items-center gap-1">
|
||||||
<div className="relative text-sm font-medium inline-flex flex-row items-center w-auto dark:text-gray-400">
|
<div className="relative text-sm inline-flex flex-row items-center w-auto gap-2 dark:text-gray-400">
|
||||||
<DatePicker
|
<CalendarIcon className="w-4 h-4" />
|
||||||
selected={currentMonth}
|
{currentMonth.toLocaleString(i18n.language, { year: "numeric", month: "long" })}
|
||||||
onChange={(date) => {
|
|
||||||
if (date) {
|
|
||||||
onMonthChange(dayjs(date).format("YYYY-MM"));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
dateFormat="MMMM yyyy"
|
|
||||||
showMonthYearPicker
|
|
||||||
showFullMonthYearPicker
|
|
||||||
customInput={
|
|
||||||
<span className="cursor-pointer text-base hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
|
||||||
{currentMonth.toLocaleString(i18n.language, { year: "numeric", month: "long" })}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
popperPlacement="bottom-start"
|
|
||||||
calendarClassName="!bg-white !border-gray-200 !font-normal !shadow-lg"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end items-center shrink-0 gap-1">
|
<div className="flex justify-end items-center shrink-0 gap-1">
|
||||||
<button className="p-1 cursor-pointer hover:opacity-80 transition-opacity" onClick={handlePrevMonth} aria-label="Previous month">
|
<button className="p-1 cursor-pointer hover:opacity-80 transition-opacity" onClick={handlePrevMonth} aria-label="Previous month">
|
||||||
|
Reference in New Issue
Block a user