diff --git a/apps/renderer/src/atoms/settings/ui.ts b/apps/renderer/src/atoms/settings/ui.ts index 8340f53eb..8a0fb1e25 100644 --- a/apps/renderer/src/atoms/settings/ui.ts +++ b/apps/renderer/src/atoms/settings/ui.ts @@ -19,6 +19,7 @@ export const createDefaultSettings = (): UISettings => ({ modalDraggable: true, modalOpaque: true, reduceMotion: false, + usePointerCursor: false, // Font uiFontFamily: "SN Pro", diff --git a/apps/renderer/src/components/common/ShadowDOM.tsx b/apps/renderer/src/components/common/ShadowDOM.tsx index 80135fd9e..09884fc68 100644 --- a/apps/renderer/src/components/common/ShadowDOM.tsx +++ b/apps/renderer/src/components/common/ShadowDOM.tsx @@ -103,6 +103,7 @@ export const ShadowDOM: FC>> & { const uiFont = useUISettingKey("uiFontFamily") const reduceMotion = useReduceMotion() + const usePointerCursor = useUISettingKey("usePointerCursor") return ( @@ -111,8 +112,9 @@ export const ShadowDOM: FC>> & { style={useMemo( () => ({ fontFamily: `${uiFont},"SN Pro", system-ui, sans-serif`, + "--pointer": usePointerCursor ? "pointer" : "default", }), - [uiFont], + [uiFont, usePointerCursor], )} id="shadow-html" data-motion-reduce={reduceMotion} diff --git a/apps/renderer/src/components/ui/button/variants.tsx b/apps/renderer/src/components/ui/button/variants.tsx index c7f99ccef..d87f3ca1f 100644 --- a/apps/renderer/src/components/ui/button/variants.tsx +++ b/apps/renderer/src/components/ui/button/variants.tsx @@ -7,7 +7,7 @@ import { cn } from "~/lib/utils" export const styledButtonVariant = cva( [ - "inline-flex cursor-default select-none items-center justify-center gap-2 outline-offset-2 transition active:transition-none disabled:cursor-not-allowed", + "inline-flex cursor-button select-none items-center justify-center gap-2 outline-offset-2 transition active:transition-none disabled:cursor-not-allowed", "ring-accent/20 duration-200 disabled:ring-0", "align-middle", ], diff --git a/apps/renderer/src/components/ui/context-menu/context-menu.tsx b/apps/renderer/src/components/ui/context-menu/context-menu.tsx index e6c58492f..8ce004024 100644 --- a/apps/renderer/src/components/ui/context-menu/context-menu.tsx +++ b/apps/renderer/src/components/ui/context-menu/context-menu.tsx @@ -24,7 +24,7 @@ const ContextMenuSubTrigger = React.forwardRef< = ({ onError={errorHandle} className={cn( !(props.width || props.height) && "size-full", - "bg-gray-200 object-cover duration-200 dark:bg-neutral-800", + "cursor-card bg-gray-200 object-cover duration-200 dark:bg-neutral-800", popper && "cursor-zoom-in", mediaLoadState === "loaded" ? "opacity-100" : "opacity-0", @@ -126,7 +126,7 @@ const MediaImpl: FC = ({ className={cn( "center", !(props.width || props.height) && "size-full", - "relative bg-stone-100 object-cover", + "relative cursor-card bg-stone-100 object-cover", mediaContainerClassName, )} onClick={handleClick} diff --git a/apps/renderer/src/components/ui/media/SwipeMedia.tsx b/apps/renderer/src/components/ui/media/SwipeMedia.tsx index bcac8d084..da50c2da3 100644 --- a/apps/renderer/src/components/ui/media/SwipeMedia.tsx +++ b/apps/renderer/src/components/ui/media/SwipeMedia.tsx @@ -59,7 +59,7 @@ export function SwipeMedia({
(({ className, ...props }, ref) => ( @@ -54,7 +55,7 @@ const SelectScrollDownButton = React.forwardRef< >(({ className, ...props }, ref) => ( @@ -70,9 +71,11 @@ const SelectContent = React.forwardRef< (({ className, ...props }, ref) => ( - + )) Switch.displayName = SwitchPrimitives.Root.displayName diff --git a/apps/renderer/src/initialize/index.ts b/apps/renderer/src/initialize/index.ts index b978e7328..df82fada9 100644 --- a/apps/renderer/src/initialize/index.ts +++ b/apps/renderer/src/initialize/index.ts @@ -7,6 +7,8 @@ import duration from "dayjs/plugin/duration" import localizedFormat from "dayjs/plugin/localizedFormat" import relativeTime from "dayjs/plugin/relativeTime" import { enableMapSet } from "immer" +import React from "react" +import ReactDOM from "react-dom" import { toast } from "sonner" import { getUISettings } from "~/atoms/settings/ui" @@ -119,6 +121,10 @@ export const initializeApp = async () => { data_hydrated_time: dataHydratedTime, version: APP_VERSION, }) + + // expose `React` `ReactDOM` to global, it's easier for developers to make plugins + window.React = React + window.ReactDOM = ReactDOM } import.meta.hot?.dispose(cleanup) diff --git a/apps/renderer/src/modules/ai/ai-daily/daily.tsx b/apps/renderer/src/modules/ai/ai-daily/daily.tsx index 1caa5993d..e22a099d0 100644 --- a/apps/renderer/src/modules/ai/ai-daily/daily.tsx +++ b/apps/renderer/src/modules/ai/ai-daily/daily.tsx @@ -65,7 +65,7 @@ export const DailyReportTitle = ({
{t("ai_daily.title", { title })}
- + diff --git a/apps/renderer/src/modules/discover/recommendations-card.tsx b/apps/renderer/src/modules/discover/recommendations-card.tsx index 9ea155fb7..fde564d04 100644 --- a/apps/renderer/src/modules/discover/recommendations-card.tsx +++ b/apps/renderer/src/modules/discover/recommendations-card.tsx @@ -25,20 +25,21 @@ export const RecommendationCard: FC = memo(({ data, rou
    {Object.keys(data.routes).map((route) => ( -
  • { - present({ - content: () => ( - - ), - icon: , - title: `${data.name} - ${data.routes[route].name}`, - }) - }} - > - {data.routes[route].name} +
  • +
  • ))}
diff --git a/apps/renderer/src/modules/entry-column/Items/video-item.tsx b/apps/renderer/src/modules/entry-column/Items/video-item.tsx index 314058079..943ccf66d 100644 --- a/apps/renderer/src/modules/entry-column/Items/video-item.tsx +++ b/apps/renderer/src/modules/entry-column/Items/video-item.tsx @@ -58,7 +58,7 @@ export function VideoItem({ entryId, entryPreview, translation }: UniversalItemP return (
{ if (iframeSrc) { modalStack.present({ diff --git a/apps/renderer/src/modules/entry-column/templates/list-item-template.tsx b/apps/renderer/src/modules/entry-column/templates/list-item-template.tsx index b55d3f0b6..4ab6ecf43 100644 --- a/apps/renderer/src/modules/entry-column/templates/list-item-template.tsx +++ b/apps/renderer/src/modules/entry-column/templates/list-item-template.tsx @@ -68,7 +68,7 @@ export function ListItem({ onMouseEnter={handlePrefetchEntry} onMouseLeave={handlePrefetchEntry.cancel} className={cn( - "group relative flex py-4 pl-3 pr-2", + "group relative flex cursor-menu py-4 pl-3 pr-2", !asRead && "before:absolute before:-left-0.5 before:top-[1.4375rem] before:block before:size-2 before:rounded-full before:bg-accent", )} diff --git a/apps/renderer/src/modules/entry-content/index.tsx b/apps/renderer/src/modules/entry-content/index.tsx index 8987863c5..0d34683a9 100644 --- a/apps/renderer/src/modules/entry-content/index.tsx +++ b/apps/renderer/src/modules/entry-content/index.tsx @@ -219,7 +219,7 @@ export const EntryContentRender: Component<{ entryId: string }> = ({ entryId, cl
diff --git a/apps/renderer/src/modules/feed-column/category.tsx b/apps/renderer/src/modules/feed-column/category.tsx index 439e8366c..b64032676 100644 --- a/apps/renderer/src/modules/feed-column/category.tsx +++ b/apps/renderer/src/modules/feed-column/category.tsx @@ -120,7 +120,7 @@ function FeedCategoryImpl({ {!!showCollapse && (
{ diff --git a/apps/renderer/src/modules/feed-column/header.tsx b/apps/renderer/src/modules/feed-column/header.tsx index 3f42dcdee..e2a87dddf 100644 --- a/apps/renderer/src/modules/feed-column/header.tsx +++ b/apps/renderer/src/modules/feed-column/header.tsx @@ -126,7 +126,7 @@ const LogoContextMenu: FC = ({ children }) => { toast.success(t.common("app.copied_to_clipboard")) }} className={cn( - "relative flex cursor-default select-none items-center rounded-sm px-1 py-0.5 text-sm outline-none", + "relative flex cursor-menu select-none items-center rounded-sm px-1 py-0.5 text-sm outline-none", "focus-within:outline-transparent hover:bg-theme-item-hover dark:hover:bg-neutral-800", "gap-2 text-foreground/80 [&_svg]:size-3", )} diff --git a/apps/renderer/src/modules/feed-column/item.tsx b/apps/renderer/src/modules/feed-column/item.tsx index 20e74c5d2..2fd1bec89 100644 --- a/apps/renderer/src/modules/feed-column/item.tsx +++ b/apps/renderer/src/modules/feed-column/item.tsx @@ -68,7 +68,7 @@ const FeedItemImpl = ({ view, feedId, className, showUnreadCount = true }: FeedI
{ @@ -140,7 +140,7 @@ function FeedListImpl({ className, view }: { className?: string; view: number })
diff --git a/apps/renderer/src/modules/profile/user-profile-modal.tsx b/apps/renderer/src/modules/profile/user-profile-modal.tsx index 5cefbbff0..7571cbdbb 100644 --- a/apps/renderer/src/modules/profile/user-profile-modal.tsx +++ b/apps/renderer/src/modules/profile/user-profile-modal.tsx @@ -373,7 +373,7 @@ const SubscriptionItem: FC<{ data-feed-id={subscription.feedId} > diff --git a/apps/renderer/src/modules/settings/control.tsx b/apps/renderer/src/modules/settings/control.tsx index dfdb28ebc..c991cc80e 100644 --- a/apps/renderer/src/modules/settings/control.tsx +++ b/apps/renderer/src/modules/settings/control.tsx @@ -37,7 +37,7 @@ export const SettingSwitch: Component<{ return (
- +
) } diff --git a/apps/renderer/src/modules/settings/tabs/apperance.tsx b/apps/renderer/src/modules/settings/tabs/apperance.tsx index 278282c80..484a497db 100644 --- a/apps/renderer/src/modules/settings/tabs/apperance.tsx +++ b/apps/renderer/src/modules/settings/tabs/apperance.tsx @@ -93,6 +93,10 @@ export const SettingAppearance = () => { label: t("appearance.reduce_motion.label"), description: t("appearance.reduce_motion.description"), }), + defineItem("usePointerCursor", { + label: t("appearance.use_pointer_cursor.label"), + description: t("appearance.use_pointer_cursor.description"), + }), ]} />
diff --git a/apps/renderer/src/modules/settings/tabs/wallet/transaction-section/index.tsx b/apps/renderer/src/modules/settings/tabs/wallet/transaction-section/index.tsx index 116ef3100..42f0fd9d8 100644 --- a/apps/renderer/src/modules/settings/tabs/wallet/transaction-section/index.tsx +++ b/apps/renderer/src/modules/settings/tabs/wallet/transaction-section/index.tsx @@ -174,7 +174,7 @@ const UserRenderer = ({ onClick={() => { if (user?.id) presentUserModal(user.id) }} - className={cn("flex items-center", user?.id ? "cursor-pointer" : "cursor-default")} + className="flex cursor-button items-center" > {name === APP_NAME ? ( diff --git a/apps/renderer/src/pages/(external)/(with-layout)/profile/[id]/index.tsx b/apps/renderer/src/pages/(external)/(with-layout)/profile/[id]/index.tsx index 498edaaf3..8a841e8ef 100644 --- a/apps/renderer/src/pages/(external)/(with-layout)/profile/[id]/index.tsx +++ b/apps/renderer/src/pages/(external)/(with-layout)/profile/[id]/index.tsx @@ -73,7 +73,7 @@ export function Component() { {subscriptions.data?.[category].map((subscription) => (
diff --git a/apps/renderer/src/pages/(main)/layout.tsx b/apps/renderer/src/pages/(main)/layout.tsx index 8efb3272f..e0e65b87e 100644 --- a/apps/renderer/src/pages/(main)/layout.tsx +++ b/apps/renderer/src/pages/(main)/layout.tsx @@ -53,7 +53,7 @@ const FooterInfo = () => { onClick={() => { window.open(`${repository.url}/releases`) }} - className="center cursor-pointer rounded-full border bg-background p-1.5 shadow-sm" + className="center rounded-full border bg-background p-1.5 shadow-sm" > diff --git a/apps/renderer/src/pages/settings/layout.tsx b/apps/renderer/src/pages/settings/layout.tsx index f01ca46b9..f0353b163 100644 --- a/apps/renderer/src/pages/settings/layout.tsx +++ b/apps/renderer/src/pages/settings/layout.tsx @@ -19,7 +19,7 @@ function Layout() { {settings.map((t) => ( { fontFamily: fontCss, }) root.style.cssText += `\n--fo-font-family: ${fontCss}` + root.style.cssText += `\n--pointer: ${setting.usePointerCursor ? "pointer" : "default"}` Object.assign(document.body.style, { fontFamily: fontCss, }) - }, [setting.uiFontFamily]) + }, [setting.uiFontFamily, setting.usePointerCursor]) useEffect(() => { if (setting.showDockBadge) { diff --git a/apps/renderer/src/styles/base.css b/apps/renderer/src/styles/base.css index 00db7ae55..b9fa4870e 100644 --- a/apps/renderer/src/styles/base.css +++ b/apps/renderer/src/styles/base.css @@ -21,7 +21,7 @@ body, } button { - @apply cursor-default; + cursor: var(--pointer); } .prose a { diff --git a/apps/renderer/src/styles/cursor.css b/apps/renderer/src/styles/cursor.css new file mode 100644 index 000000000..44600e474 --- /dev/null +++ b/apps/renderer/src/styles/cursor.css @@ -0,0 +1,11 @@ +html, +#shadow-html { + --cursor-button: var(--pointer); + --cursor-select: var(--pointer); + --cursor-checkbox: var(--pointer); + --cursor-link: var(--pointer); + --cursor-menu: default; + --cursor-radio: var(--pointer); + --cursor-switch: var(--pointer); + --cursor-card: var(--pointer); +} diff --git a/apps/renderer/src/styles/main.css b/apps/renderer/src/styles/main.css index c2bd55c4e..92f1866ce 100644 --- a/apps/renderer/src/styles/main.css +++ b/apps/renderer/src/styles/main.css @@ -4,3 +4,4 @@ @import "./colors.css"; @import "./additional.css"; @import "./scrollbar.css"; +@import "./cursor.css"; diff --git a/locales/settings/en.json b/locales/settings/en.json index 33937f27a..3f9fa74aa 100644 --- a/locales/settings/en.json +++ b/locales/settings/en.json @@ -73,6 +73,8 @@ "appearance.title": "Appearance", "appearance.ui_font": "UI Font", "appearance.unread_count": "Unread count", + "appearance.use_pointer_cursor.description": "Change the cursor to a pointer when hovering over any interactive element.", + "appearance.use_pointer_cursor.label": "Use pointer cursors", "common.give_star": "Love our product? Give us a star on GitHub!", "feeds.claimTips": "To claim your feeds and receive tips, right-click on the feed in your subscription list and select Claim.", "feeds.noFeeds": "No claimed feeds", diff --git a/locales/settings/zh-CN.json b/locales/settings/zh-CN.json index dbfa3efb1..1d83e906f 100644 --- a/locales/settings/zh-CN.json +++ b/locales/settings/zh-CN.json @@ -73,6 +73,8 @@ "appearance.title": "外观", "appearance.ui_font": "界面字体", "appearance.unread_count": "未读数", + "appearance.use_pointer_cursor.description": "悬停在任何可交互元素上时将光标更改为指针。", + "appearance.use_pointer_cursor.label": "使用指针光标", "common.give_star": "喜欢我们的产品吗? 在 GitHub 上给我们 star 吧!", "feeds.claimTips": "要认证你的订阅源并接收打赏,请在订阅列表中右键点击订阅源并选择“认证”。", "feeds.noFeeds": "没有已认证的订阅源", diff --git a/packages/shared/src/interface/settings.ts b/packages/shared/src/interface/settings.ts index e2094493b..2f4496125 100644 --- a/packages/shared/src/interface/settings.ts +++ b/packages/shared/src/interface/settings.ts @@ -22,6 +22,7 @@ export interface UISettings { modalDraggable: boolean modalOpaque: boolean reduceMotion: boolean + usePointerCursor: boolean | null uiFontFamily: string readerFontFamily: string readerRenderInlineStyle: boolean diff --git a/tailwind.config.ts b/tailwind.config.ts index 530668396..14526a5ad 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -27,6 +27,16 @@ export default resolveConfig({ theme: "var(--fo-font-family)", default: "SN pro, sans-serif, system-ui", }, + cursor: { + button: "var(--cursor-button)", + select: "var(--cursor-select)", + checkbox: "var(--cursor-checkbox)", + link: "var(--cursor-link)", + menu: "var(--cursor-menu)", + radio: "var(--cursor-radio)", + switch: "var(--cursor-switch)", + card: "var(--cursor-card)", + }, colors: { border: "hsl(var(--border) / )", background: "hsl(var(--background) / )",