mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
chore: tweak timeline styles
This commit is contained in:
23
web/src/components/TimelineSidebar.tsx
Normal file
23
web/src/components/TimelineSidebar.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import classNames from "classnames";
|
||||
import SearchBar from "./SearchBar";
|
||||
import TagList from "./TagList";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const TimelineSidebar = (props: Props) => {
|
||||
return (
|
||||
<aside
|
||||
className={classNames(
|
||||
"relative w-full h-auto max-h-screen overflow-auto hide-scrollbar flex flex-col justify-start items-start",
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
<SearchBar />
|
||||
<TagList />
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimelineSidebar;
|
37
web/src/components/TimelineSidebarDrawer.tsx
Normal file
37
web/src/components/TimelineSidebarDrawer.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Drawer, IconButton } from "@mui/joy";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import Icon from "./Icon";
|
||||
import TimelineSidebar from "./TimelineSidebar";
|
||||
|
||||
const TimelineSidebarDrawer = () => {
|
||||
const location = useLocation();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(false);
|
||||
}, [location.pathname]);
|
||||
|
||||
const toggleDrawer = (inOpen: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
|
||||
if (event.type === "keydown" && ((event as React.KeyboardEvent).key === "Tab" || (event as React.KeyboardEvent).key === "Shift")) {
|
||||
return;
|
||||
}
|
||||
|
||||
setOpen(inOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton onClick={toggleDrawer(true)}>
|
||||
<Icon.Search className="w-5 h-auto dark:text-gray-400" />
|
||||
</IconButton>
|
||||
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}>
|
||||
<div className="w-full h-full px-5 bg-zinc-100 dark:bg-zinc-900">
|
||||
<TimelineSidebar className="py-4" />
|
||||
</div>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimelineSidebarDrawer;
|
@@ -1,6 +1,5 @@
|
||||
import { Button, Divider, IconButton } from "@mui/joy";
|
||||
import classNames from "classnames";
|
||||
import { sum } from "lodash-es";
|
||||
import { Fragment, useEffect, useRef, useState } from "react";
|
||||
import ActivityCalendar from "@/components/ActivityCalendar";
|
||||
import Empty from "@/components/Empty";
|
||||
@@ -9,6 +8,8 @@ import showMemoEditorDialog from "@/components/MemoEditor/MemoEditorDialog";
|
||||
import MemoFilter from "@/components/MemoFilter";
|
||||
import MemoView from "@/components/MemoView";
|
||||
import MobileHeader from "@/components/MobileHeader";
|
||||
import TimelineSidebar from "@/components/TimelineSidebar";
|
||||
import TimelineSidebarDrawer from "@/components/TimelineSidebarDrawer";
|
||||
import { memoServiceClient } from "@/grpcweb";
|
||||
import { DAILY_TIMESTAMP, DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
|
||||
import { getNormalizedTimeString, getTimeStampByDate } from "@/helpers/datetime";
|
||||
@@ -120,50 +121,53 @@ const Timeline = () => {
|
||||
|
||||
return (
|
||||
<section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
|
||||
<MobileHeader />
|
||||
<div className="w-full px-4 sm:px-6">
|
||||
<div className="w-full shadow flex flex-col justify-start items-start px-4 py-3 rounded-xl bg-white dark:bg-zinc-800 text-black dark:text-gray-300">
|
||||
<div className="relative w-full flex flex-row justify-between items-center">
|
||||
<div>
|
||||
<div
|
||||
className="py-1 flex flex-row justify-start items-center select-none opacity-80"
|
||||
onClick={() => setSelectedDay(undefined)}
|
||||
>
|
||||
<Icon.GanttChartSquare className="w-6 h-auto mr-1 opacity-80" />
|
||||
<span className="text-lg">{t("timeline.title")}</span>
|
||||
{!md && (
|
||||
<MobileHeader>
|
||||
<TimelineSidebarDrawer />
|
||||
</MobileHeader>
|
||||
)}
|
||||
<div className={classNames("w-full flex flex-row justify-start items-start px-4 sm:px-6 gap-4")}>
|
||||
<div className={classNames(md ? "w-[calc(100%-15rem)]" : "w-full")}>
|
||||
<div className="w-full shadow flex flex-col justify-start items-start px-4 py-3 rounded-xl bg-white dark:bg-zinc-800 text-black dark:text-gray-300">
|
||||
<div className="relative w-full flex flex-row justify-between items-center">
|
||||
<div>
|
||||
<div
|
||||
className="py-1 flex flex-row justify-start items-center select-none opacity-80"
|
||||
onClick={() => setSelectedDay(undefined)}
|
||||
>
|
||||
<Icon.GanttChartSquare className="w-6 h-auto mr-1 opacity-80" />
|
||||
<span className="text-lg">{t("timeline.title")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end items-center gap-2">
|
||||
<IconButton variant="outlined" size="sm" onClick={() => handleNewMemo()}>
|
||||
<Icon.Plus className="w-5 h-auto" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end items-center gap-2">
|
||||
<IconButton variant="outlined" size="sm" onClick={() => handleNewMemo()}>
|
||||
<Icon.Plus className="w-5 h-auto" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full h-auto flex flex-col justify-start items-start">
|
||||
<MemoFilter className="px-2 my-4" />
|
||||
<div className="w-full h-auto flex flex-col justify-start items-start">
|
||||
<MemoFilter className="px-2 my-4" />
|
||||
|
||||
{groupedByMonth.map((group, index) => (
|
||||
<Fragment key={group.month}>
|
||||
<div className={classNames("flex justify-start items-start w-full mt-2 last:mb-4", md ? "flex-row" : "flex-col")}>
|
||||
<div className={classNames("flex shrink-0", md ? "flex-col w-40 pr-4 pl-2 pb-8" : "flex-row w-full pl-1 mt-2 mb-2")}>
|
||||
<div className={classNames("w-full flex flex-col", md && "mt-4 mb-2")}>
|
||||
<span className="font-medium text-3xl leading-none mb-1">
|
||||
{new Date(group.month).toLocaleString(i18n.language, { month: "short", timeZone: "UTC" })}
|
||||
</span>
|
||||
<span className="opacity-60">{new Date(group.month).getUTCFullYear()}</span>
|
||||
<span className="text-xs opacity-40">Total: {sum(Object.values(group.data))}</span>
|
||||
{groupedByMonth.map((group, index) => (
|
||||
<Fragment key={group.month}>
|
||||
<div className={classNames("flex flex-col justify-start items-start w-full mt-2 last:mb-4")}>
|
||||
<div className={classNames("flex shrink-0 flex-row w-full pl-1 mt-2 mb-2")}>
|
||||
<div className={classNames("w-full flex flex-col")}>
|
||||
<span className="font-medium text-3xl leading-none mb-1">
|
||||
{new Date(group.month).toLocaleString(i18n.language, { month: "short", timeZone: "UTC" })}
|
||||
</span>
|
||||
<span className="opacity-60">{new Date(group.month).getUTCFullYear()}</span>
|
||||
</div>
|
||||
<ActivityCalendar month={group.month} data={group.data} onClick={(date) => setSelectedDay(date)} />
|
||||
</div>
|
||||
<ActivityCalendar month={group.month} data={group.data} onClick={(date) => setSelectedDay(date)} />
|
||||
</div>
|
||||
|
||||
<div className={classNames("flex flex-col justify-start items-start", md ? "w-[calc(100%-8rem)]" : "w-full")}>
|
||||
{group.memos.map((memo, index) => (
|
||||
<div
|
||||
key={`${memo.id}-${memo.displayTime}`}
|
||||
className={classNames("relative w-full flex flex-col justify-start items-start pl-4 sm:pl-10 pt-0")}
|
||||
>
|
||||
<MemoView className="!border !border-gray-100 dark:!border-zinc-700" memo={memo} />
|
||||
{group.memos.length > 1 && (
|
||||
<div className={classNames("w-full flex flex-col justify-start items-start")}>
|
||||
{group.memos.map((memo, index) => (
|
||||
<div
|
||||
key={`${memo.id}-${memo.displayTime}`}
|
||||
className={classNames("relative w-full flex flex-col justify-start items-start pl-4 sm:pl-10 pt-0")}
|
||||
>
|
||||
<MemoView className="!border max-w-full !border-gray-100 dark:!border-zinc-700" memo={memo} />
|
||||
<div className="absolute -left-2 sm:left-2 top-4 h-full">
|
||||
{index !== group.memos.length - 1 && (
|
||||
<div className="absolute top-2 left-[7px] h-full w-0.5 bg-gray-200 dark:bg-gray-700 block"></div>
|
||||
@@ -172,35 +176,40 @@ const Timeline = () => {
|
||||
<Icon.Circle className="w-2 h-auto bg-gray-200 text-gray-200 dark:bg-gray-700 dark:text-gray-700 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{index !== groupedByMonth.length - 1 && <Divider className="w-full !my-4 md:!mb-8 !bg-gray-100 dark:!bg-zinc-700" />}
|
||||
</Fragment>
|
||||
))}
|
||||
{isRequesting ? (
|
||||
<div className="flex flex-row justify-center items-center w-full my-4 text-gray-400">
|
||||
<Icon.Loader className="w-4 h-auto animate-spin mr-1" />
|
||||
<p className="text-sm italic">{t("memo.fetching-data")}</p>
|
||||
</div>
|
||||
{index !== groupedByMonth.length - 1 && <Divider className="w-full !my-4 md:!mb-8 !bg-gray-100 dark:!bg-zinc-700" />}
|
||||
</Fragment>
|
||||
))}
|
||||
{isRequesting ? (
|
||||
<div className="flex flex-row justify-center items-center w-full my-4 text-gray-400">
|
||||
<Icon.Loader className="w-4 h-auto animate-spin mr-1" />
|
||||
<p className="text-sm italic">{t("memo.fetching-data")}</p>
|
||||
</div>
|
||||
) : !nextPageTokenRef.current ? (
|
||||
sortedMemos.length === 0 && (
|
||||
<div className="w-full mt-12 mb-8 flex flex-col justify-center items-center italic">
|
||||
<Empty />
|
||||
<p className="mt-2 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p>
|
||||
) : !nextPageTokenRef.current ? (
|
||||
sortedMemos.length === 0 && (
|
||||
<div className="w-full mt-12 mb-8 flex flex-col justify-center items-center italic">
|
||||
<Empty />
|
||||
<p className="mt-2 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="w-full flex flex-row justify-center items-center my-4">
|
||||
<Button variant="plain" endDecorator={<Icon.ArrowDown className="w-5 h-auto" />} onClick={fetchMemos}>
|
||||
{t("memo.fetch-more")}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="w-full flex flex-row justify-center items-center my-4">
|
||||
<Button variant="plain" endDecorator={<Icon.ArrowDown className="w-5 h-auto" />} onClick={fetchMemos}>
|
||||
{t("memo.fetch-more")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{md && (
|
||||
<div className="sticky top-0 left-0 shrink-0 -mt-6 w-56 h-full">
|
||||
<TimelineSidebar className="py-6" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
@@ -2,7 +2,7 @@ import react from "@vitejs/plugin-react";
|
||||
import { resolve } from "path";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
let devProxyServer = "http://localhost:8081/";
|
||||
let devProxyServer = "http://localhost:8081";
|
||||
if (process.env.DEV_PROXY_SERVER && process.env.DEV_PROXY_SERVER.length > 0) {
|
||||
console.log("Use devProxyServer from environment: ", process.env.DEV_PROXY_SERVER);
|
||||
devProxyServer = process.env.DEV_PROXY_SERVER;
|
||||
|
Reference in New Issue
Block a user