From e5b0583d38e41d81663bec1ee16e0c24cb385d7a Mon Sep 17 00:00:00 2001 From: Innei Date: Wed, 21 Aug 2024 16:24:27 +0800 Subject: [PATCH] feat: new design for mark read in date item Signed-off-by: Innei --- .../src/components/ui/datetime/index.tsx | 2 +- .../components/ui/popover/index.module.css | 19 ++++ .../ui/{popover.tsx => popover/index.tsx} | 6 +- .../src/components/ux/transition/icon.tsx | 54 ++++++---- src/renderer/src/lib/native-menu.ts | 13 ++- .../entry-column/components/DateItem.tsx | 98 +++++++++++++++---- .../components/mark-all-button.tsx | 60 +++--------- .../modules/entry-column/hooks/useMarkAll.ts | 33 +++++++ .../src/modules/feed-column/index.tsx | 3 +- src/renderer/src/styles/tailwind-extend.css | 18 ++++ tailwind.config.ts | 1 + 11 files changed, 217 insertions(+), 90 deletions(-) create mode 100644 src/renderer/src/components/ui/popover/index.module.css rename src/renderer/src/components/ui/{popover.tsx => popover/index.tsx} (65%) create mode 100644 src/renderer/src/modules/entry-column/hooks/useMarkAll.ts diff --git a/src/renderer/src/components/ui/datetime/index.tsx b/src/renderer/src/components/ui/datetime/index.tsx index 7372a54ea..2f8d9d587 100644 --- a/src/renderer/src/components/ui/datetime/index.tsx +++ b/src/renderer/src/components/ui/datetime/index.tsx @@ -137,7 +137,7 @@ export const RelativeDay = ({ date }: { date: Date }) => { return ( - {dateString} + {dateString} {dayjs(date).format("llll")} diff --git a/src/renderer/src/components/ui/popover/index.module.css b/src/renderer/src/components/ui/popover/index.module.css new file mode 100644 index 000000000..c48885e9c --- /dev/null +++ b/src/renderer/src/components/ui/popover/index.module.css @@ -0,0 +1,19 @@ +.popover { + border: 0.5px solid rgb(216, 216, 216); + box-shadow: rgba(0, 0, 0, 0.067) 0px 3px 8px, rgba(0, 0, 0, 0.067) 0px 2px 5px, + rgba(0, 0, 0, 0.067) 0px 1px 1px; + border-radius: 6px; + + @apply bg-background z-50 outline-none; +} + +[data-theme="dark"] .popover { + box-shadow: rgba(0, 0, 0, 0.184) 0px 3px 8px, rgba(0, 0, 0, 0.184) 0px 2px 5px, + rgba(0, 0, 0, 0.184) 0px 1px 1px; + background-color: rgb(27, 28, 31); + border: 0.5px solid rgb(56, 59, 65); +} + +.popover__padding--base { + padding: 8px 16px; +} diff --git a/src/renderer/src/components/ui/popover.tsx b/src/renderer/src/components/ui/popover/index.tsx similarity index 65% rename from src/renderer/src/components/ui/popover.tsx rename to src/renderer/src/components/ui/popover/index.tsx index eed2cf94d..7e25d28ee 100644 --- a/src/renderer/src/components/ui/popover.tsx +++ b/src/renderer/src/components/ui/popover/index.tsx @@ -2,6 +2,8 @@ import * as PopoverPrimitive from "@radix-ui/react-popover" import { cn } from "@renderer/lib/utils" import * as React from "react" +import styles from "./index.module.css" + const Popover = PopoverPrimitive.Root const PopoverTrigger = PopoverPrimitive.Trigger @@ -18,7 +20,9 @@ const PopoverContent = React.forwardRef< align={align} sideOffset={sideOffset} className={cn( - "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", + styles.popover, + styles["popover__padding--base"], + "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className, )} {...props} diff --git a/src/renderer/src/components/ux/transition/icon.tsx b/src/renderer/src/components/ux/transition/icon.tsx index 6243367ee..09e513692 100644 --- a/src/renderer/src/components/ux/transition/icon.tsx +++ b/src/renderer/src/components/ux/transition/icon.tsx @@ -1,4 +1,5 @@ import { AnimatePresence, m } from "framer-motion" +import { useEffect, useState } from "react" export const IconScaleTransition = ({ icon1, @@ -9,24 +10,35 @@ export const IconScaleTransition = ({ icon1: string icon2: string -}) => ( - - {status === "init" ? ( - - ) : ( - - )} - -) +}) => { + const [isMount, isMounted] = useState(false) + useEffect(() => { + isMounted(true) + return () => { + isMounted(false) + } + }, []) + + const initial = isMount ? { scale: 0 } : true + return ( + + {status === "init" ? ( + + ) : ( + + )} + + ) +} diff --git a/src/renderer/src/lib/native-menu.ts b/src/renderer/src/lib/native-menu.ts index 98e2dd187..d05b08b2b 100644 --- a/src/renderer/src/lib/native-menu.ts +++ b/src/renderer/src/lib/native-menu.ts @@ -17,12 +17,17 @@ export type NativeMenuItem = | { type: "separator", disabled?: boolean } function sortShortcutsString(shortcut: string) { - // always order by Shift, Ctrl, Alt, Meta - const arr = shortcut.split("+") const order = ["Shift", "Ctrl", "Alt", "Meta"] - arr.sort((a, b) => order.indexOf(a) - order.indexOf(b)) - return arr.join("+") + const arr = shortcut.split("+") + + const sortedModifiers = arr + .filter((key) => order.includes(key)) + .sort((a, b) => order.indexOf(a) - order.indexOf(b)) + + const otherKeys = arr.filter((key) => !order.includes(key)) + + return [...sortedModifiers, ...otherKeys].join("+") } export const showNativeMenu = async ( items: Array>, diff --git a/src/renderer/src/modules/entry-column/components/DateItem.tsx b/src/renderer/src/modules/entry-column/components/DateItem.tsx index f8fb71af1..841fe22b9 100644 --- a/src/renderer/src/modules/entry-column/components/DateItem.tsx +++ b/src/renderer/src/modules/entry-column/components/DateItem.tsx @@ -1,20 +1,30 @@ +import { ActionButton } from "@renderer/components/ui/button" import { RelativeDay } from "@renderer/components/ui/datetime" import { useScrollViewElement } from "@renderer/components/ui/scroll-area/hooks" +import { IconScaleTransition } from "@renderer/components/ux/transition/icon" import { FeedViewType } from "@renderer/lib/enum" import { cn } from "@renderer/lib/utils" import { throttle } from "lodash-es" -import { memo, useLayoutEffect, useMemo, useRef, useState } from "react" +import { + memo, + useLayoutEffect, + useMemo, + useRef, + useState, +} from "react" +import { useMarkAll } from "../hooks/useMarkAll" import { MarkAllReadButton } from "./mark-all-button" -const useParseDate = (date: string) => useMemo(() => { - const dateObj = new Date(date) - return { - dateObj, - startOfDay: new Date(dateObj.setHours(0, 0, 0, 0)).getTime(), - endOfDay: new Date(dateObj.setHours(23, 59, 59, 999)).getTime(), - } -}, [date]) +const useParseDate = (date: string) => + useMemo(() => { + const dateObj = new Date(date) + return { + dateObj, + startOfDay: new Date(dateObj.setHours(0, 0, 0, 0)).getTime(), + endOfDay: new Date(dateObj.setHours(23, 59, 59, 999)).getTime(), + } + }, [date]) const useSticky = () => { const $scroller = useScrollViewElement() @@ -64,18 +74,70 @@ const UniversalDateItem = ({ const { startOfDay, endOfDay, dateObj } = useParseDate(date) const { isSticky, itemRef } = useSticky() + const RelativeElement = + + const handleMarkAllAsRead = useMarkAll({ + startTime: startOfDay, + endTime: endOfDay, + }) + + const [confirmMark, setConfirmMark] = useState(false) + + const timerRef = useRef() + return ( -
- { + clearTimeout(timerRef.current) + }} + onMouseLeave={() => { + timerRef.current = setTimeout(() => { + setConfirmMark(false) + }, 1000) + }} + > + + Mark + + {RelativeElement} + + as read + + )} + onClick={() => { + if (confirmMark) { + clearTimeout(timerRef.current) + handleMarkAllAsRead() + setConfirmMark(false) + } else { + setConfirmMark(true) + } }} - /> - {RelativeElement} + className="size-7 text-base" + > + + + + {confirmMark ? ( +
+ Mark + + {RelativeElement} + {" "} + as read? +
+ ) : ( + RelativeElement + )}
) } diff --git a/src/renderer/src/modules/entry-column/components/mark-all-button.tsx b/src/renderer/src/modules/entry-column/components/mark-all-button.tsx index 45cb30108..4b0d20a3c 100644 --- a/src/renderer/src/modules/entry-column/components/mark-all-button.tsx +++ b/src/renderer/src/modules/entry-column/components/mark-all-button.tsx @@ -1,8 +1,5 @@ import { PopoverPortal } from "@radix-ui/react-popover" -import { - ActionButton, - Button, -} from "@renderer/components/ui/button" +import { ActionButton, Button } from "@renderer/components/ui/button" import { Popover, PopoverClose, @@ -10,21 +7,16 @@ import { PopoverTrigger, } from "@renderer/components/ui/popover" import { shortcuts } from "@renderer/constants/shortcuts" -import { useRouteParms } from "@renderer/hooks/biz/useRouteParams" import { cn } from "@renderer/lib/utils" -import { - subscriptionActions, - useFolderFeedsByFeedId, -} from "@renderer/store/subscription" import { AnimatePresence, m } from "framer-motion" import type { FC, ReactNode } from "react" -import { forwardRef, useCallback, useState } from "react" +import { forwardRef, useState } from "react" + +import type { MarkAllFilter } from "../hooks/useMarkAll" +import { useMarkAll } from "../hooks/useMarkAll" interface MarkAllButtonProps { - filter?: { - startTime: number - endTime: number - } + filter?: MarkAllFilter className?: string which?: ReactNode @@ -46,8 +38,9 @@ export const MarkAllReadButton = forwardRef< tooltip={( Mark + {which} - {" "} + as read )} @@ -58,19 +51,23 @@ export const MarkAllReadButton = forwardRef< - -
+ +
Mark + {which} - {" "} + as read?
- + ) } - -const useMarkAll = (filter: MarkAllButtonProps["filter"]) => { - const routerParams = useRouteParms() - const { feedId, view } = routerParams - const folderIds = useFolderFeedsByFeedId({ - feedId, - view, - }) - - return useCallback(async () => { - if (!routerParams) return - - if (typeof routerParams.feedId === "number" || routerParams.isAllFeeds) { - subscriptionActions.markReadByView(view, filter) - } else if (folderIds) { - subscriptionActions.markReadByFeedIds(folderIds, view, filter) - } else if (routerParams.feedId) { - subscriptionActions.markReadByFeedIds( - routerParams.feedId?.split(","), - view, - filter, - ) - } - }, [routerParams, folderIds, view, filter]) -} diff --git a/src/renderer/src/modules/entry-column/hooks/useMarkAll.ts b/src/renderer/src/modules/entry-column/hooks/useMarkAll.ts new file mode 100644 index 000000000..7f8689b69 --- /dev/null +++ b/src/renderer/src/modules/entry-column/hooks/useMarkAll.ts @@ -0,0 +1,33 @@ +import { useRouteParms } from "@renderer/hooks/biz/useRouteParams" +import { subscriptionActions } from "@renderer/store/subscription" +import { useFolderFeedsByFeedId } from "@renderer/store/subscription/hooks" +import { useCallback } from "react" + +export interface MarkAllFilter { + startTime: number + endTime: number +} +export const useMarkAll = (filter?: MarkAllFilter) => { + const routerParams = useRouteParms() + const { feedId, view } = routerParams + const folderIds = useFolderFeedsByFeedId({ + feedId, + view, + }) + + return useCallback(async () => { + if (!routerParams) return + + if (typeof routerParams.feedId === "number" || routerParams.isAllFeeds) { + subscriptionActions.markReadByView(view, filter) + } else if (folderIds) { + subscriptionActions.markReadByFeedIds(folderIds, view, filter) + } else if (routerParams.feedId) { + subscriptionActions.markReadByFeedIds( + routerParams.feedId?.split(","), + view, + filter, + ) + } + }, [routerParams, folderIds, view, filter]) +} diff --git a/src/renderer/src/modules/feed-column/index.tsx b/src/renderer/src/modules/feed-column/index.tsx index e5cad6950..df4b37f93 100644 --- a/src/renderer/src/modules/feed-column/index.tsx +++ b/src/renderer/src/modules/feed-column/index.tsx @@ -191,13 +191,14 @@ export function FeedColumn({ children }: PropsWithChildren) { > {normalStyle && (
{ e.stopPropagation() navigateBackHome() }} > + {APP_NAME}
)} diff --git a/src/renderer/src/styles/tailwind-extend.css b/src/renderer/src/styles/tailwind-extend.css index 29898fc0b..c1d7d7f9a 100644 --- a/src/renderer/src/styles/tailwind-extend.css +++ b/src/renderer/src/styles/tailwind-extend.css @@ -280,3 +280,21 @@ box-shadow: -12px 0px 17px 2px rgba(0, 0, 0, 0.653); } } + +@layer components { + .animate-mask-in { + animation: mask-in 0.5s ease-in-out forwards; + } + @keyframes mask-in { + 0% { + mask: linear-gradient(90deg, #000 25%, #000000e6 50%, #00000000) 150% 0 / + 400% no-repeat; + opacity: 0.2; + } + 100% { + mask: linear-gradient(90deg, #000 25%, #000000e6 50%, #00000000) 0 / 400% + no-repeat; + opacity: 1; + } + } +} diff --git a/tailwind.config.ts b/tailwind.config.ts index d5835a872..327fa6382 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -30,6 +30,7 @@ export default resolveConfig({ extend: { fontFamily: { theme: "var(--fo-font-family)", + default: "SN pro, sans-serif, system-ui", }, colors: { border: "hsl(var(--border) / )",